From cb1e7e9d493733eed6e5b6c96b4e4ee1323f3647 Mon Sep 17 00:00:00 2001 From: Dzmitry Neviadomski Date: Sun, 7 Feb 2021 04:48:04 +0300 Subject: [PATCH] Roll TagLib 1.12.0 --- .../TagLib/TagLib.xcodeproj/project.pbxproj | 566 +++++- .../TagLib/taglib/3rdparty/utf8-cpp/checked.h | 327 ++++ .../TagLib/taglib/3rdparty/utf8-cpp/core.h | 332 ++++ Frameworks/TagLib/taglib/AUTHORS | 10 + Frameworks/TagLib/taglib/CMakeLists.txt | 168 ++ Frameworks/TagLib/taglib/COPYING.MPL | 470 +++++ .../TagLib/taglib/ConfigureChecks.cmake | 203 +++ Frameworks/TagLib/taglib/Doxyfile.cmake | 210 +++ Frameworks/TagLib/taglib/INSTALL.md | 175 ++ Frameworks/TagLib/taglib/NEWS | 335 ++++ Frameworks/TagLib/taglib/README.md | 26 + .../TagLib/taglib/bindings/CMakeLists.txt | 1 + Frameworks/TagLib/taglib/bindings/README | 6 + .../TagLib/taglib/bindings/c/CMakeLists.txt | 75 + Frameworks/TagLib/taglib/bindings/c/tag_c.cpp | 315 ++++ Frameworks/TagLib/taglib/bindings/c/tag_c.h | 299 +++ .../taglib/bindings/c/taglib_c.pc.cmake | 12 + .../taglib/cmake/modules/FindCppUnit.cmake | 69 + .../cmake/modules/MacroEnsureVersion.cmake | 71 + .../TagLib/taglib/cmake_uninstall.cmake.in | 21 + Frameworks/TagLib/taglib/config.h | 39 +- Frameworks/TagLib/taglib/config.h.cmake | 34 + Frameworks/TagLib/taglib/doc/README | 1 + Frameworks/TagLib/taglib/doc/api-footer.html | 4 + Frameworks/TagLib/taglib/doc/api-header.html | 41 + Frameworks/TagLib/taglib/doc/taglib-api.css | 395 ++++ Frameworks/TagLib/taglib/doc/taglib.png | Bin 0 -> 2748 bytes .../TagLib/taglib/examples/CMakeLists.txt | 40 + .../TagLib/taglib/examples/framelist.cpp | 117 ++ .../TagLib/taglib/examples/strip-id3v1.cpp | 40 + .../TagLib/taglib/examples/tagreader.cpp | 89 + .../TagLib/taglib/examples/tagreader_c.c | 81 + .../TagLib/taglib/examples/tagwriter.cpp | 185 ++ Frameworks/TagLib/taglib/taglib-config.cmake | 55 + .../TagLib/taglib/taglib-config.cmd.cmake | 36 + Frameworks/TagLib/taglib/taglib.pc.cmake | 11 + .../TagLib/taglib/taglib/CMakeLists.txt | 372 ++++ .../taglib/taglib/ape/ape-tag-format.txt | 4 +- .../TagLib/taglib/taglib/ape/apefile.cpp | 258 +-- Frameworks/TagLib/taglib/taglib/ape/apefile.h | 94 +- .../TagLib/taglib/taglib/ape/apefooter.cpp | 92 +- .../TagLib/taglib/taglib/ape/apefooter.h | 16 +- .../TagLib/taglib/taglib/ape/apeitem.cpp | 125 +- Frameworks/TagLib/taglib/taglib/ape/apeitem.h | 54 +- .../taglib/taglib/ape/apeproperties.cpp | 200 +- .../TagLib/taglib/taglib/ape/apeproperties.h | 67 +- .../TagLib/taglib/taglib/ape/apetag.cpp | 304 +++- Frameworks/TagLib/taglib/taglib/ape/apetag.h | 60 +- .../TagLib/taglib/taglib/asf/asfattribute.cpp | 203 +-- .../TagLib/taglib/taglib/asf/asfattribute.h | 9 +- .../TagLib/taglib/taglib/asf/asffile.cpp | 614 ++++--- Frameworks/TagLib/taglib/taglib/asf/asffile.h | 70 +- .../TagLib/taglib/taglib/asf/asfpicture.cpp | 44 +- .../TagLib/taglib/taglib/asf/asfpicture.h | 11 +- .../taglib/taglib/asf/asfproperties.cpp | 129 +- .../TagLib/taglib/taglib/asf/asfproperties.h | 120 +- .../TagLib/taglib/taglib/asf/asftag.cpp | 265 ++- Frameworks/TagLib/taglib/taglib/asf/asftag.h | 56 +- .../TagLib/taglib/taglib/asf/asfutils.h | 104 ++ .../TagLib/taglib/taglib/audioproperties.cpp | 62 +- .../TagLib/taglib/taglib/audioproperties.h | 19 +- Frameworks/TagLib/taglib/taglib/fileref.cpp | 429 +++-- Frameworks/TagLib/taglib/taglib/fileref.h | 34 +- .../TagLib/taglib/taglib/flac/flacfile.cpp | 542 +++--- .../TagLib/taglib/taglib/flac/flacfile.h | 175 +- .../taglib/taglib/flac/flacmetadatablock.cpp | 6 +- .../TagLib/taglib/taglib/flac/flacpicture.cpp | 34 +- .../taglib/taglib/flac/flacproperties.cpp | 90 +- .../taglib/taglib/flac/flacproperties.h | 60 +- .../taglib/flac/flacunknownmetadatablock.cpp | 11 +- Frameworks/TagLib/taglib/taglib/it/itfile.cpp | 335 ++++ Frameworks/TagLib/taglib/taglib/it/itfile.h | 109 ++ .../TagLib/taglib/taglib/it/itproperties.cpp | 260 +++ .../TagLib/taglib/taglib/it/itproperties.h | 107 ++ Frameworks/TagLib/taglib/taglib/m4a/mp4file.h | 162 -- .../TagLib/taglib/taglib/mod/modfile.cpp | 192 ++ Frameworks/TagLib/taglib/taglib/mod/modfile.h | 114 ++ .../TagLib/taglib/taglib/mod/modfilebase.cpp | 125 ++ .../TagLib/taglib/taglib/mod/modfilebase.h | 66 + .../TagLib/taglib/taglib/mod/modfileprivate.h | 67 + .../taglib/taglib/mod/modproperties.cpp | 111 ++ .../TagLib/taglib/taglib/mod/modproperties.h | 71 + .../TagLib/taglib/taglib/mod/modtag.cpp | 174 ++ Frameworks/TagLib/taglib/taglib/mod/modtag.h | 194 ++ .../TagLib/taglib/taglib/mp4/mp4atom.cpp | 90 +- Frameworks/TagLib/taglib/taglib/mp4/mp4atom.h | 68 +- .../TagLib/taglib/taglib/mp4/mp4coverart.cpp | 38 +- .../TagLib/taglib/taglib/mp4/mp4coverart.h | 17 +- .../TagLib/taglib/taglib/mp4/mp4file.cpp | 127 +- Frameworks/TagLib/taglib/taglib/mp4/mp4file.h | 58 +- .../TagLib/taglib/taglib/mp4/mp4item.cpp | 121 +- Frameworks/TagLib/taglib/taglib/mp4/mp4item.h | 21 + .../taglib/taglib/mp4/mp4properties.cpp | 274 ++- .../TagLib/taglib/taglib/mp4/mp4properties.h | 61 +- .../TagLib/taglib/taglib/mp4/mp4tag.cpp | 765 ++++++-- Frameworks/TagLib/taglib/taglib/mp4/mp4tag.h | 146 +- .../TagLib/taglib/taglib/mpc/mpcfile.cpp | 321 ++-- Frameworks/TagLib/taglib/taglib/mpc/mpcfile.h | 99 +- .../taglib/taglib/mpc/mpcproperties.cpp | 296 ++- .../TagLib/taglib/taglib/mpc/mpcproperties.h | 83 +- .../taglib/taglib/mpeg/id3v1/id3v1genres.cpp | 401 ++-- .../taglib/taglib/mpeg/id3v1/id3v1genres.h | 2 +- .../taglib/taglib/mpeg/id3v1/id3v1tag.cpp | 130 +- .../taglib/taglib/mpeg/id3v1/id3v1tag.h | 39 +- .../id3v2/frames/attachedpictureframe.cpp | 21 +- .../taglib/mpeg/id3v2/frames/chapterframe.cpp | 309 ++++ .../taglib/mpeg/id3v2/frames/chapterframe.h | 249 +++ .../mpeg/id3v2/frames/commentsframe.cpp | 40 +- .../taglib/mpeg/id3v2/frames/commentsframe.h | 17 +- .../id3v2/frames/eventtimingcodesframe.cpp | 144 ++ .../mpeg/id3v2/frames/eventtimingcodesframe.h | 185 ++ .../frames/generalencapsulatedobjectframe.cpp | 33 +- .../frames/generalencapsulatedobjectframe.h | 1 + .../mpeg/id3v2/frames/ownershipframe.cpp | 171 ++ .../taglib/mpeg/id3v2/frames/ownershipframe.h | 151 ++ .../taglib/mpeg/id3v2/frames/podcastframe.cpp | 89 + .../taglib/mpeg/id3v2/frames/podcastframe.h | 82 + .../mpeg/id3v2/frames/popularimeterframe.cpp | 23 +- .../mpeg/id3v2/frames/popularimeterframe.h | 6 +- .../taglib/mpeg/id3v2/frames/privateframe.cpp | 19 +- .../mpeg/id3v2/frames/relativevolumeframe.cpp | 25 +- .../mpeg/id3v2/frames/relativevolumeframe.h | 14 +- .../id3v2/frames/synchronizedlyricsframe.cpp | 242 +++ .../id3v2/frames/synchronizedlyricsframe.h | 231 +++ .../id3v2/frames/tableofcontentsframe.cpp | 360 ++++ .../mpeg/id3v2/frames/tableofcontentsframe.h | 260 +++ .../id3v2/frames/textidentificationframe.cpp | 208 ++- .../id3v2/frames/textidentificationframe.h | 57 +- .../frames/uniquefileidentifierframe.cpp | 44 +- .../id3v2/frames/uniquefileidentifierframe.h | 14 +- .../taglib/mpeg/id3v2/frames/unknownframe.cpp | 12 +- .../frames/unsynchronizedlyricsframe.cpp | 62 +- .../id3v2/frames/unsynchronizedlyricsframe.h | 24 +- .../taglib/mpeg/id3v2/frames/urllinkframe.cpp | 76 +- .../taglib/mpeg/id3v2/frames/urllinkframe.h | 18 + .../TagLib/taglib/taglib/mpeg/id3v2/id3v2.h | 24 + .../taglib/mpeg/id3v2/id3v2extendedheader.cpp | 8 +- .../taglib/mpeg/id3v2/id3v2extendedheader.h | 4 +- .../taglib/taglib/mpeg/id3v2/id3v2footer.cpp | 21 +- .../taglib/taglib/mpeg/id3v2/id3v2footer.h | 2 +- .../taglib/taglib/mpeg/id3v2/id3v2frame.cpp | 403 +++- .../taglib/taglib/mpeg/id3v2/id3v2frame.h | 149 +- .../taglib/mpeg/id3v2/id3v2framefactory.cpp | 377 ++-- .../taglib/mpeg/id3v2/id3v2framefactory.h | 44 +- .../taglib/taglib/mpeg/id3v2/id3v2header.cpp | 62 +- .../taglib/taglib/mpeg/id3v2/id3v2header.h | 17 +- .../taglib/mpeg/id3v2/id3v2synchdata.cpp | 43 +- .../taglib/taglib/mpeg/id3v2/id3v2synchdata.h | 4 +- .../taglib/taglib/mpeg/id3v2/id3v2tag.cpp | 577 ++++-- .../taglib/taglib/mpeg/id3v2/id3v2tag.h | 159 +- .../TagLib/taglib/taglib/mpeg/mpegfile.cpp | 598 +++--- .../TagLib/taglib/taglib/mpeg/mpegfile.h | 174 +- .../TagLib/taglib/taglib/mpeg/mpegheader.cpp | 132 +- .../TagLib/taglib/taglib/mpeg/mpegheader.h | 18 +- .../taglib/taglib/mpeg/mpegproperties.cpp | 159 +- .../taglib/taglib/mpeg/mpegproperties.h | 46 +- .../TagLib/taglib/taglib/mpeg/mpegutils.h | 63 + .../TagLib/taglib/taglib/mpeg/xingheader.cpp | 105 +- .../TagLib/taglib/taglib/mpeg/xingheader.h | 61 +- .../taglib/taglib/ogg/flac/oggflacfile.cpp | 100 +- .../taglib/taglib/ogg/flac/oggflacfile.h | 60 +- .../TagLib/taglib/taglib/ogg/oggfile.cpp | 402 ++-- Frameworks/TagLib/taglib/taglib/ogg/oggfile.h | 33 +- .../TagLib/taglib/taglib/ogg/oggpage.cpp | 184 +- Frameworks/TagLib/taglib/taglib/ogg/oggpage.h | 33 +- .../taglib/taglib/ogg/oggpageheader.cpp | 67 +- .../TagLib/taglib/taglib/ogg/oggpageheader.h | 8 +- .../taglib/taglib/ogg/opus/opusfile.cpp | 64 +- .../TagLib/taglib/taglib/ogg/opus/opusfile.h | 57 +- .../taglib/taglib/ogg/opus/opusproperties.cpp | 118 +- .../taglib/taglib/ogg/opus/opusproperties.h | 61 +- .../taglib/taglib/ogg/speex/speexfile.cpp | 50 +- .../taglib/taglib/ogg/speex/speexfile.h | 47 +- .../taglib/ogg/speex/speexproperties.cpp | 87 +- .../taglib/taglib/ogg/speex/speexproperties.h | 46 +- .../taglib/taglib/ogg/vorbis/vorbisfile.cpp | 47 +- .../taglib/taglib/ogg/vorbis/vorbisfile.h | 47 +- .../taglib/ogg/vorbis/vorbisproperties.cpp | 84 +- .../taglib/ogg/vorbis/vorbisproperties.h | 41 +- .../TagLib/taglib/taglib/ogg/xiphcomment.cpp | 362 ++-- .../TagLib/taglib/taglib/ogg/xiphcomment.h | 93 +- .../taglib/taglib/riff/aiff/aifffile.cpp | 103 +- .../TagLib/taglib/taglib/riff/aiff/aifffile.h | 63 +- .../taglib/riff/aiff/aiffproperties.cpp | 169 +- .../taglib/taglib/riff/aiff/aiffproperties.h | 124 +- .../TagLib/taglib/taglib/riff/rifffile.cpp | 332 ++-- .../TagLib/taglib/taglib/riff/rifffile.h | 58 +- .../TagLib/taglib/taglib/riff/riffutils.h | 60 + .../TagLib/taglib/taglib/riff/wav/infotag.cpp | 256 +++ .../TagLib/taglib/taglib/riff/wav/infotag.h | 192 ++ .../TagLib/taglib/taglib/riff/wav/wavfile.cpp | 205 ++- .../TagLib/taglib/taglib/riff/wav/wavfile.h | 125 +- .../taglib/taglib/riff/wav/wavproperties.cpp | 159 +- .../taglib/taglib/riff/wav/wavproperties.h | 122 +- .../TagLib/taglib/taglib/s3m/s3mfile.cpp | 248 +++ Frameworks/TagLib/taglib/taglib/s3m/s3mfile.h | 112 ++ .../taglib/taglib/s3m/s3mproperties.cpp | 219 +++ .../TagLib/taglib/taglib/s3m/s3mproperties.h | 94 + Frameworks/TagLib/taglib/taglib/tag.cpp | 97 + Frameworks/TagLib/taglib/taglib/tag.h | 86 +- Frameworks/TagLib/taglib/taglib/taglib.pro | 275 --- .../taglib/taglib/taglib_config.h.cmake | 11 + .../TagLib/taglib/taglib/taglib_export.h | 4 - Frameworks/TagLib/taglib/taglib/tagunion.cpp | 136 +- Frameworks/TagLib/taglib/taglib/tagunion.h | 19 +- Frameworks/TagLib/taglib/taglib/tagutils.cpp | 105 ++ Frameworks/TagLib/taglib/taglib/tagutils.h | 55 + .../TagLib/taglib/taglib/toolkit/taglib.h | 65 +- .../taglib/taglib/toolkit/tbytevector.cpp | 1360 ++++++++------ .../taglib/taglib/toolkit/tbytevector.h | 278 ++- .../taglib/taglib/toolkit/tbytevectorlist.cpp | 4 +- .../taglib/toolkit/tbytevectorstream.cpp | 167 ++ .../taglib/taglib/toolkit/tbytevectorstream.h | 145 ++ .../TagLib/taglib/taglib/toolkit/tdebug.cpp | 45 +- .../TagLib/taglib/taglib/toolkit/tdebug.h | 15 +- .../taglib/taglib/toolkit/tdebuglistener.cpp | 85 + .../taglib/taglib/toolkit/tdebuglistener.h | 74 + .../TagLib/taglib/taglib/toolkit/tfile.cpp | 521 +++--- .../TagLib/taglib/taglib/toolkit/tfile.h | 111 +- .../taglib/taglib/toolkit/tfilestream.cpp | 507 ++++++ .../taglib/taglib/toolkit/tfilestream.h | 159 ++ .../taglib/taglib/toolkit/tiostream.cpp | 114 ++ .../TagLib/taglib/taglib/toolkit/tiostream.h | 170 ++ .../TagLib/taglib/taglib/toolkit/tlist.h | 26 +- .../TagLib/taglib/taglib/toolkit/tlist.tcc | 47 +- .../TagLib/taglib/taglib/toolkit/tmap.h | 8 +- .../TagLib/taglib/taglib/toolkit/tmap.tcc | 39 +- .../taglib/taglib/toolkit/tpropertymap.cpp | 179 ++ .../taglib/taglib/toolkit/tpropertymap.h | 242 +++ .../taglib/taglib/toolkit/trefcounter.cpp | 96 + .../taglib/taglib/toolkit/trefcounter.h | 114 ++ .../TagLib/taglib/taglib/toolkit/tstring.cpp | 964 ++++------ .../TagLib/taglib/taglib/toolkit/tstring.h | 206 ++- .../taglib/taglib/toolkit/tstringlist.h | 4 +- .../TagLib/taglib/taglib/toolkit/tutils.h | 243 +++ .../TagLib/taglib/taglib/toolkit/tzlib.cpp | 106 ++ .../TagLib/taglib/taglib/toolkit/tzlib.h | 54 + .../TagLib/taglib/taglib/toolkit/unicode.cpp | 303 ---- .../TagLib/taglib/taglib/toolkit/unicode.h | 149 -- .../taglib/taglib/trueaudio/trueaudiofile.cpp | 236 ++- .../taglib/taglib/trueaudio/trueaudiofile.h | 125 +- .../taglib/trueaudio/trueaudioproperties.cpp | 85 +- .../taglib/trueaudio/trueaudioproperties.h | 50 +- .../taglib/taglib/wavpack/wavpackfile.cpp | 231 +-- .../taglib/taglib/wavpack/wavpackfile.h | 85 +- .../taglib/wavpack/wavpackproperties.cpp | 300 ++- .../taglib/taglib/wavpack/wavpackproperties.h | 61 +- Frameworks/TagLib/taglib/taglib/xm/xmfile.cpp | 644 +++++++ Frameworks/TagLib/taglib/taglib/xm/xmfile.h | 112 ++ .../TagLib/taglib/taglib/xm/xmproperties.cpp | 195 ++ .../TagLib/taglib/taglib/xm/xmproperties.h | 85 + Frameworks/TagLib/taglib/taglib_config.h | 11 +- Frameworks/TagLib/taglib/tests/CMakeLists.txt | 80 + .../TagLib/taglib/tests/data/005411.id3 | Bin 0 -> 38402 bytes Frameworks/TagLib/taglib/tests/data/64bit.mp4 | Bin 0 -> 85 bytes Frameworks/TagLib/taglib/tests/data/alaw.aifc | Bin 0 -> 1890 bytes Frameworks/TagLib/taglib/tests/data/alaw.wav | Bin 0 -> 56858 bytes .../TagLib/taglib/tests/data/ape-id3v1.mp3 | Bin 0 -> 8419 bytes .../TagLib/taglib/tests/data/ape-id3v2.mp3 | Bin 0 -> 9341 bytes Frameworks/TagLib/taglib/tests/data/ape.mp3 | Bin 0 -> 8291 bytes .../TagLib/taglib/tests/data/bladeenc.mp3 | Bin 0 -> 28422 bytes .../TagLib/taglib/tests/data/blank_video.m4v | Bin 0 -> 15018 bytes .../TagLib/taglib/tests/data/broken-tenc.id3 | Bin 0 -> 400 bytes .../TagLib/taglib/tests/data/changed.mod | Bin 0 -> 3132 bytes .../TagLib/taglib/tests/data/changed.s3m | Bin 0 -> 544 bytes .../TagLib/taglib/tests/data/changed.xm | Bin 0 -> 5471 bytes Frameworks/TagLib/taglib/tests/data/click.mpc | Bin 0 -> 1588 bytes Frameworks/TagLib/taglib/tests/data/click.wv | Bin 0 -> 3176 bytes .../tests/data/compressed_id3_frame.mp3 | Bin 0 -> 5000 bytes .../data/correctness_gain_silent_output.opus | Bin 0 -> 35506 bytes .../TagLib/taglib/tests/data/covr-junk.m4a | Bin 0 -> 5108 bytes .../TagLib/taglib/tests/data/dsd_stereo.wv | Bin 0 -> 52595 bytes .../taglib/tests/data/duplicate_id3v2.aiff | Bin 0 -> 8124 bytes .../taglib/tests/data/duplicate_id3v2.mp3 | Bin 0 -> 10138 bytes .../taglib/tests/data/duplicate_tags.wav | Bin 0 -> 17052 bytes .../taglib/tests/data/empty-seektable.flac | Bin 0 -> 4608 bytes .../TagLib/taglib/tests/data/empty.aiff | Bin 0 -> 5936 bytes Frameworks/TagLib/taglib/tests/data/empty.ogg | Bin 0 -> 4328 bytes Frameworks/TagLib/taglib/tests/data/empty.spx | Bin 0 -> 24301 bytes Frameworks/TagLib/taglib/tests/data/empty.tta | Bin 0 -> 79538 bytes Frameworks/TagLib/taglib/tests/data/empty.wav | Bin 0 -> 14744 bytes .../TagLib/taglib/tests/data/empty_alac.m4a | Bin 0 -> 5380 bytes .../TagLib/taglib/tests/data/empty_flac.oga | Bin 0 -> 9113 bytes .../TagLib/taglib/tests/data/empty_vorbis.oga | Bin 0 -> 4328 bytes .../taglib/tests/data/excessive_alloc.aif | Bin 0 -> 2170 bytes .../taglib/tests/data/excessive_alloc.mp3 | Bin 0 -> 925 bytes .../TagLib/taglib/tests/data/float64.wav | Bin 0 -> 68584 bytes .../TagLib/taglib/tests/data/four_channels.wv | Bin 0 -> 53520 bytes .../TagLib/taglib/tests/data/garbage.mp3 | Bin 0 -> 8190 bytes Frameworks/TagLib/taglib/tests/data/gnre.m4a | Bin 0 -> 5026 bytes .../TagLib/taglib/tests/data/has-tags.m4a | Bin 0 -> 5108 bytes .../TagLib/taglib/tests/data/id3v22-tda.mp3 | Bin 0 -> 4096 bytes .../TagLib/taglib/tests/data/ilst-is-last.m4a | Bin 0 -> 32768 bytes .../TagLib/taglib/tests/data/infloop.m4a | Bin 0 -> 53192 bytes .../TagLib/taglib/tests/data/infloop.mpc | Bin 0 -> 434 bytes .../TagLib/taglib/tests/data/infloop.wav | Bin 0 -> 14272 bytes .../TagLib/taglib/tests/data/infloop.wv | Bin 0 -> 2462 bytes .../taglib/tests/data/invalid-frames1.mp3 | Bin 0 -> 8164 bytes .../taglib/tests/data/invalid-frames2.mp3 | Bin 0 -> 7898 bytes .../taglib/tests/data/invalid-frames3.mp3 | Bin 0 -> 8192 bytes .../TagLib/taglib/tests/data/lame_cbr.mp3 | Bin 0 -> 4096 bytes .../TagLib/taglib/tests/data/lame_vbr.mp3 | Bin 0 -> 4096 bytes .../TagLib/taglib/tests/data/longloop.ape | Bin 0 -> 184 bytes .../TagLib/taglib/tests/data/lossless.wma | Bin 0 -> 99013 bytes .../taglib/tests/data/lowercase-fields.ogg | Bin 0 -> 4477 bytes .../TagLib/taglib/tests/data/mac-390-hdr.ape | Bin 0 -> 128 bytes .../TagLib/taglib/tests/data/mac-396.ape | Bin 0 -> 104 bytes .../taglib/tests/data/mac-399-id3v2.ape | Bin 0 -> 89155 bytes .../taglib/tests/data/mac-399-tagged.ape | Bin 0 -> 91591 bytes .../TagLib/taglib/tests/data/mac-399.ape | Bin 0 -> 85212 bytes Frameworks/TagLib/taglib/tests/data/mpeg2.mp3 | Bin 0 -> 16384 bytes .../TagLib/taglib/tests/data/multiple-vc.flac | Bin 0 -> 4754 bytes .../TagLib/taglib/tests/data/no-extension | Bin 0 -> 256 bytes .../TagLib/taglib/tests/data/no-tags.3g2 | Bin 0 -> 68335 bytes .../TagLib/taglib/tests/data/no-tags.flac | Bin 0 -> 4692 bytes .../TagLib/taglib/tests/data/no-tags.m4a | Bin 0 -> 2898 bytes .../TagLib/taglib/tests/data/no_length.wv | Bin 0 -> 532 bytes Frameworks/TagLib/taglib/tests/data/noise.aif | Bin 0 -> 4400 bytes .../TagLib/taglib/tests/data/noise_odd.aif | Bin 0 -> 4399 bytes .../taglib/tests/data/non_standard_rate.wv | Bin 0 -> 132 bytes .../taglib/tests/data/pcm_with_fact_chunk.wav | Bin 0 -> 14756 bytes .../TagLib/taglib/tests/data/rare_frames.mp3 | Bin 0 -> 8320 bytes .../TagLib/taglib/tests/data/segfault.aif | Bin 0 -> 31 bytes .../TagLib/taglib/tests/data/segfault.mpc | Bin 0 -> 19 bytes .../TagLib/taglib/tests/data/segfault.oga | Bin 0 -> 120 bytes .../TagLib/taglib/tests/data/segfault.wav | Bin 0 -> 30 bytes .../TagLib/taglib/tests/data/segfault2.mpc | 1 + .../TagLib/taglib/tests/data/silence-1.wma | Bin 0 -> 35416 bytes .../taglib/tests/data/silence-44-s.flac | Bin 0 -> 50904 bytes .../TagLib/taglib/tests/data/sinewave.flac | Bin 0 -> 64567 bytes .../TagLib/taglib/tests/data/stripped.xm | Bin 0 -> 602 bytes .../TagLib/taglib/tests/data/sv4_header.mpc | Bin 0 -> 128 bytes .../TagLib/taglib/tests/data/sv5_header.mpc | Bin 0 -> 128 bytes .../TagLib/taglib/tests/data/sv8_header.mpc | Bin 0 -> 114 bytes .../TagLib/taglib/tests/data/tagged.tta | Bin 0 -> 81819 bytes Frameworks/TagLib/taglib/tests/data/tagged.wv | Bin 0 -> 76627 bytes Frameworks/TagLib/taglib/tests/data/test.it | Bin 0 -> 644 bytes Frameworks/TagLib/taglib/tests/data/test.mod | Bin 0 -> 3132 bytes Frameworks/TagLib/taglib/tests/data/test.ogg | Bin 0 -> 4408 bytes Frameworks/TagLib/taglib/tests/data/test.s3m | Bin 0 -> 544 bytes Frameworks/TagLib/taglib/tests/data/test.xm | Bin 0 -> 5471 bytes .../taglib/tests/data/toc_many_children.mp3 | Bin 0 -> 11525 bytes .../TagLib/taglib/tests/data/uint8we.wav | Bin 0 -> 47240 bytes .../tests/data/unsupported-extension.xx | Bin 0 -> 256 bytes .../TagLib/taglib/tests/data/unsynch.id3 | Bin 0 -> 320 bytes Frameworks/TagLib/taglib/tests/data/w000.mp3 | Bin 0 -> 512 bytes Frameworks/TagLib/taglib/tests/data/xing.mp3 | Bin 0 -> 8208 bytes .../taglib/tests/data/zero-length-mdat.m4a | Bin 0 -> 4517 bytes .../taglib/tests/data/zero-size-chunk.wav | Bin 0 -> 1024 bytes .../taglib/tests/data/zero-sized-padding.flac | Bin 0 -> 4692 bytes .../TagLib/taglib/tests/data/zerodiv.ape | Bin 0 -> 946 bytes .../TagLib/taglib/tests/data/zerodiv.mpc | Bin 0 -> 405 bytes Frameworks/TagLib/taglib/tests/main.cpp | 90 + Frameworks/TagLib/taglib/tests/plainfile.h | 50 + Frameworks/TagLib/taglib/tests/test_aiff.cpp | 162 ++ Frameworks/TagLib/taglib/tests/test_ape.cpp | 238 +++ .../TagLib/taglib/tests/test_apetag.cpp | 171 ++ Frameworks/TagLib/taglib/tests/test_asf.cpp | 401 ++++ .../TagLib/taglib/tests/test_bytevector.cpp | 599 ++++++ .../taglib/tests/test_bytevectorlist.cpp | 63 + .../taglib/tests/test_bytevectorstream.cpp | 131 ++ Frameworks/TagLib/taglib/tests/test_file.cpp | 145 ++ .../TagLib/taglib/tests/test_fileref.cpp | 394 ++++ Frameworks/TagLib/taglib/tests/test_flac.cpp | 672 +++++++ .../TagLib/taglib/tests/test_flacpicture.cpp | 77 + .../tests/test_flacunknownmetadatablock.cpp | 63 + Frameworks/TagLib/taglib/tests/test_id3v1.cpp | 88 + Frameworks/TagLib/taglib/tests/test_id3v2.cpp | 1613 +++++++++++++++++ Frameworks/TagLib/taglib/tests/test_info.cpp | 75 + Frameworks/TagLib/taglib/tests/test_it.cpp | 139 ++ Frameworks/TagLib/taglib/tests/test_list.cpp | 76 + Frameworks/TagLib/taglib/tests/test_map.cpp | 72 + Frameworks/TagLib/taglib/tests/test_mod.cpp | 133 ++ Frameworks/TagLib/taglib/tests/test_mp4.cpp | 659 +++++++ .../TagLib/taglib/tests/test_mp4coverart.cpp | 74 + .../TagLib/taglib/tests/test_mp4item.cpp | 62 + Frameworks/TagLib/taglib/tests/test_mpc.cpp | 193 ++ Frameworks/TagLib/taglib/tests/test_mpeg.cpp | 539 ++++++ Frameworks/TagLib/taglib/tests/test_ogg.cpp | 227 +++ .../TagLib/taglib/tests/test_oggflac.cpp | 122 ++ Frameworks/TagLib/taglib/tests/test_opus.cpp | 137 ++ .../TagLib/taglib/tests/test_propertymap.cpp | 114 ++ Frameworks/TagLib/taglib/tests/test_riff.cpp | 298 +++ Frameworks/TagLib/taglib/tests/test_s3m.cpp | 127 ++ Frameworks/TagLib/taglib/tests/test_speex.cpp | 102 ++ .../TagLib/taglib/tests/test_string.cpp | 371 ++++ .../TagLib/taglib/tests/test_synchdata.cpp | 127 ++ .../TagLib/taglib/tests/test_trueaudio.cpp | 135 ++ Frameworks/TagLib/taglib/tests/test_wav.cpp | 389 ++++ .../TagLib/taglib/tests/test_wavpack.cpp | 183 ++ .../TagLib/taglib/tests/test_xiphcomment.cpp | 215 +++ Frameworks/TagLib/taglib/tests/test_xm.cpp | 220 +++ Frameworks/TagLib/taglib/tests/utils.h | 150 ++ 393 files changed, 38013 insertions(+), 7577 deletions(-) create mode 100644 Frameworks/TagLib/taglib/3rdparty/utf8-cpp/checked.h create mode 100644 Frameworks/TagLib/taglib/3rdparty/utf8-cpp/core.h create mode 100644 Frameworks/TagLib/taglib/CMakeLists.txt create mode 100644 Frameworks/TagLib/taglib/COPYING.MPL create mode 100644 Frameworks/TagLib/taglib/ConfigureChecks.cmake create mode 100644 Frameworks/TagLib/taglib/Doxyfile.cmake create mode 100644 Frameworks/TagLib/taglib/INSTALL.md create mode 100644 Frameworks/TagLib/taglib/NEWS create mode 100644 Frameworks/TagLib/taglib/README.md create mode 100644 Frameworks/TagLib/taglib/bindings/CMakeLists.txt create mode 100644 Frameworks/TagLib/taglib/bindings/README create mode 100644 Frameworks/TagLib/taglib/bindings/c/CMakeLists.txt create mode 100644 Frameworks/TagLib/taglib/bindings/c/tag_c.cpp create mode 100644 Frameworks/TagLib/taglib/bindings/c/tag_c.h create mode 100644 Frameworks/TagLib/taglib/bindings/c/taglib_c.pc.cmake create mode 100644 Frameworks/TagLib/taglib/cmake/modules/FindCppUnit.cmake create mode 100644 Frameworks/TagLib/taglib/cmake/modules/MacroEnsureVersion.cmake create mode 100644 Frameworks/TagLib/taglib/cmake_uninstall.cmake.in create mode 100644 Frameworks/TagLib/taglib/config.h.cmake create mode 100644 Frameworks/TagLib/taglib/doc/README create mode 100644 Frameworks/TagLib/taglib/doc/api-footer.html create mode 100644 Frameworks/TagLib/taglib/doc/api-header.html create mode 100644 Frameworks/TagLib/taglib/doc/taglib-api.css create mode 100644 Frameworks/TagLib/taglib/doc/taglib.png create mode 100644 Frameworks/TagLib/taglib/examples/CMakeLists.txt create mode 100644 Frameworks/TagLib/taglib/examples/framelist.cpp create mode 100644 Frameworks/TagLib/taglib/examples/strip-id3v1.cpp create mode 100644 Frameworks/TagLib/taglib/examples/tagreader.cpp create mode 100644 Frameworks/TagLib/taglib/examples/tagreader_c.c create mode 100644 Frameworks/TagLib/taglib/examples/tagwriter.cpp create mode 100644 Frameworks/TagLib/taglib/taglib-config.cmake create mode 100644 Frameworks/TagLib/taglib/taglib-config.cmd.cmake create mode 100644 Frameworks/TagLib/taglib/taglib.pc.cmake create mode 100644 Frameworks/TagLib/taglib/taglib/CMakeLists.txt create mode 100644 Frameworks/TagLib/taglib/taglib/asf/asfutils.h create mode 100644 Frameworks/TagLib/taglib/taglib/it/itfile.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/it/itfile.h create mode 100644 Frameworks/TagLib/taglib/taglib/it/itproperties.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/it/itproperties.h delete mode 100755 Frameworks/TagLib/taglib/taglib/m4a/mp4file.h create mode 100644 Frameworks/TagLib/taglib/taglib/mod/modfile.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/mod/modfile.h create mode 100644 Frameworks/TagLib/taglib/taglib/mod/modfilebase.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/mod/modfilebase.h create mode 100644 Frameworks/TagLib/taglib/taglib/mod/modfileprivate.h create mode 100644 Frameworks/TagLib/taglib/taglib/mod/modproperties.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/mod/modproperties.h create mode 100644 Frameworks/TagLib/taglib/taglib/mod/modtag.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/mod/modtag.h create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/chapterframe.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/chapterframe.h create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/eventtimingcodesframe.h create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/ownershipframe.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/ownershipframe.h create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/podcastframe.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/podcastframe.h create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/tableofcontentsframe.h create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2.h create mode 100644 Frameworks/TagLib/taglib/taglib/mpeg/mpegutils.h create mode 100644 Frameworks/TagLib/taglib/taglib/riff/riffutils.h create mode 100644 Frameworks/TagLib/taglib/taglib/riff/wav/infotag.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/riff/wav/infotag.h create mode 100644 Frameworks/TagLib/taglib/taglib/s3m/s3mfile.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/s3m/s3mfile.h create mode 100644 Frameworks/TagLib/taglib/taglib/s3m/s3mproperties.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/s3m/s3mproperties.h delete mode 100644 Frameworks/TagLib/taglib/taglib/taglib.pro create mode 100644 Frameworks/TagLib/taglib/taglib/taglib_config.h.cmake create mode 100644 Frameworks/TagLib/taglib/taglib/tagutils.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/tagutils.h create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorstream.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorstream.h create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tdebuglistener.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tdebuglistener.h create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tfilestream.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tfilestream.h create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tiostream.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tiostream.h create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tpropertymap.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tpropertymap.h create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/trefcounter.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/trefcounter.h create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tutils.h create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tzlib.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/tzlib.h delete mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/unicode.cpp delete mode 100644 Frameworks/TagLib/taglib/taglib/toolkit/unicode.h create mode 100644 Frameworks/TagLib/taglib/taglib/xm/xmfile.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/xm/xmfile.h create mode 100644 Frameworks/TagLib/taglib/taglib/xm/xmproperties.cpp create mode 100644 Frameworks/TagLib/taglib/taglib/xm/xmproperties.h create mode 100644 Frameworks/TagLib/taglib/tests/CMakeLists.txt create mode 100644 Frameworks/TagLib/taglib/tests/data/005411.id3 create mode 100644 Frameworks/TagLib/taglib/tests/data/64bit.mp4 create mode 100644 Frameworks/TagLib/taglib/tests/data/alaw.aifc create mode 100644 Frameworks/TagLib/taglib/tests/data/alaw.wav create mode 100644 Frameworks/TagLib/taglib/tests/data/ape-id3v1.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/ape-id3v2.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/ape.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/bladeenc.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/blank_video.m4v create mode 100644 Frameworks/TagLib/taglib/tests/data/broken-tenc.id3 create mode 100644 Frameworks/TagLib/taglib/tests/data/changed.mod create mode 100644 Frameworks/TagLib/taglib/tests/data/changed.s3m create mode 100644 Frameworks/TagLib/taglib/tests/data/changed.xm create mode 100644 Frameworks/TagLib/taglib/tests/data/click.mpc create mode 100644 Frameworks/TagLib/taglib/tests/data/click.wv create mode 100644 Frameworks/TagLib/taglib/tests/data/compressed_id3_frame.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/correctness_gain_silent_output.opus create mode 100644 Frameworks/TagLib/taglib/tests/data/covr-junk.m4a create mode 100644 Frameworks/TagLib/taglib/tests/data/dsd_stereo.wv create mode 100644 Frameworks/TagLib/taglib/tests/data/duplicate_id3v2.aiff create mode 100644 Frameworks/TagLib/taglib/tests/data/duplicate_id3v2.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/duplicate_tags.wav create mode 100644 Frameworks/TagLib/taglib/tests/data/empty-seektable.flac create mode 100644 Frameworks/TagLib/taglib/tests/data/empty.aiff create mode 100644 Frameworks/TagLib/taglib/tests/data/empty.ogg create mode 100644 Frameworks/TagLib/taglib/tests/data/empty.spx create mode 100644 Frameworks/TagLib/taglib/tests/data/empty.tta create mode 100644 Frameworks/TagLib/taglib/tests/data/empty.wav create mode 100644 Frameworks/TagLib/taglib/tests/data/empty_alac.m4a create mode 100644 Frameworks/TagLib/taglib/tests/data/empty_flac.oga create mode 100644 Frameworks/TagLib/taglib/tests/data/empty_vorbis.oga create mode 100644 Frameworks/TagLib/taglib/tests/data/excessive_alloc.aif create mode 100644 Frameworks/TagLib/taglib/tests/data/excessive_alloc.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/float64.wav create mode 100644 Frameworks/TagLib/taglib/tests/data/four_channels.wv create mode 100644 Frameworks/TagLib/taglib/tests/data/garbage.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/gnre.m4a create mode 100644 Frameworks/TagLib/taglib/tests/data/has-tags.m4a create mode 100644 Frameworks/TagLib/taglib/tests/data/id3v22-tda.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/ilst-is-last.m4a create mode 100644 Frameworks/TagLib/taglib/tests/data/infloop.m4a create mode 100644 Frameworks/TagLib/taglib/tests/data/infloop.mpc create mode 100644 Frameworks/TagLib/taglib/tests/data/infloop.wav create mode 100644 Frameworks/TagLib/taglib/tests/data/infloop.wv create mode 100644 Frameworks/TagLib/taglib/tests/data/invalid-frames1.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/invalid-frames2.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/invalid-frames3.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/lame_cbr.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/lame_vbr.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/longloop.ape create mode 100644 Frameworks/TagLib/taglib/tests/data/lossless.wma create mode 100644 Frameworks/TagLib/taglib/tests/data/lowercase-fields.ogg create mode 100644 Frameworks/TagLib/taglib/tests/data/mac-390-hdr.ape create mode 100644 Frameworks/TagLib/taglib/tests/data/mac-396.ape create mode 100644 Frameworks/TagLib/taglib/tests/data/mac-399-id3v2.ape create mode 100644 Frameworks/TagLib/taglib/tests/data/mac-399-tagged.ape create mode 100644 Frameworks/TagLib/taglib/tests/data/mac-399.ape create mode 100644 Frameworks/TagLib/taglib/tests/data/mpeg2.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/multiple-vc.flac create mode 100644 Frameworks/TagLib/taglib/tests/data/no-extension create mode 100644 Frameworks/TagLib/taglib/tests/data/no-tags.3g2 create mode 100644 Frameworks/TagLib/taglib/tests/data/no-tags.flac create mode 100644 Frameworks/TagLib/taglib/tests/data/no-tags.m4a create mode 100644 Frameworks/TagLib/taglib/tests/data/no_length.wv create mode 100644 Frameworks/TagLib/taglib/tests/data/noise.aif create mode 100644 Frameworks/TagLib/taglib/tests/data/noise_odd.aif create mode 100644 Frameworks/TagLib/taglib/tests/data/non_standard_rate.wv create mode 100644 Frameworks/TagLib/taglib/tests/data/pcm_with_fact_chunk.wav create mode 100644 Frameworks/TagLib/taglib/tests/data/rare_frames.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/segfault.aif create mode 100644 Frameworks/TagLib/taglib/tests/data/segfault.mpc create mode 100644 Frameworks/TagLib/taglib/tests/data/segfault.oga create mode 100644 Frameworks/TagLib/taglib/tests/data/segfault.wav create mode 100644 Frameworks/TagLib/taglib/tests/data/segfault2.mpc create mode 100644 Frameworks/TagLib/taglib/tests/data/silence-1.wma create mode 100644 Frameworks/TagLib/taglib/tests/data/silence-44-s.flac create mode 100644 Frameworks/TagLib/taglib/tests/data/sinewave.flac create mode 100644 Frameworks/TagLib/taglib/tests/data/stripped.xm create mode 100644 Frameworks/TagLib/taglib/tests/data/sv4_header.mpc create mode 100644 Frameworks/TagLib/taglib/tests/data/sv5_header.mpc create mode 100644 Frameworks/TagLib/taglib/tests/data/sv8_header.mpc create mode 100644 Frameworks/TagLib/taglib/tests/data/tagged.tta create mode 100644 Frameworks/TagLib/taglib/tests/data/tagged.wv create mode 100644 Frameworks/TagLib/taglib/tests/data/test.it create mode 100644 Frameworks/TagLib/taglib/tests/data/test.mod create mode 100644 Frameworks/TagLib/taglib/tests/data/test.ogg create mode 100644 Frameworks/TagLib/taglib/tests/data/test.s3m create mode 100644 Frameworks/TagLib/taglib/tests/data/test.xm create mode 100644 Frameworks/TagLib/taglib/tests/data/toc_many_children.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/uint8we.wav create mode 100644 Frameworks/TagLib/taglib/tests/data/unsupported-extension.xx create mode 100644 Frameworks/TagLib/taglib/tests/data/unsynch.id3 create mode 100644 Frameworks/TagLib/taglib/tests/data/w000.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/xing.mp3 create mode 100644 Frameworks/TagLib/taglib/tests/data/zero-length-mdat.m4a create mode 100644 Frameworks/TagLib/taglib/tests/data/zero-size-chunk.wav create mode 100644 Frameworks/TagLib/taglib/tests/data/zero-sized-padding.flac create mode 100644 Frameworks/TagLib/taglib/tests/data/zerodiv.ape create mode 100644 Frameworks/TagLib/taglib/tests/data/zerodiv.mpc create mode 100644 Frameworks/TagLib/taglib/tests/main.cpp create mode 100644 Frameworks/TagLib/taglib/tests/plainfile.h create mode 100644 Frameworks/TagLib/taglib/tests/test_aiff.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_ape.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_apetag.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_asf.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_bytevector.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_bytevectorlist.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_bytevectorstream.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_file.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_fileref.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_flac.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_flacpicture.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_flacunknownmetadatablock.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_id3v1.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_id3v2.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_info.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_it.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_list.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_map.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_mod.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_mp4.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_mp4coverart.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_mp4item.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_mpc.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_mpeg.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_ogg.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_oggflac.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_opus.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_propertymap.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_riff.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_s3m.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_speex.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_string.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_synchdata.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_trueaudio.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_wav.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_wavpack.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_xiphcomment.cpp create mode 100644 Frameworks/TagLib/taglib/tests/test_xm.cpp create mode 100644 Frameworks/TagLib/taglib/tests/utils.h diff --git a/Frameworks/TagLib/TagLib.xcodeproj/project.pbxproj b/Frameworks/TagLib/TagLib.xcodeproj/project.pbxproj index 44484d409..b87ee2ade 100644 --- a/Frameworks/TagLib/TagLib.xcodeproj/project.pbxproj +++ b/Frameworks/TagLib/TagLib.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 174C7A370F4FD43100E18B0F /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 174C7A360F4FD43100E18B0F /* libz.dylib */; }; 32AE5A5A14E70ED600420CA0 /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE59A014E70ED600420CA0 /* config.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 32AE5A5B14E70ED600420CA0 /* ape-tag-format.txt in Resources */ = {isa = PBXBuildFile; fileRef = 32AE59A314E70ED600420CA0 /* ape-tag-format.txt */; }; 32AE5A5C14E70ED600420CA0 /* apefile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE59A414E70ED600420CA0 /* apefile.cpp */; }; 32AE5A5D14E70ED600420CA0 /* apefile.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE59A514E70ED600420CA0 /* apefile.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32AE5A5E14E70ED600420CA0 /* apefooter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE59A614E70ED600420CA0 /* apefooter.cpp */; }; @@ -86,10 +85,6 @@ 32AE5AA514E70ED600420CA0 /* unsynchronizedlyricsframe.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE59F514E70ED600420CA0 /* unsynchronizedlyricsframe.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32AE5AA614E70ED600420CA0 /* urllinkframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE59F614E70ED600420CA0 /* urllinkframe.cpp */; }; 32AE5AA714E70ED600420CA0 /* urllinkframe.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE59F714E70ED600420CA0 /* urllinkframe.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 32AE5AA814E70ED600420CA0 /* id3v2.2.0.txt in Resources */ = {isa = PBXBuildFile; fileRef = 32AE59F814E70ED600420CA0 /* id3v2.2.0.txt */; }; - 32AE5AA914E70ED600420CA0 /* id3v2.3.0.txt in Resources */ = {isa = PBXBuildFile; fileRef = 32AE59F914E70ED600420CA0 /* id3v2.3.0.txt */; }; - 32AE5AAA14E70ED600420CA0 /* id3v2.4.0-frames.txt in Resources */ = {isa = PBXBuildFile; fileRef = 32AE59FA14E70ED600420CA0 /* id3v2.4.0-frames.txt */; }; - 32AE5AAB14E70ED600420CA0 /* id3v2.4.0-structure.txt in Resources */ = {isa = PBXBuildFile; fileRef = 32AE59FB14E70ED600420CA0 /* id3v2.4.0-structure.txt */; }; 32AE5AAC14E70ED600420CA0 /* id3v2extendedheader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE59FC14E70ED600420CA0 /* id3v2extendedheader.cpp */; }; 32AE5AAD14E70ED600420CA0 /* id3v2extendedheader.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE59FD14E70ED600420CA0 /* id3v2extendedheader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32AE5AAE14E70ED600420CA0 /* id3v2footer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE59FE14E70ED600420CA0 /* id3v2footer.cpp */; }; @@ -136,13 +131,8 @@ 32AE5AD714E70ED600420CA0 /* aiffproperties.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE5A2D14E70ED600420CA0 /* aiffproperties.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32AE5AD814E70ED600420CA0 /* rifffile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE5A2E14E70ED600420CA0 /* rifffile.cpp */; }; 32AE5AD914E70ED600420CA0 /* rifffile.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE5A2F14E70ED600420CA0 /* rifffile.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 32AE5ADA14E70ED600420CA0 /* wavfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE5A3114E70ED600420CA0 /* wavfile.cpp */; }; - 32AE5ADB14E70ED600420CA0 /* wavfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE5A3214E70ED600420CA0 /* wavfile.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 32AE5ADC14E70ED600420CA0 /* wavproperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE5A3314E70ED600420CA0 /* wavproperties.cpp */; }; - 32AE5ADD14E70ED600420CA0 /* wavproperties.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE5A3414E70ED600420CA0 /* wavproperties.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32AE5ADE14E70ED600420CA0 /* tag.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE5A3514E70ED600420CA0 /* tag.cpp */; }; 32AE5ADF14E70ED600420CA0 /* tag.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE5A3614E70ED600420CA0 /* tag.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 32AE5AE014E70ED600420CA0 /* taglib.pro in Resources */ = {isa = PBXBuildFile; fileRef = 32AE5A3714E70ED600420CA0 /* taglib.pro */; }; 32AE5AE114E70ED600420CA0 /* taglib_export.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE5A3814E70ED600420CA0 /* taglib_export.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32AE5AE214E70ED600420CA0 /* tagunion.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE5A3914E70ED600420CA0 /* tagunion.cpp */; }; 32AE5AE314E70ED600420CA0 /* tagunion.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE5A3A14E70ED600420CA0 /* tagunion.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -161,8 +151,6 @@ 32AE5AF214E70ED600420CA0 /* tstring.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE5A4A14E70ED600420CA0 /* tstring.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32AE5AF314E70ED600420CA0 /* tstringlist.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE5A4B14E70ED600420CA0 /* tstringlist.cpp */; }; 32AE5AF414E70ED600420CA0 /* tstringlist.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE5A4C14E70ED600420CA0 /* tstringlist.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 32AE5AF514E70ED700420CA0 /* unicode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE5A4D14E70ED600420CA0 /* unicode.cpp */; }; - 32AE5AF614E70ED700420CA0 /* unicode.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE5A4E14E70ED600420CA0 /* unicode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32AE5AF714E70ED700420CA0 /* trueaudiofile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE5A5014E70ED600420CA0 /* trueaudiofile.cpp */; }; 32AE5AF814E70ED700420CA0 /* trueaudiofile.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AE5A5114E70ED600420CA0 /* trueaudiofile.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32AE5AF914E70ED700420CA0 /* trueaudioproperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32AE5A5214E70ED600420CA0 /* trueaudioproperties.cpp */; }; @@ -180,6 +168,118 @@ 83790D261809E8CA0073CF51 /* opusproperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83790D221809E8CA0073CF51 /* opusproperties.cpp */; }; 83790D271809E8CA0073CF51 /* opusproperties.h in Headers */ = {isa = PBXBuildFile; fileRef = 83790D231809E8CA0073CF51 /* opusproperties.h */; }; 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C1666FE841158C02AAC07 /* InfoPlist.strings */; }; + EDE862FD25CF6BD70086EFD3 /* tpropertymap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE862FC25CF6BD60086EFD3 /* tpropertymap.cpp */; }; + EDE8630225CF6C260086EFD3 /* tfilestream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8630025CF6C260086EFD3 /* tfilestream.cpp */; }; + EDE8630325CF6C260086EFD3 /* trefcounter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8630125CF6C260086EFD3 /* trefcounter.cpp */; }; + EDE8630625CF6C5B0086EFD3 /* itproperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8630425CF6C5B0086EFD3 /* itproperties.cpp */; }; + EDE8630725CF6C5B0086EFD3 /* itfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8630525CF6C5B0086EFD3 /* itfile.cpp */; }; + EDE8630D25CF6CAE0086EFD3 /* xmfile.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8630925CF6CAE0086EFD3 /* xmfile.h */; }; + EDE8630E25CF6CAE0086EFD3 /* xmfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8630A25CF6CAE0086EFD3 /* xmfile.cpp */; }; + EDE8630F25CF6CAE0086EFD3 /* xmproperties.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8630B25CF6CAE0086EFD3 /* xmproperties.h */; }; + EDE8631025CF6CAE0086EFD3 /* xmproperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8630C25CF6CAE0086EFD3 /* xmproperties.cpp */; }; + EDE8631B25CF6CC60086EFD3 /* modproperties.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8631225CF6CC60086EFD3 /* modproperties.h */; }; + EDE8631C25CF6CC60086EFD3 /* modfileprivate.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8631325CF6CC60086EFD3 /* modfileprivate.h */; }; + EDE8631D25CF6CC60086EFD3 /* modtag.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8631425CF6CC60086EFD3 /* modtag.cpp */; }; + EDE8631E25CF6CC60086EFD3 /* modfilebase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8631525CF6CC60086EFD3 /* modfilebase.cpp */; }; + EDE8631F25CF6CC60086EFD3 /* modfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8631625CF6CC60086EFD3 /* modfile.cpp */; }; + EDE8632025CF6CC60086EFD3 /* modfile.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8631725CF6CC60086EFD3 /* modfile.h */; }; + EDE8632125CF6CC60086EFD3 /* modtag.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8631825CF6CC60086EFD3 /* modtag.h */; }; + EDE8632225CF6CC60086EFD3 /* modproperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8631925CF6CC60086EFD3 /* modproperties.cpp */; }; + EDE8632325CF6CC60086EFD3 /* modfilebase.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8631A25CF6CC60086EFD3 /* modfilebase.h */; }; + EDE8632925CF6CDF0086EFD3 /* s3mfile.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8632525CF6CDF0086EFD3 /* s3mfile.h */; }; + EDE8632A25CF6CDF0086EFD3 /* s3mproperties.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8632625CF6CDF0086EFD3 /* s3mproperties.h */; }; + EDE8632B25CF6CDF0086EFD3 /* s3mproperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8632725CF6CDF0086EFD3 /* s3mproperties.cpp */; }; + EDE8632C25CF6CE00086EFD3 /* s3mfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8632825CF6CDF0086EFD3 /* s3mfile.cpp */; }; + EDE8633925CF6CF50086EFD3 /* infotag.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8632E25CF6CF50086EFD3 /* infotag.cpp */; }; + EDE8633A25CF6CF50086EFD3 /* infotag.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8632F25CF6CF50086EFD3 /* infotag.h */; }; + EDE8633B25CF6CF50086EFD3 /* wavfile.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8633025CF6CF50086EFD3 /* wavfile.h */; }; + EDE8633C25CF6CF50086EFD3 /* wavfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8633125CF6CF50086EFD3 /* wavfile.cpp */; }; + EDE8633D25CF6CF50086EFD3 /* wavproperties.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8633225CF6CF50086EFD3 /* wavproperties.h */; }; + EDE8633E25CF6CF50086EFD3 /* wavproperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8633325CF6CF50086EFD3 /* wavproperties.cpp */; }; + EDE8633F25CF6CF50086EFD3 /* aiffproperties.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8633525CF6CF50086EFD3 /* aiffproperties.h */; }; + EDE8634025CF6CF50086EFD3 /* aiffproperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8633625CF6CF50086EFD3 /* aiffproperties.cpp */; }; + EDE8634125CF6CF50086EFD3 /* aifffile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8633725CF6CF50086EFD3 /* aifffile.cpp */; }; + EDE8634225CF6CF50086EFD3 /* aifffile.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8633825CF6CF50086EFD3 /* aifffile.h */; }; + EDE8634525CF6D1C0086EFD3 /* tagutils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8634325CF6D1C0086EFD3 /* tagutils.cpp */; }; + EDE8634625CF6D1C0086EFD3 /* tagutils.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8634425CF6D1C0086EFD3 /* tagutils.h */; }; + EDE8635325CF6D3D0086EFD3 /* tbytevectorstream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8634725CF6D3D0086EFD3 /* tbytevectorstream.cpp */; }; + EDE8635425CF6D3D0086EFD3 /* tfilestream.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8634825CF6D3D0086EFD3 /* tfilestream.h */; }; + EDE8635525CF6D3D0086EFD3 /* tdebuglistener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8634925CF6D3D0086EFD3 /* tdebuglistener.cpp */; }; + EDE8635625CF6D3D0086EFD3 /* tpropertymap.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8634A25CF6D3D0086EFD3 /* tpropertymap.h */; }; + EDE8635725CF6D3D0086EFD3 /* trefcounter.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8634B25CF6D3D0086EFD3 /* trefcounter.h */; }; + EDE8635825CF6D3D0086EFD3 /* tdebuglistener.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8634C25CF6D3D0086EFD3 /* tdebuglistener.h */; }; + EDE8635925CF6D3D0086EFD3 /* tbytevectorstream.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8634D25CF6D3D0086EFD3 /* tbytevectorstream.h */; }; + EDE8635A25CF6D3D0086EFD3 /* tutils.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8634E25CF6D3D0086EFD3 /* tutils.h */; }; + EDE8635B25CF6D3D0086EFD3 /* tiostream.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8634F25CF6D3D0086EFD3 /* tiostream.h */; }; + EDE8635C25CF6D3D0086EFD3 /* tiostream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8635025CF6D3D0086EFD3 /* tiostream.cpp */; }; + EDE8635D25CF6D3D0086EFD3 /* tzlib.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8635125CF6D3D0086EFD3 /* tzlib.cpp */; }; + EDE8635E25CF6D3D0086EFD3 /* tzlib.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8635225CF6D3D0086EFD3 /* tzlib.h */; }; + EDE863A525CF6D710086EFD3 /* mpegfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8636025CF6D710086EFD3 /* mpegfile.cpp */; }; + EDE863A625CF6D710086EFD3 /* xingheader.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8636125CF6D710086EFD3 /* xingheader.h */; }; + EDE863A725CF6D710086EFD3 /* id3v2footer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8636325CF6D710086EFD3 /* id3v2footer.cpp */; }; + EDE863A825CF6D710086EFD3 /* id3v2footer.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8636425CF6D710086EFD3 /* id3v2footer.h */; }; + EDE863A925CF6D710086EFD3 /* id3v2extendedheader.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8636525CF6D710086EFD3 /* id3v2extendedheader.h */; }; + EDE863AA25CF6D710086EFD3 /* id3v2.4.0-structure.txt in Resources */ = {isa = PBXBuildFile; fileRef = EDE8636625CF6D710086EFD3 /* id3v2.4.0-structure.txt */; }; + EDE863AB25CF6D710086EFD3 /* id3v2header.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8636725CF6D710086EFD3 /* id3v2header.h */; }; + EDE863AC25CF6D710086EFD3 /* id3v2.2.0.txt in Resources */ = {isa = PBXBuildFile; fileRef = EDE8636825CF6D710086EFD3 /* id3v2.2.0.txt */; }; + EDE863AD25CF6D710086EFD3 /* id3v2frame.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8636925CF6D710086EFD3 /* id3v2frame.h */; }; + EDE863AE25CF6D710086EFD3 /* id3v2framefactory.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8636A25CF6D710086EFD3 /* id3v2framefactory.h */; }; + EDE863AF25CF6D710086EFD3 /* id3v2header.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8636B25CF6D710086EFD3 /* id3v2header.cpp */; }; + EDE863B025CF6D710086EFD3 /* id3v2frame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8636C25CF6D710086EFD3 /* id3v2frame.cpp */; }; + EDE863B125CF6D710086EFD3 /* synchronizedlyricsframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8636E25CF6D710086EFD3 /* synchronizedlyricsframe.cpp */; }; + EDE863B225CF6D710086EFD3 /* uniquefileidentifierframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8636F25CF6D710086EFD3 /* uniquefileidentifierframe.cpp */; }; + EDE863B325CF6D710086EFD3 /* privateframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8637025CF6D710086EFD3 /* privateframe.cpp */; }; + EDE863B425CF6D710086EFD3 /* attachedpictureframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8637125CF6D710086EFD3 /* attachedpictureframe.h */; }; + EDE863B525CF6D710086EFD3 /* unknownframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8637225CF6D710086EFD3 /* unknownframe.h */; }; + EDE863B625CF6D710086EFD3 /* unknownframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8637325CF6D710086EFD3 /* unknownframe.cpp */; }; + EDE863B725CF6D710086EFD3 /* unsynchronizedlyricsframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8637425CF6D710086EFD3 /* unsynchronizedlyricsframe.h */; }; + EDE863B825CF6D710086EFD3 /* generalencapsulatedobjectframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8637525CF6D710086EFD3 /* generalencapsulatedobjectframe.h */; }; + EDE863B925CF6D710086EFD3 /* eventtimingcodesframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8637625CF6D710086EFD3 /* eventtimingcodesframe.cpp */; }; + EDE863BA25CF6D710086EFD3 /* unsynchronizedlyricsframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8637725CF6D710086EFD3 /* unsynchronizedlyricsframe.cpp */; }; + EDE863BB25CF6D710086EFD3 /* tableofcontentsframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8637825CF6D710086EFD3 /* tableofcontentsframe.h */; }; + EDE863BC25CF6D710086EFD3 /* podcastframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8637925CF6D710086EFD3 /* podcastframe.h */; }; + EDE863BD25CF6D710086EFD3 /* podcastframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8637A25CF6D710086EFD3 /* podcastframe.cpp */; }; + EDE863BE25CF6D710086EFD3 /* synchronizedlyricsframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8637B25CF6D710086EFD3 /* synchronizedlyricsframe.h */; }; + EDE863BF25CF6D710086EFD3 /* commentsframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8637C25CF6D710086EFD3 /* commentsframe.cpp */; }; + EDE863C025CF6D710086EFD3 /* chapterframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8637D25CF6D710086EFD3 /* chapterframe.cpp */; }; + EDE863C125CF6D710086EFD3 /* tableofcontentsframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8637E25CF6D710086EFD3 /* tableofcontentsframe.cpp */; }; + EDE863C225CF6D710086EFD3 /* generalencapsulatedobjectframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8637F25CF6D710086EFD3 /* generalencapsulatedobjectframe.cpp */; }; + EDE863C325CF6D710086EFD3 /* commentsframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8638025CF6D710086EFD3 /* commentsframe.h */; }; + EDE863C425CF6D710086EFD3 /* relativevolumeframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8638125CF6D710086EFD3 /* relativevolumeframe.cpp */; }; + EDE863C525CF6D710086EFD3 /* popularimeterframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8638225CF6D710086EFD3 /* popularimeterframe.h */; }; + EDE863C625CF6D710086EFD3 /* ownershipframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8638325CF6D710086EFD3 /* ownershipframe.cpp */; }; + EDE863C725CF6D710086EFD3 /* attachedpictureframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8638425CF6D710086EFD3 /* attachedpictureframe.cpp */; }; + EDE863C825CF6D710086EFD3 /* relativevolumeframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8638525CF6D710086EFD3 /* relativevolumeframe.h */; }; + EDE863C925CF6D710086EFD3 /* privateframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8638625CF6D710086EFD3 /* privateframe.h */; }; + EDE863CA25CF6D710086EFD3 /* eventtimingcodesframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8638725CF6D710086EFD3 /* eventtimingcodesframe.h */; }; + EDE863CB25CF6D710086EFD3 /* chapterframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8638825CF6D710086EFD3 /* chapterframe.h */; }; + EDE863CC25CF6D710086EFD3 /* ownershipframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8638925CF6D710086EFD3 /* ownershipframe.h */; }; + EDE863CD25CF6D710086EFD3 /* textidentificationframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8638A25CF6D710086EFD3 /* textidentificationframe.cpp */; }; + EDE863CE25CF6D710086EFD3 /* urllinkframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8638B25CF6D710086EFD3 /* urllinkframe.cpp */; }; + EDE863CF25CF6D710086EFD3 /* uniquefileidentifierframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8638C25CF6D710086EFD3 /* uniquefileidentifierframe.h */; }; + EDE863D025CF6D710086EFD3 /* textidentificationframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8638D25CF6D710086EFD3 /* textidentificationframe.h */; }; + EDE863D125CF6D710086EFD3 /* popularimeterframe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8638E25CF6D710086EFD3 /* popularimeterframe.cpp */; }; + EDE863D225CF6D710086EFD3 /* urllinkframe.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8638F25CF6D710086EFD3 /* urllinkframe.h */; }; + EDE863D325CF6D710086EFD3 /* id3v2synchdata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8639025CF6D710086EFD3 /* id3v2synchdata.cpp */; }; + EDE863D425CF6D710086EFD3 /* id3v2.3.0.txt in Resources */ = {isa = PBXBuildFile; fileRef = EDE8639125CF6D710086EFD3 /* id3v2.3.0.txt */; }; + EDE863D525CF6D710086EFD3 /* id3v2framefactory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8639225CF6D710086EFD3 /* id3v2framefactory.cpp */; }; + EDE863D625CF6D710086EFD3 /* id3v2synchdata.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8639325CF6D710086EFD3 /* id3v2synchdata.h */; }; + EDE863D725CF6D710086EFD3 /* id3v2.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8639425CF6D710086EFD3 /* id3v2.h */; }; + EDE863D825CF6D710086EFD3 /* id3v2extendedheader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8639525CF6D710086EFD3 /* id3v2extendedheader.cpp */; }; + EDE863D925CF6D710086EFD3 /* id3v2tag.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8639625CF6D710086EFD3 /* id3v2tag.cpp */; }; + EDE863DA25CF6D710086EFD3 /* id3v2.4.0-frames.txt in Resources */ = {isa = PBXBuildFile; fileRef = EDE8639725CF6D710086EFD3 /* id3v2.4.0-frames.txt */; }; + EDE863DB25CF6D710086EFD3 /* id3v2tag.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8639825CF6D710086EFD3 /* id3v2tag.h */; }; + EDE863DC25CF6D710086EFD3 /* mpegheader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8639925CF6D710086EFD3 /* mpegheader.cpp */; }; + EDE863DD25CF6D710086EFD3 /* mpegutils.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8639A25CF6D710086EFD3 /* mpegutils.h */; }; + EDE863DE25CF6D710086EFD3 /* xingheader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE8639B25CF6D710086EFD3 /* xingheader.cpp */; }; + EDE863DF25CF6D710086EFD3 /* mpegproperties.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8639C25CF6D710086EFD3 /* mpegproperties.h */; }; + EDE863E025CF6D710086EFD3 /* mpegheader.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8639D25CF6D710086EFD3 /* mpegheader.h */; }; + EDE863E125CF6D710086EFD3 /* mpegfile.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE8639E25CF6D710086EFD3 /* mpegfile.h */; }; + EDE863E225CF6D710086EFD3 /* id3v1tag.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE863A025CF6D710086EFD3 /* id3v1tag.h */; }; + EDE863E325CF6D710086EFD3 /* id3v1genres.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE863A125CF6D710086EFD3 /* id3v1genres.cpp */; }; + EDE863E425CF6D710086EFD3 /* id3v1tag.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE863A225CF6D710086EFD3 /* id3v1tag.cpp */; }; + EDE863E525CF6D710086EFD3 /* id3v1genres.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE863A325CF6D710086EFD3 /* id3v1genres.h */; }; + EDE863E625CF6D710086EFD3 /* mpegproperties.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDE863A425CF6D710086EFD3 /* mpegproperties.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -312,13 +412,8 @@ 32AE5A2D14E70ED600420CA0 /* aiffproperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aiffproperties.h; sourceTree = ""; }; 32AE5A2E14E70ED600420CA0 /* rifffile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rifffile.cpp; sourceTree = ""; }; 32AE5A2F14E70ED600420CA0 /* rifffile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rifffile.h; sourceTree = ""; }; - 32AE5A3114E70ED600420CA0 /* wavfile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wavfile.cpp; sourceTree = ""; }; - 32AE5A3214E70ED600420CA0 /* wavfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wavfile.h; sourceTree = ""; }; - 32AE5A3314E70ED600420CA0 /* wavproperties.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wavproperties.cpp; sourceTree = ""; }; - 32AE5A3414E70ED600420CA0 /* wavproperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wavproperties.h; sourceTree = ""; }; 32AE5A3514E70ED600420CA0 /* tag.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tag.cpp; sourceTree = ""; }; 32AE5A3614E70ED600420CA0 /* tag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tag.h; sourceTree = ""; }; - 32AE5A3714E70ED600420CA0 /* taglib.pro */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = taglib.pro; sourceTree = ""; }; 32AE5A3814E70ED600420CA0 /* taglib_export.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = taglib_export.h; sourceTree = ""; }; 32AE5A3914E70ED600420CA0 /* tagunion.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tagunion.cpp; sourceTree = ""; }; 32AE5A3A14E70ED600420CA0 /* tagunion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tagunion.h; sourceTree = ""; }; @@ -339,8 +434,6 @@ 32AE5A4A14E70ED600420CA0 /* tstring.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tstring.h; sourceTree = ""; }; 32AE5A4B14E70ED600420CA0 /* tstringlist.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tstringlist.cpp; sourceTree = ""; }; 32AE5A4C14E70ED600420CA0 /* tstringlist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tstringlist.h; sourceTree = ""; }; - 32AE5A4D14E70ED600420CA0 /* unicode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unicode.cpp; sourceTree = ""; }; - 32AE5A4E14E70ED600420CA0 /* unicode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = unicode.h; sourceTree = ""; }; 32AE5A5014E70ED600420CA0 /* trueaudiofile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = trueaudiofile.cpp; sourceTree = ""; }; 32AE5A5114E70ED600420CA0 /* trueaudiofile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = trueaudiofile.h; sourceTree = ""; }; 32AE5A5214E70ED600420CA0 /* trueaudioproperties.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = trueaudioproperties.cpp; sourceTree = ""; }; @@ -359,6 +452,118 @@ 83790D231809E8CA0073CF51 /* opusproperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = opusproperties.h; sourceTree = ""; }; 8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8DC2EF5B0486A6940098B216 /* TagLib.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TagLib.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EDE862FC25CF6BD60086EFD3 /* tpropertymap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tpropertymap.cpp; sourceTree = ""; }; + EDE8630025CF6C260086EFD3 /* tfilestream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tfilestream.cpp; sourceTree = ""; }; + EDE8630125CF6C260086EFD3 /* trefcounter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = trefcounter.cpp; sourceTree = ""; }; + EDE8630425CF6C5B0086EFD3 /* itproperties.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = itproperties.cpp; path = taglib/taglib/it/itproperties.cpp; sourceTree = SOURCE_ROOT; }; + EDE8630525CF6C5B0086EFD3 /* itfile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = itfile.cpp; path = taglib/taglib/it/itfile.cpp; sourceTree = SOURCE_ROOT; }; + EDE8630925CF6CAE0086EFD3 /* xmfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xmfile.h; sourceTree = ""; }; + EDE8630A25CF6CAE0086EFD3 /* xmfile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xmfile.cpp; sourceTree = ""; }; + EDE8630B25CF6CAE0086EFD3 /* xmproperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xmproperties.h; sourceTree = ""; }; + EDE8630C25CF6CAE0086EFD3 /* xmproperties.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xmproperties.cpp; sourceTree = ""; }; + EDE8631225CF6CC60086EFD3 /* modproperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modproperties.h; sourceTree = ""; }; + EDE8631325CF6CC60086EFD3 /* modfileprivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modfileprivate.h; sourceTree = ""; }; + EDE8631425CF6CC60086EFD3 /* modtag.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = modtag.cpp; sourceTree = ""; }; + EDE8631525CF6CC60086EFD3 /* modfilebase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = modfilebase.cpp; sourceTree = ""; }; + EDE8631625CF6CC60086EFD3 /* modfile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = modfile.cpp; sourceTree = ""; }; + EDE8631725CF6CC60086EFD3 /* modfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modfile.h; sourceTree = ""; }; + EDE8631825CF6CC60086EFD3 /* modtag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modtag.h; sourceTree = ""; }; + EDE8631925CF6CC60086EFD3 /* modproperties.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = modproperties.cpp; sourceTree = ""; }; + EDE8631A25CF6CC60086EFD3 /* modfilebase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modfilebase.h; sourceTree = ""; }; + EDE8632525CF6CDF0086EFD3 /* s3mfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = s3mfile.h; sourceTree = ""; }; + EDE8632625CF6CDF0086EFD3 /* s3mproperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = s3mproperties.h; sourceTree = ""; }; + EDE8632725CF6CDF0086EFD3 /* s3mproperties.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = s3mproperties.cpp; sourceTree = ""; }; + EDE8632825CF6CDF0086EFD3 /* s3mfile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = s3mfile.cpp; sourceTree = ""; }; + EDE8632E25CF6CF50086EFD3 /* infotag.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = infotag.cpp; sourceTree = ""; }; + EDE8632F25CF6CF50086EFD3 /* infotag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = infotag.h; sourceTree = ""; }; + EDE8633025CF6CF50086EFD3 /* wavfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wavfile.h; sourceTree = ""; }; + EDE8633125CF6CF50086EFD3 /* wavfile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wavfile.cpp; sourceTree = ""; }; + EDE8633225CF6CF50086EFD3 /* wavproperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wavproperties.h; sourceTree = ""; }; + EDE8633325CF6CF50086EFD3 /* wavproperties.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wavproperties.cpp; sourceTree = ""; }; + EDE8633525CF6CF50086EFD3 /* aiffproperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aiffproperties.h; sourceTree = ""; }; + EDE8633625CF6CF50086EFD3 /* aiffproperties.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = aiffproperties.cpp; sourceTree = ""; }; + EDE8633725CF6CF50086EFD3 /* aifffile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = aifffile.cpp; sourceTree = ""; }; + EDE8633825CF6CF50086EFD3 /* aifffile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aifffile.h; sourceTree = ""; }; + EDE8634325CF6D1C0086EFD3 /* tagutils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tagutils.cpp; sourceTree = ""; }; + EDE8634425CF6D1C0086EFD3 /* tagutils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tagutils.h; sourceTree = ""; }; + EDE8634725CF6D3D0086EFD3 /* tbytevectorstream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tbytevectorstream.cpp; sourceTree = ""; }; + EDE8634825CF6D3D0086EFD3 /* tfilestream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tfilestream.h; sourceTree = ""; }; + EDE8634925CF6D3D0086EFD3 /* tdebuglistener.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tdebuglistener.cpp; sourceTree = ""; }; + EDE8634A25CF6D3D0086EFD3 /* tpropertymap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tpropertymap.h; sourceTree = ""; }; + EDE8634B25CF6D3D0086EFD3 /* trefcounter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = trefcounter.h; sourceTree = ""; }; + EDE8634C25CF6D3D0086EFD3 /* tdebuglistener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tdebuglistener.h; sourceTree = ""; }; + EDE8634D25CF6D3D0086EFD3 /* tbytevectorstream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tbytevectorstream.h; sourceTree = ""; }; + EDE8634E25CF6D3D0086EFD3 /* tutils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tutils.h; sourceTree = ""; }; + EDE8634F25CF6D3D0086EFD3 /* tiostream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tiostream.h; sourceTree = ""; }; + EDE8635025CF6D3D0086EFD3 /* tiostream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tiostream.cpp; sourceTree = ""; }; + EDE8635125CF6D3D0086EFD3 /* tzlib.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tzlib.cpp; sourceTree = ""; }; + EDE8635225CF6D3D0086EFD3 /* tzlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tzlib.h; sourceTree = ""; }; + EDE8636025CF6D710086EFD3 /* mpegfile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mpegfile.cpp; sourceTree = ""; }; + EDE8636125CF6D710086EFD3 /* xingheader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xingheader.h; sourceTree = ""; }; + EDE8636325CF6D710086EFD3 /* id3v2footer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = id3v2footer.cpp; sourceTree = ""; }; + EDE8636425CF6D710086EFD3 /* id3v2footer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id3v2footer.h; sourceTree = ""; }; + EDE8636525CF6D710086EFD3 /* id3v2extendedheader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id3v2extendedheader.h; sourceTree = ""; }; + EDE8636625CF6D710086EFD3 /* id3v2.4.0-structure.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "id3v2.4.0-structure.txt"; sourceTree = ""; }; + EDE8636725CF6D710086EFD3 /* id3v2header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id3v2header.h; sourceTree = ""; }; + EDE8636825CF6D710086EFD3 /* id3v2.2.0.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = id3v2.2.0.txt; sourceTree = ""; }; + EDE8636925CF6D710086EFD3 /* id3v2frame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id3v2frame.h; sourceTree = ""; }; + EDE8636A25CF6D710086EFD3 /* id3v2framefactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id3v2framefactory.h; sourceTree = ""; }; + EDE8636B25CF6D710086EFD3 /* id3v2header.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = id3v2header.cpp; sourceTree = ""; }; + EDE8636C25CF6D710086EFD3 /* id3v2frame.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = id3v2frame.cpp; sourceTree = ""; }; + EDE8636E25CF6D710086EFD3 /* synchronizedlyricsframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = synchronizedlyricsframe.cpp; sourceTree = ""; }; + EDE8636F25CF6D710086EFD3 /* uniquefileidentifierframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = uniquefileidentifierframe.cpp; sourceTree = ""; }; + EDE8637025CF6D710086EFD3 /* privateframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = privateframe.cpp; sourceTree = ""; }; + EDE8637125CF6D710086EFD3 /* attachedpictureframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = attachedpictureframe.h; sourceTree = ""; }; + EDE8637225CF6D710086EFD3 /* unknownframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = unknownframe.h; sourceTree = ""; }; + EDE8637325CF6D710086EFD3 /* unknownframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unknownframe.cpp; sourceTree = ""; }; + EDE8637425CF6D710086EFD3 /* unsynchronizedlyricsframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = unsynchronizedlyricsframe.h; sourceTree = ""; }; + EDE8637525CF6D710086EFD3 /* generalencapsulatedobjectframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = generalencapsulatedobjectframe.h; sourceTree = ""; }; + EDE8637625CF6D710086EFD3 /* eventtimingcodesframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = eventtimingcodesframe.cpp; sourceTree = ""; }; + EDE8637725CF6D710086EFD3 /* unsynchronizedlyricsframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = unsynchronizedlyricsframe.cpp; sourceTree = ""; }; + EDE8637825CF6D710086EFD3 /* tableofcontentsframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tableofcontentsframe.h; sourceTree = ""; }; + EDE8637925CF6D710086EFD3 /* podcastframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = podcastframe.h; sourceTree = ""; }; + EDE8637A25CF6D710086EFD3 /* podcastframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = podcastframe.cpp; sourceTree = ""; }; + EDE8637B25CF6D710086EFD3 /* synchronizedlyricsframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = synchronizedlyricsframe.h; sourceTree = ""; }; + EDE8637C25CF6D710086EFD3 /* commentsframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = commentsframe.cpp; sourceTree = ""; }; + EDE8637D25CF6D710086EFD3 /* chapterframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = chapterframe.cpp; sourceTree = ""; }; + EDE8637E25CF6D710086EFD3 /* tableofcontentsframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tableofcontentsframe.cpp; sourceTree = ""; }; + EDE8637F25CF6D710086EFD3 /* generalencapsulatedobjectframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = generalencapsulatedobjectframe.cpp; sourceTree = ""; }; + EDE8638025CF6D710086EFD3 /* commentsframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = commentsframe.h; sourceTree = ""; }; + EDE8638125CF6D710086EFD3 /* relativevolumeframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = relativevolumeframe.cpp; sourceTree = ""; }; + EDE8638225CF6D710086EFD3 /* popularimeterframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = popularimeterframe.h; sourceTree = ""; }; + EDE8638325CF6D710086EFD3 /* ownershipframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ownershipframe.cpp; sourceTree = ""; }; + EDE8638425CF6D710086EFD3 /* attachedpictureframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = attachedpictureframe.cpp; sourceTree = ""; }; + EDE8638525CF6D710086EFD3 /* relativevolumeframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = relativevolumeframe.h; sourceTree = ""; }; + EDE8638625CF6D710086EFD3 /* privateframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = privateframe.h; sourceTree = ""; }; + EDE8638725CF6D710086EFD3 /* eventtimingcodesframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = eventtimingcodesframe.h; sourceTree = ""; }; + EDE8638825CF6D710086EFD3 /* chapterframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = chapterframe.h; sourceTree = ""; }; + EDE8638925CF6D710086EFD3 /* ownershipframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ownershipframe.h; sourceTree = ""; }; + EDE8638A25CF6D710086EFD3 /* textidentificationframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = textidentificationframe.cpp; sourceTree = ""; }; + EDE8638B25CF6D710086EFD3 /* urllinkframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = urllinkframe.cpp; sourceTree = ""; }; + EDE8638C25CF6D710086EFD3 /* uniquefileidentifierframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uniquefileidentifierframe.h; sourceTree = ""; }; + EDE8638D25CF6D710086EFD3 /* textidentificationframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = textidentificationframe.h; sourceTree = ""; }; + EDE8638E25CF6D710086EFD3 /* popularimeterframe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = popularimeterframe.cpp; sourceTree = ""; }; + EDE8638F25CF6D710086EFD3 /* urllinkframe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = urllinkframe.h; sourceTree = ""; }; + EDE8639025CF6D710086EFD3 /* id3v2synchdata.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = id3v2synchdata.cpp; sourceTree = ""; }; + EDE8639125CF6D710086EFD3 /* id3v2.3.0.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = id3v2.3.0.txt; sourceTree = ""; }; + EDE8639225CF6D710086EFD3 /* id3v2framefactory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = id3v2framefactory.cpp; sourceTree = ""; }; + EDE8639325CF6D710086EFD3 /* id3v2synchdata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id3v2synchdata.h; sourceTree = ""; }; + EDE8639425CF6D710086EFD3 /* id3v2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id3v2.h; sourceTree = ""; }; + EDE8639525CF6D710086EFD3 /* id3v2extendedheader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = id3v2extendedheader.cpp; sourceTree = ""; }; + EDE8639625CF6D710086EFD3 /* id3v2tag.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = id3v2tag.cpp; sourceTree = ""; }; + EDE8639725CF6D710086EFD3 /* id3v2.4.0-frames.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "id3v2.4.0-frames.txt"; sourceTree = ""; }; + EDE8639825CF6D710086EFD3 /* id3v2tag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id3v2tag.h; sourceTree = ""; }; + EDE8639925CF6D710086EFD3 /* mpegheader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mpegheader.cpp; sourceTree = ""; }; + EDE8639A25CF6D710086EFD3 /* mpegutils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mpegutils.h; sourceTree = ""; }; + EDE8639B25CF6D710086EFD3 /* xingheader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xingheader.cpp; sourceTree = ""; }; + EDE8639C25CF6D710086EFD3 /* mpegproperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mpegproperties.h; sourceTree = ""; }; + EDE8639D25CF6D710086EFD3 /* mpegheader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mpegheader.h; sourceTree = ""; }; + EDE8639E25CF6D710086EFD3 /* mpegfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mpegfile.h; sourceTree = ""; }; + EDE863A025CF6D710086EFD3 /* id3v1tag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id3v1tag.h; sourceTree = ""; }; + EDE863A125CF6D710086EFD3 /* id3v1genres.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = id3v1genres.cpp; sourceTree = ""; }; + EDE863A225CF6D710086EFD3 /* id3v1tag.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = id3v1tag.cpp; sourceTree = ""; }; + EDE863A325CF6D710086EFD3 /* id3v1genres.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id3v1genres.h; sourceTree = ""; }; + EDE863A425CF6D710086EFD3 /* mpegproperties.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mpegproperties.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -430,6 +635,12 @@ 32AE59A114E70ED600420CA0 /* taglib */ = { isa = PBXGroup; children = ( + EDE8635F25CF6D710086EFD3 /* mpeg */, + EDE8634325CF6D1C0086EFD3 /* tagutils.cpp */, + EDE8634425CF6D1C0086EFD3 /* tagutils.h */, + EDE8632425CF6CDF0086EFD3 /* s3m */, + EDE8631125CF6CC60086EFD3 /* mod */, + EDE8630825CF6CAE0086EFD3 /* xm */, 32AE59A214E70ED600420CA0 /* ape */, 32AE59AE14E70ED600420CA0 /* asf */, 32AE59B914E70ED600420CA0 /* audioproperties.cpp */, @@ -444,7 +655,6 @@ 32AE5A2814E70ED600420CA0 /* riff */, 32AE5A3514E70ED600420CA0 /* tag.cpp */, 32AE5A3614E70ED600420CA0 /* tag.h */, - 32AE5A3714E70ED600420CA0 /* taglib.pro */, 32AE5A3814E70ED600420CA0 /* taglib_export.h */, 32AE5A3914E70ED600420CA0 /* tagunion.cpp */, 32AE5A3A14E70ED600420CA0 /* tagunion.h */, @@ -674,10 +884,13 @@ 32AE5A2814E70ED600420CA0 /* riff */ = { isa = PBXGroup; children = ( + EDE8633425CF6CF50086EFD3 /* aiff */, + EDE8632D25CF6CF50086EFD3 /* wav */, + EDE8630525CF6C5B0086EFD3 /* itfile.cpp */, + EDE8630425CF6C5B0086EFD3 /* itproperties.cpp */, 32AE5A2914E70ED600420CA0 /* aiff */, 32AE5A2E14E70ED600420CA0 /* rifffile.cpp */, 32AE5A2F14E70ED600420CA0 /* rifffile.h */, - 32AE5A3014E70ED600420CA0 /* wav */, ); path = riff; sourceTree = ""; @@ -693,20 +906,24 @@ path = aiff; sourceTree = ""; }; - 32AE5A3014E70ED600420CA0 /* wav */ = { - isa = PBXGroup; - children = ( - 32AE5A3114E70ED600420CA0 /* wavfile.cpp */, - 32AE5A3214E70ED600420CA0 /* wavfile.h */, - 32AE5A3314E70ED600420CA0 /* wavproperties.cpp */, - 32AE5A3414E70ED600420CA0 /* wavproperties.h */, - ); - path = wav; - sourceTree = ""; - }; 32AE5A3B14E70ED600420CA0 /* toolkit */ = { isa = PBXGroup; children = ( + EDE8634725CF6D3D0086EFD3 /* tbytevectorstream.cpp */, + EDE8634D25CF6D3D0086EFD3 /* tbytevectorstream.h */, + EDE8634925CF6D3D0086EFD3 /* tdebuglistener.cpp */, + EDE8634C25CF6D3D0086EFD3 /* tdebuglistener.h */, + EDE8634825CF6D3D0086EFD3 /* tfilestream.h */, + EDE8635025CF6D3D0086EFD3 /* tiostream.cpp */, + EDE8634F25CF6D3D0086EFD3 /* tiostream.h */, + EDE8634A25CF6D3D0086EFD3 /* tpropertymap.h */, + EDE8634B25CF6D3D0086EFD3 /* trefcounter.h */, + EDE8634E25CF6D3D0086EFD3 /* tutils.h */, + EDE8635125CF6D3D0086EFD3 /* tzlib.cpp */, + EDE8635225CF6D3D0086EFD3 /* tzlib.h */, + EDE8630025CF6C260086EFD3 /* tfilestream.cpp */, + EDE8630125CF6C260086EFD3 /* trefcounter.cpp */, + EDE862FC25CF6BD60086EFD3 /* tpropertymap.cpp */, 32AE5A3C14E70ED600420CA0 /* taglib.h */, 32AE5A3D14E70ED600420CA0 /* tbytevector.cpp */, 32AE5A3E14E70ED600420CA0 /* tbytevector.h */, @@ -724,8 +941,6 @@ 32AE5A4A14E70ED600420CA0 /* tstring.h */, 32AE5A4B14E70ED600420CA0 /* tstringlist.cpp */, 32AE5A4C14E70ED600420CA0 /* tstringlist.h */, - 32AE5A4D14E70ED600420CA0 /* unicode.cpp */, - 32AE5A4E14E70ED600420CA0 /* unicode.h */, ); path = toolkit; sourceTree = ""; @@ -773,6 +988,165 @@ name = Source; sourceTree = ""; }; + EDE8630825CF6CAE0086EFD3 /* xm */ = { + isa = PBXGroup; + children = ( + EDE8630925CF6CAE0086EFD3 /* xmfile.h */, + EDE8630A25CF6CAE0086EFD3 /* xmfile.cpp */, + EDE8630B25CF6CAE0086EFD3 /* xmproperties.h */, + EDE8630C25CF6CAE0086EFD3 /* xmproperties.cpp */, + ); + path = xm; + sourceTree = ""; + }; + EDE8631125CF6CC60086EFD3 /* mod */ = { + isa = PBXGroup; + children = ( + EDE8631225CF6CC60086EFD3 /* modproperties.h */, + EDE8631325CF6CC60086EFD3 /* modfileprivate.h */, + EDE8631425CF6CC60086EFD3 /* modtag.cpp */, + EDE8631525CF6CC60086EFD3 /* modfilebase.cpp */, + EDE8631625CF6CC60086EFD3 /* modfile.cpp */, + EDE8631725CF6CC60086EFD3 /* modfile.h */, + EDE8631825CF6CC60086EFD3 /* modtag.h */, + EDE8631925CF6CC60086EFD3 /* modproperties.cpp */, + EDE8631A25CF6CC60086EFD3 /* modfilebase.h */, + ); + path = mod; + sourceTree = ""; + }; + EDE8632425CF6CDF0086EFD3 /* s3m */ = { + isa = PBXGroup; + children = ( + EDE8632525CF6CDF0086EFD3 /* s3mfile.h */, + EDE8632625CF6CDF0086EFD3 /* s3mproperties.h */, + EDE8632725CF6CDF0086EFD3 /* s3mproperties.cpp */, + EDE8632825CF6CDF0086EFD3 /* s3mfile.cpp */, + ); + path = s3m; + sourceTree = ""; + }; + EDE8632D25CF6CF50086EFD3 /* wav */ = { + isa = PBXGroup; + children = ( + EDE8632E25CF6CF50086EFD3 /* infotag.cpp */, + EDE8632F25CF6CF50086EFD3 /* infotag.h */, + EDE8633025CF6CF50086EFD3 /* wavfile.h */, + EDE8633125CF6CF50086EFD3 /* wavfile.cpp */, + EDE8633225CF6CF50086EFD3 /* wavproperties.h */, + EDE8633325CF6CF50086EFD3 /* wavproperties.cpp */, + ); + path = wav; + sourceTree = ""; + }; + EDE8633425CF6CF50086EFD3 /* aiff */ = { + isa = PBXGroup; + children = ( + EDE8633525CF6CF50086EFD3 /* aiffproperties.h */, + EDE8633625CF6CF50086EFD3 /* aiffproperties.cpp */, + EDE8633725CF6CF50086EFD3 /* aifffile.cpp */, + EDE8633825CF6CF50086EFD3 /* aifffile.h */, + ); + path = aiff; + sourceTree = ""; + }; + EDE8635F25CF6D710086EFD3 /* mpeg */ = { + isa = PBXGroup; + children = ( + EDE8636025CF6D710086EFD3 /* mpegfile.cpp */, + EDE8636125CF6D710086EFD3 /* xingheader.h */, + EDE8636225CF6D710086EFD3 /* id3v2 */, + EDE8639925CF6D710086EFD3 /* mpegheader.cpp */, + EDE8639A25CF6D710086EFD3 /* mpegutils.h */, + EDE8639B25CF6D710086EFD3 /* xingheader.cpp */, + EDE8639C25CF6D710086EFD3 /* mpegproperties.h */, + EDE8639D25CF6D710086EFD3 /* mpegheader.h */, + EDE8639E25CF6D710086EFD3 /* mpegfile.h */, + EDE8639F25CF6D710086EFD3 /* id3v1 */, + EDE863A425CF6D710086EFD3 /* mpegproperties.cpp */, + ); + path = mpeg; + sourceTree = ""; + }; + EDE8636225CF6D710086EFD3 /* id3v2 */ = { + isa = PBXGroup; + children = ( + EDE8636325CF6D710086EFD3 /* id3v2footer.cpp */, + EDE8636425CF6D710086EFD3 /* id3v2footer.h */, + EDE8636525CF6D710086EFD3 /* id3v2extendedheader.h */, + EDE8636625CF6D710086EFD3 /* id3v2.4.0-structure.txt */, + EDE8636725CF6D710086EFD3 /* id3v2header.h */, + EDE8636825CF6D710086EFD3 /* id3v2.2.0.txt */, + EDE8636925CF6D710086EFD3 /* id3v2frame.h */, + EDE8636A25CF6D710086EFD3 /* id3v2framefactory.h */, + EDE8636B25CF6D710086EFD3 /* id3v2header.cpp */, + EDE8636C25CF6D710086EFD3 /* id3v2frame.cpp */, + EDE8636D25CF6D710086EFD3 /* frames */, + EDE8639025CF6D710086EFD3 /* id3v2synchdata.cpp */, + EDE8639125CF6D710086EFD3 /* id3v2.3.0.txt */, + EDE8639225CF6D710086EFD3 /* id3v2framefactory.cpp */, + EDE8639325CF6D710086EFD3 /* id3v2synchdata.h */, + EDE8639425CF6D710086EFD3 /* id3v2.h */, + EDE8639525CF6D710086EFD3 /* id3v2extendedheader.cpp */, + EDE8639625CF6D710086EFD3 /* id3v2tag.cpp */, + EDE8639725CF6D710086EFD3 /* id3v2.4.0-frames.txt */, + EDE8639825CF6D710086EFD3 /* id3v2tag.h */, + ); + path = id3v2; + sourceTree = ""; + }; + EDE8636D25CF6D710086EFD3 /* frames */ = { + isa = PBXGroup; + children = ( + EDE8636E25CF6D710086EFD3 /* synchronizedlyricsframe.cpp */, + EDE8636F25CF6D710086EFD3 /* uniquefileidentifierframe.cpp */, + EDE8637025CF6D710086EFD3 /* privateframe.cpp */, + EDE8637125CF6D710086EFD3 /* attachedpictureframe.h */, + EDE8637225CF6D710086EFD3 /* unknownframe.h */, + EDE8637325CF6D710086EFD3 /* unknownframe.cpp */, + EDE8637425CF6D710086EFD3 /* unsynchronizedlyricsframe.h */, + EDE8637525CF6D710086EFD3 /* generalencapsulatedobjectframe.h */, + EDE8637625CF6D710086EFD3 /* eventtimingcodesframe.cpp */, + EDE8637725CF6D710086EFD3 /* unsynchronizedlyricsframe.cpp */, + EDE8637825CF6D710086EFD3 /* tableofcontentsframe.h */, + EDE8637925CF6D710086EFD3 /* podcastframe.h */, + EDE8637A25CF6D710086EFD3 /* podcastframe.cpp */, + EDE8637B25CF6D710086EFD3 /* synchronizedlyricsframe.h */, + EDE8637C25CF6D710086EFD3 /* commentsframe.cpp */, + EDE8637D25CF6D710086EFD3 /* chapterframe.cpp */, + EDE8637E25CF6D710086EFD3 /* tableofcontentsframe.cpp */, + EDE8637F25CF6D710086EFD3 /* generalencapsulatedobjectframe.cpp */, + EDE8638025CF6D710086EFD3 /* commentsframe.h */, + EDE8638125CF6D710086EFD3 /* relativevolumeframe.cpp */, + EDE8638225CF6D710086EFD3 /* popularimeterframe.h */, + EDE8638325CF6D710086EFD3 /* ownershipframe.cpp */, + EDE8638425CF6D710086EFD3 /* attachedpictureframe.cpp */, + EDE8638525CF6D710086EFD3 /* relativevolumeframe.h */, + EDE8638625CF6D710086EFD3 /* privateframe.h */, + EDE8638725CF6D710086EFD3 /* eventtimingcodesframe.h */, + EDE8638825CF6D710086EFD3 /* chapterframe.h */, + EDE8638925CF6D710086EFD3 /* ownershipframe.h */, + EDE8638A25CF6D710086EFD3 /* textidentificationframe.cpp */, + EDE8638B25CF6D710086EFD3 /* urllinkframe.cpp */, + EDE8638C25CF6D710086EFD3 /* uniquefileidentifierframe.h */, + EDE8638D25CF6D710086EFD3 /* textidentificationframe.h */, + EDE8638E25CF6D710086EFD3 /* popularimeterframe.cpp */, + EDE8638F25CF6D710086EFD3 /* urllinkframe.h */, + ); + path = frames; + sourceTree = ""; + }; + EDE8639F25CF6D710086EFD3 /* id3v1 */ = { + isa = PBXGroup; + children = ( + EDE863A025CF6D710086EFD3 /* id3v1tag.h */, + EDE863A125CF6D710086EFD3 /* id3v1genres.cpp */, + EDE863A225CF6D710086EFD3 /* id3v1tag.cpp */, + EDE863A325CF6D710086EFD3 /* id3v1genres.h */, + ); + path = id3v1; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -782,54 +1156,90 @@ files = ( 32AE5A5A14E70ED600420CA0 /* config.h in Headers */, 32AE5A5D14E70ED600420CA0 /* apefile.h in Headers */, + EDE8634225CF6CF50086EFD3 /* aifffile.h in Headers */, + EDE8635625CF6D3D0086EFD3 /* tpropertymap.h in Headers */, 32AE5A5F14E70ED600420CA0 /* apefooter.h in Headers */, 32AE5A6114E70ED600420CA0 /* apeitem.h in Headers */, 32AE5A6314E70ED600420CA0 /* apeproperties.h in Headers */, + EDE8635925CF6D3D0086EFD3 /* tbytevectorstream.h in Headers */, 32AE5A6514E70ED600420CA0 /* apetag.h in Headers */, + EDE8634625CF6D1C0086EFD3 /* tagutils.h in Headers */, 32AE5A6714E70ED600420CA0 /* asfattribute.h in Headers */, 32AE5A6914E70ED600420CA0 /* asffile.h in Headers */, 32AE5A6B14E70ED600420CA0 /* asfpicture.h in Headers */, 32AE5A6D14E70ED600420CA0 /* asfproperties.h in Headers */, + EDE8633D25CF6CF50086EFD3 /* wavproperties.h in Headers */, 32AE5A6F14E70ED600420CA0 /* asftag.h in Headers */, 32AE5A7114E70ED600420CA0 /* audioproperties.h in Headers */, + EDE863A625CF6D710086EFD3 /* xingheader.h in Headers */, 32AE5A7314E70ED600420CA0 /* fileref.h in Headers */, + EDE8630F25CF6CAE0086EFD3 /* xmproperties.h in Headers */, + EDE863AE25CF6D710086EFD3 /* id3v2framefactory.h in Headers */, + EDE863CF25CF6D710086EFD3 /* uniquefileidentifierframe.h in Headers */, 32AE5A7514E70ED600420CA0 /* flacfile.h in Headers */, + EDE863CC25CF6D710086EFD3 /* ownershipframe.h in Headers */, + EDE863E525CF6D710086EFD3 /* id3v1genres.h in Headers */, 83790D271809E8CA0073CF51 /* opusproperties.h in Headers */, 32AE5A7714E70ED600420CA0 /* flacmetadatablock.h in Headers */, 32AE5A7914E70ED600420CA0 /* flacpicture.h in Headers */, + EDE8632325CF6CC60086EFD3 /* modfilebase.h in Headers */, 32AE5A7B14E70ED600420CA0 /* flacproperties.h in Headers */, + EDE8631C25CF6CC60086EFD3 /* modfileprivate.h in Headers */, 32AE5A7D14E70ED600420CA0 /* flacunknownmetadatablock.h in Headers */, 32AE5A7F14E70ED600420CA0 /* mp4atom.h in Headers */, + EDE863C525CF6D710086EFD3 /* popularimeterframe.h in Headers */, + EDE863B825CF6D710086EFD3 /* generalencapsulatedobjectframe.h in Headers */, 32AE5A8114E70ED600420CA0 /* mp4coverart.h in Headers */, 32AE5A8314E70ED600420CA0 /* mp4file.h in Headers */, + EDE863B425CF6D710086EFD3 /* attachedpictureframe.h in Headers */, + EDE8632A25CF6CDF0086EFD3 /* s3mproperties.h in Headers */, 32AE5A8514E70ED600420CA0 /* mp4item.h in Headers */, 32AE5A8714E70ED600420CA0 /* mp4properties.h in Headers */, 32AE5A8914E70ED600420CA0 /* mp4tag.h in Headers */, + EDE863BB25CF6D710086EFD3 /* tableofcontentsframe.h in Headers */, 32AE5A8B14E70ED600420CA0 /* mpcfile.h in Headers */, 32AE5A8D14E70ED600420CA0 /* mpcproperties.h in Headers */, 32AE5A8F14E70ED600420CA0 /* id3v1genres.h in Headers */, + EDE863AB25CF6D710086EFD3 /* id3v2header.h in Headers */, 32AE5A9114E70ED600420CA0 /* id3v1tag.h in Headers */, 32AE5A9314E70ED600420CA0 /* attachedpictureframe.h in Headers */, 32AE5A9514E70ED600420CA0 /* commentsframe.h in Headers */, 32AE5A9714E70ED600420CA0 /* generalencapsulatedobjectframe.h in Headers */, 32AE5A9914E70ED600420CA0 /* popularimeterframe.h in Headers */, + EDE863C925CF6D710086EFD3 /* privateframe.h in Headers */, + EDE863BE25CF6D710086EFD3 /* synchronizedlyricsframe.h in Headers */, 32AE5A9B14E70ED600420CA0 /* privateframe.h in Headers */, 32AE5A9D14E70ED600420CA0 /* relativevolumeframe.h in Headers */, 32AE5A9F14E70ED600420CA0 /* textidentificationframe.h in Headers */, + EDE8633F25CF6CF50086EFD3 /* aiffproperties.h in Headers */, + EDE8635425CF6D3D0086EFD3 /* tfilestream.h in Headers */, + EDE863B725CF6D710086EFD3 /* unsynchronizedlyricsframe.h in Headers */, 32AE5AA114E70ED600420CA0 /* uniquefileidentifierframe.h in Headers */, + EDE863BC25CF6D710086EFD3 /* podcastframe.h in Headers */, 32AE5AA314E70ED600420CA0 /* unknownframe.h in Headers */, 32AE5AA514E70ED600420CA0 /* unsynchronizedlyricsframe.h in Headers */, + EDE8635E25CF6D3D0086EFD3 /* tzlib.h in Headers */, + EDE863DB25CF6D710086EFD3 /* id3v2tag.h in Headers */, 32AE5AA714E70ED600420CA0 /* urllinkframe.h in Headers */, + EDE863D625CF6D710086EFD3 /* id3v2synchdata.h in Headers */, + EDE8635A25CF6D3D0086EFD3 /* tutils.h in Headers */, 32AE5AAD14E70ED600420CA0 /* id3v2extendedheader.h in Headers */, 32AE5AAF14E70ED600420CA0 /* id3v2footer.h in Headers */, 32AE5AB114E70ED600420CA0 /* id3v2frame.h in Headers */, + EDE8633A25CF6CF50086EFD3 /* infotag.h in Headers */, + EDE863DF25CF6D710086EFD3 /* mpegproperties.h in Headers */, 32AE5AB314E70ED600420CA0 /* id3v2framefactory.h in Headers */, + EDE863AD25CF6D710086EFD3 /* id3v2frame.h in Headers */, 32AE5AB514E70ED600420CA0 /* id3v2header.h in Headers */, + EDE863D025CF6D710086EFD3 /* textidentificationframe.h in Headers */, 32AE5AB714E70ED600420CA0 /* id3v2synchdata.h in Headers */, + EDE863CA25CF6D710086EFD3 /* eventtimingcodesframe.h in Headers */, 32AE5AB914E70ED600420CA0 /* id3v2tag.h in Headers */, 32AE5ABB14E70ED600420CA0 /* mpegfile.h in Headers */, + EDE863E125CF6D710086EFD3 /* mpegfile.h in Headers */, 32AE5ABD14E70ED600420CA0 /* mpegheader.h in Headers */, 32AE5ABF14E70ED600420CA0 /* mpegproperties.h in Headers */, + EDE863A925CF6D710086EFD3 /* id3v2extendedheader.h in Headers */, 32AE5AC114E70ED600420CA0 /* xingheader.h in Headers */, 32AE5AC314E70ED600420CA0 /* oggflacfile.h in Headers */, 32AE5AC514E70ED600420CA0 /* oggfile.h in Headers */, @@ -841,29 +1251,45 @@ 32AE5AD114E70ED600420CA0 /* vorbisproperties.h in Headers */, 32AE5AD314E70ED600420CA0 /* xiphcomment.h in Headers */, 32AE5AD514E70ED600420CA0 /* aifffile.h in Headers */, + EDE8635B25CF6D3D0086EFD3 /* tiostream.h in Headers */, 32AE5AD714E70ED600420CA0 /* aiffproperties.h in Headers */, 32AE5AD914E70ED600420CA0 /* rifffile.h in Headers */, - 32AE5ADB14E70ED600420CA0 /* wavfile.h in Headers */, 32AE5B0014E70F4700420CA0 /* tlist.tcc in Headers */, 83790D251809E8CA0073CF51 /* opusfile.h in Headers */, - 32AE5ADD14E70ED600420CA0 /* wavproperties.h in Headers */, + EDE8631B25CF6CC60086EFD3 /* modproperties.h in Headers */, + EDE863CB25CF6D710086EFD3 /* chapterframe.h in Headers */, 32AE5B0114E70F4A00420CA0 /* tmap.tcc in Headers */, 32AE5ADF14E70ED600420CA0 /* tag.h in Headers */, + EDE863C325CF6D710086EFD3 /* commentsframe.h in Headers */, 32AE5AE114E70ED600420CA0 /* taglib_export.h in Headers */, + EDE8632025CF6CC60086EFD3 /* modfile.h in Headers */, 32AE5AE314E70ED600420CA0 /* tagunion.h in Headers */, 32AE5AE414E70ED600420CA0 /* taglib.h in Headers */, + EDE863B525CF6D710086EFD3 /* unknownframe.h in Headers */, 32AE5AE614E70ED600420CA0 /* tbytevector.h in Headers */, 32AE5AE814E70ED600420CA0 /* tbytevectorlist.h in Headers */, 32AE5AEA14E70ED600420CA0 /* tdebug.h in Headers */, + EDE863DD25CF6D710086EFD3 /* mpegutils.h in Headers */, 32AE5AEC14E70ED600420CA0 /* tfile.h in Headers */, 32AE5AED14E70ED600420CA0 /* tlist.h in Headers */, + EDE8632925CF6CDF0086EFD3 /* s3mfile.h in Headers */, 32AE5AEF14E70ED600420CA0 /* tmap.h in Headers */, 32AE5AF214E70ED600420CA0 /* tstring.h in Headers */, + EDE8633B25CF6CF50086EFD3 /* wavfile.h in Headers */, + EDE863C825CF6D710086EFD3 /* relativevolumeframe.h in Headers */, + EDE863E025CF6D710086EFD3 /* mpegheader.h in Headers */, 32AE5AF414E70ED600420CA0 /* tstringlist.h in Headers */, - 32AE5AF614E70ED700420CA0 /* unicode.h in Headers */, + EDE863A825CF6D710086EFD3 /* id3v2footer.h in Headers */, + EDE8632125CF6CC60086EFD3 /* modtag.h in Headers */, 32AE5AF814E70ED700420CA0 /* trueaudiofile.h in Headers */, + EDE863D725CF6D710086EFD3 /* id3v2.h in Headers */, + EDE8635825CF6D3D0086EFD3 /* tdebuglistener.h in Headers */, + EDE8635725CF6D3D0086EFD3 /* trefcounter.h in Headers */, + EDE8630D25CF6CAE0086EFD3 /* xmfile.h in Headers */, + EDE863E225CF6D710086EFD3 /* id3v1tag.h in Headers */, 32AE5AFA14E70ED700420CA0 /* trueaudioproperties.h in Headers */, 32AE5AFC14E70ED700420CA0 /* wavpackfile.h in Headers */, + EDE863D225CF6D710086EFD3 /* urllinkframe.h in Headers */, 32AE5AFE14E70ED700420CA0 /* wavpackproperties.h in Headers */, 32AE5AFF14E70ED700420CA0 /* taglib_config.h in Headers */, ); @@ -929,13 +1355,11 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EDE863AC25CF6D710086EFD3 /* id3v2.2.0.txt in Resources */, + EDE863DA25CF6D710086EFD3 /* id3v2.4.0-frames.txt in Resources */, 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */, - 32AE5A5B14E70ED600420CA0 /* ape-tag-format.txt in Resources */, - 32AE5AA814E70ED600420CA0 /* id3v2.2.0.txt in Resources */, - 32AE5AA914E70ED600420CA0 /* id3v2.3.0.txt in Resources */, - 32AE5AAA14E70ED600420CA0 /* id3v2.4.0-frames.txt in Resources */, - 32AE5AAB14E70ED600420CA0 /* id3v2.4.0-structure.txt in Resources */, - 32AE5AE014E70ED600420CA0 /* taglib.pro in Resources */, + EDE863D425CF6D710086EFD3 /* id3v2.3.0.txt in Resources */, + EDE863AA25CF6D710086EFD3 /* id3v2.4.0-structure.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -947,38 +1371,61 @@ buildActionMask = 2147483647; files = ( 32AE5A5C14E70ED600420CA0 /* apefile.cpp in Sources */, + EDE8630325CF6C260086EFD3 /* trefcounter.cpp in Sources */, 32AE5A5E14E70ED600420CA0 /* apefooter.cpp in Sources */, 32AE5A6014E70ED600420CA0 /* apeitem.cpp in Sources */, + EDE8631F25CF6CC60086EFD3 /* modfile.cpp in Sources */, 32AE5A6214E70ED600420CA0 /* apeproperties.cpp in Sources */, + EDE8631025CF6CAE0086EFD3 /* xmproperties.cpp in Sources */, 32AE5A6414E70ED600420CA0 /* apetag.cpp in Sources */, 32AE5A6614E70ED600420CA0 /* asfattribute.cpp in Sources */, 32AE5A6814E70ED600420CA0 /* asffile.cpp in Sources */, + EDE863D125CF6D710086EFD3 /* popularimeterframe.cpp in Sources */, + EDE8634025CF6CF50086EFD3 /* aiffproperties.cpp in Sources */, + EDE863A725CF6D710086EFD3 /* id3v2footer.cpp in Sources */, 32AE5A6A14E70ED600420CA0 /* asfpicture.cpp in Sources */, 32AE5A6C14E70ED600420CA0 /* asfproperties.cpp in Sources */, 32AE5A6E14E70ED600420CA0 /* asftag.cpp in Sources */, + EDE8634525CF6D1C0086EFD3 /* tagutils.cpp in Sources */, + EDE863DC25CF6D710086EFD3 /* mpegheader.cpp in Sources */, 32AE5A7014E70ED600420CA0 /* audioproperties.cpp in Sources */, + EDE863B125CF6D710086EFD3 /* synchronizedlyricsframe.cpp in Sources */, 32AE5A7214E70ED600420CA0 /* fileref.cpp in Sources */, 32AE5A7414E70ED600420CA0 /* flacfile.cpp in Sources */, + EDE8630725CF6C5B0086EFD3 /* itfile.cpp in Sources */, + EDE863C425CF6D710086EFD3 /* relativevolumeframe.cpp in Sources */, 32AE5A7614E70ED600420CA0 /* flacmetadatablock.cpp in Sources */, + EDE863CE25CF6D710086EFD3 /* urllinkframe.cpp in Sources */, 32AE5A7814E70ED600420CA0 /* flacpicture.cpp in Sources */, 32AE5A7A14E70ED600420CA0 /* flacproperties.cpp in Sources */, + EDE863D925CF6D710086EFD3 /* id3v2tag.cpp in Sources */, + EDE863C625CF6D710086EFD3 /* ownershipframe.cpp in Sources */, 32AE5A7C14E70ED600420CA0 /* flacunknownmetadatablock.cpp in Sources */, 32AE5A7E14E70ED600420CA0 /* mp4atom.cpp in Sources */, 32AE5A8014E70ED600420CA0 /* mp4coverart.cpp in Sources */, 32AE5A8214E70ED600420CA0 /* mp4file.cpp in Sources */, + EDE8634125CF6CF50086EFD3 /* aifffile.cpp in Sources */, 32AE5A8414E70ED600420CA0 /* mp4item.cpp in Sources */, 32AE5A8614E70ED600420CA0 /* mp4properties.cpp in Sources */, + EDE863E325CF6D710086EFD3 /* id3v1genres.cpp in Sources */, + EDE863AF25CF6D710086EFD3 /* id3v2header.cpp in Sources */, 32AE5A8814E70ED600420CA0 /* mp4tag.cpp in Sources */, 32AE5A8A14E70ED600420CA0 /* mpcfile.cpp in Sources */, 32AE5A8C14E70ED600420CA0 /* mpcproperties.cpp in Sources */, 32AE5A8E14E70ED600420CA0 /* id3v1genres.cpp in Sources */, 32AE5A9014E70ED600420CA0 /* id3v1tag.cpp in Sources */, 32AE5A9214E70ED600420CA0 /* attachedpictureframe.cpp in Sources */, + EDE863DE25CF6D710086EFD3 /* xingheader.cpp in Sources */, 32AE5A9414E70ED600420CA0 /* commentsframe.cpp in Sources */, 32AE5A9614E70ED600420CA0 /* generalencapsulatedobjectframe.cpp in Sources */, + EDE8631D25CF6CC60086EFD3 /* modtag.cpp in Sources */, + EDE863E425CF6D710086EFD3 /* id3v1tag.cpp in Sources */, 32AE5A9814E70ED600420CA0 /* popularimeterframe.cpp in Sources */, 32AE5A9A14E70ED600420CA0 /* privateframe.cpp in Sources */, + EDE863B025CF6D710086EFD3 /* id3v2frame.cpp in Sources */, 32AE5A9C14E70ED600420CA0 /* relativevolumeframe.cpp in Sources */, + EDE863B925CF6D710086EFD3 /* eventtimingcodesframe.cpp in Sources */, + EDE8633925CF6CF50086EFD3 /* infotag.cpp in Sources */, 32AE5A9E14E70ED600420CA0 /* textidentificationframe.cpp in Sources */, 32AE5AA014E70ED600420CA0 /* uniquefileidentifierframe.cpp in Sources */, 83790D241809E8CA0073CF51 /* opusfile.cpp in Sources */, @@ -986,21 +1433,37 @@ 32AE5AA414E70ED600420CA0 /* unsynchronizedlyricsframe.cpp in Sources */, 32AE5AA614E70ED600420CA0 /* urllinkframe.cpp in Sources */, 32AE5AAC14E70ED600420CA0 /* id3v2extendedheader.cpp in Sources */, + EDE863D325CF6D710086EFD3 /* id3v2synchdata.cpp in Sources */, + EDE863C025CF6D710086EFD3 /* chapterframe.cpp in Sources */, 32AE5AAE14E70ED600420CA0 /* id3v2footer.cpp in Sources */, 32AE5AB014E70ED600420CA0 /* id3v2frame.cpp in Sources */, 32AE5AB214E70ED600420CA0 /* id3v2framefactory.cpp in Sources */, 32AE5AB414E70ED600420CA0 /* id3v2header.cpp in Sources */, + EDE8633C25CF6CF50086EFD3 /* wavfile.cpp in Sources */, 32AE5AB614E70ED600420CA0 /* id3v2synchdata.cpp in Sources */, + EDE863D525CF6D710086EFD3 /* id3v2framefactory.cpp in Sources */, + EDE863B225CF6D710086EFD3 /* uniquefileidentifierframe.cpp in Sources */, 32AE5AB814E70ED600420CA0 /* id3v2tag.cpp in Sources */, + EDE863A525CF6D710086EFD3 /* mpegfile.cpp in Sources */, 32AE5ABA14E70ED600420CA0 /* mpegfile.cpp in Sources */, 32AE5ABC14E70ED600420CA0 /* mpegheader.cpp in Sources */, + EDE8635325CF6D3D0086EFD3 /* tbytevectorstream.cpp in Sources */, + EDE863BD25CF6D710086EFD3 /* podcastframe.cpp in Sources */, 32AE5ABE14E70ED600420CA0 /* mpegproperties.cpp in Sources */, 32AE5AC014E70ED600420CA0 /* xingheader.cpp in Sources */, + EDE863BF25CF6D710086EFD3 /* commentsframe.cpp in Sources */, + EDE8635D25CF6D3D0086EFD3 /* tzlib.cpp in Sources */, 32AE5AC214E70ED600420CA0 /* oggflacfile.cpp in Sources */, 32AE5AC414E70ED600420CA0 /* oggfile.cpp in Sources */, 32AE5AC614E70ED600420CA0 /* oggpage.cpp in Sources */, 32AE5AC814E70ED600420CA0 /* oggpageheader.cpp in Sources */, + EDE863D825CF6D710086EFD3 /* id3v2extendedheader.cpp in Sources */, + EDE863B625CF6D710086EFD3 /* unknownframe.cpp in Sources */, + EDE8630E25CF6CAE0086EFD3 /* xmfile.cpp in Sources */, + EDE8635C25CF6D3D0086EFD3 /* tiostream.cpp in Sources */, + EDE8631E25CF6CC60086EFD3 /* modfilebase.cpp in Sources */, 32AE5ACA14E70ED600420CA0 /* speexfile.cpp in Sources */, + EDE8635525CF6D3D0086EFD3 /* tdebuglistener.cpp in Sources */, 32AE5ACC14E70ED600420CA0 /* speexproperties.cpp in Sources */, 32AE5ACE14E70ED600420CA0 /* vorbisfile.cpp in Sources */, 32AE5AD014E70ED600420CA0 /* vorbisproperties.cpp in Sources */, @@ -1008,22 +1471,33 @@ 32AE5AD414E70ED600420CA0 /* aifffile.cpp in Sources */, 32AE5AD614E70ED600420CA0 /* aiffproperties.cpp in Sources */, 32AE5AD814E70ED600420CA0 /* rifffile.cpp in Sources */, - 32AE5ADA14E70ED600420CA0 /* wavfile.cpp in Sources */, - 32AE5ADC14E70ED600420CA0 /* wavproperties.cpp in Sources */, 32AE5ADE14E70ED600420CA0 /* tag.cpp in Sources */, 32AE5AE214E70ED600420CA0 /* tagunion.cpp in Sources */, + EDE862FD25CF6BD70086EFD3 /* tpropertymap.cpp in Sources */, + EDE863C225CF6D710086EFD3 /* generalencapsulatedobjectframe.cpp in Sources */, 32AE5AE514E70ED600420CA0 /* tbytevector.cpp in Sources */, + EDE863BA25CF6D710086EFD3 /* unsynchronizedlyricsframe.cpp in Sources */, 32AE5AE714E70ED600420CA0 /* tbytevectorlist.cpp in Sources */, + EDE863C125CF6D710086EFD3 /* tableofcontentsframe.cpp in Sources */, + EDE8632B25CF6CDF0086EFD3 /* s3mproperties.cpp in Sources */, + EDE863B325CF6D710086EFD3 /* privateframe.cpp in Sources */, 32AE5AE914E70ED600420CA0 /* tdebug.cpp in Sources */, + EDE863C725CF6D710086EFD3 /* attachedpictureframe.cpp in Sources */, + EDE8632225CF6CC60086EFD3 /* modproperties.cpp in Sources */, 32AE5AEB14E70ED600420CA0 /* tfile.cpp in Sources */, 32AE5AF114E70ED600420CA0 /* tstring.cpp in Sources */, 32AE5AF314E70ED600420CA0 /* tstringlist.cpp in Sources */, - 32AE5AF514E70ED700420CA0 /* unicode.cpp in Sources */, 83790D261809E8CA0073CF51 /* opusproperties.cpp in Sources */, 32AE5AF714E70ED700420CA0 /* trueaudiofile.cpp in Sources */, + EDE8630625CF6C5B0086EFD3 /* itproperties.cpp in Sources */, 32AE5AF914E70ED700420CA0 /* trueaudioproperties.cpp in Sources */, + EDE863E625CF6D710086EFD3 /* mpegproperties.cpp in Sources */, + EDE8630225CF6C260086EFD3 /* tfilestream.cpp in Sources */, 32AE5AFB14E70ED700420CA0 /* wavpackfile.cpp in Sources */, + EDE8633E25CF6CF50086EFD3 /* wavproperties.cpp in Sources */, 32AE5AFD14E70ED700420CA0 /* wavpackproperties.cpp in Sources */, + EDE8632C25CF6CE00086EFD3 /* s3mfile.cpp in Sources */, + EDE863CD25CF6D710086EFD3 /* textidentificationframe.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Frameworks/TagLib/taglib/3rdparty/utf8-cpp/checked.h b/Frameworks/TagLib/taglib/3rdparty/utf8-cpp/checked.h new file mode 100644 index 000000000..2aef5838d --- /dev/null +++ b/Frameworks/TagLib/taglib/3rdparty/utf8-cpp/checked.h @@ -0,0 +1,327 @@ +// Copyright 2006-2016 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" +#include + +namespace utf8 +{ + // Base for the exceptions that may be thrown from the library + class exception : public ::std::exception { + }; + + // Exceptions that may be thrown from the library functions. + class invalid_code_point : public exception { + uint32_t cp; + public: + invalid_code_point(uint32_t codepoint) : cp(codepoint) {} + virtual const char* what() const throw() { return "Invalid code point"; } + uint32_t code_point() const {return cp;} + }; + + class invalid_utf8 : public exception { + uint8_t u8; + public: + invalid_utf8 (uint8_t u) : u8(u) {} + virtual const char* what() const throw() { return "Invalid UTF-8"; } + uint8_t utf8_octet() const {return u8;} + }; + + class invalid_utf16 : public exception { + uint16_t u16; + public: + invalid_utf16 (uint16_t u) : u16(u) {} + virtual const char* what() const throw() { return "Invalid UTF-16"; } + uint16_t utf16_word() const {return u16;} + }; + + class not_enough_room : public exception { + public: + virtual const char* what() const throw() { return "Not enough space"; } + }; + + /// The library API - functions intended to be called by the users + + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (!utf8::internal::is_code_point_valid(cp)) + throw invalid_code_point(cp); + + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f) | 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) + { + while (start != end) { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) { + case internal::UTF8_OK : + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + throw not_enough_room(); + case internal::INVALID_LEAD: + out = utf8::append (replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::append (replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } + + template + inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) + { + static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd); + return utf8::replace_invalid(start, end, out, replacement_marker); + } + + template + uint32_t next(octet_iterator& it, octet_iterator end) + { + uint32_t cp = 0; + internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); + switch (err_code) { + case internal::UTF8_OK : + break; + case internal::NOT_ENOUGH_ROOM : + throw not_enough_room(); + case internal::INVALID_LEAD : + case internal::INCOMPLETE_SEQUENCE : + case internal::OVERLONG_SEQUENCE : + throw invalid_utf8(*it); + case internal::INVALID_CODE_POINT : + throw invalid_code_point(cp); + } + return cp; + } + + template + uint32_t peek_next(octet_iterator it, octet_iterator end) + { + return utf8::next(it, end); + } + + template + uint32_t prior(octet_iterator& it, octet_iterator start) + { + // can't do much if it == start + if (it == start) + throw not_enough_room(); + + octet_iterator end = it; + // Go back until we hit either a lead octet or start + while (utf8::internal::is_trail(*(--it))) + if (it == start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + return utf8::peek_next(it, end); + } + + /// Deprecated in versions that include "prior" + template + uint32_t previous(octet_iterator& it, octet_iterator pass_start) + { + octet_iterator end = it; + while (utf8::internal::is_trail(*(--it))) + if (it == pass_start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + octet_iterator temp = it; + return utf8::next(temp, end); + } + + template + void advance (octet_iterator& it, distance_type n, octet_iterator end) + { + for (distance_type i = 0; i < n; ++i) + utf8::next(it, end); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::next(first, last); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + if (start != end) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + if (utf8::internal::is_trail_surrogate(trail_surrogate)) + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + else + throw invalid_utf16(static_cast(trail_surrogate)); + } + else + throw invalid_utf16(static_cast(cp)); + + } + // Lone trail surrogate + else if (utf8::internal::is_trail_surrogate(cp)) + throw invalid_utf16(static_cast(cp)); + + result = utf8::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) { + uint32_t cp = utf8::next(start, end); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::next(start, end); + + return result; + } + + // The iterator class + template + class iterator : public std::iterator { + octet_iterator it; + octet_iterator range_start; + octet_iterator range_end; + public: + iterator () {} + explicit iterator (const octet_iterator& octet_it, + const octet_iterator& rangestart, + const octet_iterator& rangeend) : + it(octet_it), range_start(rangestart), range_end(rangeend) + { + if (it < range_start || it > range_end) + throw std::out_of_range("Invalid utf-8 iterator position"); + } + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::next(temp, range_end); + } + bool operator == (const iterator& rhs) const + { + if (range_start != rhs.range_start || range_end != rhs.range_end) + throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + utf8::next(it, range_end); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + utf8::next(it, range_end); + return temp; + } + iterator& operator -- () + { + utf8::prior(it, range_start); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::prior(it, range_start); + return temp; + } + }; // class iterator + +} // namespace utf8 + +#endif //header guard + + diff --git a/Frameworks/TagLib/taglib/3rdparty/utf8-cpp/core.h b/Frameworks/TagLib/taglib/3rdparty/utf8-cpp/core.h new file mode 100644 index 000000000..ae0f367db --- /dev/null +++ b/Frameworks/TagLib/taglib/3rdparty/utf8-cpp/core.h @@ -0,0 +1,332 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include + +namespace utf8 +{ + // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers + // You may need to change them to match your system. + // These typedefs have the same names as ones from cstdint, or boost/cstdint + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + +// Helper code - not intended to be directly called by the library users. May be changed at any time +namespace internal +{ + // Unicode constants + // Leading (high) surrogates: 0xd800 - 0xdbff + // Trailing (low) surrogates: 0xdc00 - 0xdfff + const uint16_t LEAD_SURROGATE_MIN = 0xd800u; + const uint16_t LEAD_SURROGATE_MAX = 0xdbffu; + const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u; + const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu; + const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10); + const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN; + + // Maximum valid value for a Unicode code point + const uint32_t CODE_POINT_MAX = 0x0010ffffu; + + template + inline uint8_t mask8(octet_type oc) + { + return static_cast(0xff & oc); + } + template + inline uint16_t mask16(u16_type oc) + { + return static_cast(0xffff & oc); + } + template + inline bool is_trail(octet_type oc) + { + return ((utf8::internal::mask8(oc) >> 6) == 0x2); + } + + template + inline bool is_lead_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); + } + + template + inline bool is_trail_surrogate(u16 cp) + { + return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_code_point_valid(u32 cp) + { + return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp)); + } + + template + inline typename std::iterator_traits::difference_type + sequence_length(octet_iterator lead_it) + { + uint8_t lead = utf8::internal::mask8(*lead_it); + if (lead < 0x80) + return 1; + else if ((lead >> 5) == 0x6) + return 2; + else if ((lead >> 4) == 0xe) + return 3; + else if ((lead >> 3) == 0x1e) + return 4; + else + return 0; + } + + template + inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length) + { + if (cp < 0x80) { + if (length != 1) + return true; + } + else if (cp < 0x800) { + if (length != 2) + return true; + } + else if (cp < 0x10000) { + if (length != 3) + return true; + } + + return false; + } + + enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT}; + + /// Helper for get_sequence_x + template + utf_error increase_safely(octet_iterator& it, octet_iterator end) + { + if (++it == end) + return NOT_ENOUGH_ROOM; + + if (!utf8::internal::is_trail(*it)) + return INCOMPLETE_SEQUENCE; + + return UTF8_OK; + } + + #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;} + + /// get_sequence_x functions decode utf-8 sequences of the length x + template + utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + return UTF8_OK; + } + + template + utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f); + + return UTF8_OK; + } + + template + utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + template + utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR + + template + utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + // Save the original value of it so we can go back in case of failure + // Of course, it does not make much sense with i.e. stream iterators + octet_iterator original_it = it; + + uint32_t cp = 0; + // Determine the sequence length based on the lead octet + typedef typename std::iterator_traits::difference_type octet_difference_type; + const octet_difference_type length = utf8::internal::sequence_length(it); + + // Get trail octets and calculate the code point + utf_error err = UTF8_OK; + switch (length) { + case 0: + return INVALID_LEAD; + case 1: + err = utf8::internal::get_sequence_1(it, end, cp); + break; + case 2: + err = utf8::internal::get_sequence_2(it, end, cp); + break; + case 3: + err = utf8::internal::get_sequence_3(it, end, cp); + break; + case 4: + err = utf8::internal::get_sequence_4(it, end, cp); + break; + } + + if (err == UTF8_OK) { + // Decoding succeeded. Now, security checks... + if (utf8::internal::is_code_point_valid(cp)) { + if (!utf8::internal::is_overlong_sequence(cp, length)){ + // Passed! Return here. + code_point = cp; + ++it; + return UTF8_OK; + } + else + err = OVERLONG_SEQUENCE; + } + else + err = INVALID_CODE_POINT; + } + + // Failure branch - restore the original value of the iterator + it = original_it; + return err; + } + + template + inline utf_error validate_next(octet_iterator& it, octet_iterator end) { + uint32_t ignored; + return utf8::internal::validate_next(it, end, ignored); + } + +} // namespace internal + + /// The library API - functions intended to be called by the users + + // Byte order mark + const uint8_t bom[] = {0xef, 0xbb, 0xbf}; + + template + octet_iterator find_invalid(octet_iterator start, octet_iterator end) + { + octet_iterator result = start; + while (result != end) { + utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end); + if (err_code != internal::UTF8_OK) + return result; + } + return result; + } + + template + inline bool is_valid(octet_iterator start, octet_iterator end) + { + return (utf8::find_invalid(start, end) == end); + } + + template + inline bool starts_with_bom (octet_iterator it, octet_iterator end) + { + return ( + ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) && + ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) && + ((it != end) && (utf8::internal::mask8(*it)) == bom[2]) + ); + } + + //Deprecated in release 2.3 + template + inline bool is_bom (octet_iterator it) + { + return ( + (utf8::internal::mask8(*it++)) == bom[0] && + (utf8::internal::mask8(*it++)) == bom[1] && + (utf8::internal::mask8(*it)) == bom[2] + ); + } +} // namespace utf8 + +#endif // header guard + + diff --git a/Frameworks/TagLib/taglib/AUTHORS b/Frameworks/TagLib/taglib/AUTHORS index 8872bd80e..279ee2219 100644 --- a/Frameworks/TagLib/taglib/AUTHORS +++ b/Frameworks/TagLib/taglib/AUTHORS @@ -1,11 +1,21 @@ Scott Wheeler Author, maintainer +Lukas Lalinsky + Implementation of multiple new file formats, many bug fixes, maintainer +Tsuda Kageyu + A lot of bug fixes and performance improvements, maintainer. +Stephen F. Booth + DSF metadata implementation, bug fixes, maintainer. Ismael Orenstein Xing header implementation Allan Sandfeld Jensen FLAC metadata implementation Teemu Tervo Numerous bug reports and fixes +Mathias Panzenböck + Mod, S3M, IT and XM metadata implementations +Damien Plisson + DSDIFF metadata implementation Please send all patches and questions to taglib-devel@kde.org rather than to individual developers! diff --git a/Frameworks/TagLib/taglib/CMakeLists.txt b/Frameworks/TagLib/taglib/CMakeLists.txt new file mode 100644 index 000000000..5fc91cc63 --- /dev/null +++ b/Frameworks/TagLib/taglib/CMakeLists.txt @@ -0,0 +1,168 @@ +cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR) + +project(taglib) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") + +if(DEFINED ENABLE_STATIC) + message(FATAL_ERROR "This option is no longer available, use BUILD_SHARED_LIBS instead") +endif() + +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) +if(APPLE) + option(BUILD_FRAMEWORK "Build an OS X framework" OFF) + if(BUILD_FRAMEWORK) + set(BUILD_SHARED_LIBS ON) + #set(CMAKE_MACOSX_RPATH 1) + set(FRAMEWORK_INSTALL_DIR "/Library/Frameworks" CACHE STRING "Directory to install frameworks to.") + endif() +endif() +if(NOT BUILD_SHARED_LIBS) + add_definitions(-DTAGLIB_STATIC) +endif() +option(ENABLE_STATIC_RUNTIME "Visual Studio, link with runtime statically" OFF) + +option(ENABLE_CCACHE "Use ccache when building libtag" OFF) +if(ENABLE_CCACHE) + find_program(CCACHE_FOUND ccache) + if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) + endif() +endif() + +option(VISIBILITY_HIDDEN "Build with -fvisibility=hidden" OFF) +option(BUILD_TESTS "Build the test suite" OFF) +option(BUILD_EXAMPLES "Build the examples" OFF) +option(BUILD_BINDINGS "Build the bindings" ON) + +option(NO_ITUNES_HACKS "Disable workarounds for iTunes bugs" OFF) + +option(PLATFORM_WINRT "Enable WinRT support" OFF) +if(PLATFORM_WINRT) + add_definitions(-DPLATFORM_WINRT) +endif() + +add_definitions(-DHAVE_CONFIG_H) +set(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests/") + +## the following are directories where stuff will be installed to +set(LIB_SUFFIX "" CACHE STRING "Define suffix of directory name (32/64)") +set(EXEC_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" CACHE PATH "Base directory for executables and libraries") +set(BIN_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/bin" CACHE PATH "The subdirectory to the binaries prefix (default prefix/bin)") +set(LIB_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "The subdirectory relative to the install prefix where libraries will be installed (default is /lib${LIB_SUFFIX})") +set(INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "The subdirectory to the header prefix") + +if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +endif() + +if(MSVC AND ENABLE_STATIC_RUNTIME) + foreach(flag_var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endforeach(flag_var) +endif() + +# Read version information from file taglib/toolkit/taglib.h into variables +# TAGLIB_LIB_MAJOR_VERSION, TAGLIB_LIB_MINOR_VERSION, TAGLIB_LIB_PATCH_VERSION. +foreach(version_part MAJOR MINOR PATCH) + set(version_var_name "TAGLIB_${version_part}_VERSION") + file(STRINGS taglib/toolkit/taglib.h version_line + REGEX "^#define +${version_var_name}") + if(NOT version_line) + message(FATAL_ERROR "${version_var_name} not found in taglib.h") + endif() + string(REGEX MATCH "${version_var_name} +([^ ]+)" result ${version_line}) + set(TAGLIB_LIB_${version_part}_VERSION ${CMAKE_MATCH_1}) +endforeach(version_part) + +# Only used to force cmake rerun when taglib.h changes. +configure_file(taglib/toolkit/taglib.h ${CMAKE_CURRENT_BINARY_DIR}/taglib.h.stamp) + +if("${TAGLIB_LIB_PATCH_VERSION}" EQUAL "0") + set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}") +else() + set(TAGLIB_LIB_VERSION_STRING "${TAGLIB_LIB_MAJOR_VERSION}.${TAGLIB_LIB_MINOR_VERSION}.${TAGLIB_LIB_PATCH_VERSION}") +endif() + +# 1. If the library source code has changed at all since the last update, then increment revision. +# 2. If any interfaces have been added, removed, or changed since the last update, increment current, and set revision to 0. +# 3. If any interfaces have been added since the last public release, then increment age. +# 4. If any interfaces have been removed since the last public release, then set age to 0. +set(TAGLIB_SOVERSION_CURRENT 19) +set(TAGLIB_SOVERSION_REVISION 0) +set(TAGLIB_SOVERSION_AGE 18) + +math(EXPR TAGLIB_SOVERSION_MAJOR "${TAGLIB_SOVERSION_CURRENT} - ${TAGLIB_SOVERSION_AGE}") +math(EXPR TAGLIB_SOVERSION_MINOR "${TAGLIB_SOVERSION_AGE}") +math(EXPR TAGLIB_SOVERSION_PATCH "${TAGLIB_SOVERSION_REVISION}") + +include(ConfigureChecks.cmake) + +if(${ZLIB_FOUND}) + set(ZLIB_LIBRARIES_FLAGS -lz) +endif() + +if(NOT WIN32) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config" @ONLY) + install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config" DESTINATION "${BIN_INSTALL_DIR}") +endif() + +if(WIN32) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib-config.cmd.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd") + install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/taglib-config.cmd" DESTINATION "${BIN_INSTALL_DIR}") +endif() + +if(NOT BUILD_FRAMEWORK) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/taglib.pc.cmake" "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc" @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/taglib.pc" DESTINATION "${LIB_INSTALL_DIR}/pkgconfig") +endif() + +if(NOT HAVE_ZLIB AND ZLIB_SOURCE) + set(HAVE_ZLIB 1) + set(HAVE_ZLIB_SOURCE 1) +endif() + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +configure_file(config.h.cmake "${CMAKE_CURRENT_BINARY_DIR}/config.h") + +if(WITH_ASF) + set(TAGLIB_WITH_ASF TRUE) +endif() +if(WITH_MP4) + set(TAGLIB_WITH_MP4 TRUE) +endif() + +option(TRACE_IN_RELEASE "Output debug messages even in release mode" OFF) +if(TRACE_IN_RELEASE) + set(TRACE_IN_RELEASE TRUE) +endif() + +configure_file(taglib/taglib_config.h.cmake "${CMAKE_CURRENT_BINARY_DIR}/taglib_config.h") + +add_subdirectory(taglib) + +if(BUILD_BINDINGS) + add_subdirectory(bindings) +endif() + +if(BUILD_TESTS AND NOT BUILD_SHARED_LIBS) + enable_testing() + add_subdirectory(tests) +endif() + +if(BUILD_EXAMPLES) + add_subdirectory(examples) +endif() + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile") +file(COPY doc/taglib.png DESTINATION doc) +add_custom_target(docs doxygen) + +# uninstall target +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) + +if(NOT TARGET uninstall) + add_custom_target(uninstall COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") +endif() diff --git a/Frameworks/TagLib/taglib/COPYING.MPL b/Frameworks/TagLib/taglib/COPYING.MPL new file mode 100644 index 000000000..7714141d1 --- /dev/null +++ b/Frameworks/TagLib/taglib/COPYING.MPL @@ -0,0 +1,470 @@ + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is ______________________________________. + + The Initial Developer of the Original Code is ________________________. + Portions created by ______________________ are Copyright (C) ______ + _______________________. All Rights Reserved. + + Contributor(s): ______________________________________. + + Alternatively, the contents of this file may be used under the terms + of the _____ license (the "[___] License"), in which case the + provisions of [______] License are applicable instead of those + above. If you wish to allow use of your version of this file only + under the terms of the [____] License and not to allow others to use + your version of this file under the MPL, indicate your decision by + deleting the provisions above and replace them with the notice and + other provisions required by the [___] License. If you do not delete + the provisions above, a recipient may use your version of this file + under either the MPL or the [___] License." + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] + diff --git a/Frameworks/TagLib/taglib/ConfigureChecks.cmake b/Frameworks/TagLib/taglib/ConfigureChecks.cmake new file mode 100644 index 000000000..bcdbfe20f --- /dev/null +++ b/Frameworks/TagLib/taglib/ConfigureChecks.cmake @@ -0,0 +1,203 @@ +include(CheckLibraryExists) +include(CheckTypeSize) +include(CheckCXXSourceCompiles) + +# Check if the size of numeric types are suitable. + +check_type_size("short" SIZEOF_SHORT) +if(NOT ${SIZEOF_SHORT} EQUAL 2) + message(FATAL_ERROR "TagLib requires that short is 16-bit wide.") +endif() + +check_type_size("int" SIZEOF_INT) +if(NOT ${SIZEOF_INT} EQUAL 4) + message(FATAL_ERROR "TagLib requires that int is 32-bit wide.") +endif() + +check_type_size("long long" SIZEOF_LONGLONG) +if(NOT ${SIZEOF_LONGLONG} EQUAL 8) + message(FATAL_ERROR "TagLib requires that long long is 64-bit wide.") +endif() + +check_type_size("wchar_t" SIZEOF_WCHAR_T) +if(${SIZEOF_WCHAR_T} LESS 2) + message(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.") +endif() + +check_type_size("float" SIZEOF_FLOAT) +if(NOT ${SIZEOF_FLOAT} EQUAL 4) + message(FATAL_ERROR "TagLib requires that float is 32-bit wide.") +endif() + +check_type_size("double" SIZEOF_DOUBLE) +if(NOT ${SIZEOF_DOUBLE} EQUAL 8) + message(FATAL_ERROR "TagLib requires that double is 64-bit wide.") +endif() + +# Determine which kind of atomic operations your compiler supports. + +check_cxx_source_compiles(" + int main() { + volatile int x; + __sync_add_and_fetch(&x, 1); + int y = __sync_sub_and_fetch(&x, 1); + return 0; + } + " HAVE_GCC_ATOMIC) + +if(NOT HAVE_GCC_ATOMIC) + check_cxx_source_compiles(" + #include + int main() { + volatile int32_t x; + OSAtomicIncrement32Barrier(&x); + int32_t y = OSAtomicDecrement32Barrier(&x); + return 0; + } + " HAVE_MAC_ATOMIC) + + if(NOT HAVE_MAC_ATOMIC) + check_cxx_source_compiles(" + #include + int main() { + volatile LONG x; + InterlockedIncrement(&x); + LONG y = InterlockedDecrement(&x); + return 0; + } + " HAVE_WIN_ATOMIC) + + if(NOT HAVE_WIN_ATOMIC) + check_cxx_source_compiles(" + #include + int main() { + volatile int x; + __sync_add_and_fetch(&x, 1); + int y = __sync_sub_and_fetch(&x, 1); + return 0; + } + " HAVE_IA64_ATOMIC) + endif() + endif() +endif() + +# Determine which kind of byte swap functions your compiler supports. + +check_cxx_source_compiles(" + int main() { + __builtin_bswap16(0); + __builtin_bswap32(0); + __builtin_bswap64(0); + return 0; + } +" HAVE_GCC_BYTESWAP) + +if(NOT HAVE_GCC_BYTESWAP) + check_cxx_source_compiles(" + #include + int main() { + __bswap_16(0); + __bswap_32(0); + __bswap_64(0); + return 0; + } + " HAVE_GLIBC_BYTESWAP) + + if(NOT HAVE_GLIBC_BYTESWAP) + check_cxx_source_compiles(" + #include + int main() { + _byteswap_ushort(0); + _byteswap_ulong(0); + _byteswap_uint64(0); + return 0; + } + " HAVE_MSC_BYTESWAP) + + if(NOT HAVE_MSC_BYTESWAP) + check_cxx_source_compiles(" + #include + int main() { + OSSwapInt16(0); + OSSwapInt32(0); + OSSwapInt64(0); + return 0; + } + " HAVE_MAC_BYTESWAP) + + if(NOT HAVE_MAC_BYTESWAP) + check_cxx_source_compiles(" + #include + int main() { + swap16(0); + swap32(0); + swap64(0); + return 0; + } + " HAVE_OPENBSD_BYTESWAP) + endif() + endif() + endif() +endif() + +# Determine whether your compiler supports some safer version of vsprintf. + +check_cxx_source_compiles(" + #include + #include + int main() { + char buf[20]; + va_list args; + vsnprintf(buf, 20, \"%d\", args); + return 0; + } +" HAVE_VSNPRINTF) + +if(NOT HAVE_VSNPRINTF) + check_cxx_source_compiles(" + #include + #include + int main() { + char buf[20]; + va_list args; + vsprintf_s(buf, \"%d\", args); + return 0; + } + " HAVE_VSPRINTF_S) +endif() + +# Determine whether your compiler supports ISO _strdup. + +check_cxx_source_compiles(" + #include + int main() { + _strdup(0); + return 0; + } +" HAVE_ISO_STRDUP) + +# Determine whether zlib is installed. + +if(NOT ZLIB_SOURCE) + find_package(ZLIB) + if(ZLIB_FOUND) + set(HAVE_ZLIB 1) + else() + set(HAVE_ZLIB 0) + endif() +endif() + +# Determine whether CppUnit is installed. + +if(BUILD_TESTS AND NOT BUILD_SHARED_LIBS) + find_package(CppUnit) + if(NOT CppUnit_FOUND) + message(STATUS "CppUnit not found, disabling tests.") + set(BUILD_TESTS OFF) + endif() +endif() + +# Detect WinRT mode +if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + set(PLATFORM WINRT 1) +endif() diff --git a/Frameworks/TagLib/taglib/Doxyfile.cmake b/Frameworks/TagLib/taglib/Doxyfile.cmake new file mode 100644 index 000000000..6da30bb5d --- /dev/null +++ b/Frameworks/TagLib/taglib/Doxyfile.cmake @@ -0,0 +1,210 @@ +# Doxyfile 1.3.4 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = TagLib +PROJECT_NUMBER = ${TAGLIB_LIB_VERSION_STRING} +OUTPUT_DIRECTORY = doc +OUTPUT_LANGUAGE = English +USE_WINDOWS_ENCODING = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +DISTRIBUTE_GROUP_DOC = NO +TAB_SIZE = 4 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = YES +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= NO +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = NO +WARN_IF_DOC_ERROR = YES +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = @CMAKE_SOURCE_DIR@/taglib +FILE_PATTERNS = *.h \ + *.hh \ + *.H +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = @CMAKE_SOURCE_DIR@/doc/api-header.html +HTML_FOOTER = @CMAKE_SOURCE_DIR@/doc/api-footer.html +HTML_STYLESHEET = @CMAKE_SOURCE_DIR@/doc/taglib-api.css +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = YES +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = letter +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = DO_NOT_DOCUMENT \ + DOXYGEN \ + WITH_MP4 \ + WITH_ASF +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +MAX_DOT_GRAPH_DEPTH = 0 +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/Frameworks/TagLib/taglib/INSTALL.md b/Frameworks/TagLib/taglib/INSTALL.md new file mode 100644 index 000000000..ee9a81ae6 --- /dev/null +++ b/Frameworks/TagLib/taglib/INSTALL.md @@ -0,0 +1,175 @@ +TagLib Installation +=================== + +TagLib uses the CMake build system. As a user, you will most likely want to +build TagLib in release mode and install it into a system-wide location. +This can be done using the following commands: + + cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release . + make + sudo make install + +In order to build the included examples, use the `BUILD_EXAMPLES` option: + + cmake -DBUILD_EXAMPLES=ON [...] + +See http://www.cmake.org/cmake/help/runningcmake.html for generic help on +running CMake. + +Mac OS X +-------- + +On Mac OS X, you might want to build a framework that can be easily integrated +into your application. If you set the BUILD_FRAMEWORK option on, it will compile +TagLib as a framework. For example, the following command can be used to build +an Universal Binary framework with Mac OS X 10.4 as the deployment target: + + cmake -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_FRAMEWORK=ON \ + -DCMAKE_C_COMPILER=/usr/bin/gcc-4.0 \ + -DCMAKE_CXX_COMPILER=/usr/bin/c++-4.0 \ + -DCMAKE_OSX_SYSROOT=/Developer/SDKs/MacOSX10.4u.sdk/ \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.4 \ + -DCMAKE_OSX_ARCHITECTURES="ppc;i386;x86_64" + +For a 10.6 Snow Leopard static library with both 32-bit and 64-bit code, use: + + cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.6 \ + -DCMAKE_OSX_ARCHITECTURES="i386;x86_64" \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_INSTALL_PREFIX="" + +After `make`, and `make install`, add `libtag.` to your XCode project, and add +the include folder to the project's User Header Search Paths. + +Windows +------- + +It's Windows ... Systems vary! +This means you need to adjust things to suit your system, especially paths. + +Tested with: +* Microsoft Visual Studio 2010, 2015, 2017 +* Microsoft C++ Build Tools 2015, 2017 (standalone packages not requiring Visual Studio) +* Gcc by mingw-w64.sf.net v4.6.3 (Strawberry Perl 32b) +* MinGW32-4.8.0 + +Requirements: +* Tool chain, build environment, whatever ya want to call it ... + Installed and working. +* CMake program. (Available at: www.cmake.org) + Installed and working. + +Optional: +* Zlib library. + Available in some tool chains, not all. + Search the web, take your choice. + +Useful configuration options used with CMake (GUI and/or command line): + Any of the `ZLIB_` variables may be used at the command line, `ZLIB_ROOT` is only + available on the command line. + + | option | description | + ---------------------| ------------| + `ZLIB_ROOT=` | Where to find ZLib's root directory. Assumes parent of: `\include` and `\lib.`| + `ZLIB_INCLUDE_DIR=` | Where to find ZLib's Include directory.| + `ZLIB_LIBRARY=` | Where to find ZLib's Library. + `ZLIB_SOURCE=` | Where to find ZLib's Source Code. Alternative to `ZLIB_INCLUDE_DIR` and `ZLIB_LIBRARY`. + `CMAKE_INSTALL_PREFIX=` | Where to install Taglib. | + `CMAKE_BUILD_TYPE=` | Release, Debug, etc ... (Not available in MSVC) | + +The easiest way is at the command prompt (Visual C++ command prompt for MSVS users – batch file and/or shortcuts are your friends). + +1. **Build the Makefiles:** + + Replace "GENERATOR" with your needs. + * For MSVS: `Visual Studio XX YYYY`, e.g. `Visual Studio 14 2015`. + + **Note**: As Visual Studio 2017 supports CMake, you can skip this step and open the taglib + folder in VS instead. + * For MinGW: `MinGW Makefiles` + + C:\GitRoot\taglib> cmake -G "GENERATOR" -DCMAKE_INSTALL_PREFIX=C:\Libraries\taglib + + Or use the CMake GUI: + 1. Open CMake GUI. + 2. Set paths: *Where is the source code* and *Where to build the binaries*. + + In the example, both would be: `C:\GitRoot\taglib` + 3. Tick: Advanced + 4. Select: Configure + 5. Select: Generator + 6. Tick: Use default native compilers + 7. Select: Finish + Wait until done. + 8. If using ZLib, Scroll down. + (to the bottom of the list of options ... should go over them all) + 1. Edit: `ZLIB_INCLUDE_DIR` + 2. Edit: `ZLIB_LIBRARY` + 9. Select: Generate + +2. **Build the project** + * MSVS: + + C:\GitRoot\taglib> msbuild all_build.vcxproj /p:Configuration=Release + OR (Depending on MSVS version or personal choice) + + C:\GitRoot\taglib> devenv all_build.vcxproj /build Release + OR in the MSVS GUI: + 1. Open MSVS. + 2. Open taglib solution. + 3. Set build type to: Release (look in the tool bars) + 2. Hit F7 to build the solution. (project) + * MinGW: + + C:\GitRoot\taglib> gmake + + OR (Depending on MinGW install) + + C:\GitRoot\taglib> mingw32-make + + + +3. **Install the project** + + (Change `install` to `uninstall` to uninstall the project) + * MSVS: + + C:\GitRoot\taglib> msbuild install.vcxproj + OR (Depending on MSVC version or personal choice) + + C:\GitRoot\taglib> devenv install.vcxproj + + Or in the MSVS GUI: + 1. Open project. + 2. Open Solution Explorer. + 3. Right Click: INSTALL + 4. Select: Project Only + 5. Select: Build Only INSTALL + * MinGW: + + C:\GitRoot\taglib> gmake install + OR (Depending on MinGW install) + + C:\GitRoot\taglib> mingw32-make install + + +To build a static library, set the following two options with CMake: + + -DBUILD_SHARED_LIBS=OFF -DENABLE_STATIC_RUNTIME=ON + +Including `ENABLE_STATIC_RUNTIME=ON` indicates you want TagLib built using the +static runtime library, rather than the DLL form of the runtime. + +Unit Tests +---------- + +If you want to run the test suite to make sure TagLib works properly on your +system, you need to have cppunit installed. To build the tests, include +the option `-DBUILD_TESTS=on` when running cmake. + +The test suite has a custom target in the build system, so you can run +the tests using make: + + make check diff --git a/Frameworks/TagLib/taglib/NEWS b/Frameworks/TagLib/taglib/NEWS new file mode 100644 index 000000000..4b1cab502 --- /dev/null +++ b/Frameworks/TagLib/taglib/NEWS @@ -0,0 +1,335 @@ +============================ + + * Added support for WinRT. + * Added support for Linux on POWER. + * Added support for classical music tags of iTunes 12.5. + * Added support for file descriptor to FileStream. + * Added support for 'cmID', 'purl', 'egid' MP4 atoms. + * Added support for 'GRP1' ID3v2 frame. + * Added support for extensible WAV subformat. + * Enabled FileRef to detect file types based on the stream content. + * Dropped support for Windows 9x and NT 4.0 or older. + * Check for mandatory header objects in ASF files. + * More tolerant handling of RIFF padding, WAV files, broken MPEG streams. + * Improved calculation of Ogg, Opus, Speex, WAV, MP4 bitrates. + * Improved Windows compatibility by storing FLAC picture after comments. + * Fixed numerical genres in ID3v2.3.0 'TCON' frames. + * Fixed consistency of API removing MP4 items when empty values are set. + * Fixed consistency of API preferring COMM frames with no description. + * Fixed OOB read on invalid Ogg FLAC files (CVE-2018-11439). + * Fixed handling of empty MPEG files. + * Fixed parsing MP4 mdhd timescale. + * Fixed reading MP4 atoms with zero length. + * Fixed reading FLAC files with zero-sized seektables. + * Fixed handling of lowercase field names in Vorbis Comments. + * Fixed handling of 'rate' atoms in MP4 files. + * Fixed handling of invalid UTF-8 sequences. + * Fixed possible file corruptions when saving Ogg files. + * Fixed handling of non-audio blocks, sampling rates, DSD audio in WavPack files. + * TableOfContentsFrame::toString() improved. + * UserTextIdentificationFrame::toString() improved. + * Marked FileRef::create() deprecated. + * Marked MPEG::File::save() with boolean parameters deprecated, + provide overloads with enum parameters. + * Several smaller bug fixes and performance improvements. + +TagLib 1.11.1 (Oct 24, 2016) +============================ + + * Fixed binary incompatible change in TagLib::String. + * Fixed reading ID3v2 CTOC frames with a lot of entries. + * Fixed seeking ByteVectorStream from the end. + +TagLib 1.11 (Apr 29, 2016) +========================== + +1.11: + + * Fixed reading APE items with long keys. + * Fixed reading ID3v2 SYLT frames when description is empty. + +1.11 BETA 2: + + * Better handling of PCM WAV files with a 'fact' chunk. + * Better handling of corrupted APE tags. + * Efficient decoding of unsynchronized ID3v2 frames. + * Fixed text encoding when saving certain frames in ID3v2.3 tags. + * Fixed updating the size of RIFF files when removing chunks. + * Several smaller bug fixes and performance improvements. + +1.11 BETA: + + * New API for creating FileRef from IOStream. + * Added support for ID3v2 PCST and WFED frames. + * Added support for pictures in XiphComment. + * Added String::clear(). + * Added FLAC::File::strip() for removing non-standard tags. + * Added alternative functions to XiphComment::removeField(). + * Added BUILD_BINDINGS build option. + * Added ENABLE_CCACHE build option. + * Replaced ENABLE_STATIC build option with BUILD_SHARED_LIBS. + * Better handling of duplicate ID3v2 tags in all kinds of files. + * Better handling of duplicate tag chunks in WAV files. + * Better handling of duplicate tag chunks in AIFF files. + * Better handling of duplicate Vorbis comment blocks in FLAC files. + * Better handling of broken MPEG audio frames. + * Fixed crash when calling File::properties() after strip(). + * Fixed crash when parsing certain MPEG files. + * Fixed crash when saving Ogg files. + * Fixed possible file corruptions when saving ASF files. + * Fixed possible file corruptions when saving FLAC files. + * Fixed possible file corruptions when saving MP4 files. + * Fixed possible file corruptions when saving MPEG files. + * Fixed possible file corruptions when saving APE files. + * Fixed possible file corruptions when saving Musepack files. + * Fixed possible file corruptions when saving WavPack files. + * Fixed updating the comment field of Vorbis comments. + * Fixed reading date and time in ID3v2.3 tags. + * Marked ByteVector::null and ByteVector::isNull() deprecated. + * Marked String::null and String::isNull() deprecated. + * Marked XiphComment::removeField() deprecated. + * Marked Ogg::Page::getCopyWithNewPageSequenceNumber() deprecated. It returns null. + * Marked custom integer types deprecated. + * Many smaller bug fixes and performance improvements. + +TagLib 1.10 (Nov 11, 2015) +========================== + +1.10: + + * Added new options to the tagwriter example. + * Fixed self-assignment operator in some types. + * Fixed extraction of MP4 tag keys with an empty list. + +1.10 BETA: + + * New API for the audio length in milliseconds. + * Added support for ID3v2 ETCO and SYLT frames. + * Added support for album artist in PropertyMap API of MP4 files. + * Added support for embedded frames in ID3v2 CHAP and CTOC frames. + * Added support for AIFF-C files. + * Better handling of duplicate ID3v2 tags in MPEG files. + * Allowed generating taglib.pc on Windows. + * Added ZLIB_SOURCE build option. + * Fixed backwards-incompatible change in TagLib::String when constructing UTF16 strings. + * Fixed crash when parsing certain FLAC files. + * Fixed crash when encoding empty strings. + * Fixed saving of certain XM files on OS X. + * Changed Xiph and APE generic getters to return space-concatenated values. + * Fixed possible file corruptions when removing tags from WAV files. + * Added support for MP4 files with 64-bit atoms in certain 64-bit environments. + * Prevented ID3v2 padding from being too large. + * Fixed crash when parsing corrupted APE files. + * Fixed crash when parsing corrupted WAV files. + * Fixed crash when parsing corrupted Ogg FLAC files. + * Fixed crash when parsing corrupted MPEG files. + * Fixed saving empty tags in WAV files. + * Fixed crash when parsing corrupted Musepack files. + * Fixed possible memory leaks when parsing AIFF and WAV files. + * Fixed crash when parsing corrupted MP4 files. + * Stopped writing empty ID3v2 frames. + * Fixed possible file corruptions when saving WMA files. + * Added TagLib::MP4::Tag::isEmpty(). + * Added accessors to manipulate MP4 tags. + * Fixed crash when parsing corrupted WavPack files. + * Fixed seeking MPEG frames. + * Fixed reading FLAC files with zero-sized padding blocks. + * Added support for reading the encoder information of WMA files. + * Added support for reading the codec of WAV files. + * Added support for multi channel WavPack files. + * Added support for reading the nominal bitrate of Ogg Speex files. + * Added support for VBR headers in MPEG files. + * Marked FLAC::File::streamInfoData() deprecated. It returns an empty ByteVector. + * Marked FLAC::File::streamLength() deprecated. It returns zero. + * Fixed possible file corruptions when adding an ID3v1 tag to FLAC files. + * Many smaller bug fixes and performance improvements. + +TagLib 1.9.1 (Oct 8, 2013) +========================== + + * Fixed binary incompatible change in TagLib::Map and TagLib::List. + * Fixed constructing String from ByteVector. + * Fixed compilation on MSVC with the /Zc:wchar_t- option. + * Fixed detecting of RIFF files with invalid chunk sizes. + * Added TagLib::MP4::Properties::codec(). + +TagLib 1.9 (Oct 6, 2013) +======================== + + * Added support for the Ogg Opus file format. + * Added support for INFO tags in WAV files. + * Changed FileStream to use Windows file API. + * Included taglib-config.cmd script for Windows. + * New ID3v1::Tag methods for working directly with genre numbers. + * New MPEG::File methods for checking which tags are saved in the file. + * Added support for the PropertyMap API to ASF and MP4 files. + * Added MusicBrainz identifiers to the PropertyMap API. + * Allowed reading of MP4 cover art without an explicitly specified format. + * Better parsing of corrupted FLAC files. + * Fixed saving of PropertyMap comments without description into ID3v2 tags. + * Fixed crash when parsing certain XM files. + * Fixed compilation of unit test with clang. + * Better handling of files that can't be open or have read-only permissions. + * Improved atomic reference counting. + * New hookable API for debug messages. + * More complete Windows install instructions. + * Many smaller bug fixes and performance improvements. + +TagLib 1.8 (Sep 6, 2012) +======================== + +1.8: + + * Added support for OWNE ID3 frames. + * Changed key validation in the new PropertyMap API. + * ID3v1::Tag::setStringHandler will no londer delete the previous handler, + the caller is responsible for this. + * File objects will also no longer delete the passed IOStream objects. It + should be done in the caller code after the File object is no longer + used. + * Added ID3v2::Tag::setLatin1StringHandler for custom handling of + latin1-encoded text in ID3v2 frames. + * Fixed validation of ID3v2 frame IDs (IDs with '0' were ignored). + +1.8 BETA: + + * New API for accessing tags by name. + * New abstract I/O stream layer to allow custom I/O handlers. + * Support for writing ID3v2.3 tags. + * Support for various module file formats (MOD, S3M, IT, XM). + * Support for MP4 and ASF is now enabled by default. + * Started using atomic int operations for reference counting. + * Added methods for checking if WMA and MP4 files are DRM-protected. + * Added taglib_free to the C bindings. + * New method to allow removing pictures from FLAC files. + * Support for reading audio properties from ALAC and Musepack SV8 files. + * Added replay-gain information to Musepack audio properties. + * Support for APEv2 binary tags. + * Many AudioProperties subclasses now provide information about the total number of samples. + * Various small bug fixes. + +TagLib 1.7.2 (Apr 20, 2012) +=========================== + + * Fixed division by zero while parsing corrupted MP4 files (CVE-2012-2396). + * Fixed compilation on Haiku. + +TagLib 1.7.1 (Mar 17, 2012) +=========================== + + * Improved parsing of corrupted WMA, RIFF and OGG files. + * Fixed a memory leak in the WMA parser. + * Fixed a memory leak in the FLAC parser. + * Fixed a possible division by zero in the APE parser. + * Added detection of TTA2 files. + * Fixed saving of multiple identically named tags to Vorbis Comments. + +TagLib 1.7 (Mar 11, 2011) +========================= + +1.7: + + * Fixed memory leaks in the FLAC file format parser. + * Fixed bitrate calculation for WAV files. + +1.7 RC1: + + * Support for reading/writing tags from Monkey's Audio files. (BUG:210404) + * Support for reading/writing embedded pictures from WMA files. + * Support for reading/writing embedded pictures from FLAC files (BUG:218696). + * Implemented APE::Tag::isEmpty() to check for all APE tags, not just the + basic ones. + * Added reading of WAV audio length. (BUG:116033) + * Exposed FLAC MD5 signature of the uncompressed audio stream via + FLAC::Properties::signature(). (BUG:160172) + * Added function ByteVector::toHex() for hex-encoding of byte vectors. + * WavPack reader now tries to get the audio length by finding the final + block, if the header doesn't have the information. (BUG:258016) + * Fixed a memory leak in the ID3v2.2 PIC frame parser. (BUG:257007) + * Fixed writing of RIFF files with even chunk sizes. (BUG:243954) + * Fixed compilation on MSVC 2010. + * Removed support for building using autoconf/automake. + * API docs can be now built using "make docs". + +TagLib 1.6.3 (Apr 17, 2010) +=========================== + + * Fixed definitions of the TAGLIB_WITH_MP4 and TAGLIB_WITH_ASF macros. + * Fixed upgrading of ID3v2.3 genre frame with ID3v1 code 0 (Blues). + * New method `int String::toInt(bool *ok)` which can return whether the + conversion to a number was successful. + * Fixed parsing of incorrectly written lengths in ID3v2 (affects mainly + compressed frames). (BUG:231075) + +TagLib 1.6.2 (Apr 9, 2010) +========================== + + * Read Vorbis Comments from the first FLAC metadata block, if there are + multiple ones. (BUG:211089) + * Fixed a memory leak in FileRef's OGA format detection. + * Fixed compilation with the Sun Studio compiler. (BUG:215225) + * Handle WM/TrackNumber attributes with DWORD content in WMA files. + (BUG:218526) + * More strict check if something is a valid MP4 file. (BUG:216819) + * Correctly save MP4 int-pair atoms with flags set to 0. + * Fixed compilation of the test runner on Windows. + * Store ASF attributes larger than 64k in the metadata library object. + * Ignore trailing non-data atoms when parsing MP4 covr atoms. + * Don't upgrade ID3v2.2 frame TDA to TDRC. (BUG:228968) + +TagLib 1.6.1 (Oct 31, 2009) +=========================== + + * Better detection of the audio codec of .oga files in FileRef. + * Fixed saving of Vorbis comments to Ogg FLAC files. TagLib tried to + include the Vorbis framing bit, which is only correct for Ogg Vorbis. + * Public symbols now have explicitly set visibility to "default" on GCC. + * Added missing exports for static ID3v1 functions. + * Fixed a typo in taglib_c.pc + * Fixed a failing test on ppc64. + * Support for binary 'covr' atom in MP4 files. TagLib 1.6 treated them + as text atoms, which corrupted them in some cases. + * Fixed ID3v1-style genre to string conversion in MP4 files. + +TagLib 1.6 (Sep 13, 2009) +========================= + +1.6: + + * New CMake option to build a static version - ENABLE_STATIC. + * Added support for disabling dllimport/dllexport on Windows using the + TAGLIB_STATIC macro. + * Support for parsing the obsolete 'gnre' MP4 atom. + * New cpp macros TAGLIB_WITH_MP4 and TAGLIB_WITH_ASF to determine if + TagLib was built with MP4/ASF support. + +1.6 RC1: + + * Split Ogg packets larger than 64k into multiple pages. (BUG:171957) + * TagLib can now use FLAC padding block. (BUG:107659) + * ID3v2.2 frames are now not incorrectly saved. (BUG:176373) + * Support for ID3v2.2 PIC frames. (BUG:167786) + * Fixed a bug in ByteVectorList::split(). + * XiphComment::year() now falls back to YEAR if DATE doesn't exist + and XiphComment::year() falls back to TRACKNUM if TRACKNUMBER doesn't + exist. (BUG:144396) + * Improved ID3v2.3 genre parsing. (BUG:188578) + * Better checking of corrupted ID3v2 APIC data. (BUG:168382) + * Bitrate calculating using the Xing header now uses floating point + numbers. (BUG:172556) + * New TagLib::String method rfind(). + * Added support for MP4 file format with iTunes-style metadata [optional]. + * Added support for ASF (WMA) file format [optional]. + * Fixed crash when saving a Locator APEv2 tag. (BUG:169810) + * Fixed a possible crash in the non-const version of String::operator[] + and in String::operator+=. (BUG:169389) + * Added support for PRIV ID3v2 frames. + * Empty ID3v2 genres are no longer treated as numeric ID3v1 genres. + * Added support for the POPM (rating/playcount) ID3v2 frame. + * Generic RIFF file format support: + * Support for AIFF files with ID3v2 tags. + * Support for WAV files with ID3v2 tags. + * Fixed crash on handling unsupported ID3v2 frames, e.g. on encrypted + frames. (BUG:161721) + * Fixed overflow while calculating bitrate of FLAC files with a very + high bitrate. diff --git a/Frameworks/TagLib/taglib/README.md b/Frameworks/TagLib/taglib/README.md new file mode 100644 index 000000000..1c9b1f38a --- /dev/null +++ b/Frameworks/TagLib/taglib/README.md @@ -0,0 +1,26 @@ +# TagLib + +[![Build Status](https://travis-ci.org/taglib/taglib.svg?branch=master)](https://travis-ci.org/taglib/taglib) + +### TagLib Audio Metadata Library + +http://taglib.org/ + +TagLib is a library for reading and editing the metadata of several +popular audio formats. Currently it supports both ID3v1 and [ID3v2][] +for MP3 files, [Ogg Vorbis][] comments and ID3 tags +in [FLAC][], MPC, Speex, WavPack, TrueAudio, WAV, AIFF, MP4, APE, +and ASF files. + +TagLib is distributed under the [GNU Lesser General Public License][] +(LGPL) and [Mozilla Public License][] (MPL). Essentially that means that +it may be used in proprietary applications, but if changes are made to +TagLib they must be contributed back to the project. Please review the +licenses if you are considering using TagLib in your project. + + [ID3v2]: http://www.id3.org + [Ogg Vorbis]: http://vorbis.com/ + [FLAC]: https://xiph.org/flac/ + [GNU Lesser General Public License]: http://www.gnu.org/licenses/lgpl.html + [Mozilla Public License]: http://www.mozilla.org/MPL/MPL-1.1.html + diff --git a/Frameworks/TagLib/taglib/bindings/CMakeLists.txt b/Frameworks/TagLib/taglib/bindings/CMakeLists.txt new file mode 100644 index 000000000..d019843ec --- /dev/null +++ b/Frameworks/TagLib/taglib/bindings/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(c) diff --git a/Frameworks/TagLib/taglib/bindings/README b/Frameworks/TagLib/taglib/bindings/README new file mode 100644 index 000000000..bfbd44ae0 --- /dev/null +++ b/Frameworks/TagLib/taglib/bindings/README @@ -0,0 +1,6 @@ +There are a few other people that have done bindings externally that I have +been made aware of. I have not personally reviewed these bindings, but I'm +listing them here so that those who find them useful are able to find them: + +http://developer.kde.org/~wheeler/taglib.html#bindings + diff --git a/Frameworks/TagLib/taglib/bindings/c/CMakeLists.txt b/Frameworks/TagLib/taglib/bindings/c/CMakeLists.txt new file mode 100644 index 000000000..ebb1267f1 --- /dev/null +++ b/Frameworks/TagLib/taglib/bindings/c/CMakeLists.txt @@ -0,0 +1,75 @@ +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/toolkit + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/asf + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpeg + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/vorbis + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/flac + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/flac + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpc + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mp4 + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpeg/id3v2 + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/mpeg/id3v2/frames + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/wavpack + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/speex + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/trueaudio +) + +set(tag_c_HDRS tag_c.h) + +add_library(tag_c tag_c.cpp ${tag_c_HDRS}) + +target_link_libraries(tag_c tag) +set_target_properties(tag_c PROPERTIES + PUBLIC_HEADER "${tag_c_HDRS}" + DEFINE_SYMBOL MAKE_TAGLIB_LIB +) +if(VISIBILITY_HIDDEN) + set_target_properties(tag_c PROPERTIES C_VISIBILITY_PRESET hidden + ) +endif() +if(BUILD_FRAMEWORK) + set_target_properties(tag_c PROPERTIES FRAMEWORK TRUE) +endif() + +# On Solaris we need to explicitly add the C++ standard and runtime +# libraries to the libs used by the C bindings, because those C bindings +# themselves won't pull in the C++ libs -- and if a C application is +# using the C bindings then we get link errors. +check_library_exists(Crun __RTTI___ "" HAVE_CRUN_LIB) +if(HAVE_CRUN_LIB) + # Which libraries to link depends critically on which + # STL version is going to be used by your application + # and which runtime is in use. While Crun is pretty much + # the only game in town, the three available STLs -- Cstd, + # stlport4 and stdcxx -- make this a mess. The KDE-Solaris + # team supports stdcxx (Apache RogueWave stdcxx 4.1.3). + + # According to http://bugs.kde.org/show_bug.cgi?id=215225 the library can have the following two names: + find_library(ROGUEWAVE_STDCXX_LIBRARY NAMES stdcxx4 stdcxx) + if(NOT ROGUEWAVE_STDCXX_LIBRARY) + message(FATAL_ERROR "Did not find supported STL library (tried stdcxx4 and stdcxx)") + endif() + target_link_libraries(tag_c ${ROGUEWAVE_STDCXX_LIBRARY} Crun) +endif() + +set_target_properties(tag_c PROPERTIES + VERSION 0.0.0 + SOVERSION 0 + DEFINE_SYMBOL MAKE_TAGLIB_C_LIB + INSTALL_NAME_DIR ${LIB_INSTALL_DIR} +) +install(TARGETS tag_c + FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + RUNTIME DESTINATION ${BIN_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} + PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR}/taglib +) + +if(NOT BUILD_FRAMEWORK) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/taglib_c.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/taglib_c.pc) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/taglib_c.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) +endif() + diff --git a/Frameworks/TagLib/taglib/bindings/c/tag_c.cpp b/Frameworks/TagLib/taglib/bindings/c/tag_c.cpp new file mode 100644 index 000000000..04c549566 --- /dev/null +++ b/Frameworks/TagLib/taglib/bindings/c/tag_c.cpp @@ -0,0 +1,315 @@ +/*************************************************************************** + copyright : (C) 2003 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * USA * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tag_c.h" + +using namespace TagLib; + +namespace +{ + List strings; + bool unicodeStrings = true; + bool stringManagementEnabled = true; + + char *stringToCharArray(const String &s) + { + const std::string str = s.to8Bit(unicodeStrings); + +#ifdef HAVE_ISO_STRDUP + + return ::_strdup(str.c_str()); + +#else + + return ::strdup(str.c_str()); + +#endif + } + + String charArrayToString(const char *s) + { + return String(s, unicodeStrings ? String::UTF8 : String::Latin1); + } +} + +void taglib_set_strings_unicode(BOOL unicode) +{ + unicodeStrings = (unicode != 0); +} + +void taglib_set_string_management_enabled(BOOL management) +{ + stringManagementEnabled = (management != 0); +} + +void taglib_free(void* pointer) +{ + free(pointer); +} + +//////////////////////////////////////////////////////////////////////////////// +// TagLib::File wrapper +//////////////////////////////////////////////////////////////////////////////// + +TagLib_File *taglib_file_new(const char *filename) +{ + return reinterpret_cast(FileRef::create(filename)); +} + +TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type) +{ + switch(type) { + case TagLib_File_MPEG: + return reinterpret_cast(new MPEG::File(filename)); + case TagLib_File_OggVorbis: + return reinterpret_cast(new Ogg::Vorbis::File(filename)); + case TagLib_File_FLAC: + return reinterpret_cast(new FLAC::File(filename)); + case TagLib_File_MPC: + return reinterpret_cast(new MPC::File(filename)); + case TagLib_File_OggFlac: + return reinterpret_cast(new Ogg::FLAC::File(filename)); + case TagLib_File_WavPack: + return reinterpret_cast(new WavPack::File(filename)); + case TagLib_File_Speex: + return reinterpret_cast(new Ogg::Speex::File(filename)); + case TagLib_File_TrueAudio: + return reinterpret_cast(new TrueAudio::File(filename)); + case TagLib_File_MP4: + return reinterpret_cast(new MP4::File(filename)); + case TagLib_File_ASF: + return reinterpret_cast(new ASF::File(filename)); + default: + return 0; + } +} + +void taglib_file_free(TagLib_File *file) +{ + delete reinterpret_cast(file); +} + +BOOL taglib_file_is_valid(const TagLib_File *file) +{ + return reinterpret_cast(file)->isValid(); +} + +TagLib_Tag *taglib_file_tag(const TagLib_File *file) +{ + const File *f = reinterpret_cast(file); + return reinterpret_cast(f->tag()); +} + +const TagLib_AudioProperties *taglib_file_audioproperties(const TagLib_File *file) +{ + const File *f = reinterpret_cast(file); + return reinterpret_cast(f->audioProperties()); +} + +BOOL taglib_file_save(TagLib_File *file) +{ + return reinterpret_cast(file)->save(); +} + +//////////////////////////////////////////////////////////////////////////////// +// TagLib::Tag wrapper +//////////////////////////////////////////////////////////////////////////////// + +char *taglib_tag_title(const TagLib_Tag *tag) +{ + const Tag *t = reinterpret_cast(tag); + char *s = stringToCharArray(t->title()); + if(stringManagementEnabled) + strings.append(s); + return s; +} + +char *taglib_tag_artist(const TagLib_Tag *tag) +{ + const Tag *t = reinterpret_cast(tag); + char *s = stringToCharArray(t->artist()); + if(stringManagementEnabled) + strings.append(s); + return s; +} + +char *taglib_tag_album(const TagLib_Tag *tag) +{ + const Tag *t = reinterpret_cast(tag); + char *s = stringToCharArray(t->album()); + if(stringManagementEnabled) + strings.append(s); + return s; +} + +char *taglib_tag_comment(const TagLib_Tag *tag) +{ + const Tag *t = reinterpret_cast(tag); + char *s = stringToCharArray(t->comment()); + if(stringManagementEnabled) + strings.append(s); + return s; +} + +char *taglib_tag_genre(const TagLib_Tag *tag) +{ + const Tag *t = reinterpret_cast(tag); + char *s = stringToCharArray(t->genre()); + if(stringManagementEnabled) + strings.append(s); + return s; +} + +unsigned int taglib_tag_year(const TagLib_Tag *tag) +{ + const Tag *t = reinterpret_cast(tag); + return t->year(); +} + +unsigned int taglib_tag_track(const TagLib_Tag *tag) +{ + const Tag *t = reinterpret_cast(tag); + return t->track(); +} + +void taglib_tag_set_title(TagLib_Tag *tag, const char *title) +{ + Tag *t = reinterpret_cast(tag); + t->setTitle(charArrayToString(title)); +} + +void taglib_tag_set_artist(TagLib_Tag *tag, const char *artist) +{ + Tag *t = reinterpret_cast(tag); + t->setArtist(charArrayToString(artist)); +} + +void taglib_tag_set_album(TagLib_Tag *tag, const char *album) +{ + Tag *t = reinterpret_cast(tag); + t->setAlbum(charArrayToString(album)); +} + +void taglib_tag_set_comment(TagLib_Tag *tag, const char *comment) +{ + Tag *t = reinterpret_cast(tag); + t->setComment(charArrayToString(comment)); +} + +void taglib_tag_set_genre(TagLib_Tag *tag, const char *genre) +{ + Tag *t = reinterpret_cast(tag); + t->setGenre(charArrayToString(genre)); +} + +void taglib_tag_set_year(TagLib_Tag *tag, unsigned int year) +{ + Tag *t = reinterpret_cast(tag); + t->setYear(year); +} + +void taglib_tag_set_track(TagLib_Tag *tag, unsigned int track) +{ + Tag *t = reinterpret_cast(tag); + t->setTrack(track); +} + +void taglib_tag_free_strings() +{ + if(!stringManagementEnabled) + return; + + for(List::ConstIterator it = strings.begin(); it != strings.end(); ++it) + free(*it); + strings.clear(); +} + +//////////////////////////////////////////////////////////////////////////////// +// TagLib::AudioProperties wrapper +//////////////////////////////////////////////////////////////////////////////// + +int taglib_audioproperties_length(const TagLib_AudioProperties *audioProperties) +{ + const AudioProperties *p = reinterpret_cast(audioProperties); + return p->length(); +} + +int taglib_audioproperties_bitrate(const TagLib_AudioProperties *audioProperties) +{ + const AudioProperties *p = reinterpret_cast(audioProperties); + return p->bitrate(); +} + +int taglib_audioproperties_samplerate(const TagLib_AudioProperties *audioProperties) +{ + const AudioProperties *p = reinterpret_cast(audioProperties); + return p->sampleRate(); +} + +int taglib_audioproperties_channels(const TagLib_AudioProperties *audioProperties) +{ + const AudioProperties *p = reinterpret_cast(audioProperties); + return p->channels(); +} + +void taglib_id3v2_set_default_text_encoding(TagLib_ID3v2_Encoding encoding) +{ + String::Type type = String::Latin1; + + switch(encoding) + { + case TagLib_ID3v2_Latin1: + type = String::Latin1; + break; + case TagLib_ID3v2_UTF16: + type = String::UTF16; + break; + case TagLib_ID3v2_UTF16BE: + type = String::UTF16BE; + break; + case TagLib_ID3v2_UTF8: + type = String::UTF8; + break; + } + + ID3v2::FrameFactory::instance()->setDefaultTextEncoding(type); +} diff --git a/Frameworks/TagLib/taglib/bindings/c/tag_c.h b/Frameworks/TagLib/taglib/bindings/c/tag_c.h new file mode 100644 index 000000000..8d5f85ff7 --- /dev/null +++ b/Frameworks/TagLib/taglib/bindings/c/tag_c.h @@ -0,0 +1,299 @@ +/*************************************************************************** + copyright : (C) 2003 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * + * USA * + ***************************************************************************/ + +#ifndef TAGLIB_TAG_C +#define TAGLIB_TAG_C + +/* Do not include this in the main TagLib documentation. */ +#ifndef DO_NOT_DOCUMENT + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(TAGLIB_STATIC) +#define TAGLIB_C_EXPORT +#elif defined(_WIN32) || defined(_WIN64) +#ifdef MAKE_TAGLIB_C_LIB +#define TAGLIB_C_EXPORT __declspec(dllexport) +#else +#define TAGLIB_C_EXPORT __declspec(dllimport) +#endif +#elif defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 1) +#define TAGLIB_C_EXPORT __attribute__ ((visibility("default"))) +#else +#define TAGLIB_C_EXPORT +#endif + +#ifndef BOOL +#define BOOL int +#endif + +/******************************************************************************* + * [ TagLib C Binding ] + * + * This is an interface to TagLib's "simple" API, meaning that you can read and + * modify media files in a generic, but not specialized way. This is a rough + * representation of TagLib::File and TagLib::Tag, for which the documentation + * is somewhat more complete and worth consulting. + *******************************************************************************/ + +/* + * These are used for type provide some type safety to the C API (as opposed to + * using void *, but pointers to them are simply cast to the corresponding C++ + * types in the implementation. + */ + +typedef struct { int dummy; } TagLib_File; +typedef struct { int dummy; } TagLib_Tag; +typedef struct { int dummy; } TagLib_AudioProperties; + +/*! + * By default all strings coming into or out of TagLib's C API are in UTF8. + * However, it may be desirable for TagLib to operate on Latin1 (ISO-8859-1) + * strings in which case this should be set to FALSE. + */ +TAGLIB_C_EXPORT void taglib_set_strings_unicode(BOOL unicode); + +/*! + * TagLib can keep track of strings that are created when outputting tag values + * and clear them using taglib_tag_clear_strings(). This is enabled by default. + * However if you wish to do more fine grained management of strings, you can do + * so by setting \a management to FALSE. + */ +TAGLIB_C_EXPORT void taglib_set_string_management_enabled(BOOL management); + +/*! + * Explicitly free a string returned from TagLib + */ +TAGLIB_C_EXPORT void taglib_free(void* pointer); + +/******************************************************************************* + * File API + ******************************************************************************/ + +typedef enum { + TagLib_File_MPEG, + TagLib_File_OggVorbis, + TagLib_File_FLAC, + TagLib_File_MPC, + TagLib_File_OggFlac, + TagLib_File_WavPack, + TagLib_File_Speex, + TagLib_File_TrueAudio, + TagLib_File_MP4, + TagLib_File_ASF +} TagLib_File_Type; + +/*! + * Creates a TagLib file based on \a filename. TagLib will try to guess the file + * type. + * + * \returns NULL if the file type cannot be determined or the file cannot + * be opened. + */ +TAGLIB_C_EXPORT TagLib_File *taglib_file_new(const char *filename); + +/*! + * Creates a TagLib file based on \a filename. Rather than attempting to guess + * the type, it will use the one specified by \a type. + */ +TAGLIB_C_EXPORT TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type); + +/*! + * Frees and closes the file. + */ +TAGLIB_C_EXPORT void taglib_file_free(TagLib_File *file); + +/*! + * Returns true if the file is open and readable and valid information for + * the Tag and / or AudioProperties was found. + */ + +TAGLIB_C_EXPORT BOOL taglib_file_is_valid(const TagLib_File *file); + +/*! + * Returns a pointer to the tag associated with this file. This will be freed + * automatically when the file is freed. + */ +TAGLIB_C_EXPORT TagLib_Tag *taglib_file_tag(const TagLib_File *file); + +/*! + * Returns a pointer to the audio properties associated with this file. This + * will be freed automatically when the file is freed. + */ +TAGLIB_C_EXPORT const TagLib_AudioProperties *taglib_file_audioproperties(const TagLib_File *file); + +/*! + * Saves the \a file to disk. + */ +TAGLIB_C_EXPORT BOOL taglib_file_save(TagLib_File *file); + +/****************************************************************************** + * Tag API + ******************************************************************************/ + +/*! + * Returns a string with this tag's title. + * + * \note By default this string should be UTF8 encoded and its memory should be + * freed using taglib_tag_free_strings(). + */ +TAGLIB_C_EXPORT char *taglib_tag_title(const TagLib_Tag *tag); + +/*! + * Returns a string with this tag's artist. + * + * \note By default this string should be UTF8 encoded and its memory should be + * freed using taglib_tag_free_strings(). + */ +TAGLIB_C_EXPORT char *taglib_tag_artist(const TagLib_Tag *tag); + +/*! + * Returns a string with this tag's album name. + * + * \note By default this string should be UTF8 encoded and its memory should be + * freed using taglib_tag_free_strings(). + */ +TAGLIB_C_EXPORT char *taglib_tag_album(const TagLib_Tag *tag); + +/*! + * Returns a string with this tag's comment. + * + * \note By default this string should be UTF8 encoded and its memory should be + * freed using taglib_tag_free_strings(). + */ +TAGLIB_C_EXPORT char *taglib_tag_comment(const TagLib_Tag *tag); + +/*! + * Returns a string with this tag's genre. + * + * \note By default this string should be UTF8 encoded and its memory should be + * freed using taglib_tag_free_strings(). + */ +TAGLIB_C_EXPORT char *taglib_tag_genre(const TagLib_Tag *tag); + +/*! + * Returns the tag's year or 0 if year is not set. + */ +TAGLIB_C_EXPORT unsigned int taglib_tag_year(const TagLib_Tag *tag); + +/*! + * Returns the tag's track number or 0 if track number is not set. + */ +TAGLIB_C_EXPORT unsigned int taglib_tag_track(const TagLib_Tag *tag); + +/*! + * Sets the tag's title. + * + * \note By default this string should be UTF8 encoded. + */ +TAGLIB_C_EXPORT void taglib_tag_set_title(TagLib_Tag *tag, const char *title); + +/*! + * Sets the tag's artist. + * + * \note By default this string should be UTF8 encoded. + */ +TAGLIB_C_EXPORT void taglib_tag_set_artist(TagLib_Tag *tag, const char *artist); + +/*! + * Sets the tag's album. + * + * \note By default this string should be UTF8 encoded. + */ +TAGLIB_C_EXPORT void taglib_tag_set_album(TagLib_Tag *tag, const char *album); + +/*! + * Sets the tag's comment. + * + * \note By default this string should be UTF8 encoded. + */ +TAGLIB_C_EXPORT void taglib_tag_set_comment(TagLib_Tag *tag, const char *comment); + +/*! + * Sets the tag's genre. + * + * \note By default this string should be UTF8 encoded. + */ +TAGLIB_C_EXPORT void taglib_tag_set_genre(TagLib_Tag *tag, const char *genre); + +/*! + * Sets the tag's year. 0 indicates that this field should be cleared. + */ +TAGLIB_C_EXPORT void taglib_tag_set_year(TagLib_Tag *tag, unsigned int year); + +/*! + * Sets the tag's track number. 0 indicates that this field should be cleared. + */ +TAGLIB_C_EXPORT void taglib_tag_set_track(TagLib_Tag *tag, unsigned int track); + +/*! + * Frees all of the strings that have been created by the tag. + */ +TAGLIB_C_EXPORT void taglib_tag_free_strings(void); + +/****************************************************************************** + * Audio Properties API + ******************************************************************************/ + +/*! + * Returns the length of the file in seconds. + */ +TAGLIB_C_EXPORT int taglib_audioproperties_length(const TagLib_AudioProperties *audioProperties); + +/*! + * Returns the bitrate of the file in kb/s. + */ +TAGLIB_C_EXPORT int taglib_audioproperties_bitrate(const TagLib_AudioProperties *audioProperties); + +/*! + * Returns the sample rate of the file in Hz. + */ +TAGLIB_C_EXPORT int taglib_audioproperties_samplerate(const TagLib_AudioProperties *audioProperties); + +/*! + * Returns the number of channels in the audio stream. + */ +TAGLIB_C_EXPORT int taglib_audioproperties_channels(const TagLib_AudioProperties *audioProperties); + +/******************************************************************************* + * Special convenience ID3v2 functions + *******************************************************************************/ + +typedef enum { + TagLib_ID3v2_Latin1, + TagLib_ID3v2_UTF16, + TagLib_ID3v2_UTF16BE, + TagLib_ID3v2_UTF8 +} TagLib_ID3v2_Encoding; + +/*! + * This sets the default encoding for ID3v2 frames that are written to tags. + */ + +TAGLIB_C_EXPORT void taglib_id3v2_set_default_text_encoding(TagLib_ID3v2_Encoding encoding); + +#ifdef __cplusplus +} +#endif +#endif /* DO_NOT_DOCUMENT */ +#endif diff --git a/Frameworks/TagLib/taglib/bindings/c/taglib_c.pc.cmake b/Frameworks/TagLib/taglib/bindings/c/taglib_c.pc.cmake new file mode 100644 index 000000000..232f4f784 --- /dev/null +++ b/Frameworks/TagLib/taglib/bindings/c/taglib_c.pc.cmake @@ -0,0 +1,12 @@ +prefix=${CMAKE_INSTALL_PREFIX} +exec_prefix=${CMAKE_INSTALL_PREFIX} +libdir=${LIB_INSTALL_DIR} +includedir=${INCLUDE_INSTALL_DIR} + + +Name: TagLib C Bindings +Description: Audio meta-data library (C bindings) +Requires: taglib +Version: ${TAGLIB_LIB_VERSION_STRING} +Libs: -L${LIB_INSTALL_DIR} -ltag_c +Cflags: -I${INCLUDE_INSTALL_DIR}/taglib diff --git a/Frameworks/TagLib/taglib/cmake/modules/FindCppUnit.cmake b/Frameworks/TagLib/taglib/cmake/modules/FindCppUnit.cmake new file mode 100644 index 000000000..adaaeb614 --- /dev/null +++ b/Frameworks/TagLib/taglib/cmake/modules/FindCppUnit.cmake @@ -0,0 +1,69 @@ +# - Try to find the libcppunit libraries +# Once done this will define +# +# CppUnit_FOUND - system has libcppunit +# CPPUNIT_INCLUDE_DIR - the libcppunit include directory +# CPPUNIT_LIBRARIES - libcppunit library + +include (MacroEnsureVersion) + +if(NOT CPPUNIT_MIN_VERSION) + SET(CPPUNIT_MIN_VERSION 1.12.0) +endif(NOT CPPUNIT_MIN_VERSION) + +FIND_PROGRAM(CPPUNIT_CONFIG_EXECUTABLE cppunit-config ) + +IF(CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARIES) + + # in cache already + SET(CppUnit_FOUND TRUE) + +ELSE(CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARIES) + + SET(CPPUNIT_INCLUDE_DIR) + SET(CPPUNIT_LIBRARIES) + + IF(CPPUNIT_CONFIG_EXECUTABLE) + EXEC_PROGRAM(${CPPUNIT_CONFIG_EXECUTABLE} ARGS --cflags RETURN_VALUE _return_VALUE OUTPUT_VARIABLE CPPUNIT_CFLAGS) + EXEC_PROGRAM(${CPPUNIT_CONFIG_EXECUTABLE} ARGS --libs RETURN_VALUE _return_VALUE OUTPUT_VARIABLE CPPUNIT_LIBRARIES) + EXEC_PROGRAM(${CPPUNIT_CONFIG_EXECUTABLE} ARGS --version RETURN_VALUE _return_VALUE OUTPUT_VARIABLE CPPUNIT_INSTALLED_VERSION) + STRING(REGEX REPLACE "-I(.+)" "\\1" CPPUNIT_CFLAGS "${CPPUNIT_CFLAGS}") + ELSE(CPPUNIT_CONFIG_EXECUTABLE) + # in case win32 needs to find it the old way? + FIND_PATH(CPPUNIT_CFLAGS cppunit/TestRunner.h PATHS /usr/include /usr/local/include ) + FIND_LIBRARY(CPPUNIT_LIBRARIES NAMES cppunit PATHS /usr/lib /usr/local/lib ) + # how can we find cppunit version? + MESSAGE (STATUS "Ensure you cppunit installed version is at least ${CPPUNIT_MIN_VERSION}") + SET (CPPUNIT_INSTALLED_VERSION ${CPPUNIT_MIN_VERSION}) + ENDIF(CPPUNIT_CONFIG_EXECUTABLE) + + SET(CPPUNIT_INCLUDE_DIR ${CPPUNIT_CFLAGS} "${CPPUNIT_CFLAGS}/cppunit") + +ENDIF(CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARIES) + +IF(CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARIES) + + SET(CppUnit_FOUND TRUE) + + if(NOT CppUnit_FIND_QUIETLY) + MESSAGE (STATUS "Found cppunit: ${CPPUNIT_LIBRARIES}") + endif(NOT CppUnit_FIND_QUIETLY) + + IF(CPPUNIT_CONFIG_EXECUTABLE) + EXEC_PROGRAM(${CPPUNIT_CONFIG_EXECUTABLE} ARGS --version RETURN_VALUE _return_VALUE OUTPUT_VARIABLE CPPUNIT_INSTALLED_VERSION) + ENDIF(CPPUNIT_CONFIG_EXECUTABLE) + + macro_ensure_version( ${CPPUNIT_MIN_VERSION} ${CPPUNIT_INSTALLED_VERSION} CPPUNIT_INSTALLED_VERSION_OK ) + + IF(NOT CPPUNIT_INSTALLED_VERSION_OK) + MESSAGE ("** CppUnit version is too old: found ${CPPUNIT_INSTALLED_VERSION} installed, ${CPPUNIT_MIN_VERSION} or major is required") + SET(CppUnit_FOUND FALSE) + ENDIF(NOT CPPUNIT_INSTALLED_VERSION_OK) + +ELSE(CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARIES) + + SET(CppUnit_FOUND FALSE CACHE BOOL "Not found cppunit library") + +ENDIF(CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARIES) + +MARK_AS_ADVANCED(CPPUNIT_INCLUDE_DIR CPPUNIT_LIBRARIES) diff --git a/Frameworks/TagLib/taglib/cmake/modules/MacroEnsureVersion.cmake b/Frameworks/TagLib/taglib/cmake/modules/MacroEnsureVersion.cmake new file mode 100644 index 000000000..c6df537a4 --- /dev/null +++ b/Frameworks/TagLib/taglib/cmake/modules/MacroEnsureVersion.cmake @@ -0,0 +1,71 @@ +# This macro compares version numbers of the form "x.y.z" +# MACRO_ENSURE_VERSION( FOO_MIN_VERSION FOO_VERSION_FOUND FOO_VERSION_OK) +# will set FOO_VERSIN_OK to true if FOO_VERSION_FOUND >= FOO_MIN_VERSION +# where both have to be in a 3-part-version format, leading and trailing +# text is ok, e.g. +# MACRO_ENSURE_VERSION( "2.5.31" "flex 2.5.4a" VERSION_OK) +# which means 2.5.31 is required and "flex 2.5.4a" is what was found on the system + +# Copyright (c) 2006, David Faure, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +MACRO(MACRO_ENSURE_VERSION requested_version found_version var_too_old) + + # parse the parts of the version string + STRING(REGEX REPLACE "([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" req_major_vers "${requested_version}") + STRING(REGEX REPLACE "[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" req_minor_vers "${requested_version}") + STRING(REGEX REPLACE "[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" req_patch_vers "${requested_version}") + + STRING(REGEX REPLACE "[^0-9]*([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" found_major_vers "${found_version}") + STRING(REGEX REPLACE "[^0-9]*[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" found_minor_vers "${found_version}") + STRING(REGEX REPLACE "[^0-9]*[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" found_patch_vers "${found_version}") + + # compute an overall version number which can be compared at once + MATH(EXPR req_vers_num "${req_major_vers}*10000 + ${req_minor_vers}*100 + ${req_patch_vers}") + MATH(EXPR found_vers_num "${found_major_vers}*10000 + ${found_minor_vers}*100 + ${found_patch_vers}") + + if (found_vers_num LESS req_vers_num) + set( ${var_too_old} FALSE ) + else (found_vers_num LESS req_vers_num) + set( ${var_too_old} TRUE ) + endif (found_vers_num LESS req_vers_num) + +ENDMACRO(MACRO_ENSURE_VERSION) + + +# This macro compares version numbers of the form "x.y" +# MACRO_ENSURE_VERSION( FOO_MIN_VERSION FOO_VERSION_FOUND FOO_VERSION_OK) +# will set FOO_VERSIN_OK to true if FOO_VERSION_FOUND >= FOO_MIN_VERSION +# where both have to be in a 2-part-version format, leading and trailing +# text is ok, e.g. +# MACRO_ENSURE_VERSION( "0.5" "foo 0.6" VERSION_OK) +# which means 0.5 is required and "foo 0.6" is what was found on the system + +# Copyright (c) 2006, David Faure, +# Copyright (c) 2007, Pino Toscano, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +MACRO(MACRO_ENSURE_VERSION2 requested_version found_version var_too_old) + + # parse the parts of the version string + STRING(REGEX REPLACE "([0-9]+)\\.[0-9]+" "\\1" req_major_vers "${requested_version}") + STRING(REGEX REPLACE "[0-9]+\\.([0-9]+)" "\\1" req_minor_vers "${requested_version}") + + STRING(REGEX REPLACE "[^0-9]*([0-9]+)\\.[0-9]+.*" "\\1" found_major_vers "${found_version}") + STRING(REGEX REPLACE "[^0-9]*[0-9]+\\.([0-9]+).*" "\\1" found_minor_vers "${found_version}") + + # compute an overall version number which can be compared at once + MATH(EXPR req_vers_num "${req_major_vers}*100 + ${req_minor_vers}") + MATH(EXPR found_vers_num "${found_major_vers}*100 + ${found_minor_vers}") + + if (found_vers_num LESS req_vers_num) + set( ${var_too_old} FALSE ) + else (found_vers_num LESS req_vers_num) + set( ${var_too_old} TRUE ) + endif (found_vers_num LESS req_vers_num) + +ENDMACRO(MACRO_ENSURE_VERSION2) diff --git a/Frameworks/TagLib/taglib/cmake_uninstall.cmake.in b/Frameworks/TagLib/taglib/cmake_uninstall.cmake.in new file mode 100644 index 000000000..72e030fb3 --- /dev/null +++ b/Frameworks/TagLib/taglib/cmake_uninstall.cmake.in @@ -0,0 +1,21 @@ +if (NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +endif() + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach (file ${files}) + message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + if (EXISTS "$ENV{DESTDIR}${file}") + execute_process( + COMMAND @CMAKE_COMMAND@ -E remove "$ENV{DESTDIR}${file}" + OUTPUT_VARIABLE rm_out + RESULT_VARIABLE rm_retval + ) + if(NOT ${rm_retval} EQUAL 0) + message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + endif () + else () + message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + endif () +endforeach() diff --git a/Frameworks/TagLib/taglib/config.h b/Frameworks/TagLib/taglib/config.h index b0fb3e5a0..fa812887f 100644 --- a/Frameworks/TagLib/taglib/config.h +++ b/Frameworks/TagLib/taglib/config.h @@ -1,11 +1,34 @@ -/* config-taglib.h. Generated by cmake from config-taglib.h.cmake */ +/* config.h. Generated by cmake from config.h.cmake */ -/* NOTE: only add something here if it is really needed by all of kdelibs. - Otherwise please prefer adding to the relevant config-foo.h.cmake file, - to minimize recompilations and increase modularity. */ -/* Define if you have libz */ +#ifndef TAGLIB_CONFIG_H +#define TAGLIB_CONFIG_H + +/* Defined if your compiler supports some byte swap functions */ +#define HAVE_GCC_BYTESWAP 1 +/* #undef HAVE_GLIBC_BYTESWAP */ +/* #undef HAVE_MSC_BYTESWAP */ +/* #undef HAVE_MAC_BYTESWAP */ +/* #undef HAVE_OPENBSD_BYTESWAP */ + +/* Defined if your compiler supports some atomic operations */ +#define HAVE_GCC_ATOMIC 1 +/* #undef HAVE_MAC_ATOMIC */ +/* #undef HAVE_WIN_ATOMIC */ +/* #undef HAVE_IA64_ATOMIC */ + +/* Defined if your compiler supports some safer version of vsprintf */ +#define HAVE_VSNPRINTF 1 +/* #undef HAVE_VSPRINTF_S */ + +/* Defined if your compiler supports ISO _strdup */ +/* #undef HAVE_ISO_STRDUP */ + +/* Defined if zlib is installed */ #define HAVE_ZLIB 1 -/* #undef NO_ITUNES_HACKS */ -#define WITH_ASF 1 -#define WITH_MP4 1 +/* Indicates whether debug messages are shown even in release mode */ +/* #undef TRACE_IN_RELEASE */ + +#define TESTS_DIR "/Users/nevack/work/taglib/tests/" + +#endif diff --git a/Frameworks/TagLib/taglib/config.h.cmake b/Frameworks/TagLib/taglib/config.h.cmake new file mode 100644 index 000000000..8d8c36abd --- /dev/null +++ b/Frameworks/TagLib/taglib/config.h.cmake @@ -0,0 +1,34 @@ +/* config.h. Generated by cmake from config.h.cmake */ + +#ifndef TAGLIB_CONFIG_H +#define TAGLIB_CONFIG_H + +/* Defined if your compiler supports some byte swap functions */ +#cmakedefine HAVE_GCC_BYTESWAP 1 +#cmakedefine HAVE_GLIBC_BYTESWAP 1 +#cmakedefine HAVE_MSC_BYTESWAP 1 +#cmakedefine HAVE_MAC_BYTESWAP 1 +#cmakedefine HAVE_OPENBSD_BYTESWAP 1 + +/* Defined if your compiler supports some atomic operations */ +#cmakedefine HAVE_GCC_ATOMIC 1 +#cmakedefine HAVE_MAC_ATOMIC 1 +#cmakedefine HAVE_WIN_ATOMIC 1 +#cmakedefine HAVE_IA64_ATOMIC 1 + +/* Defined if your compiler supports some safer version of vsprintf */ +#cmakedefine HAVE_VSNPRINTF 1 +#cmakedefine HAVE_VSPRINTF_S 1 + +/* Defined if your compiler supports ISO _strdup */ +#cmakedefine HAVE_ISO_STRDUP 1 + +/* Defined if zlib is installed */ +#cmakedefine HAVE_ZLIB 1 + +/* Indicates whether debug messages are shown even in release mode */ +#cmakedefine TRACE_IN_RELEASE 1 + +#cmakedefine TESTS_DIR "@TESTS_DIR@" + +#endif diff --git a/Frameworks/TagLib/taglib/doc/README b/Frameworks/TagLib/taglib/doc/README new file mode 100644 index 000000000..b2ebd36d6 --- /dev/null +++ b/Frameworks/TagLib/taglib/doc/README @@ -0,0 +1 @@ +Run "make docs" in the parent directory to generate the TagLib API documentation. diff --git a/Frameworks/TagLib/taglib/doc/api-footer.html b/Frameworks/TagLib/taglib/doc/api-footer.html new file mode 100644 index 000000000..9b151ee89 --- /dev/null +++ b/Frameworks/TagLib/taglib/doc/api-footer.html @@ -0,0 +1,4 @@ + + + + diff --git a/Frameworks/TagLib/taglib/doc/api-header.html b/Frameworks/TagLib/taglib/doc/api-header.html new file mode 100644 index 000000000..ab133c258 --- /dev/null +++ b/Frameworks/TagLib/taglib/doc/api-header.html @@ -0,0 +1,41 @@ + + + + $title ($projectname) + + + + + +
+ + + + + + +
+ + +
+ + + + + +

TagLib $projectnumber ($title)

+ +
+
+
+ +
diff --git a/Frameworks/TagLib/taglib/doc/taglib-api.css b/Frameworks/TagLib/taglib/doc/taglib-api.css new file mode 100644 index 000000000..3fa820e25 --- /dev/null +++ b/Frameworks/TagLib/taglib/doc/taglib-api.css @@ -0,0 +1,395 @@ +body { + font-family: sans-serif; + background: white; + color: black; + margin: 0px; + padding: 15px; +} + +a:link { + font-weight: bold; + text-decoration: none; + color: gray; +} + +a:visited { + font-weight: bold; + text-decoration: none; + color: gray; +} + +a:hover { + color: #cccccc; + text-decoration: underline; +} + +a:active { + color: #cccccc; + text-decoration: underline; +} + +img { + border-style: none; +} + +h1 { + font-family: sans-serif; +} + +h2 { + font-family: sans-serif; +} + +h3 { + font-family: sans-serif; +} + +/* container */ + +#container { + position: absolute; + border-width: thin; + border-style: solid; + width: 95%; +} + +/* intro */ + +#intro { + padding: 5px; + margin: 0px; + background: #cccccc; + border-width: medium; + border-style: solid; +} + +#intro h1 { + margin: 5px; + padding: 5px; +} + +/* links */ + +#links { + font-size: x-small; + vertical-align: bottom; +} + +#links a { + border-width: thin; + border-style: dotted; + border-color: white; + /* margin: 0px 10px 0px 0px; */ + margin: 1px; + padding: 3px; + line-height: 230% +} + +#links a:hover { + color: black; + text-decoration: underline; +} + +#links h3 { + outline-width: thin; + border-style: solid; + padding: 2px; + margin: 3px 0px 3px 0px; +} + +/* menu */ + +#menu h3 { + text-align: center; +} + +/* text */ + +#text { + margin: 0px; + padding: 5px 5px 0px 5px; + float: left; +} + +#text h3 { + border-width: thin; + border-style: solid; + padding: 2px; + margin: 3px 0px 3px 0px; +} + +#text li { + margin: 0px 0px 10px 0px; +} + +#text ul { + margin: 5px; + padding: 0px 0px 0px 20px; +} + +#leftcolumn { + float: left; + width: 300px; + margin: 0px 10px 0px 0px; + padding: 0px; +} + +#rightcolumn { + float: right; + width: 210px; + margin: 0px; + padding: 0px; +} + +/* vspacer */ + +.vspacer { + height: 10px; +} + +.silver { + border-width: thin; + border-color: black; + border-style: solid; + background: #cccccc; +} + +a.code { + text-decoration: none; + font-weight: normal; + color: #4444ee +} + +a.codeRef { + font-weight: normal; + color: #4444ee +} + +div.fragment { + width: 98%; + border: 1px solid #CCCCCC; + background-color: #f5f5f5; + padding-left: 4px; + margin: 4px; +} + +div.ah { + background-color: black; + font-weight: bold; color: #ffffff; + margin-bottom: 3px; + margin-top: 3px +} + +#text td { + width: auto; +} + +div.memdoc { + margin-top: 0px; + margin-bottom: 20px; + padding: 10px 10px 10px 40px; +} + +div.memproto { + border: thin solid black; + background-color: #f2f2ff; + width: 100%; + margin-top: 20px; + padding-top: 10px; + padding-bottom: 10px; +} + +td.paramtype { + color: #602020; +} + +table.memname { + font-weight: bold; +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + margin-bottom: 6px; + font-weight: bold +} + +div.groupText { + margin-left: 16px; + font-style: italic; + font-size: smaller +} + +body { + background: white; + color: black; + margin-right: 20px; + margin-left: 20px; +} + +td.indexkey { + background-color: #eeeeff; + font-weight: bold; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px +} + +td.indexvalue { + background-color: #eeeeff; + font-style: italic; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px +} + +tr.memlist { + background-color: #f0f0f0; +} + +p.formulaDsp { + text-align: center; +} + +img.formulaDsp { + +} + +img.formulaInl { + vertical-align: middle; +} + +span.keyword { + color: #008000 +} + +span.keywordtype { + color: #604020 +} + +span.keywordflow { + color: #e08000 +} + +span.comment { + color: #800000 +} + +span.preprocessor { + color: #806020 +} + +span.stringliteral { + color: #002080 +} + +span.charliteral { + color: #008080 +} + +.mdTable { + border: 1px solid #868686; + background-color: #f2f2ff; +} + +.mdRow { + padding: 8px 20px; +} + +.mdescLeft { + font-size: smaller; + font-family: Arial, Helvetica, sans-serif; + background-color: #FAFAFA; + padding-left: 8px; + border-top: 1px none #E0E0E0; + border-right: 1px none #E0E0E0; + border-bottom: 1px none #E0E0E0; + border-left: 1px none #E0E0E0; + margin: 0px; +} + +.mdescRight { + font-size: smaller; + font-family: Arial, Helvetica, sans-serif; + font-style: italic; + background-color: #FAFAFA; + padding-left: 4px; + border-top: 1px none #E0E0E0; + border-right: 1px none #E0E0E0; + border-bottom: 1px none #E0E0E0; + border-left: 1px none #E0E0E0; + margin: 0px; + padding-bottom: 0px; + padding-right: 8px; +} + +.memItemLeft { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-style: solid; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-family: Geneva, Arial, Helvetica, sans-serif; + font-size: 12px; +} + +.memItemRight { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-style: solid; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-family: Geneva, Arial, Helvetica, sans-serif; + font-size: 13px; +} + +.search { + color: #0000ee; + font-weight: bold; +} + +form.search { + margin-bottom: 0px; + margin-top: 0px; +} + +input.search { + font-size: 75%; + color: #000080; + font-weight: normal; + background-color: #eeeeff; +} + +td.tiny { + font-size: 75%; +} diff --git a/Frameworks/TagLib/taglib/doc/taglib.png b/Frameworks/TagLib/taglib/doc/taglib.png new file mode 100644 index 0000000000000000000000000000000000000000..2791cc887670ef22450bb7f35686912c8ebf1af3 GIT binary patch literal 2748 zcmeHJ`!^Ge8(;2WY-L&`>?<+KB}AdomW>SC3yDgZEawX$K{m4<5!+inxP@llC zU1rZ5-~s?5U5@7osNJD|;vWb8e;w%B$_m}xeu<33;sJX^M8(7#3n_E~Stj{7*=WNgz zTf6i2SO-TZ9G>8O!PU*(<0A1A>9Uu%kFTG9Kwwbtm8&74Vd0TcH=-#wV`6{16&FuU zNK8sjrKhFe&iMTfBQq;ICpYhI{=LGY;*!$)56W2O6_r&FA63^puC1$o^0c9`srgw; zYg_wsc1LGd_a8mIFZ%iiUJkx~!{H7Mza4r1;UjNs{NEFwCa0!nX6NQVFDx!Ce_2^w z`}SQR{IULXb8GuoK}4+#03eZ#L0S=R@K%a8TnN62-U#cQ zxshDVk#FbxU`Lz@(~J1&+eu_Ot!Zb!u*!iy;VrIT89^4V-MpyV4f}5Z6)S#xz44_nH)ZwLEX1m@h{jx#s^%-I12Uo{L;lYU>S6TgGhdGB z-86#$!MWVF=DePxCFXUv3MhxFgQId@QM>wAZS7>TD&n;AB}hM;sMl-S_gyQtOMwwaMxEZ(SGTen$NAH(Dr;;wTMLm2rm%dEFDtTox@-@yo_8=#PTFHV7~^?_z$R zxYn}=x)!a^d?VBXN`@a&+4V)@HnEI?SU4HG803Max{73rO=A^u6V+|LA$#@%CB=*IUN&JV>%TnPaWua;K8=X) zpSvd-Td5$wDKjt8&kuf+pJ>TL+qB&R?R2y>PS1{tx#f1xCAyl)A(-a{^sG4}pTUpl ztg3d6gXi+mVt)PemeH1eoYGiIzLX?oAlxv0CO2Tt2$3mtQ6CBPvdnoJ@5=iiQW;6p zc>9sOuYQ&Ylk4RZvdLE0c#sqmBw7x|aAJkrfxun-y0rEm@2t7Il^velx+l*`Qw)L2 z)`j*t2l*aEy9oZ)V-~)2jw||+ifBAL3^{cV3xm5QqFn!1=#fWOGe<@0y>#7aOb9Jd z!s*C3e4j~aj#e^~#w3Az7ur%2)4Wbpj*fhVUN6Wxbu4;*IfD{0% zQoa$@Udr-hi3Y79kklmJ=+Y4wsH+lFP@NgTzB48X%f#J#Nl6_S`s?6_dSO6N;J+5P zu^UPj*dEX5-;B6#=a%-SM}qHPr}dZ-ykfMyaF`YuAyW>Vm7?dh$lnh8;Nq`WWR{&b zBBsfxt^F7=KRqz~+RzDK#!s)vj zsatPb7)ztssHyH0VO`s+bIM6K6o8->F0jXseoN3oW9__3hbWIj{JeF=U4}s5lSVi# zw#t+TqrY&FE*YfDoD}>?lf~0)V&KF4hJpzmTg#Ul{||Qvj_p^UPjnwVWj4Ru^F)Xv zLGjvWoABrk%7>zpsxf$<+mDnCq9BWrcdZYyA%PApFWC2Z%3WSOK?QL$I3(B|F3WH? z4WfZQyeVn5U4GqoK{c)PD!)VUOcm3K|6JJ^PhZK+@oaI_KdM+%MOa|b23AVKQInP^ zYGNnIs!l>mJ>D7==NK<_P6bJ}Z}6~cAe#><8Q*GOw&NI@heJfQF$9%LP_DFP-r zcOWM=?B30%IXuiaEYmisp4m}nFn(~G&yB}u3|IbQ*v^zb9NK<*e+SU)oHl8-*H!25p8|uzA|G3m GsDA+=UdN#T literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/examples/CMakeLists.txt b/Frameworks/TagLib/taglib/examples/CMakeLists.txt new file mode 100644 index 000000000..f991739dc --- /dev/null +++ b/Frameworks/TagLib/taglib/examples/CMakeLists.txt @@ -0,0 +1,40 @@ +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/toolkit + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ape + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v1 + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2 + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2/frames + ${CMAKE_CURRENT_SOURCE_DIR}/../bindings/c/ +) + +if(NOT BUILD_SHARED_LIBS) + add_definitions(-DTAGLIB_STATIC) +endif() + +########### next target ############### + +add_executable(tagreader tagreader.cpp) +target_link_libraries(tagreader tag) + +########### next target ############### + +add_executable(tagreader_c tagreader_c.c) +target_link_libraries(tagreader_c tag_c) + +########### next target ############### + +add_executable(tagwriter tagwriter.cpp) +target_link_libraries(tagwriter tag) + +########### next target ############### + +add_executable(framelist framelist.cpp) +target_link_libraries(framelist tag) + +########### next target ############### + +add_executable(strip-id3v1 strip-id3v1.cpp) +target_link_libraries(strip-id3v1 tag) + diff --git a/Frameworks/TagLib/taglib/examples/framelist.cpp b/Frameworks/TagLib/taglib/examples/framelist.cpp new file mode 100644 index 000000000..44914cc96 --- /dev/null +++ b/Frameworks/TagLib/taglib/examples/framelist.cpp @@ -0,0 +1,117 @@ +/* Copyright (C) 2003 Scott Wheeler + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include + +#include + +using namespace std; +using namespace TagLib; + +int main(int argc, char *argv[]) +{ + // process the command line args + + + for(int i = 1; i < argc; i++) { + + cout << "******************** \"" << argv[i] << "\"********************" << endl; + + MPEG::File f(argv[i]); + + ID3v2::Tag *id3v2tag = f.ID3v2Tag(); + + if(id3v2tag) { + + cout << "ID3v2." + << id3v2tag->header()->majorVersion() + << "." + << id3v2tag->header()->revisionNumber() + << ", " + << id3v2tag->header()->tagSize() + << " bytes in tag" + << endl; + + ID3v2::FrameList::ConstIterator it = id3v2tag->frameList().begin(); + for(; it != id3v2tag->frameList().end(); it++) { + cout << (*it)->frameID(); + + if(ID3v2::CommentsFrame *comment = dynamic_cast(*it)) + if(!comment->description().isEmpty()) + cout << " [" << comment->description() << "]"; + + cout << " - \"" << (*it)->toString() << "\"" << endl; + } + } + else + cout << "file does not have a valid id3v2 tag" << endl; + + cout << endl << "ID3v1" << endl; + + ID3v1::Tag *id3v1tag = f.ID3v1Tag(); + + if(id3v1tag) { + cout << "title - \"" << id3v1tag->title() << "\"" << endl; + cout << "artist - \"" << id3v1tag->artist() << "\"" << endl; + cout << "album - \"" << id3v1tag->album() << "\"" << endl; + cout << "year - \"" << id3v1tag->year() << "\"" << endl; + cout << "comment - \"" << id3v1tag->comment() << "\"" << endl; + cout << "track - \"" << id3v1tag->track() << "\"" << endl; + cout << "genre - \"" << id3v1tag->genre() << "\"" << endl; + } + else + cout << "file does not have a valid id3v1 tag" << endl; + + APE::Tag *ape = f.APETag(); + + cout << endl << "APE" << endl; + + if(ape) { + for(APE::ItemListMap::ConstIterator it = ape->itemListMap().begin(); + it != ape->itemListMap().end(); ++it) + { + if((*it).second.type() != APE::Item::Binary) + cout << (*it).first << " - \"" << (*it).second.toString() << "\"" << endl; + else + cout << (*it).first << " - Binary data (" << (*it).second.binaryData().size() << " bytes)" << endl; + } + } + else + cout << "file does not have a valid APE tag" << endl; + + cout << endl; + } +} diff --git a/Frameworks/TagLib/taglib/examples/strip-id3v1.cpp b/Frameworks/TagLib/taglib/examples/strip-id3v1.cpp new file mode 100644 index 000000000..ab36d7117 --- /dev/null +++ b/Frameworks/TagLib/taglib/examples/strip-id3v1.cpp @@ -0,0 +1,40 @@ +/* Copyright (C) 2003 Scott Wheeler + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +using namespace TagLib; + +int main(int argc, char *argv[]) +{ + for(int i = 1; i < argc; i++) { + + std::cout << "******************** Stripping ID3v1 Tag From: \"" << argv[i] << "\"********************" << std::endl; + + MPEG::File f(argv[i]); + f.strip(MPEG::File::ID3v1); + } +} diff --git a/Frameworks/TagLib/taglib/examples/tagreader.cpp b/Frameworks/TagLib/taglib/examples/tagreader.cpp new file mode 100644 index 000000000..ac81be628 --- /dev/null +++ b/Frameworks/TagLib/taglib/examples/tagreader.cpp @@ -0,0 +1,89 @@ +/* Copyright (C) 2003 Scott Wheeler + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include + +using namespace std; + +int main(int argc, char *argv[]) +{ + for(int i = 1; i < argc; i++) { + + cout << "******************** \"" << argv[i] << "\" ********************" << endl; + + TagLib::FileRef f(argv[i]); + + if(!f.isNull() && f.tag()) { + + TagLib::Tag *tag = f.tag(); + + cout << "-- TAG (basic) --" << endl; + cout << "title - \"" << tag->title() << "\"" << endl; + cout << "artist - \"" << tag->artist() << "\"" << endl; + cout << "album - \"" << tag->album() << "\"" << endl; + cout << "year - \"" << tag->year() << "\"" << endl; + cout << "comment - \"" << tag->comment() << "\"" << endl; + cout << "track - \"" << tag->track() << "\"" << endl; + cout << "genre - \"" << tag->genre() << "\"" << endl; + + TagLib::PropertyMap tags = f.file()->properties(); + + unsigned int longest = 0; + for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) { + if (i->first.size() > longest) { + longest = i->first.size(); + } + } + + cout << "-- TAG (properties) --" << endl; + for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) { + for(TagLib::StringList::ConstIterator j = i->second.begin(); j != i->second.end(); ++j) { + cout << left << std::setw(longest) << i->first << " - " << '"' << *j << '"' << endl; + } + } + + } + + if(!f.isNull() && f.audioProperties()) { + + TagLib::AudioProperties *properties = f.audioProperties(); + + int seconds = properties->length() % 60; + int minutes = (properties->length() - seconds) / 60; + + cout << "-- AUDIO --" << endl; + cout << "bitrate - " << properties->bitrate() << endl; + cout << "sample rate - " << properties->sampleRate() << endl; + cout << "channels - " << properties->channels() << endl; + cout << "length - " << minutes << ":" << setfill('0') << setw(2) << seconds << endl; + } + } + return 0; +} diff --git a/Frameworks/TagLib/taglib/examples/tagreader_c.c b/Frameworks/TagLib/taglib/examples/tagreader_c.c new file mode 100644 index 000000000..043699264 --- /dev/null +++ b/Frameworks/TagLib/taglib/examples/tagreader_c.c @@ -0,0 +1,81 @@ +/* Copyright (C) 2003 Scott Wheeler + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#ifndef FALSE +#define FALSE 0 +#endif + +int main(int argc, char *argv[]) +{ + int i; + int seconds; + int minutes; + TagLib_File *file; + TagLib_Tag *tag; + const TagLib_AudioProperties *properties; + + taglib_set_strings_unicode(FALSE); + + for(i = 1; i < argc; i++) { + printf("******************** \"%s\" ********************\n", argv[i]); + + file = taglib_file_new(argv[i]); + + if(file == NULL) + break; + + tag = taglib_file_tag(file); + properties = taglib_file_audioproperties(file); + + if(tag != NULL) { + printf("-- TAG --\n"); + printf("title - \"%s\"\n", taglib_tag_title(tag)); + printf("artist - \"%s\"\n", taglib_tag_artist(tag)); + printf("album - \"%s\"\n", taglib_tag_album(tag)); + printf("year - \"%i\"\n", taglib_tag_year(tag)); + printf("comment - \"%s\"\n", taglib_tag_comment(tag)); + printf("track - \"%i\"\n", taglib_tag_track(tag)); + printf("genre - \"%s\"\n", taglib_tag_genre(tag)); + } + + if(properties != NULL) { + seconds = taglib_audioproperties_length(properties) % 60; + minutes = (taglib_audioproperties_length(properties) - seconds) / 60; + + printf("-- AUDIO --\n"); + printf("bitrate - %i\n", taglib_audioproperties_bitrate(properties)); + printf("sample rate - %i\n", taglib_audioproperties_samplerate(properties)); + printf("channels - %i\n", taglib_audioproperties_channels(properties)); + printf("length - %i:%02i\n", minutes, seconds); + } + + taglib_tag_free_strings(); + taglib_file_free(file); + } + + return 0; +} diff --git a/Frameworks/TagLib/taglib/examples/tagwriter.cpp b/Frameworks/TagLib/taglib/examples/tagwriter.cpp new file mode 100644 index 000000000..ed8b0d7ab --- /dev/null +++ b/Frameworks/TagLib/taglib/examples/tagwriter.cpp @@ -0,0 +1,185 @@ +/* Copyright (C) 2004 Scott Wheeler + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace std; + +bool isArgument(const char *s) +{ + return strlen(s) == 2 && s[0] == '-'; +} + +bool isFile(const char *s) +{ + struct stat st; +#ifdef _WIN32 + return ::stat(s, &st) == 0 && (st.st_mode & (S_IFREG)); +#else + return ::stat(s, &st) == 0 && (st.st_mode & (S_IFREG | S_IFLNK)); +#endif +} + +void usage() +{ + cout << endl; + cout << "Usage: tagwriter " << endl; + cout << endl; + cout << "Where the valid fields are:" << endl; + cout << " -t " << endl; + cout << " -a <artist>" << endl; + cout << " -A <album>" << endl; + cout << " -c <comment>" << endl; + cout << " -g <genre>" << endl; + cout << " -y <year>" << endl; + cout << " -T <track>" << endl; + cout << " -R <tagname> <tagvalue>" << endl; + cout << " -I <tagname> <tagvalue>" << endl; + cout << " -D <tagname>" << endl; + cout << endl; + + exit(1); +} + +void checkForRejectedProperties(const TagLib::PropertyMap &tags) +{ // stolen from tagreader.cpp + if(tags.size() > 0) { + unsigned int longest = 0; + for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) { + if(i->first.size() > longest) { + longest = i->first.size(); + } + } + cout << "-- rejected TAGs (properties) --" << endl; + for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) { + for(TagLib::StringList::ConstIterator j = i->second.begin(); j != i->second.end(); ++j) { + cout << left << std::setw(longest) << i->first << " - " << '"' << *j << '"' << endl; + } + } + } +} + +int main(int argc, char *argv[]) +{ + TagLib::List<TagLib::FileRef> fileList; + + while(argc > 0 && isFile(argv[argc - 1])) { + + TagLib::FileRef f(argv[argc - 1]); + + if(!f.isNull() && f.tag()) + fileList.append(f); + + argc--; + } + + if(fileList.isEmpty()) + usage(); + + for(int i = 1; i < argc - 1; i += 2) { + + if(isArgument(argv[i]) && i + 1 < argc && !isArgument(argv[i + 1])) { + + char field = argv[i][1]; + TagLib::String value = argv[i + 1]; + + TagLib::List<TagLib::FileRef>::ConstIterator it; + for(it = fileList.begin(); it != fileList.end(); ++it) { + + TagLib::Tag *t = (*it).tag(); + + switch (field) { + case 't': + t->setTitle(value); + break; + case 'a': + t->setArtist(value); + break; + case 'A': + t->setAlbum(value); + break; + case 'c': + t->setComment(value); + break; + case 'g': + t->setGenre(value); + break; + case 'y': + t->setYear(value.toInt()); + break; + case 'T': + t->setTrack(value.toInt()); + break; + case 'R': + case 'I': + if(i + 2 < argc) { + TagLib::PropertyMap map = (*it).file()->properties (); + if(field == 'R') { + map.replace(value, TagLib::String(argv[i + 2])); + } + else { + map.insert(value, TagLib::String(argv[i + 2])); + } + ++i; + checkForRejectedProperties((*it).file()->setProperties(map)); + } + else { + usage(); + } + break; + case 'D': { + TagLib::PropertyMap map = (*it).file()->properties(); + map.erase(value); + checkForRejectedProperties((*it).file()->setProperties(map)); + break; + } + default: + usage(); + break; + } + } + } + else + usage(); + } + + TagLib::List<TagLib::FileRef>::ConstIterator it; + for(it = fileList.begin(); it != fileList.end(); ++it) + (*it).file()->save(); + + return 0; +} diff --git a/Frameworks/TagLib/taglib/taglib-config.cmake b/Frameworks/TagLib/taglib/taglib-config.cmake new file mode 100644 index 000000000..d500fe606 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib-config.cmake @@ -0,0 +1,55 @@ +#!/bin/sh + +usage() +{ + echo "usage: $0 [OPTIONS]" +cat << EOH + +options: + [--libs] + [--cflags] + [--version] + [--prefix] +EOH + exit 1; +} + +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@LIB_INSTALL_DIR@ +includedir=@INCLUDE_INSTALL_DIR@ + +flags="" + +if test $# -eq 0 ; then + usage +fi + +while test $# -gt 0 +do + case $1 in + --libs) + flags="$flags -L$libdir -ltag @ZLIB_LIBRARIES_FLAGS@" + ;; + --cflags) + flags="$flags -I$includedir -I$includedir/taglib" + ;; + --version) + echo @TAGLIB_LIB_VERSION_STRING@ + ;; + --prefix) + echo $prefix + ;; + *) + echo "$0: unknown option $1" + echo + usage + ;; + esac + shift +done + +if test -n "$flags" +then + echo $flags +fi diff --git a/Frameworks/TagLib/taglib/taglib-config.cmd.cmake b/Frameworks/TagLib/taglib/taglib-config.cmd.cmake new file mode 100644 index 000000000..1b807ec85 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib-config.cmd.cmake @@ -0,0 +1,36 @@ +@echo off +goto beginning + * + * It is what it is, you can do with it as you please. + * + * Just don't blame me if it teaches your computer to smoke! + * + * -Enjoy + * fh :)_~ + * +:beginning +if /i "%1#" == "--libs#" goto doit +if /i "%1#" == "--cflags#" goto doit +if /i "%1#" == "--version#" goto doit +if /i "%1#" == "--prefix#" goto doit + +echo "usage: %0 [OPTIONS]" +echo [--libs] +echo [--cflags] +echo [--version] +echo [--prefix] +goto theend + + * + * NOTE: Windows does not assume libraries are prefixed with 'lib'. + * NOTE: If '-llibtag' is the last element, it is easily appended in the users installation/makefile process + * to allow for static, shared or debug builds. + * It would be preferable if the top level CMakeLists.txt provided the library name during config. ?? +:doit +if /i "%1#" == "--libs#" echo -L${LIB_INSTALL_DIR} -llibtag +if /i "%1#" == "--cflags#" echo -I${INCLUDE_INSTALL_DIR} -I${INCLUDE_INSTALL_DIR}/taglib +if /i "%1#" == "--version#" echo ${TAGLIB_LIB_VERSION_STRING} +if /i "%1#" == "--prefix#" echo ${CMAKE_INSTALL_PREFIX} + +:theend + diff --git a/Frameworks/TagLib/taglib/taglib.pc.cmake b/Frameworks/TagLib/taglib/taglib.pc.cmake new file mode 100644 index 000000000..71ee09af1 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib.pc.cmake @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@LIB_INSTALL_DIR@ +includedir=@INCLUDE_INSTALL_DIR@ + +Name: TagLib +Description: Audio meta-data library +Requires: +Version: @TAGLIB_LIB_VERSION_STRING@ +Libs: -L${libdir} -ltag @ZLIB_LIBRARIES_FLAGS@ +Cflags: -I${includedir} -I${includedir}/taglib diff --git a/Frameworks/TagLib/taglib/taglib/CMakeLists.txt b/Frameworks/TagLib/taglib/taglib/CMakeLists.txt new file mode 100644 index 000000000..563583efa --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/CMakeLists.txt @@ -0,0 +1,372 @@ +set(CMAKE_INCLUDE_CURRENT_DIR ON) +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/toolkit + ${CMAKE_CURRENT_SOURCE_DIR}/asf + ${CMAKE_CURRENT_SOURCE_DIR}/mpeg + ${CMAKE_CURRENT_SOURCE_DIR}/ogg + ${CMAKE_CURRENT_SOURCE_DIR}/ogg/flac + ${CMAKE_CURRENT_SOURCE_DIR}/flac + ${CMAKE_CURRENT_SOURCE_DIR}/mpc + ${CMAKE_CURRENT_SOURCE_DIR}/mp4 + ${CMAKE_CURRENT_SOURCE_DIR}/ogg/vorbis + ${CMAKE_CURRENT_SOURCE_DIR}/ogg/speex + ${CMAKE_CURRENT_SOURCE_DIR}/ogg/opus + ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2 + ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2/frames + ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v1 + ${CMAKE_CURRENT_SOURCE_DIR}/ape + ${CMAKE_CURRENT_SOURCE_DIR}/wavpack + ${CMAKE_CURRENT_SOURCE_DIR}/trueaudio + ${CMAKE_CURRENT_SOURCE_DIR}/riff + ${CMAKE_CURRENT_SOURCE_DIR}/riff/aiff + ${CMAKE_CURRENT_SOURCE_DIR}/riff/wav + ${CMAKE_CURRENT_SOURCE_DIR}/mod + ${CMAKE_CURRENT_SOURCE_DIR}/s3m + ${CMAKE_CURRENT_SOURCE_DIR}/it + ${CMAKE_CURRENT_SOURCE_DIR}/xm + ${taglib_SOURCE_DIR}/3rdparty +) + +if(ZLIB_FOUND) + include_directories(${ZLIB_INCLUDE_DIR}) +elseif(HAVE_ZLIB_SOURCE) + include_directories(${ZLIB_SOURCE}) +endif() + +set(tag_HDRS + tag.h + fileref.h + audioproperties.h + taglib_export.h + ${CMAKE_CURRENT_BINARY_DIR}/../taglib_config.h + toolkit/taglib.h + toolkit/tstring.h + toolkit/tlist.h + toolkit/tlist.tcc + toolkit/tstringlist.h + toolkit/tbytevector.h + toolkit/tbytevectorlist.h + toolkit/tbytevectorstream.h + toolkit/tiostream.h + toolkit/tfile.h + toolkit/tfilestream.h + toolkit/tmap.h + toolkit/tmap.tcc + toolkit/tpropertymap.h + toolkit/trefcounter.h + toolkit/tdebuglistener.h + mpeg/mpegfile.h + mpeg/mpegproperties.h + mpeg/mpegheader.h + mpeg/xingheader.h + mpeg/id3v1/id3v1tag.h + mpeg/id3v1/id3v1genres.h + mpeg/id3v2/id3v2.h + mpeg/id3v2/id3v2extendedheader.h + mpeg/id3v2/id3v2frame.h + mpeg/id3v2/id3v2header.h + mpeg/id3v2/id3v2synchdata.h + mpeg/id3v2/id3v2footer.h + mpeg/id3v2/id3v2framefactory.h + mpeg/id3v2/id3v2tag.h + mpeg/id3v2/frames/attachedpictureframe.h + mpeg/id3v2/frames/commentsframe.h + mpeg/id3v2/frames/eventtimingcodesframe.h + mpeg/id3v2/frames/generalencapsulatedobjectframe.h + mpeg/id3v2/frames/ownershipframe.h + mpeg/id3v2/frames/popularimeterframe.h + mpeg/id3v2/frames/privateframe.h + mpeg/id3v2/frames/relativevolumeframe.h + mpeg/id3v2/frames/synchronizedlyricsframe.h + mpeg/id3v2/frames/textidentificationframe.h + mpeg/id3v2/frames/uniquefileidentifierframe.h + mpeg/id3v2/frames/unknownframe.h + mpeg/id3v2/frames/unsynchronizedlyricsframe.h + mpeg/id3v2/frames/urllinkframe.h + mpeg/id3v2/frames/chapterframe.h + mpeg/id3v2/frames/tableofcontentsframe.h + mpeg/id3v2/frames/podcastframe.h + ogg/oggfile.h + ogg/oggpage.h + ogg/oggpageheader.h + ogg/xiphcomment.h + ogg/vorbis/vorbisfile.h + ogg/vorbis/vorbisproperties.h + ogg/flac/oggflacfile.h + ogg/speex/speexfile.h + ogg/speex/speexproperties.h + ogg/opus/opusfile.h + ogg/opus/opusproperties.h + flac/flacfile.h + flac/flacpicture.h + flac/flacproperties.h + flac/flacmetadatablock.h + ape/apefile.h + ape/apeproperties.h + ape/apetag.h + ape/apefooter.h + ape/apeitem.h + mpc/mpcfile.h + mpc/mpcproperties.h + wavpack/wavpackfile.h + wavpack/wavpackproperties.h + trueaudio/trueaudiofile.h + trueaudio/trueaudioproperties.h + riff/rifffile.h + riff/aiff/aifffile.h + riff/aiff/aiffproperties.h + riff/wav/wavfile.h + riff/wav/wavproperties.h + riff/wav/infotag.h + asf/asffile.h + asf/asfproperties.h + asf/asftag.h + asf/asfattribute.h + asf/asfpicture.h + mp4/mp4file.h + mp4/mp4atom.h + mp4/mp4tag.h + mp4/mp4item.h + mp4/mp4properties.h + mp4/mp4coverart.h + mod/modfilebase.h + mod/modfile.h + mod/modtag.h + mod/modproperties.h + it/itfile.h + it/itproperties.h + s3m/s3mfile.h + s3m/s3mproperties.h + xm/xmfile.h + xm/xmproperties.h +) + +set(mpeg_SRCS + mpeg/mpegfile.cpp + mpeg/mpegproperties.cpp + mpeg/mpegheader.cpp + mpeg/xingheader.cpp +) + +set(id3v1_SRCS + mpeg/id3v1/id3v1tag.cpp + mpeg/id3v1/id3v1genres.cpp +) + +set(id3v2_SRCS + mpeg/id3v2/id3v2framefactory.cpp + mpeg/id3v2/id3v2synchdata.cpp + mpeg/id3v2/id3v2tag.cpp + mpeg/id3v2/id3v2header.cpp + mpeg/id3v2/id3v2frame.cpp + mpeg/id3v2/id3v2footer.cpp + mpeg/id3v2/id3v2extendedheader.cpp + ) + +set(frames_SRCS + mpeg/id3v2/frames/attachedpictureframe.cpp + mpeg/id3v2/frames/commentsframe.cpp + mpeg/id3v2/frames/eventtimingcodesframe.cpp + mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp + mpeg/id3v2/frames/ownershipframe.cpp + mpeg/id3v2/frames/popularimeterframe.cpp + mpeg/id3v2/frames/privateframe.cpp + mpeg/id3v2/frames/relativevolumeframe.cpp + mpeg/id3v2/frames/synchronizedlyricsframe.cpp + mpeg/id3v2/frames/textidentificationframe.cpp + mpeg/id3v2/frames/uniquefileidentifierframe.cpp + mpeg/id3v2/frames/unknownframe.cpp + mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp + mpeg/id3v2/frames/urllinkframe.cpp + mpeg/id3v2/frames/chapterframe.cpp + mpeg/id3v2/frames/tableofcontentsframe.cpp + mpeg/id3v2/frames/podcastframe.cpp +) + +set(ogg_SRCS + ogg/oggfile.cpp + ogg/oggpage.cpp + ogg/oggpageheader.cpp + ogg/xiphcomment.cpp +) + +set(vorbis_SRCS + ogg/vorbis/vorbisfile.cpp + ogg/vorbis/vorbisproperties.cpp +) + +set(flacs_SRCS + flac/flacfile.cpp + flac/flacpicture.cpp + flac/flacproperties.cpp + flac/flacmetadatablock.cpp + flac/flacunknownmetadatablock.cpp +) + +set(oggflacs_SRCS + ogg/flac/oggflacfile.cpp +) + +set(mpc_SRCS + mpc/mpcfile.cpp + mpc/mpcproperties.cpp +) + +set(mp4_SRCS + mp4/mp4file.cpp + mp4/mp4atom.cpp + mp4/mp4tag.cpp + mp4/mp4item.cpp + mp4/mp4properties.cpp + mp4/mp4coverart.cpp +) + +set(ape_SRCS + ape/apetag.cpp + ape/apefooter.cpp + ape/apeitem.cpp + ape/apefile.cpp + ape/apeproperties.cpp +) + +set(wavpack_SRCS + wavpack/wavpackfile.cpp + wavpack/wavpackproperties.cpp +) + +set(speex_SRCS + ogg/speex/speexfile.cpp + ogg/speex/speexproperties.cpp +) + +set(opus_SRCS + ogg/opus/opusfile.cpp + ogg/opus/opusproperties.cpp +) + +set(trueaudio_SRCS + trueaudio/trueaudiofile.cpp + trueaudio/trueaudioproperties.cpp +) + +set(asf_SRCS + asf/asftag.cpp + asf/asffile.cpp + asf/asfproperties.cpp + asf/asfattribute.cpp + asf/asfpicture.cpp +) + +set(riff_SRCS + riff/rifffile.cpp +) + +set(aiff_SRCS + riff/aiff/aifffile.cpp + riff/aiff/aiffproperties.cpp +) + +set(wav_SRCS + riff/wav/wavfile.cpp + riff/wav/wavproperties.cpp + riff/wav/infotag.cpp +) + +set(mod_SRCS + mod/modfilebase.cpp + mod/modfile.cpp + mod/modtag.cpp + mod/modproperties.cpp +) + +set(s3m_SRCS + s3m/s3mfile.cpp + s3m/s3mproperties.cpp +) + +set(it_SRCS + it/itfile.cpp + it/itproperties.cpp +) + +set(xm_SRCS + xm/xmfile.cpp + xm/xmproperties.cpp +) + +set(toolkit_SRCS + toolkit/tstring.cpp + toolkit/tstringlist.cpp + toolkit/tbytevector.cpp + toolkit/tbytevectorlist.cpp + toolkit/tbytevectorstream.cpp + toolkit/tiostream.cpp + toolkit/tfile.cpp + toolkit/tfilestream.cpp + toolkit/tdebug.cpp + toolkit/tpropertymap.cpp + toolkit/trefcounter.cpp + toolkit/tdebuglistener.cpp + toolkit/tzlib.cpp +) + +if(HAVE_ZLIB_SOURCE) + set(zlib_SRCS + ${ZLIB_SOURCE}/adler32.c + ${ZLIB_SOURCE}/crc32.c + ${ZLIB_SOURCE}/inffast.c + ${ZLIB_SOURCE}/inflate.c + ${ZLIB_SOURCE}/inftrees.c + ${ZLIB_SOURCE}/zutil.c + ) +endif() + +set(tag_LIB_SRCS + ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} + ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS} + ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS} + ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} + ${zlib_SRCS} + tag.cpp + tagunion.cpp + fileref.cpp + audioproperties.cpp + tagutils.cpp +) + +add_library(tag ${tag_LIB_SRCS} ${tag_HDRS}) +set_property(TARGET tag PROPERTY CXX_STANDARD 98) + +if(HAVE_ZLIB AND NOT HAVE_ZLIB_SOURCE) + target_link_libraries(tag ${ZLIB_LIBRARIES}) +endif() + +set_target_properties(tag PROPERTIES + VERSION ${TAGLIB_SOVERSION_MAJOR}.${TAGLIB_SOVERSION_MINOR}.${TAGLIB_SOVERSION_PATCH} + SOVERSION ${TAGLIB_SOVERSION_MAJOR} + INSTALL_NAME_DIR ${LIB_INSTALL_DIR} + DEFINE_SYMBOL MAKE_TAGLIB_LIB + LINK_INTERFACE_LIBRARIES "" + PUBLIC_HEADER "${tag_HDRS}" +) +if(VISIBILITY_HIDDEN) + set_target_properties(tag PROPERTIES C_VISIBILITY_PRESET hidden) +endif() + +if(BUILD_FRAMEWORK) + unset(INSTALL_NAME_DIR) + set_target_properties(tag PROPERTIES + FRAMEWORK TRUE + MACOSX_RPATH 1 + VERSION "A" + SOVERSION "A" + ) +endif() + +install(TARGETS tag + FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + RUNTIME DESTINATION ${BIN_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} + PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR}/taglib +) diff --git a/Frameworks/TagLib/taglib/taglib/ape/ape-tag-format.txt b/Frameworks/TagLib/taglib/taglib/ape/ape-tag-format.txt index 21ff1c861..cb6f46ff4 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/ape-tag-format.txt +++ b/Frameworks/TagLib/taglib/taglib/ape/ape-tag-format.txt @@ -49,8 +49,8 @@ APE Tag Version 2.000 (with header, recommended): APE tag items should be sorted ascending by size. When streaming, parts of the APE tag may be dropped to reduce the danger of drop outs between tracks. This -is not required, but is strongly recommended. It would be desirable for the i -tems to be sorted by importance / size, but this is not feasible. This +is not required, but is strongly recommended. It would be desirable for the +items to be sorted by importance / size, but this is not feasible. This convention should only be broken when adding less important small items and it is not desirable to rewrite the entire tag. An APE tag at the end of a file (the recommended location) must have at least a footer; an APE tag at the diff --git a/Frameworks/TagLib/taglib/taglib/ape/apefile.cpp b/Frameworks/TagLib/taglib/taglib/ape/apefile.cpp index 5d914756e..a10c1f646 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apefile.cpp +++ b/Frameworks/TagLib/taglib/taglib/ape/apefile.cpp @@ -36,9 +36,11 @@ #include <tdebug.h> #include <tagunion.h> #include <id3v1tag.h> +#include <id3v2header.h> +#include <tpropertymap.h> +#include <tagutils.h> #include "apefile.h" - #include "apetag.h" #include "apefooter.h" @@ -46,7 +48,7 @@ using namespace TagLib; namespace { - enum { APEIndex, ID3v1Index }; + enum { ApeAPEIndex = 0, ApeID3v1Index = 1 }; } class APE::File::FilePrivate @@ -56,40 +58,61 @@ public: APELocation(-1), APESize(0), ID3v1Location(-1), - properties(0), - hasAPE(false), - hasID3v1(false) {} + ID3v2Header(0), + ID3v2Location(-1), + ID3v2Size(0), + properties(0) {} ~FilePrivate() { + delete ID3v2Header; delete properties; } long APELocation; - uint APESize; + long APESize; long ID3v1Location; + ID3v2::Header *ID3v2Header; + long ID3v2Location; + long ID3v2Size; + TagUnion tag; Properties *properties; - - // These indicate whether the file *on disk* has these tags, not if - // this data structure does. This is used in computing offsets. - - bool hasAPE; - bool hasID3v1; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool APE::File::isSupported(IOStream *stream) +{ + // An APE file has an ID "MAC " somewhere. An ID3v2 tag may precede. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true); + return (buffer.find("MAC ") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -APE::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(file) +APE::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties); +} + +APE::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties); } APE::File::~File() @@ -102,6 +125,24 @@ TagLib::Tag *APE::File::tag() const return &d->tag; } +PropertyMap APE::File::properties() const +{ + return d->tag.properties(); +} + +void APE::File::removeUnsupportedProperties(const StringList &properties) +{ + d->tag.removeUnsupportedProperties(properties); +} + +PropertyMap APE::File::setProperties(const PropertyMap &properties) +{ + if(ID3v1Tag()) + ID3v1Tag()->setProperties(properties); + + return APETag(true)->setProperties(properties); +} + APE::Properties *APE::File::audioProperties() const { return d->properties; @@ -116,155 +157,158 @@ bool APE::File::save() // Update ID3v1 tag - if(ID3v1Tag()) { - if(d->hasID3v1) { + if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) { + + // ID3v1 tag is not empty. Update the old one or create a new one. + + if(d->ID3v1Location >= 0) { seek(d->ID3v1Location); - writeBlock(ID3v1Tag()->render()); } else { seek(0, End); d->ID3v1Location = tell(); - writeBlock(ID3v1Tag()->render()); - d->hasID3v1 = true; } + + writeBlock(ID3v1Tag()->render()); } else { - if(d->hasID3v1) { - removeBlock(d->ID3v1Location, 128); - d->hasID3v1 = false; - if(d->hasAPE) { - if(d->APELocation > d->ID3v1Location) - d->APELocation -= 128; - } + + // ID3v1 tag is empty. Remove the old one. + + if(d->ID3v1Location >= 0) { + truncate(d->ID3v1Location); + d->ID3v1Location = -1; } } // Update APE tag - if(APETag()) { - if(d->hasAPE) - insert(APETag()->render(), d->APELocation, d->APESize); - else { - if(d->hasID3v1) { - insert(APETag()->render(), d->ID3v1Location, 0); - d->APESize = APETag()->footer()->completeTagSize(); - d->hasAPE = true; + if(APETag() && !APETag()->isEmpty()) { + + // APE tag is not empty. Update the old one or create a new one. + + if(d->APELocation < 0) { + if(d->ID3v1Location >= 0) d->APELocation = d->ID3v1Location; - d->ID3v1Location += d->APESize; - } - else { - seek(0, End); - d->APELocation = tell(); - writeBlock(APETag()->render()); - d->APESize = APETag()->footer()->completeTagSize(); - d->hasAPE = true; - } + else + d->APELocation = length(); } + + const ByteVector data = APETag()->render(); + insert(data, d->APELocation, d->APESize); + + if(d->ID3v1Location >= 0) + d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize); + + d->APESize = data.size(); } else { - if(d->hasAPE) { + + // APE tag is empty. Remove the old one. + + if(d->APELocation >= 0) { removeBlock(d->APELocation, d->APESize); - d->hasAPE = false; - if(d->hasID3v1) { - if(d->ID3v1Location > d->APELocation) { - d->ID3v1Location -= d->APESize; - } - } + + if(d->ID3v1Location >= 0) + d->ID3v1Location -= d->APESize; + + d->APELocation = -1; + d->APESize = 0; } } - return true; + return true; } ID3v1::Tag *APE::File::ID3v1Tag(bool create) { - return d->tag.access<ID3v1::Tag>(ID3v1Index, create); + return d->tag.access<ID3v1::Tag>(ApeID3v1Index, create); } APE::Tag *APE::File::APETag(bool create) { - return d->tag.access<APE::Tag>(APEIndex, create); + return d->tag.access<APE::Tag>(ApeAPEIndex, create); } void APE::File::strip(int tags) { - if(tags & ID3v1) { - d->tag.set(ID3v1Index, 0); + if(tags & ID3v1) + d->tag.set(ApeID3v1Index, 0); + + if(tags & APE) + d->tag.set(ApeAPEIndex, 0); + + if(!ID3v1Tag()) APETag(true); - } +} - if(tags & APE) { - d->tag.set(APEIndex, 0); +bool APE::File::hasAPETag() const +{ + return (d->APELocation >= 0); +} - if(!ID3v1Tag()) - APETag(true); - } +bool APE::File::hasID3v1Tag() const +{ + return (d->ID3v1Location >= 0); } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void APE::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) +void APE::File::read(bool readProperties) { + // Look for an ID3v2 tag + + d->ID3v2Location = Utils::findID3v2(this); + + if(d->ID3v2Location >= 0) { + seek(d->ID3v2Location); + d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size())); + d->ID3v2Size = d->ID3v2Header->completeTagSize(); + } + // Look for an ID3v1 tag - d->ID3v1Location = findID3v1(); + d->ID3v1Location = Utils::findID3v1(this); - if(d->ID3v1Location >= 0) { - d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); - d->hasID3v1 = true; - } + if(d->ID3v1Location >= 0) + d->tag.set(ApeID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); // Look for an APE tag - d->APELocation = findAPE(); + d->APELocation = Utils::findAPE(this, d->ID3v1Location); if(d->APELocation >= 0) { - d->tag.set(APEIndex, new APE::Tag(this, d->APELocation)); + d->tag.set(ApeAPEIndex, new APE::Tag(this, d->APELocation)); d->APESize = APETag()->footer()->completeTagSize(); - d->APELocation = d->APELocation + APETag()->footer()->size() - d->APESize; - d->hasAPE = true; + d->APELocation = d->APELocation + APE::Footer::size() - d->APESize; } - if(!d->hasID3v1) + if(d->ID3v1Location < 0) APETag(true); // Look for APE audio properties if(readProperties) { - d->properties = new Properties(this); + + long streamLength; + + if(d->APELocation >= 0) + streamLength = d->APELocation; + else if(d->ID3v1Location >= 0) + streamLength = d->ID3v1Location; + else + streamLength = length(); + + if(d->ID3v2Location >= 0) { + seek(d->ID3v2Location + d->ID3v2Size); + streamLength -= (d->ID3v2Location + d->ID3v2Size); + } + else { + seek(0); + } + + d->properties = new Properties(this, streamLength); } } - -long APE::File::findAPE() -{ - if(!isValid()) - return -1; - - if(d->hasID3v1) - seek(-160, End); - else - seek(-32, End); - - long p = tell(); - - if(readBlock(8) == APE::Tag::fileIdentifier()) - return p; - - return -1; -} - -long APE::File::findID3v1() -{ - if(!isValid()) - return -1; - - seek(-128, End); - long p = tell(); - - if(readBlock(3) == ID3v1::Tag::fileIdentifier()) - return p; - - return -1; -} diff --git a/Frameworks/TagLib/taglib/taglib/ape/apefile.h b/Frameworks/TagLib/taglib/taglib/ape/apefile.h index a4bc80d90..267778bae 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apefile.h +++ b/Frameworks/TagLib/taglib/taglib/ape/apefile.h @@ -59,7 +59,7 @@ namespace TagLib { //! An implementation of TagLib::File with APE specific methods /*! - * This implements and provides an interface APE WavPack files to the + * This implements and provides an interface for APE files to the * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing * the abstract TagLib::File API as well as providing some additional * information specific to APE files. @@ -84,13 +84,26 @@ namespace TagLib { }; /*! - * Contructs an WavPack file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs an APE file from \a file. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs an APE file from \a stream. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ @@ -102,6 +115,26 @@ namespace TagLib { */ virtual TagLib::Tag *tag() const; + /*! + * Implements the unified property interface -- export function. + * If the file contains both an APE and an ID3v1 tag, only APE + * will be converted to the PropertyMap. + */ + PropertyMap properties() const; + + /*! + * Removes unsupported properties. Forwards to the actual Tag's + * removeUnsupportedProperties() function. + */ + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + * Creates an APEv2 tag if necessary. A potentially existing ID3v1 + * tag will be updated as well. + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the APE::Properties for this file. If no audio properties * were read then this will return a null pointer. @@ -119,27 +152,38 @@ namespace TagLib { /*! * Returns a pointer to the ID3v1 tag of the file. * - * If \a create is false (the default) this will return a null pointer + * If \a create is false (the default) this may return a null pointer * if there is no valid ID3v1 tag. If \a create is true it will create - * an ID3v1 tag if one does not exist. If there is already an APE tag, the - * new ID3v1 tag will be placed after it. + * an ID3v1 tag if one does not exist and returns a valid pointer. * - * \note The Tag <b>is still</b> owned by the APE::File and should not be + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * on disk actually has an ID3v1 tag. + * + * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasID3v1Tag() */ ID3v1::Tag *ID3v1Tag(bool create = false); /*! * Returns a pointer to the APE tag of the file. * - * If \a create is false (the default) this will return a null pointer + * If \a create is false (the default) this may return a null pointer * if there is no valid APE tag. If \a create is true it will create - * a APE tag if one does not exist. + * an APE tag if one does not exist and returns a valid pointer. * - * \note The Tag <b>is still</b> owned by the APE::File and should not be + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file + * on disk actually has an APE tag. + * + * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasAPETag() */ APE::Tag *APETag(bool create = false); @@ -153,14 +197,34 @@ namespace TagLib { */ void strip(int tags = AllTags); + /*! + * Returns whether or not the file on disk actually has an APE tag. + * + * \see APETag() + */ + bool hasAPETag() const; + + /*! + * Returns whether or not the file on disk actually has an ID3v1 tag. + * + * \see ID3v1Tag() + */ + bool hasID3v1Tag() const; + + /*! + * Returns whether or not the given \a stream can be opened as an APE + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); - void scan(); - long findID3v1(); - long findAPE(); + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/ape/apefooter.cpp b/Frameworks/TagLib/taglib/taglib/ape/apefooter.cpp index 0805e9e00..c5c99a5a4 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apefooter.cpp +++ b/Frameworks/TagLib/taglib/taglib/ape/apefooter.cpp @@ -24,7 +24,7 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include <ostream> +#include <iostream> #include <bitset> #include <tstring.h> @@ -35,132 +35,130 @@ using namespace TagLib; using namespace APE; -class Footer::FooterPrivate +class APE::Footer::FooterPrivate { public: - FooterPrivate() : version(0), - footerPresent(true), - headerPresent(false), - isHeader(false), - itemCount(0), - tagSize(0) {} + FooterPrivate() : + version(0), + footerPresent(true), + headerPresent(false), + isHeader(false), + itemCount(0), + tagSize(0) {} - ~FooterPrivate() {} - - uint version; + unsigned int version; bool footerPresent; bool headerPresent; bool isHeader; - uint itemCount; - uint tagSize; - - static const uint size = 32; + unsigned int itemCount; + unsigned int tagSize; }; //////////////////////////////////////////////////////////////////////////////// // static members //////////////////////////////////////////////////////////////////////////////// -TagLib::uint Footer::size() +unsigned int APE::Footer::size() { - return FooterPrivate::size; + return 32; } -ByteVector Footer::fileIdentifier() +ByteVector APE::Footer::fileIdentifier() { - return ByteVector::fromCString("APETAGEX"); + return ByteVector("APETAGEX"); } //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -Footer::Footer() +APE::Footer::Footer() : + d(new FooterPrivate()) { - d = new FooterPrivate; } -Footer::Footer(const ByteVector &data) +APE::Footer::Footer(const ByteVector &data) : + d(new FooterPrivate()) { - d = new FooterPrivate; parse(data); } -Footer::~Footer() +APE::Footer::~Footer() { delete d; } -TagLib::uint Footer::version() const +unsigned int APE::Footer::version() const { return d->version; } -bool Footer::headerPresent() const +bool APE::Footer::headerPresent() const { return d->headerPresent; } -bool Footer::footerPresent() const +bool APE::Footer::footerPresent() const { return d->footerPresent; } -bool Footer::isHeader() const +bool APE::Footer::isHeader() const { return d->isHeader; } -void Footer::setHeaderPresent(bool b) const +void APE::Footer::setHeaderPresent(bool b) const { d->headerPresent = b; } -TagLib::uint Footer::itemCount() const +unsigned int APE::Footer::itemCount() const { return d->itemCount; } -void Footer::setItemCount(uint s) +void APE::Footer::setItemCount(unsigned int s) { d->itemCount = s; } -TagLib::uint Footer::tagSize() const +unsigned int APE::Footer::tagSize() const { return d->tagSize; } -TagLib::uint Footer::completeTagSize() const +unsigned int APE::Footer::completeTagSize() const { if(d->headerPresent) - return d->tagSize + d->size; + return d->tagSize + size(); else return d->tagSize; } -void Footer::setTagSize(uint s) +void APE::Footer::setTagSize(unsigned int s) { d->tagSize = s; } -void Footer::setData(const ByteVector &data) +void APE::Footer::setData(const ByteVector &data) { parse(data); } -ByteVector Footer::renderFooter() const +ByteVector APE::Footer::renderFooter() const { - return render(false); + return render(false); } -ByteVector Footer::renderHeader() const +ByteVector APE::Footer::renderHeader() const { - if (!d->headerPresent) return ByteVector(); - + if(!d->headerPresent) + return ByteVector(); + else return render(true); } @@ -168,7 +166,7 @@ ByteVector Footer::renderHeader() const // protected members //////////////////////////////////////////////////////////////////////////////// -void Footer::parse(const ByteVector &data) +void APE::Footer::parse(const ByteVector &data) { if(data.size() < size()) return; @@ -177,19 +175,19 @@ void Footer::parse(const ByteVector &data) // Read the version number - d->version = data.mid(8, 4).toUInt(false); + d->version = data.toUInt(8, false); // Read the tag size - d->tagSize = data.mid(12, 4).toUInt(false); + d->tagSize = data.toUInt(12, false); // Read the item count - d->itemCount = data.mid(16, 4).toUInt(false); + d->itemCount = data.toUInt(16, false); // Read the flags - std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.mid(20, 4).toUInt(false))); + std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.toUInt(20, false))); d->headerPresent = flags[31]; d->footerPresent = !flags[30]; @@ -197,7 +195,7 @@ void Footer::parse(const ByteVector &data) } -ByteVector Footer::render(bool isHeader) const +ByteVector APE::Footer::render(bool isHeader) const { ByteVector v; diff --git a/Frameworks/TagLib/taglib/taglib/ape/apefooter.h b/Frameworks/TagLib/taglib/taglib/ape/apefooter.h index 080f93006..47741d0dc 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apefooter.h +++ b/Frameworks/TagLib/taglib/taglib/ape/apefooter.h @@ -37,7 +37,7 @@ namespace TagLib { /*! * This class implements APE footers (and headers). It attempts to follow, both - * semantically and programatically, the structure specified in + * semantically and programmatically, the structure specified in * the APE v2.0 standard. The API is based on the properties of APE footer and * headers specified there. */ @@ -64,7 +64,7 @@ namespace TagLib { /*! * Returns the version number. (Note: This is the 1000 or 2000.) */ - uint version() const; + unsigned int version() const; /*! * Returns true if a header is present in the tag. @@ -89,13 +89,13 @@ namespace TagLib { /*! * Returns the number of items in the tag. */ - uint itemCount() const; + unsigned int itemCount() const; /*! * Set the item count to \a s. * \see itemCount() */ - void setItemCount(uint s); + void setItemCount(unsigned int s); /*! * Returns the tag size in bytes. This is the size of the frame content and footer. @@ -103,7 +103,7 @@ namespace TagLib { * * \see completeTagSize() */ - uint tagSize() const; + unsigned int tagSize() const; /*! * Returns the tag size, including if present, the header @@ -111,18 +111,18 @@ namespace TagLib { * * \see tagSize() */ - uint completeTagSize() const; + unsigned int completeTagSize() const; /*! * Set the tag size to \a s. * \see tagSize() */ - void setTagSize(uint s); + void setTagSize(unsigned int s); /*! * Returns the size of the footer. Presently this is always 32 bytes. */ - static uint size(); + static unsigned int size(); /*! * Returns the string used to identify an APE tag inside of a file. diff --git a/Frameworks/TagLib/taglib/taglib/ape/apeitem.cpp b/Frameworks/TagLib/taglib/taglib/ape/apeitem.cpp index 350eac0b5..45f22da47 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apeitem.cpp +++ b/Frameworks/TagLib/taglib/taglib/ape/apeitem.cpp @@ -34,7 +34,9 @@ using namespace APE; class APE::Item::ItemPrivate { public: - ItemPrivate() : type(Text), readOnly(false) {} + ItemPrivate() : + type(Text), + readOnly(false) {} Item::ItemTypes type; String key; @@ -43,28 +45,45 @@ public: bool readOnly; }; -APE::Item::Item() +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +APE::Item::Item() : + d(new ItemPrivate()) { - d = new ItemPrivate; } -APE::Item::Item(const String &key, const String &value) +APE::Item::Item(const String &key, const String &value) : + d(new ItemPrivate()) { - d = new ItemPrivate; d->key = key; d->text.append(value); } -APE::Item::Item(const String &key, const StringList &values) +APE::Item::Item(const String &key, const StringList &values) : + d(new ItemPrivate()) { - d = new ItemPrivate; d->key = key; d->text = values; } -APE::Item::Item(const Item &item) +APE::Item::Item(const String &key, const ByteVector &value, bool binary) : + d(new ItemPrivate()) +{ + d->key = key; + if(binary) { + d->type = Binary; + d->value = value; + } + else { + d->text.append(value); + } +} + +APE::Item::Item(const Item &item) : + d(new ItemPrivate(*item.d)) { - d = new ItemPrivate(*item.d); } APE::Item::~Item() @@ -74,11 +93,17 @@ APE::Item::~Item() Item &APE::Item::operator=(const Item &item) { - delete d; - d = new ItemPrivate(*item.d); + Item(item).swap(*this); return *this; } +void APE::Item::swap(Item &item) +{ + using std::swap; + + swap(d, item.d); +} + void APE::Item::setReadOnly(bool readOnly) { d->readOnly = readOnly; @@ -104,6 +129,18 @@ String APE::Item::key() const return d->key; } +ByteVector APE::Item::binaryData() const +{ + return d->value; +} + +void APE::Item::setBinaryData(const ByteVector &value) +{ + d->type = Binary; + d->value = value; + d->text.clear(); +} + ByteVector APE::Item::value() const { // This seems incorrect as it won't be actually rendering the value to keep it @@ -114,32 +151,58 @@ ByteVector APE::Item::value() const void APE::Item::setKey(const String &key) { - d->key = key; + d->key = key; } void APE::Item::setValue(const String &value) { - d->text = value; + d->type = Text; + d->text = value; + d->value.clear(); } void APE::Item::setValues(const StringList &value) { - d->text = value; + d->type = Text; + d->text = value; + d->value.clear(); } void APE::Item::appendValue(const String &value) { - d->text.append(value); + d->type = Text; + d->text.append(value); + d->value.clear(); } void APE::Item::appendValues(const StringList &values) { - d->text.append(values); + d->type = Text; + d->text.append(values); + d->value.clear(); } int APE::Item::size() const { - return 8 + d->key.size() + 1 + d->value.size(); + int result = 8 + d->key.size() + 1; + switch(d->type) { + case Text: + if(!d->text.isEmpty()) { + StringList::ConstIterator it = d->text.begin(); + + result += it->data(String::UTF8).size(); + it++; + for(; it != d->text.end(); ++it) + result += 1 + it->data(String::UTF8).size(); + } + break; + + case Binary: + case Locator: + result += d->value.size(); + break; + } + return result; } StringList APE::Item::toStringList() const @@ -154,19 +217,22 @@ StringList APE::Item::values() const String APE::Item::toString() const { - return isEmpty() ? String::null : d->text.front(); + if(d->type == Text && !isEmpty()) + return d->text.front(); + else + return String(); } bool APE::Item::isEmpty() const { switch(d->type) { case Text: - case Binary: if(d->text.isEmpty()) return true; if(d->text.size() == 1 && d->text.front().isEmpty()) return true; return false; + case Binary: case Locator: return d->value.isEmpty(); default: @@ -183,24 +249,29 @@ void APE::Item::parse(const ByteVector &data) return; } - uint valueLength = data.mid(0, 4).toUInt(false); - uint flags = data.mid(4, 4).toUInt(false); + const unsigned int valueLength = data.toUInt(0, false); + const unsigned int flags = data.toUInt(4, false); - d->key = String(data.mid(8), String::UTF8); + // An item key can contain ASCII characters from 0x20 up to 0x7E, not UTF-8. + // We assume that the validity of the given key has been checked. - d->value = data.mid(8 + d->key.size() + 1, valueLength); + d->key = String(&data[8], String::Latin1); + + const ByteVector value = data.mid(8 + d->key.size() + 1, valueLength); setReadOnly(flags & 1); setType(ItemTypes((flags >> 1) & 3)); - if(int(d->type) < 2) - d->text = StringList(ByteVectorList::split(d->value, '\0'), String::UTF8); + if(Text == d->type) + d->text = StringList(ByteVectorList::split(value, '\0'), String::UTF8); + else + d->value = value; } ByteVector APE::Item::render() const { ByteVector data; - TagLib::uint flags = ((d->readOnly) ? 1 : 0) | (d->type << 1); + unsigned int flags = ((d->readOnly) ? 1 : 0) | (d->type << 1); ByteVector value; if(isEmpty()) @@ -222,7 +293,7 @@ ByteVector APE::Item::render() const data.append(ByteVector::fromUInt(value.size(), false)); data.append(ByteVector::fromUInt(flags, false)); - data.append(d->key.data(String::UTF8)); + data.append(d->key.data(String::Latin1)); data.append(ByteVector('\0')); data.append(value); diff --git a/Frameworks/TagLib/taglib/taglib/ape/apeitem.h b/Frameworks/TagLib/taglib/taglib/ape/apeitem.h index 01fcc764f..cfd157c35 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apeitem.h +++ b/Frameworks/TagLib/taglib/taglib/ape/apeitem.h @@ -59,16 +59,22 @@ namespace TagLib { Item(); /*! - * Constructs an item with \a key and \a value. + * Constructs a text item with \a key and \a value. */ // BIC: Remove this, StringList has a constructor from a single string Item(const String &key, const String &value); /*! - * Constructs an item with \a key and \a values. + * Constructs a text item with \a key and \a values. */ Item(const String &key, const StringList &values); + /*! + * Constructs an item with \a key and \a value. + * If \a binary is true a Binary item will be created, otherwise \a value will be interpreted as text + */ + Item(const String &key, const ByteVector &value, bool binary); + /*! * Construct an item as a copy of \a item. */ @@ -84,6 +90,11 @@ namespace TagLib { */ Item &operator=(const Item &item); + /*! + * Exchanges the content of this item by the content of \a item. + */ + void swap(Item &item); + /*! * Returns the key. */ @@ -91,12 +102,20 @@ namespace TagLib { /*! * Returns the binary value. - * - * \deprecated This will be removed in the next binary incompatible version - * as it is not kept in sync with the things that are set using setValue() - * and friends. + * If the item type is not \a Binary, always returns an empty ByteVector. */ + ByteVector binaryData() const; + + /*! + * Set the binary value to \a value + * The item's type will also be set to \a Binary + */ + void setBinaryData(const ByteVector &value); + +#ifndef DO_NOT_DOCUMENT + /* Remove in next binary incompatible release */ ByteVector value() const; +#endif /*! * Sets the key for the item to \a key. @@ -104,14 +123,14 @@ namespace TagLib { void setKey(const String &key); /*! - * Sets the value of the item to \a value and clears any previous contents. + * Sets the text value of the item to \a value and clears any previous contents. * * \see toString() */ void setValue(const String &value); /*! - * Sets the value of the item to the list of values in \a value and clears + * Sets the text value of the item to the list of values in \a value and clears * any previous contents. * * \see toStringList() @@ -119,14 +138,14 @@ namespace TagLib { void setValues(const StringList &values); /*! - * Appends \a value to create (or extend) the current list of values. + * Appends \a value to create (or extend) the current list of text values. * * \see toString() */ void appendValue(const String &value); /*! - * Appends \a values to extend the current list of values. + * Appends \a values to extend the current list of text values. * * \see toStringList() */ @@ -138,19 +157,20 @@ namespace TagLib { int size() const; /*! - * Returns the value as a single string. In case of multiple strings, - * the first is returned. + * Returns the value as a single string. In case of multiple strings, + * the first is returned. If the data type is not \a Text, always returns + * an empty String. */ String toString() const; - /*! - * \deprecated - * \see values - */ +#ifndef DO_NOT_DOCUMENT + /* Remove in next binary incompatible release */ StringList toStringList() const; +#endif /*! - * Returns the list of values. + * Returns the list of text values. If the data type is not \a Text, always + * returns an empty StringList. */ StringList values() const; diff --git a/Frameworks/TagLib/taglib/taglib/ape/apeproperties.cpp b/Frameworks/TagLib/taglib/taglib/ape/apeproperties.cpp index 3154d1045..dee7e8c05 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apeproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/ape/apeproperties.cpp @@ -33,21 +33,22 @@ #include "id3v2tag.h" #include "apeproperties.h" #include "apefile.h" +#include "apetag.h" +#include "apefooter.h" using namespace TagLib; class APE::Properties::PropertiesPrivate { public: - PropertiesPrivate(File *file, long streamLength) : + PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), version(0), bitsPerSample(0), - file(file), - streamLength(streamLength) {} + sampleFrames(0) {} int length; int bitrate; @@ -55,18 +56,25 @@ public: int channels; int version; int bitsPerSample; - File *file; - long streamLength; + unsigned int sampleFrames; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -APE::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +APE::Properties::Properties(File *, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file, file->length()); - read(); + debug("APE::Properties::Properties() -- This constructor is no longer used."); +} + +APE::Properties::Properties(File *file, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) +{ + read(file, streamLength); } APE::Properties::~Properties() @@ -75,6 +83,16 @@ APE::Properties::~Properties() } int APE::Properties::length() const +{ + return lengthInSeconds(); +} + +int APE::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int APE::Properties::lengthInMilliseconds() const { return d->length; } @@ -104,121 +122,131 @@ int APE::Properties::bitsPerSample() const return d->bitsPerSample; } +unsigned int APE::Properties::sampleFrames() const +{ + return d->sampleFrames; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// - -void APE::Properties::read() +namespace { - // First we are searching the descriptor - long offset = findDescriptor(); - if(offset < 0) - return; + int headerVersion(const ByteVector &header) + { + if(header.size() < 6 || !header.startsWith("MAC ")) + return -1; - // Then we read the header common for all versions of APE - d->file->seek(offset); - ByteVector commonHeader=d->file->readBlock(6); - if(!commonHeader.startsWith("MAC ")) - return; - d->version = commonHeader.mid(4).toUInt(false); - - if(d->version >= 3980) { - analyzeCurrent(); - } - else { - analyzeOld(); + return header.toUShort(4, false); } } -long APE::Properties::findDescriptor() +void APE::Properties::read(File *file, long streamLength) { - long ID3v2Location = findID3v2(); - long ID3v2OriginalSize = 0; - bool hasID3v2 = false; - if(ID3v2Location >= 0) { - ID3v2::Tag tag(d->file, ID3v2Location, 0); - ID3v2OriginalSize = tag.header()->completeTagSize(); - if(tag.header()->tagSize() > 0) - hasID3v2 = true; + // First, we assume that the file pointer is set at the first descriptor. + long offset = file->tell(); + int version = headerVersion(file->readBlock(6)); + + // Next, we look for the descriptor. + if(version < 0) { + offset = file->find("MAC ", offset); + file->seek(offset); + version = headerVersion(file->readBlock(6)); } - long offset = 0; - if(hasID3v2) - offset = d->file->find("MAC ", ID3v2Location + ID3v2OriginalSize); + if(version < 0) { + debug("APE::Properties::read() -- APE descriptor not found"); + return; + } + + d->version = version; + + if(d->version >= 3980) + analyzeCurrent(file); else - offset = d->file->find("MAC "); + analyzeOld(file); - if(offset < 0) { - debug("APE::Properties::findDescriptor() -- APE descriptor not found"); - return -1; + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast<int>(length + 0.5); + d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5); } - - return offset; } -long APE::Properties::findID3v2() -{ - if(!d->file->isValid()) - return -1; - - d->file->seek(0); - - if(d->file->readBlock(3) == ID3v2::Header::fileIdentifier()) - return 0; - - return -1; -} - -void APE::Properties::analyzeCurrent() +void APE::Properties::analyzeCurrent(File *file) { // Read the descriptor - d->file->seek(2, File::Current); - ByteVector descriptor = d->file->readBlock(44); - uint descriptorBytes = descriptor.mid(0,4).toUInt(false); + file->seek(2, File::Current); + const ByteVector descriptor = file->readBlock(44); + if(descriptor.size() < 44) { + debug("APE::Properties::analyzeCurrent() -- descriptor is too short."); + return; + } - if ((descriptorBytes - 52) > 0) - d->file->seek(descriptorBytes - 52, File::Current); + const unsigned int descriptorBytes = descriptor.toUInt(0, false); + + if((descriptorBytes - 52) > 0) + file->seek(descriptorBytes - 52, File::Current); // Read the header - ByteVector header = d->file->readBlock(24); + const ByteVector header = file->readBlock(24); + if(header.size() < 24) { + debug("APE::Properties::analyzeCurrent() -- MAC header is too short."); + return; + } // Get the APE info - d->channels = header.mid(18, 2).toShort(false); - d->sampleRate = header.mid(20, 4).toUInt(false); - d->bitsPerSample = header.mid(16, 2).toShort(false); - //d->compressionLevel = + d->channels = header.toShort(18, false); + d->sampleRate = header.toUInt(20, false); + d->bitsPerSample = header.toShort(16, false); - uint totalFrames = header.mid(12, 4).toUInt(false); - uint blocksPerFrame = header.mid(4, 4).toUInt(false); - uint finalFrameBlocks = header.mid(8, 4).toUInt(false); - uint totalBlocks = totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0; - d->length = totalBlocks / d->sampleRate; - d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; + const unsigned int totalFrames = header.toUInt(12, false); + if(totalFrames == 0) + return; + + const unsigned int blocksPerFrame = header.toUInt(4, false); + const unsigned int finalFrameBlocks = header.toUInt(8, false); + d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; } -void APE::Properties::analyzeOld() +void APE::Properties::analyzeOld(File *file) { - ByteVector header = d->file->readBlock(26); - uint totalFrames = header.mid(18, 4).toUInt(false); + const ByteVector header = file->readBlock(26); + if(header.size() < 26) { + debug("APE::Properties::analyzeOld() -- MAC header is too short."); + return; + } + + const unsigned int totalFrames = header.toUInt(18, false); // Fail on 0 length APE files (catches non-finalized APE files) if(totalFrames == 0) return; - short compressionLevel = header.mid(0, 2).toShort(false); - uint blocksPerFrame; + const short compressionLevel = header.toShort(0, false); + unsigned int blocksPerFrame; if(d->version >= 3950) blocksPerFrame = 73728 * 4; else if(d->version >= 3900 || (d->version >= 3800 && compressionLevel == 4000)) blocksPerFrame = 73728; else blocksPerFrame = 9216; - d->channels = header.mid(4, 2).toShort(false); - d->sampleRate = header.mid(6, 4).toUInt(false); - uint finalFrameBlocks = header.mid(22, 4).toUInt(false); - uint totalBlocks = totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0; - d->length = totalBlocks / d->sampleRate; - d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; -} + // Get the APE info + d->channels = header.toShort(4, false); + d->sampleRate = header.toUInt(6, false); + + const unsigned int finalFrameBlocks = header.toUInt(22, false); + d->sampleFrames = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; + + // Get the bit depth from the RIFF-fmt chunk. + file->seek(16, File::Current); + const ByteVector fmt = file->readBlock(28); + if(fmt.size() < 28 || !fmt.startsWith("WAVEfmt ")) { + debug("APE::Properties::analyzeOld() -- fmt header is too short."); + return; + } + + d->bitsPerSample = fmt.toShort(26, false); +} diff --git a/Frameworks/TagLib/taglib/taglib/ape/apeproperties.h b/Frameworks/TagLib/taglib/taglib/ape/apeproperties.h index 8b543a572..ebbf949bd 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apeproperties.h +++ b/Frameworks/TagLib/taglib/taglib/ape/apeproperties.h @@ -51,27 +51,75 @@ namespace TagLib { public: /*! * Create an instance of APE::Properties with the data read from the - * ByteVector \a data. + * APE::File \a file. + * + * \deprecated */ - Properties(File *f, ReadStyle style = Average); + TAGLIB_DEPRECATED Properties(File *file, ReadStyle style = Average); + + /*! + * Create an instance of APE::Properties with the data read from the + * APE::File \a file. + */ + Properties(File *file, long streamLength, ReadStyle style = Average); /*! * Destroys this APE::Properties instance. */ virtual ~Properties(); - // Reimplementations. + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; - virtual int length() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns number of bits per sample. + * Returns the number of bits per audio sample. */ int bitsPerSample() const; + /*! + * Returns the total number of audio samples in file. + */ + unsigned int sampleFrames() const; + /*! * Returns APE version. */ @@ -81,13 +129,10 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(File *file, long streamLength); - long findDescriptor(); - long findID3v2(); - - void analyzeCurrent(); - void analyzeOld(); + void analyzeCurrent(File *file); + void analyzeOld(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp b/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp index 3bfde906d..a2bdaeed9 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp +++ b/Frameworks/TagLib/taglib/taglib/ape/apetag.cpp @@ -23,7 +23,7 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef __SUNPRO_CC +#if defined(__SUNPRO_CC) && (__SUNPRO_CC < 0x5130) // Sun Studio finds multiple specializations of Map because // it considers specializations with and without class types // to be different; this define forces Map to use only the @@ -34,6 +34,9 @@ #include <tfile.h> #include <tstring.h> #include <tmap.h> +#include <tpropertymap.h> +#include <tdebug.h> +#include <tutils.h> #include "apetag.h" #include "apefooter.h" @@ -42,17 +45,44 @@ using namespace TagLib; using namespace APE; +namespace +{ + const unsigned int MinKeyLength = 2; + const unsigned int MaxKeyLength = 255; + + bool isKeyValid(const ByteVector &key) + { + const char *invalidKeys[] = { "ID3", "TAG", "OGGS", "MP+", 0 }; + + // only allow printable ASCII including space (32..126) + + for(ByteVector::ConstIterator it = key.begin(); it != key.end(); ++it) { + const int c = static_cast<unsigned char>(*it); + if(c < 32 || c > 126) + return false; + } + + const String upperKey = String(key).upper(); + for(size_t i = 0; invalidKeys[i] != 0; ++i) { + if(upperKey == invalidKeys[i]) + return false; + } + + return true; + } +} + class APE::Tag::TagPrivate { public: - TagPrivate() : file(0), footerLocation(-1), tagLength(0) {} + TagPrivate() : + file(0), + footerLocation(0) {} File *file; long footerLocation; - long tagLength; Footer footer; - ItemListMap itemListMap; }; @@ -60,14 +90,16 @@ public: // public methods //////////////////////////////////////////////////////////////////////////////// -APE::Tag::Tag() : TagLib::Tag() +APE::Tag::Tag() : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; } -APE::Tag::Tag(File *file, long footerLocation) : TagLib::Tag() +APE::Tag::Tag(TagLib::File *file, long footerLocation) : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; d->file = file; d->footerLocation = footerLocation; @@ -87,80 +119,52 @@ ByteVector APE::Tag::fileIdentifier() String APE::Tag::title() const { if(d->itemListMap["TITLE"].isEmpty()) - return String::null; - return d->itemListMap["TITLE"].toString(); + return String(); + return d->itemListMap["TITLE"].values().toString(); } String APE::Tag::artist() const { if(d->itemListMap["ARTIST"].isEmpty()) - return String::null; - return d->itemListMap["ARTIST"].toString(); + return String(); + return d->itemListMap["ARTIST"].values().toString(); } String APE::Tag::album() const { if(d->itemListMap["ALBUM"].isEmpty()) - return String::null; - return d->itemListMap["ALBUM"].toString(); + return String(); + return d->itemListMap["ALBUM"].values().toString(); } String APE::Tag::comment() const { if(d->itemListMap["COMMENT"].isEmpty()) - return String::null; - return d->itemListMap["COMMENT"].toString(); + return String(); + return d->itemListMap["COMMENT"].values().toString(); } String APE::Tag::genre() const { if(d->itemListMap["GENRE"].isEmpty()) - return String::null; - return d->itemListMap["GENRE"].toString(); + return String(); + return d->itemListMap["GENRE"].values().toString(); } -TagLib::uint APE::Tag::year() const +unsigned int APE::Tag::year() const { if(d->itemListMap["YEAR"].isEmpty()) return 0; return d->itemListMap["YEAR"].toString().toInt(); } -TagLib::uint APE::Tag::track() const +unsigned int APE::Tag::track() const { if(d->itemListMap["TRACK"].isEmpty()) return 0; return d->itemListMap["TRACK"].toString().toInt(); } -float APE::Tag::rgAlbumGain() const -{ - if (d->itemListMap["REPLAYGAIN_ALBUM_GAIN"].isEmpty()) - return 0; - return d->itemListMap["REPLAYGAIN_ALBUM_GAIN"].toString().toFloat(); -} - -float APE::Tag::rgAlbumPeak() const -{ - if (d->itemListMap["REPLAYGAIN_ALBUM_PEAK"].isEmpty()) - return 0; - return d->itemListMap["REPLAYGAIN_ALBUM_PEAK"].toString().toFloat(); -} - -float APE::Tag::rgTrackGain() const -{ - if (d->itemListMap["REPLAYGAIN_TRACK_GAIN"].isEmpty()) - return 0; - return d->itemListMap["REPLAYGAIN_TRACK_GAIN"].toString().toFloat(); -} - -float APE::Tag::rgTrackPeak() const -{ - if (d->itemListMap["REPLAYGAIN_TRACK_PEAK"].isEmpty()) - return 0; - return d->itemListMap["REPLAYGAIN_TRACK_PEAK"].toString().toFloat(); -} - void APE::Tag::setTitle(const String &s) { addValue("TITLE", s, true); @@ -186,52 +190,119 @@ void APE::Tag::setGenre(const String &s) addValue("GENRE", s, true); } -void APE::Tag::setYear(uint i) +void APE::Tag::setYear(unsigned int i) { - if(i <= 0) + if(i == 0) removeItem("YEAR"); else addValue("YEAR", String::number(i), true); } -void APE::Tag::setTrack(uint i) +void APE::Tag::setTrack(unsigned int i) { - if(i <= 0) + if(i == 0) removeItem("TRACK"); else addValue("TRACK", String::number(i), true); } -void APE::Tag::setRGAlbumGain(float f) +namespace { - if (f == 0) - removeItem("REPLAYGAIN_ALBUM_GAIN"); - else - addValue("REPLAYGAIN_ALBUM_GAIN", String::number(f) + " dB", true); + // conversions of tag keys between what we use in PropertyMap and what's usual + // for APE tags + // usual, APE + const char *keyConversions[][2] = {{"TRACKNUMBER", "TRACK" }, + {"DATE", "YEAR" }, + {"ALBUMARTIST", "ALBUM ARTIST"}, + {"DISCNUMBER", "DISC" }, + {"REMIXER", "MIXARTIST" }, + {"RELEASESTATUS", "MUSICBRAINZ_ALBUMSTATUS" }, + {"RELEASETYPE", "MUSICBRAINZ_ALBUMTYPE" }}; + const size_t keyConversionsSize = sizeof(keyConversions) / sizeof(keyConversions[0]); } -void APE::Tag::setRGAlbumPeak(float f) +PropertyMap APE::Tag::properties() const { - if (f == 0) - removeItem("REPLAYGAIN_ALBUM_PEAK"); - else - addValue("REPLAYGAIN_ALBUM_PEAK", String::number(f), true); + PropertyMap properties; + ItemListMap::ConstIterator it = itemListMap().begin(); + for(; it != itemListMap().end(); ++it) { + String tagName = it->first.upper(); + // if the item is Binary or Locator, or if the key is an invalid string, + // add to unsupportedData + if(it->second.type() != Item::Text || tagName.isEmpty()) { + properties.unsupportedData().append(it->first); + } + else { + // Some tags need to be handled specially + for(size_t i = 0; i < keyConversionsSize; ++i) { + if(tagName == keyConversions[i][1]) + tagName = keyConversions[i][0]; + } + properties[tagName].append(it->second.toStringList()); + } + } + return properties; } -void APE::Tag::setRGTrackGain(float f) +void APE::Tag::removeUnsupportedProperties(const StringList &properties) { - if (f == 0) - removeItem("REPLAYGAIN_TRACK_GAIN"); - else - addValue("REPLAYGAIN_TRACK_GAIN", String::number(f) + " dB", true); + StringList::ConstIterator it = properties.begin(); + for(; it != properties.end(); ++it) + removeItem(*it); } -void APE::Tag::setRGTrackPeak(float f) +PropertyMap APE::Tag::setProperties(const PropertyMap &origProps) { - if (f == 0) - removeItem("REPLAYGAIN_TRACK_PEAK"); - else - addValue("REPLAYGAIN_TRACK_PEAK", String::number(f), true); + PropertyMap properties(origProps); // make a local copy that can be modified + + // see comment in properties() + for(size_t i = 0; i < keyConversionsSize; ++i) + if(properties.contains(keyConversions[i][0])) { + properties.insert(keyConversions[i][1], properties[keyConversions[i][0]]); + properties.erase(keyConversions[i][0]); + } + + // first check if tags need to be removed completely + StringList toRemove; + ItemListMap::ConstIterator remIt = itemListMap().begin(); + for(; remIt != itemListMap().end(); ++remIt) { + String key = remIt->first.upper(); + // only remove if a) key is valid, b) type is text, c) key not contained in new properties + if(!key.isEmpty() && remIt->second.type() == APE::Item::Text && !properties.contains(key)) + toRemove.append(remIt->first); + } + + for(StringList::ConstIterator removeIt = toRemove.begin(); removeIt != toRemove.end(); removeIt++) + removeItem(*removeIt); + + // now sync in the "forward direction" + PropertyMap::ConstIterator it = properties.begin(); + PropertyMap invalid; + for(; it != properties.end(); ++it) { + const String &tagName = it->first; + if(!checkKey(tagName)) + invalid.insert(it->first, it->second); + else if(!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) { + if(it->second.isEmpty()) + removeItem(tagName); + else { + StringList::ConstIterator valueIt = it->second.begin(); + addValue(tagName, *valueIt, true); + ++valueIt; + for(; valueIt != it->second.end(); ++valueIt) + addValue(tagName, *valueIt, false); + } + } + } + return invalid; +} + +bool APE::Tag::checkKey(const String &key) +{ + if(key.size() < MinKeyLength || key.size() > MaxKeyLength) + return false; + + return isKeyValid(key.data(String::UTF8)); } APE::Footer *APE::Tag::footer() const @@ -246,26 +317,46 @@ const APE::ItemListMap& APE::Tag::itemListMap() const void APE::Tag::removeItem(const String &key) { - Map<const String, Item>::Iterator it = d->itemListMap.find(key.upper()); - if(it != d->itemListMap.end()) - d->itemListMap.erase(it); + d->itemListMap.erase(key.upper()); } void APE::Tag::addValue(const String &key, const String &value, bool replace) { if(replace) removeItem(key); - if(!value.isEmpty()) { - if(d->itemListMap.contains(key) || !replace) - d->itemListMap[key.upper()].appendValue(value); - else - setItem(key, Item(key, value)); - } + + if(value.isEmpty()) + return; + + // Text items may contain more than one value. + // Binary or locator items may have only one value, hence always replaced. + + ItemListMap::Iterator it = d->itemListMap.find(key.upper()); + + if(it != d->itemListMap.end() && it->second.type() == Item::Text) + it->second.appendValue(value); + else + setItem(key, Item(key, value)); +} + +void APE::Tag::setData(const String &key, const ByteVector &value) +{ + removeItem(key); + + if(value.isEmpty()) + return; + + setItem(key, Item(key, value, true)); } void APE::Tag::setItem(const String &key, const Item &item) { - d->itemListMap.insert(key.upper(), item); + if(!checkKey(key)) { + debug("APE::Tag::setItem() - Couldn't set an item due to an invalid key."); + return; + } + + d->itemListMap[key.upper()] = item; } bool APE::Tag::isEmpty() const @@ -285,7 +376,7 @@ void APE::Tag::read() d->footer.setData(d->file->readBlock(Footer::size())); if(d->footer.tagSize() <= Footer::size() || - d->footer.tagSize() > uint(d->file->length())) + d->footer.tagSize() > static_cast<unsigned long>(d->file->length())) return; d->file->seek(d->footerLocation + Footer::size() - d->footer.tagSize()); @@ -296,15 +387,11 @@ void APE::Tag::read() ByteVector APE::Tag::render() const { ByteVector data; - uint itemCount = 0; + unsigned int itemCount = 0; - { - for(Map<const String, Item>::ConstIterator it = d->itemListMap.begin(); - it != d->itemListMap.end(); ++it) - { - data.append(it->second.render()); - itemCount++; - } + for(ItemListMap::ConstIterator it = d->itemListMap.begin(); it != d->itemListMap.end(); ++it) { + data.append(it->second.render()); + itemCount++; } d->footer.setItemCount(itemCount); @@ -316,16 +403,37 @@ ByteVector APE::Tag::render() const void APE::Tag::parse(const ByteVector &data) { - uint pos = 0; - // 11 bytes is the minimum size for an APE item - for(uint i = 0; i < d->footer.itemCount() && pos <= data.size() - 11; i++) { - APE::Item item; - item.parse(data.mid(pos)); + if(data.size() < 11) + return; - d->itemListMap.insert(item.key().upper(), item); + unsigned int pos = 0; - pos += item.size(); + for(unsigned int i = 0; i < d->footer.itemCount() && pos <= data.size() - 11; i++) { + + const int nullPos = data.find('\0', pos + 8); + if(nullPos < 0) { + debug("APE::Tag::parse() - Couldn't find a key/value separator. Stopped parsing."); + return; + } + + const unsigned int keyLength = nullPos - pos - 8; + const unsigned int valLegnth = data.toUInt(pos, false); + + if(keyLength >= MinKeyLength + && keyLength <= MaxKeyLength + && isKeyValid(data.mid(pos + 8, keyLength))) + { + APE::Item item; + item.parse(data.mid(pos)); + + d->itemListMap.insert(item.key().upper(), item); + } + else { + debug("APE::Tag::parse() - Skipped an item due to an invalid key."); + } + + pos += keyLength + valLegnth + 9; } } diff --git a/Frameworks/TagLib/taglib/taglib/ape/apetag.h b/Frameworks/TagLib/taglib/taglib/ape/apetag.h index 5cda8a7a0..f4d4fba64 100644 --- a/Frameworks/TagLib/taglib/taglib/ape/apetag.h +++ b/Frameworks/TagLib/taglib/taglib/ape/apetag.h @@ -92,24 +92,47 @@ namespace TagLib { virtual String album() const; virtual String comment() const; virtual String genre() const; - virtual uint year() const; - virtual uint track() const; - virtual float rgAlbumGain() const; - virtual float rgAlbumPeak() const; - virtual float rgTrackGain() const; - virtual float rgTrackPeak() const; + virtual unsigned int year() const; + virtual unsigned int track() const; virtual void setTitle(const String &s); virtual void setArtist(const String &s); virtual void setAlbum(const String &s); virtual void setComment(const String &s); virtual void setGenre(const String &s); - virtual void setYear(uint i); - virtual void setTrack(uint i); - virtual void setRGAlbumGain(float f); - virtual void setRGAlbumPeak(float f); - virtual void setRGTrackGain(float f); - virtual void setRGTrackPeak(float f); + virtual void setYear(unsigned int i); + virtual void setTrack(unsigned int i); + + /*! + * Implements the unified tag dictionary interface -- export function. + * APE tags are perfectly compatible with the dictionary interface because they + * support both arbitrary tag names and multiple values. Currently only + * APE items of type *Text* are handled by the dictionary interface; all *Binary* + * and *Locator* items will be put into the unsupportedData list and can be + * deleted on request using removeUnsupportedProperties(). The same happens + * to Text items if their key is invalid for PropertyMap (which should actually + * never happen). + * + * The only conversion done by this export function is to rename the APE tags + * TRACK to TRACKNUMBER, YEAR to DATE, and ALBUM ARTIST to ALBUMARTIST, respectively, + * in order to be compliant with the names used in other formats. + */ + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified tag dictionary interface -- import function. The same + * comments as for the export function apply; additionally note that the APE tag + * specification requires keys to have between 2 and 16 printable ASCII characters + * with the exception of the fixed strings "ID3", "TAG", "OGGS", and "MP+". + */ + PropertyMap setProperties(const PropertyMap &); + + /*! + * Check if the given String is a valid APE tag key. + */ + static bool checkKey(const String&); /*! * Returns a pointer to the tag's footer. @@ -120,7 +143,7 @@ namespace TagLib { * Returns a reference to the item list map. This is an ItemListMap of * all of the items in the tag. * - * This is the most powerfull structure for accessing the items of the tag. + * This is the most powerful structure for accessing the items of the tag. * * APE tags are case-insensitive, all keys in this map have been converted * to upper case. @@ -136,12 +159,19 @@ namespace TagLib { void removeItem(const String &key); /*! - * Adds to the item specified by \a key the data \a value. If \a replace + * Adds to the text item specified by \a key the data \a value. If \a replace * is true, then all of the other values on the same key will be removed - * first. + * first. If a binary item exists for \a key it will be removed first. */ void addValue(const String &key, const String &value, bool replace = true); + /*! + * Set the binary data for the key specified by \a item to \a value + * This will convert the item to type \a Binary if it isn't already and + * all of the other values on the same key will be removed. + */ + void setData(const String &key, const ByteVector &value); + /*! * Sets the \a key item to the value of \a item. If an item with the \a key is already * present, it will be replaced. diff --git a/Frameworks/TagLib/taglib/taglib/asf/asfattribute.cpp b/Frameworks/TagLib/taglib/taglib/asf/asfattribute.cpp index ecdf1f618..6faf7973b 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asfattribute.cpp +++ b/Frameworks/TagLib/taglib/taglib/asf/asfattribute.cpp @@ -23,36 +23,29 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WITH_ASF - #include <taglib.h> #include <tdebug.h> +#include <trefcounter.h> + #include "asfattribute.h" #include "asffile.h" +#include "asfutils.h" using namespace TagLib; class ASF::Attribute::AttributePrivate : public RefCounter { public: - AttributePrivate() - : pictureValue(ASF::Picture::fromInvalid()), - stream(0), - language(0) {} + AttributePrivate() : + pictureValue(ASF::Picture::fromInvalid()), + numericValue(0), + stream(0), + language(0) {} AttributeTypes type; String stringValue; ByteVector byteVectorValue; ASF::Picture pictureValue; - union { - unsigned int intValue; - unsigned short shortValue; - unsigned long long longLongValue; - bool boolValue; - }; + unsigned long long numericValue; int stream; int language; }; @@ -61,82 +54,86 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -ASF::Attribute::Attribute() +ASF::Attribute::Attribute() : + d(new AttributePrivate()) { - d = new AttributePrivate; d->type = UnicodeType; } -ASF::Attribute::Attribute(const ASF::Attribute &other) - : d(other.d) +ASF::Attribute::Attribute(const ASF::Attribute &other) : + d(other.d) { d->ref(); } +ASF::Attribute::Attribute(const String &value) : + d(new AttributePrivate()) +{ + d->type = UnicodeType; + d->stringValue = value; +} + +ASF::Attribute::Attribute(const ByteVector &value) : + d(new AttributePrivate()) +{ + d->type = BytesType; + d->byteVectorValue = value; +} + +ASF::Attribute::Attribute(const ASF::Picture &value) : + d(new AttributePrivate()) +{ + d->type = BytesType; + d->pictureValue = value; +} + +ASF::Attribute::Attribute(unsigned int value) : + d(new AttributePrivate()) +{ + d->type = DWordType; + d->numericValue = value; +} + +ASF::Attribute::Attribute(unsigned long long value) : + d(new AttributePrivate()) +{ + d->type = QWordType; + d->numericValue = value; +} + +ASF::Attribute::Attribute(unsigned short value) : + d(new AttributePrivate()) +{ + d->type = WordType; + d->numericValue = value; +} + +ASF::Attribute::Attribute(bool value) : + d(new AttributePrivate()) +{ + d->type = BoolType; + d->numericValue = value; +} + ASF::Attribute &ASF::Attribute::operator=(const ASF::Attribute &other) { - if(d->deref()) - delete d; - d = other.d; - d->ref(); + Attribute(other).swap(*this); return *this; } +void ASF::Attribute::swap(Attribute &other) +{ + using std::swap; + + swap(d, other.d); +} + ASF::Attribute::~Attribute() { if(d->deref()) delete d; } -ASF::Attribute::Attribute(const String &value) -{ - d = new AttributePrivate; - d->type = UnicodeType; - d->stringValue = value; -} - -ASF::Attribute::Attribute(const ByteVector &value) -{ - d = new AttributePrivate; - d->type = BytesType; - d->byteVectorValue = value; -} - -ASF::Attribute::Attribute(const ASF::Picture &value) -{ - d = new AttributePrivate; - d->type = BytesType; - d->pictureValue = value; -} - -ASF::Attribute::Attribute(unsigned int value) -{ - d = new AttributePrivate; - d->type = DWordType; - d->intValue = value; -} - -ASF::Attribute::Attribute(unsigned long long value) -{ - d = new AttributePrivate; - d->type = QWordType; - d->longLongValue = value; -} - -ASF::Attribute::Attribute(unsigned short value) -{ - d = new AttributePrivate; - d->type = WordType; - d->shortValue = value; -} - -ASF::Attribute::Attribute(bool value) -{ - d = new AttributePrivate; - d->type = BoolType; - d->boolValue = value; -} - ASF::Attribute::AttributeTypes ASF::Attribute::type() const { return d->type; @@ -156,22 +153,22 @@ ByteVector ASF::Attribute::toByteVector() const unsigned short ASF::Attribute::toBool() const { - return d->shortValue; + return d->numericValue ? 1 : 0; } unsigned short ASF::Attribute::toUShort() const { - return d->shortValue; + return static_cast<unsigned short>(d->numericValue); } unsigned int ASF::Attribute::toUInt() const { - return d->intValue; + return static_cast<unsigned int>(d->numericValue); } unsigned long long ASF::Attribute::toULongLong() const { - return d->longLongValue; + return static_cast<unsigned long long>(d->numericValue); } ASF::Picture ASF::Attribute::toPicture() const @@ -181,28 +178,28 @@ ASF::Picture ASF::Attribute::toPicture() const String ASF::Attribute::parse(ASF::File &f, int kind) { - uint size, nameLength; + unsigned int size, nameLength; String name; d->pictureValue = Picture::fromInvalid(); // extended content descriptor if(kind == 0) { - nameLength = f.readWORD(); - name = f.readString(nameLength); - d->type = ASF::Attribute::AttributeTypes(f.readWORD()); - size = f.readWORD(); + nameLength = readWORD(&f); + name = readString(&f, nameLength); + d->type = ASF::Attribute::AttributeTypes(readWORD(&f)); + size = readWORD(&f); } // metadata & metadata library else { - int temp = f.readWORD(); + int temp = readWORD(&f); // metadata library if(kind == 2) { d->language = temp; } - d->stream = f.readWORD(); - nameLength = f.readWORD(); - d->type = ASF::Attribute::AttributeTypes(f.readWORD()); - size = f.readDWORD(); - name = f.readString(nameLength); + d->stream = readWORD(&f); + nameLength = readWORD(&f); + d->type = ASF::Attribute::AttributeTypes(readWORD(&f)); + size = readDWORD(&f); + name = readString(&f, nameLength); } if(kind != 2 && size > 65535) { @@ -211,28 +208,28 @@ String ASF::Attribute::parse(ASF::File &f, int kind) switch(d->type) { case WordType: - d->shortValue = f.readWORD(); + d->numericValue = readWORD(&f); break; case BoolType: if(kind == 0) { - d->boolValue = f.readDWORD() == 1; + d->numericValue = (readDWORD(&f) != 0); } else { - d->boolValue = f.readWORD() == 1; + d->numericValue = (readWORD(&f) != 0); } break; case DWordType: - d->intValue = f.readDWORD(); + d->numericValue = readDWORD(&f); break; case QWordType: - d->longLongValue = f.readQWORD(); + d->numericValue = readQWORD(&f); break; case UnicodeType: - d->stringValue = f.readString(size); + d->stringValue = readString(&f, size); break; case BytesType: @@ -279,28 +276,28 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const switch (d->type) { case WordType: - data.append(ByteVector::fromShort(d->shortValue, false)); + data.append(ByteVector::fromShort(toUShort(), false)); break; case BoolType: if(kind == 0) { - data.append(ByteVector::fromUInt(d->boolValue ? 1 : 0, false)); + data.append(ByteVector::fromUInt(toBool(), false)); } else { - data.append(ByteVector::fromShort(d->boolValue ? 1 : 0, false)); + data.append(ByteVector::fromShort(toBool(), false)); } break; case DWordType: - data.append(ByteVector::fromUInt(d->intValue, false)); + data.append(ByteVector::fromUInt(toUInt(), false)); break; case QWordType: - data.append(ByteVector::fromLongLong(d->longLongValue, false)); + data.append(ByteVector::fromLongLong(toULongLong(), false)); break; case UnicodeType: - data.append(File::renderString(d->stringValue)); + data.append(renderString(d->stringValue)); break; case BytesType: @@ -314,13 +311,13 @@ ByteVector ASF::Attribute::render(const String &name, int kind) const } if(kind == 0) { - data = File::renderString(name, true) + + data = renderString(name, true) + ByteVector::fromShort((int)d->type, false) + ByteVector::fromShort(data.size(), false) + data; } else { - ByteVector nameData = File::renderString(name); + ByteVector nameData = renderString(name); data = ByteVector::fromShort(kind == 2 ? d->language : 0, false) + ByteVector::fromShort(d->stream, false) + ByteVector::fromShort(nameData.size(), false) + @@ -352,5 +349,3 @@ void ASF::Attribute::setStream(int value) { d->stream = value; } - -#endif diff --git a/Frameworks/TagLib/taglib/taglib/asf/asfattribute.h b/Frameworks/TagLib/taglib/taglib/asf/asfattribute.h index 561869997..1738eb459 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asfattribute.h +++ b/Frameworks/TagLib/taglib/taglib/asf/asfattribute.h @@ -70,7 +70,7 @@ namespace TagLib /*! * Constructs an attribute with \a key and a BytesType \a value. */ - Attribute(const ByteVector &value); + Attribute(const ByteVector &value); /*! * Constructs an attribute with \a key and a Picture \a value. @@ -113,7 +113,12 @@ namespace TagLib /*! * Copies the contents of \a other into this item. */ - ASF::Attribute &operator=(const Attribute &other); + Attribute &operator=(const Attribute &other); + + /*! + * Exchanges the content of the Attribute by the content of \a other. + */ + void swap(Attribute &other); /*! * Destroys the attribute. diff --git a/Frameworks/TagLib/taglib/taglib/asf/asffile.cpp b/Frameworks/TagLib/taglib/taglib/asf/asffile.cpp index 0a947472a..fd754803d 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asffile.cpp +++ b/Frameworks/TagLib/taglib/taglib/asf/asffile.cpp @@ -23,203 +23,261 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WITH_ASF - #include <tdebug.h> #include <tbytevectorlist.h> +#include <tpropertymap.h> #include <tstring.h> +#include <tagutils.h> + #include "asffile.h" #include "asftag.h" #include "asfproperties.h" +#include "asfutils.h" using namespace TagLib; class ASF::File::FilePrivate { public: + class BaseObject; + class UnknownObject; + class FilePropertiesObject; + class StreamPropertiesObject; + class ContentDescriptionObject; + class ExtendedContentDescriptionObject; + class HeaderExtensionObject; + class CodecListObject; + class MetadataObject; + class MetadataLibraryObject; + FilePrivate(): - size(0), + headerSize(0), tag(0), properties(0), contentDescriptionObject(0), extendedContentDescriptionObject(0), headerExtensionObject(0), metadataObject(0), - metadataLibraryObject(0) {} - unsigned long long size; + metadataLibraryObject(0) + { + objects.setAutoDelete(true); + } + + ~FilePrivate() + { + delete tag; + delete properties; + } + + unsigned long long headerSize; + ASF::Tag *tag; ASF::Properties *properties; - List<ASF::File::BaseObject *> objects; - ASF::File::ContentDescriptionObject *contentDescriptionObject; - ASF::File::ExtendedContentDescriptionObject *extendedContentDescriptionObject; - ASF::File::HeaderExtensionObject *headerExtensionObject; - ASF::File::MetadataObject *metadataObject; - ASF::File::MetadataLibraryObject *metadataLibraryObject; + + List<BaseObject *> objects; + + ContentDescriptionObject *contentDescriptionObject; + ExtendedContentDescriptionObject *extendedContentDescriptionObject; + HeaderExtensionObject *headerExtensionObject; + MetadataObject *metadataObject; + MetadataLibraryObject *metadataLibraryObject; }; -static ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); -static ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16); -static ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16); -static ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); -static ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16); -static ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); -static ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); -static ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); +namespace +{ + const ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); + const ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16); + const ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16); + const ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); + const ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16); + const ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); + const ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); + const ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); + const ByteVector codecListGuid("\x40\x52\xd1\x86\x1d\x31\xd0\x11\xa3\xa4\x00\xa0\xc9\x03\x48\xf6", 16); + const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16); + const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16); + const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); +} -class ASF::File::BaseObject +class ASF::File::FilePrivate::BaseObject { public: ByteVector data; virtual ~BaseObject() {} - virtual ByteVector guid() = 0; + virtual ByteVector guid() const = 0; virtual void parse(ASF::File *file, unsigned int size); virtual ByteVector render(ASF::File *file); }; -class ASF::File::UnknownObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::UnknownObject : public ASF::File::FilePrivate::BaseObject { ByteVector myGuid; public: UnknownObject(const ByteVector &guid); - ByteVector guid(); + ByteVector guid() const; }; -class ASF::File::FilePropertiesObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::FilePropertiesObject : public ASF::File::FilePrivate::BaseObject { public: - ByteVector guid(); - void parse(ASF::File *file, uint size); + ByteVector guid() const; + void parse(ASF::File *file, unsigned int size); }; -class ASF::File::StreamPropertiesObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::StreamPropertiesObject : public ASF::File::FilePrivate::BaseObject { public: - ByteVector guid(); - void parse(ASF::File *file, uint size); + ByteVector guid() const; + void parse(ASF::File *file, unsigned int size); }; -class ASF::File::ContentDescriptionObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::ContentDescriptionObject : public ASF::File::FilePrivate::BaseObject { public: - ByteVector guid(); - void parse(ASF::File *file, uint size); + ByteVector guid() const; + void parse(ASF::File *file, unsigned int size); ByteVector render(ASF::File *file); }; -class ASF::File::ExtendedContentDescriptionObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::ExtendedContentDescriptionObject : public ASF::File::FilePrivate::BaseObject { public: ByteVectorList attributeData; - ByteVector guid(); - void parse(ASF::File *file, uint size); + ByteVector guid() const; + void parse(ASF::File *file, unsigned int size); ByteVector render(ASF::File *file); }; -class ASF::File::MetadataObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::MetadataObject : public ASF::File::FilePrivate::BaseObject { public: ByteVectorList attributeData; - ByteVector guid(); - void parse(ASF::File *file, uint size); + ByteVector guid() const; + void parse(ASF::File *file, unsigned int size); ByteVector render(ASF::File *file); }; -class ASF::File::MetadataLibraryObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::MetadataLibraryObject : public ASF::File::FilePrivate::BaseObject { public: ByteVectorList attributeData; - ByteVector guid(); - void parse(ASF::File *file, uint size); + ByteVector guid() const; + void parse(ASF::File *file, unsigned int size); ByteVector render(ASF::File *file); }; -class ASF::File::HeaderExtensionObject : public ASF::File::BaseObject +class ASF::File::FilePrivate::HeaderExtensionObject : public ASF::File::FilePrivate::BaseObject { public: - List<ASF::File::BaseObject *> objects; - ByteVector guid(); - void parse(ASF::File *file, uint size); + List<ASF::File::FilePrivate::BaseObject *> objects; + HeaderExtensionObject(); + ByteVector guid() const; + void parse(ASF::File *file, unsigned int size); ByteVector render(ASF::File *file); }; -void ASF::File::BaseObject::parse(ASF::File *file, unsigned int size) +class ASF::File::FilePrivate::CodecListObject : public ASF::File::FilePrivate::BaseObject +{ +public: + ByteVector guid() const; + void parse(ASF::File *file, unsigned int size); + +private: + enum CodecType + { + Video = 0x0001, + Audio = 0x0002, + Unknown = 0xFFFF + }; +}; + +void ASF::File::FilePrivate::BaseObject::parse(ASF::File *file, unsigned int size) { data.clear(); - if (size > 24 && size <= (unsigned int)(file->length())) + if(size > 24 && size <= (unsigned int)(file->length())) data = file->readBlock(size - 24); else - data = ByteVector::null; + data = ByteVector(); } -ByteVector ASF::File::BaseObject::render(ASF::File * /*file*/) +ByteVector ASF::File::FilePrivate::BaseObject::render(ASF::File * /*file*/) { return guid() + ByteVector::fromLongLong(data.size() + 24, false) + data; } -ASF::File::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid) +ASF::File::FilePrivate::UnknownObject::UnknownObject(const ByteVector &guid) : myGuid(guid) { } -ByteVector ASF::File::UnknownObject::guid() +ByteVector ASF::File::FilePrivate::UnknownObject::guid() const { return myGuid; } -ByteVector ASF::File::FilePropertiesObject::guid() +ByteVector ASF::File::FilePrivate::FilePropertiesObject::guid() const { return filePropertiesGuid; } -void ASF::File::FilePropertiesObject::parse(ASF::File *file, uint size) +void ASF::File::FilePrivate::FilePropertiesObject::parse(ASF::File *file, unsigned int size) { BaseObject::parse(file, size); - file->d->properties->setLength((int)(data.mid(40, 8).toLongLong(false) / 10000000L - data.mid(56, 8).toLongLong(false) / 1000L)); + if(data.size() < 64) { + debug("ASF::File::FilePrivate::FilePropertiesObject::parse() -- data is too short."); + return; + } + + const long long duration = data.toLongLong(40, false); + const long long preroll = data.toLongLong(56, false); + file->d->properties->setLengthInMilliseconds(static_cast<int>(duration / 10000.0 - preroll + 0.5)); } -ByteVector ASF::File::StreamPropertiesObject::guid() +ByteVector ASF::File::FilePrivate::StreamPropertiesObject::guid() const { return streamPropertiesGuid; } -void ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size) +void ASF::File::FilePrivate::StreamPropertiesObject::parse(ASF::File *file, unsigned int size) { BaseObject::parse(file, size); - file->d->properties->setChannels(data.mid(56, 2).toShort(false)); - file->d->properties->setSampleRate(data.mid(58, 4).toUInt(false)); - file->d->properties->setBitrate(data.mid(62, 4).toUInt(false) * 8 / 1000); + if(data.size() < 70) { + debug("ASF::File::FilePrivate::StreamPropertiesObject::parse() -- data is too short."); + return; + } + + file->d->properties->setCodec(data.toUShort(54, false)); + file->d->properties->setChannels(data.toUShort(56, false)); + file->d->properties->setSampleRate(data.toUInt(58, false)); + file->d->properties->setBitrate(static_cast<int>(data.toUInt(62, false) * 8.0 / 1000.0 + 0.5)); + file->d->properties->setBitsPerSample(data.toUShort(68, false)); } -ByteVector ASF::File::ContentDescriptionObject::guid() +ByteVector ASF::File::FilePrivate::ContentDescriptionObject::guid() const { return contentDescriptionGuid; } -void ASF::File::ContentDescriptionObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::ContentDescriptionObject::parse(ASF::File *file, unsigned int /*size*/) { - file->d->contentDescriptionObject = this; - int titleLength = file->readWORD(); - int artistLength = file->readWORD(); - int copyrightLength = file->readWORD(); - int commentLength = file->readWORD(); - int ratingLength = file->readWORD(); - file->d->tag->setTitle(file->readString(titleLength)); - file->d->tag->setArtist(file->readString(artistLength)); - file->d->tag->setCopyright(file->readString(copyrightLength)); - file->d->tag->setComment(file->readString(commentLength)); - file->d->tag->setRating(file->readString(ratingLength)); + const int titleLength = readWORD(file); + const int artistLength = readWORD(file); + const int copyrightLength = readWORD(file); + const int commentLength = readWORD(file); + const int ratingLength = readWORD(file); + file->d->tag->setTitle(readString(file,titleLength)); + file->d->tag->setArtist(readString(file,artistLength)); + file->d->tag->setCopyright(readString(file,copyrightLength)); + file->d->tag->setComment(readString(file,commentLength)); + file->d->tag->setRating(readString(file,ratingLength)); } -ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::ContentDescriptionObject::render(ASF::File *file) { - ByteVector v1 = file->renderString(file->d->tag->title()); - ByteVector v2 = file->renderString(file->d->tag->artist()); - ByteVector v3 = file->renderString(file->d->tag->copyright()); - ByteVector v4 = file->renderString(file->d->tag->comment()); - ByteVector v5 = file->renderString(file->d->tag->rating()); + const ByteVector v1 = renderString(file->d->tag->title()); + const ByteVector v2 = renderString(file->d->tag->artist()); + const ByteVector v3 = renderString(file->d->tag->copyright()); + const ByteVector v4 = renderString(file->d->tag->comment()); + const ByteVector v5 = renderString(file->d->tag->rating()); data.clear(); data.append(ByteVector::fromShort(v1.size(), false)); data.append(ByteVector::fromShort(v2.size(), false)); @@ -234,15 +292,14 @@ ByteVector ASF::File::ContentDescriptionObject::render(ASF::File *file) return BaseObject::render(file); } -ByteVector ASF::File::ExtendedContentDescriptionObject::guid() +ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::guid() const { return extendedContentDescriptionGuid; } -void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::ExtendedContentDescriptionObject::parse(ASF::File *file, unsigned int /*size*/) { - file->d->extendedContentDescriptionObject = this; - int count = file->readWORD(); + int count = readWORD(file); while(count--) { ASF::Attribute attribute; String name = attribute.parse(*file); @@ -250,23 +307,22 @@ void ASF::File::ExtendedContentDescriptionObject::parse(ASF::File *file, uint /* } } -ByteVector ASF::File::ExtendedContentDescriptionObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::ExtendedContentDescriptionObject::render(ASF::File *file) { data.clear(); data.append(ByteVector::fromShort(attributeData.size(), false)); - data.append(attributeData.toByteVector(ByteVector::null)); + data.append(attributeData.toByteVector("")); return BaseObject::render(file); } -ByteVector ASF::File::MetadataObject::guid() +ByteVector ASF::File::FilePrivate::MetadataObject::guid() const { return metadataGuid; } -void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::MetadataObject::parse(ASF::File *file, unsigned int /*size*/) { - file->d->metadataObject = this; - int count = file->readWORD(); + int count = readWORD(file); while(count--) { ASF::Attribute attribute; String name = attribute.parse(*file, 1); @@ -274,23 +330,22 @@ void ASF::File::MetadataObject::parse(ASF::File *file, uint /*size*/) } } -ByteVector ASF::File::MetadataObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::MetadataObject::render(ASF::File *file) { data.clear(); data.append(ByteVector::fromShort(attributeData.size(), false)); - data.append(attributeData.toByteVector(ByteVector::null)); + data.append(attributeData.toByteVector("")); return BaseObject::render(file); } -ByteVector ASF::File::MetadataLibraryObject::guid() +ByteVector ASF::File::FilePrivate::MetadataLibraryObject::guid() const { return metadataLibraryGuid; } -void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::MetadataLibraryObject::parse(ASF::File *file, unsigned int /*size*/) { - file->d->metadataLibraryObject = this; - int count = file->readWORD(); + int count = readWORD(file); while(count--) { ASF::Attribute attribute; String name = attribute.parse(*file, 2); @@ -298,76 +353,158 @@ void ASF::File::MetadataLibraryObject::parse(ASF::File *file, uint /*size*/) } } -ByteVector ASF::File::MetadataLibraryObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::MetadataLibraryObject::render(ASF::File *file) { data.clear(); data.append(ByteVector::fromShort(attributeData.size(), false)); - data.append(attributeData.toByteVector(ByteVector::null)); + data.append(attributeData.toByteVector("")); return BaseObject::render(file); } -ByteVector ASF::File::HeaderExtensionObject::guid() +ASF::File::FilePrivate::HeaderExtensionObject::HeaderExtensionObject() +{ + objects.setAutoDelete(true); +} + +ByteVector ASF::File::FilePrivate::HeaderExtensionObject::guid() const { return headerExtensionGuid; } -void ASF::File::HeaderExtensionObject::parse(ASF::File *file, uint /*size*/) +void ASF::File::FilePrivate::HeaderExtensionObject::parse(ASF::File *file, unsigned int /*size*/) { - file->d->headerExtensionObject = this; file->seek(18, File::Current); - long long dataSize = file->readDWORD(); + long long dataSize = readDWORD(file); long long dataPos = 0; while(dataPos < dataSize) { ByteVector guid = file->readBlock(16); - long long size = file->readQWORD(); + if(guid.size() != 16) { + file->setValid(false); + break; + } + bool ok; + long long size = readQWORD(file, &ok); + if(!ok) { + file->setValid(false); + break; + } BaseObject *obj; if(guid == metadataGuid) { - obj = new MetadataObject(); + file->d->metadataObject = new MetadataObject(); + obj = file->d->metadataObject; } else if(guid == metadataLibraryGuid) { - obj = new MetadataLibraryObject(); + file->d->metadataLibraryObject = new MetadataLibraryObject(); + obj = file->d->metadataLibraryObject; } else { obj = new UnknownObject(guid); } - obj->parse(file, size); + obj->parse(file, (unsigned int)size); objects.append(obj); dataPos += size; } } -ByteVector ASF::File::HeaderExtensionObject::render(ASF::File *file) +ByteVector ASF::File::FilePrivate::HeaderExtensionObject::render(ASF::File *file) { data.clear(); - for(unsigned int i = 0; i < objects.size(); i++) { - data.append(objects[i]->render(file)); + for(List<BaseObject *>::ConstIterator it = objects.begin(); it != objects.end(); ++it) { + data.append((*it)->render(file)); } data = ByteVector("\x11\xD2\xD3\xAB\xBA\xA9\xcf\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65\x06\x00", 18) + ByteVector::fromUInt(data.size(), false) + data; return BaseObject::render(file); } +ByteVector ASF::File::FilePrivate::CodecListObject::guid() const +{ + return codecListGuid; +} + +void ASF::File::FilePrivate::CodecListObject::parse(ASF::File *file, unsigned int size) +{ + BaseObject::parse(file, size); + if(data.size() <= 20) { + debug("ASF::File::FilePrivate::CodecListObject::parse() -- data is too short."); + return; + } + + unsigned int pos = 16; + + const int count = data.toUInt(pos, false); + pos += 4; + + for(int i = 0; i < count; ++i) { + + if(pos >= data.size()) + break; + + const CodecType type = static_cast<CodecType>(data.toUShort(pos, false)); + pos += 2; + + int nameLength = data.toUShort(pos, false); + pos += 2; + + const unsigned int namePos = pos; + pos += nameLength * 2; + + const int descLength = data.toUShort(pos, false); + pos += 2; + + const unsigned int descPos = pos; + pos += descLength * 2; + + const int infoLength = data.toUShort(pos, false); + pos += 2 + infoLength * 2; + + if(type == CodecListObject::Audio) { + // First audio codec found. + + const String name(data.mid(namePos, nameLength * 2), String::UTF16LE); + file->d->properties->setCodecName(name.stripWhiteSpace()); + + const String desc(data.mid(descPos, descLength * 2), String::UTF16LE); + file->d->properties->setCodecDescription(desc.stripWhiteSpace()); + + break; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool ASF::File::isSupported(IOStream *stream) +{ + // An ASF file has to start with the designated GUID. + + const ByteVector id = Utils::readHeader(stream, 16, false); + return (id == headerGuid); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -ASF::File::File(FileName file, bool readProperties, Properties::ReadStyle propertiesStyle) - : TagLib::File(file) +ASF::File::File(FileName file, bool, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(); +} + +ASF::File::File(IOStream *stream, bool, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) +{ + if(isOpen()) + read(); } ASF::File::~File() { - for(unsigned int i = 0; i < d->objects.size(); i++) { - delete d->objects[i]; - } - if(d->tag) { - delete d->tag; - } - if(d->properties) { - delete d->properties; - } delete d; } @@ -376,98 +513,84 @@ ASF::Tag *ASF::File::tag() const return d->tag; } +PropertyMap ASF::File::properties() const +{ + return d->tag->properties(); +} + +void ASF::File::removeUnsupportedProperties(const StringList &properties) +{ + d->tag->removeUnsupportedProperties(properties); +} + +PropertyMap ASF::File::setProperties(const PropertyMap &properties) +{ + return d->tag->setProperties(properties); +} + ASF::Properties *ASF::File::audioProperties() const { return d->properties; } -void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*propertiesStyle*/) -{ - if(!isValid()) - return; - - ByteVector guid = readBlock(16); - if(guid != headerGuid) { - debug("ASF: Not an ASF file."); - return; - } - - d->tag = new ASF::Tag(); - d->properties = new ASF::Properties(); - - d->size = readQWORD(); - int numObjects = readDWORD(); - seek(2, Current); - - for(int i = 0; i < numObjects; i++) { - ByteVector guid = readBlock(16); - long size = (long)readQWORD(); - BaseObject *obj; - if(guid == filePropertiesGuid) { - obj = new FilePropertiesObject(); - } - else if(guid == streamPropertiesGuid) { - obj = new StreamPropertiesObject(); - } - else if(guid == contentDescriptionGuid) { - obj = new ContentDescriptionObject(); - } - else if(guid == extendedContentDescriptionGuid) { - obj = new ExtendedContentDescriptionObject(); - } - else if(guid == headerExtensionGuid) { - obj = new HeaderExtensionObject(); - } - else { - obj = new UnknownObject(guid); - } - obj->parse(this, size); - d->objects.append(obj); - } -} - bool ASF::File::save() { if(readOnly()) { - debug("ASF: File is read-only."); + debug("ASF::File::save() -- File is read only."); + return false; + } + + if(!isValid()) { + debug("ASF::File::save() -- Trying to save invalid file."); return false; } if(!d->contentDescriptionObject) { - d->contentDescriptionObject = new ContentDescriptionObject(); + d->contentDescriptionObject = new FilePrivate::ContentDescriptionObject(); d->objects.append(d->contentDescriptionObject); } if(!d->extendedContentDescriptionObject) { - d->extendedContentDescriptionObject = new ExtendedContentDescriptionObject(); + d->extendedContentDescriptionObject = new FilePrivate::ExtendedContentDescriptionObject(); d->objects.append(d->extendedContentDescriptionObject); } if(!d->headerExtensionObject) { - d->headerExtensionObject = new HeaderExtensionObject(); + d->headerExtensionObject = new FilePrivate::HeaderExtensionObject(); d->objects.append(d->headerExtensionObject); } if(!d->metadataObject) { - d->metadataObject = new MetadataObject(); + d->metadataObject = new FilePrivate::MetadataObject(); d->headerExtensionObject->objects.append(d->metadataObject); } if(!d->metadataLibraryObject) { - d->metadataLibraryObject = new MetadataLibraryObject(); + d->metadataLibraryObject = new FilePrivate::MetadataLibraryObject(); d->headerExtensionObject->objects.append(d->metadataLibraryObject); } - ASF::AttributeListMap::ConstIterator it = d->tag->attributeListMap().begin(); - for(; it != d->tag->attributeListMap().end(); it++) { + d->extendedContentDescriptionObject->attributeData.clear(); + d->metadataObject->attributeData.clear(); + d->metadataLibraryObject->attributeData.clear(); + + const AttributeListMap allAttributes = d->tag->attributeListMap(); + + for(AttributeListMap::ConstIterator it = allAttributes.begin(); it != allAttributes.end(); ++it) { + const String &name = it->first; const AttributeList &attributes = it->second; + bool inExtendedContentDescriptionObject = false; bool inMetadataObject = false; - for(unsigned int j = 0; j < attributes.size(); j++) { - const Attribute &attribute = attributes[j]; - bool largeValue = attribute.dataSize() > 65535; - if(!inExtendedContentDescriptionObject && !largeValue && attribute.language() == 0 && attribute.stream() == 0) { + + for(AttributeList::ConstIterator jt = attributes.begin(); jt != attributes.end(); ++jt) { + + const Attribute &attribute = *jt; + const bool largeValue = (attribute.dataSize() > 65535); + const bool guid = (attribute.type() == Attribute::GuidType); + + if(!inExtendedContentDescriptionObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() == 0) { d->extendedContentDescriptionObject->attributeData.append(attribute.render(name)); inExtendedContentDescriptionObject = true; } - else if(!inMetadataObject && !largeValue && attribute.language() == 0 && attribute.stream() != 0) { + else if(!inMetadataObject && !guid && !largeValue && attribute.language() == 0 && attribute.stream() != 0) { d->metadataObject->attributeData.append(attribute.render(name, 1)); inMetadataObject = true; } @@ -478,66 +601,105 @@ bool ASF::File::save() } ByteVector data; - for(unsigned int i = 0; i < d->objects.size(); i++) { - data.append(d->objects[i]->render(this)); + for(List<FilePrivate::BaseObject *>::ConstIterator it = d->objects.begin(); it != d->objects.end(); ++it) { + data.append((*it)->render(this)); } - data = headerGuid + ByteVector::fromLongLong(data.size() + 30, false) + ByteVector::fromUInt(d->objects.size(), false) + ByteVector("\x01\x02", 2) + data; - insert(data, 0, d->size); + + seek(16); + writeBlock(ByteVector::fromLongLong(data.size() + 30, false)); + writeBlock(ByteVector::fromUInt(d->objects.size(), false)); + writeBlock(ByteVector("\x01\x02", 2)); + + insert(data, 30, static_cast<unsigned long>(d->headerSize - 30)); + + d->headerSize = data.size() + 30; return true; } //////////////////////////////////////////////////////////////////////////////// -// protected members +// private members //////////////////////////////////////////////////////////////////////////////// -int ASF::File::readBYTE() +void ASF::File::read() { - ByteVector v = readBlock(1); - return v[0]; -} + if(!isValid()) + return; -int ASF::File::readWORD() -{ - ByteVector v = readBlock(2); - return v.toUShort(false); -} + if(readBlock(16) != headerGuid) { + debug("ASF::File::read(): Not an ASF file."); + setValid(false); + return; + } -unsigned int ASF::File::readDWORD() -{ - ByteVector v = readBlock(4); - return v.toUInt(false); -} + d->tag = new ASF::Tag(); + d->properties = new ASF::Properties(); -long long ASF::File::readQWORD() -{ - ByteVector v = readBlock(8); - return v.toLongLong(false); -} + bool ok; + d->headerSize = readQWORD(this, &ok); + if(!ok) { + setValid(false); + return; + } + int numObjects = readDWORD(this, &ok); + if(!ok) { + setValid(false); + return; + } + seek(2, Current); -String ASF::File::readString(int length) -{ - ByteVector data = readBlock(length); - unsigned int size = data.size(); - while (size >= 2) { - if(data[size - 1] != '\0' || data[size - 2] != '\0') { + FilePrivate::FilePropertiesObject *filePropertiesObject = 0; + FilePrivate::StreamPropertiesObject *streamPropertiesObject = 0; + for(int i = 0; i < numObjects; i++) { + const ByteVector guid = readBlock(16); + if(guid.size() != 16) { + setValid(false); break; } - size -= 2; + long size = (long)readQWORD(this, &ok); + if(!ok) { + setValid(false); + break; + } + FilePrivate::BaseObject *obj; + if(guid == filePropertiesGuid) { + filePropertiesObject = new FilePrivate::FilePropertiesObject(); + obj = filePropertiesObject; + } + else if(guid == streamPropertiesGuid) { + streamPropertiesObject = new FilePrivate::StreamPropertiesObject(); + obj = streamPropertiesObject; + } + else if(guid == contentDescriptionGuid) { + d->contentDescriptionObject = new FilePrivate::ContentDescriptionObject(); + obj = d->contentDescriptionObject; + } + else if(guid == extendedContentDescriptionGuid) { + d->extendedContentDescriptionObject = new FilePrivate::ExtendedContentDescriptionObject(); + obj = d->extendedContentDescriptionObject; + } + else if(guid == headerExtensionGuid) { + d->headerExtensionObject = new FilePrivate::HeaderExtensionObject(); + obj = d->headerExtensionObject; + } + else if(guid == codecListGuid) { + obj = new FilePrivate::CodecListObject(); + } + else { + if(guid == contentEncryptionGuid || + guid == extendedContentEncryptionGuid || + guid == advancedContentEncryptionGuid) { + d->properties->setEncrypted(true); + } + obj = new FilePrivate::UnknownObject(guid); + } + obj->parse(this, size); + d->objects.append(obj); } - if(size != data.size()) { - data.resize(size); - } - return String(data, String::UTF16LE); -} -ByteVector ASF::File::renderString(const String &str, bool includeLength) -{ - ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false); - if(includeLength) { - data = ByteVector::fromShort(data.size(), false) + data; + if(!filePropertiesObject || !streamPropertiesObject) { + debug("ASF::File::read(): Missing mandatory header objects."); + setValid(false); + return; } - return data; } - -#endif diff --git a/Frameworks/TagLib/taglib/taglib/asf/asffile.h b/Frameworks/TagLib/taglib/taglib/asf/asffile.h index 9242aa68d..05cf4ee2a 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asffile.h +++ b/Frameworks/TagLib/taglib/taglib/asf/asffile.h @@ -48,14 +48,27 @@ namespace TagLib { public: /*! - * Contructs an ASF file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs an ASF file from \a file. * * \note In the current implementation, both \a readProperties and - * \a propertiesStyle are ignored. + * \a propertiesStyle are ignored. The audio properties are always + * read. */ - File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + File(FileName file, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Constructs an ASF file from \a stream. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); /*! * Destroys this instance of the File. @@ -74,6 +87,22 @@ namespace TagLib { */ virtual Tag *tag() const; + /*! + * Implements the unified property interface -- export function. + */ + PropertyMap properties() const; + + /*! + * Removes unsupported properties. Forwards to the actual Tag's + * removeUnsupportedProperties() function. + */ + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the ASF audio properties for this file. */ @@ -86,28 +115,17 @@ namespace TagLib { */ virtual bool save(); + /*! + * Returns whether or not the given \a stream can be opened as an ASF + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: - - int readBYTE(); - int readWORD(); - unsigned int readDWORD(); - long long readQWORD(); - static ByteVector renderString(const String &str, bool includeLength = false); - String readString(int len); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); - - friend class Attribute; - friend class Picture; - - class BaseObject; - class UnknownObject; - class FilePropertiesObject; - class StreamPropertiesObject; - class ContentDescriptionObject; - class ExtendedContentDescriptionObject; - class HeaderExtensionObject; - class MetadataObject; - class MetadataLibraryObject; + void read(); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/asf/asfpicture.cpp b/Frameworks/TagLib/taglib/taglib/asf/asfpicture.cpp index 551229b53..7039fb15f 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asfpicture.cpp +++ b/Frameworks/TagLib/taglib/taglib/asf/asfpicture.cpp @@ -23,21 +23,18 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WITH_ASF - #include <taglib.h> #include <tdebug.h> +#include <trefcounter.h> + #include "asfattribute.h" #include "asffile.h" #include "asfpicture.h" +#include "asfutils.h" using namespace TagLib; -class ASF::Picture::PicturePriavte : public RefCounter +class ASF::Picture::PicturePrivate : public RefCounter { public: bool valid; @@ -51,14 +48,14 @@ public: // Picture class members //////////////////////////////////////////////////////////////////////////////// -ASF::Picture::Picture() +ASF::Picture::Picture() : + d(new PicturePrivate()) { - d = new PicturePriavte(); d->valid = true; } -ASF::Picture::Picture(const Picture& other) - : d(other.d) +ASF::Picture::Picture(const Picture& other) : + d(other.d) { d->ref(); } @@ -123,24 +120,27 @@ int ASF::Picture::dataSize() const ASF::Picture& ASF::Picture::operator=(const ASF::Picture& other) { - if(other.d != d) { - if(d->deref()) - delete d; - d = other.d; - d->ref(); - } + Picture(other).swap(*this); return *this; } +void ASF::Picture::swap(Picture &other) +{ + using std::swap; + + swap(d, other.d); +} + ByteVector ASF::Picture::render() const { if(!isValid()) - return ByteVector::null; + return ByteVector(); + return ByteVector((char)d->type) + ByteVector::fromUInt(d->picture.size(), false) + - ASF::File::renderString(d->mimeType) + - ASF::File::renderString(d->description) + + renderString(d->mimeType) + + renderString(d->description) + d->picture; } @@ -151,7 +151,7 @@ void ASF::Picture::parse(const ByteVector& bytes) return; int pos = 0; d->type = (Type)bytes[0]; ++pos; - uint dataLen = bytes.mid(pos, 4).toUInt(false); pos+=4; + const unsigned int dataLen = bytes.toUInt(pos, false); pos+=4; const ByteVector nullStringTerminator(2, 0); @@ -181,5 +181,3 @@ ASF::Picture ASF::Picture::fromInvalid() ret.d->valid = false; return ret; } - -#endif diff --git a/Frameworks/TagLib/taglib/taglib/asf/asfpicture.h b/Frameworks/TagLib/taglib/taglib/asf/asfpicture.h index 5c1bfbfdb..17233ba90 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asfpicture.h +++ b/Frameworks/TagLib/taglib/taglib/asf/asfpicture.h @@ -117,6 +117,11 @@ namespace TagLib */ Picture& operator=(const Picture& other); + /*! + * Exchanges the content of the Picture by the content of \a other. + */ + void swap(Picture &other); + /*! * Returns true if Picture stores valid picture */ @@ -205,11 +210,11 @@ namespace TagLib /* THIS IS PRIVATE, DON'T TOUCH IT! */ void parse(const ByteVector& ); static Picture fromInvalid(); - friend class Attribute; #endif + private: - struct PicturePriavte; - PicturePriavte *d; + class PicturePrivate; + PicturePrivate *d; }; } } diff --git a/Frameworks/TagLib/taglib/taglib/asf/asfproperties.cpp b/Frameworks/TagLib/taglib/taglib/asf/asfproperties.cpp index 02d2d942a..a836da30b 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asfproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/asf/asfproperties.cpp @@ -23,12 +23,6 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WITH_ASF - #include <tdebug.h> #include <tstring.h> #include "asfproperties.h" @@ -38,29 +32,52 @@ using namespace TagLib; class ASF::Properties::PropertiesPrivate { public: - PropertiesPrivate(): length(0), bitrate(0), sampleRate(0), channels(0) {} + PropertiesPrivate() : + length(0), + bitrate(0), + sampleRate(0), + channels(0), + bitsPerSample(0), + codec(ASF::Properties::Unknown), + encrypted(false) {} + int length; int bitrate; int sampleRate; int channels; + int bitsPerSample; + ASF::Properties::Codec codec; + String codecName; + String codecDescription; + bool encrypted; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -ASF::Properties::Properties() : AudioProperties(AudioProperties::Average) +ASF::Properties::Properties() : + AudioProperties(AudioProperties::Average), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate; } ASF::Properties::~Properties() { - if(d) - delete d; + delete d; } int ASF::Properties::length() const +{ + return lengthInSeconds(); +} + +int ASF::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int ASF::Properties::lengthInMilliseconds() const { return d->length; } @@ -78,30 +95,100 @@ int ASF::Properties::sampleRate() const int ASF::Properties::channels() const { return d->channels; -} +} + +int ASF::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + +ASF::Properties::Codec ASF::Properties::codec() const +{ + return d->codec; +} + +String ASF::Properties::codecName() const +{ + return d->codecName; +} + +String ASF::Properties::codecDescription() const +{ + return d->codecDescription; +} + +bool ASF::Properties::isEncrypted() const +{ + return d->encrypted; +} //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void ASF::Properties::setLength(int length) +void ASF::Properties::setLength(int /*length*/) { - d->length = length; + debug("ASF::Properties::setLength() -- This method is deprecated. Do not use."); } -void ASF::Properties::setBitrate(int length) +void ASF::Properties::setLengthInMilliseconds(int value) { - d->bitrate = length; + d->length = value; } -void ASF::Properties::setSampleRate(int length) +void ASF::Properties::setBitrate(int value) { - d->sampleRate = length; + d->bitrate = value; } -void ASF::Properties::setChannels(int length) +void ASF::Properties::setSampleRate(int value) { - d->channels = length; + d->sampleRate = value; } -#endif +void ASF::Properties::setChannels(int value) +{ + d->channels = value; +} + +void ASF::Properties::setBitsPerSample(int value) +{ + d->bitsPerSample = value; +} + +void ASF::Properties::setCodec(int value) +{ + switch(value) + { + case 0x0160: + d->codec = WMA1; + break; + case 0x0161: + d->codec = WMA2; + break; + case 0x0162: + d->codec = WMA9Pro; + break; + case 0x0163: + d->codec = WMA9Lossless; + break; + default: + d->codec = Unknown; + break; + } +} + +void ASF::Properties::setCodecName(const String &value) +{ + d->codecName = value; +} + +void ASF::Properties::setCodecDescription(const String &value) +{ + d->codecDescription = value; +} + +void ASF::Properties::setEncrypted(bool value) +{ + d->encrypted = value; +} diff --git a/Frameworks/TagLib/taglib/taglib/asf/asfproperties.h b/Frameworks/TagLib/taglib/taglib/asf/asfproperties.h index 290eac772..317bf104f 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asfproperties.h +++ b/Frameworks/TagLib/taglib/taglib/asf/asfproperties.h @@ -40,7 +40,38 @@ namespace TagLib { public: /*! - * Create an instance of ASF::Properties. + * Audio codec types can be used in ASF file. + */ + enum Codec + { + /*! + * Couldn't detect the codec. + */ + Unknown = 0, + + /*! + * Windows Media Audio 1 + */ + WMA1, + + /*! + * Windows Media Audio 2 or above + */ + WMA2, + + /*! + * Windows Media Audio 9 Professional + */ + WMA9Pro, + + /*! + * Windows Media Audio 9 Lossless + */ + WMA9Lossless, + }; + + /*! + * Creates an instance of ASF::Properties. */ Properties(); @@ -49,17 +80,98 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - virtual int length() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + /*! + * Returns the number of bits per audio sample. + */ + int bitsPerSample() const; + + /*! + * Returns the codec used in the file. + * + * \see codecName() + * \see codecDescription() + */ + Codec codec() const; + + /*! + * Returns the concrete codec name, for example "Windows Media Audio 9.1" + * used in the file if available, otherwise an empty string. + * + * \see codec() + * \see codecDescription() + */ + String codecName() const; + + /*! + * Returns the codec description, typically contains the encoder settings, + * for example "VBR Quality 50, 44kHz, stereo 1-pass VBR" if available, + * otherwise an empty string. + * + * \see codec() + * \see codecName() + */ + String codecDescription() const; + + /*! + * Returns whether or not the file is encrypted. + */ + bool isEncrypted() const; + #ifndef DO_NOT_DOCUMENT + // deprecated void setLength(int value); + + void setLengthInMilliseconds(int value); void setBitrate(int value); void setSampleRate(int value); void setChannels(int value); + void setBitsPerSample(int value); + void setCodec(int value); + void setCodecName(const String &value); + void setCodecDescription(const String &value); + void setEncrypted(bool value); #endif private: @@ -71,4 +183,4 @@ namespace TagLib { } -#endif +#endif diff --git a/Frameworks/TagLib/taglib/taglib/asf/asftag.cpp b/Frameworks/TagLib/taglib/taglib/asf/asftag.cpp index 6c6e96794..935db046c 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asftag.cpp +++ b/Frameworks/TagLib/taglib/taglib/asf/asftag.cpp @@ -23,12 +23,7 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WITH_ASF - +#include <tpropertymap.h> #include "asftag.h" using namespace TagLib; @@ -44,16 +39,15 @@ public: AttributeListMap attributeListMap; }; -ASF::Tag::Tag() -: TagLib::Tag() +ASF::Tag::Tag() : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; } ASF::Tag::~Tag() { - if(d) - delete d; + delete d; } String ASF::Tag::title() const @@ -70,7 +64,7 @@ String ASF::Tag::album() const { if(d->attributeListMap.contains("WM/AlbumTitle")) return d->attributeListMap["WM/AlbumTitle"][0].toString(); - return String::null; + return String(); } String ASF::Tag::copyright() const @@ -113,31 +107,7 @@ String ASF::Tag::genre() const { if(d->attributeListMap.contains("WM/Genre")) return d->attributeListMap["WM/Genre"][0].toString(); - return String::null; -} - -float -ASF::Tag::rgAlbumGain() const -{ - return 0; -} - -float -ASF::Tag::rgAlbumPeak() const -{ - return 0; -} - -float -ASF::Tag::rgTrackGain() const -{ - return 0; -} - -float -ASF::Tag::rgTrackPeak() const -{ - return 0; + return String(); } void ASF::Tag::setTitle(const String &value) @@ -175,46 +145,39 @@ void ASF::Tag::setGenre(const String &value) setAttribute("WM/Genre", value); } -void ASF::Tag::setYear(uint value) +void ASF::Tag::setYear(unsigned int value) { setAttribute("WM/Year", String::number(value)); } -void ASF::Tag::setTrack(uint value) +void ASF::Tag::setTrack(unsigned int value) { setAttribute("WM/TrackNumber", String::number(value)); } -void -ASF::Tag::setRGAlbumGain(float) -{ -} - -void -ASF::Tag::setRGAlbumPeak(float) -{ -} - -void -ASF::Tag::setRGTrackGain(float) -{ -} - -void -ASF::Tag::setRGTrackPeak(float) -{ -} - ASF::AttributeListMap& ASF::Tag::attributeListMap() { return d->attributeListMap; } +const ASF::AttributeListMap &ASF::Tag::attributeListMap() const +{ + return d->attributeListMap; +} + +bool ASF::Tag::contains(const String &key) const +{ + return d->attributeListMap.contains(key); +} + void ASF::Tag::removeItem(const String &key) { - AttributeListMap::Iterator it = d->attributeListMap.find(key); - if(it != d->attributeListMap.end()) - d->attributeListMap.erase(it); + d->attributeListMap.erase(key); +} + +ASF::AttributeList ASF::Tag::attribute(const String &name) const +{ + return d->attributeListMap[name]; } void ASF::Tag::setAttribute(const String &name, const Attribute &attribute) @@ -224,6 +187,11 @@ void ASF::Tag::setAttribute(const String &name, const Attribute &attribute) d->attributeListMap.insert(name, value); } +void ASF::Tag::setAttribute(const String &name, const AttributeList &values) +{ + d->attributeListMap.insert(name, values); +} + void ASF::Tag::addAttribute(const String &name, const Attribute &attribute) { if(d->attributeListMap.contains(name)) { @@ -242,4 +210,175 @@ bool ASF::Tag::isEmpty() const d->attributeListMap.isEmpty(); } -#endif +namespace +{ + const char *keyTranslation[][2] = { + { "WM/AlbumTitle", "ALBUM" }, + { "WM/AlbumArtist", "ALBUMARTIST" }, + { "WM/Composer", "COMPOSER" }, + { "WM/Writer", "LYRICIST" }, + { "WM/Conductor", "CONDUCTOR" }, + { "WM/ModifiedBy", "REMIXER" }, + { "WM/Year", "DATE" }, + { "WM/OriginalReleaseYear", "ORIGINALDATE" }, + { "WM/Producer", "PRODUCER" }, + { "WM/ContentGroupDescription", "GROUPING" }, + { "WM/SubTitle", "SUBTITLE" }, + { "WM/SetSubTitle", "DISCSUBTITLE" }, + { "WM/TrackNumber", "TRACKNUMBER" }, + { "WM/PartOfSet", "DISCNUMBER" }, + { "WM/Genre", "GENRE" }, + { "WM/BeatsPerMinute", "BPM" }, + { "WM/Mood", "MOOD" }, + { "WM/ISRC", "ISRC" }, + { "WM/Lyrics", "LYRICS" }, + { "WM/Media", "MEDIA" }, + { "WM/Publisher", "LABEL" }, + { "WM/CatalogNo", "CATALOGNUMBER" }, + { "WM/Barcode", "BARCODE" }, + { "WM/EncodedBy", "ENCODEDBY" }, + { "WM/AlbumSortOrder", "ALBUMSORT" }, + { "WM/AlbumArtistSortOrder", "ALBUMARTISTSORT" }, + { "WM/ArtistSortOrder", "ARTISTSORT" }, + { "WM/TitleSortOrder", "TITLESORT" }, + { "WM/Script", "SCRIPT" }, + { "WM/Language", "LANGUAGE" }, + { "WM/ARTISTS", "ARTISTS" }, + { "ASIN", "ASIN" }, + { "MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID" }, + { "MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID" }, + { "MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID" }, + { "MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, + { "MusicBrainz/Album Release Country", "RELEASECOUNTRY" }, + { "MusicBrainz/Album Status", "RELEASESTATUS" }, + { "MusicBrainz/Album Type", "RELEASETYPE" }, + { "MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "MusicBrainz/Release Track Id", "MUSICBRAINZ_RELEASETRACKID" }, + { "MusicBrainz/Work Id", "MUSICBRAINZ_WORKID" }, + { "MusicIP/PUID", "MUSICIP_PUID" }, + { "Acoustid/Id", "ACOUSTID_ID" }, + { "Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT" }, + }; + const size_t keyTranslationSize = sizeof(keyTranslation) / sizeof(keyTranslation[0]); + + String translateKey(const String &key) + { + for(size_t i = 0; i < keyTranslationSize; ++i) { + if(key == keyTranslation[i][0]) + return keyTranslation[i][1]; + } + + return String(); + } +} + +PropertyMap ASF::Tag::properties() const +{ + PropertyMap props; + + if(!d->title.isEmpty()) { + props["TITLE"] = d->title; + } + if(!d->artist.isEmpty()) { + props["ARTIST"] = d->artist; + } + if(!d->copyright.isEmpty()) { + props["COPYRIGHT"] = d->copyright; + } + if(!d->comment.isEmpty()) { + props["COMMENT"] = d->comment; + } + + ASF::AttributeListMap::ConstIterator it = d->attributeListMap.begin(); + for(; it != d->attributeListMap.end(); ++it) { + const String key = translateKey(it->first); + if(!key.isEmpty()) { + AttributeList::ConstIterator it2 = it->second.begin(); + for(; it2 != it->second.end(); ++it2) { + if(key == "TRACKNUMBER") { + if(it2->type() == ASF::Attribute::DWordType) + props.insert(key, String::number(it2->toUInt())); + else + props.insert(key, it2->toString()); + } + else { + props.insert(key, it2->toString()); + } + } + } + else { + props.unsupportedData().append(it->first); + } + } + return props; +} + +void ASF::Tag::removeUnsupportedProperties(const StringList &props) +{ + StringList::ConstIterator it = props.begin(); + for(; it != props.end(); ++it) + d->attributeListMap.erase(*it); +} + +PropertyMap ASF::Tag::setProperties(const PropertyMap &props) +{ + static Map<String, String> reverseKeyMap; + if(reverseKeyMap.isEmpty()) { + int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]); + for(int i = 0; i < numKeys; i++) { + reverseKeyMap[keyTranslation[i][1]] = keyTranslation[i][0]; + } + } + + PropertyMap origProps = properties(); + PropertyMap::ConstIterator it = origProps.begin(); + for(; it != origProps.end(); ++it) { + if(!props.contains(it->first) || props[it->first].isEmpty()) { + if(it->first == "TITLE") { + d->title.clear(); + } + else if(it->first == "ARTIST") { + d->artist.clear(); + } + else if(it->first == "COMMENT") { + d->comment.clear(); + } + else if(it->first == "COPYRIGHT") { + d->copyright.clear(); + } + else { + d->attributeListMap.erase(reverseKeyMap[it->first]); + } + } + } + + PropertyMap ignoredProps; + it = props.begin(); + for(; it != props.end(); ++it) { + if(reverseKeyMap.contains(it->first)) { + String name = reverseKeyMap[it->first]; + removeItem(name); + StringList::ConstIterator it2 = it->second.begin(); + for(; it2 != it->second.end(); ++it2) { + addAttribute(name, *it2); + } + } + else if(it->first == "TITLE") { + d->title = it->second.toString(); + } + else if(it->first == "ARTIST") { + d->artist = it->second.toString(); + } + else if(it->first == "COMMENT") { + d->comment = it->second.toString(); + } + else if(it->first == "COPYRIGHT") { + d->copyright = it->second.toString(); + } + else { + ignoredProps.insert(it->first, it->second); + } + } + + return ignoredProps; +} diff --git a/Frameworks/TagLib/taglib/taglib/asf/asftag.h b/Frameworks/TagLib/taglib/taglib/asf/asftag.h index 69820cbcc..bbd98212f 100644 --- a/Frameworks/TagLib/taglib/taglib/asf/asftag.h +++ b/Frameworks/TagLib/taglib/taglib/asf/asftag.h @@ -90,18 +90,13 @@ namespace TagLib { /*! * Returns the year; if there is no year set, this will return 0. */ - virtual uint year() const; + virtual unsigned int year() const; /*! * Returns the track number; if there is no track number set, this will * return 0. */ - virtual uint track() const; - - virtual float rgAlbumGain() const; - virtual float rgAlbumPeak() const; - virtual float rgTrackGain() const; - virtual float rgTrackPeak() const; + virtual unsigned int track() const; /*! * Sets the title to \a s. @@ -125,34 +120,29 @@ namespace TagLib { virtual void setComment(const String &s); /*! - * Sets the rating to \a s. + * Sets the rating to \a s. */ virtual void setRating(const String &s); /*! - * Sets the copyright to \a s. + * Sets the copyright to \a s. */ virtual void setCopyright(const String &s); /*! - * Sets the genre to \a s. + * Sets the genre to \a s. */ virtual void setGenre(const String &s); /*! * Sets the year to \a i. If \a s is 0 then this value will be cleared. */ - virtual void setYear(uint i); + virtual void setYear(unsigned int i); /*! * Sets the track to \a i. If \a s is 0 then this value will be cleared. */ - virtual void setTrack(uint i); - - virtual void setRGAlbumGain(float f); - virtual void setRGAlbumPeak(float f); - virtual void setRGTrackGain(float f); - virtual void setRGTrackPeak(float f); + virtual void setTrack(unsigned int i); /*! * Returns true if the tag does not contain any data. This should be @@ -162,30 +152,54 @@ namespace TagLib { virtual bool isEmpty() const; /*! - * Returns a reference to the item list map. This is an AttributeListMap of - * all of the items in the tag. - * - * This is the most powerfull structure for accessing the items of the tag. + * \deprecated */ AttributeListMap &attributeListMap(); + /*! + * Returns a reference to the item list map. This is an AttributeListMap of + * all of the items in the tag. + */ + // BIC: return by value + const AttributeListMap &attributeListMap() const; + + /*! + * \return True if a value for \a attribute is currently set. + */ + bool contains(const String &name) const; + /*! * Removes the \a key attribute from the tag */ void removeItem(const String &name); + /*! + * \return The list of values for the key \a name, or an empty list if no + * values have been set. + */ + AttributeList attribute(const String &name) const; + /*! * Sets the \a key attribute to the value of \a attribute. If an attribute * with the \a key is already present, it will be replaced. */ void setAttribute(const String &name, const Attribute &attribute); + /*! + * Sets multiple \a values to the key \a name. + */ + void setAttribute(const String &name, const AttributeList &values); + /*! * Sets the \a key attribute to the value of \a attribute. If an attribute * with the \a key is already present, it will be added to the list. */ void addAttribute(const String &name, const Attribute &attribute); + PropertyMap properties() const; + void removeUnsupportedProperties(const StringList& properties); + PropertyMap setProperties(const PropertyMap &properties); + private: class TagPrivate; diff --git a/Frameworks/TagLib/taglib/taglib/asf/asfutils.h b/Frameworks/TagLib/taglib/taglib/asf/asfutils.h new file mode 100644 index 000000000..a8ecd70dc --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/asf/asfutils.h @@ -0,0 +1,104 @@ +/*************************************************************************** + copyright : (C) 2015 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_ASFUTILS_H +#define TAGLIB_ASFUTILS_H + +// THIS FILE IS NOT A PART OF THE TAGLIB API + +#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header + +namespace TagLib +{ + namespace ASF + { + namespace + { + + inline unsigned short readWORD(File *file, bool *ok = 0) + { + const ByteVector v = file->readBlock(2); + if(v.size() != 2) { + if(ok) *ok = false; + return 0; + } + if(ok) *ok = true; + return v.toUShort(false); + } + + inline unsigned int readDWORD(File *file, bool *ok = 0) + { + const ByteVector v = file->readBlock(4); + if(v.size() != 4) { + if(ok) *ok = false; + return 0; + } + if(ok) *ok = true; + return v.toUInt(false); + } + + inline long long readQWORD(File *file, bool *ok = 0) + { + const ByteVector v = file->readBlock(8); + if(v.size() != 8) { + if(ok) *ok = false; + return 0; + } + if(ok) *ok = true; + return v.toLongLong(false); + } + + inline String readString(File *file, int length) + { + ByteVector data = file->readBlock(length); + unsigned int size = data.size(); + while (size >= 2) { + if(data[size - 1] != '\0' || data[size - 2] != '\0') { + break; + } + size -= 2; + } + if(size != data.size()) { + data.resize(size); + } + return String(data, String::UTF16LE); + } + + inline ByteVector renderString(const String &str, bool includeLength = false) + { + ByteVector data = str.data(String::UTF16LE) + ByteVector::fromShort(0, false); + if(includeLength) { + data = ByteVector::fromShort(data.size(), false) + data; + } + return data; + } + + } + } +} + +#endif + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/audioproperties.cpp b/Frameworks/TagLib/taglib/taglib/audioproperties.cpp index 298b97da1..c29051a64 100644 --- a/Frameworks/TagLib/taglib/taglib/audioproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/audioproperties.cpp @@ -23,10 +23,59 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include <tbytevector.h> + +#include "aiffproperties.h" +#include "apeproperties.h" +#include "asfproperties.h" +#include "flacproperties.h" +#include "mp4properties.h" +#include "mpcproperties.h" +#include "mpegproperties.h" +#include "opusproperties.h" +#include "speexproperties.h" +#include "trueaudioproperties.h" +#include "vorbisproperties.h" +#include "wavproperties.h" +#include "wavpackproperties.h" + #include "audioproperties.h" using namespace TagLib; +// This macro is a workaround for the fact that we can't add virtual functions. +// Should be true virtual functions in taglib2. + +#define VIRTUAL_FUNCTION_WORKAROUND(function_name, default_value) \ + if(dynamic_cast<const APE::Properties*>(this)) \ + return dynamic_cast<const APE::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const ASF::Properties*>(this)) \ + return dynamic_cast<const ASF::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const FLAC::Properties*>(this)) \ + return dynamic_cast<const FLAC::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const MP4::Properties*>(this)) \ + return dynamic_cast<const MP4::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const MPC::Properties*>(this)) \ + return dynamic_cast<const MPC::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const MPEG::Properties*>(this)) \ + return dynamic_cast<const MPEG::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const Ogg::Opus::Properties*>(this)) \ + return dynamic_cast<const Ogg::Opus::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const Ogg::Speex::Properties*>(this)) \ + return dynamic_cast<const Ogg::Speex::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const TrueAudio::Properties*>(this)) \ + return dynamic_cast<const TrueAudio::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const RIFF::AIFF::Properties*>(this)) \ + return dynamic_cast<const RIFF::AIFF::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const RIFF::WAV::Properties*>(this)) \ + return dynamic_cast<const RIFF::WAV::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const Vorbis::Properties*>(this)) \ + return dynamic_cast<const Vorbis::Properties*>(this)->function_name(); \ + else if(dynamic_cast<const WavPack::Properties*>(this)) \ + return dynamic_cast<const WavPack::Properties*>(this)->function_name(); \ + else \ + return (default_value); + class AudioProperties::AudioPropertiesPrivate { @@ -41,11 +90,22 @@ AudioProperties::~AudioProperties() } +int AudioProperties::lengthInSeconds() const +{ + VIRTUAL_FUNCTION_WORKAROUND(lengthInSeconds, 0) +} + +int AudioProperties::lengthInMilliseconds() const +{ + VIRTUAL_FUNCTION_WORKAROUND(lengthInMilliseconds, 0) +} + //////////////////////////////////////////////////////////////////////////////// // protected methods //////////////////////////////////////////////////////////////////////////////// -AudioProperties::AudioProperties(ReadStyle) +AudioProperties::AudioProperties(ReadStyle) : + d(0) { } diff --git a/Frameworks/TagLib/taglib/taglib/audioproperties.h b/Frameworks/TagLib/taglib/taglib/audioproperties.h index e9844fa0c..f27aefca8 100644 --- a/Frameworks/TagLib/taglib/taglib/audioproperties.h +++ b/Frameworks/TagLib/taglib/taglib/audioproperties.h @@ -34,7 +34,7 @@ namespace TagLib { /*! * The values here are common to most audio formats. For more specific, codec - * dependant values, please see see the subclasses APIs. This is meant to + * dependent values, please see see the subclasses APIs. This is meant to * compliment the TagLib::File and TagLib::Tag APIs in providing a simple * interface that is sufficient for most applications. */ @@ -69,6 +69,23 @@ namespace TagLib { */ virtual int length() const = 0; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + /*! * Returns the most appropriate bit rate for the file in kb/s. For constant * bitrate formats this is simply the bitrate of the file. For variable diff --git a/Frameworks/TagLib/taglib/taglib/fileref.cpp b/Frameworks/TagLib/taglib/taglib/fileref.cpp index 11e5a331c..0f392ec73 100644 --- a/Frameworks/TagLib/taglib/taglib/fileref.cpp +++ b/Frameworks/TagLib/taglib/taglib/fileref.cpp @@ -1,7 +1,7 @@ /*************************************************************************** copyright : (C) 2002 - 2008 by Scott Wheeler email : wheeler@kde.org - + copyright : (C) 2010 by Alex Novichkov email : novichko@atnet.ru (added APE file support) @@ -27,13 +27,13 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif +#include <cstring> #include <tfile.h> +#include <tfilestream.h> #include <tstring.h> #include <tdebug.h> +#include <trefcounter.h> #include "fileref.h" #include "asffile.h" @@ -50,44 +50,267 @@ #include "aifffile.h" #include "wavfile.h" #include "apefile.h" +#include "modfile.h" +#include "s3mfile.h" +#include "itfile.h" +#include "xmfile.h" using namespace TagLib; +namespace +{ + typedef List<const FileRef::FileTypeResolver *> ResolverList; + ResolverList fileTypeResolvers; + + // Detect the file type by user-defined resolvers. + + File *detectByResolvers(FileName fileName, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) + { + if(::strlen(fileName) == 0) + return 0; + ResolverList::ConstIterator it = fileTypeResolvers.begin(); + for(; it != fileTypeResolvers.end(); ++it) { + File *file = (*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle); + if(file) + return file; + } + + return 0; + } + + // Detect the file type based on the file extension. + + File* detectByExtension(IOStream *stream, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) + { +#ifdef _WIN32 + const String s = stream->name().toString(); +#else + const String s(stream->name()); +#endif + + String ext; + const int pos = s.rfind("."); + if(pos != -1) + ext = s.substr(pos + 1).upper(); + + // If this list is updated, the method defaultFileExtensions() should also be + // updated. However at some point that list should be created at the same time + // that a default file type resolver is created. + + if(ext.isEmpty()) + return 0; + + // .oga can be any audio in the Ogg container. So leave it to content-based detection. + + if(ext == "MP3") + return new MPEG::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + if(ext == "OGG") + return new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "FLAC") + return new FLAC::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + if(ext == "MPC") + return new MPC::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "WV") + return new WavPack::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "SPX") + return new Ogg::Speex::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "OPUS") + return new Ogg::Opus::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "TTA") + return new TrueAudio::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2" || ext == "M4V") + return new MP4::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "WMA" || ext == "ASF") + return new ASF::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC") + return new RIFF::AIFF::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "WAV") + return new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "APE") + return new APE::File(stream, readAudioProperties, audioPropertiesStyle); + // module, nst and wow are possible but uncommon extensions + if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW") + return new Mod::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "S3M") + return new S3M::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "IT") + return new IT::File(stream, readAudioProperties, audioPropertiesStyle); + if(ext == "XM") + return new XM::File(stream, readAudioProperties, audioPropertiesStyle); + + return 0; + } + + // Detect the file type based on the actual content of the stream. + + File *detectByContent(IOStream *stream, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) + { + File *file = 0; + + if(MPEG::File::isSupported(stream)) + file = new MPEG::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + else if(Ogg::Vorbis::File::isSupported(stream)) + file = new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle); + else if(Ogg::FLAC::File::isSupported(stream)) + file = new Ogg::FLAC::File(stream, readAudioProperties, audioPropertiesStyle); + else if(FLAC::File::isSupported(stream)) + file = new FLAC::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + else if(MPC::File::isSupported(stream)) + file = new MPC::File(stream, readAudioProperties, audioPropertiesStyle); + else if(WavPack::File::isSupported(stream)) + file = new WavPack::File(stream, readAudioProperties, audioPropertiesStyle); + else if(Ogg::Speex::File::isSupported(stream)) + file = new Ogg::Speex::File(stream, readAudioProperties, audioPropertiesStyle); + else if(Ogg::Opus::File::isSupported(stream)) + file = new Ogg::Opus::File(stream, readAudioProperties, audioPropertiesStyle); + else if(TrueAudio::File::isSupported(stream)) + file = new TrueAudio::File(stream, readAudioProperties, audioPropertiesStyle); + else if(MP4::File::isSupported(stream)) + file = new MP4::File(stream, readAudioProperties, audioPropertiesStyle); + else if(ASF::File::isSupported(stream)) + file = new ASF::File(stream, readAudioProperties, audioPropertiesStyle); + else if(RIFF::AIFF::File::isSupported(stream)) + file = new RIFF::AIFF::File(stream, readAudioProperties, audioPropertiesStyle); + else if(RIFF::WAV::File::isSupported(stream)) + file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle); + else if(APE::File::isSupported(stream)) + file = new APE::File(stream, readAudioProperties, audioPropertiesStyle); + + // isSupported() only does a quick check, so double check the file here. + + if(file) { + if(file->isValid()) + return file; + else + delete file; + } + + return 0; + } + + // Internal function that supports FileRef::create(). + // This looks redundant, but necessary in order not to change the previous + // behavior of FileRef::create(). + + File* createInternal(FileName fileName, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) + { + File *file = detectByResolvers(fileName, readAudioProperties, audioPropertiesStyle); + if(file) + return file; + +#ifdef _WIN32 + const String s = fileName.toString(); +#else + const String s(fileName); +#endif + + String ext; + const int pos = s.rfind("."); + if(pos != -1) + ext = s.substr(pos + 1).upper(); + + if(ext.isEmpty()) + return 0; + + if(ext == "MP3") + return new MPEG::File(fileName, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + if(ext == "OGG") + return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "OGA") { + /* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */ + File *file = new Ogg::FLAC::File(fileName, readAudioProperties, audioPropertiesStyle); + if(file->isValid()) + return file; + delete file; + return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle); + } + if(ext == "FLAC") + return new FLAC::File(fileName, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); + if(ext == "MPC") + return new MPC::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "WV") + return new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "SPX") + return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "OPUS") + return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "TTA") + return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2" || ext == "M4V") + return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "WMA" || ext == "ASF") + return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "AIF" || ext == "AIFF" || ext == "AFC" || ext == "AIFC") + return new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "WAV") + return new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "APE") + return new APE::File(fileName, readAudioProperties, audioPropertiesStyle); + // module, nst and wow are possible but uncommon extensions + if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW") + return new Mod::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "S3M") + return new S3M::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "IT") + return new IT::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "XM") + return new XM::File(fileName, readAudioProperties, audioPropertiesStyle); + + return 0; + } +} + class FileRef::FileRefPrivate : public RefCounter { public: - FileRefPrivate(File *f) : RefCounter(), file(f) {} + FileRefPrivate() : + RefCounter(), + file(0), + stream(0) {} + ~FileRefPrivate() { delete file; + delete stream; } - File *file; - static List<const FileTypeResolver *> fileTypeResolvers; + File *file; + IOStream *stream; }; -List<const FileRef::FileTypeResolver *> FileRef::FileRefPrivate::fileTypeResolvers; - //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -FileRef::FileRef() +FileRef::FileRef() : + d(new FileRefPrivate()) { - d = new FileRefPrivate(0); } FileRef::FileRef(FileName fileName, bool readAudioProperties, - AudioProperties::ReadStyle audioPropertiesStyle) + AudioProperties::ReadStyle audioPropertiesStyle) : + d(new FileRefPrivate()) { - d = new FileRefPrivate(create(fileName, readAudioProperties, audioPropertiesStyle)); + parse(fileName, readAudioProperties, audioPropertiesStyle); } -FileRef::FileRef(File *file) +FileRef::FileRef(IOStream* stream, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle) : + d(new FileRefPrivate()) { - d = new FileRefPrivate(file); + parse(stream, readAudioProperties, audioPropertiesStyle); } -FileRef::FileRef(const FileRef &ref) : d(ref.d) +FileRef::FileRef(File *file) : + d(new FileRefPrivate()) +{ + d->file = file; +} + +FileRef::FileRef(const FileRef &ref) : + d(ref.d) { d->ref(); } @@ -132,7 +355,7 @@ bool FileRef::save() const FileRef::FileTypeResolver *FileRef::addFileTypeResolver(const FileRef::FileTypeResolver *resolver) // static { - FileRefPrivate::fileTypeResolvers.prepend(resolver); + fileTypeResolvers.prepend(resolver); return resolver; } @@ -143,140 +366,120 @@ StringList FileRef::defaultFileExtensions() l.append("ogg"); l.append("flac"); l.append("oga"); + l.append("opus"); l.append("mp3"); l.append("mpc"); l.append("wv"); l.append("spx"); l.append("tta"); -#ifdef TAGLIB_WITH_MP4 l.append("m4a"); + l.append("m4r"); l.append("m4b"); l.append("m4p"); l.append("3g2"); l.append("mp4"); -#endif -#ifdef TAGLIB_WITH_ASF + l.append("m4v"); l.append("wma"); l.append("asf"); -#endif l.append("aif"); l.append("aiff"); + l.append("afc"); + l.append("aifc"); l.append("wav"); l.append("ape"); - l.append("ac3"); - l.append("opus"); - l.append("tak"); - l.append("ac3"); - l.append("apl"); - l.append("dts"); - l.append("dtshd"); + l.append("mod"); + l.append("module"); // alias for "mod" + l.append("nst"); // alias for "mod" + l.append("wow"); // alias for "mod" + l.append("s3m"); + l.append("it"); + l.append("xm"); return l; } bool FileRef::isNull() const { - return !d->file || !d->file->isValid(); + return (!d->file || !d->file->isValid()); } FileRef &FileRef::operator=(const FileRef &ref) { - if(&ref == this) - return *this; - - if(d->deref()) - delete d; - - d = ref.d; - d->ref(); - + FileRef(ref).swap(*this); return *this; } +void FileRef::swap(FileRef &ref) +{ + using std::swap; + + swap(d, ref.d); +} + bool FileRef::operator==(const FileRef &ref) const { - return ref.d->file == d->file; + return (ref.d->file == d->file); } bool FileRef::operator!=(const FileRef &ref) const { - return ref.d->file != d->file; + return (ref.d->file != d->file); } File *FileRef::create(FileName fileName, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle) // static { - - List<const FileTypeResolver *>::ConstIterator it = FileRefPrivate::fileTypeResolvers.begin(); - - for(; it != FileRefPrivate::fileTypeResolvers.end(); ++it) { - File *file = (*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle); - if(file) - return file; - } - - // Ok, this is really dumb for now, but it works for testing. - - String s; - -#ifdef _WIN32 - s = (wcslen((const wchar_t *) fileName) > 0) ? String((const wchar_t *) fileName) : String((const char *) fileName); -#else - s = fileName; -#endif - - // If this list is updated, the method defaultFileExtensions() should also be - // updated. However at some point that list should be created at the same time - // that a default file type resolver is created. - - int pos = s.rfind("."); - if(pos != -1) { - String ext = s.substr(pos + 1).upper(); - if(ext == "MP3") - return new MPEG::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "OGG") - return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "OPUS") - return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "OGA") { - /* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */ - File *file = new Ogg::FLAC::File(fileName, readAudioProperties, audioPropertiesStyle); - if (file->isValid()) - return file; - delete file; - file = new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle); - if (file->isValid()) - return file; - delete file; - return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle); - } - if(ext == "FLAC") - return new FLAC::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "MPC") - return new MPC::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "WV") - return new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "SPX") - return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "TTA") - return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle); -#ifdef TAGLIB_WITH_MP4 - if(ext == "M4A" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2") - return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle); -#endif -#ifdef TAGLIB_WITH_ASF - if(ext == "WMA" || ext == "ASF") - return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle); -#endif - if(ext == "AIF" || ext == "AIFF") - return new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "WAV") - return new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "APE") - return new APE::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "TAK" || ext == "AC3" || ext == "APL" || ext == "DTS" || ext == "DTSHD") - return new APE::File(fileName, false, audioPropertiesStyle); - } - - return 0; + return createInternal(fileName, readAudioProperties, audioPropertiesStyle); +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void FileRef::parse(FileName fileName, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) +{ + // Try user-defined resolvers. + + d->file = detectByResolvers(fileName, readAudioProperties, audioPropertiesStyle); + if(d->file) + return; + + // Try to resolve file types based on the file extension. + + d->stream = new FileStream(fileName); + d->file = detectByExtension(d->stream, readAudioProperties, audioPropertiesStyle); + if(d->file) + return; + + // At last, try to resolve file types based on the actual content. + + d->file = detectByContent(d->stream, readAudioProperties, audioPropertiesStyle); + if(d->file) + return; + + // Stream have to be closed here if failed to resolve file types. + + delete d->stream; + d->stream = 0; +} + +void FileRef::parse(IOStream *stream, bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) +{ + // Try user-defined resolvers. + + d->file = detectByResolvers(stream->name(), readAudioProperties, audioPropertiesStyle); + if(d->file) + return; + + // Try to resolve file types based on the file extension. + + d->file = detectByExtension(stream, readAudioProperties, audioPropertiesStyle); + if(d->file) + return; + + // At last, try to resolve file types based on the actual content of the file. + + d->file = detectByContent(stream, readAudioProperties, audioPropertiesStyle); } diff --git a/Frameworks/TagLib/taglib/taglib/fileref.h b/Frameworks/TagLib/taglib/taglib/fileref.h index 0f0c21a4d..c36f54cbd 100644 --- a/Frameworks/TagLib/taglib/taglib/fileref.h +++ b/Frameworks/TagLib/taglib/taglib/fileref.h @@ -72,7 +72,7 @@ namespace TagLib { * * class MyFileTypeResolver : FileTypeResolver * { - * TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle) + * TagLib::File *createFile(TagLib::FileName *fileName, bool, AudioProperties::ReadStyle) const * { * if(someCheckForAnMP3File(fileName)) * return new TagLib::MPEG::File(fileName); @@ -128,7 +128,24 @@ namespace TagLib { audioPropertiesStyle = AudioProperties::Average); /*! - * Contruct a FileRef using \a file. The FileRef now takes ownership of the + * Construct a FileRef from an opened \a IOStream. If \a readAudioProperties + * is true then the audio properties will be read using \a audioPropertiesStyle. + * If \a readAudioProperties is false then \a audioPropertiesStyle will be + * ignored. + * + * Also see the note in the class documentation about why you may not want to + * use this method in your application. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + explicit FileRef(IOStream* stream, + bool readAudioProperties = true, + AudioProperties::ReadStyle + audioPropertiesStyle = AudioProperties::Average); + + /*! + * Construct a FileRef using \a file. The FileRef now takes ownership of the * pointer and will delete the File when it passes out of scope. */ explicit FileRef(File *file); @@ -191,7 +208,7 @@ namespace TagLib { * is tried. * * Returns a pointer to the added resolver (the same one that's passed in -- - * this is mostly so that static inialializers have something to use for + * this is mostly so that static initializers have something to use for * assignment). * * \see FileTypeResolver @@ -209,7 +226,7 @@ namespace TagLib { * by TagLib for resolution is case-insensitive. * * \note This does not account for any additional file type resolvers that - * are plugged in. Also note that this is not intended to replace a propper + * are plugged in. Also note that this is not intended to replace a proper * mime-type resolution system, but is just here for reference. * * \see FileTypeResolver @@ -226,6 +243,11 @@ namespace TagLib { */ FileRef &operator=(const FileRef &ref); + /*! + * Exchanges the content of the FileRef by the content of \a ref. + */ + void swap(FileRef &ref); + /*! * Returns true if this FileRef and \a ref point to the same File object. */ @@ -252,8 +274,10 @@ namespace TagLib { bool readAudioProperties = true, AudioProperties::ReadStyle audioPropertiesStyle = AudioProperties::Average); - private: + void parse(FileName fileName, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle); + void parse(IOStream *stream, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle); + class FileRefPrivate; FileRefPrivate *d; }; diff --git a/Frameworks/TagLib/taglib/taglib/flac/flacfile.cpp b/Frameworks/TagLib/taglib/taglib/flac/flacfile.cpp index c8cc1fb85..ada215db1 100644 --- a/Frameworks/TagLib/taglib/taglib/flac/flacfile.cpp +++ b/Frameworks/TagLib/taglib/taglib/flac/flacfile.cpp @@ -28,6 +28,8 @@ #include <tlist.h> #include <tdebug.h> #include <tagunion.h> +#include <tpropertymap.h> +#include <tagutils.h> #include <id3v2header.h> #include <id3v2tag.h> @@ -43,80 +45,96 @@ using namespace TagLib; namespace { - enum { XiphIndex = 0, ID3v2Index = 1, ID3v1Index = 2 }; - enum { MinPaddingLength = 4096 }; - enum { LastBlockFlag = 0x80 }; + typedef List<FLAC::MetadataBlock *> BlockList; + typedef BlockList::Iterator BlockIterator; + typedef BlockList::Iterator BlockConstIterator; + + enum { FlacXiphIndex = 0, FlacID3v2Index = 1, FlacID3v1Index = 2 }; + + const long MinPaddingLength = 4096; + const long MaxPaddingLegnth = 1024 * 1024; + + const char LastBlockFlag = '\x80'; } class FLAC::File::FilePrivate { public: - FilePrivate() : - ID3v2FrameFactory(ID3v2::FrameFactory::instance()), + FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) : + ID3v2FrameFactory(frameFactory), ID3v2Location(-1), ID3v2OriginalSize(0), ID3v1Location(-1), properties(0), flacStart(0), streamStart(0), - streamLength(0), - scanned(false), - hasXiphComment(false), - hasID3v2(false), - hasID3v1(false) + scanned(false) { + blocks.setAutoDelete(true); } ~FilePrivate() { - for(uint i = 0; i < blocks.size(); i++) { - delete blocks[i]; - } delete properties; } const ID3v2::FrameFactory *ID3v2FrameFactory; long ID3v2Location; - uint ID3v2OriginalSize; + long ID3v2OriginalSize; long ID3v1Location; TagUnion tag; Properties *properties; - ByteVector streamInfoData; ByteVector xiphCommentData; - List<MetadataBlock *> blocks; + BlockList blocks; long flacStart; long streamStart; - long streamLength; bool scanned; - - bool hasXiphComment; - bool hasID3v2; - bool hasID3v1; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool FLAC::File::isSupported(IOStream *stream) +{ + // A FLAC file has an ID "fLaC" somewhere. An ID3v2 tag may precede. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true); + return (buffer.find("fLaC") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -FLAC::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : - TagLib::File(file) +FLAC::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties); } FLAC::File::File(FileName file, ID3v2::FrameFactory *frameFactory, - bool readProperties, Properties::ReadStyle propertiesStyle) : - TagLib::File(file) + bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate(frameFactory)) { - d = new FilePrivate; - d->ID3v2FrameFactory = frameFactory; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties); +} + +FLAC::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, + bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate(frameFactory)) +{ + if(isOpen()) + read(readProperties); } FLAC::File::~File() @@ -129,12 +147,26 @@ TagLib::Tag *FLAC::File::tag() const return &d->tag; } +PropertyMap FLAC::File::properties() const +{ + return d->tag.properties(); +} + +void FLAC::File::removeUnsupportedProperties(const StringList &unsupported) +{ + d->tag.removeUnsupportedProperties(unsupported); +} + +PropertyMap FLAC::File::setProperties(const PropertyMap &properties) +{ + return xiphComment(true)->setProperties(properties); +} + FLAC::Properties *FLAC::File::audioProperties() const { return d->properties; } - bool FLAC::File::save() { if(readOnly()) { @@ -148,101 +180,154 @@ bool FLAC::File::save() } // Create new vorbis comments - - Tag::duplicate(&d->tag, xiphComment(true), true); + if(!hasXiphComment()) + Tag::duplicate(&d->tag, xiphComment(true), false); d->xiphCommentData = xiphComment()->render(false); // Replace metadata blocks - bool foundVorbisCommentBlock = false; - List<MetadataBlock *> newBlocks; - for(uint i = 0; i < d->blocks.size(); i++) { - MetadataBlock *block = d->blocks[i]; - if(block->code() == MetadataBlock::VorbisComment) { - // Set the new Vorbis Comment block - block = new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData); - foundVorbisCommentBlock = true; - } - if(block->code() == MetadataBlock::Padding) { + MetadataBlock *commentBlock = + new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData); + for(BlockIterator it = d->blocks.begin(); it != d->blocks.end();) { + if((*it)->code() == MetadataBlock::VorbisComment) { + // Remove the old Vorbis Comment block + delete *it; + it = d->blocks.erase(it); continue; } - newBlocks.append(block); + if(commentBlock && (*it)->code() == MetadataBlock::Picture) { + // Set the new Vorbis Comment block before the first picture block + d->blocks.insert(it, commentBlock); + commentBlock = 0; + } + ++it; } - if(!foundVorbisCommentBlock) { - newBlocks.append(new UnknownMetadataBlock(MetadataBlock::VorbisComment, d->xiphCommentData)); - foundVorbisCommentBlock = true; - } - d->blocks = newBlocks; + if(commentBlock) + d->blocks.append(commentBlock); // Render data for the metadata blocks ByteVector data; - for(uint i = 0; i < newBlocks.size(); i++) { - FLAC::MetadataBlock *block = newBlocks[i]; - ByteVector blockData = block->render(); + for(BlockConstIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) { + ByteVector blockData = (*it)->render(); ByteVector blockHeader = ByteVector::fromUInt(blockData.size()); - blockHeader[0] = block->code(); + blockHeader[0] = (*it)->code(); data.append(blockHeader); data.append(blockData); } - // Adjust the padding block(s) + // Compute the amount of padding, and append that to data. long originalLength = d->streamStart - d->flacStart; - int paddingLength = originalLength - data.size() - 4; - if (paddingLength < 0) { + long paddingLength = originalLength - data.size() - 4; + + if(paddingLength <= 0) { paddingLength = MinPaddingLength; } - ByteVector padding = ByteVector::fromUInt(paddingLength); - padding.resize(paddingLength + 4); - padding[0] = FLAC::MetadataBlock::Padding | LastBlockFlag; - data.append(padding); + else { + // Padding won't increase beyond 1% of the file size or 1MB. + + long threshold = length() / 100; + threshold = std::max(threshold, MinPaddingLength); + threshold = std::min(threshold, MaxPaddingLegnth); + + if(paddingLength > threshold) + paddingLength = MinPaddingLength; + } + + ByteVector paddingHeader = ByteVector::fromUInt(paddingLength); + paddingHeader[0] = static_cast<char>(MetadataBlock::Padding | LastBlockFlag); + data.append(paddingHeader); + data.resize(static_cast<unsigned int>(data.size() + paddingLength)); // Write the data to the file insert(data, d->flacStart, originalLength); - d->hasXiphComment = true; + + d->streamStart += (static_cast<long>(data.size()) - originalLength); + + if(d->ID3v1Location >= 0) + d->ID3v1Location += (static_cast<long>(data.size()) - originalLength); // Update ID3 tags - if(ID3v2Tag()) { - if(d->hasID3v2) { - if(d->ID3v2Location < d->flacStart) - debug("FLAC::File::save() -- This can't be right -- an ID3v2 tag after the " - "start of the FLAC bytestream? Not writing the ID3v2 tag."); - else - insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize); + if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) { + + // ID3v2 tag is not empty. Update the old one or create a new one. + + if(d->ID3v2Location < 0) + d->ID3v2Location = 0; + + data = ID3v2Tag()->render(); + insert(data, d->ID3v2Location, d->ID3v2OriginalSize); + + d->flacStart += (static_cast<long>(data.size()) - d->ID3v2OriginalSize); + d->streamStart += (static_cast<long>(data.size()) - d->ID3v2OriginalSize); + + if(d->ID3v1Location >= 0) + d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize); + + d->ID3v2OriginalSize = data.size(); + } + else { + + // ID3v2 tag is empty. Remove the old one. + + if(d->ID3v2Location >= 0) { + removeBlock(d->ID3v2Location, d->ID3v2OriginalSize); + + d->flacStart -= d->ID3v2OriginalSize; + d->streamStart -= d->ID3v2OriginalSize; + + if(d->ID3v1Location >= 0) + d->ID3v1Location -= d->ID3v2OriginalSize; + + d->ID3v2Location = -1; + d->ID3v2OriginalSize = 0; } - else - insert(ID3v2Tag()->render(), 0, 0); } - if(ID3v1Tag()) { - seek(-128, End); + if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) { + + // ID3v1 tag is not empty. Update the old one or create a new one. + + if(d->ID3v1Location >= 0) { + seek(d->ID3v1Location); + } + else { + seek(0, End); + d->ID3v1Location = tell(); + } + writeBlock(ID3v1Tag()->render()); } + else { + + // ID3v1 tag is empty. Remove the old one. + + if(d->ID3v1Location >= 0) { + truncate(d->ID3v1Location); + d->ID3v1Location = -1; + } + } return true; } ID3v2::Tag *FLAC::File::ID3v2Tag(bool create) { - if(!create || d->tag[ID3v2Index]) - return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]); - - d->tag.set(ID3v2Index, new ID3v2::Tag); - return static_cast<ID3v2::Tag *>(d->tag[ID3v2Index]); + return d->tag.access<ID3v2::Tag>(FlacID3v2Index, create); } ID3v1::Tag *FLAC::File::ID3v1Tag(bool create) { - return d->tag.access<ID3v1::Tag>(ID3v1Index, create); + return d->tag.access<ID3v1::Tag>(FlacID3v1Index, create); } Ogg::XiphComment *FLAC::File::xiphComment(bool create) { - return d->tag.access<Ogg::XiphComment>(XiphIndex, create); + return d->tag.access<Ogg::XiphComment>(FlacXiphIndex, create); } void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory) @@ -250,37 +335,108 @@ void FLAC::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory) d->ID3v2FrameFactory = factory; } +ByteVector FLAC::File::streamInfoData() +{ + debug("FLAC::File::streamInfoData() -- This function is obsolete. Returning an empty ByteVector."); + return ByteVector(); +} + +long FLAC::File::streamLength() +{ + debug("FLAC::File::streamLength() -- This function is obsolete. Returning zero."); + return 0; +} + +List<FLAC::Picture *> FLAC::File::pictureList() +{ + List<Picture *> pictures; + for(BlockConstIterator it = d->blocks.begin(); it != d->blocks.end(); ++it) { + Picture *picture = dynamic_cast<Picture *>(*it); + if(picture) { + pictures.append(picture); + } + } + return pictures; +} + +void FLAC::File::addPicture(Picture *picture) +{ + d->blocks.append(picture); +} + +void FLAC::File::removePicture(Picture *picture, bool del) +{ + BlockIterator it = d->blocks.find(picture); + if(it != d->blocks.end()) + d->blocks.erase(it); + + if(del) + delete picture; +} + +void FLAC::File::removePictures() +{ + for(BlockIterator it = d->blocks.begin(); it != d->blocks.end(); ) { + if(dynamic_cast<Picture *>(*it)) { + delete *it; + it = d->blocks.erase(it); + } + else { + ++it; + } + } +} + +void FLAC::File::strip(int tags) +{ + if(tags & ID3v1) + d->tag.set(FlacID3v1Index, 0); + + if(tags & ID3v2) + d->tag.set(FlacID3v2Index, 0); + + if(tags & XiphComment) { + xiphComment()->removeAllFields(); + xiphComment()->removeAllPictures(); + } +} + +bool FLAC::File::hasXiphComment() const +{ + return !d->xiphCommentData.isEmpty(); +} + +bool FLAC::File::hasID3v1Tag() const +{ + return (d->ID3v1Location >= 0); +} + +bool FLAC::File::hasID3v2Tag() const +{ + return (d->ID3v2Location >= 0); +} //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void FLAC::File::read(bool readProperties) { // Look for an ID3v2 tag - d->ID3v2Location = findID3v2(); + d->ID3v2Location = Utils::findID3v2(this); if(d->ID3v2Location >= 0) { - - d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory)); - + d->tag.set(FlacID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory)); d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize(); - - if(ID3v2Tag()->header()->tagSize() <= 0) - d->tag.set(ID3v2Index, 0); - else - d->hasID3v2 = true; } // Look for an ID3v1 tag - d->ID3v1Location = findID3v1(); + d->ID3v1Location = Utils::findID3v1(this); - if(d->ID3v1Location >= 0) { - d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); - d->hasID3v1 = true; - } + if(d->ID3v1Location >= 0) + d->tag.set(FlacID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); // Look for FLAC metadata, including vorbis comments @@ -289,28 +445,26 @@ void FLAC::File::read(bool readProperties, Properties::ReadStyle propertiesStyle if(!isValid()) return; - if(d->hasXiphComment) - d->tag.set(XiphIndex, new Ogg::XiphComment(xiphCommentData())); + if(!d->xiphCommentData.isEmpty()) + d->tag.set(FlacXiphIndex, new Ogg::XiphComment(d->xiphCommentData)); else - d->tag.set(XiphIndex, new Ogg::XiphComment); + d->tag.set(FlacXiphIndex, new Ogg::XiphComment()); - if(readProperties) - d->properties = new Properties(streamInfoData(), streamLength(), propertiesStyle); -} + if(readProperties) { -ByteVector FLAC::File::streamInfoData() -{ - return isValid() ? d->streamInfoData : ByteVector(); -} + // First block should be the stream_info metadata -ByteVector FLAC::File::xiphCommentData() const -{ - return (isValid() && d->hasXiphComment) ? d->xiphCommentData : ByteVector(); -} + const ByteVector infoData = d->blocks.front()->render(); -long FLAC::File::streamLength() -{ - return d->streamLength; + long streamLength; + + if(d->ID3v1Location >= 0) + streamLength = d->ID3v1Location - d->streamStart; + else + streamLength = length() - d->streamStart; + + d->properties = new Properties(infoData, streamLength); + } } void FLAC::File::scan() @@ -325,7 +479,7 @@ void FLAC::File::scan() long nextBlockOffset; - if(d->hasID3v2) + if(d->ID3v2Location >= 0) nextBlockOffset = find("fLaC", d->ID3v2Location + d->ID3v2OriginalSize); else nextBlockOffset = find("fLaC"); @@ -339,47 +493,46 @@ void FLAC::File::scan() nextBlockOffset += 4; d->flacStart = nextBlockOffset; - seek(nextBlockOffset); + while(true) { - ByteVector header = readBlock(4); + seek(nextBlockOffset); + const ByteVector header = readBlock(4); - // Header format (from spec): - // <1> Last-metadata-block flag - // <7> BLOCK_TYPE - // 0 : STREAMINFO - // 1 : PADDING - // .. - // 4 : VORBIS_COMMENT - // .. - // <24> Length of metadata to follow + // Header format (from spec): + // <1> Last-metadata-block flag + // <7> BLOCK_TYPE + // 0 : STREAMINFO + // 1 : PADDING + // .. + // 4 : VORBIS_COMMENT + // .. + // 6 : PICTURE + // .. + // <24> Length of metadata to follow - char blockType = header[0] & 0x7f; - bool isLastBlock = (header[0] & 0x80) != 0; - uint length = header.mid(1, 3).toUInt(); + const char blockType = header[0] & ~LastBlockFlag; + const bool isLastBlock = (header[0] & LastBlockFlag) != 0; + const unsigned int blockLength = header.toUInt(1U, 3U); - // First block should be the stream_info metadata + // First block should be the stream_info metadata - if(blockType != MetadataBlock::StreamInfo) { - debug("FLAC::File::scan() -- invalid FLAC stream"); - setValid(false); - return; - } + if(d->blocks.isEmpty() && blockType != MetadataBlock::StreamInfo) { + debug("FLAC::File::scan() -- First block should be the stream_info metadata"); + setValid(false); + return; + } - d->streamInfoData = readBlock(length); - d->blocks.append(new UnknownMetadataBlock(blockType, d->streamInfoData)); - nextBlockOffset += length + 4; + if(blockLength == 0 + && blockType != MetadataBlock::Padding && blockType != MetadataBlock::SeekTable) + { + debug("FLAC::File::scan() -- Zero-sized metadata block found"); + setValid(false); + return; + } - // Search through the remaining metadata - while(!isLastBlock) { - - header = readBlock(4); - blockType = header[0] & 0x7f; - isLastBlock = (header[0] & 0x80) != 0; - length = header.mid(1, 3).toUInt(); - - ByteVector data = readBlock(length); - if(data.size() != length) { - debug("FLAC::File::scan() -- FLAC stream corrupted"); + const ByteVector data = readBlock(blockLength); + if(data.size() != blockLength) { + debug("FLAC::File::scan() -- Failed to read a metadata block"); setValid(false); return; } @@ -388,12 +541,12 @@ void FLAC::File::scan() // Found the vorbis-comment if(blockType == MetadataBlock::VorbisComment) { - if(!d->hasXiphComment) { + if(d->xiphCommentData.isEmpty()) { d->xiphCommentData = data; - d->hasXiphComment = true; + block = new UnknownMetadataBlock(MetadataBlock::VorbisComment, data); } else { - debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, using the first one"); + debug("FLAC::File::scan() -- multiple Vorbis Comment blocks found, discarding"); } } else if(blockType == MetadataBlock::Picture) { @@ -402,98 +555,29 @@ void FLAC::File::scan() block = picture; } else { - debug("FLAC::File::scan() -- invalid picture found, discarting"); + debug("FLAC::File::scan() -- invalid picture found, discarding"); delete picture; } } - - if(!block) { - block = new UnknownMetadataBlock(blockType, data); - } - if(block->code() != MetadataBlock::Padding) { - d->blocks.append(block); + else if(blockType == MetadataBlock::Padding) { + // Skip all padding blocks. } else { - delete block; + block = new UnknownMetadataBlock(blockType, data); } - nextBlockOffset += length + 4; + if(block) + d->blocks.append(block); - if(nextBlockOffset >= File::length()) { - debug("FLAC::File::scan() -- FLAC stream corrupted"); - setValid(false); - return; - } - seek(nextBlockOffset); + nextBlockOffset += blockLength + 4; + + if(isLastBlock) + break; } // End of metadata, now comes the datastream d->streamStart = nextBlockOffset; - d->streamLength = File::length() - d->streamStart; - - if(d->hasID3v1) - d->streamLength -= 128; d->scanned = true; } - -long FLAC::File::findID3v1() -{ - if(!isValid()) - return -1; - - seek(-128, End); - long p = tell(); - - if(readBlock(3) == ID3v1::Tag::fileIdentifier()) - return p; - - return -1; -} - -long FLAC::File::findID3v2() -{ - if(!isValid()) - return -1; - - seek(0); - - if(readBlock(3) == ID3v2::Header::fileIdentifier()) - return 0; - - return -1; -} - -List<FLAC::Picture *> FLAC::File::pictureList() -{ - List<Picture *> pictures; - for(uint i = 0; i < d->blocks.size(); i++) { - Picture *picture = dynamic_cast<Picture *>(d->blocks[i]); - if(picture) { - pictures.append(picture); - } - } - return pictures; -} - -void FLAC::File::addPicture(Picture *picture) -{ - d->blocks.append(picture); -} - -void FLAC::File::removePictures() -{ - List<MetadataBlock *> newBlocks; - for(uint i = 0; i < d->blocks.size(); i++) { - Picture *picture = dynamic_cast<Picture *>(d->blocks[i]); - if(picture) { - delete picture; - } - else { - newBlocks.append(d->blocks[i]); - } - } - d->blocks = newBlocks; -} - diff --git a/Frameworks/TagLib/taglib/taglib/flac/flacfile.h b/Frameworks/TagLib/taglib/taglib/flac/flacfile.h index 64e67bc06..1b8654ebd 100644 --- a/Frameworks/TagLib/taglib/taglib/flac/flacfile.h +++ b/Frameworks/TagLib/taglib/taglib/flac/flacfile.h @@ -29,6 +29,7 @@ #include "taglib_export.h" #include "tfile.h" #include "tlist.h" +#include "tag.h" #include "flacpicture.h" #include "flacproperties.h" @@ -36,7 +37,6 @@ namespace TagLib { class Tag; - namespace ID3v2 { class FrameFactory; class Tag; } namespace ID3v1 { class Tag; } namespace Ogg { class XiphComment; } @@ -46,7 +46,7 @@ namespace TagLib { /*! * This is implementation of FLAC metadata for non-Ogg FLAC files. At some * point when Ogg / FLAC is more common there will be a similar implementation - * under the Ogg hiearchy. + * under the Ogg hierarchy. * * This supports ID3v1, ID3v2 and Xiph style comments as well as reading stream * properties from the file. @@ -67,9 +67,27 @@ namespace TagLib { { public: /*! - * Contructs a FLAC file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * This set of flags is used for various operations and is suitable for + * being OR-ed together. + */ + enum TagTypes { + //! Empty set. Matches no tag types. + NoTags = 0x0000, + //! Matches Vorbis comments. + XiphComment = 0x0001, + //! Matches ID3v1 tags. + ID3v1 = 0x0002, + //! Matches ID3v2 tags. + ID3v2 = 0x0004, + //! Matches all tag types. + AllTags = 0xffff + }; + + /*! + * Constructs a FLAC file from \a file. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. * * \deprecated This constructor will be dropped in favor of the one below * in a future version. @@ -78,18 +96,36 @@ namespace TagLib { Properties::ReadStyle propertiesStyle = Properties::Average); /*! - * Contructs a FLAC file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs an FLAC file from \a file. If \a readProperties is true the + * file's audio properties will also be read. * * If this file contains and ID3v2 tag the frames will be created using * \a frameFactory. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ // BIC: merge with the above constructor File(FileName file, ID3v2::FrameFactory *frameFactory, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs a FLAC file from \a stream. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * If this file contains and ID3v2 tag the frames will be created using + * \a frameFactory. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + // BIC: merge with the above constructor + File(IOStream *stream, ID3v2::FrameFactory *frameFactory, + bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ @@ -105,6 +141,25 @@ namespace TagLib { */ virtual TagLib::Tag *tag() const; + /*! + * Implements the unified property interface -- export function. + * If the file contains more than one tag (e.g. XiphComment and ID3v1), + * only the first one (in the order XiphComment, ID3v2, ID3v1) will be + * converted to the PropertyMap. + */ + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &); + + /*! + * Implements the unified property interface -- import function. + * This always creates a Xiph comment, if none exists. The return value + * relates to the Xiph comment only. + * Ignores any changes to ID3v1 or ID3v2 comments since they are not allowed + * in the FLAC specification. + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the FLAC::Properties for this file. If no audio properties * were read then this will return a null pointer. @@ -123,39 +178,57 @@ namespace TagLib { /*! * Returns a pointer to the ID3v2 tag of the file. * - * If \a create is false (the default) this will return a null pointer + * If \a create is false (the default) this returns a null pointer * if there is no valid ID3v2 tag. If \a create is true it will create - * an ID3v2 tag if one does not exist. + * an ID3v2 tag if one does not exist and returns a valid pointer. * - * \note The Tag <b>is still</b> owned by the FLAC::File and should not be + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file + * on disk actually has an ID3v2 tag. + * + * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasID3v2Tag() */ ID3v2::Tag *ID3v2Tag(bool create = false); /*! * Returns a pointer to the ID3v1 tag of the file. * - * If \a create is false (the default) this will return a null pointer - * if there is no valid ID3v1 tag. If \a create is true it will create - * an ID3v1 tag if one does not exist. + * If \a create is false (the default) this returns a null pointer + * if there is no valid APE tag. If \a create is true it will create + * an APE tag if one does not exist and returns a valid pointer. * - * \note The Tag <b>is still</b> owned by the FLAC::File and should not be + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * on disk actually has an ID3v1 tag. + * + * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasID3v1Tag() */ ID3v1::Tag *ID3v1Tag(bool create = false); /*! * Returns a pointer to the XiphComment for the file. * - * If \a create is false (the default) this will return a null pointer + * If \a create is false (the default) this returns a null pointer * if there is no valid XiphComment. If \a create is true it will create - * a XiphComment if one does not exist. + * a XiphComment if one does not exist and returns a valid pointer. + * + * \note This may return a valid pointer regardless of whether or not the + * file on disk has a XiphComment. Use hasXiphComment() to check if the + * file on disk actually has a XiphComment. * * \note The Tag <b>is still</b> owned by the FLAC::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasXiphComment() */ Ogg::XiphComment *xiphComment(bool create = false); @@ -165,30 +238,37 @@ namespace TagLib { * when * * \see ID3v2FrameFactory + * \deprecated This value should be passed in via the constructor */ - void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); + TAGLIB_DEPRECATED void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); /*! * Returns the block of data used by FLAC::Properties for parsing the * stream properties. * - * \deprecated This method will not be public in a future release. + * \deprecated Always returns an empty vector. */ - ByteVector streamInfoData(); // BIC: remove + TAGLIB_DEPRECATED ByteVector streamInfoData(); // BIC: remove /*! * Returns the length of the audio-stream, used by FLAC::Properties for * calculating the bitrate. * - * \deprecated This method will not be public in a future release. + * \deprecated Always returns zero. */ - long streamLength(); // BIC: remove + TAGLIB_DEPRECATED long streamLength(); // BIC: remove /*! * Returns a list of pictures attached to the FLAC file. */ List<Picture *> pictureList(); + /*! + * Removes an attached picture. If \a del is true the picture's memory + * will be freed; if it is false, it must be deleted by the user. + */ + void removePicture(Picture *picture, bool del = true); + /*! * Remove all attached images. */ @@ -202,16 +282,57 @@ namespace TagLib { */ void addPicture(Picture *picture); + /*! + * This will remove the tags that match the OR-ed together TagTypes from + * the file. By default it removes all tags. + * + * \warning This will also invalidate pointers to the tags as their memory + * will be freed. + * + * \note In order to make the removal permanent save() still needs to be + * called. + * + * \note This won't remove the Vorbis comment block completely. The + * vendor ID will be preserved. + */ + void strip(int tags = AllTags); + + /*! + * Returns whether or not the file on disk actually has a XiphComment. + * + * \see xiphComment() + */ + bool hasXiphComment() const; + + /*! + * Returns whether or not the file on disk actually has an ID3v1 tag. + * + * \see ID3v1Tag() + */ + bool hasID3v1Tag() const; + + /*! + * Returns whether or not the file on disk actually has an ID3v2 tag. + * + * \see ID3v2Tag() + */ + bool hasID3v2Tag() const; + + /*! + * Returns whether or not the given \a stream can be opened as a FLAC + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); void scan(); - long findID3v2(); - long findID3v1(); - ByteVector xiphCommentData() const; - long findPaddingBreak(long nextPageOffset, long targetOffset, bool *isLast); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/flac/flacmetadatablock.cpp b/Frameworks/TagLib/taglib/taglib/flac/flacmetadatablock.cpp index 0671fc3a3..17ab05f37 100644 --- a/Frameworks/TagLib/taglib/taglib/flac/flacmetadatablock.cpp +++ b/Frameworks/TagLib/taglib/taglib/flac/flacmetadatablock.cpp @@ -23,17 +23,13 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - #include <taglib.h> #include <tdebug.h> #include "flacmetadatablock.h" using namespace TagLib; -class FLAC::MetadataBlock::MetadataBlockPrivate +class FLAC::MetadataBlock::MetadataBlockPrivate { public: MetadataBlockPrivate() {} diff --git a/Frameworks/TagLib/taglib/taglib/flac/flacpicture.cpp b/Frameworks/TagLib/taglib/taglib/flac/flacpicture.cpp index 389671994..ec07ad140 100644 --- a/Frameworks/TagLib/taglib/taglib/flac/flacpicture.cpp +++ b/Frameworks/TagLib/taglib/taglib/flac/flacpicture.cpp @@ -23,17 +23,13 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - #include <taglib.h> #include <tdebug.h> #include "flacpicture.h" using namespace TagLib; -class FLAC::Picture::PicturePrivate +class FLAC::Picture::PicturePrivate { public: PicturePrivate() : @@ -54,14 +50,14 @@ public: ByteVector data; }; -FLAC::Picture::Picture() +FLAC::Picture::Picture() : + d(new PicturePrivate()) { - d = new PicturePrivate; } -FLAC::Picture::Picture(const ByteVector &data) +FLAC::Picture::Picture(const ByteVector &data) : + d(new PicturePrivate()) { - d = new PicturePrivate; parse(data); } @@ -82,10 +78,10 @@ bool FLAC::Picture::parse(const ByteVector &data) return false; } - int pos = 0; - d->type = FLAC::Picture::Type(data.mid(pos, 4).toUInt()); + unsigned int pos = 0; + d->type = FLAC::Picture::Type(data.toUInt(pos)); pos += 4; - uint mimeTypeLength = data.mid(pos, 4).toUInt(); + unsigned int mimeTypeLength = data.toUInt(pos); pos += 4; if(pos + mimeTypeLength + 24 > data.size()) { debug("Invalid picture block."); @@ -93,7 +89,7 @@ bool FLAC::Picture::parse(const ByteVector &data) } d->mimeType = String(data.mid(pos, mimeTypeLength), String::UTF8); pos += mimeTypeLength; - uint descriptionLength = data.mid(pos, 4).toUInt(); + unsigned int descriptionLength = data.toUInt(pos); pos += 4; if(pos + descriptionLength + 20 > data.size()) { debug("Invalid picture block."); @@ -101,15 +97,15 @@ bool FLAC::Picture::parse(const ByteVector &data) } d->description = String(data.mid(pos, descriptionLength), String::UTF8); pos += descriptionLength; - d->width = data.mid(pos, 4).toUInt(); + d->width = data.toUInt(pos); pos += 4; - d->height = data.mid(pos, 4).toUInt(); + d->height = data.toUInt(pos); pos += 4; - d->colorDepth = data.mid(pos, 4).toUInt(); + d->colorDepth = data.toUInt(pos); pos += 4; - d->numColors = data.mid(pos, 4).toUInt(); + d->numColors = data.toUInt(pos); pos += 4; - uint dataLength = data.mid(pos, 4).toUInt(); + unsigned int dataLength = data.toUInt(pos); pos += 4; if(pos + dataLength > data.size()) { debug("Invalid picture block."); @@ -117,7 +113,7 @@ bool FLAC::Picture::parse(const ByteVector &data) } d->data = data.mid(pos, dataLength); - return true; + return true; } ByteVector FLAC::Picture::render() const diff --git a/Frameworks/TagLib/taglib/taglib/flac/flacproperties.cpp b/Frameworks/TagLib/taglib/taglib/flac/flacproperties.cpp index d36d26ec6..b947f039b 100644 --- a/Frameworks/TagLib/taglib/taglib/flac/flacproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/flac/flacproperties.cpp @@ -34,24 +34,20 @@ using namespace TagLib; class FLAC::Properties::PropertiesPrivate { public: - PropertiesPrivate(ByteVector d, long st, ReadStyle s) : - data(d), - streamLength(st), - style(s), + PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), - sampleWidth(0), - channels(0) {} + bitsPerSample(0), + channels(0), + sampleFrames(0) {} - ByteVector data; - long streamLength; - ReadStyle style; int length; int bitrate; int sampleRate; - int sampleWidth; + int bitsPerSample; int channels; + unsigned long long sampleFrames; ByteVector signature; }; @@ -59,16 +55,18 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -FLAC::Properties::Properties(ByteVector data, long streamLength, ReadStyle style) : AudioProperties(style) +FLAC::Properties::Properties(ByteVector data, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(data, streamLength, style); - read(); + read(data, streamLength); } -FLAC::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +FLAC::Properties::Properties(File *, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file->streamInfoData(), file->streamLength(), style); - read(); + debug("FLAC::Properties::Properties() - This constructor is no longer used."); } FLAC::Properties::~Properties() @@ -77,6 +75,16 @@ FLAC::Properties::~Properties() } int FLAC::Properties::length() const +{ + return lengthInSeconds(); +} + +int FLAC::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int FLAC::Properties::lengthInMilliseconds() const { return d->length; } @@ -91,9 +99,14 @@ int FLAC::Properties::sampleRate() const return d->sampleRate; } +int FLAC::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + int FLAC::Properties::sampleWidth() const { - return d->sampleWidth; + return bitsPerSample(); } int FLAC::Properties::channels() const @@ -101,6 +114,11 @@ int FLAC::Properties::channels() const return d->channels; } +unsigned long long FLAC::Properties::sampleFrames() const +{ + return d->sampleFrames; +} + ByteVector FLAC::Properties::signature() const { return d->signature; @@ -110,14 +128,14 @@ ByteVector FLAC::Properties::signature() const // private members //////////////////////////////////////////////////////////////////////////////// -void FLAC::Properties::read() +void FLAC::Properties::read(const ByteVector &data, long streamLength) { - if(d->data.size() < 18) { + if(data.size() < 18) { debug("FLAC::Properties::read() - FLAC properties must contain at least 18 bytes."); return; } - int pos = 0; + unsigned int pos = 0; // Minimum block size (in samples) pos += 2; @@ -131,28 +149,28 @@ void FLAC::Properties::read() // Maximum frame size (in bytes) pos += 3; - uint flags = d->data.mid(pos, 4).toUInt(true); - d->sampleRate = flags >> 12; - d->channels = ((flags >> 9) & 7) + 1; - d->sampleWidth = ((flags >> 4) & 31) + 1; + const unsigned int flags = data.toUInt(pos, true); + pos += 4; + + d->sampleRate = flags >> 12; + d->channels = ((flags >> 9) & 7) + 1; + d->bitsPerSample = ((flags >> 4) & 31) + 1; // The last 4 bits are the most significant 4 bits for the 36 bit // stream length in samples. (Audio files measured in days) - uint highLength =d->sampleRate > 0 ? (((flags & 0xf) << 28) / d->sampleRate) << 4 : 0; + const unsigned long long hi = flags & 0xf; + const unsigned long long lo = data.toUInt(pos, true); pos += 4; - d->length = d->sampleRate > 0 ? - (d->data.mid(pos, 4).toUInt(true)) / d->sampleRate + highLength : 0; - pos += 4; + d->sampleFrames = (hi << 32) | lo; - // Uncompressed bitrate: + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast<int>(length + 0.5); + d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5); + } - //d->bitrate = ((d->sampleRate * d->channels) / 1000) * d->sampleWidth; - - // Real bitrate: - - d->bitrate = d->length > 0 ? ((d->streamLength * 8UL) / d->length) / 1000 : 0; - - d->signature = d->data.mid(pos, 32); + if(data.size() >= pos + 16) + d->signature = data.mid(pos, 16); } diff --git a/Frameworks/TagLib/taglib/taglib/flac/flacproperties.h b/Frameworks/TagLib/taglib/taglib/flac/flacproperties.h index 5bba6417b..743e58722 100644 --- a/Frameworks/TagLib/taglib/taglib/flac/flacproperties.h +++ b/Frameworks/TagLib/taglib/taglib/flac/flacproperties.h @@ -64,22 +64,72 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; - virtual int length() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + /*! + * Returns the number of bits per audio sample as read from the FLAC + * identification header. + */ + int bitsPerSample() const; + /*! * Returns the sample width as read from the FLAC identification * header. + * + * \note This method is just an alias of bitsPerSample(). + * + * \deprecated */ - int sampleWidth() const; + TAGLIB_DEPRECATED int sampleWidth() const; + + /*! + * Return the number of sample frames. + */ + unsigned long long sampleFrames() const; /*! * Returns the MD5 signature of the uncompressed audio stream as read - * from the stream info header header. + * from the stream info header. */ ByteVector signature() const; @@ -87,7 +137,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(const ByteVector &data, long streamLength); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/flac/flacunknownmetadatablock.cpp b/Frameworks/TagLib/taglib/taglib/flac/flacunknownmetadatablock.cpp index 96e3447f7..f9cf6e658 100644 --- a/Frameworks/TagLib/taglib/taglib/flac/flacunknownmetadatablock.cpp +++ b/Frameworks/TagLib/taglib/taglib/flac/flacunknownmetadatablock.cpp @@ -23,10 +23,6 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - #include <taglib.h> #include <tdebug.h> #include <tstring.h> @@ -34,7 +30,7 @@ using namespace TagLib; -class FLAC::UnknownMetadataBlock::UnknownMetadataBlockPrivate +class FLAC::UnknownMetadataBlock::UnknownMetadataBlockPrivate { public: UnknownMetadataBlockPrivate() : code(0) {} @@ -43,11 +39,10 @@ public: ByteVector data; }; -FLAC::UnknownMetadataBlock::UnknownMetadataBlock(int code, const ByteVector &data) +FLAC::UnknownMetadataBlock::UnknownMetadataBlock(int code, const ByteVector &data) : + d(new UnknownMetadataBlockPrivate()) { - d = new UnknownMetadataBlockPrivate; d->code = code; - //debug(String(data.toHex())); d->data = data; } diff --git a/Frameworks/TagLib/taglib/taglib/it/itfile.cpp b/Frameworks/TagLib/taglib/taglib/it/itfile.cpp new file mode 100644 index 000000000..3b35f0d8b --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/it/itfile.cpp @@ -0,0 +1,335 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include "tstringlist.h" +#include "itfile.h" +#include "tdebug.h" +#include "modfileprivate.h" +#include "tpropertymap.h" + +using namespace TagLib; +using namespace IT; + +class IT::File::FilePrivate +{ +public: + FilePrivate(AudioProperties::ReadStyle propertiesStyle) + : tag(), properties(propertiesStyle) + { + } + + Mod::Tag tag; + IT::Properties properties; +}; + +IT::File::File(FileName file, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + Mod::FileBase(file), + d(new FilePrivate(propertiesStyle)) +{ + if(isOpen()) + read(readProperties); +} + +IT::File::File(IOStream *stream, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + Mod::FileBase(stream), + d(new FilePrivate(propertiesStyle)) +{ + if(isOpen()) + read(readProperties); +} + +IT::File::~File() +{ + delete d; +} + +Mod::Tag *IT::File::tag() const +{ + return &d->tag; +} + +PropertyMap IT::File::properties() const +{ + return d->tag.properties(); +} + +PropertyMap IT::File::setProperties(const PropertyMap &properties) +{ + return d->tag.setProperties(properties); +} + +IT::Properties *IT::File::audioProperties() const +{ + return &d->properties; +} + +bool IT::File::save() +{ + if(readOnly()) + { + debug("IT::File::save() - Cannot save to a read only file."); + return false; + } + seek(4); + writeString(d->tag.title(), 25); + writeByte(0); + + seek(2, Current); + + unsigned short length = 0; + unsigned short instrumentCount = 0; + unsigned short sampleCount = 0; + + if(!readU16L(length) || !readU16L(instrumentCount) || !readU16L(sampleCount)) + return false; + + seek(15, Current); + + // write comment as instrument and sample names: + StringList lines = d->tag.comment().split("\n"); + for(unsigned short i = 0; i < instrumentCount; ++ i) { + seek(192L + length + ((long)i << 2)); + unsigned long instrumentOffset = 0; + if(!readU32L(instrumentOffset)) + return false; + + seek(instrumentOffset + 32); + + if(i < lines.size()) + writeString(lines[i], 25); + else + writeString(String(), 25); + writeByte(0); + } + + for(unsigned short i = 0; i < sampleCount; ++ i) { + seek(192L + length + ((long)instrumentCount << 2) + ((long)i << 2)); + unsigned long sampleOffset = 0; + if(!readU32L(sampleOffset)) + return false; + + seek(sampleOffset + 20); + + if((unsigned int)(i + instrumentCount) < lines.size()) + writeString(lines[i + instrumentCount], 25); + else + writeString(String(), 25); + writeByte(0); + } + + // write rest as message: + StringList messageLines; + for(unsigned int i = instrumentCount + sampleCount; i < lines.size(); ++ i) + messageLines.append(lines[i]); + ByteVector message = messageLines.toString("\r").data(String::Latin1); + + // it's actually not really stated if the message needs a + // terminating NUL but it does not hurt to add one: + if(message.size() > 7999) + message.resize(7999); + message.append((char)0); + + unsigned short special = 0; + unsigned short messageLength = 0; + unsigned long messageOffset = 0; + + seek(46); + if(!readU16L(special)) + return false; + + unsigned long fileSize = File::length(); + if(special & Properties::MessageAttached) { + seek(54); + if(!readU16L(messageLength) || !readU32L(messageOffset)) + return false; + + if(messageLength == 0) + messageOffset = fileSize; + } + else + { + messageOffset = fileSize; + seek(46); + writeU16L(special | 0x1); + } + + if(messageOffset + messageLength >= fileSize) { + // append new message + seek(54); + writeU16L(message.size()); + writeU32L(messageOffset); + seek(messageOffset); + writeBlock(message); + truncate(messageOffset + message.size()); + } + else { + // Only overwrite existing message. + // I'd need to parse (understand!) the whole file for more. + // Although I could just move the message to the end of file + // and let the existing one be, but that would waste space. + message.resize(messageLength, 0); + seek(messageOffset); + writeBlock(message); + } + return true; +} + +void IT::File::read(bool) +{ + if(!isOpen()) + return; + + seek(0); + READ_ASSERT(readBlock(4) == "IMPM"); + READ_STRING(d->tag.setTitle, 26); + + seek(2, Current); + + READ_U16L_AS(length); + READ_U16L_AS(instrumentCount); + READ_U16L_AS(sampleCount); + + d->properties.setInstrumentCount(instrumentCount); + d->properties.setSampleCount(sampleCount); + READ_U16L(d->properties.setPatternCount); + READ_U16L(d->properties.setVersion); + READ_U16L(d->properties.setCompatibleVersion); + READ_U16L(d->properties.setFlags); + READ_U16L_AS(special); + d->properties.setSpecial(special); + READ_BYTE(d->properties.setGlobalVolume); + READ_BYTE(d->properties.setMixVolume); + READ_BYTE(d->properties.setBpmSpeed); + READ_BYTE(d->properties.setTempo); + READ_BYTE(d->properties.setPanningSeparation); + READ_BYTE(d->properties.setPitchWheelDepth); + + // IT supports some kind of comment tag. Still, the + // sample/instrument names are abused as comments so + // I just add all together. + String message; + if(special & Properties::MessageAttached) { + READ_U16L_AS(messageLength); + READ_U32L_AS(messageOffset); + seek(messageOffset); + ByteVector messageBytes = readBlock(messageLength); + READ_ASSERT(messageBytes.size() == messageLength); + int index = messageBytes.find((char) 0); + if(index > -1) + messageBytes.resize(index, 0); + messageBytes.replace('\r', '\n'); + message = messageBytes; + } + + seek(64); + + ByteVector pannings = readBlock(64); + ByteVector volumes = readBlock(64); + READ_ASSERT(pannings.size() == 64 && volumes.size() == 64); + int channels = 0; + for(int i = 0; i < 64; ++ i) { + // Strictly speaking an IT file has always 64 channels, but + // I don't count disabled and muted channels. + // But this always gives 64 channels for all my files anyway. + // Strangely VLC does report other values. I wonder how VLC + // gets it's values. + if((unsigned char) pannings[i] < 128 && volumes[i] > 0) + ++channels; + } + d->properties.setChannels(channels); + + // real length might be shorter because of skips and terminator + unsigned short realLength = 0; + for(unsigned short i = 0; i < length; ++ i) { + READ_BYTE_AS(order); + if(order == 255) break; + if(order != 254) ++ realLength; + } + d->properties.setLengthInPatterns(realLength); + + StringList comment; + // Note: I found files that have nil characters somewhere + // in the instrument/sample names and more characters + // afterwards. The spec does not mention such a case. + // Currently I just discard anything after a nil, but + // e.g. VLC seems to interpret a nil as a space. I + // don't know what is the proper behaviour. + for(unsigned short i = 0; i < instrumentCount; ++ i) { + seek(192L + length + ((long)i << 2)); + READ_U32L_AS(instrumentOffset); + seek(instrumentOffset); + + ByteVector instrumentMagic = readBlock(4); + READ_ASSERT(instrumentMagic == "IMPI"); + + READ_STRING_AS(dosFileName, 13); + + seek(15, Current); + + READ_STRING_AS(instrumentName, 26); + comment.append(instrumentName); + } + + for(unsigned short i = 0; i < sampleCount; ++ i) { + seek(192L + length + ((long)instrumentCount << 2) + ((long)i << 2)); + READ_U32L_AS(sampleOffset); + + seek(sampleOffset); + + ByteVector sampleMagic = readBlock(4); + READ_ASSERT(sampleMagic == "IMPS"); + + READ_STRING_AS(dosFileName, 13); + READ_BYTE_AS(globalVolume); + READ_BYTE_AS(sampleFlags); + READ_BYTE_AS(sampleVolume); + READ_STRING_AS(sampleName, 26); + /* + READ_BYTE_AS(sampleCvt); + READ_BYTE_AS(samplePanning); + READ_U32L_AS(sampleLength); + READ_U32L_AS(loopStart); + READ_U32L_AS(loopStop); + READ_U32L_AS(c5speed); + READ_U32L_AS(sustainLoopStart); + READ_U32L_AS(sustainLoopEnd); + READ_U32L_AS(sampleDataOffset); + READ_BYTE_AS(vibratoSpeed); + READ_BYTE_AS(vibratoDepth); + READ_BYTE_AS(vibratoRate); + READ_BYTE_AS(vibratoType); + */ + + comment.append(sampleName); + } + + if(message.size() > 0) + comment.append(message); + d->tag.setComment(comment.toString("\n")); + d->tag.setTrackerName("Impulse Tracker"); +} diff --git a/Frameworks/TagLib/taglib/taglib/it/itfile.h b/Frameworks/TagLib/taglib/taglib/it/itfile.h new file mode 100644 index 000000000..19327dc65 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/it/itfile.h @@ -0,0 +1,109 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_ITFILE_H +#define TAGLIB_ITFILE_H + +#include "tfile.h" +#include "audioproperties.h" +#include "taglib_export.h" +#include "modfilebase.h" +#include "modtag.h" +#include "itproperties.h" + +namespace TagLib { + + namespace IT { + + class TAGLIB_EXPORT File : public Mod::FileBase { + public: + /*! + * Constructs a Impulse Tracker file from \a file. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + */ + File(FileName file, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Constructs a Impulse Tracker file from \a stream. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + File(IOStream *stream, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + Mod::Tag *tag() const; + + /*! + * Forwards to Mod::Tag::properties(). + * BIC: will be removed once File::toDict() is made virtual + */ + PropertyMap properties() const; + + /*! + * Forwards to Mod::Tag::setProperties(). + * BIC: will be removed once File::setProperties() is made virtual + */ + PropertyMap setProperties(const PropertyMap &); + + /*! + * Returns the IT::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + IT::Properties *audioProperties() const; + + /*! + * Save the file. + * This is the same as calling save(AllTags); + * + * \note Saving Impulse Tracker tags is not supported. + */ + bool save(); + + + private: + File(const File &); + File &operator=(const File &); + + void read(bool readProperties); + + class FilePrivate; + FilePrivate *d; + }; + } +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/it/itproperties.cpp b/Frameworks/TagLib/taglib/taglib/it/itproperties.cpp new file mode 100644 index 000000000..c317b660e --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/it/itproperties.cpp @@ -0,0 +1,260 @@ +/*************************************************************************** + copyright :(C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include "itproperties.h" + +using namespace TagLib; +using namespace IT; + +class IT::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate() : + channels(0), + lengthInPatterns(0), + instrumentCount(0), + sampleCount(0), + patternCount(0), + version(0), + compatibleVersion(0), + flags(0), + special(0), + globalVolume(0), + mixVolume(0), + tempo(0), + bpmSpeed(0), + panningSeparation(0), + pitchWheelDepth(0) + { + } + + int channels; + unsigned short lengthInPatterns; + unsigned short instrumentCount; + unsigned short sampleCount; + unsigned short patternCount; + unsigned short version; + unsigned short compatibleVersion; + unsigned short flags; + unsigned short special; + unsigned char globalVolume; + unsigned char mixVolume; + unsigned char tempo; + unsigned char bpmSpeed; + unsigned char panningSeparation; + unsigned char pitchWheelDepth; +}; + +IT::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) : + AudioProperties(propertiesStyle), + d(new PropertiesPrivate()) +{ +} + +IT::Properties::~Properties() +{ + delete d; +} + +int IT::Properties::length() const +{ + return 0; +} + +int IT::Properties::lengthInSeconds() const +{ + return 0; +} + +int IT::Properties::lengthInMilliseconds() const +{ + return 0; +} + +int IT::Properties::bitrate() const +{ + return 0; +} + +int IT::Properties::sampleRate() const +{ + return 0; +} + +int IT::Properties::channels() const +{ + return d->channels; +} + +unsigned short IT::Properties::lengthInPatterns() const +{ + return d->lengthInPatterns; +} + +bool IT::Properties::stereo() const +{ + return d->flags & Stereo; +} + +unsigned short IT::Properties::instrumentCount() const +{ + return d->instrumentCount; +} + +unsigned short IT::Properties::sampleCount() const +{ + return d->sampleCount; +} + +unsigned short IT::Properties::patternCount() const +{ + return d->patternCount; +} + +unsigned short IT::Properties::version() const +{ + return d->version; +} + +unsigned short IT::Properties::compatibleVersion() const +{ + return d->compatibleVersion; +} + +unsigned short IT::Properties::flags() const +{ + return d->flags; +} + +unsigned short IT::Properties::special() const +{ + return d->special; +} + +unsigned char IT::Properties::globalVolume() const +{ + return d->globalVolume; +} + +unsigned char IT::Properties::mixVolume() const +{ + return d->mixVolume; +} + +unsigned char IT::Properties::tempo() const +{ + return d->tempo; +} + +unsigned char IT::Properties::bpmSpeed() const +{ + return d->bpmSpeed; +} + +unsigned char IT::Properties::panningSeparation() const +{ + return d->panningSeparation; +} + +unsigned char IT::Properties::pitchWheelDepth() const +{ + return d->pitchWheelDepth; +} + +void IT::Properties::setChannels(int channels) +{ + d->channels = channels; +} + +void IT::Properties::setLengthInPatterns(unsigned short lengthInPatterns) +{ + d->lengthInPatterns = lengthInPatterns; +} + +void IT::Properties::setInstrumentCount(unsigned short instrumentCount) +{ + d->instrumentCount = instrumentCount; +} + +void IT::Properties::setSampleCount(unsigned short sampleCount) +{ + d->sampleCount = sampleCount; +} + +void IT::Properties::setPatternCount(unsigned short patternCount) +{ + d->patternCount = patternCount; +} + +void IT::Properties::setFlags(unsigned short flags) +{ + d->flags = flags; +} + +void IT::Properties::setSpecial(unsigned short special) +{ + d->special = special; +} + +void IT::Properties::setCompatibleVersion(unsigned short compatibleVersion) +{ + d->compatibleVersion = compatibleVersion; +} + +void IT::Properties::setVersion(unsigned short version) +{ + d->version = version; +} + +void IT::Properties::setGlobalVolume(unsigned char globalVolume) +{ + d->globalVolume = globalVolume; +} + +void IT::Properties::setMixVolume(unsigned char mixVolume) +{ + d->mixVolume = mixVolume; +} + +void IT::Properties::setTempo(unsigned char tempo) +{ + d->tempo = tempo; +} + +void IT::Properties::setBpmSpeed(unsigned char bpmSpeed) +{ + d->bpmSpeed = bpmSpeed; +} + +void IT::Properties::setPanningSeparation(unsigned char panningSeparation) +{ + d->panningSeparation = panningSeparation; +} + +void IT::Properties::setPitchWheelDepth(unsigned char pitchWheelDepth) +{ + d->pitchWheelDepth = pitchWheelDepth; +} diff --git a/Frameworks/TagLib/taglib/taglib/it/itproperties.h b/Frameworks/TagLib/taglib/taglib/it/itproperties.h new file mode 100644 index 000000000..be1c9d264 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/it/itproperties.h @@ -0,0 +1,107 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_ITPROPERTIES_H +#define TAGLIB_ITPROPERTIES_H + +#include "taglib.h" +#include "audioproperties.h" + +namespace TagLib { + namespace IT { + class TAGLIB_EXPORT Properties : public AudioProperties { + friend class File; + public: + /*! Flag bits. */ + enum { + Stereo = 1, + Vol0MixOptimizations = 2, + UseInstruments = 4, + LinearSlides = 8, + OldEffects = 16, + LinkEffects = 32, + UseMidiPitchController = 64, + RequestEmbeddedMidiConf = 128 + }; + + /*! Special bits. */ + enum { + MessageAttached = 1, + MidiConfEmbedded = 8 + }; + + Properties(AudioProperties::ReadStyle propertiesStyle); + virtual ~Properties(); + + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; + + unsigned short lengthInPatterns() const; + bool stereo() const; + unsigned short instrumentCount() const; + unsigned short sampleCount() const; + unsigned short patternCount() const; + unsigned short version() const; + unsigned short compatibleVersion() const; + unsigned short flags() const; + unsigned short special() const; + unsigned char globalVolume() const; + unsigned char mixVolume() const; + unsigned char tempo() const; + unsigned char bpmSpeed() const; + unsigned char panningSeparation() const; + unsigned char pitchWheelDepth() const; + + void setChannels(int channels); + void setLengthInPatterns(unsigned short lengthInPatterns); + void setInstrumentCount(unsigned short instrumentCount); + void setSampleCount (unsigned short sampleCount); + void setPatternCount(unsigned short patternCount); + void setVersion (unsigned short version); + void setCompatibleVersion(unsigned short compatibleVersion); + void setFlags (unsigned short flags); + void setSpecial (unsigned short special); + void setGlobalVolume(unsigned char globalVolume); + void setMixVolume (unsigned char mixVolume); + void setTempo (unsigned char tempo); + void setBpmSpeed (unsigned char bpmSpeed); + void setPanningSeparation(unsigned char panningSeparation); + void setPitchWheelDepth (unsigned char pitchWheelDepth); + + private: + Properties(const Properties&); + Properties &operator=(const Properties&); + + class PropertiesPrivate; + PropertiesPrivate *d; + }; + } +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/m4a/mp4file.h b/Frameworks/TagLib/taglib/taglib/m4a/mp4file.h deleted file mode 100755 index c2a2c32c2..000000000 --- a/Frameworks/TagLib/taglib/taglib/m4a/mp4file.h +++ /dev/null @@ -1,162 +0,0 @@ -/*************************************************************************** - copyright : (C) 2002, 2003 by Jochen Issing - email : jochen.issing@isign-softart.de - ***************************************************************************/ - -/*************************************************************************** - * This library is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Lesser General Public License version * - * 2.1 as published by the Free Software Foundation. * - * * - * This library is distributed in the hope that it will be useful, but * - * WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU Lesser General Public * - * License along with this library; if not, write to the Free Software * - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * - * USA * - ***************************************************************************/ - -#ifndef TAGLIB_MP4FILE_H -#define TAGLIB_MP4FILE_H - -#include "tfile.h" -#include "audioproperties.h" - -#include "mp4fourcc.h" - -namespace TagLib { - - typedef unsigned long long ulonglong; - - class Tag; - - namespace MP4 - { - class Mp4TagsProxy; - class Mp4PropsProxy; - - //! An implementation of TagLib::File with mp4 itunes specific methods - - /*! - * This implements and provides an interface for mp4 itunes files to the - * TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing - * the abstract TagLib::File API as well as providing some additional - * information specific to mp4 itunes files. (TODO) - */ - - class File : public TagLib::File - { - public: - /*! - * Contructs an mp4 itunes file object without reading a file. Allows object - * fields to be set up before reading. - */ - File(); - - /*! - * Contructs an mp4 itunes file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. - */ - File(FileName file, bool readProperties = true, - TagLib::AudioProperties::ReadStyle propertiesStyle = TagLib::AudioProperties::Average); - - /*! - * Destroys this instance of the File. - */ - virtual ~File(); - - /*! - * Returns the Tag for this file. This will be an APE tag, an ID3v1 tag - * or a combination of the two. - */ - virtual TagLib::Tag *tag() const; - - /*! - * Returns the mp4 itunes::Properties for this file. If no audio properties - * were read then this will return a null pointer. - */ - virtual AudioProperties *audioProperties() const; - - /*! - * Reads from mp4 itunes file. If \a readProperties is true the file's - * audio properties will also be read using \a propertiesStyle. If false, - * \a propertiesStyle is ignored. - */ - void read(bool readProperties = true, - TagLib::AudioProperties::ReadStyle propertiesStyle = TagLib::AudioProperties::Average); - - /*! - * Saves the file. - */ - virtual bool save(); - - /*! - * This will remove all tags. - * - * \note This will also invalidate pointers to the tags - * as their memory will be freed. - * \note In order to make the removal permanent save() still needs to be called - */ - void remove(); - - /*! - * Helper function for parsing the MP4 file - reads the size and type of the next box. - * Returns true if read succeeded - not at EOF - */ - bool readSizeAndType( TagLib::uint& size, MP4::Fourcc& fourcc ); - - /*! - * Helper function to read the length of an descriptor in systems manner - */ - TagLib::uint readSystemsLen(); - - /*! - * Helper function for reading an unsigned int out of the file (big endian method) - */ - bool readInt( TagLib::uint& toRead ); - - /*! - * Helper function for reading an unsigned short out of the file (big endian method) - */ - bool readShort( TagLib::uint& toRead ); - - /*! - * Helper function for reading an unsigned long long (64bit) out of the file (big endian method) - */ - bool readLongLong( TagLib::ulonglong& toRead ); - - /*! - * Helper function to read a fourcc code - */ - bool readFourcc( TagLib::MP4::Fourcc& fourcc ); - - /*! - * Function to get the tags proxy for registration of the tags boxes. - * The proxy provides direct access to the data boxes of the certain tags - normally - * covered by several levels of subboxes - */ - Mp4TagsProxy* tagProxy() const; - - /*! - * Function to get the properties proxy for registration of the properties boxes. - * The proxy provides direct access to the needed boxes describing audio properties. - */ - Mp4PropsProxy* propProxy() const; - - private: - File(const File &); - File &operator=(const File &); - - class FilePrivate; - FilePrivate *d; - }; - - } // namespace MP4 - -} // namespace TagLib - -#endif // TAGLIB_MP4FILE_H diff --git a/Frameworks/TagLib/taglib/taglib/mod/modfile.cpp b/Frameworks/TagLib/taglib/taglib/mod/modfile.cpp new file mode 100644 index 000000000..00d330ebd --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mod/modfile.cpp @@ -0,0 +1,192 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include "modfile.h" +#include "tstringlist.h" +#include "tdebug.h" +#include "modfileprivate.h" +#include "tpropertymap.h" + +using namespace TagLib; +using namespace Mod; + +class Mod::File::FilePrivate +{ +public: + FilePrivate(AudioProperties::ReadStyle propertiesStyle) + : properties(propertiesStyle) + { + } + + Mod::Tag tag; + Mod::Properties properties; +}; + +Mod::File::File(FileName file, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + Mod::FileBase(file), + d(new FilePrivate(propertiesStyle)) +{ + if(isOpen()) + read(readProperties); +} + +Mod::File::File(IOStream *stream, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + Mod::FileBase(stream), + d(new FilePrivate(propertiesStyle)) +{ + if(isOpen()) + read(readProperties); +} + +Mod::File::~File() +{ + delete d; +} + +Mod::Tag *Mod::File::tag() const +{ + return &d->tag; +} + +Mod::Properties *Mod::File::audioProperties() const +{ + return &d->properties; +} + +PropertyMap Mod::File::properties() const +{ + return d->tag.properties(); +} + +PropertyMap Mod::File::setProperties(const PropertyMap &properties) +{ + return d->tag.setProperties(properties); +} + +bool Mod::File::save() +{ + if(readOnly()) { + debug("Mod::File::save() - Cannot save to a read only file."); + return false; + } + seek(0); + writeString(d->tag.title(), 20); + StringList lines = d->tag.comment().split("\n"); + unsigned int n = std::min(lines.size(), d->properties.instrumentCount()); + for(unsigned int i = 0; i < n; ++ i) { + writeString(lines[i], 22); + seek(8, Current); + } + + for(unsigned int i = n; i < d->properties.instrumentCount(); ++ i) { + writeString(String(), 22); + seek(8, Current); + } + return true; +} + +void Mod::File::read(bool) +{ + if(!isOpen()) + return; + + seek(1080); + ByteVector modId = readBlock(4); + READ_ASSERT(modId.size() == 4); + + int channels = 4; + unsigned int instruments = 31; + if(modId == "M.K." || modId == "M!K!" || modId == "M&K!" || modId == "N.T.") { + d->tag.setTrackerName("ProTracker"); + channels = 4; + } + else if(modId.startsWith("FLT") || modId.startsWith("TDZ")) { + d->tag.setTrackerName("StarTrekker"); + char digit = modId[3]; + READ_ASSERT(digit >= '0' && digit <= '9'); + channels = digit - '0'; + } + else if(modId.endsWith("CHN")) { + d->tag.setTrackerName("StarTrekker"); + char digit = modId[0]; + READ_ASSERT(digit >= '0' && digit <= '9'); + channels = digit - '0'; + } + else if(modId == "CD81" || modId == "OKTA") { + d->tag.setTrackerName("Atari Oktalyzer"); + channels = 8; + } + else if(modId.endsWith("CH") || modId.endsWith("CN")) { + d->tag.setTrackerName("TakeTracker"); + char digit = modId[0]; + READ_ASSERT(digit >= '0' && digit <= '9'); + channels = (digit - '0') * 10; + digit = modId[1]; + READ_ASSERT(digit >= '0' && digit <= '9'); + channels += digit - '0'; + } + else { + // Not sure if this is correct. I'd need a file + // created with NoiseTracker to check this. + d->tag.setTrackerName("NoiseTracker"); // probably + channels = 4; + instruments = 15; + } + d->properties.setChannels(channels); + d->properties.setInstrumentCount(instruments); + + seek(0); + READ_STRING(d->tag.setTitle, 20); + + StringList comment; + for(unsigned int i = 0; i < instruments; ++ i) { + READ_STRING_AS(instrumentName, 22); + // value in words, * 2 (<< 1) for bytes: + READ_U16B_AS(sampleLength); + + READ_BYTE_AS(fineTuneByte); + int fineTune = fineTuneByte & 0xF; + // > 7 means negative value + if(fineTune > 7) fineTune -= 16; + + READ_BYTE_AS(volume); + if(volume > 64) volume = 64; + // volume in decibels: 20 * log10(volume / 64) + + // value in words, * 2 (<< 1) for bytes: + READ_U16B_AS(repeatStart); + // value in words, * 2 (<< 1) for bytes: + READ_U16B_AS(repatLength); + + comment.append(instrumentName); + } + + READ_BYTE(d->properties.setLengthInPatterns); + + d->tag.setComment(comment.toString("\n")); +} diff --git a/Frameworks/TagLib/taglib/taglib/mod/modfile.h b/Frameworks/TagLib/taglib/taglib/mod/modfile.h new file mode 100644 index 000000000..b66452d79 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mod/modfile.h @@ -0,0 +1,114 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MODFILE_H +#define TAGLIB_MODFILE_H + +#include "tfile.h" +#include "audioproperties.h" +#include "taglib_export.h" +#include "modfilebase.h" +#include "modtag.h" +#include "modproperties.h" + +namespace TagLib { + + namespace Mod { + + class TAGLIB_EXPORT File : public TagLib::Mod::FileBase + { + public: + /*! + * Constructs a Protracker file from \a file. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + */ + File(FileName file, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Constructs a Protracker file from \a stream. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + File(IOStream *stream, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + Mod::Tag *tag() const; + + /*! + * Implements the unified property interface -- export function. + * Forwards to Mod::Tag::properties(). + */ + PropertyMap properties() const; + + /*! + * Implements the unified property interface -- import function. + * Forwards to Mod::Tag::setProperties(). + */ + PropertyMap setProperties(const PropertyMap &); + /*! + * Returns the Mod::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + Mod::Properties *audioProperties() const; + + /*! + * Save the file. + * This is the same as calling save(AllTags); + * + * \note Saving Protracker tags is not supported. + */ + bool save(); + + private: + File(const File &); + File &operator=(const File &); + + void read(bool readProperties); + + class FilePrivate; + FilePrivate *d; + }; + + } + +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mod/modfilebase.cpp b/Frameworks/TagLib/taglib/taglib/mod/modfilebase.cpp new file mode 100644 index 000000000..142f669ff --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mod/modfilebase.cpp @@ -0,0 +1,125 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include "tdebug.h" +#include "modfilebase.h" + +using namespace TagLib; +using namespace Mod; + +Mod::FileBase::FileBase(FileName file) : TagLib::File(file) +{ +} + +Mod::FileBase::FileBase(IOStream *stream) : TagLib::File(stream) +{ +} + +void Mod::FileBase::writeString(const String &s, unsigned long size, char padding) +{ + ByteVector data(s.data(String::Latin1)); + data.resize(size, padding); + writeBlock(data); +} + +bool Mod::FileBase::readString(String &s, unsigned long size) +{ + ByteVector data(readBlock(size)); + if(data.size() < size) return false; + int index = data.find((char) 0); + if(index > -1) + { + data.resize(index); + } + data.replace('\xff', ' '); + + s = data; + return true; +} + +void Mod::FileBase::writeByte(unsigned char byte) +{ + ByteVector data(1, byte); + writeBlock(data); +} + +void Mod::FileBase::writeU16L(unsigned short number) +{ + writeBlock(ByteVector::fromShort(number, false)); +} + +void Mod::FileBase::writeU32L(unsigned long number) +{ + writeBlock(ByteVector::fromUInt(number, false)); +} + +void Mod::FileBase::writeU16B(unsigned short number) +{ + writeBlock(ByteVector::fromShort(number, true)); +} + +void Mod::FileBase::writeU32B(unsigned long number) +{ + writeBlock(ByteVector::fromUInt(number, true)); +} + +bool Mod::FileBase::readByte(unsigned char &byte) +{ + ByteVector data(readBlock(1)); + if(data.size() < 1) return false; + byte = data[0]; + return true; +} + +bool Mod::FileBase::readU16L(unsigned short &number) +{ + ByteVector data(readBlock(2)); + if(data.size() < 2) return false; + number = data.toUShort(false); + return true; +} + +bool Mod::FileBase::readU32L(unsigned long &number) { + ByteVector data(readBlock(4)); + if(data.size() < 4) return false; + number = data.toUInt(false); + return true; +} + +bool Mod::FileBase::readU16B(unsigned short &number) +{ + ByteVector data(readBlock(2)); + if(data.size() < 2) return false; + number = data.toUShort(true); + return true; +} + +bool Mod::FileBase::readU32B(unsigned long &number) { + ByteVector data(readBlock(4)); + if(data.size() < 4) return false; + number = data.toUInt(true); + return true; +} diff --git a/Frameworks/TagLib/taglib/taglib/mod/modfilebase.h b/Frameworks/TagLib/taglib/taglib/mod/modfilebase.h new file mode 100644 index 000000000..bae97167c --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mod/modfilebase.h @@ -0,0 +1,66 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MODFILEBASE_H +#define TAGLIB_MODFILEBASE_H + +#include "taglib.h" +#include "tfile.h" +#include "tstring.h" +#include "tlist.h" +#include "taglib_export.h" + +#include <algorithm> + +namespace TagLib { + + namespace Mod { + + class TAGLIB_EXPORT FileBase : public TagLib::File + { + protected: + FileBase(FileName file); + FileBase(IOStream *stream); + + void writeString(const String &s, unsigned long size, char padding = 0); + void writeByte(unsigned char byte); + void writeU16L(unsigned short number); + void writeU32L(unsigned long number); + void writeU16B(unsigned short number); + void writeU32B(unsigned long number); + + bool readString(String &s, unsigned long size); + bool readByte(unsigned char &byte); + bool readU16L(unsigned short &number); + bool readU32L(unsigned long &number); + bool readU16B(unsigned short &number); + bool readU32B(unsigned long &number); + }; + + } + +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mod/modfileprivate.h b/Frameworks/TagLib/taglib/taglib/mod/modfileprivate.h new file mode 100644 index 000000000..781db74c9 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mod/modfileprivate.h @@ -0,0 +1,67 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef TAGLIB_MODFILEPRIVATE_H +#define TAGLIB_MODFILEPRIVATE_H + +// some helper-macros only used internally by (s3m|it|xm)file.cpp +#define READ_ASSERT(cond) \ + if(!(cond)) \ + { \ + setValid(false); \ + return; \ + } + +#define READ(setter,type,read) \ + { \ + type number; \ + READ_ASSERT(read(number)); \ + setter(number); \ + } + +#define READ_BYTE(setter) READ(setter,unsigned char,readByte) +#define READ_U16L(setter) READ(setter,unsigned short,readU16L) +#define READ_U32L(setter) READ(setter,unsigned long,readU32L) +#define READ_U16B(setter) READ(setter,unsigned short,readU16B) +#define READ_U32B(setter) READ(setter,unsigned long,readU32B) + +#define READ_STRING(setter,size) \ + { \ + String s; \ + READ_ASSERT(readString(s, size)); \ + setter(s); \ + } + +#define READ_AS(type,name,read) \ + type name = 0; \ + READ_ASSERT(read(name)); + +#define READ_BYTE_AS(name) READ_AS(unsigned char,name,readByte) +#define READ_U16L_AS(name) READ_AS(unsigned short,name,readU16L) +#define READ_U32L_AS(name) READ_AS(unsigned long,name,readU32L) +#define READ_U16B_AS(name) READ_AS(unsigned short,name,readU16B) +#define READ_U32B_AS(name) READ_AS(unsigned long,name,readU32B) + +#define READ_STRING_AS(name,size) \ + String name; \ + READ_ASSERT(readString(name, size)); + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mod/modproperties.cpp b/Frameworks/TagLib/taglib/taglib/mod/modproperties.cpp new file mode 100644 index 000000000..c6bf39475 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mod/modproperties.cpp @@ -0,0 +1,111 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include "modproperties.h" + +using namespace TagLib; +using namespace Mod; + +class Mod::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate() : + channels(0), + instrumentCount(0), + lengthInPatterns(0) + { + } + + int channels; + unsigned int instrumentCount; + unsigned char lengthInPatterns; +}; + +Mod::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) : + AudioProperties(propertiesStyle), + d(new PropertiesPrivate()) +{ +} + +Mod::Properties::~Properties() +{ + delete d; +} + +int Mod::Properties::length() const +{ + return 0; +} + +int Mod::Properties::lengthInSeconds() const +{ + return 0; +} + +int Mod::Properties::lengthInMilliseconds() const +{ + return 0; +} + +int Mod::Properties::bitrate() const +{ + return 0; +} + +int Mod::Properties::sampleRate() const +{ + return 0; +} + +int Mod::Properties::channels() const +{ + return d->channels; +} + +unsigned int Mod::Properties::instrumentCount() const +{ + return d->instrumentCount; +} + +unsigned char Mod::Properties::lengthInPatterns() const +{ + return d->lengthInPatterns; +} + +void Mod::Properties::setChannels(int channels) +{ + d->channels = channels; +} + +void Mod::Properties::setInstrumentCount(unsigned int instrumentCount) +{ + d->instrumentCount = instrumentCount; +} + +void Mod::Properties::setLengthInPatterns(unsigned char lengthInPatterns) +{ + d->lengthInPatterns = lengthInPatterns; +} diff --git a/Frameworks/TagLib/taglib/taglib/mod/modproperties.h b/Frameworks/TagLib/taglib/taglib/mod/modproperties.h new file mode 100644 index 000000000..471229431 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mod/modproperties.h @@ -0,0 +1,71 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MODPROPERTIES_H +#define TAGLIB_MODPROPERTIES_H + +#include "taglib.h" +#include "audioproperties.h" + +namespace TagLib { + + namespace Mod { + + class TAGLIB_EXPORT Properties : public AudioProperties + { + public: + Properties(AudioProperties::ReadStyle propertiesStyle); + virtual ~Properties(); + + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; + + unsigned int instrumentCount() const; + unsigned char lengthInPatterns() const; + + void setChannels(int channels); + + void setInstrumentCount(unsigned int sampleCount); + void setLengthInPatterns(unsigned char lengthInPatterns); + + private: + friend class File; + + Properties(const Properties&); + Properties &operator=(const Properties&); + + class PropertiesPrivate; + PropertiesPrivate *d; + }; + + } + +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mod/modtag.cpp b/Frameworks/TagLib/taglib/taglib/mod/modtag.cpp new file mode 100644 index 000000000..0e8d03719 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mod/modtag.cpp @@ -0,0 +1,174 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include "modtag.h" +#include "tstringlist.h" +#include "tpropertymap.h" + +using namespace TagLib; +using namespace Mod; + +class Mod::Tag::TagPrivate +{ +public: + TagPrivate() + { + } + + String title; + String comment; + String trackerName; +}; + +Mod::Tag::Tag() : + TagLib::Tag(), + d(new TagPrivate()) +{ +} + +Mod::Tag::~Tag() +{ + delete d; +} + +String Mod::Tag::title() const +{ + return d->title; +} + +String Mod::Tag::artist() const +{ + return String(); +} + +String Mod::Tag::album() const +{ + return String(); +} + +String Mod::Tag::comment() const +{ + return d->comment; +} + +String Mod::Tag::genre() const +{ + return String(); +} + +unsigned int Mod::Tag::year() const +{ + return 0; +} + +unsigned int Mod::Tag::track() const +{ + return 0; +} + +String Mod::Tag::trackerName() const +{ + return d->trackerName; +} + +void Mod::Tag::setTitle(const String &title) +{ + d->title = title; +} + +void Mod::Tag::setArtist(const String &) +{ +} + +void Mod::Tag::setAlbum(const String &) +{ +} + +void Mod::Tag::setComment(const String &comment) +{ + d->comment = comment; +} + +void Mod::Tag::setGenre(const String &) +{ +} + +void Mod::Tag::setYear(unsigned int) +{ +} + +void Mod::Tag::setTrack(unsigned int) +{ +} + +void Mod::Tag::setTrackerName(const String &trackerName) +{ + d->trackerName = trackerName; +} + +PropertyMap Mod::Tag::properties() const +{ + PropertyMap properties; + properties["TITLE"] = d->title; + properties["COMMENT"] = d->comment; + if(!(d->trackerName.isEmpty())) + properties["TRACKERNAME"] = d->trackerName; + return properties; +} + +PropertyMap Mod::Tag::setProperties(const PropertyMap &origProps) +{ + PropertyMap properties(origProps); + properties.removeEmpty(); + StringList oneValueSet; + if(properties.contains("TITLE")) { + d->title = properties["TITLE"].front(); + oneValueSet.append("TITLE"); + } else + d->title.clear(); + + if(properties.contains("COMMENT")) { + d->comment = properties["COMMENT"].front(); + oneValueSet.append("COMMENT"); + } else + d->comment.clear(); + + if(properties.contains("TRACKERNAME")) { + d->trackerName = properties["TRACKERNAME"].front(); + oneValueSet.append("TRACKERNAME"); + } else + d->trackerName.clear(); + + // for each tag that has been set above, remove the first entry in the corresponding + // value list. The others will be returned as unsupported by this format. + for(StringList::ConstIterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) { + if(properties[*it].size() == 1) + properties.erase(*it); + else + properties[*it].erase( properties[*it].begin() ); + } + return properties; +} diff --git a/Frameworks/TagLib/taglib/taglib/mod/modtag.h b/Frameworks/TagLib/taglib/taglib/mod/modtag.h new file mode 100644 index 000000000..dee066171 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mod/modtag.h @@ -0,0 +1,194 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MODTAG_H +#define TAGLIB_MODTAG_H + +#include "tag.h" + +namespace TagLib { + + namespace Mod { + + /*! + * Tags for module files (Mod, S3M, IT, XM). + * + * Note that only the \a title is supported as such by most + * module file formats. Except for XM files the \a trackerName + * is derived from the file format or the flavour of the file + * format. For XM files it is stored in the file. + * + * The \a comment tag is not strictly supported by module files, + * but it is common practice to abuse instrument/sample/pattern + * names as multiline comments. TagLib does so as well. + */ + class TAGLIB_EXPORT Tag : public TagLib::Tag + { + public: + Tag(); + virtual ~Tag(); + + /*! + * Returns the track name; if no track name is present in the tag + * String::null will be returned. + */ + virtual String title() const; + + /*! + * Not supported by module files. Therefore always returns String::null. + */ + virtual String artist() const; + + /*! + * Not supported by module files. Therefore always returns String::null. + */ + virtual String album() const; + + /*! + * Returns the track comment derived from the instrument/sample/pattern + * names; if no comment is present in the tag String::null will be + * returned. + */ + virtual String comment() const; + + /*! + * Not supported by module files. Therefore always returns String::null. + */ + virtual String genre() const; + + /*! + * Not supported by module files. Therefore always returns 0. + */ + virtual unsigned int year() const; + + /*! + * Not supported by module files. Therefore always returns 0. + */ + virtual unsigned int track() const; + + /*! + * Returns the name of the tracker used to create/edit the module file. + * Only XM files store this tag to the file as such, for other formats + * (Mod, S3M, IT) this is derived from the file type or the flavour of + * the file type. Therefore only XM files might have an empty + * (String::null) tracker name. + */ + String trackerName() const; + + /*! + * Sets the title to \a title. If \a title is String::null then this + * value will be cleared. + * + * The length limits per file type are (1 character = 1 byte): + * Mod 20 characters, S3M 27 characters, IT 25 characters and XM 20 + * characters. + */ + virtual void setTitle(const String &title); + + /*! + * Not supported by module files and therefore ignored. + */ + virtual void setArtist(const String &artist); + + /*! + * Not supported by module files and therefore ignored. + */ + virtual void setAlbum(const String &album); + + /*! + * Sets the comment to \a comment. If \a comment is String::null then + * this value will be cleared. + * + * Note that module file formats don't actually support a comment tag. + * Instead the names of instruments/patterns/samples are abused as + * a multiline comment. Because of this the number of lines in a + * module file is fixed to the number of instruments/patterns/samples. + * + * Also note that the instrument/pattern/sample name length is limited + * an thus the line length in comments are limited. Too big comments + * will be truncated. + * + * The line length limits per file type are (1 character = 1 byte): + * Mod 22 characters, S3M 27 characters, IT 25 characters and XM 22 + * characters. + */ + virtual void setComment(const String &comment); + + /*! + * Not supported by module files and therefore ignored. + */ + virtual void setGenre(const String &genre); + + /*! + * Not supported by module files and therefore ignored. + */ + virtual void setYear(unsigned int year); + + /*! + * Not supported by module files and therefore ignored. + */ + virtual void setTrack(unsigned int track); + + /*! + * Sets the tracker name to \a trackerName. If \a trackerName is + * String::null then this value will be cleared. + * + * Note that only XM files support this tag. Setting the + * tracker name for other module file formats will be ignored. + * + * The length of this tag is limited to 20 characters (1 character + * = 1 byte). + */ + void setTrackerName(const String &trackerName); + + /*! + * Implements the unified property interface -- export function. + * Since the module tag is very limited, the exported map is as well. + */ + PropertyMap properties() const; + + /*! + * Implements the unified property interface -- import function. + * Because of the limitations of the module file tag, any tags besides + * COMMENT, TITLE and, if it is an XM file, TRACKERNAME, will be + * returned. Additionally, if the map contains tags with multiple values, + * all but the first will be contained in the returned map of unsupported + * properties. + */ + PropertyMap setProperties(const PropertyMap &); + + private: + Tag(const Tag &); + Tag &operator=(const Tag &); + + class TagPrivate; + TagPrivate *d; + }; + + } + +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4atom.cpp b/Frameworks/TagLib/taglib/taglib/mp4/mp4atom.cpp index 26d6d25a2..20709212e 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4atom.cpp +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4atom.cpp @@ -23,11 +23,7 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WITH_MP4 +#include <climits> #include <tdebug.h> #include <tstring.h> @@ -35,16 +31,19 @@ using namespace TagLib; -const char *MP4::Atom::containers[10] = { +const char *MP4::Atom::containers[11] = { "moov", "udta", "mdia", "meta", "ilst", "stbl", "minf", "moof", "traf", "trak", + "stsd" }; MP4::Atom::Atom(File *file) { + children.setAutoDelete(true); + offset = file->tell(); ByteVector header = file->readBlock(8); - if (header.size() != 8) { + if(header.size() != 8) { // The atom header must be 8 bytes long, otherwise there is either // trailing garbage or the file is truncated debug("MP4: Couldn't read 8 bytes of data for atom header"); @@ -53,22 +52,28 @@ MP4::Atom::Atom(File *file) return; } - length = header.mid(0, 4).toUInt(); + length = header.toUInt(); - if (length == 1) { - long long longLength = file->readBlock(8).toLongLong(); - if (longLength >= 8 && longLength <= 0xFFFFFFFF) { - // The atom has a 64-bit length, but it's actually a 32-bit value - length = (long)longLength; + if(length == 0) { + // The last atom which extends to the end of the file. + length = file->length() - offset; + } + else if(length == 1) { + // The atom has a 64-bit length. + const long long longLength = file->readBlock(8).toLongLong(); + if(longLength <= LONG_MAX) { + // The actual length fits in long. That's always the case if long is 64-bit. + length = static_cast<long>(longLength); } else { - debug("MP4: 64-bit atoms are not supported"); - length = 0; - file->seek(0, File::End); - return; + debug("MP4: 64-bit atoms are not supported"); + length = 0; + file->seek(0, File::End); + return; } } - if (length < 8) { + + if(length < 8) { debug("MP4: Invalid atom size"); length = 0; file->seek(0, File::End); @@ -82,10 +87,13 @@ MP4::Atom::Atom(File *file) if(name == "meta") { file->seek(4, File::Current); } + else if(name == "stsd") { + file->seek(8, File::Current); + } while(file->tell() < offset + length) { MP4::Atom *child = new MP4::Atom(file); children.append(child); - if (child->length == 0) + if(child->length == 0) return; } return; @@ -97,10 +105,6 @@ MP4::Atom::Atom(File *file) MP4::Atom::~Atom() { - for(unsigned int i = 0; i < children.size(); i++) { - delete children[i]; - } - children.clear(); } MP4::Atom * @@ -109,9 +113,9 @@ MP4::Atom::find(const char *name1, const char *name2, const char *name3, const c if(name1 == 0) { return this; } - for(unsigned int i = 0; i < children.size(); i++) { - if(children[i]->name == name1) { - return children[i]->find(name2, name3, name4); + for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) { + if((*it)->name == name1) { + return (*it)->find(name2, name3, name4); } } return 0; @@ -121,12 +125,12 @@ MP4::AtomList MP4::Atom::findall(const char *name, bool recursive) { MP4::AtomList result; - for(unsigned int i = 0; i < children.size(); i++) { - if(children[i]->name == name) { - result.append(children[i]); + for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) { + if((*it)->name == name) { + result.append(*it); } if(recursive) { - result.append(children[i]->findall(name, recursive)); + result.append((*it)->findall(name, recursive)); } } return result; @@ -139,9 +143,9 @@ MP4::Atom::path(MP4::AtomList &path, const char *name1, const char *name2, const if(name1 == 0) { return true; } - for(unsigned int i = 0; i < children.size(); i++) { - if(children[i]->name == name1) { - return children[i]->path(path, name2, name3); + for(AtomList::ConstIterator it = children.begin(); it != children.end(); ++it) { + if((*it)->name == name1) { + return (*it)->path(path, name2, name3); } } return false; @@ -149,6 +153,8 @@ MP4::Atom::path(MP4::AtomList &path, const char *name1, const char *name2, const MP4::Atoms::Atoms(File *file) { + atoms.setAutoDelete(true); + file->seek(0, File::End); long end = file->tell(); file->seek(0); @@ -162,18 +168,14 @@ MP4::Atoms::Atoms(File *file) MP4::Atoms::~Atoms() { - for(unsigned int i = 0; i < atoms.size(); i++) { - delete atoms[i]; - } - atoms.clear(); } MP4::Atom * MP4::Atoms::find(const char *name1, const char *name2, const char *name3, const char *name4) { - for(unsigned int i = 0; i < atoms.size(); i++) { - if(atoms[i]->name == name1) { - return atoms[i]->find(name2, name3, name4); + for(AtomList::ConstIterator it = atoms.begin(); it != atoms.end(); ++it) { + if((*it)->name == name1) { + return (*it)->find(name2, name3, name4); } } return 0; @@ -183,9 +185,9 @@ MP4::AtomList MP4::Atoms::path(const char *name1, const char *name2, const char *name3, const char *name4) { MP4::AtomList path; - for(unsigned int i = 0; i < atoms.size(); i++) { - if(atoms[i]->name == name1) { - if(!atoms[i]->path(path, name2, name3, name4)) { + for(AtomList::ConstIterator it = atoms.begin(); it != atoms.end(); ++it) { + if((*it)->name == name1) { + if(!(*it)->path(path, name2, name3, name4)) { path.clear(); } return path; @@ -193,5 +195,3 @@ MP4::Atoms::path(const char *name1, const char *name2, const char *name3, const } return path; } - -#endif diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4atom.h b/Frameworks/TagLib/taglib/taglib/mp4/mp4atom.h index 7d9dac28a..cbb0d10ad 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4atom.h +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4atom.h @@ -1,5 +1,5 @@ /************************************************************************** - copyright : (C) 2007 by Lukáš Lalinský + copyright : (C) 2007,2011 by Lukáš Lalinský email : lalinsky@gmail.com **************************************************************************/ @@ -40,32 +40,66 @@ namespace TagLib { class Atom; typedef TagLib::List<Atom *> AtomList; + enum AtomDataType + { + TypeImplicit = 0, // for use with tags for which no type needs to be indicated because only one type is allowed + TypeUTF8 = 1, // without any count or null terminator + TypeUTF16 = 2, // also known as UTF-16BE + TypeSJIS = 3, // deprecated unless it is needed for special Japanese characters + TypeHTML = 6, // the HTML file header specifies which HTML version + TypeXML = 7, // the XML header must identify the DTD or schemas + TypeUUID = 8, // also known as GUID; stored as 16 bytes in binary (valid as an ID) + TypeISRC = 9, // stored as UTF-8 text (valid as an ID) + TypeMI3P = 10, // stored as UTF-8 text (valid as an ID) + TypeGIF = 12, // (deprecated) a GIF image + TypeJPEG = 13, // a JPEG image + TypePNG = 14, // a PNG image + TypeURL = 15, // absolute, in UTF-8 characters + TypeDuration = 16, // in milliseconds, 32-bit integer + TypeDateTime = 17, // in UTC, counting seconds since midnight, January 1, 1904; 32 or 64-bits + TypeGenred = 18, // a list of enumerated values + TypeInteger = 21, // a signed big-endian integer with length one of { 1,2,3,4,8 } bytes + TypeRIAAPA = 24, // RIAA parental advisory; { -1=no, 1=yes, 0=unspecified }, 8-bit integer + TypeUPC = 25, // Universal Product Code, in text UTF-8 format (valid as an ID) + TypeBMP = 27, // Windows bitmap image + TypeUndefined = 255 // undefined + }; + + struct AtomData { + AtomData(AtomDataType type, ByteVector data) : type(type), locale(0), data(data) {} + AtomDataType type; + int locale; + ByteVector data; + }; + + typedef TagLib::List<AtomData> AtomDataList; + class Atom { public: - Atom(File *file); - ~Atom(); - Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0); - bool path(AtomList &path, const char *name1, const char *name2 = 0, const char *name3 = 0); - AtomList findall(const char *name, bool recursive = false); - long offset; - long length; - TagLib::ByteVector name; - AtomList children; + Atom(File *file); + ~Atom(); + Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0); + bool path(AtomList &path, const char *name1, const char *name2 = 0, const char *name3 = 0); + AtomList findall(const char *name, bool recursive = false); + long offset; + long length; + TagLib::ByteVector name; + AtomList children; private: - static const int numContainers = 10; - static const char *containers[10]; + static const int numContainers = 11; + static const char *containers[11]; }; //! Root-level atoms class Atoms { public: - Atoms(File *file); - ~Atoms(); - Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0); - AtomList path(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0); - AtomList atoms; + Atoms(File *file); + ~Atoms(); + Atom *find(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0); + AtomList path(const char *name1, const char *name2 = 0, const char *name3 = 0, const char *name4 = 0); + AtomList atoms; }; } diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4coverart.cpp b/Frameworks/TagLib/taglib/taglib/mp4/mp4coverart.cpp index 86555fc18..69c9e685e 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4coverart.cpp +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4coverart.cpp @@ -23,14 +23,9 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WITH_MP4 - #include <taglib.h> #include <tdebug.h> +#include "trefcounter.h" #include "mp4coverart.h" using namespace TagLib; @@ -38,20 +33,27 @@ using namespace TagLib; class MP4::CoverArt::CoverArtPrivate : public RefCounter { public: - CoverArtPrivate() : RefCounter(), format(MP4::CoverArt::JPEG) {} + CoverArtPrivate() : + RefCounter(), + format(MP4::CoverArt::JPEG) {} Format format; ByteVector data; }; -MP4::CoverArt::CoverArt(Format format, const ByteVector &data) +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +MP4::CoverArt::CoverArt(Format format, const ByteVector &data) : + d(new CoverArtPrivate()) { - d = new CoverArtPrivate; d->format = format; d->data = data; } -MP4::CoverArt::CoverArt(const CoverArt &item) : d(item.d) +MP4::CoverArt::CoverArt(const CoverArt &item) : + d(item.d) { d->ref(); } @@ -59,14 +61,18 @@ MP4::CoverArt::CoverArt(const CoverArt &item) : d(item.d) MP4::CoverArt & MP4::CoverArt::operator=(const CoverArt &item) { - if(d->deref()) { - delete d; - } - d = item.d; - d->ref(); + CoverArt(item).swap(*this); return *this; } +void +MP4::CoverArt::swap(CoverArt &item) +{ + using std::swap; + + swap(d, item.d); +} + MP4::CoverArt::~CoverArt() { if(d->deref()) { @@ -85,5 +91,3 @@ MP4::CoverArt::data() const { return d->data; } - -#endif diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4coverart.h b/Frameworks/TagLib/taglib/taglib/mp4/mp4coverart.h index 26c4f9d9e..ebfb3f949 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4coverart.h +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4coverart.h @@ -29,6 +29,7 @@ #include "tlist.h" #include "tbytevector.h" #include "taglib_export.h" +#include "mp4atom.h" namespace TagLib { @@ -41,16 +42,28 @@ namespace TagLib { * This describes the image type. */ enum Format { - JPEG = 0x0D, - PNG = 0x0E + JPEG = TypeJPEG, + PNG = TypePNG, + BMP = TypeBMP, + GIF = TypeGIF, + Unknown = TypeImplicit, }; CoverArt(Format format, const ByteVector &data); ~CoverArt(); CoverArt(const CoverArt &item); + + /*! + * Copies the contents of \a item into this CoverArt. + */ CoverArt &operator=(const CoverArt &item); + /*! + * Exchanges the content of the CoverArt by the content of \a item. + */ + void swap(CoverArt &item); + //! Format of the image Format format() const; diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4file.cpp b/Frameworks/TagLib/taglib/taglib/mp4/mp4file.cpp index 2d59a8e59..5ad8396de 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4file.cpp +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4file.cpp @@ -23,53 +23,84 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WITH_MP4 - #include <tdebug.h> #include <tstring.h> +#include <tpropertymap.h> +#include <tagutils.h> + #include "mp4atom.h" #include "mp4tag.h" #include "mp4file.h" using namespace TagLib; +namespace +{ + bool checkValid(const MP4::AtomList &list) + { + for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) { + + if((*it)->length == 0) + return false; + + if(!checkValid((*it)->children)) + return false; + } + + return true; + } +} + class MP4::File::FilePrivate { public: - FilePrivate() : tag(0), atoms(0), properties(0) - { - } + FilePrivate() : + tag(0), + atoms(0), + properties(0) {} ~FilePrivate() { - if(atoms) { - delete atoms; - atoms = 0; - } - if(tag) { - delete tag; - tag = 0; - } - if(properties) { - delete properties; - properties = 0; - } + delete atoms; + delete tag; + delete properties; } - MP4::Tag *tag; - MP4::Atoms *atoms; + MP4::Tag *tag; + MP4::Atoms *atoms; MP4::Properties *properties; }; -MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle audioPropertiesStyle) - : TagLib::File(file) +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool MP4::File::isSupported(IOStream *stream) { - d = new FilePrivate; - read(readProperties, audioPropertiesStyle); + // An MP4 file has to have an "ftyp" box first. + + const ByteVector id = Utils::readHeader(stream, 8, false); + return id.containsAt("ftyp", 4); +} + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +MP4::File::File(FileName file, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties); +} + +MP4::File::File(IOStream *stream, bool readProperties, AudioProperties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties); } MP4::File::~File() @@ -83,46 +114,48 @@ MP4::File::tag() const return d->tag; } +PropertyMap MP4::File::properties() const +{ + return d->tag->properties(); +} + +void MP4::File::removeUnsupportedProperties(const StringList &properties) +{ + d->tag->removeUnsupportedProperties(properties); +} + +PropertyMap MP4::File::setProperties(const PropertyMap &properties) +{ + return d->tag->setProperties(properties); +} + MP4::Properties * MP4::File::audioProperties() const { return d->properties; } -bool -MP4::File::checkValid(const MP4::AtomList &list) -{ - for(uint i = 0; i < list.size(); i++) { - if(list[i]->length == 0) - return false; - if(!checkValid(list[i]->children)) - return false; - } - return true; -} - void -MP4::File::read(bool readProperties, Properties::ReadStyle audioPropertiesStyle) +MP4::File::read(bool readProperties) { if(!isValid()) return; d->atoms = new Atoms(this); - if (!checkValid(d->atoms->atoms)) { + if(!checkValid(d->atoms->atoms)) { setValid(false); return; } // must have a moov atom, otherwise consider it invalid - MP4::Atom *moov = d->atoms->find("moov"); - if(!moov) { + if(!d->atoms->find("moov")) { setValid(false); return; } d->tag = new Tag(this, d->atoms); if(readProperties) { - d->properties = new Properties(this, d->atoms, audioPropertiesStyle); + d->properties = new Properties(this, d->atoms); } } @@ -142,4 +175,8 @@ MP4::File::save() return d->tag->save(); } -#endif +bool +MP4::File::hasMP4Tag() const +{ + return (d->atoms->find("moov", "udta", "meta", "ilst") != 0); +} diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4file.h b/Frameworks/TagLib/taglib/taglib/mp4/mp4file.h index 5c28d7747..8a46d17df 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4file.h +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4file.h @@ -49,14 +49,25 @@ namespace TagLib { { public: /*! - * Contructs a MP4 file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs an MP4 file from \a file. If \a readProperties is true the + * file's audio properties will also be read. * - * \note In the current implementation, both \a readProperties and - * \a propertiesStyle are ignored. + * \note In the current implementation, \a propertiesStyle is ignored. */ - File(FileName file, bool readProperties = true, Properties::ReadStyle audioPropertiesStyle = Properties::Average); + File(FileName file, bool readProperties = true, + Properties::ReadStyle audioPropertiesStyle = Properties::Average); + + /*! + * Constructs an MP4 file from \a stream. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle audioPropertiesStyle = Properties::Average); /*! * Destroys this instance of the File. @@ -75,6 +86,22 @@ namespace TagLib { */ Tag *tag() const; + /*! + * Implements the unified property interface -- export function. + */ + PropertyMap properties() const; + + /*! + * Removes unsupported properties. Forwards to the actual Tag's + * removeUnsupportedProperties() function. + */ + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the MP4 audio properties for this file. */ @@ -87,10 +114,23 @@ namespace TagLib { */ bool save(); - private: + /*! + * Returns whether or not the file on disk actually has an MP4 tag, or the + * file has a Metadata Item List (ilst) atom. + */ + bool hasMP4Tag() const; - void read(bool readProperties, Properties::ReadStyle audioPropertiesStyle); - bool checkValid(const MP4::AtomList &list); + /*! + * Returns whether or not the given \a stream can be opened as an ASF + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + + private: + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4item.cpp b/Frameworks/TagLib/taglib/taglib/mp4/mp4item.cpp index 7dcf59463..787ed457c 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4item.cpp +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4item.cpp @@ -23,14 +23,9 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WITH_MP4 - #include <taglib.h> #include <tdebug.h> +#include "trefcounter.h" #include "mp4item.h" using namespace TagLib; @@ -38,25 +33,34 @@ using namespace TagLib; class MP4::Item::ItemPrivate : public RefCounter { public: - ItemPrivate() : RefCounter(), valid(true) {} + ItemPrivate() : + RefCounter(), + valid(true), + atomDataType(TypeUndefined) {} bool valid; + AtomDataType atomDataType; union { bool m_bool; int m_int; IntPair m_intPair; + unsigned char m_byte; + unsigned int m_uint; + long long m_longlong; }; StringList m_stringList; + ByteVectorList m_byteVectorList; MP4::CoverArtList m_coverArtList; }; -MP4::Item::Item() +MP4::Item::Item() : + d(new ItemPrivate()) { - d = new ItemPrivate; d->valid = false; } -MP4::Item::Item(const Item &item) : d(item.d) +MP4::Item::Item(const Item &item) : + d(item.d) { d->ref(); } @@ -64,52 +68,89 @@ MP4::Item::Item(const Item &item) : d(item.d) MP4::Item & MP4::Item::operator=(const Item &item) { - if(d->deref()) { - delete d; - } - d = item.d; - d->ref(); + Item(item).swap(*this); return *this; } +void +MP4::Item::swap(Item &item) +{ + using std::swap; + + swap(d, item.d); +} + MP4::Item::~Item() { - if(d->deref()) { + if(d->deref()) delete d; - } } -MP4::Item::Item(bool value) +MP4::Item::Item(bool value) : + d(new ItemPrivate()) { - d = new ItemPrivate; d->m_bool = value; } -MP4::Item::Item(int value) +MP4::Item::Item(int value) : + d(new ItemPrivate()) { - d = new ItemPrivate; d->m_int = value; } -MP4::Item::Item(int value1, int value2) +MP4::Item::Item(unsigned char value) : + d(new ItemPrivate()) +{ + d->m_byte = value; +} + +MP4::Item::Item(unsigned int value) : + d(new ItemPrivate()) +{ + d->m_uint = value; +} + +MP4::Item::Item(long long value) : + d(new ItemPrivate()) +{ + d->m_longlong = value; +} + +MP4::Item::Item(int value1, int value2) : + d(new ItemPrivate()) { - d = new ItemPrivate; d->m_intPair.first = value1; d->m_intPair.second = value2; } -MP4::Item::Item(const StringList &value) +MP4::Item::Item(const ByteVectorList &value) : + d(new ItemPrivate()) +{ + d->m_byteVectorList = value; +} + +MP4::Item::Item(const StringList &value) : + d(new ItemPrivate()) { - d = new ItemPrivate; d->m_stringList = value; } -MP4::Item::Item(const MP4::CoverArtList &value) +MP4::Item::Item(const MP4::CoverArtList &value) : + d(new ItemPrivate()) { - d = new ItemPrivate; d->m_coverArtList = value; } +void MP4::Item::setAtomDataType(MP4::AtomDataType type) +{ + d->atomDataType = type; +} + +MP4::AtomDataType MP4::Item::atomDataType() const +{ + return d->atomDataType; +} + bool MP4::Item::toBool() const { @@ -122,6 +163,24 @@ MP4::Item::toInt() const return d->m_int; } +unsigned char +MP4::Item::toByte() const +{ + return d->m_byte; +} + +unsigned int +MP4::Item::toUInt() const +{ + return d->m_uint; +} + +long long +MP4::Item::toLongLong() const +{ + return d->m_longlong; +} + MP4::Item::IntPair MP4::Item::toIntPair() const { @@ -134,6 +193,12 @@ MP4::Item::toStringList() const return d->m_stringList; } +ByteVectorList +MP4::Item::toByteVectorList() const +{ + return d->m_byteVectorList; +} + MP4::CoverArtList MP4::Item::toCoverArtList() const { @@ -145,5 +210,3 @@ MP4::Item::isValid() const { return d->valid; } - -#endif diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4item.h b/Frameworks/TagLib/taglib/taglib/mp4/mp4item.h index 3158b4dd5..3821135bd 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4item.h +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4item.h @@ -43,19 +43,40 @@ namespace TagLib { Item(); Item(const Item &item); + + /*! + * Copies the contents of \a item into this Item. + */ Item &operator=(const Item &item); + + /*! + * Exchanges the content of the Item by the content of \a item. + */ + void swap(Item &item); + ~Item(); Item(int value); + Item(unsigned char value); + Item(unsigned int value); + Item(long long value); Item(bool value); Item(int first, int second); Item(const StringList &value); + Item(const ByteVectorList &value); Item(const CoverArtList &value); + void setAtomDataType(AtomDataType type); + AtomDataType atomDataType() const; + int toInt() const; + unsigned char toByte() const; + unsigned int toUInt() const; + long long toLongLong() const; bool toBool() const; IntPair toIntPair() const; StringList toStringList() const; + ByteVectorList toByteVectorList() const; CoverArtList toCoverArtList() const; bool isValid() const; diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4properties.cpp b/Frameworks/TagLib/taglib/taglib/mp4/mp4properties.cpp index a62bda99e..6c6976fab 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4properties.cpp +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4properties.cpp @@ -23,12 +23,6 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WITH_MP4 - #include <tdebug.h> #include <tstring.h> #include "mp4file.h" @@ -37,98 +31,57 @@ using namespace TagLib; +namespace +{ + // Calculate the total bytes used by audio data, used to calculate the bitrate + long long calculateMdatLength(const MP4::AtomList &list) + { + long long totalLength = 0; + for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) { + long length = (*it)->length; + if(length == 0) + return 0; // for safety, see checkValid() in mp4file.cpp + + if((*it)->name == "mdat") + totalLength += length; + + totalLength += calculateMdatLength((*it)->children); + } + + return totalLength; + } +} + class MP4::Properties::PropertiesPrivate { public: - PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), bitsPerSample(0) {} + PropertiesPrivate() : + length(0), + bitrate(0), + sampleRate(0), + channels(0), + bitsPerSample(0), + encrypted(false), + codec(MP4::Properties::Unknown) {} int length; int bitrate; int sampleRate; int channels; int bitsPerSample; + bool encrypted; + Codec codec; }; -MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) - : AudioProperties(style) +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +MP4::Properties::Properties(File *file, MP4::Atoms *atoms, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate; - - MP4::Atom *moov = atoms->find("moov"); - if(!moov) { - debug("MP4: Atom 'moov' not found"); - return; - } - - MP4::Atom *trak = 0; - ByteVector data; - - MP4::AtomList trakList = moov->findall("trak"); - for (unsigned int i = 0; i < trakList.size(); i++) { - trak = trakList[i]; - MP4::Atom *hdlr = trak->find("mdia", "hdlr"); - if(!hdlr) { - debug("MP4: Atom 'trak.mdia.hdlr' not found"); - return; - } - file->seek(hdlr->offset); - data = file->readBlock(hdlr->length); - if(data.mid(16, 4) == "soun") { - break; - } - trak = 0; - } - if (!trak) { - debug("MP4: No audio tracks"); - return; - } - - MP4::Atom *mdhd = trak->find("mdia", "mdhd"); - if(!mdhd) { - debug("MP4: Atom 'trak.mdia.mdhd' not found"); - return; - } - - file->seek(mdhd->offset); - data = file->readBlock(mdhd->length); - if(data[8] == 0) { - unsigned int unit = data.mid(20, 4).toUInt(); - unsigned int length = data.mid(24, 4).toUInt(); - d->length = length / unit; - } - else { - long long unit = data.mid(28, 8).toLongLong(); - long long length = data.mid(36, 8).toLongLong(); - d->length = int(length / unit); - } - - MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd"); - if(!atom) { - return; - } - - file->seek(atom->offset); - data = file->readBlock(atom->length); - if(data.mid(20, 4) == "mp4a") { - d->channels = data.mid(40, 2).toShort(); - d->bitsPerSample = data.mid(42, 2).toShort(); - d->sampleRate = data.mid(46, 4).toUInt(); - if(data.mid(56, 4) == "esds" && data[64] == 0x03) { - long pos = 65; - if(data.mid(pos, 3) == "\x80\x80\x80") { - pos += 3; - } - pos += 4; - if(data[pos] == 0x04) { - pos += 1; - if(data.mid(pos, 3) == "\x80\x80\x80") { - pos += 3; - } - pos += 10; - d->bitrate = (data.mid(pos, 4).toUInt() + 500) / 1000; - } - } - } + read(file, atoms); } MP4::Properties::~Properties() @@ -150,6 +103,18 @@ MP4::Properties::sampleRate() const int MP4::Properties::length() const +{ + return lengthInSeconds(); +} + +int +MP4::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int +MP4::Properties::lengthInMilliseconds() const { return d->length; } @@ -166,4 +131,139 @@ MP4::Properties::bitsPerSample() const return d->bitsPerSample; } -#endif +bool +MP4::Properties::isEncrypted() const +{ + return d->encrypted; +} + +MP4::Properties::Codec +MP4::Properties::codec() const +{ + return d->codec; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void +MP4::Properties::read(File *file, Atoms *atoms) +{ + MP4::Atom *moov = atoms->find("moov"); + if(!moov) { + debug("MP4: Atom 'moov' not found"); + return; + } + + MP4::Atom *trak = 0; + ByteVector data; + + const MP4::AtomList trakList = moov->findall("trak"); + for(MP4::AtomList::ConstIterator it = trakList.begin(); it != trakList.end(); ++it) { + trak = *it; + MP4::Atom *hdlr = trak->find("mdia", "hdlr"); + if(!hdlr) { + debug("MP4: Atom 'trak.mdia.hdlr' not found"); + return; + } + file->seek(hdlr->offset); + data = file->readBlock(hdlr->length); + if(data.containsAt("soun", 16)) { + break; + } + trak = 0; + } + if(!trak) { + debug("MP4: No audio tracks"); + return; + } + + MP4::Atom *mdhd = trak->find("mdia", "mdhd"); + if(!mdhd) { + debug("MP4: Atom 'trak.mdia.mdhd' not found"); + return; + } + + file->seek(mdhd->offset); + data = file->readBlock(mdhd->length); + + const unsigned int version = data[8]; + long long unit; + long long length; + if(version == 1) { + if(data.size() < 36 + 8) { + debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected"); + return; + } + unit = data.toUInt(28U); + length = data.toLongLong(32U); + } + else { + if(data.size() < 24 + 8) { + debug("MP4: Atom 'trak.mdia.mdhd' is smaller than expected"); + return; + } + unit = data.toUInt(20U); + length = data.toUInt(24U); + } + if(unit > 0 && length > 0) + d->length = static_cast<int>(length * 1000.0 / unit + 0.5); + + MP4::Atom *atom = trak->find("mdia", "minf", "stbl", "stsd"); + if(!atom) { + return; + } + + file->seek(atom->offset); + data = file->readBlock(atom->length); + if(data.containsAt("mp4a", 20)) { + d->codec = AAC; + d->channels = data.toShort(40U); + d->bitsPerSample = data.toShort(42U); + d->sampleRate = data.toUInt(46U); + if(data.containsAt("esds", 56) && data[64] == 0x03) { + unsigned int pos = 65; + if(data.containsAt("\x80\x80\x80", pos)) { + pos += 3; + } + pos += 4; + if(data[pos] == 0x04) { + pos += 1; + if(data.containsAt("\x80\x80\x80", pos)) { + pos += 3; + } + pos += 10; + const unsigned int bitrateValue = data.toUInt(pos); + if(bitrateValue != 0 || d->length <= 0) { + d->bitrate = static_cast<int>((bitrateValue + 500) / 1000.0 + 0.5); + } + else { + d->bitrate = static_cast<int>( + (calculateMdatLength(atoms->atoms) * 8) / d->length); + } + } + } + } + else if(data.containsAt("alac", 20)) { + if(atom->length == 88 && data.containsAt("alac", 56)) { + d->codec = ALAC; + d->bitsPerSample = data.at(69); + d->channels = data.at(73); + d->bitrate = static_cast<int>(data.toUInt(80U) / 1000.0 + 0.5); + d->sampleRate = data.toUInt(84U); + + if(d->bitrate == 0 && d->length > 0) { + // There are files which do not contain a nominal bitrate, e.g. those + // generated by refalac64.exe. Calculate the bitrate from the audio + // data size (mdat atoms) and the duration. + d->bitrate = (calculateMdatLength(atoms->atoms) * 8) / d->length; + } + } + } + + MP4::Atom *drms = atom->find("drms"); + if(drms) { + d->encrypted = true; + } +} diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4properties.h b/Frameworks/TagLib/taglib/taglib/mp4/mp4properties.h index ef813850b..492a48cce 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4properties.h +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4properties.h @@ -40,16 +40,75 @@ namespace TagLib { class TAGLIB_EXPORT Properties : public AudioProperties { public: + enum Codec { + Unknown = 0, + AAC, + ALAC + }; + Properties(File *file, Atoms *atoms, ReadStyle style = Average); virtual ~Properties(); - virtual int length() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + + /*! + * Returns the number of bits per audio sample. + */ virtual int bitsPerSample() const; + /*! + * Returns whether or not the file is encrypted. + */ + bool isEncrypted() const; + + /*! + * Returns the codec used in the file. + */ + Codec codec() const; + private: + void read(File *file, Atoms *atoms); + class PropertiesPrivate; PropertiesPrivate *d; }; diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.cpp b/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.cpp index 8870eaa8d..a99b71016 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.cpp +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.cpp @@ -1,5 +1,5 @@ /************************************************************************** - copyright : (C) 2007 by Lukáš Lalinský + copyright : (C) 2007,2011 by Lukáš Lalinský email : lalinsky@gmail.com **************************************************************************/ @@ -23,14 +23,9 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#ifdef WITH_MP4 - #include <tdebug.h> #include <tstring.h> +#include <tpropertymap.h> #include "mp4atom.h" #include "mp4tag.h" #include "id3v1genres.h" @@ -40,16 +35,23 @@ using namespace TagLib; class MP4::Tag::TagPrivate { public: - TagPrivate() : file(0), atoms(0) {} - ~TagPrivate() {} + TagPrivate() : + file(0), + atoms(0) {} + TagLib::File *file; Atoms *atoms; - ItemListMap items; + ItemMap items; }; -MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) +MP4::Tag::Tag() : + d(new TagPrivate()) +{ +} + +MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) : + d(new TagPrivate()) { - d = new TagPrivate; d->file = file; d->atoms = atoms; @@ -59,29 +61,55 @@ MP4::Tag::Tag(TagLib::File *file, MP4::Atoms *atoms) return; } - for(unsigned int i = 0; i < ilst->children.size(); i++) { - MP4::Atom *atom = ilst->children[i]; + for(AtomList::ConstIterator it = ilst->children.begin(); it != ilst->children.end(); ++it) { + MP4::Atom *atom = *it; file->seek(atom->offset + 8); if(atom->name == "----") { - parseFreeForm(atom, file); + parseFreeForm(atom); } else if(atom->name == "trkn" || atom->name == "disk") { - parseIntPair(atom, file); + parseIntPair(atom); } - else if(atom->name == "cpil" || atom->name == "pgap" || atom->name == "pcst") { - parseBool(atom, file); + else if(atom->name == "cpil" || atom->name == "pgap" || atom->name == "pcst" || + atom->name == "hdvd" || atom->name == "shwm") { + parseBool(atom); } - else if(atom->name == "tmpo") { - parseInt(atom, file); + else if(atom->name == "tmpo" || atom->name == "\251mvi" || atom->name == "\251mvc") { + parseInt(atom); + } + else if(atom->name == "rate") { + AtomDataList data = parseData2(atom); + if(!data.isEmpty()) { + AtomData val = data[0]; + if (val.type == TypeUTF8) { + addItem(atom->name, StringList(String(val.data, String::UTF8))); + } else { + addItem(atom->name, (int)(val.data.toShort())); + } + } + } + else if(atom->name == "tvsn" || atom->name == "tves" || atom->name == "cnID" || + atom->name == "sfID" || atom->name == "atID" || atom->name == "geID" || + atom->name == "cmID") { + parseUInt(atom); + } + else if(atom->name == "plID") { + parseLongLong(atom); + } + else if(atom->name == "stik" || atom->name == "rtng" || atom->name == "akID") { + parseByte(atom); } else if(atom->name == "gnre") { - parseGnre(atom, file); + parseGnre(atom); } else if(atom->name == "covr") { - parseCovr(atom, file); + parseCovr(atom); + } + else if(atom->name == "purl" || atom->name == "egid") { + parseText(atom, -1); } else { - parseText(atom, file); + parseText(atom); } } } @@ -91,17 +119,22 @@ MP4::Tag::~Tag() delete d; } -ByteVectorList -MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool freeForm) +MP4::AtomDataList +MP4::Tag::parseData2(const MP4::Atom *atom, int expectedFlags, bool freeForm) { - ByteVectorList result; - ByteVector data = file->readBlock(atom->length - 8); + AtomDataList result; + ByteVector data = d->file->readBlock(atom->length - 8); int i = 0; unsigned int pos = 0; while(pos < data.size()) { - int length = data.mid(pos, 4).toUInt(); - ByteVector name = data.mid(pos + 4, 4); - int flags = data.mid(pos + 8, 4).toUInt(); + const int length = static_cast<int>(data.toUInt(pos)); + if(length < 12) { + debug("MP4: Too short atom"); + return result; + } + + const ByteVector name = data.mid(pos + 4, 4); + const int flags = static_cast<int>(data.toUInt(pos + 8)); if(freeForm && i < 2) { if(i == 0 && name != "mean") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"mean\""); @@ -111,7 +144,7 @@ MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool debug("MP4: Unexpected atom \"" + name + "\", expecting \"name\""); return result; } - result.append(data.mid(pos + 12, length - 12)); + result.append(AtomData(AtomDataType(flags), data.mid(pos + 12, length - 12))); } else { if(name != "data") { @@ -119,7 +152,7 @@ MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool return result; } if(expectedFlags == -1 || flags == expectedFlags) { - result.append(data.mid(pos + 16, length - 16)); + result.append(AtomData(AtomDataType(flags), data.mid(pos + 16, length - 16))); } } pos += length; @@ -128,198 +161,313 @@ MP4::Tag::parseData(MP4::Atom *atom, TagLib::File *file, int expectedFlags, bool return result; } -void -MP4::Tag::parseInt(MP4::Atom *atom, TagLib::File *file) +ByteVectorList +MP4::Tag::parseData(const MP4::Atom *atom, int expectedFlags, bool freeForm) { - ByteVectorList data = parseData(atom, file); - if(data.size()) { - d->items.insert(atom->name, (int)data[0].toShort()); + AtomDataList data = parseData2(atom, expectedFlags, freeForm); + ByteVectorList result; + for(AtomDataList::ConstIterator it = data.begin(); it != data.end(); ++it) { + result.append(it->data); + } + return result; +} + +void +MP4::Tag::parseInt(const MP4::Atom *atom) +{ + ByteVectorList data = parseData(atom); + if(!data.isEmpty()) { + addItem(atom->name, (int)data[0].toShort()); } } void -MP4::Tag::parseGnre(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseUInt(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); - if(data.size()) { + ByteVectorList data = parseData(atom); + if(!data.isEmpty()) { + addItem(atom->name, data[0].toUInt()); + } +} + +void +MP4::Tag::parseLongLong(const MP4::Atom *atom) +{ + ByteVectorList data = parseData(atom); + if(!data.isEmpty()) { + addItem(atom->name, data[0].toLongLong()); + } +} + +void +MP4::Tag::parseByte(const MP4::Atom *atom) +{ + ByteVectorList data = parseData(atom); + if(!data.isEmpty()) { + addItem(atom->name, static_cast<unsigned char>(data[0].at(0))); + } +} + +void +MP4::Tag::parseGnre(const MP4::Atom *atom) +{ + ByteVectorList data = parseData(atom); + if(!data.isEmpty()) { int idx = (int)data[0].toShort(); - if(!d->items.contains("\251gen") && idx > 0) { - d->items.insert("\251gen", StringList(ID3v1::genre(idx - 1))); + if(idx > 0) { + addItem("\251gen", StringList(ID3v1::genre(idx - 1))); } } } void -MP4::Tag::parseIntPair(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseIntPair(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); - if(data.size()) { - int a = data[0].mid(2, 2).toShort(); - int b = data[0].mid(4, 2).toShort(); - d->items.insert(atom->name, MP4::Item(a, b)); + ByteVectorList data = parseData(atom); + if(!data.isEmpty()) { + const int a = data[0].toShort(2U); + const int b = data[0].toShort(4U); + addItem(atom->name, MP4::Item(a, b)); } } void -MP4::Tag::parseBool(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseBool(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file); - if(data.size()) { + ByteVectorList data = parseData(atom); + if(!data.isEmpty()) { bool value = data[0].size() ? data[0][0] != '\0' : false; - d->items.insert(atom->name, value); + addItem(atom->name, value); } } void -MP4::Tag::parseText(MP4::Atom *atom, TagLib::File *file, int expectedFlags) +MP4::Tag::parseText(const MP4::Atom *atom, int expectedFlags) { - ByteVectorList data = parseData(atom, file, expectedFlags); - if(data.size()) { + ByteVectorList data = parseData(atom, expectedFlags); + if(!data.isEmpty()) { StringList value; - for(unsigned int i = 0; i < data.size(); i++) { - value.append(String(data[i], String::UTF8)); + for(ByteVectorList::ConstIterator it = data.begin(); it != data.end(); ++it) { + value.append(String(*it, String::UTF8)); } - d->items.insert(atom->name, value); + addItem(atom->name, value); } } void -MP4::Tag::parseFreeForm(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseFreeForm(const MP4::Atom *atom) { - ByteVectorList data = parseData(atom, file, 1, true); + AtomDataList data = parseData2(atom, -1, true); if(data.size() > 2) { - StringList value; - for(unsigned int i = 2; i < data.size(); i++) { - value.append(String(data[i], String::UTF8)); + AtomDataList::ConstIterator itBegin = data.begin(); + + String name = "----:"; + name += String((itBegin++)->data, String::UTF8); // data[0].data + name += ':'; + name += String((itBegin++)->data, String::UTF8); // data[1].data + + AtomDataType type = itBegin->type; // data[2].type + + for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) { + if(it->type != type) { + debug("MP4: We currently don't support values with multiple types"); + break; + } + } + if(type == TypeUTF8) { + StringList value; + for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) { + value.append(String(it->data, String::UTF8)); + } + Item item(value); + item.setAtomDataType(type); + addItem(name, item); + } + else { + ByteVectorList value; + for(AtomDataList::ConstIterator it = itBegin; it != data.end(); ++it) { + value.append(it->data); + } + Item item(value); + item.setAtomDataType(type); + addItem(name, item); } - String name = "----:" + data[0] + ':' + data[1]; - d->items.insert(name, value); } } void -MP4::Tag::parseCovr(MP4::Atom *atom, TagLib::File *file) +MP4::Tag::parseCovr(const MP4::Atom *atom) { MP4::CoverArtList value; - ByteVector data = file->readBlock(atom->length - 8); + ByteVector data = d->file->readBlock(atom->length - 8); unsigned int pos = 0; while(pos < data.size()) { - int length = data.mid(pos, 4).toUInt(); - ByteVector name = data.mid(pos + 4, 4); - int flags = data.mid(pos + 8, 4).toUInt(); + const int length = static_cast<int>(data.toUInt(pos)); + if(length < 12) { + debug("MP4: Too short atom"); + break; + } + + const ByteVector name = data.mid(pos + 4, 4); + const int flags = static_cast<int>(data.toUInt(pos + 8)); if(name != "data") { debug("MP4: Unexpected atom \"" + name + "\", expecting \"data\""); break; } - if(flags == MP4::CoverArt::PNG || flags == MP4::CoverArt::JPEG) { + if(flags == TypeJPEG || flags == TypePNG || flags == TypeBMP || + flags == TypeGIF || flags == TypeImplicit) { value.append(MP4::CoverArt(MP4::CoverArt::Format(flags), data.mid(pos + 16, length - 16))); } + else { + debug("MP4: Unknown covr format " + String::number(flags)); + } pos += length; } - if(value.size() > 0) - d->items.insert(atom->name, value); + if(!value.isEmpty()) + addItem(atom->name, value); } ByteVector -MP4::Tag::padIlst(const ByteVector &data, int length) +MP4::Tag::padIlst(const ByteVector &data, int length) const { - if (length == -1) { + if(length == -1) { length = ((data.size() + 1023) & ~1023) - data.size(); } return renderAtom("free", ByteVector(length, '\1')); } ByteVector -MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) +MP4::Tag::renderAtom(const ByteVector &name, const ByteVector &data) const { return ByteVector::fromUInt(data.size() + 8) + name + data; } ByteVector -MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) +MP4::Tag::renderData(const ByteVector &name, int flags, const ByteVectorList &data) const { ByteVector result; - for(unsigned int i = 0; i < data.size(); i++) { - result.append(renderAtom("data", ByteVector::fromUInt(flags) + ByteVector(4, '\0') + data[i])); + for(ByteVectorList::ConstIterator it = data.begin(); it != data.end(); ++it) { + result.append(renderAtom("data", ByteVector::fromUInt(flags) + ByteVector(4, '\0') + *it)); } return renderAtom(name, result); } ByteVector -MP4::Tag::renderBool(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderBool(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector(1, item.toBool() ? '\1' : '\0')); - return renderData(name, 0x15, data); + return renderData(name, TypeInteger, data); } ByteVector -MP4::Tag::renderInt(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderInt(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector::fromShort(item.toInt())); - return renderData(name, 0x15, data); + return renderData(name, TypeInteger, data); } ByteVector -MP4::Tag::renderIntPair(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderUInt(const ByteVector &name, const MP4::Item &item) const +{ + ByteVectorList data; + data.append(ByteVector::fromUInt(item.toUInt())); + return renderData(name, TypeInteger, data); +} + +ByteVector +MP4::Tag::renderLongLong(const ByteVector &name, const MP4::Item &item) const +{ + ByteVectorList data; + data.append(ByteVector::fromLongLong(item.toLongLong())); + return renderData(name, TypeInteger, data); +} + +ByteVector +MP4::Tag::renderByte(const ByteVector &name, const MP4::Item &item) const +{ + ByteVectorList data; + data.append(ByteVector(1, item.toByte())); + return renderData(name, TypeInteger, data); +} + +ByteVector +MP4::Tag::renderIntPair(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector(2, '\0') + ByteVector::fromShort(item.toIntPair().first) + ByteVector::fromShort(item.toIntPair().second) + ByteVector(2, '\0')); - return renderData(name, 0x00, data); + return renderData(name, TypeImplicit, data); } ByteVector -MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderIntPairNoTrailing(const ByteVector &name, const MP4::Item &item) const { ByteVectorList data; data.append(ByteVector(2, '\0') + ByteVector::fromShort(item.toIntPair().first) + ByteVector::fromShort(item.toIntPair().second)); - return renderData(name, 0x00, data); + return renderData(name, TypeImplicit, data); } ByteVector -MP4::Tag::renderText(const ByteVector &name, MP4::Item &item, int flags) +MP4::Tag::renderText(const ByteVector &name, const MP4::Item &item, int flags) const { ByteVectorList data; StringList value = item.toStringList(); - for(unsigned int i = 0; i < value.size(); i++) { - data.append(value[i].data(String::UTF8)); + for(StringList::ConstIterator it = value.begin(); it != value.end(); ++it) { + data.append(it->data(String::UTF8)); } return renderData(name, flags, data); } ByteVector -MP4::Tag::renderCovr(const ByteVector &name, MP4::Item &item) +MP4::Tag::renderCovr(const ByteVector &name, const MP4::Item &item) const { ByteVector data; MP4::CoverArtList value = item.toCoverArtList(); - for(unsigned int i = 0; i < value.size(); i++) { - data.append(renderAtom("data", ByteVector::fromUInt(value[i].format()) + - ByteVector(4, '\0') + value[i].data())); + for(MP4::CoverArtList::ConstIterator it = value.begin(); it != value.end(); ++it) { + data.append(renderAtom("data", ByteVector::fromUInt(it->format()) + + ByteVector(4, '\0') + it->data())); } return renderAtom(name, data); } ByteVector -MP4::Tag::renderFreeForm(const String &name, MP4::Item &item) +MP4::Tag::renderFreeForm(const String &name, const MP4::Item &item) const { StringList header = StringList::split(name, ":"); - if (header.size() != 3) { + if(header.size() != 3) { debug("MP4: Invalid free-form item name \"" + name + "\""); - return ByteVector::null; + return ByteVector(); } ByteVector data; data.append(renderAtom("mean", ByteVector::fromUInt(0) + header[1].data(String::UTF8))); data.append(renderAtom("name", ByteVector::fromUInt(0) + header[2].data(String::UTF8))); - StringList value = item.toStringList(); - for(unsigned int i = 0; i < value.size(); i++) { - data.append(renderAtom("data", ByteVector::fromUInt(1) + ByteVector(4, '\0') + value[i].data(String::UTF8))); + AtomDataType type = item.atomDataType(); + if(type == TypeUndefined) { + if(!item.toStringList().isEmpty()) { + type = TypeUTF8; + } + else { + type = TypeImplicit; + } + } + if(type == TypeUTF8) { + StringList value = item.toStringList(); + for(StringList::ConstIterator it = value.begin(); it != value.end(); ++it) { + data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + it->data(String::UTF8))); + } + } + else { + ByteVectorList value = item.toByteVectorList(); + for(ByteVectorList::ConstIterator it = value.begin(); it != value.end(); ++it) { + data.append(renderAtom("data", ByteVector::fromUInt(type) + ByteVector(4, '\0') + *it)); + } } return renderAtom("----", data); } @@ -328,28 +476,53 @@ bool MP4::Tag::save() { ByteVector data; - for(MP4::ItemListMap::Iterator i = d->items.begin(); i != d->items.end(); i++) { - const String name = i->first; + for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) { + const String name = it->first; if(name.startsWith("----")) { - data.append(renderFreeForm(name, i->second)); + data.append(renderFreeForm(name, it->second)); } else if(name == "trkn") { - data.append(renderIntPair(name.data(String::Latin1), i->second)); + data.append(renderIntPair(name.data(String::Latin1), it->second)); } else if(name == "disk") { - data.append(renderIntPairNoTrailing(name.data(String::Latin1), i->second)); + data.append(renderIntPairNoTrailing(name.data(String::Latin1), it->second)); } - else if(name == "cpil" || name == "pgap" || name == "pcst") { - data.append(renderBool(name.data(String::Latin1), i->second)); + else if(name == "cpil" || name == "pgap" || name == "pcst" || name == "hdvd" || + name == "shwm") { + data.append(renderBool(name.data(String::Latin1), it->second)); } - else if(name == "tmpo") { - data.append(renderInt(name.data(String::Latin1), i->second)); + else if(name == "tmpo" || name == "\251mvi" || name == "\251mvc") { + data.append(renderInt(name.data(String::Latin1), it->second)); + } + else if (name == "rate") { + const MP4::Item& item = it->second; + StringList value = item.toStringList(); + if (value.isEmpty()) { + data.append(renderInt(name.data(String::Latin1), item)); + } + else { + data.append(renderText(name.data(String::Latin1), item)); + } + } + else if(name == "tvsn" || name == "tves" || name == "cnID" || + name == "sfID" || name == "atID" || name == "geID" || + name == "cmID") { + data.append(renderUInt(name.data(String::Latin1), it->second)); + } + else if(name == "plID") { + data.append(renderLongLong(name.data(String::Latin1), it->second)); + } + else if(name == "stik" || name == "rtng" || name == "akID") { + data.append(renderByte(name.data(String::Latin1), it->second)); } else if(name == "covr") { - data.append(renderCovr(name.data(String::Latin1), i->second)); + data.append(renderCovr(name.data(String::Latin1), it->second)); + } + else if(name == "purl" || name == "egid") { + data.append(renderText(name.data(String::Latin1), it->second, TypeImplicit)); } else if(name.size() == 4){ - data.append(renderText(name.data(String::Latin1), i->second)); + data.append(renderText(name.data(String::Latin1), it->second)); } else { debug("MP4: Unknown item name \"" + name + "\""); @@ -369,22 +542,28 @@ MP4::Tag::save() } void -MP4::Tag::updateParents(AtomList &path, long delta, int ignore) +MP4::Tag::updateParents(const AtomList &path, long delta, int ignore) { - for(unsigned int i = 0; i < path.size() - ignore; i++) { - d->file->seek(path[i]->offset); + if(static_cast<int>(path.size()) <= ignore) + return; + + AtomList::ConstIterator itEnd = path.end(); + std::advance(itEnd, 0 - ignore); + + for(AtomList::ConstIterator it = path.begin(); it != itEnd; ++it) { + d->file->seek((*it)->offset); long size = d->file->readBlock(4).toUInt(); // 64-bit if (size == 1) { d->file->seek(4, File::Current); // Skip name long long longSize = d->file->readBlock(8).toLongLong(); // Seek the offset of the 64-bit size - d->file->seek(path[i]->offset + 8); + d->file->seek((*it)->offset + 8); d->file->writeBlock(ByteVector::fromLongLong(longSize + delta)); } // 32-bit else { - d->file->seek(path[i]->offset); + d->file->seek((*it)->offset); d->file->writeBlock(ByteVector::fromUInt(size + delta)); } } @@ -396,18 +575,18 @@ MP4::Tag::updateOffsets(long delta, long offset) MP4::Atom *moov = d->atoms->find("moov"); if(moov) { MP4::AtomList stco = moov->findall("stco", true); - for(unsigned int i = 0; i < stco.size(); i++) { - MP4::Atom *atom = stco[i]; + for(MP4::AtomList::ConstIterator it = stco.begin(); it != stco.end(); ++it) { + MP4::Atom *atom = *it; if(atom->offset > offset) { atom->offset += delta; } d->file->seek(atom->offset + 12); ByteVector data = d->file->readBlock(atom->length - 12); - unsigned int count = data.mid(0, 4).toUInt(); + unsigned int count = data.toUInt(); d->file->seek(atom->offset + 16); - int pos = 4; + unsigned int pos = 4; while(count--) { - long o = data.mid(pos, 4).toUInt(); + long o = static_cast<long>(data.toUInt(pos)); if(o > offset) { o += delta; } @@ -417,18 +596,18 @@ MP4::Tag::updateOffsets(long delta, long offset) } MP4::AtomList co64 = moov->findall("co64", true); - for(unsigned int i = 0; i < co64.size(); i++) { - MP4::Atom *atom = co64[i]; + for(MP4::AtomList::ConstIterator it = co64.begin(); it != co64.end(); ++it) { + MP4::Atom *atom = *it; if(atom->offset > offset) { atom->offset += delta; } d->file->seek(atom->offset + 12); ByteVector data = d->file->readBlock(atom->length - 12); - unsigned int count = data.mid(0, 4).toUInt(); + unsigned int count = data.toUInt(); d->file->seek(atom->offset + 16); - int pos = 4; + unsigned int pos = 4; while(count--) { - long long o = data.mid(pos, 8).toLongLong(); + long long o = data.toLongLong(pos); if(o > offset) { o += delta; } @@ -441,16 +620,16 @@ MP4::Tag::updateOffsets(long delta, long offset) MP4::Atom *moof = d->atoms->find("moof"); if(moof) { MP4::AtomList tfhd = moof->findall("tfhd", true); - for(unsigned int i = 0; i < tfhd.size(); i++) { - MP4::Atom *atom = tfhd[i]; + for(MP4::AtomList::ConstIterator it = tfhd.begin(); it != tfhd.end(); ++it) { + MP4::Atom *atom = *it; if(atom->offset > offset) { atom->offset += delta; } d->file->seek(atom->offset + 9); - ByteVector data = d->file->readBlock(atom->offset - 9); - unsigned int flags = (ByteVector(1, '\0') + data.mid(0, 3)).toUInt(); + ByteVector data = d->file->readBlock(atom->length - 9); + const unsigned int flags = data.toUInt(0, 3, true); if(flags & 1) { - long long o = data.mid(7, 8).toLongLong(); + long long o = data.toLongLong(7U); if(o > offset) { o += delta; } @@ -462,10 +641,11 @@ MP4::Tag::updateOffsets(long delta, long offset) } void -MP4::Tag::saveNew(ByteVector &data) +MP4::Tag::saveNew(ByteVector data) { - data = renderAtom("meta", TagLib::ByteVector(4, '\0') + - renderAtom("hdlr", TagLib::ByteVector(8, '\0') + TagLib::ByteVector("mdirappl") + TagLib::ByteVector(9, '\0')) + + data = renderAtom("meta", ByteVector(4, '\0') + + renderAtom("hdlr", ByteVector(8, '\0') + ByteVector("mdirappl") + + ByteVector(9, '\0')) + data + padIlst(data)); AtomList path = d->atoms->path("moov", "udta"); @@ -474,26 +654,33 @@ MP4::Tag::saveNew(ByteVector &data) data = renderAtom("udta", data); } - long offset = path[path.size() - 1]->offset + 8; + long offset = path.back()->offset + 8; d->file->insert(data, offset, 0); updateParents(path, data.size()); updateOffsets(data.size(), offset); + + // Insert the newly created atoms into the tree to keep it up-to-date. + + d->file->seek(offset); + path.back()->children.prepend(new Atom(d->file)); } void -MP4::Tag::saveExisting(ByteVector &data, AtomList &path) +MP4::Tag::saveExisting(ByteVector data, const AtomList &path) { - MP4::Atom *ilst = path[path.size() - 1]; + AtomList::ConstIterator it = path.end(); + + MP4::Atom *ilst = *(--it); long offset = ilst->offset; long length = ilst->length; - MP4::Atom *meta = path[path.size() - 2]; - AtomList::Iterator index = meta->children.find(ilst); + MP4::Atom *meta = *(--it); + AtomList::ConstIterator index = meta->children.find(ilst); // check if there is an atom before 'ilst', and possibly use it as padding if(index != meta->children.begin()) { - AtomList::Iterator prevIndex = index; + AtomList::ConstIterator prevIndex = index; prevIndex--; MP4::Atom *prev = *prevIndex; if(prev->name == "free") { @@ -502,7 +689,7 @@ MP4::Tag::saveExisting(ByteVector &data, AtomList &path) } } // check if there is an atom after 'ilst', and possibly use it as padding - AtomList::Iterator nextIndex = index; + AtomList::ConstIterator nextIndex = index; nextIndex++; if(nextIndex != meta->children.end()) { MP4::Atom *next = *nextIndex; @@ -534,7 +721,7 @@ MP4::Tag::title() const { if(d->items.contains("\251nam")) return d->items["\251nam"].toStringList().toString(", "); - return String::null; + return String(); } String @@ -542,7 +729,7 @@ MP4::Tag::artist() const { if(d->items.contains("\251ART")) return d->items["\251ART"].toStringList().toString(", "); - return String::null; + return String(); } String @@ -550,7 +737,7 @@ MP4::Tag::album() const { if(d->items.contains("\251alb")) return d->items["\251alb"].toStringList().toString(", "); - return String::null; + return String(); } String @@ -558,7 +745,7 @@ MP4::Tag::comment() const { if(d->items.contains("\251cmt")) return d->items["\251cmt"].toStringList().toString(", "); - return String::null; + return String(); } String @@ -566,7 +753,7 @@ MP4::Tag::genre() const { if(d->items.contains("\251gen")) return d->items["\251gen"].toStringList().toString(", "); - return String::null; + return String(); } unsigned int @@ -585,88 +772,290 @@ MP4::Tag::track() const return 0; } -float MP4::Tag::rgAlbumGain() const -{ - return 0; -} - -float MP4::Tag::rgAlbumPeak() const -{ - return 0; -} - -float MP4::Tag::rgTrackGain() const -{ - return 0; -} - -float MP4::Tag::rgTrackPeak() const -{ - return 0; -} - void MP4::Tag::setTitle(const String &value) { - d->items["\251nam"] = StringList(value); + setTextItem("\251nam", value); } void MP4::Tag::setArtist(const String &value) { - d->items["\251ART"] = StringList(value); + setTextItem("\251ART", value); } void MP4::Tag::setAlbum(const String &value) { - d->items["\251alb"] = StringList(value); + setTextItem("\251alb", value); } void MP4::Tag::setComment(const String &value) { - d->items["\251cmt"] = StringList(value); + setTextItem("\251cmt", value); } void MP4::Tag::setGenre(const String &value) { - d->items["\251gen"] = StringList(value); + setTextItem("\251gen", value); } void -MP4::Tag::setYear(uint value) +MP4::Tag::setTextItem(const String &key, const String &value) { - d->items["\251day"] = StringList(String::number(value)); + if (!value.isEmpty()) { + d->items[key] = StringList(value); + } else { + d->items.erase(key); + } } void -MP4::Tag::setTrack(uint value) +MP4::Tag::setYear(unsigned int value) { - d->items["trkn"] = MP4::Item(value, 0); + if (value == 0) { + d->items.erase("\251day"); + } + else { + d->items["\251day"] = StringList(String::number(value)); + } } -void MP4::Tag::setRGAlbumGain(float f) +void +MP4::Tag::setTrack(unsigned int value) { + if (value == 0) { + d->items.erase("trkn"); + } + else { + d->items["trkn"] = MP4::Item(value, 0); + } } -void MP4::Tag::setRGAlbumPeak(float f) +bool MP4::Tag::isEmpty() const { + return d->items.isEmpty(); } -void MP4::Tag::setRGTrackGain(float f) -{ -} - -void MP4::Tag::setRGTrackPeak(float f) -{ -} - -MP4::ItemListMap & -MP4::Tag::itemListMap() +MP4::ItemMap &MP4::Tag::itemListMap() { return d->items; } -#endif +const MP4::ItemMap &MP4::Tag::itemMap() const +{ + return d->items; +} + +MP4::Item MP4::Tag::item(const String &key) const +{ + return d->items[key]; +} + +void MP4::Tag::setItem(const String &key, const Item &value) +{ + d->items[key] = value; +} + +void MP4::Tag::removeItem(const String &key) +{ + d->items.erase(key); +} + +bool MP4::Tag::contains(const String &key) const +{ + return d->items.contains(key); +} + +namespace +{ + const char *keyTranslation[][2] = { + { "\251nam", "TITLE" }, + { "\251ART", "ARTIST" }, + { "\251alb", "ALBUM" }, + { "\251cmt", "COMMENT" }, + { "\251gen", "GENRE" }, + { "\251day", "DATE" }, + { "\251wrt", "COMPOSER" }, + { "\251grp", "GROUPING" }, + { "aART", "ALBUMARTIST" }, + { "trkn", "TRACKNUMBER" }, + { "disk", "DISCNUMBER" }, + { "cpil", "COMPILATION" }, + { "tmpo", "BPM" }, + { "cprt", "COPYRIGHT" }, + { "\251lyr", "LYRICS" }, + { "\251too", "ENCODEDBY" }, + { "soal", "ALBUMSORT" }, + { "soaa", "ALBUMARTISTSORT" }, + { "soar", "ARTISTSORT" }, + { "sonm", "TITLESORT" }, + { "soco", "COMPOSERSORT" }, + { "sosn", "SHOWSORT" }, + { "shwm", "SHOWWORKMOVEMENT" }, + { "pgap", "GAPLESSPLAYBACK" }, + { "pcst", "PODCAST" }, + { "catg", "PODCASTCATEGORY" }, + { "desc", "PODCASTDESC" }, + { "egid", "PODCASTID" }, + { "purl", "PODCASTURL" }, + { "tves", "TVEPISODE" }, + { "tven", "TVEPISODEID" }, + { "tvnn", "TVNETWORK" }, + { "tvsn", "TVSEASON" }, + { "tvsh", "TVSHOW" }, + { "\251wrk", "WORK" }, + { "\251mvn", "MOVEMENTNAME" }, + { "\251mvi", "MOVEMENTNUMBER" }, + { "\251mvc", "MOVEMENTCOUNT" }, + { "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" }, + { "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, + { "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" }, + { "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, + { "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "----:com.apple.iTunes:MusicBrainz Release Track Id", "MUSICBRAINZ_RELEASETRACKID" }, + { "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, + { "----:com.apple.iTunes:MusicBrainz Album Release Country", "RELEASECOUNTRY" }, + { "----:com.apple.iTunes:MusicBrainz Album Status", "RELEASESTATUS" }, + { "----:com.apple.iTunes:MusicBrainz Album Type", "RELEASETYPE" }, + { "----:com.apple.iTunes:ARTISTS", "ARTISTS" }, + { "----:com.apple.iTunes:originaldate", "ORIGINALDATE" }, + { "----:com.apple.iTunes:ASIN", "ASIN" }, + { "----:com.apple.iTunes:LABEL", "LABEL" }, + { "----:com.apple.iTunes:LYRICIST", "LYRICIST" }, + { "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" }, + { "----:com.apple.iTunes:REMIXER", "REMIXER" }, + { "----:com.apple.iTunes:ENGINEER", "ENGINEER" }, + { "----:com.apple.iTunes:PRODUCER", "PRODUCER" }, + { "----:com.apple.iTunes:DJMIXER", "DJMIXER" }, + { "----:com.apple.iTunes:MIXER", "MIXER" }, + { "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" }, + { "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" }, + { "----:com.apple.iTunes:MOOD", "MOOD" }, + { "----:com.apple.iTunes:ISRC", "ISRC" }, + { "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" }, + { "----:com.apple.iTunes:BARCODE", "BARCODE" }, + { "----:com.apple.iTunes:SCRIPT", "SCRIPT" }, + { "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" }, + { "----:com.apple.iTunes:LICENSE", "LICENSE" }, + { "----:com.apple.iTunes:MEDIA", "MEDIA" }, + }; + const size_t keyTranslationSize = sizeof(keyTranslation) / sizeof(keyTranslation[0]); + + String translateKey(const String &key) + { + for(size_t i = 0; i < keyTranslationSize; ++i) { + if(key == keyTranslation[i][0]) + return keyTranslation[i][1]; + } + + return String(); + } +} + +PropertyMap MP4::Tag::properties() const +{ + PropertyMap props; + for(MP4::ItemMap::ConstIterator it = d->items.begin(); it != d->items.end(); ++it) { + const String key = translateKey(it->first); + if(!key.isEmpty()) { + if(key == "TRACKNUMBER" || key == "DISCNUMBER") { + MP4::Item::IntPair ip = it->second.toIntPair(); + String value = String::number(ip.first); + if(ip.second) { + value += "/" + String::number(ip.second); + } + props[key] = value; + } + else if(key == "BPM" || key == "MOVEMENTNUMBER" || key == "MOVEMENTCOUNT" || + key == "TVEPISODE" || key == "TVSEASON") { + props[key] = String::number(it->second.toInt()); + } + else if(key == "COMPILATION" || key == "SHOWWORKMOVEMENT" || + key == "GAPLESSPLAYBACK" || key == "PODCAST") { + props[key] = String::number(it->second.toBool()); + } + else { + props[key] = it->second.toStringList(); + } + } + else { + props.unsupportedData().append(it->first); + } + } + return props; +} + +void MP4::Tag::removeUnsupportedProperties(const StringList &props) +{ + for(StringList::ConstIterator it = props.begin(); it != props.end(); ++it) + d->items.erase(*it); +} + +PropertyMap MP4::Tag::setProperties(const PropertyMap &props) +{ + static Map<String, String> reverseKeyMap; + if(reverseKeyMap.isEmpty()) { + int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]); + for(int i = 0; i < numKeys; i++) { + reverseKeyMap[keyTranslation[i][1]] = keyTranslation[i][0]; + } + } + + PropertyMap origProps = properties(); + for(PropertyMap::ConstIterator it = origProps.begin(); it != origProps.end(); ++it) { + if(!props.contains(it->first) || props[it->first].isEmpty()) { + d->items.erase(reverseKeyMap[it->first]); + } + } + + PropertyMap ignoredProps; + for(PropertyMap::ConstIterator it = props.begin(); it != props.end(); ++it) { + if(reverseKeyMap.contains(it->first)) { + String name = reverseKeyMap[it->first]; + if((it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") && !it->second.isEmpty()) { + StringList parts = StringList::split(it->second.front(), "/"); + if(!parts.isEmpty()) { + int first = parts[0].toInt(); + int second = 0; + if(parts.size() > 1) { + second = parts[1].toInt(); + } + d->items[name] = MP4::Item(first, second); + } + } + else if((it->first == "BPM" || it->first == "MOVEMENTNUMBER" || + it->first == "MOVEMENTCOUNT" || it->first == "TVEPISODE" || + it->first == "TVSEASON") && !it->second.isEmpty()) { + int value = it->second.front().toInt(); + d->items[name] = MP4::Item(value); + } + else if((it->first == "COMPILATION" || it->first == "SHOWWORKMOVEMENT" || + it->first == "GAPLESSPLAYBACK" || it->first == "PODCAST") && + !it->second.isEmpty()) { + bool value = (it->second.front().toInt() != 0); + d->items[name] = MP4::Item(value); + } + else { + d->items[name] = it->second; + } + } + else { + ignoredProps.insert(it->first, it->second); + } + } + + return ignoredProps; +} + +void MP4::Tag::addItem(const String &name, const Item &value) +{ + if(!d->items.contains(name)) { + d->items.insert(name, value); + } + else { + debug("MP4: Ignoring duplicate atom \"" + name + "\""); + } +} diff --git a/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.h b/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.h index 0a592a03b..ccee8e06b 100644 --- a/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.h +++ b/Frameworks/TagLib/taglib/taglib/mp4/mp4tag.h @@ -1,5 +1,5 @@ /************************************************************************** - copyright : (C) 2007 by Lukáš Lalinský + copyright : (C) 2007,2011 by Lukáš Lalinský email : lalinsky@gmail.com **************************************************************************/ @@ -39,67 +39,119 @@ namespace TagLib { namespace MP4 { - typedef TagLib::Map<String, Item> ItemListMap; + /*! + * \deprecated + */ + TAGLIB_DEPRECATED typedef TagLib::Map<String, Item> ItemListMap; + typedef TagLib::Map<String, Item> ItemMap; class TAGLIB_EXPORT Tag: public TagLib::Tag { public: + Tag(); Tag(TagLib::File *file, Atoms *atoms); - ~Tag(); + virtual ~Tag(); bool save(); - String title() const; - String artist() const; - String album() const; - String comment() const; - String genre() const; - uint year() const; - uint track() const; - float rgAlbumGain() const; - float rgAlbumPeak() const; - float rgTrackGain() const; - float rgTrackPeak() const; + virtual String title() const; + virtual String artist() const; + virtual String album() const; + virtual String comment() const; + virtual String genre() const; + virtual unsigned int year() const; + virtual unsigned int track() const; - void setTitle(const String &value); - void setArtist(const String &value); - void setAlbum(const String &value); - void setComment(const String &value); - void setGenre(const String &value); - void setYear(uint value); - void setTrack(uint value); - void setRGAlbumGain(float); - void setRGAlbumPeak(float); - void setRGTrackGain(float); - void setRGTrackPeak(float); + virtual void setTitle(const String &value); + virtual void setArtist(const String &value); + virtual void setAlbum(const String &value); + virtual void setComment(const String &value); + virtual void setGenre(const String &value); + virtual void setYear(unsigned int value); + virtual void setTrack(unsigned int value); - ItemListMap &itemListMap(); + virtual bool isEmpty() const; + + /*! + * \deprecated Use the item() and setItem() API instead + */ + TAGLIB_DEPRECATED ItemMap &itemListMap(); + + /*! + * Returns a string-keyed map of the MP4::Items for this tag. + */ + const ItemMap &itemMap() const; + + /*! + * \return The item, if any, corresponding to \a key. + */ + Item item(const String &key) const; + + /*! + * Sets the value of \a key to \a value, overwriting any previous value. + */ + void setItem(const String &key, const Item &value); + + /*! + * Removes the entry with \a key from the tag, or does nothing if it does + * not exist. + */ + void removeItem(const String &key); + + /*! + * \return True if the tag contains an entry for \a key. + */ + bool contains(const String &key) const; + + PropertyMap properties() const; + void removeUnsupportedProperties(const StringList& properties); + PropertyMap setProperties(const PropertyMap &properties); + + protected: + /*! + * Sets the value of \a key to \a value, overwriting any previous value. + * If \a value is empty, the item is removed. + */ + void setTextItem(const String &key, const String &value); private: - TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false); - void parseText(Atom *atom, TagLib::File *file, int expectedFlags = 1); - void parseFreeForm(Atom *atom, TagLib::File *file); - void parseInt(Atom *atom, TagLib::File *file); - void parseGnre(Atom *atom, TagLib::File *file); - void parseIntPair(Atom *atom, TagLib::File *file); - void parseBool(Atom *atom, TagLib::File *file); - void parseCovr(Atom *atom, TagLib::File *file); + AtomDataList parseData2(const Atom *atom, int expectedFlags = -1, + bool freeForm = false); + ByteVectorList parseData(const Atom *atom, int expectedFlags = -1, + bool freeForm = false); + void parseText(const Atom *atom, int expectedFlags = 1); + void parseFreeForm(const Atom *atom); + void parseInt(const Atom *atom); + void parseByte(const Atom *atom); + void parseUInt(const Atom *atom); + void parseLongLong(const Atom *atom); + void parseGnre(const Atom *atom); + void parseIntPair(const Atom *atom); + void parseBool(const Atom *atom); + void parseCovr(const Atom *atom); - TagLib::ByteVector padIlst(const ByteVector &data, int length = -1); - TagLib::ByteVector renderAtom(const ByteVector &name, const TagLib::ByteVector &data); - TagLib::ByteVector renderData(const ByteVector &name, int flags, const TagLib::ByteVectorList &data); - TagLib::ByteVector renderText(const ByteVector &name, Item &item, int flags = 1); - TagLib::ByteVector renderFreeForm(const String &name, Item &item); - TagLib::ByteVector renderBool(const ByteVector &name, Item &item); - TagLib::ByteVector renderInt(const ByteVector &name, Item &item); - TagLib::ByteVector renderIntPair(const ByteVector &name, Item &item); - TagLib::ByteVector renderIntPairNoTrailing(const ByteVector &name, Item &item); - TagLib::ByteVector renderCovr(const ByteVector &name, Item &item); + ByteVector padIlst(const ByteVector &data, int length = -1) const; + ByteVector renderAtom(const ByteVector &name, const ByteVector &data) const; + ByteVector renderData(const ByteVector &name, int flags, + const ByteVectorList &data) const; + ByteVector renderText(const ByteVector &name, const Item &item, + int flags = TypeUTF8) const; + ByteVector renderFreeForm(const String &name, const Item &item) const; + ByteVector renderBool(const ByteVector &name, const Item &item) const; + ByteVector renderInt(const ByteVector &name, const Item &item) const; + ByteVector renderByte(const ByteVector &name, const Item &item) const; + ByteVector renderUInt(const ByteVector &name, const Item &item) const; + ByteVector renderLongLong(const ByteVector &name, const Item &item) const; + ByteVector renderIntPair(const ByteVector &name, const Item &item) const; + ByteVector renderIntPairNoTrailing(const ByteVector &name, const Item &item) const; + ByteVector renderCovr(const ByteVector &name, const Item &item) const; - void updateParents(AtomList &path, long delta, int ignore = 0); + void updateParents(const AtomList &path, long delta, int ignore = 0); void updateOffsets(long delta, long offset); - void saveNew(TagLib::ByteVector &data); - void saveExisting(TagLib::ByteVector &data, AtomList &path); + void saveNew(ByteVector data); + void saveExisting(ByteVector data, const AtomList &path); + + void addItem(const String &name, const Item &value); class TagPrivate; TagPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/mpc/mpcfile.cpp b/Frameworks/TagLib/taglib/taglib/mpc/mpcfile.cpp index 9e9d6b883..0ffaf8933 100644 --- a/Frameworks/TagLib/taglib/taglib/mpc/mpcfile.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpc/mpcfile.cpp @@ -27,6 +27,8 @@ #include <tstring.h> #include <tagunion.h> #include <tdebug.h> +#include <tpropertymap.h> +#include <tagutils.h> #include "mpcfile.h" #include "id3v1tag.h" @@ -38,7 +40,7 @@ using namespace TagLib; namespace { - enum { APEIndex, ID3v1Index }; + enum { MPCAPEIndex = 0, MPCID3v1Index = 1 }; } class MPC::File::FilePrivate @@ -51,11 +53,7 @@ public: ID3v2Header(0), ID3v2Location(-1), ID3v2Size(0), - properties(0), - scanned(false), - hasAPE(false), - hasID3v1(false), - hasID3v2(false) {} + properties(0) {} ~FilePrivate() { @@ -64,36 +62,50 @@ public: } long APELocation; - uint APESize; + long APESize; long ID3v1Location; ID3v2::Header *ID3v2Header; long ID3v2Location; - uint ID3v2Size; + long ID3v2Size; TagUnion tag; Properties *properties; - bool scanned; - - // These indicate whether the file *on disk* has these tags, not if - // this data structure does. This is used in computing offsets. - - bool hasAPE; - bool hasID3v1; - bool hasID3v2; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool MPC::File::isSupported(IOStream *stream) +{ + // A newer MPC file has to start with "MPCK" or "MP+", but older files don't + // have keys to do a quick check. + + const ByteVector id = Utils::readHeader(stream, 4, false); + return (id == "MPCK" || id.startsWith("MP+")); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -MPC::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(file) +MPC::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties); +} + +MPC::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties); } MPC::File::~File() @@ -106,6 +118,24 @@ TagLib::Tag *MPC::File::tag() const return &d->tag; } +PropertyMap MPC::File::properties() const +{ + return d->tag.properties(); +} + +void MPC::File::removeUnsupportedProperties(const StringList &properties) +{ + d->tag.removeUnsupportedProperties(properties); +} + +PropertyMap MPC::File::setProperties(const PropertyMap &properties) +{ + if(ID3v1Tag()) + ID3v1Tag()->setProperties(properties); + + return APETag(true)->setProperties(properties); +} + MPC::Properties *MPC::File::audioProperties() const { return d->properties; @@ -120,101 +150,109 @@ bool MPC::File::save() // Possibly strip ID3v2 tag - if(d->hasID3v2 && !d->ID3v2Header) { + if(!d->ID3v2Header && d->ID3v2Location >= 0) { removeBlock(d->ID3v2Location, d->ID3v2Size); - d->hasID3v2 = false; - if(d->hasID3v1) - d->ID3v1Location -= d->ID3v2Size; - if(d->hasAPE) + + if(d->APELocation >= 0) d->APELocation -= d->ID3v2Size; + + if(d->ID3v1Location >= 0) + d->ID3v1Location -= d->ID3v2Size; + + d->ID3v2Location = -1; + d->ID3v2Size = 0; } // Update ID3v1 tag - if(ID3v1Tag()) { - if(d->hasID3v1) { + if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) { + + // ID3v1 tag is not empty. Update the old one or create a new one. + + if(d->ID3v1Location >= 0) { seek(d->ID3v1Location); - writeBlock(ID3v1Tag()->render()); } else { seek(0, End); d->ID3v1Location = tell(); - writeBlock(ID3v1Tag()->render()); - d->hasID3v1 = true; } - } else - if(d->hasID3v1) { - removeBlock(d->ID3v1Location, 128); - d->hasID3v1 = false; - if(d->hasAPE) { - if(d->APELocation > d->ID3v1Location) - d->APELocation -= 128; - } + + writeBlock(ID3v1Tag()->render()); + } + else { + + // ID3v1 tag is empty. Remove the old one. + + if(d->ID3v1Location >= 0) { + truncate(d->ID3v1Location); + d->ID3v1Location = -1; } + } // Update APE tag - if(APETag()) { - if(d->hasAPE) - insert(APETag()->render(), d->APELocation, d->APESize); - else { - if(d->hasID3v1) { - insert(APETag()->render(), d->ID3v1Location, 0); - d->APESize = APETag()->footer()->completeTagSize(); - d->hasAPE = true; + if(APETag() && !APETag()->isEmpty()) { + + // APE tag is not empty. Update the old one or create a new one. + + if(d->APELocation < 0) { + if(d->ID3v1Location >= 0) d->APELocation = d->ID3v1Location; - d->ID3v1Location += d->APESize; - } - else { - seek(0, End); - d->APELocation = tell(); - writeBlock(APETag()->render()); - d->APESize = APETag()->footer()->completeTagSize(); - d->hasAPE = true; - } + else + d->APELocation = length(); + } + + const ByteVector data = APETag()->render(); + insert(data, d->APELocation, d->APESize); + + if(d->ID3v1Location >= 0) + d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize); + + d->APESize = data.size(); + } + else { + + // APE tag is empty. Remove the old one. + + if(d->APELocation >= 0) { + removeBlock(d->APELocation, d->APESize); + + if(d->ID3v1Location >= 0) + d->ID3v1Location -= d->APESize; + + d->APELocation = -1; + d->APESize = 0; } } - else - if(d->hasAPE) { - removeBlock(d->APELocation, d->APESize); - d->hasAPE = false; - if(d->hasID3v1) { - if(d->ID3v1Location > d->APELocation) - d->ID3v1Location -= d->APESize; - } - } return true; } ID3v1::Tag *MPC::File::ID3v1Tag(bool create) { - return d->tag.access<ID3v1::Tag>(ID3v1Index, create); + return d->tag.access<ID3v1::Tag>(MPCID3v1Index, create); } APE::Tag *MPC::File::APETag(bool create) { - return d->tag.access<APE::Tag>(APEIndex, create); + return d->tag.access<APE::Tag>(MPCAPEIndex, create); } void MPC::File::strip(int tags) { - if(tags & ID3v1) { - d->tag.set(ID3v1Index, 0); + if(tags & ID3v1) + d->tag.set(MPCID3v1Index, 0); + + if(tags & APE) + d->tag.set(MPCAPEIndex, 0); + + if(!ID3v1Tag()) APETag(true); - } if(tags & ID3v2) { delete d->ID3v2Header; d->ID3v2Header = 0; } - - if(tags & APE) { - d->tag.set(APEIndex, 0); - - if(!ID3v1Tag()) - APETag(true); - } } void MPC::File::remove(int tags) @@ -222,104 +260,73 @@ void MPC::File::remove(int tags) strip(tags); } +bool MPC::File::hasID3v1Tag() const +{ + return (d->ID3v1Location >= 0); +} + +bool MPC::File::hasAPETag() const +{ + return (d->APELocation >= 0); +} //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) +void MPC::File::read(bool readProperties) { - // Look for an ID3v1 tag + // Look for an ID3v2 tag - d->ID3v1Location = findID3v1(); - - if(d->ID3v1Location >= 0) { - d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); - d->hasID3v1 = true; - } - - // Look for an APE tag - - findAPE(); - - d->APELocation = findAPE(); - - if(d->APELocation >= 0) { - d->tag.set(APEIndex, new APE::Tag(this, d->APELocation)); - - d->APESize = APETag()->footer()->completeTagSize(); - d->APELocation = d->APELocation + APETag()->footer()->size() - d->APESize; - d->hasAPE = true; - } - - if(!d->hasID3v1) - APETag(true); - - // Look for and skip an ID3v2 tag - - d->ID3v2Location = findID3v2(); + d->ID3v2Location = Utils::findID3v2(this); if(d->ID3v2Location >= 0) { seek(d->ID3v2Location); d->ID3v2Header = new ID3v2::Header(readBlock(ID3v2::Header::size())); d->ID3v2Size = d->ID3v2Header->completeTagSize(); - d->hasID3v2 = true; } - if(d->hasID3v2) - seek(d->ID3v2Location + d->ID3v2Size); - else - seek(0); + // Look for an ID3v1 tag + + d->ID3v1Location = Utils::findID3v1(this); + + if(d->ID3v1Location >= 0) + d->tag.set(MPCID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); + + // Look for an APE tag + + d->APELocation = Utils::findAPE(this, d->ID3v1Location); + + if(d->APELocation >= 0) { + d->tag.set(MPCAPEIndex, new APE::Tag(this, d->APELocation)); + d->APESize = APETag()->footer()->completeTagSize(); + d->APELocation = d->APELocation + APE::Footer::size() - d->APESize; + } + + if(d->ID3v1Location < 0) + APETag(true); // Look for MPC metadata if(readProperties) { - d->properties = new Properties(readBlock(MPC::HeaderSize), - length() - d->ID3v2Size - d->APESize); + + long streamLength; + + if(d->APELocation >= 0) + streamLength = d->APELocation; + else if(d->ID3v1Location >= 0) + streamLength = d->ID3v1Location; + else + streamLength = length(); + + if(d->ID3v2Location >= 0) { + seek(d->ID3v2Location + d->ID3v2Size); + streamLength -= (d->ID3v2Location + d->ID3v2Size); + } + else { + seek(0); + } + + d->properties = new Properties(this, streamLength); } } - -long MPC::File::findAPE() -{ - if(!isValid()) - return -1; - - if(d->hasID3v1) - seek(-160, End); - else - seek(-32, End); - - long p = tell(); - - if(readBlock(8) == APE::Tag::fileIdentifier()) - return p; - - return -1; -} - -long MPC::File::findID3v1() -{ - if(!isValid()) - return -1; - - seek(-128, End); - long p = tell(); - - if(readBlock(3) == ID3v1::Tag::fileIdentifier()) - return p; - - return -1; -} - -long MPC::File::findID3v2() -{ - if(!isValid()) - return -1; - - seek(0); - - if(readBlock(3) == ID3v2::Header::fileIdentifier()) - return 0; - - return -1; -} diff --git a/Frameworks/TagLib/taglib/taglib/mpc/mpcfile.h b/Frameworks/TagLib/taglib/taglib/mpc/mpcfile.h index 6adc0ffb9..eb3ec54a2 100644 --- a/Frameworks/TagLib/taglib/taglib/mpc/mpcfile.h +++ b/Frameworks/TagLib/taglib/taglib/mpc/mpcfile.h @@ -28,9 +28,12 @@ #include "taglib_export.h" #include "tfile.h" +#include "tag.h" #include "mpcproperties.h" +#include "tlist.h" + namespace TagLib { class Tag; @@ -81,13 +84,26 @@ namespace TagLib { }; /*! - * Contructs an MPC file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs an MPC file from \a file. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs an MPC file from \a stream. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ @@ -99,6 +115,22 @@ namespace TagLib { */ virtual TagLib::Tag *tag() const; + /*! + * Implements the unified property interface -- export function. + * If the file contains both an APE and an ID3v1 tag, only the APE + * tag will be converted to the PropertyMap. + */ + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + * Affects only the APEv2 tag which will be created if necessary. + * If an ID3v1 tag exists, it will be updated as well. + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the MPC::Properties for this file. If no audio properties * were read then this will return a null pointer. @@ -107,34 +139,47 @@ namespace TagLib { /*! * Saves the file. + * + * This returns true if the save was successful. */ virtual bool save(); /*! * Returns a pointer to the ID3v1 tag of the file. * - * If \a create is false (the default) this will return a null pointer - * if there is no valid ID3v1 tag. If \a create is true it will create - * an ID3v1 tag if one does not exist. If there is already an APE tag, the - * new ID3v1 tag will be placed after it. + * If \a create is false (the default) this returns a null pointer + * if there is no valid APE tag. If \a create is true it will create + * an APE tag if one does not exist and returns a valid pointer. * - * \note The Tag <b>is still</b> owned by the APE::File and should not be + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * on disk actually has an ID3v1 tag. + * + * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasID3v1Tag() */ ID3v1::Tag *ID3v1Tag(bool create = false); /*! * Returns a pointer to the APE tag of the file. * - * If \a create is false (the default) this will return a null pointer + * If \a create is false (the default) this may return a null pointer * if there is no valid APE tag. If \a create is true it will create - * a APE tag if one does not exist. If there is already an ID3v1 tag, thes - * new APE tag will be placed before it. + * an APE tag if one does not exist and returns a valid pointer. If + * there already be an ID3v1 tag, the new APE tag will be placed before it. * - * \note The Tag <b>is still</b> owned by the APE::File and should not be + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file + * on disk actually has an APE tag. + * + * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasAPETag() */ APE::Tag *APETag(bool create = false); @@ -153,18 +198,36 @@ namespace TagLib { * \deprecated * \see strip */ - void remove(int tags = AllTags); + TAGLIB_DEPRECATED void remove(int tags = AllTags); + /*! + * Returns whether or not the file on disk actually has an ID3v1 tag. + * + * \see ID3v1Tag() + */ + bool hasID3v1Tag() const; + + /*! + * Returns whether or not the file on disk actually has an APE tag. + * + * \see APETag() + */ + bool hasAPETag() const; + + /*! + * Returns whether or not the given \a stream can be opened as an MPC + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); private: File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); - void scan(); - long findAPE(); - long findID3v1(); - long findID3v2(); + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/mpc/mpcproperties.cpp b/Frameworks/TagLib/taglib/taglib/mpc/mpcproperties.cpp index 9adc69246..21de6d495 100644 --- a/Frameworks/TagLib/taglib/taglib/mpc/mpcproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpc/mpcproperties.cpp @@ -26,6 +26,7 @@ #include <tstring.h> #include <tdebug.h> #include <bitset> +#include <math.h> #include "mpcproperties.h" #include "mpcfile.h" @@ -35,34 +36,56 @@ using namespace TagLib; class MPC::Properties::PropertiesPrivate { public: - PropertiesPrivate(const ByteVector &d, long length, ReadStyle s) : - data(d), - streamLength(length), - style(s), + PropertiesPrivate() : version(0), length(0), bitrate(0), sampleRate(0), - channels(0) {} + channels(0), + totalFrames(0), + sampleFrames(0), + trackGain(0), + trackPeak(0), + albumGain(0), + albumPeak(0) {} - ByteVector data; - long streamLength; - ReadStyle style; int version; int length; int bitrate; int sampleRate; int channels; + unsigned int totalFrames; + unsigned int sampleFrames; + int trackGain; + int trackPeak; + int albumGain; + int albumPeak; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -MPC::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style) +MPC::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(data, streamLength, style); - read(); + readSV7(data, streamLength); +} + +MPC::Properties::Properties(File *file, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) +{ + ByteVector magic = file->readBlock(4); + if(magic == "MPCK") { + // Musepack version 8 + readSV8(file, streamLength); + } + else { + // Musepack version 7 or older, fixed size header + readSV7(magic + file->readBlock(MPC::HeaderSize - 4), streamLength); + } } MPC::Properties::~Properties() @@ -71,6 +94,16 @@ MPC::Properties::~Properties() } int MPC::Properties::length() const +{ + return lengthInSeconds(); +} + +int MPC::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int MPC::Properties::lengthInMilliseconds() const { return d->length; } @@ -95,46 +128,241 @@ int MPC::Properties::mpcVersion() const return d->version; } +unsigned int MPC::Properties::totalFrames() const +{ + return d->totalFrames; +} + +unsigned int MPC::Properties::sampleFrames() const +{ + return d->sampleFrames; +} + +int MPC::Properties::trackGain() const +{ + return d->trackGain; +} + +int MPC::Properties::trackPeak() const +{ + return d->trackPeak; +} + +int MPC::Properties::albumGain() const +{ + return d->albumGain; +} + +int MPC::Properties::albumPeak() const +{ + return d->albumPeak; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -static const unsigned short sftable [4] = { 44100, 48000, 37800, 32000 }; - -void MPC::Properties::read() +namespace { - if(!d->data.startsWith("MP+")) - return; + unsigned long readSize(File *file, unsigned int &sizeLength, bool &eof) + { + sizeLength = 0; + eof = false; - d->version = d->data[3] & 15; + unsigned char tmp; + unsigned long size = 0; - unsigned int frames; + do { + const ByteVector b = file->readBlock(1); + if(b.isEmpty()) { + eof = true; + break; + } - if(d->version >= 7) { - frames = d->data.mid(4, 4).toUInt(false); + tmp = b[0]; + size = (size << 7) | (tmp & 0x7F); + sizeLength++; + } while((tmp & 0x80)); + return size; + } - std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(d->data.mid(8, 4).toUInt(false))); - d->sampleRate = sftable[flags[17] * 2 + flags[16]]; - d->channels = 2; + unsigned long readSize(const ByteVector &data, unsigned int &pos) + { + unsigned char tmp; + unsigned long size = 0; + + do { + tmp = data[pos++]; + size = (size << 7) | (tmp & 0x7F); + } while((tmp & 0x80) && (pos < data.size())); + return size; + } + + // This array looks weird, but the same as original MusePack code found at: + // https://www.musepack.net/index.php?pg=src + const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 }; +} + +void MPC::Properties::readSV8(File *file, long streamLength) +{ + bool readSH = false, readRG = false; + + while(!readSH && !readRG) { + const ByteVector packetType = file->readBlock(2); + + unsigned int packetSizeLength; + bool eof; + const unsigned long packetSize = readSize(file, packetSizeLength, eof); + if(eof) { + debug("MPC::Properties::readSV8() - Reached to EOF."); + break; + } + + const unsigned long dataSize = packetSize - 2 - packetSizeLength; + + const ByteVector data = file->readBlock(dataSize); + if(data.size() != dataSize) { + debug("MPC::Properties::readSV8() - dataSize doesn't match the actual data size."); + break; + } + + if(packetType == "SH") { + // Stream Header + // http://trac.musepack.net/wiki/SV8Specification#StreamHeaderPacket + + if(dataSize <= 5) { + debug("MPC::Properties::readSV8() - \"SH\" packet is too short to parse."); + break; + } + + readSH = true; + + unsigned int pos = 4; + d->version = data[pos]; + pos += 1; + d->sampleFrames = readSize(data, pos); + if(pos > dataSize - 3) { + debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt."); + break; + } + + const unsigned long begSilence = readSize(data, pos); + if(pos > dataSize - 2) { + debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt."); + break; + } + + const unsigned short flags = data.toUShort(pos, true); + pos += 2; + + d->sampleRate = sftable[(flags >> 13) & 0x07]; + d->channels = ((flags >> 4) & 0x0F) + 1; + + const unsigned int frameCount = d->sampleFrames - begSilence; + if(frameCount > 0 && d->sampleRate > 0) { + const double length = frameCount * 1000.0 / d->sampleRate; + d->length = static_cast<int>(length + 0.5); + d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5); + } + } + else if (packetType == "RG") { + // Replay Gain + // http://trac.musepack.net/wiki/SV8Specification#ReplaygainPacket + + if(dataSize <= 9) { + debug("MPC::Properties::readSV8() - \"RG\" packet is too short to parse."); + break; + } + + readRG = true; + + const int replayGainVersion = data[0]; + if(replayGainVersion == 1) { + d->trackGain = data.toShort(1, true); + d->trackPeak = data.toShort(3, true); + d->albumGain = data.toShort(5, true); + d->albumPeak = data.toShort(7, true); + } + } + + else if(packetType == "SE") { + break; + } + + else { + file->seek(dataSize, File::Current); + } + } +} + +void MPC::Properties::readSV7(const ByteVector &data, long streamLength) +{ + if(data.startsWith("MP+")) { + d->version = data[3] & 15; + if(d->version < 7) + return; + + d->totalFrames = data.toUInt(4, false); + + const unsigned int flags = data.toUInt(8, false); + d->sampleRate = sftable[(flags >> 16) & 0x03]; + d->channels = 2; + + const unsigned int gapless = data.toUInt(5, false); + + d->trackGain = data.toShort(14, false); + d->trackPeak = data.toUShort(12, false); + d->albumGain = data.toShort(18, false); + d->albumPeak = data.toUShort(16, false); + + // convert gain info + if(d->trackGain != 0) { + int tmp = (int)((64.82 - (short)d->trackGain / 100.) * 256. + .5); + if(tmp >= (1 << 16) || tmp < 0) tmp = 0; + d->trackGain = tmp; + } + + if(d->albumGain != 0) { + int tmp = (int)((64.82 - d->albumGain / 100.) * 256. + .5); + if(tmp >= (1 << 16) || tmp < 0) tmp = 0; + d->albumGain = tmp; + } + + if (d->trackPeak != 0) + d->trackPeak = (int)(log10((double)d->trackPeak) * 20 * 256 + .5); + + if (d->albumPeak != 0) + d->albumPeak = (int)(log10((double)d->albumPeak) * 20 * 256 + .5); + + bool trueGapless = (gapless >> 31) & 0x0001; + if(trueGapless) { + unsigned int lastFrameSamples = (gapless >> 20) & 0x07FF; + d->sampleFrames = d->totalFrames * 1152 - lastFrameSamples; + } + else + d->sampleFrames = d->totalFrames * 1152 - 576; } else { - uint headerData = d->data.mid(0, 4).toUInt(false); + const unsigned int headerData = data.toUInt(0, false); - d->bitrate = (headerData >> 23) & 0x01ff; - d->version = (headerData >> 11) & 0x03ff; + d->bitrate = (headerData >> 23) & 0x01ff; + d->version = (headerData >> 11) & 0x03ff; d->sampleRate = 44100; - d->channels = 2; + d->channels = 2; if(d->version >= 5) - frames = d->data.mid(4, 4).toUInt(false); + d->totalFrames = data.toUInt(4, false); else - frames = d->data.mid(6, 2).toUInt(false); + d->totalFrames = data.toUShort(6, false); + + d->sampleFrames = d->totalFrames * 1152 - 576; } - uint samples = frames * 1152 - 576; + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast<int>(length + 0.5); - d->length = d->sampleRate > 0 ? (samples + (d->sampleRate / 2)) / d->sampleRate : 0; - - if(!d->bitrate) - d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; + if(d->bitrate == 0) + d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5); + } } diff --git a/Frameworks/TagLib/taglib/taglib/mpc/mpcproperties.h b/Frameworks/TagLib/taglib/taglib/mpc/mpcproperties.h index d1593458f..9a902dc9e 100644 --- a/Frameworks/TagLib/taglib/taglib/mpc/mpcproperties.h +++ b/Frameworks/TagLib/taglib/taglib/mpc/mpcproperties.h @@ -35,7 +35,7 @@ namespace TagLib { class File; - static const uint HeaderSize = 8*7; + static const unsigned int HeaderSize = 8 * 7; //! An implementation of audio property reading for MPC @@ -50,31 +50,104 @@ namespace TagLib { /*! * Create an instance of MPC::Properties with the data read from the * ByteVector \a data. + * + * This constructor is deprecated. It only works for MPC version up to 7. */ Properties(const ByteVector &data, long streamLength, ReadStyle style = Average); + /*! + * Create an instance of MPC::Properties with the data read directly + * from a MPC::File. + */ + Properties(File *file, long streamLength, ReadStyle style = Average); + /*! * Destroys this MPC::Properties instance. */ virtual ~Properties(); - // Reimplementations. + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; - virtual int length() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns the version of the bitstream (SV4-SV7) + * Returns the version of the bitstream (SV4-SV8) */ int mpcVersion() const; + unsigned int totalFrames() const; + unsigned int sampleFrames() const; + + /*! + * Returns the track gain as an integer value, + * to convert to dB: trackGain in dB = 64.82 - (trackGain / 256) + */ + int trackGain() const; + + /*! + * Returns the track peak as an integer value, + * to convert to dB: trackPeak in dB = trackPeak / 256 + * to convert to floating [-1..1]: trackPeak = 10^(trackPeak / 256 / 20)/32768 + */ + int trackPeak() const; + + /*! + * Returns the album gain as an integer value, + * to convert to dB: albumGain in dB = 64.82 - (albumGain / 256) + */ + int albumGain() const; + + /*! + * Returns the album peak as an integer value, + * to convert to dB: albumPeak in dB = albumPeak / 256 + * to convert to floating [-1..1]: albumPeak = 10^(albumPeak / 256 / 20)/32768 + */ + int albumPeak() const; + private: Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void readSV7(const ByteVector &data, long streamLength); + void readSV8(File *file, long streamLength); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1genres.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1genres.cpp index 7893c72ce..d72a2a75d 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1genres.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1genres.cpp @@ -27,193 +27,260 @@ using namespace TagLib; -namespace TagLib { - namespace ID3v1 { - - static const int genresSize = 148; - static const String genres[] = { - "Blues", - "Classic Rock", - "Country", - "Dance", - "Disco", - "Funk", - "Grunge", - "Hip-Hop", - "Jazz", - "Metal", - "New Age", - "Oldies", - "Other", - "Pop", - "R&B", - "Rap", - "Reggae", - "Rock", - "Techno", - "Industrial", - "Alternative", - "Ska", - "Death Metal", - "Pranks", - "Soundtrack", - "Euro-Techno", - "Ambient", - "Trip-Hop", - "Vocal", - "Jazz+Funk", - "Fusion", - "Trance", - "Classical", - "Instrumental", - "Acid", - "House", - "Game", - "Sound Clip", - "Gospel", - "Noise", - "Alternative Rock", - "Bass", - "Soul", - "Punk", - "Space", - "Meditative", - "Instrumental Pop", - "Instrumental Rock", - "Ethnic", - "Gothic", - "Darkwave", - "Techno-Industrial", - "Electronic", - "Pop-Folk", - "Eurodance", - "Dream", - "Southern Rock", - "Comedy", - "Cult", - "Gangsta", - "Top 40", - "Christian Rap", - "Pop/Funk", - "Jungle", - "Native American", - "Cabaret", - "New Wave", - "Psychedelic", - "Rave", - "Showtunes", - "Trailer", - "Lo-Fi", - "Tribal", - "Acid Punk", - "Acid Jazz", - "Polka", - "Retro", - "Musical", - "Rock & Roll", - "Hard Rock", - "Folk", - "Folk/Rock", - "National Folk", - "Swing", - "Fusion", - "Bebob", - "Latin", - "Revival", - "Celtic", - "Bluegrass", - "Avantgarde", - "Gothic Rock", - "Progressive Rock", - "Psychedelic Rock", - "Symphonic Rock", - "Slow Rock", - "Big Band", - "Chorus", - "Easy Listening", - "Acoustic", - "Humour", - "Speech", - "Chanson", - "Opera", - "Chamber Music", - "Sonata", - "Symphony", - "Booty Bass", - "Primus", - "Porn Groove", - "Satire", - "Slow Jam", - "Club", - "Tango", - "Samba", - "Folklore", - "Ballad", - "Power Ballad", - "Rhythmic Soul", - "Freestyle", - "Duet", - "Punk Rock", - "Drum Solo", - "A Cappella", - "Euro-House", - "Dance Hall", - "Goa", - "Drum & Bass", - "Club-House", - "Hardcore", - "Terror", - "Indie", - "BritPop", - "Negerpunk", - "Polsk Punk", - "Beat", - "Christian Gangsta Rap", - "Heavy Metal", - "Black Metal", - "Crossover", - "Contemporary Christian", - "Christian Rock", - "Merengue", - "Salsa", - "Thrash Metal", - "Anime", - "Jpop", - "Synthpop" - }; - } +namespace +{ + const wchar_t *genres[] = { + L"Blues", + L"Classic Rock", + L"Country", + L"Dance", + L"Disco", + L"Funk", + L"Grunge", + L"Hip-Hop", + L"Jazz", + L"Metal", + L"New Age", + L"Oldies", + L"Other", + L"Pop", + L"R&B", + L"Rap", + L"Reggae", + L"Rock", + L"Techno", + L"Industrial", + L"Alternative", + L"Ska", + L"Death Metal", + L"Pranks", + L"Soundtrack", + L"Euro-Techno", + L"Ambient", + L"Trip-Hop", + L"Vocal", + L"Jazz-Funk", + L"Fusion", + L"Trance", + L"Classical", + L"Instrumental", + L"Acid", + L"House", + L"Game", + L"Sound Clip", + L"Gospel", + L"Noise", + L"Alternative Rock", + L"Bass", + L"Soul", + L"Punk", + L"Space", + L"Meditative", + L"Instrumental Pop", + L"Instrumental Rock", + L"Ethnic", + L"Gothic", + L"Darkwave", + L"Techno-Industrial", + L"Electronic", + L"Pop-Folk", + L"Eurodance", + L"Dream", + L"Southern Rock", + L"Comedy", + L"Cult", + L"Gangsta", + L"Top 40", + L"Christian Rap", + L"Pop/Funk", + L"Jungle", + L"Native American", + L"Cabaret", + L"New Wave", + L"Psychedelic", + L"Rave", + L"Showtunes", + L"Trailer", + L"Lo-Fi", + L"Tribal", + L"Acid Punk", + L"Acid Jazz", + L"Polka", + L"Retro", + L"Musical", + L"Rock & Roll", + L"Hard Rock", + L"Folk", + L"Folk Rock", + L"National Folk", + L"Swing", + L"Fast Fusion", + L"Bebop", + L"Latin", + L"Revival", + L"Celtic", + L"Bluegrass", + L"Avant-garde", + L"Gothic Rock", + L"Progressive Rock", + L"Psychedelic Rock", + L"Symphonic Rock", + L"Slow Rock", + L"Big Band", + L"Chorus", + L"Easy Listening", + L"Acoustic", + L"Humour", + L"Speech", + L"Chanson", + L"Opera", + L"Chamber Music", + L"Sonata", + L"Symphony", + L"Booty Bass", + L"Primus", + L"Porn Groove", + L"Satire", + L"Slow Jam", + L"Club", + L"Tango", + L"Samba", + L"Folklore", + L"Ballad", + L"Power Ballad", + L"Rhythmic Soul", + L"Freestyle", + L"Duet", + L"Punk Rock", + L"Drum Solo", + L"A Cappella", + L"Euro-House", + L"Dancehall", + L"Goa", + L"Drum & Bass", + L"Club-House", + L"Hardcore Techno", + L"Terror", + L"Indie", + L"Britpop", + L"Worldbeat", + L"Polsk Punk", + L"Beat", + L"Christian Gangsta Rap", + L"Heavy Metal", + L"Black Metal", + L"Crossover", + L"Contemporary Christian", + L"Christian Rock", + L"Merengue", + L"Salsa", + L"Thrash Metal", + L"Anime", + L"Jpop", + L"Synthpop", + L"Abstract", + L"Art Rock", + L"Baroque", + L"Bhangra", + L"Big Beat", + L"Breakbeat", + L"Chillout", + L"Downtempo", + L"Dub", + L"EBM", + L"Eclectic", + L"Electro", + L"Electroclash", + L"Emo", + L"Experimental", + L"Garage", + L"Global", + L"IDM", + L"Illbient", + L"Industro-Goth", + L"Jam Band", + L"Krautrock", + L"Leftfield", + L"Lounge", + L"Math Rock", + L"New Romantic", + L"Nu-Breakz", + L"Post-Punk", + L"Post-Rock", + L"Psytrance", + L"Shoegaze", + L"Space Rock", + L"Trop Rock", + L"World Music", + L"Neoclassical", + L"Audiobook", + L"Audio Theatre", + L"Neue Deutsche Welle", + L"Podcast", + L"Indie Rock", + L"G-Funk", + L"Dubstep", + L"Garage Rock", + L"Psybient" + }; + const int genresSize = sizeof(genres) / sizeof(genres[0]); } StringList ID3v1::genreList() { - static StringList l; - if(l.isEmpty()) { - for(int i = 0; i < genresSize; i++) - l.append(genres[i]); + StringList l; + for(int i = 0; i < genresSize; i++) { + l.append(genres[i]); } + return l; } ID3v1::GenreMap ID3v1::genreMap() { - static GenreMap m; - if(m.isEmpty()) { - for(int i = 0; i < genresSize; i++) - m.insert(genres[i], i); + GenreMap m; + for(int i = 0; i < genresSize; i++) { + m.insert(genres[i], i); } + return m; } String ID3v1::genre(int i) { if(i >= 0 && i < genresSize) - return genres[i]; - return String::null; + return String(genres[i]); // always make a copy + else + return String(); } int ID3v1::genreIndex(const String &name) { - if(genreMap().contains(name)) - return genreMap()[name]; + for(int i = 0; i < genresSize; ++i) { + if(name == genres[i]) + return i; + } + + // If the name was not found, try the names which have been changed + static const struct { + const wchar_t *genre; + int code; + } fixUpGenres[] = { + { L"Jazz+Funk", 29 }, + { L"Folk/Rock", 81 }, + { L"Bebob", 85 }, + { L"Avantgarde", 90 }, + { L"Dance Hall", 125 }, + { L"Hardcore", 129 }, + { L"BritPop", 132 }, + { L"Negerpunk", 133 } + }; + static const int fixUpGenresSize = + sizeof(fixUpGenres) / sizeof(fixUpGenres[0]); + for(int i = 0; i < fixUpGenresSize; ++i) { + if(name == fixUpGenres[i].genre) + return fixUpGenres[i].code; + } + return 255; } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1genres.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1genres.h index 271f72590..0a0dd9700 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1genres.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1genres.h @@ -49,7 +49,7 @@ namespace TagLib { /*! * Returns the name of the genre at \a index in the ID3v1 genre list. If - * \a index is out of range -- less than zero or greater than 146 -- a null + * \a index is out of range -- less than zero or greater than 191 -- a null * string will be returned. */ String TAGLIB_EXPORT genre(int index); diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp index 206db26f0..ca9304113 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.cpp @@ -32,10 +32,20 @@ using namespace TagLib; using namespace ID3v1; +namespace +{ + const ID3v1::StringHandler defaultStringHandler; + const ID3v1::StringHandler *stringHandler = &defaultStringHandler; +} + class ID3v1::Tag::TagPrivate { public: - TagPrivate() : file(0), tagOffset(-1), track(0), genre(255) {} + TagPrivate() : + file(0), + tagOffset(0), + track(0), + genre(255) {} File *file; long tagOffset; @@ -45,18 +55,18 @@ public: String album; String year; String comment; - uchar track; - uchar genre; - - static const StringHandler *stringHandler; + unsigned char track; + unsigned char genre; }; -const ID3v1::StringHandler *ID3v1::Tag::TagPrivate::stringHandler = new StringHandler; - //////////////////////////////////////////////////////////////////////////////// // StringHandler implementation //////////////////////////////////////////////////////////////////////////////// +StringHandler::StringHandler() +{ +} + String ID3v1::StringHandler::parse(const ByteVector &data) const { return String(data, String::Latin1).stripWhiteSpace(); @@ -64,26 +74,26 @@ String ID3v1::StringHandler::parse(const ByteVector &data) const ByteVector ID3v1::StringHandler::render(const String &s) const { - if(!s.isLatin1()) - { + if(s.isLatin1()) + return s.data(String::Latin1); + else return ByteVector(); - } - - return s.data(String::Latin1); } //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// -ID3v1::Tag::Tag() : TagLib::Tag() +ID3v1::Tag::Tag() : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; } -ID3v1::Tag::Tag(File *file, long tagOffset) : TagLib::Tag() +ID3v1::Tag::Tag(File *file, long tagOffset) : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; d->file = file; d->tagOffset = tagOffset; @@ -100,11 +110,11 @@ ByteVector ID3v1::Tag::render() const ByteVector data; data.append(fileIdentifier()); - data.append(TagPrivate::stringHandler->render(d->title).resize(30)); - data.append(TagPrivate::stringHandler->render(d->artist).resize(30)); - data.append(TagPrivate::stringHandler->render(d->album).resize(30)); - data.append(TagPrivate::stringHandler->render(d->year).resize(4)); - data.append(TagPrivate::stringHandler->render(d->comment).resize(28)); + data.append(stringHandler->render(d->title).resize(30)); + data.append(stringHandler->render(d->artist).resize(30)); + data.append(stringHandler->render(d->album).resize(30)); + data.append(stringHandler->render(d->year).resize(4)); + data.append(stringHandler->render(d->comment).resize(28)); data.append(char(0)); data.append(char(d->track)); data.append(char(d->genre)); @@ -142,36 +152,16 @@ String ID3v1::Tag::genre() const return ID3v1::genre(d->genre); } -TagLib::uint ID3v1::Tag::year() const +unsigned int ID3v1::Tag::year() const { return d->year.toInt(); } -TagLib::uint ID3v1::Tag::track() const +unsigned int ID3v1::Tag::track() const { return d->track; } -float ID3v1::Tag::rgAlbumGain() const -{ - return 0; -} - -float ID3v1::Tag::rgAlbumPeak() const -{ - return 0; -} - -float ID3v1::Tag::rgTrackGain() const -{ - return 0; -} - -float ID3v1::Tag::rgTrackPeak() const -{ - return 0; -} - void ID3v1::Tag::setTitle(const String &s) { d->title = s; @@ -197,36 +187,32 @@ void ID3v1::Tag::setGenre(const String &s) d->genre = ID3v1::genreIndex(s); } -void ID3v1::Tag::setYear(uint i) +void ID3v1::Tag::setYear(unsigned int i) { - d->year = i > 0 ? String::number(i) : String::null; + d->year = i > 0 ? String::number(i) : String(); } -void ID3v1::Tag::setTrack(uint i) +void ID3v1::Tag::setTrack(unsigned int i) { d->track = i < 256 ? i : 0; } +unsigned int ID3v1::Tag::genreNumber() const +{ + return d->genre; +} + +void ID3v1::Tag::setGenreNumber(unsigned int i) +{ + d->genre = i < 256 ? i : 255; +} + void ID3v1::Tag::setStringHandler(const StringHandler *handler) { - delete TagPrivate::stringHandler; - TagPrivate::stringHandler = handler; -} - -void ID3v1::Tag::setRGAlbumGain(float) -{ -} - -void ID3v1::Tag::setRGAlbumPeak(float) -{ -} - -void ID3v1::Tag::setRGTrackGain(float) -{ -} - -void ID3v1::Tag::setRGTrackPeak(float) -{ + if(handler) + stringHandler = handler; + else + stringHandler = &defaultStringHandler; } //////////////////////////////////////////////////////////////////////////////// @@ -238,7 +224,7 @@ void ID3v1::Tag::read() if(d->file && d->file->isValid()) { d->file->seek(d->tagOffset); // read the tag -- always 128 bytes - ByteVector data = d->file->readBlock(128); + const ByteVector data = d->file->readBlock(128); // some initial sanity checking if(data.size() == 128 && data.startsWith("TAG")) @@ -252,16 +238,16 @@ void ID3v1::Tag::parse(const ByteVector &data) { int offset = 3; - d->title = TagPrivate::stringHandler->parse(data.mid(offset, 30)); + d->title = stringHandler->parse(data.mid(offset, 30)); offset += 30; - d->artist = TagPrivate::stringHandler->parse(data.mid(offset, 30)); + d->artist = stringHandler->parse(data.mid(offset, 30)); offset += 30; - d->album = TagPrivate::stringHandler->parse(data.mid(offset, 30)); + d->album = stringHandler->parse(data.mid(offset, 30)); offset += 30; - d->year = TagPrivate::stringHandler->parse(data.mid(offset, 4)); + d->year = stringHandler->parse(data.mid(offset, 4)); offset += 4; // Check for ID3v1.1 -- Note that ID3v1 *does not* support "track zero" -- this @@ -272,13 +258,13 @@ void ID3v1::Tag::parse(const ByteVector &data) if(data[offset + 28] == 0 && data[offset + 29] != 0) { // ID3v1.1 detected - d->comment = TagPrivate::stringHandler->parse(data.mid(offset, 28)); - d->track = uchar(data[offset + 29]); + d->comment = stringHandler->parse(data.mid(offset, 28)); + d->track = static_cast<unsigned char>(data[offset + 29]); } else d->comment = data.mid(offset, 30); offset += 30; - d->genre = uchar(data[offset]); + d->genre = static_cast<unsigned char>(data[offset]); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h index 0b509daf8..b61f06af9 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v1/id3v1tag.h @@ -62,6 +62,7 @@ namespace TagLib { TAGLIB_IGNORE_MISSING_DESTRUCTOR public: // BIC: Add virtual destructor. + StringHandler(); /*! * Decode a string from \a data. The default implementation assumes that @@ -84,7 +85,7 @@ namespace TagLib { //! The main class in the ID3v1 implementation /*! - * This is an implementation of the ID3v1 format. ID3v1 is both the simplist + * This is an implementation of the ID3v1 format. ID3v1 is both the simplest * and most common of tag formats but is rather limited. Because of its * pervasiveness and the way that applications have been written around the * fields that it provides, the generic TagLib::Tag API is a mirror of what is @@ -139,28 +140,40 @@ namespace TagLib { virtual String album() const; virtual String comment() const; virtual String genre() const; - virtual uint year() const; - virtual uint track() const; - virtual float rgAlbumGain() const; - virtual float rgAlbumPeak() const; - virtual float rgTrackGain() const; - virtual float rgTrackPeak() const; + virtual unsigned int year() const; + virtual unsigned int track() const; virtual void setTitle(const String &s); virtual void setArtist(const String &s); virtual void setAlbum(const String &s); virtual void setComment(const String &s); virtual void setGenre(const String &s); - virtual void setYear(uint i); - virtual void setTrack(uint i); - virtual void setRGAlbumGain(float f); - virtual void setRGAlbumPeak(float f); - virtual void setRGTrackGain(float f); - virtual void setRGTrackPeak(float f); + virtual void setYear(unsigned int i); + virtual void setTrack(unsigned int i); + + /*! + * Returns the genre in number. + * + * \note Normally 255 indicates that this tag contains no genre. + */ + unsigned int genreNumber() const; + + /*! + * Sets the genre in number to \a i. + * + * \note Valid value is from 0 up to 255. Normally 255 indicates that + * this tag contains no genre. + */ + void setGenreNumber(unsigned int i); /*! * Sets the string handler that decides how the ID3v1 data will be * converted to and from binary data. + * If the parameter \a handler is null, the previous handler is + * released and default ISO-8859-1 handler is restored. + * + * \note The caller is responsible for deleting the previous handler + * as needed after it is released. * * \see StringHandler */ diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/attachedpictureframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/attachedpictureframe.cpp index e4e97d01f..8e2630cef 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/attachedpictureframe.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/attachedpictureframe.cpp @@ -48,14 +48,16 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -AttachedPictureFrame::AttachedPictureFrame() : Frame("APIC") +AttachedPictureFrame::AttachedPictureFrame() : + Frame("APIC"), + d(new AttachedPictureFramePrivate()) { - d = new AttachedPictureFramePrivate; } -AttachedPictureFrame::AttachedPictureFrame(const ByteVector &data) : Frame(data) +AttachedPictureFrame::AttachedPictureFrame(const ByteVector &data) : + Frame(data), + d(new AttachedPictureFramePrivate()) { - d = new AttachedPictureFramePrivate; setData(data); } @@ -136,8 +138,8 @@ void AttachedPictureFrame::parseFields(const ByteVector &data) int pos = 1; d->mimeType = readStringField(data, String::Latin1, &pos); - /* Now we need at least two more bytes available */ - if (uint(pos) + 1 >= data.size()) { + /* Now we need at least two more bytes available */ + if(static_cast<unsigned int>(pos) + 1 >= data.size()) { debug("Truncated picture frame."); return; } @@ -152,7 +154,7 @@ ByteVector AttachedPictureFrame::renderFields() const { ByteVector data; - String::Type encoding = checkEncoding(d->description, d->textEncoding); + String::Type encoding = checkTextEncoding(d->description, d->textEncoding); data.append(char(encoding)); data.append(d->mimeType.data(String::Latin1)); @@ -169,9 +171,10 @@ ByteVector AttachedPictureFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -AttachedPictureFrame::AttachedPictureFrame(const ByteVector &data, Header *h) : Frame(h) +AttachedPictureFrame::AttachedPictureFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new AttachedPictureFramePrivate()) { - d = new AttachedPictureFramePrivate; parseFields(fieldData(data)); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/chapterframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/chapterframe.cpp new file mode 100644 index 000000000..049f947d8 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -0,0 +1,309 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tbytevectorlist.h> +#include <tpropertymap.h> +#include <tdebug.h> +#include <stdio.h> + +#include "chapterframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class ChapterFrame::ChapterFramePrivate +{ +public: + ChapterFramePrivate() : + tagHeader(0), + startTime(0), + endTime(0), + startOffset(0), + endOffset(0) + { + embeddedFrameList.setAutoDelete(true); + } + + const ID3v2::Header *tagHeader; + ByteVector elementID; + unsigned int startTime; + unsigned int endTime; + unsigned int startOffset; + unsigned int endOffset; + FrameListMap embeddedFrameListMap; + FrameList embeddedFrameList; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data) : + ID3v2::Frame(data), + d(new ChapterFramePrivate()) +{ + d->tagHeader = tagHeader; + setData(data); +} + +ChapterFrame::ChapterFrame(const ByteVector &elementID, + unsigned int startTime, unsigned int endTime, + unsigned int startOffset, unsigned int endOffset, + const FrameList &embeddedFrames) : + ID3v2::Frame("CHAP"), + d(new ChapterFramePrivate()) +{ + // setElementID has a workaround for a previously silly API where you had to + // specifically include the null byte. + + setElementID(elementID); + + d->startTime = startTime; + d->endTime = endTime; + d->startOffset = startOffset; + d->endOffset = endOffset; + + for(FrameList::ConstIterator it = embeddedFrames.begin(); + it != embeddedFrames.end(); ++it) + addEmbeddedFrame(*it); +} + +ChapterFrame::~ChapterFrame() +{ + delete d; +} + +ByteVector ChapterFrame::elementID() const +{ + return d->elementID; +} + +unsigned int ChapterFrame::startTime() const +{ + return d->startTime; +} + +unsigned int ChapterFrame::endTime() const +{ + return d->endTime; +} + +unsigned int ChapterFrame::startOffset() const +{ + return d->startOffset; +} + +unsigned int ChapterFrame::endOffset() const +{ + return d->endOffset; +} + +void ChapterFrame::setElementID(const ByteVector &eID) +{ + d->elementID = eID; + + if(d->elementID.endsWith(char(0))) + d->elementID = d->elementID.mid(0, d->elementID.size() - 1); +} + +void ChapterFrame::setStartTime(const unsigned int &sT) +{ + d->startTime = sT; +} + +void ChapterFrame::setEndTime(const unsigned int &eT) +{ + d->endTime = eT; +} + +void ChapterFrame::setStartOffset(const unsigned int &sO) +{ + d->startOffset = sO; +} + +void ChapterFrame::setEndOffset(const unsigned int &eO) +{ + d->endOffset = eO; +} + +const FrameListMap &ChapterFrame::embeddedFrameListMap() const +{ + return d->embeddedFrameListMap; +} + +const FrameList &ChapterFrame::embeddedFrameList() const +{ + return d->embeddedFrameList; +} + +const FrameList &ChapterFrame::embeddedFrameList(const ByteVector &frameID) const +{ + return d->embeddedFrameListMap[frameID]; +} + +void ChapterFrame::addEmbeddedFrame(Frame *frame) +{ + d->embeddedFrameList.append(frame); + d->embeddedFrameListMap[frame->frameID()].append(frame); +} + +void ChapterFrame::removeEmbeddedFrame(Frame *frame, bool del) +{ + // remove the frame from the frame list + FrameList::Iterator it = d->embeddedFrameList.find(frame); + d->embeddedFrameList.erase(it); + + // ...and from the frame list map + it = d->embeddedFrameListMap[frame->frameID()].find(frame); + d->embeddedFrameListMap[frame->frameID()].erase(it); + + // ...and delete as desired + if(del) + delete frame; +} + +void ChapterFrame::removeEmbeddedFrames(const ByteVector &id) +{ + FrameList l = d->embeddedFrameListMap[id]; + for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) + removeEmbeddedFrame(*it, true); +} + +String ChapterFrame::toString() const +{ + String s = String(d->elementID) + + ": start time: " + String::number(d->startTime) + + ", end time: " + String::number(d->endTime); + + if(d->startOffset != 0xFFFFFFFF) + s += ", start offset: " + String::number(d->startOffset); + + if(d->endOffset != 0xFFFFFFFF) + s += ", end offset: " + String::number(d->endOffset); + + if(!d->embeddedFrameList.isEmpty()) { + StringList frameIDs; + for(FrameList::ConstIterator it = d->embeddedFrameList.begin(); + it != d->embeddedFrameList.end(); ++it) + frameIDs.append((*it)->frameID()); + s += ", sub-frames: [ " + frameIDs.toString(", ") + " ]"; + } + + return s; +} + +PropertyMap ChapterFrame::asProperties() const +{ + PropertyMap map; + + map.unsupportedData().append(frameID() + String("/") + d->elementID); + + return map; +} + +ChapterFrame *ChapterFrame::findByElementID(const ID3v2::Tag *tag, const ByteVector &eID) // static +{ + ID3v2::FrameList comments = tag->frameList("CHAP"); + + for(ID3v2::FrameList::ConstIterator it = comments.begin(); + it != comments.end(); + ++it) + { + ChapterFrame *frame = dynamic_cast<ChapterFrame *>(*it); + if(frame && frame->elementID() == eID) + return frame; + } + + return 0; +} + +void ChapterFrame::parseFields(const ByteVector &data) +{ + unsigned int size = data.size(); + if(size < 18) { + debug("A CHAP frame must contain at least 18 bytes (1 byte element ID " + "terminated by null and 4x4 bytes for start and end time and offset)."); + return; + } + + int pos = 0; + unsigned int embPos = 0; + d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); + d->startTime = data.toUInt(pos, true); + pos += 4; + d->endTime = data.toUInt(pos, true); + pos += 4; + d->startOffset = data.toUInt(pos, true); + pos += 4; + d->endOffset = data.toUInt(pos, true); + pos += 4; + size -= pos; + + // Embedded frames are optional + + if(size < header()->size()) + return; + + while(embPos < size - header()->size()) { + Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); + + if(!frame) + return; + + // Checks to make sure that frame parsed correctly. + if(frame->size() <= 0) { + delete frame; + return; + } + + embPos += frame->size() + header()->size(); + addEmbeddedFrame(frame); + } +} + +ByteVector ChapterFrame::renderFields() const +{ + ByteVector data; + + data.append(d->elementID); + data.append('\0'); + data.append(ByteVector::fromUInt(d->startTime, true)); + data.append(ByteVector::fromUInt(d->endTime, true)); + data.append(ByteVector::fromUInt(d->startOffset, true)); + data.append(ByteVector::fromUInt(d->endOffset, true)); + FrameList l = d->embeddedFrameList; + for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) + data.append((*it)->render()); + + return data; +} + +ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data, Header *h) : + Frame(h), + d(new ChapterFramePrivate()) +{ + d->tagHeader = tagHeader; + parseFields(fieldData(data)); +} diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/chapterframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/chapterframe.h new file mode 100644 index 000000000..0eaa140f2 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/chapterframe.h @@ -0,0 +1,249 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_CHAPTERFRAME +#define TAGLIB_CHAPTERFRAME + +#include "id3v2tag.h" +#include "id3v2frame.h" +#include "taglib_export.h" + +namespace TagLib { + + namespace ID3v2 { + + /*! + * This is an implementation of ID3v2 chapter frames. The purpose of this + * frame is to describe a single chapter within an audio file. + */ + + //! An implementation of ID3v2 chapter frames + + class TAGLIB_EXPORT ChapterFrame : public ID3v2::Frame + { + friend class FrameFactory; + + public: + /*! + * Creates a chapter frame based on \a data. \a tagHeader is required as + * the internal frames are parsed based on the tag version. + */ + ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data); + + /*! + * Creates a chapter frame with the element ID \a elementID, start time + * \a startTime, end time \a endTime, start offset \a startOffset, + * end offset \a endOffset and optionally a list of embedded frames, + * whose ownership will then be taken over by this Frame, in + * \a embeddedFrames; + * + * All times are in milliseconds. + */ + ChapterFrame(const ByteVector &elementID, + unsigned int startTime, unsigned int endTime, + unsigned int startOffset, unsigned int endOffset, + const FrameList &embeddedFrames = FrameList()); + + /*! + * Destroys the frame. + */ + virtual ~ChapterFrame(); + + /*! + * Returns the element ID of the frame. Element ID + * is a null terminated string, however it's not human-readable. + * + * \see setElementID() + */ + ByteVector elementID() const; + + /*! + * Returns time of chapter's start (in milliseconds). + * + * \see setStartTime() + */ + unsigned int startTime() const; + + /*! + * Returns time of chapter's end (in milliseconds). + * + * \see setEndTime() + */ + unsigned int endTime() const; + + /*! + * Returns zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's start. + * + * \note If returned value is 0xFFFFFFFF, start time should be used instead. + * \see setStartOffset() + */ + unsigned int startOffset() const; + + /*! + * Returns zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's end. + * + * \note If returned value is 0xFFFFFFFF, end time should be used instead. + * \see setEndOffset() + */ + unsigned int endOffset() const; + + /*! + * Sets the element ID of the frame to \a eID. If \a eID isn't + * null terminated, a null char is appended automatically. + * + * \see elementID() + */ + void setElementID(const ByteVector &eID); + + /*! + * Sets time of chapter's start (in milliseconds) to \a sT. + * + * \see startTime() + */ + void setStartTime(const unsigned int &sT); + + /*! + * Sets time of chapter's end (in milliseconds) to \a eT. + * + * \see endTime() + */ + void setEndTime(const unsigned int &eT); + + /*! + * Sets zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's start to \a sO. + * + * \see startOffset() + */ + void setStartOffset(const unsigned int &sO); + + /*! + * Sets zero based byte offset (count of bytes from the beginning + * of the audio file) of chapter's end to \a eO. + * + * \see endOffset() + */ + void setEndOffset(const unsigned int &eO); + + /*! + * Returns a reference to the frame list map. This is an FrameListMap of + * all of the frames embedded in the CHAP frame. + * + * This is the most convenient structure for accessing the CHAP frame's + * embedded frames. Many frame types allow multiple instances of the same + * frame type so this is a map of lists. In most cases however there will + * only be a single frame of a certain type. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + * + * \see embeddedFrameList() + */ + const FrameListMap &embeddedFrameListMap() const; + + /*! + * Returns a reference to the embedded frame list. This is an FrameList + * of all of the frames embedded in the CHAP frame in the order that they + * were parsed. + * + * This can be useful if for example you want iterate over the CHAP frame's + * embedded frames in the order that they occur in the CHAP frame. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + */ + const FrameList &embeddedFrameList() const; + + /*! + * Returns the embedded frame list for frames with the id \a frameID + * or an empty list if there are no embedded frames of that type. This + * is just a convenience and is equivalent to: + * + * \code + * embeddedFrameListMap()[frameID]; + * \endcode + * + * \see embeddedFrameListMap() + */ + const FrameList &embeddedFrameList(const ByteVector &frameID) const; + + /*! + * Add an embedded frame to the CHAP frame. At this point the CHAP frame + * takes ownership of the embedded frame and will handle freeing its memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void addEmbeddedFrame(Frame *frame); + + /*! + * Remove an embedded frame from the CHAP frame. If \a del is true the frame's + * memory will be freed; if it is false, it must be deleted by the user. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrame(Frame *frame, bool del = true); + + /*! + * Remove all embedded frames of type \a id from the CHAP frame and free their + * memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrames(const ByteVector &id); + + virtual String toString() const; + + PropertyMap asProperties() const; + + /*! + * CHAP frames each have a unique element ID. This searches for a CHAP + * frame with the element ID \a eID and returns a pointer to it. This + * can be used to link CTOC and CHAP frames together. + * + * \see elementID() + */ + static ChapterFrame *findByElementID(const Tag *tag, const ByteVector &eID); + + protected: + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &data, Header *h); + ChapterFrame(const ChapterFrame &); + ChapterFrame &operator=(const ChapterFrame &); + + class ChapterFramePrivate; + ChapterFramePrivate *d; + }; + } +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/commentsframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/commentsframe.cpp index 406598d99..815e5e1a1 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/commentsframe.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/commentsframe.cpp @@ -29,6 +29,7 @@ #include <tstringlist.h> #include "commentsframe.h" +#include "tpropertymap.h" using namespace TagLib; using namespace ID3v2; @@ -47,15 +48,17 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -CommentsFrame::CommentsFrame(String::Type encoding) : Frame("COMM") +CommentsFrame::CommentsFrame(String::Type encoding) : + Frame("COMM"), + d(new CommentsFramePrivate()) { - d = new CommentsFramePrivate; d->textEncoding = encoding; } -CommentsFrame::CommentsFrame(const ByteVector &data) : Frame(data) +CommentsFrame::CommentsFrame(const ByteVector &data) : + Frame(data), + d(new CommentsFramePrivate()) { - d = new CommentsFramePrivate; setData(data); } @@ -109,6 +112,17 @@ void CommentsFrame::setTextEncoding(String::Type encoding) d->textEncoding = encoding; } +PropertyMap CommentsFrame::asProperties() const +{ + String key = description().upper(); + PropertyMap map; + if(key.isEmpty() || key == "COMMENT") + map.insert("COMMENT", text()); + else + map.insert("COMMENT:" + key, text()); + return map; +} + CommentsFrame *CommentsFrame::findByDescription(const ID3v2::Tag *tag, const String &d) // static { ID3v2::FrameList comments = tag->frameList("COMM"); @@ -144,8 +158,13 @@ void CommentsFrame::parseFields(const ByteVector &data) ByteVectorList l = ByteVectorList::split(data.mid(4), textDelimiter(d->textEncoding), byteAlign, 2); if(l.size() == 2) { - d->description = String(l.front(), d->textEncoding); - d->text = String(l.back(), d->textEncoding); + if(d->textEncoding == String::Latin1) { + d->description = Tag::latin1StringHandler()->parse(l.front()); + d->text = Tag::latin1StringHandler()->parse(l.back()); + } else { + d->description = String(l.front(), d->textEncoding); + d->text = String(l.back(), d->textEncoding); + } } } @@ -155,8 +174,8 @@ ByteVector CommentsFrame::renderFields() const String::Type encoding = d->textEncoding; - encoding = checkEncoding(d->description, encoding); - encoding = checkEncoding(d->text, encoding); + encoding = checkTextEncoding(d->description, encoding); + encoding = checkTextEncoding(d->text, encoding); v.append(char(encoding)); v.append(d->language.size() == 3 ? d->language : "XXX"); @@ -171,8 +190,9 @@ ByteVector CommentsFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -CommentsFrame::CommentsFrame(const ByteVector &data, Header *h) : Frame(h) +CommentsFrame::CommentsFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new CommentsFramePrivate()) { - d = new CommentsFramePrivate(); parseFields(fieldData(data)); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/commentsframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/commentsframe.h index def01dcfc..4da9d5357 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/commentsframe.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/commentsframe.h @@ -36,7 +36,7 @@ namespace TagLib { //! An implementation of ID3v2 comments /*! - * This implements the ID3v2 comment format. An ID3v2 comment concists of + * This implements the ID3v2 comment format. An ID3v2 comment consists of * a language encoding, a description and a single text field. */ @@ -106,7 +106,7 @@ namespace TagLib { /*! * Sets the description of the comment to \a s. * - * \see decription() + * \see description() */ void setDescription(const String &s); @@ -136,9 +136,20 @@ namespace TagLib { */ void setTextEncoding(String::Type encoding); + /*! + * Parses this frame as PropertyMap with a single key. + * - if description() is empty or "COMMENT", the key will be "COMMENT" + * - if description() is not a valid PropertyMap key, the frame will be + * marked unsupported by an entry "COMM/<description>" in the unsupportedData() + * attribute of the returned map. + * - otherwise, the key will be "COMMENT:<description>" + * - The single value will be the frame's text(). + */ + PropertyMap asProperties() const; + /*! * Comments each have a unique description. This searches for a comment - * frame with the decription \a d and returns a pointer to it. If no + * frame with the description \a d and returns a pointer to it. If no * frame is found that matches the given description null is returned. * * \see description() diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp new file mode 100644 index 000000000..930318123 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/eventtimingcodesframe.cpp @@ -0,0 +1,144 @@ +/*************************************************************************** + copyright : (C) 2014 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "eventtimingcodesframe.h" +#include <tbytevectorlist.h> +#include <id3v2tag.h> +#include <tdebug.h> +#include <tpropertymap.h> + +using namespace TagLib; +using namespace ID3v2; + +class EventTimingCodesFrame::EventTimingCodesFramePrivate +{ +public: + EventTimingCodesFramePrivate() : + timestampFormat(EventTimingCodesFrame::AbsoluteMilliseconds) {} + EventTimingCodesFrame::TimestampFormat timestampFormat; + EventTimingCodesFrame::SynchedEventList synchedEvents; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +EventTimingCodesFrame::EventTimingCodesFrame() : + Frame("ETCO"), + d(new EventTimingCodesFramePrivate()) +{ +} + +EventTimingCodesFrame::EventTimingCodesFrame(const ByteVector &data) : + Frame(data), + d(new EventTimingCodesFramePrivate()) +{ + setData(data); +} + +EventTimingCodesFrame::~EventTimingCodesFrame() +{ + delete d; +} + +String EventTimingCodesFrame::toString() const +{ + return String(); +} + +EventTimingCodesFrame::TimestampFormat +EventTimingCodesFrame::timestampFormat() const +{ + return d->timestampFormat; +} + +EventTimingCodesFrame::SynchedEventList +EventTimingCodesFrame::synchedEvents() const +{ + return d->synchedEvents; +} + +void EventTimingCodesFrame::setTimestampFormat( + EventTimingCodesFrame::TimestampFormat f) +{ + d->timestampFormat = f; +} + +void EventTimingCodesFrame::setSynchedEvents( + const EventTimingCodesFrame::SynchedEventList &e) +{ + d->synchedEvents = e; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void EventTimingCodesFrame::parseFields(const ByteVector &data) +{ + const int end = data.size(); + if(end < 1) { + debug("An event timing codes frame must contain at least 1 byte."); + return; + } + + d->timestampFormat = TimestampFormat(data[0]); + + int pos = 1; + d->synchedEvents.clear(); + while(pos + 4 < end) { + EventType type = static_cast<EventType>(static_cast<unsigned char>(data[pos++])); + unsigned int time = data.toUInt(pos, true); + pos += 4; + d->synchedEvents.append(SynchedEvent(time, type)); + } +} + +ByteVector EventTimingCodesFrame::renderFields() const +{ + ByteVector v; + + v.append(char(d->timestampFormat)); + for(SynchedEventList::ConstIterator it = d->synchedEvents.begin(); + it != d->synchedEvents.end(); + ++it) { + const SynchedEvent &entry = *it; + v.append(char(entry.type)); + v.append(ByteVector::fromUInt(entry.time)); + } + + return v; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +EventTimingCodesFrame::EventTimingCodesFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new EventTimingCodesFramePrivate()) +{ + parseFields(fieldData(data)); +} diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/eventtimingcodesframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/eventtimingcodesframe.h new file mode 100644 index 000000000..3dcedb9eb --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/eventtimingcodesframe.h @@ -0,0 +1,185 @@ +/*************************************************************************** + copyright : (C) 2014 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_EVENTTIMINGCODESFRAME_H +#define TAGLIB_EVENTTIMINGCODESFRAME_H + +#include "id3v2frame.h" +#include "tlist.h" + +namespace TagLib { + + namespace ID3v2 { + + //! ID3v2 event timing codes frame + /*! + * An implementation of ID3v2 event timing codes. + */ + class TAGLIB_EXPORT EventTimingCodesFrame : public Frame + { + friend class FrameFactory; + + public: + + /*! + * Specifies the timestamp format used. + */ + enum TimestampFormat { + //! The timestamp is of unknown format. + Unknown = 0x00, + //! The timestamp represents the number of MPEG frames since + //! the beginning of the audio stream. + AbsoluteMpegFrames = 0x01, + //! The timestamp represents the number of milliseconds since + //! the beginning of the audio stream. + AbsoluteMilliseconds = 0x02 + }; + + /*! + * Event types defined in id3v2.4.0-frames.txt 4.5. Event timing codes. + */ + enum EventType { + Padding = 0x00, + EndOfInitialSilence = 0x01, + IntroStart = 0x02, + MainPartStart = 0x03, + OutroStart = 0x04, + OutroEnd = 0x05, + VerseStart = 0x06, + RefrainStart = 0x07, + InterludeStart = 0x08, + ThemeStart = 0x09, + VariationStart = 0x0a, + KeyChange = 0x0b, + TimeChange = 0x0c, + MomentaryUnwantedNoise = 0x0d, + SustainedNoise = 0x0e, + SustainedNoiseEnd = 0x0f, + IntroEnd = 0x10, + MainPartEnd = 0x11, + VerseEnd = 0x12, + RefrainEnd = 0x13, + ThemeEnd = 0x14, + Profanity = 0x15, + ProfanityEnd = 0x16, + NotPredefinedSynch0 = 0xe0, + NotPredefinedSynch1 = 0xe1, + NotPredefinedSynch2 = 0xe2, + NotPredefinedSynch3 = 0xe3, + NotPredefinedSynch4 = 0xe4, + NotPredefinedSynch5 = 0xe5, + NotPredefinedSynch6 = 0xe6, + NotPredefinedSynch7 = 0xe7, + NotPredefinedSynch8 = 0xe8, + NotPredefinedSynch9 = 0xe9, + NotPredefinedSynchA = 0xea, + NotPredefinedSynchB = 0xeb, + NotPredefinedSynchC = 0xec, + NotPredefinedSynchD = 0xed, + NotPredefinedSynchE = 0xee, + NotPredefinedSynchF = 0xef, + AudioEnd = 0xfd, + AudioFileEnds = 0xfe + }; + + /*! + * Single entry of time stamp and event. + */ + struct SynchedEvent { + SynchedEvent(unsigned int ms, EventType t) : time(ms), type(t) {} + unsigned int time; + EventType type; + }; + + /*! + * List of synchronized events. + */ + typedef TagLib::List<SynchedEvent> SynchedEventList; + + /*! + * Construct an empty event timing codes frame. + */ + explicit EventTimingCodesFrame(); + + /*! + * Construct a event timing codes frame based on the data in \a data. + */ + explicit EventTimingCodesFrame(const ByteVector &data); + + /*! + * Destroys this EventTimingCodesFrame instance. + */ + virtual ~EventTimingCodesFrame(); + + /*! + * Returns a null string. + */ + virtual String toString() const; + + /*! + * Returns the timestamp format. + */ + TimestampFormat timestampFormat() const; + + /*! + * Returns the events with the time stamps. + */ + SynchedEventList synchedEvents() const; + + /*! + * Set the timestamp format. + * + * \see timestampFormat() + */ + void setTimestampFormat(TimestampFormat f); + + /*! + * Sets the text with the time stamps. + * + * \see text() + */ + void setSynchedEvents(const SynchedEventList &e); + + protected: + // Reimplementations. + + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + /*! + * The constructor used by the FrameFactory. + */ + EventTimingCodesFrame(const ByteVector &data, Header *h); + EventTimingCodesFrame(const EventTimingCodesFrame &); + EventTimingCodesFrame &operator=(const EventTimingCodesFrame &); + + class EventTimingCodesFramePrivate; + EventTimingCodesFramePrivate *d; + }; + + } +} +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp index fa3509b13..c9b2f6dc4 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp @@ -1,6 +1,7 @@ /*************************************************************************** copyright : (C) 2002 - 2008 by Scott Wheeler email : wheeler@kde.org + copyright : (C) 2006 by Aaron VonderHaar email : avh4@users.sourceforge.net ***************************************************************************/ @@ -26,6 +27,7 @@ ***************************************************************************/ #include <tdebug.h> +#include <tstringlist.h> #include "generalencapsulatedobjectframe.h" @@ -48,14 +50,16 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame() : Frame("GEOB") +GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame() : + Frame("GEOB"), + d(new GeneralEncapsulatedObjectFramePrivate()) { - d = new GeneralEncapsulatedObjectFramePrivate; } -GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame(const ByteVector &data) : Frame(data) +GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame(const ByteVector &data) : + Frame(data), + d(new GeneralEncapsulatedObjectFramePrivate()) { - d = new GeneralEncapsulatedObjectFramePrivate; setData(data); } @@ -151,15 +155,21 @@ void GeneralEncapsulatedObjectFrame::parseFields(const ByteVector &data) ByteVector GeneralEncapsulatedObjectFrame::renderFields() const { + StringList sl; + sl.append(d->fileName); + sl.append(d->description); + + const String::Type encoding = checkTextEncoding(sl, d->textEncoding); + ByteVector data; - data.append(char(d->textEncoding)); + data.append(char(encoding)); data.append(d->mimeType.data(String::Latin1)); data.append(textDelimiter(String::Latin1)); - data.append(d->fileName.data(d->textEncoding)); - data.append(textDelimiter(d->textEncoding)); - data.append(d->description.data(d->textEncoding)); - data.append(textDelimiter(d->textEncoding)); + data.append(d->fileName.data(encoding)); + data.append(textDelimiter(encoding)); + data.append(d->description.data(encoding)); + data.append(textDelimiter(encoding)); data.append(d->data); return data; @@ -169,8 +179,9 @@ ByteVector GeneralEncapsulatedObjectFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame(const ByteVector &data, Header *h) : Frame(h) +GeneralEncapsulatedObjectFrame::GeneralEncapsulatedObjectFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new GeneralEncapsulatedObjectFramePrivate()) { - d = new GeneralEncapsulatedObjectFramePrivate; parseFields(fieldData(data)); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.h index 42f854ccd..769dfb025 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/generalencapsulatedobjectframe.h @@ -1,6 +1,7 @@ /*************************************************************************** copyright : (C) 2002 - 2008 by Scott Wheeler email : wheeler@kde.org + copyright : (C) 2006 by Aaron VonderHaar email : avh4@users.sourceforge.net ***************************************************************************/ diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/ownershipframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/ownershipframe.cpp new file mode 100644 index 000000000..0b180dbf6 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/ownershipframe.cpp @@ -0,0 +1,171 @@ +/*************************************************************************** + copyright : (C) 2012 by Rupert Daniel + email : rupert@cancelmonday.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tdebug.h> +#include <tstringlist.h> +#include <id3v2tag.h> + +#include "ownershipframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class OwnershipFrame::OwnershipFramePrivate +{ +public: + String pricePaid; + String datePurchased; + String seller; + String::Type textEncoding; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +OwnershipFrame::OwnershipFrame(String::Type encoding) : + Frame("OWNE"), + d(new OwnershipFramePrivate()) +{ + d->textEncoding = encoding; +} + +OwnershipFrame::OwnershipFrame(const ByteVector &data) : + Frame(data), + d(new OwnershipFramePrivate()) +{ + setData(data); +} + +OwnershipFrame::~OwnershipFrame() +{ + delete d; +} + +String OwnershipFrame::toString() const +{ + return "pricePaid=" + d->pricePaid + " datePurchased=" + d->datePurchased + " seller=" + d->seller; +} + +String OwnershipFrame::pricePaid() const +{ + return d->pricePaid; +} + +void OwnershipFrame::setPricePaid(const String &s) +{ + d->pricePaid = s; +} + +String OwnershipFrame::datePurchased() const +{ + return d->datePurchased; +} + +void OwnershipFrame::setDatePurchased(const String &s) +{ + d->datePurchased = s; +} + +String OwnershipFrame::seller() const +{ + return d->seller; +} + +void OwnershipFrame::setSeller(const String &s) +{ + d->seller = s; +} + +String::Type OwnershipFrame::textEncoding() const +{ + return d->textEncoding; +} + +void OwnershipFrame::setTextEncoding(String::Type encoding) +{ + d->textEncoding = encoding; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void OwnershipFrame::parseFields(const ByteVector &data) +{ + int pos = 0; + + // Get the text encoding + d->textEncoding = String::Type(data[0]); + pos += 1; + + // Read the price paid this is a null terminate string + d->pricePaid = readStringField(data, String::Latin1, &pos); + + // If we don't have at least 8 bytes left then don't parse the rest of the + // data + if(data.size() - pos < 8) { + return; + } + + // Read the date purchased YYYYMMDD + d->datePurchased = String(data.mid(pos, 8)); + pos += 8; + + // Read the seller + if(d->textEncoding == String::Latin1) + d->seller = Tag::latin1StringHandler()->parse(data.mid(pos)); + else + d->seller = String(data.mid(pos), d->textEncoding); +} + +ByteVector OwnershipFrame::renderFields() const +{ + StringList sl; + sl.append(d->seller); + + const String::Type encoding = checkTextEncoding(sl, d->textEncoding); + + ByteVector v; + + v.append(char(encoding)); + v.append(d->pricePaid.data(String::Latin1)); + v.append(textDelimiter(String::Latin1)); + v.append(d->datePurchased.data(String::Latin1)); + v.append(d->seller.data(encoding)); + + return v; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +OwnershipFrame::OwnershipFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new OwnershipFramePrivate()) +{ + parseFields(fieldData(data)); +} diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/ownershipframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/ownershipframe.h new file mode 100644 index 000000000..06a1e2336 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/ownershipframe.h @@ -0,0 +1,151 @@ +/*************************************************************************** + copyright : (C) 2012 by Rupert Daniel + email : rupert@cancelmonday.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_OWNERSHIPFRAME_H +#define TAGLIB_OWNERSHIPFRAME_H + +#include "id3v2frame.h" +#include "taglib_export.h" + +namespace TagLib { + + namespace ID3v2 { + + //! An implementation of ID3v2 "ownership" + + /*! + * This implements the ID3v2 ownership (OWNE frame). It consists of + * a price paid, a date purchased (YYYYMMDD) and the name of the seller. + */ + + class TAGLIB_EXPORT OwnershipFrame : public Frame + { + friend class FrameFactory; + + public: + /*! + * Construct an empty ownership frame. + */ + explicit OwnershipFrame(String::Type encoding = String::Latin1); + + /*! + * Construct a ownership based on the data in \a data. + */ + explicit OwnershipFrame(const ByteVector &data); + + /*! + * Destroys this OwnershipFrame instance. + */ + virtual ~OwnershipFrame(); + + /*! + * Returns the text of this popularimeter. + * + * \see text() + */ + virtual String toString() const; + + /*! + * Returns the date purchased. + * + * \see setDatePurchased() + */ + String datePurchased() const; + + /*! + * Set the date purchased. + * + * \see datePurchased() + */ + void setDatePurchased(const String &datePurchased); + + /*! + * Returns the price paid. + * + * \see setPricePaid() + */ + String pricePaid() const; + + /*! + * Set the price paid. + * + * \see pricePaid() + */ + void setPricePaid(const String &pricePaid); + + /*! + * Returns the seller. + * + * \see setSeller() + */ + String seller() const; + + /*! + * Set the seller. + * + * \see seller() + */ + void setSeller(const String &seller); + + /*! + * Returns the text encoding that will be used in rendering this frame. + * This defaults to the type that was either specified in the constructor + * or read from the frame when parsed. + * + * \see setTextEncoding() + * \see render() + */ + String::Type textEncoding() const; + + /*! + * Sets the text encoding to be used when rendering this frame to + * \a encoding. + * + * \see textEncoding() + * \see render() + */ + void setTextEncoding(String::Type encoding); + + protected: + // Reimplementations. + + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + /*! + * The constructor used by the FrameFactory. + */ + OwnershipFrame(const ByteVector &data, Header *h); + OwnershipFrame(const OwnershipFrame &); + OwnershipFrame &operator=(const OwnershipFrame &); + + class OwnershipFramePrivate; + OwnershipFramePrivate *d; + }; + + } +} +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/podcastframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/podcastframe.cpp new file mode 100644 index 000000000..7dfe471d1 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/podcastframe.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + copyright : (C) 2015 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "podcastframe.h" +#include <tpropertymap.h> + +using namespace TagLib; +using namespace ID3v2; + +class PodcastFrame::PodcastFramePrivate +{ +public: + ByteVector fieldData; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +PodcastFrame::PodcastFrame() : + Frame("PCST"), + d(new PodcastFramePrivate()) +{ + d->fieldData = ByteVector(4, '\0'); +} + +PodcastFrame::~PodcastFrame() +{ + delete d; +} + +String PodcastFrame::toString() const +{ + return String(); +} + +PropertyMap PodcastFrame::asProperties() const +{ + PropertyMap map; + map.insert("PODCAST", StringList()); + return map; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void PodcastFrame::parseFields(const ByteVector &data) +{ + d->fieldData = data; +} + +ByteVector PodcastFrame::renderFields() const +{ + return d->fieldData; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +PodcastFrame::PodcastFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new PodcastFramePrivate()) +{ + parseFields(fieldData(data)); +} diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/podcastframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/podcastframe.h new file mode 100644 index 000000000..a71278c3f --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/podcastframe.h @@ -0,0 +1,82 @@ +/*************************************************************************** + copyright : (C) 2015 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_PODCASTFRAME_H +#define TAGLIB_PODCASTFRAME_H + +#include "id3v2frame.h" +#include "taglib_export.h" + +namespace TagLib { + + namespace ID3v2 { + + //! ID3v2 podcast frame + /*! + * An implementation of ID3v2 podcast flag, a frame with four zero bytes. + */ + class TAGLIB_EXPORT PodcastFrame : public Frame + { + friend class FrameFactory; + + public: + /*! + * Construct a podcast frame. + */ + PodcastFrame(); + + /*! + * Destroys this PodcastFrame instance. + */ + virtual ~PodcastFrame(); + + /*! + * Returns a null string. + */ + virtual String toString() const; + + PropertyMap asProperties() const; + + protected: + // Reimplementations. + + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + /*! + * The constructor used by the FrameFactory. + */ + PodcastFrame(const ByteVector &data, Header *h); + PodcastFrame(const PodcastFrame &); + PodcastFrame &operator=(const PodcastFrame &); + + class PodcastFramePrivate; + PodcastFramePrivate *d; + }; + + } +} +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/popularimeterframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/popularimeterframe.cpp index cfe8c9f44..9106fabd5 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/popularimeterframe.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/popularimeterframe.cpp @@ -36,21 +36,23 @@ public: PopularimeterFramePrivate() : rating(0), counter(0) {} String email; int rating; - TagLib::uint counter; + unsigned int counter; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -PopularimeterFrame::PopularimeterFrame() : Frame("POPM") +PopularimeterFrame::PopularimeterFrame() : + Frame("POPM"), + d(new PopularimeterFramePrivate()) { - d = new PopularimeterFramePrivate; } -PopularimeterFrame::PopularimeterFrame(const ByteVector &data) : Frame(data) +PopularimeterFrame::PopularimeterFrame(const ByteVector &data) : + Frame(data), + d(new PopularimeterFramePrivate()) { - d = new PopularimeterFramePrivate; setData(data); } @@ -84,12 +86,12 @@ void PopularimeterFrame::setRating(int s) d->rating = s; } -TagLib::uint PopularimeterFrame::counter() const +unsigned int PopularimeterFrame::counter() const { return d->counter; } -void PopularimeterFrame::setCounter(TagLib::uint s) +void PopularimeterFrame::setCounter(unsigned int s) { d->counter = s; } @@ -109,7 +111,7 @@ void PopularimeterFrame::parseFields(const ByteVector &data) if(pos < size) { d->rating = (unsigned char)(data[pos++]); if(pos < size) { - d->counter = data.mid(pos, 4).toUInt(); + d->counter = data.toUInt(static_cast<unsigned int>(pos)); } } } @@ -130,8 +132,9 @@ ByteVector PopularimeterFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -PopularimeterFrame::PopularimeterFrame(const ByteVector &data, Header *h) : Frame(h) +PopularimeterFrame::PopularimeterFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new PopularimeterFramePrivate()) { - d = new PopularimeterFramePrivate; parseFields(fieldData(data)); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/popularimeterframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/popularimeterframe.h index d39f1aa8f..405ca69e8 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/popularimeterframe.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/popularimeterframe.h @@ -36,7 +36,7 @@ namespace TagLib { //! An implementation of ID3v2 "popularimeter" /*! - * This implements the ID3v2 popularimeter (POPM frame). It concists of + * This implements the ID3v2 popularimeter (POPM frame). It consists of * an email, a rating and an optional counter. */ @@ -100,14 +100,14 @@ namespace TagLib { * * \see setCounter() */ - uint counter() const; + unsigned int counter() const; /*! * Set the counter. * * \see counter() */ - void setCounter(uint counter); + void setCounter(unsigned int counter); protected: // Reimplementations. diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/privateframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/privateframe.cpp index f2d2a03c7..4f55a3ca9 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/privateframe.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/privateframe.cpp @@ -45,15 +45,17 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -PrivateFrame::PrivateFrame() : Frame("PRIV") +PrivateFrame::PrivateFrame() : + Frame("PRIV"), + d(new PrivateFramePrivate()) { - d = new PrivateFramePrivate; } -PrivateFrame::PrivateFrame(const ByteVector &data) : Frame(data) +PrivateFrame::PrivateFrame(const ByteVector &data) : + Frame(data), + d(new PrivateFramePrivate()) { - d = new PrivateFramePrivate; - setData(data); + Frame::setData(data); } PrivateFrame::~PrivateFrame() @@ -98,7 +100,7 @@ void PrivateFrame::parseFields(const ByteVector &data) } // Owner identifier is assumed to be Latin1 - + const int byteAlign = 1; const int endOfOwner = data.find(textDelimiter(String::Latin1), 0, byteAlign); @@ -121,8 +123,9 @@ ByteVector PrivateFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -PrivateFrame::PrivateFrame(const ByteVector &data, Header *h) : Frame(h) +PrivateFrame::PrivateFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new PrivateFramePrivate()) { - d = new PrivateFramePrivate(); parseFields(fieldData(data)); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/relativevolumeframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/relativevolumeframe.cpp index 955b3ad03..3398ada82 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/relativevolumeframe.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/relativevolumeframe.cpp @@ -31,11 +31,6 @@ using namespace TagLib; using namespace ID3v2; -static inline int bitsToBytes(int i) -{ - return i % 8 == 0 ? i / 8 : (i - i % 8) / 8 + 1; -} - struct ChannelData { ChannelData() : channelType(RelativeVolumeFrame::Other), volumeAdjustment(0) {} @@ -56,14 +51,16 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RelativeVolumeFrame::RelativeVolumeFrame() : Frame("RVA2") +RelativeVolumeFrame::RelativeVolumeFrame() : + Frame("RVA2"), + d(new RelativeVolumeFramePrivate()) { - d = new RelativeVolumeFramePrivate; } -RelativeVolumeFrame::RelativeVolumeFrame(const ByteVector &data) : Frame(data) +RelativeVolumeFrame::RelativeVolumeFrame(const ByteVector &data) : + Frame(data), + d(new RelativeVolumeFramePrivate()) { - d = new RelativeVolumeFramePrivate; setData(data); } @@ -185,19 +182,18 @@ void RelativeVolumeFrame::parseFields(const ByteVector &data) while(pos <= (int)data.size() - 4) { - ChannelType type = ChannelType(data[pos]); pos += 1; ChannelData &channel = d->channels[type]; - channel.volumeAdjustment = data.mid(pos, 2).toShort(); + channel.volumeAdjustment = data.toShort(static_cast<unsigned int>(pos)); pos += 2; channel.peakVolume.bitsRepresentingPeak = data[pos]; pos += 1; - int bytes = bitsToBytes(channel.peakVolume.bitsRepresentingPeak); + const int bytes = (channel.peakVolume.bitsRepresentingPeak + 7) / 8; channel.peakVolume.peakVolume = data.mid(pos, bytes); pos += bytes; } @@ -229,8 +225,9 @@ ByteVector RelativeVolumeFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -RelativeVolumeFrame::RelativeVolumeFrame(const ByteVector &data, Header *h) : Frame(h) +RelativeVolumeFrame::RelativeVolumeFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new RelativeVolumeFramePrivate()) { - d = new RelativeVolumeFramePrivate; parseFields(fieldData(data)); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/relativevolumeframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/relativevolumeframe.h index dad4e7d4e..55f13fcee 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/relativevolumeframe.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/relativevolumeframe.h @@ -131,16 +131,16 @@ namespace TagLib { /*! * \deprecated Always returns master volume. */ - ChannelType channelType() const; + TAGLIB_DEPRECATED ChannelType channelType() const; /*! * \deprecated This method no longer has any effect. */ - void setChannelType(ChannelType t); + TAGLIB_DEPRECATED void setChannelType(ChannelType t); /* * There was a terrible API goof here, and while this can't be changed to - * the way it appears below for binary compaibility reasons, let's at + * the way it appears below for binary compatibility reasons, let's at * least pretend that it looks clean. */ @@ -149,25 +149,25 @@ namespace TagLib { /*! * Returns the relative volume adjustment "index". As indicated by the * ID3v2 standard this is a 16-bit signed integer that reflects the - * decibils of adjustment when divided by 512. + * decibels of adjustment when divided by 512. * * This defaults to returning the value for the master volume channel if * available and returns 0 if the specified channel does not exist. * * \see setVolumeAdjustmentIndex() - * \see volumeAjustment() + * \see volumeAdjustment() */ short volumeAdjustmentIndex(ChannelType type = MasterVolume) const; /*! * Set the volume adjustment to \a index. As indicated by the ID3v2 - * standard this is a 16-bit signed integer that reflects the decibils of + * standard this is a 16-bit signed integer that reflects the decibels of * adjustment when divided by 512. * * By default this sets the value for the master volume. * * \see volumeAdjustmentIndex() - * \see setVolumeAjustment() + * \see setVolumeAdjustment() */ void setVolumeAdjustmentIndex(short index, ChannelType type = MasterVolume); diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp new file mode 100644 index 000000000..6a62bb495 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.cpp @@ -0,0 +1,242 @@ +/*************************************************************************** + copyright : (C) 2014 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "synchronizedlyricsframe.h" +#include <tbytevectorlist.h> +#include <id3v2tag.h> +#include <tdebug.h> +#include <tpropertymap.h> + +using namespace TagLib; +using namespace ID3v2; + +class SynchronizedLyricsFrame::SynchronizedLyricsFramePrivate +{ +public: + SynchronizedLyricsFramePrivate() : + textEncoding(String::Latin1), + timestampFormat(SynchronizedLyricsFrame::AbsoluteMilliseconds), + type(SynchronizedLyricsFrame::Lyrics) {} + String::Type textEncoding; + ByteVector language; + SynchronizedLyricsFrame::TimestampFormat timestampFormat; + SynchronizedLyricsFrame::Type type; + String description; + SynchronizedLyricsFrame::SynchedTextList synchedText; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +SynchronizedLyricsFrame::SynchronizedLyricsFrame(String::Type encoding) : + Frame("SYLT"), + d(new SynchronizedLyricsFramePrivate()) +{ + d->textEncoding = encoding; +} + +SynchronizedLyricsFrame::SynchronizedLyricsFrame(const ByteVector &data) : + Frame(data), + d(new SynchronizedLyricsFramePrivate()) +{ + setData(data); +} + +SynchronizedLyricsFrame::~SynchronizedLyricsFrame() +{ + delete d; +} + +String SynchronizedLyricsFrame::toString() const +{ + return d->description; +} + +String::Type SynchronizedLyricsFrame::textEncoding() const +{ + return d->textEncoding; +} + +ByteVector SynchronizedLyricsFrame::language() const +{ + return d->language; +} + +SynchronizedLyricsFrame::TimestampFormat +SynchronizedLyricsFrame::timestampFormat() const +{ + return d->timestampFormat; +} + +SynchronizedLyricsFrame::Type SynchronizedLyricsFrame::type() const +{ + return d->type; +} + +String SynchronizedLyricsFrame::description() const +{ + return d->description; +} + +SynchronizedLyricsFrame::SynchedTextList +SynchronizedLyricsFrame::synchedText() const +{ + return d->synchedText; +} + +void SynchronizedLyricsFrame::setTextEncoding(String::Type encoding) +{ + d->textEncoding = encoding; +} + +void SynchronizedLyricsFrame::setLanguage(const ByteVector &languageEncoding) +{ + d->language = languageEncoding.mid(0, 3); +} + +void SynchronizedLyricsFrame::setTimestampFormat(SynchronizedLyricsFrame::TimestampFormat f) +{ + d->timestampFormat = f; +} + +void SynchronizedLyricsFrame::setType(SynchronizedLyricsFrame::Type t) +{ + d->type = t; +} + +void SynchronizedLyricsFrame::setDescription(const String &s) +{ + d->description = s; +} + +void SynchronizedLyricsFrame::setSynchedText( + const SynchronizedLyricsFrame::SynchedTextList &t) +{ + d->synchedText = t; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void SynchronizedLyricsFrame::parseFields(const ByteVector &data) +{ + const int end = data.size(); + if(end < 7) { + debug("A synchronized lyrics frame must contain at least 7 bytes."); + return; + } + + d->textEncoding = String::Type(data[0]); + d->language = data.mid(1, 3); + d->timestampFormat = TimestampFormat(data[4]); + d->type = Type(data[5]); + + int pos = 6; + + d->description = readStringField(data, d->textEncoding, &pos); + if(pos == 6) + return; + + /* + * If UTF16 strings are found in SYLT frames, a BOM may only be + * present in the first string (content descriptor), and the strings of + * the synchronized text have no BOM. Here the BOM is read from + * the first string to have a specific encoding with endianness for the + * case of strings without BOM so that readStringField() will work. + */ + String::Type encWithEndianness = d->textEncoding; + if(d->textEncoding == String::UTF16) { + unsigned short bom = data.toUShort(6, true); + if(bom == 0xfffe) { + encWithEndianness = String::UTF16LE; + } else if(bom == 0xfeff) { + encWithEndianness = String::UTF16BE; + } + } + + d->synchedText.clear(); + while(pos < end) { + String::Type enc = d->textEncoding; + // If a UTF16 string has no BOM, use the encoding found above. + if(enc == String::UTF16 && pos + 1 < end) { + unsigned short bom = data.toUShort(pos, true); + if(bom != 0xfffe && bom != 0xfeff) { + enc = encWithEndianness; + } + } + String text = readStringField(data, enc, &pos); + if(pos + 4 > end) + return; + + unsigned int time = data.toUInt(pos, true); + pos += 4; + + d->synchedText.append(SynchedText(time, text)); + } +} + +ByteVector SynchronizedLyricsFrame::renderFields() const +{ + ByteVector v; + + String::Type encoding = d->textEncoding; + + encoding = checkTextEncoding(d->description, encoding); + for(SynchedTextList::ConstIterator it = d->synchedText.begin(); + it != d->synchedText.end(); + ++it) { + encoding = checkTextEncoding(it->text, encoding); + } + + v.append(char(encoding)); + v.append(d->language.size() == 3 ? d->language : "XXX"); + v.append(char(d->timestampFormat)); + v.append(char(d->type)); + v.append(d->description.data(encoding)); + v.append(textDelimiter(encoding)); + for(SynchedTextList::ConstIterator it = d->synchedText.begin(); + it != d->synchedText.end(); + ++it) { + const SynchedText &entry = *it; + v.append(entry.text.data(encoding)); + v.append(textDelimiter(encoding)); + v.append(ByteVector::fromUInt(entry.time)); + } + + return v; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +SynchronizedLyricsFrame::SynchronizedLyricsFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new SynchronizedLyricsFramePrivate()) +{ + parseFields(fieldData(data)); +} diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h new file mode 100644 index 000000000..f520c5938 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/synchronizedlyricsframe.h @@ -0,0 +1,231 @@ +/*************************************************************************** + copyright : (C) 2014 by Urs Fleisch + email : ufleisch@users.sourceforge.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_SYNCHRONIZEDLYRICSFRAME_H +#define TAGLIB_SYNCHRONIZEDLYRICSFRAME_H + +#include "id3v2frame.h" +#include "tlist.h" + +namespace TagLib { + + namespace ID3v2 { + + //! ID3v2 synchronized lyrics frame + /*! + * An implementation of ID3v2 synchronized lyrics. + */ + class TAGLIB_EXPORT SynchronizedLyricsFrame : public Frame + { + friend class FrameFactory; + + public: + + /*! + * Specifies the timestamp format used. + */ + enum TimestampFormat { + //! The timestamp is of unknown format. + Unknown = 0x00, + //! The timestamp represents the number of MPEG frames since + //! the beginning of the audio stream. + AbsoluteMpegFrames = 0x01, + //! The timestamp represents the number of milliseconds since + //! the beginning of the audio stream. + AbsoluteMilliseconds = 0x02 + }; + + /*! + * Specifies the type of text contained. + */ + enum Type { + //! The text is some other type of text. + Other = 0x00, + //! The text contains lyrical data. + Lyrics = 0x01, + //! The text contains a transcription. + TextTranscription = 0x02, + //! The text lists the movements in the piece. + Movement = 0x03, + //! The text describes events that occur. + Events = 0x04, + //! The text contains chord changes that occur in the music. + Chord = 0x05, + //! The text contains trivia or "pop up" information about the media. + Trivia = 0x06, + //! The text contains URLs for relevant webpages. + WebpageUrls = 0x07, + //! The text contains URLs for relevant images. + ImageUrls = 0x08 + }; + + /*! + * Single entry of time stamp and lyrics text. + */ + struct SynchedText { + SynchedText(unsigned int ms, String str) : time(ms), text(str) {} + unsigned int time; + String text; + }; + + /*! + * List of synchronized lyrics. + */ + typedef TagLib::List<SynchedText> SynchedTextList; + + /*! + * Construct an empty synchronized lyrics frame that will use the text + * encoding \a encoding. + */ + explicit SynchronizedLyricsFrame(String::Type encoding = String::Latin1); + + /*! + * Construct a synchronized lyrics frame based on the data in \a data. + */ + explicit SynchronizedLyricsFrame(const ByteVector &data); + + /*! + * Destroys this SynchronizedLyricsFrame instance. + */ + virtual ~SynchronizedLyricsFrame(); + + /*! + * Returns the description of this synchronized lyrics frame. + * + * \see description() + */ + virtual String toString() const; + + /*! + * Returns the text encoding that will be used in rendering this frame. + * This defaults to the type that was either specified in the constructor + * or read from the frame when parsed. + * + * \see setTextEncoding() + * \see render() + */ + String::Type textEncoding() const; + + /*! + * Returns the language encoding as a 3 byte encoding as specified by + * <a href="http://en.wikipedia.org/wiki/ISO_639">ISO-639-2</a>. + * + * \note Most taggers simply ignore this value. + * + * \see setLanguage() + */ + ByteVector language() const; + + /*! + * Returns the timestamp format. + */ + TimestampFormat timestampFormat() const; + + /*! + * Returns the type of text contained. + */ + Type type() const; + + /*! + * Returns the description of this synchronized lyrics frame. + * + * \note Most taggers simply ignore this value. + * + * \see setDescription() + */ + String description() const; + + /*! + * Returns the text with the time stamps. + */ + SynchedTextList synchedText() const; + + /*! + * Sets the text encoding to be used when rendering this frame to + * \a encoding. + * + * \see textEncoding() + * \see render() + */ + void setTextEncoding(String::Type encoding); + + /*! + * Set the language using the 3 byte language code from + * <a href="http://en.wikipedia.org/wiki/ISO_639">ISO-639-2</a> to + * \a languageCode. + * + * \see language() + */ + void setLanguage(const ByteVector &languageCode); + + /*! + * Set the timestamp format. + * + * \see timestampFormat() + */ + void setTimestampFormat(TimestampFormat f); + + /*! + * Set the type of text contained. + * + * \see type() + */ + void setType(Type t); + + /*! + * Sets the description of the synchronized lyrics frame to \a s. + * + * \see description() + */ + void setDescription(const String &s); + + /*! + * Sets the text with the time stamps. + * + * \see text() + */ + void setSynchedText(const SynchedTextList &t); + + protected: + // Reimplementations. + + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + /*! + * The constructor used by the FrameFactory. + */ + SynchronizedLyricsFrame(const ByteVector &data, Header *h); + SynchronizedLyricsFrame(const SynchronizedLyricsFrame &); + SynchronizedLyricsFrame &operator=(const SynchronizedLyricsFrame &); + + class SynchronizedLyricsFramePrivate; + SynchronizedLyricsFramePrivate *d; + }; + + } +} +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp new file mode 100644 index 000000000..36016c71e --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -0,0 +1,360 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tbytevectorlist.h> +#include <tpropertymap.h> +#include <tdebug.h> + +#include "tableofcontentsframe.h" + +using namespace TagLib; +using namespace ID3v2; + +class TableOfContentsFrame::TableOfContentsFramePrivate +{ +public: + TableOfContentsFramePrivate() : + tagHeader(0), + isTopLevel(false), + isOrdered(false) + { + embeddedFrameList.setAutoDelete(true); + } + + const ID3v2::Header *tagHeader; + ByteVector elementID; + bool isTopLevel; + bool isOrdered; + ByteVectorList childElements; + FrameListMap embeddedFrameListMap; + FrameList embeddedFrameList; +}; + +namespace { + + // These functions are needed to try to aim for backward compatibility with + // an API that previously (unreasonably) required null bytes to be appended + // at the end of identifiers explicitly by the API user. + + // BIC: remove these + + ByteVector &strip(ByteVector &b) + { + if(b.endsWith('\0')) + b.resize(b.size() - 1); + return b; + } + + ByteVectorList &strip(ByteVectorList &l) + { + for(ByteVectorList::Iterator it = l.begin(); it != l.end(); ++it) + { + strip(*it); + } + return l; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data) : + ID3v2::Frame(data), + d(new TableOfContentsFramePrivate()) +{ + d->tagHeader = tagHeader; + setData(data); +} + +TableOfContentsFrame::TableOfContentsFrame(const ByteVector &elementID, + const ByteVectorList &children, + const FrameList &embeddedFrames) : + ID3v2::Frame("CTOC"), + d(new TableOfContentsFramePrivate()) +{ + d->elementID = elementID; + strip(d->elementID); + d->childElements = children; + + for(FrameList::ConstIterator it = embeddedFrames.begin(); it != embeddedFrames.end(); ++it) + addEmbeddedFrame(*it); +} + +TableOfContentsFrame::~TableOfContentsFrame() +{ + delete d; +} + +ByteVector TableOfContentsFrame::elementID() const +{ + return d->elementID; +} + +bool TableOfContentsFrame::isTopLevel() const +{ + return d->isTopLevel; +} + +bool TableOfContentsFrame::isOrdered() const +{ + return d->isOrdered; +} + +unsigned int TableOfContentsFrame::entryCount() const +{ + return d->childElements.size(); +} + +ByteVectorList TableOfContentsFrame::childElements() const +{ + return d->childElements; +} + +void TableOfContentsFrame::setElementID(const ByteVector &eID) +{ + d->elementID = eID; + strip(d->elementID); +} + +void TableOfContentsFrame::setIsTopLevel(const bool &t) +{ + d->isTopLevel = t; +} + +void TableOfContentsFrame::setIsOrdered(const bool &o) +{ + d->isOrdered = o; +} + +void TableOfContentsFrame::setChildElements(const ByteVectorList &l) +{ + d->childElements = l; + strip(d->childElements); +} + +void TableOfContentsFrame::addChildElement(const ByteVector &cE) +{ + d->childElements.append(cE); + strip(d->childElements); +} + +void TableOfContentsFrame::removeChildElement(const ByteVector &cE) +{ + ByteVectorList::Iterator it = d->childElements.find(cE); + + if(it == d->childElements.end()) + it = d->childElements.find(cE + ByteVector("\0")); + + if(it != d->childElements.end()) + d->childElements.erase(it); +} + +const FrameListMap &TableOfContentsFrame::embeddedFrameListMap() const +{ + return d->embeddedFrameListMap; +} + +const FrameList &TableOfContentsFrame::embeddedFrameList() const +{ + return d->embeddedFrameList; +} + +const FrameList &TableOfContentsFrame::embeddedFrameList(const ByteVector &frameID) const +{ + return d->embeddedFrameListMap[frameID]; +} + +void TableOfContentsFrame::addEmbeddedFrame(Frame *frame) +{ + d->embeddedFrameList.append(frame); + d->embeddedFrameListMap[frame->frameID()].append(frame); +} + +void TableOfContentsFrame::removeEmbeddedFrame(Frame *frame, bool del) +{ + // remove the frame from the frame list + FrameList::Iterator it = d->embeddedFrameList.find(frame); + if(it != d->embeddedFrameList.end()) + d->embeddedFrameList.erase(it); + + // ...and from the frame list map + FrameList &mappedList = d->embeddedFrameListMap[frame->frameID()]; + it = mappedList.find(frame); + if(it != mappedList.end()) + mappedList.erase(it); + + // ...and delete as desired + if(del) + delete frame; +} + +void TableOfContentsFrame::removeEmbeddedFrames(const ByteVector &id) +{ + FrameList l = d->embeddedFrameListMap[id]; + for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) + removeEmbeddedFrame(*it, true); +} + +String TableOfContentsFrame::toString() const +{ + String s = String(d->elementID) + + ": top level: " + (d->isTopLevel ? "true" : "false") + + ", ordered: " + (d->isOrdered ? "true" : "false"); + + if(!d->childElements.isEmpty()) { + s+= ", chapters: [ " + String(d->childElements.toByteVector(", ")) + " ]"; + } + + if(!d->embeddedFrameList.isEmpty()) { + StringList frameIDs; + for(FrameList::ConstIterator it = d->embeddedFrameList.begin(); + it != d->embeddedFrameList.end(); ++it) + frameIDs.append((*it)->frameID()); + s += ", sub-frames: [ " + frameIDs.toString(", ") + " ]"; + } + + return s; +} + +PropertyMap TableOfContentsFrame::asProperties() const +{ + PropertyMap map; + + map.unsupportedData().append(frameID() + String("/") + d->elementID); + + return map; +} + +TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *tag, + const ByteVector &eID) // static +{ + ID3v2::FrameList tablesOfContents = tag->frameList("CTOC"); + + for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin(); + it != tablesOfContents.end(); + ++it) + { + TableOfContentsFrame *frame = dynamic_cast<TableOfContentsFrame *>(*it); + if(frame && frame->elementID() == eID) + return frame; + } + + return 0; +} + +TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const ID3v2::Tag *tag) // static +{ + ID3v2::FrameList tablesOfContents = tag->frameList("CTOC"); + + for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin(); + it != tablesOfContents.end(); + ++it) + { + TableOfContentsFrame *frame = dynamic_cast<TableOfContentsFrame *>(*it); + if(frame && frame->isTopLevel() == true) + return frame; + } + + return 0; +} + +void TableOfContentsFrame::parseFields(const ByteVector &data) +{ + unsigned int size = data.size(); + if(size < 6) { + debug("A CTOC frame must contain at least 6 bytes (1 byte element ID terminated by " + "null, 1 byte flags, 1 byte entry count and 1 byte child element ID terminated " + "by null."); + return; + } + + int pos = 0; + unsigned int embPos = 0; + d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); + d->isTopLevel = (data.at(pos) & 2) != 0; + d->isOrdered = (data.at(pos++) & 1) != 0; + unsigned int entryCount = static_cast<unsigned char>(data.at(pos++)); + for(unsigned int i = 0; i < entryCount; i++) { + ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1); + d->childElements.append(childElementID); + } + + size -= pos; + + if(size < header()->size()) + return; + + while(embPos < size - header()->size()) { + Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); + + if(!frame) + return; + + // Checks to make sure that frame parsed correctly. + if(frame->size() <= 0) { + delete frame; + return; + } + + embPos += frame->size() + header()->size(); + addEmbeddedFrame(frame); + } +} + +ByteVector TableOfContentsFrame::renderFields() const +{ + ByteVector data; + + data.append(d->elementID); + data.append('\0'); + char flags = 0; + if(d->isTopLevel) + flags += 2; + if(d->isOrdered) + flags += 1; + data.append(flags); + data.append((char)(entryCount())); + ByteVectorList::ConstIterator it = d->childElements.begin(); + while(it != d->childElements.end()) { + data.append(*it); + data.append('\0'); + it++; + } + FrameList l = d->embeddedFrameList; + for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) + data.append((*it)->render()); + + return data; +} + +TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader, + const ByteVector &data, Header *h) : + Frame(h), + d(new TableOfContentsFramePrivate()) +{ + d->tagHeader = tagHeader; + parseFields(fieldData(data)); +} diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/tableofcontentsframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/tableofcontentsframe.h new file mode 100644 index 000000000..2e5401e8a --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/tableofcontentsframe.h @@ -0,0 +1,260 @@ +/*************************************************************************** + copyright : (C) 2013 by Lukas Krejci + email : krejclu6@fel.cvut.cz + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_TABLEOFCONTENTSFRAME +#define TAGLIB_TABLEOFCONTENTSFRAME + +#include "id3v2tag.h" +#include "id3v2frame.h" + +#include "tbytevectorlist.h" + +namespace TagLib { + + namespace ID3v2 { + + /*! + * This is an implementation of ID3v2 table of contents frames. Purpose + * of this frame is to allow a table of contents to be defined. + */ + + //! An implementation of ID3v2 table of contents frames + + class TAGLIB_EXPORT TableOfContentsFrame : public ID3v2::Frame + { + friend class FrameFactory; + + public: + /*! + * Creates a table of contents frame based on \a data. \a tagHeader is + * required as the internal frames are parsed based on the tag version. + */ + TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data); + + /*! + * Creates a table of contents frame with the element ID \a elementID, + * the child elements \a children and embedded frames, which become owned + * by this frame, in \a embeddedFrames. + */ + TableOfContentsFrame(const ByteVector &elementID, + const ByteVectorList &children = ByteVectorList(), + const FrameList &embeddedFrames = FrameList()); + + /*! + * Destroys the frame. + */ + ~TableOfContentsFrame(); + + /*! + * Returns the elementID of the frame. Element ID + * is a null terminated string, however it's not human-readable. + * + * \see setElementID() + */ + ByteVector elementID() const; + + /*! + * Returns true, if the frame is top-level (doesn't have + * any parent CTOC frame). + * + * \see setIsTopLevel() + */ + bool isTopLevel() const; + + /*! + * Returns true, if the child elements list entries + * are ordered. + * + * \see setIsOrdered() + */ + bool isOrdered() const; + + /*! + * Returns count of child elements of the frame. It always + * corresponds to size of child elements list. + * + * \see childElements() + */ + unsigned int entryCount() const; + + /*! + * Returns list of child elements of the frame. + * + * \see setChildElements() + */ + ByteVectorList childElements() const; + + /*! + * Sets the elementID of the frame to \a eID. If \a eID isn't + * null terminated, a null char is appended automatically. + * + * \see elementID() + */ + void setElementID(const ByteVector &eID); + + /*! + * Sets, if the frame is top-level (doesn't have + * any parent CTOC frame). + * + * \see isTopLevel() + */ + void setIsTopLevel(const bool &t); + + /*! + * Sets, if the child elements list entries + * are ordered. + * + * \see isOrdered() + */ + void setIsOrdered(const bool &o); + + /*! + * Sets list of child elements of the frame to \a l. + * + * \see childElements() + */ + void setChildElements(const ByteVectorList &l); + + /*! + * Adds \a cE to list of child elements of the frame. + * + * \see childElements() + */ + void addChildElement(const ByteVector &cE); + + /*! + * Removes \a cE to list of child elements of the frame. + * + * \see childElements() + */ + void removeChildElement(const ByteVector &cE); + + /*! + * Returns a reference to the frame list map. This is an FrameListMap of + * all of the frames embedded in the CTOC frame. + * + * This is the most convenient structure for accessing the CTOC frame's + * embedded frames. Many frame types allow multiple instances of the same + * frame type so this is a map of lists. In most cases however there will + * only be a single frame of a certain type. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + * + * \see embeddedFrameList() + */ + const FrameListMap &embeddedFrameListMap() const; + + /*! + * Returns a reference to the embedded frame list. This is an FrameList + * of all of the frames embedded in the CTOC frame in the order that they + * were parsed. + * + * This can be useful if for example you want iterate over the CTOC frame's + * embedded frames in the order that they occur in the CTOC frame. + * + * \warning You should not modify this data structure directly, instead + * use addEmbeddedFrame() and removeEmbeddedFrame(). + */ + const FrameList &embeddedFrameList() const; + + /*! + * Returns the embedded frame list for frames with the id \a frameID + * or an empty list if there are no embedded frames of that type. This + * is just a convenience and is equivalent to: + * + * \code + * embeddedFrameListMap()[frameID]; + * \endcode + * + * \see embeddedFrameListMap() + */ + const FrameList &embeddedFrameList(const ByteVector &frameID) const; + + /*! + * Add an embedded frame to the CTOC frame. At this point the CTOC frame + * takes ownership of the embedded frame and will handle freeing its memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void addEmbeddedFrame(Frame *frame); + + /*! + * Remove an embedded frame from the CTOC frame. If \a del is true the frame's + * memory will be freed; if it is false, it must be deleted by the user. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrame(Frame *frame, bool del = true); + + /*! + * Remove all embedded frames of type \a id from the CTOC frame and free their + * memory. + * + * \note Using this method will invalidate any pointers on the list + * returned by embeddedFrameList() + */ + void removeEmbeddedFrames(const ByteVector &id); + + virtual String toString() const; + + PropertyMap asProperties() const; + + /*! + * CTOC frames each have a unique element ID. This searches for a CTOC + * frame with the element ID \a eID and returns a pointer to it. This + * can be used to link together parent and child CTOC frames. + * + * \see elementID() + */ + static TableOfContentsFrame *findByElementID(const Tag *tag, const ByteVector &eID); + + /*! + * CTOC frames each contain a flag that indicates, if CTOC frame is top-level (there isn't + * any frame, which contains this frame in its child elements list). Only a single frame + * within tag can be top-level. This searches for a top-level CTOC frame. + * + * \see isTopLevel() + */ + static TableOfContentsFrame *findTopLevel(const Tag *tag); + + protected: + virtual void parseFields(const ByteVector &data); + virtual ByteVector renderFields() const; + + private: + TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data, Header *h); + TableOfContentsFrame(const TableOfContentsFrame &); + TableOfContentsFrame &operator=(const TableOfContentsFrame &); + + class TableOfContentsFramePrivate; + TableOfContentsFramePrivate *d; + }; + } +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index e1c1bf43d..39019e611 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -25,8 +25,9 @@ #include <tbytevectorlist.h> #include <id3v2tag.h> - #include "textidentificationframe.h" +#include "tpropertymap.h" +#include "id3v1genres.h" using namespace TagLib; using namespace ID3v2; @@ -44,19 +45,48 @@ public: //////////////////////////////////////////////////////////////////////////////// TextIdentificationFrame::TextIdentificationFrame(const ByteVector &type, String::Type encoding) : - Frame(type) + Frame(type), + d(new TextIdentificationFramePrivate()) { - d = new TextIdentificationFramePrivate; d->textEncoding = encoding; } TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data) : - Frame(data) + Frame(data), + d(new TextIdentificationFramePrivate()) { - d = new TextIdentificationFramePrivate; setData(data); } +TextIdentificationFrame *TextIdentificationFrame::createTIPLFrame(const PropertyMap &properties) // static +{ + TextIdentificationFrame *frame = new TextIdentificationFrame("TIPL"); + StringList l; + for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ + const String role = involvedPeopleMap()[it->first]; + if(role.isEmpty()) // should not happen + continue; + l.append(role); + l.append(it->second.toString(",")); // comma-separated list of names + } + frame->setText(l); + return frame; +} + +TextIdentificationFrame *TextIdentificationFrame::createTMCLFrame(const PropertyMap &properties) // static +{ + TextIdentificationFrame *frame = new TextIdentificationFrame("TMCL"); + StringList l; + for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ + if(!it->first.startsWith(instrumentPrefix)) // should not happen + continue; + l.append(it->first.substr(instrumentPrefix.size())); + l.append(it->second.toString(",")); + } + frame->setText(l); + return frame; +} + TextIdentificationFrame::~TextIdentificationFrame() { delete d; @@ -92,6 +122,65 @@ void TextIdentificationFrame::setTextEncoding(String::Type encoding) d->textEncoding = encoding; } +namespace +{ + // array of allowed TIPL prefixes and their corresponding key value + const char* involvedPeople[][2] = { + {"ARRANGER", "ARRANGER"}, + {"ENGINEER", "ENGINEER"}, + {"PRODUCER", "PRODUCER"}, + {"DJ-MIX", "DJMIXER"}, + {"MIX", "MIXER"}, + }; + const size_t involvedPeopleSize = sizeof(involvedPeople) / sizeof(involvedPeople[0]); +} + +const KeyConversionMap &TextIdentificationFrame::involvedPeopleMap() // static +{ + static KeyConversionMap m; + if(m.isEmpty()) { + for(size_t i = 0; i < involvedPeopleSize; ++i) + m.insert(involvedPeople[i][1], involvedPeople[i][0]); + } + return m; +} + +PropertyMap TextIdentificationFrame::asProperties() const +{ + if(frameID() == "TIPL") + return makeTIPLProperties(); + if(frameID() == "TMCL") + return makeTMCLProperties(); + PropertyMap map; + String tagName = frameIDToKey(frameID()); + if(tagName.isEmpty()) { + map.unsupportedData().append(frameID()); + return map; + } + StringList values = fieldList(); + if(tagName == "GENRE") { + // Special case: Support ID3v1-style genre numbers. They are not officially supported in + // ID3v2, however it seems that still a lot of programs use them. + for(StringList::Iterator it = values.begin(); it != values.end(); ++it) { + bool ok = false; + int test = it->toInt(&ok); // test if the genre value is an integer + if(ok) + *it = ID3v1::genre(test); + } + } else if(tagName == "DATE") { + for(StringList::Iterator it = values.begin(); it != values.end(); ++it) { + // ID3v2 specifies ISO8601 timestamps which contain a 'T' as separator between date and time. + // Since this is unusual in other formats, the T is removed. + int tpos = it->find("T"); + if(tpos != -1) + (*it)[tpos] = ' '; + } + } + PropertyMap ret; + ret.insert(tagName, values); + return ret; +} + //////////////////////////////////////////////////////////////////////////////// // TextIdentificationFrame protected members //////////////////////////////////////////////////////////////////////////////// @@ -105,7 +194,7 @@ void TextIdentificationFrame::parseFields(const ByteVector &data) // read the string data type (the first byte of the field data) - d->textEncoding = String::Type(data[0]&3); + d->textEncoding = String::Type(data[0]); // split the byte array into chunks based on the string type (two byte delimiter // for unicode encodings) @@ -129,17 +218,19 @@ void TextIdentificationFrame::parseFields(const ByteVector &data) // append those split values to the list and make sure that the new string's // type is the same specified for this frame - for(ByteVectorList::Iterator it = l.begin(); it != l.end(); it++) { + for(ByteVectorList::ConstIterator it = l.begin(); it != l.end(); it++) { if(!(*it).isEmpty()) { - String s(*it, d->textEncoding); - d->fieldList.append(s); + if(d->textEncoding == String::Latin1) + d->fieldList.append(Tag::latin1StringHandler()->parse(*it)); + else + d->fieldList.append(String(*it, d->textEncoding)); } } } ByteVector TextIdentificationFrame::renderFields() const { - String::Type encoding = checkEncoding(d->fieldList, d->textEncoding); + String::Type encoding = checkTextEncoding(d->fieldList, d->textEncoding); ByteVector v; @@ -164,12 +255,62 @@ ByteVector TextIdentificationFrame::renderFields() const // TextIdentificationFrame private members //////////////////////////////////////////////////////////////////////////////// -TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data, Header *h) : Frame(h) +TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new TextIdentificationFramePrivate()) { - d = new TextIdentificationFramePrivate; parseFields(fieldData(data)); } +PropertyMap TextIdentificationFrame::makeTIPLProperties() const +{ + PropertyMap map; + if(fieldList().size() % 2 != 0){ + // according to the ID3 spec, TIPL must contain an even number of entries + map.unsupportedData().append(frameID()); + return map; + } + StringList l = fieldList(); + for(StringList::ConstIterator it = l.begin(); it != l.end(); ++it) { + bool found = false; + for(size_t i = 0; i < involvedPeopleSize; ++i) + if(*it == involvedPeople[i][0]) { + map.insert(involvedPeople[i][1], (++it)->split(",")); + found = true; + break; + } + if(!found){ + // invalid involved role -> mark whole frame as unsupported in order to be consistent with writing + map.clear(); + map.unsupportedData().append(frameID()); + return map; + } + } + return map; +} + +PropertyMap TextIdentificationFrame::makeTMCLProperties() const +{ + PropertyMap map; + if(fieldList().size() % 2 != 0){ + // according to the ID3 spec, TMCL must contain an even number of entries + map.unsupportedData().append(frameID()); + return map; + } + StringList l = fieldList(); + for(StringList::ConstIterator it = l.begin(); it != l.end(); ++it) { + String instrument = it->upper(); + if(instrument.isEmpty()) { + // instrument is not a valid key -> frame unsupported + map.clear(); + map.unsupportedData().append(frameID()); + return map; + } + map.insert(L"PERFORMER:" + instrument, (++it)->split(",")); + } + return map; +} + //////////////////////////////////////////////////////////////////////////////// // UserTextIdentificationFrame public members //////////////////////////////////////////////////////////////////////////////// @@ -179,8 +320,8 @@ UserTextIdentificationFrame::UserTextIdentificationFrame(String::Type encoding) d(0) { StringList l; - l.append(String::null); - l.append(String::null); + l.append(String()); + l.append(String()); setText(l); } @@ -191,16 +332,30 @@ UserTextIdentificationFrame::UserTextIdentificationFrame(const ByteVector &data) checkFields(); } +UserTextIdentificationFrame::UserTextIdentificationFrame(const String &description, const StringList &values, String::Type encoding) : + TextIdentificationFrame("TXXX", encoding), + d(0) +{ + setDescription(description); + setText(values); +} + String UserTextIdentificationFrame::toString() const { - return "[" + description() + "] " + fieldList().toString(); + // first entry is the description itself, drop from values list + StringList l = fieldList(); + for(StringList::Iterator it = l.begin(); it != l.end(); ++it) { + l.erase(it); + break; + } + return "[" + description() + "] " + l.toString(); } String UserTextIdentificationFrame::description() const { return !TextIdentificationFrame::fieldList().isEmpty() ? TextIdentificationFrame::fieldList().front() - : String::null; + : String(); } StringList UserTextIdentificationFrame::fieldList() const @@ -213,7 +368,7 @@ StringList UserTextIdentificationFrame::fieldList() const void UserTextIdentificationFrame::setText(const String &text) { if(description().isEmpty()) - setDescription(String::null); + setDescription(String()); TextIdentificationFrame::setText(StringList(description()).append(text)); } @@ -221,7 +376,7 @@ void UserTextIdentificationFrame::setText(const String &text) void UserTextIdentificationFrame::setText(const StringList &fields) { if(description().isEmpty()) - setDescription(String::null); + setDescription(String()); TextIdentificationFrame::setText(StringList(description()).append(fields)); } @@ -238,11 +393,22 @@ void UserTextIdentificationFrame::setDescription(const String &s) TextIdentificationFrame::setText(l); } +PropertyMap UserTextIdentificationFrame::asProperties() const +{ + PropertyMap map; + String tagName = txxxToKey(description()); + StringList v = fieldList(); + for(StringList::ConstIterator it = v.begin(); it != v.end(); ++it) + if(it != v.begin()) + map.insert(tagName, *it); + return map; +} + UserTextIdentificationFrame *UserTextIdentificationFrame::find( ID3v2::Tag *tag, const String &description) // static { FrameList l = tag->frameList("TXXX"); - for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) { + for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) { UserTextIdentificationFrame *f = dynamic_cast<UserTextIdentificationFrame *>(*it); if(f && f->description() == description) return f; @@ -265,7 +431,7 @@ void UserTextIdentificationFrame::checkFields() int fields = fieldList().size(); if(fields == 0) - setDescription(String::null); + setDescription(String()); if(fields <= 1) - setText(String::null); + setText(String()); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/textidentificationframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/textidentificationframe.h index 418ef9704..e49aa897b 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/textidentificationframe.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/textidentificationframe.h @@ -27,6 +27,7 @@ #define TAGLIB_TEXTIDENTIFICATIONFRAME_H #include "tstringlist.h" +#include "tmap.h" #include "taglib_export.h" #include "id3v2frame.h" @@ -36,6 +37,7 @@ namespace TagLib { namespace ID3v2 { class Tag; + typedef Map<String, String> KeyConversionMap; //! An ID3v2 text identification frame implementation @@ -123,6 +125,20 @@ namespace TagLib { */ explicit TextIdentificationFrame(const ByteVector &data); + /*! + * This is a special factory method to create a TIPL (involved people list) + * frame from the given \a properties. Will parse key=[list of values] data + * into the TIPL format as specified in the ID3 standard. + */ + static TextIdentificationFrame *createTIPLFrame(const PropertyMap &properties); + + /*! + * This is a special factory method to create a TMCL (musician credits list) + * frame from the given \a properties. Will parse key=[list of values] data + * into the TMCL format as specified in the ID3 standard, where key should be + * of the form instrumentPrefix:instrument. + */ + static TextIdentificationFrame *createTMCLFrame(const PropertyMap &properties); /*! * Destroys this TextIdentificationFrame instance. */ @@ -173,6 +189,14 @@ namespace TagLib { */ StringList fieldList() const; + /*! + * Returns a KeyConversionMap mapping a role as it would be used in a PropertyMap + * to the corresponding key used in a TIPL ID3 frame to describe that role. + */ + static const KeyConversionMap &involvedPeopleMap(); + + PropertyMap asProperties() const; + protected: // Reimplementations. @@ -188,6 +212,16 @@ namespace TagLib { TextIdentificationFrame(const TextIdentificationFrame &); TextIdentificationFrame &operator=(const TextIdentificationFrame &); + /*! + * Parses the special structure of a TIPL frame + * Only the whitelisted roles "ARRANGER", "ENGINEER", "PRODUCER", + * "DJMIXER" (ID3: "DJ-MIX") and "MIXER" (ID3: "MIX") are allowed. + */ + PropertyMap makeTIPLProperties() const; + /*! + * Parses the special structure of a TMCL frame. + */ + PropertyMap makeTMCLProperties() const; class TextIdentificationFramePrivate; TextIdentificationFramePrivate *d; }; @@ -200,7 +234,7 @@ namespace TagLib { * This description identifies the frame and must be unique. */ - //! An ID3v2 custom text identification frame implementationx + //! An ID3v2 custom text identification frame implementation class TAGLIB_EXPORT UserTextIdentificationFrame : public TextIdentificationFrame { @@ -218,6 +252,12 @@ namespace TagLib { */ explicit UserTextIdentificationFrame(const ByteVector &data); + /*! + * Creates a user defined text identification frame with the given \a description + * and \a values. + */ + UserTextIdentificationFrame(const String &description, const StringList &values, String::Type encoding = String::UTF8); + virtual String toString() const; /*! @@ -236,6 +276,21 @@ namespace TagLib { void setText(const String &text); void setText(const StringList &fields); + /*! + * A UserTextIdentificationFrame is parsed into a PropertyMap as follows: + * - the key is the frame's description, uppercased + * - if the description contains '::', only the substring after that + * separator is considered as key (compatibility with exfalso) + * - if the above rules don't yield a valid key (e.g. containing non-ASCII + * characters), the returned map will contain an entry "TXXX/<description>" + * in its unsupportedData() list. + * - The values will be copies of the fieldList(). + * - If the description() appears as value in fieldList(), it will be omitted + * in the value list, in order to be compatible with TagLib which copies + * the description() into the fieldList(). + */ + PropertyMap asProperties() const; + /*! * Searches for the user defined text frame with the description \a description * in \a tag. This returns null if no matching frames were found. diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp index e12583ade..fcb855b2e 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp @@ -24,8 +24,10 @@ ***************************************************************************/ #include <tbytevectorlist.h> +#include <tpropertymap.h> #include <tdebug.h> +#include "id3v2tag.h" #include "uniquefileidentifierframe.h" using namespace TagLib; @@ -43,16 +45,16 @@ public: //////////////////////////////////////////////////////////////////////////////// UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data) : - ID3v2::Frame(data) + ID3v2::Frame(data), + d(new UniqueFileIdentifierFramePrivate()) { - d = new UniqueFileIdentifierFramePrivate; setData(data); } UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const String &owner, const ByteVector &id) : - ID3v2::Frame("UFID") + ID3v2::Frame("UFID"), + d(new UniqueFileIdentifierFramePrivate()) { - d = new UniqueFileIdentifierFramePrivate; d->owner = owner; d->identifier = id; } @@ -84,7 +86,35 @@ void UniqueFileIdentifierFrame::setIdentifier(const ByteVector &v) String UniqueFileIdentifierFrame::toString() const { - return String::null; + return String(); +} + +PropertyMap UniqueFileIdentifierFrame::asProperties() const +{ + PropertyMap map; + if(d->owner == "http://musicbrainz.org") { + map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); + } + else { + map.unsupportedData().append(frameID() + String("/") + d->owner); + } + return map; +} + +UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +{ + ID3v2::FrameList comments = tag->frameList("UFID"); + + for(ID3v2::FrameList::ConstIterator it = comments.begin(); + it != comments.end(); + ++it) + { + UniqueFileIdentifierFrame *frame = dynamic_cast<UniqueFileIdentifierFrame *>(*it); + if(frame && frame->owner() == o) + return frame; + } + + return 0; } void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) @@ -111,8 +141,8 @@ ByteVector UniqueFileIdentifierFrame::renderFields() const } UniqueFileIdentifierFrame::UniqueFileIdentifierFrame(const ByteVector &data, Header *h) : - Frame(h) + Frame(h), + d(new UniqueFileIdentifierFramePrivate()) { - d = new UniqueFileIdentifierFramePrivate; parseFields(fieldData(data)); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h index 1292b3929..decf1b0d9 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h @@ -46,7 +46,7 @@ namespace TagLib { public: /*! - * Creates a uniqe file identifier frame based on \a data. + * Creates a unique file identifier frame based on \a data. */ UniqueFileIdentifierFrame(const ByteVector &data); @@ -94,13 +94,23 @@ namespace TagLib { virtual String toString() const; + PropertyMap asProperties() const; + + /*! + * UFID frames each have a unique owner. This searches for a UFID + * frame with the owner \a o and returns a pointer to it. + * + * \see owner() + */ + static UniqueFileIdentifierFrame *findByOwner(const Tag *tag, const String &o); + protected: virtual void parseFields(const ByteVector &data); virtual ByteVector renderFields() const; private: UniqueFileIdentifierFrame(const UniqueFileIdentifierFrame &); - UniqueFileIdentifierFrame &operator=(UniqueFileIdentifierFrame &); + UniqueFileIdentifierFrame &operator=(const UniqueFileIdentifierFrame &); UniqueFileIdentifierFrame(const ByteVector &data, Header *h); diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unknownframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unknownframe.cpp index 4def028ba..edb30084a 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unknownframe.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unknownframe.cpp @@ -38,9 +38,10 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -UnknownFrame::UnknownFrame(const ByteVector &data) : Frame(data) +UnknownFrame::UnknownFrame(const ByteVector &data) : + Frame(data), + d(new UnknownFramePrivate()) { - d = new UnknownFramePrivate; setData(data); } @@ -51,7 +52,7 @@ UnknownFrame::~UnknownFrame() String UnknownFrame::toString() const { - return String::null; + return String(); } ByteVector UnknownFrame::data() const @@ -77,8 +78,9 @@ ByteVector UnknownFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -UnknownFrame::UnknownFrame(const ByteVector &data, Header *h) : Frame(h) +UnknownFrame::UnknownFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new UnknownFramePrivate()) { - d = new UnknownFramePrivate; parseFields(fieldData(data)); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp index 0a8927e7f..eafae171c 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp @@ -1,6 +1,7 @@ /*************************************************************************** copyright : (C) 2002 - 2008 by Scott Wheeler email : wheeler@kde.org + copyright : (C) 2006 by Urs Fleisch email : ufleisch@users.sourceforge.net ***************************************************************************/ @@ -27,7 +28,9 @@ #include "unsynchronizedlyricsframe.h" #include <tbytevectorlist.h> +#include <id3v2tag.h> #include <tdebug.h> +#include <tpropertymap.h> using namespace TagLib; using namespace ID3v2; @@ -47,16 +50,16 @@ public: //////////////////////////////////////////////////////////////////////////////// UnsynchronizedLyricsFrame::UnsynchronizedLyricsFrame(String::Type encoding) : - Frame("USLT") + Frame("USLT"), + d(new UnsynchronizedLyricsFramePrivate()) { - d = new UnsynchronizedLyricsFramePrivate; d->textEncoding = encoding; } UnsynchronizedLyricsFrame::UnsynchronizedLyricsFrame(const ByteVector &data) : - Frame(data) + Frame(data), + d(new UnsynchronizedLyricsFramePrivate()) { - d = new UnsynchronizedLyricsFramePrivate; setData(data); } @@ -111,6 +114,28 @@ void UnsynchronizedLyricsFrame::setTextEncoding(String::Type encoding) d->textEncoding = encoding; } +PropertyMap UnsynchronizedLyricsFrame::asProperties() const +{ + PropertyMap map; + String key = description().upper(); + if(key.isEmpty() || key == "LYRICS") + map.insert("LYRICS", text()); + else + map.insert("LYRICS:" + key, text()); + return map; +} + +UnsynchronizedLyricsFrame *UnsynchronizedLyricsFrame::findByDescription(const ID3v2::Tag *tag, const String &d) // static +{ + ID3v2::FrameList lyrics = tag->frameList("USLT"); + + for(ID3v2::FrameList::ConstIterator it = lyrics.begin(); it != lyrics.end(); ++it){ + UnsynchronizedLyricsFrame *frame = dynamic_cast<UnsynchronizedLyricsFrame *>(*it); + if(frame && frame->description() == d) + return frame; + } + return 0; +} //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// @@ -132,20 +157,31 @@ void UnsynchronizedLyricsFrame::parseFields(const ByteVector &data) ByteVectorList::split(data.mid(4), textDelimiter(d->textEncoding), byteAlign, 2); if(l.size() == 2) { - d->description = String(l.front(), d->textEncoding); - d->text = String(l.back(), d->textEncoding); + if(d->textEncoding == String::Latin1) { + d->description = Tag::latin1StringHandler()->parse(l.front()); + d->text = Tag::latin1StringHandler()->parse(l.back()); + } else { + d->description = String(l.front(), d->textEncoding); + d->text = String(l.back(), d->textEncoding); + } } } ByteVector UnsynchronizedLyricsFrame::renderFields() const { + StringList sl; + sl.append(d->description); + sl.append(d->text); + + const String::Type encoding = checkTextEncoding(sl, d->textEncoding); + ByteVector v; - v.append(char(d->textEncoding)); + v.append(char(encoding)); v.append(d->language.size() == 3 ? d->language : "XXX"); - v.append(d->description.data(d->textEncoding)); - v.append(textDelimiter(d->textEncoding)); - v.append(d->text.data(d->textEncoding)); + v.append(d->description.data(encoding)); + v.append(textDelimiter(encoding)); + v.append(d->text.data(encoding)); return v; } @@ -154,9 +190,9 @@ ByteVector UnsynchronizedLyricsFrame::renderFields() const // private members //////////////////////////////////////////////////////////////////////////////// -UnsynchronizedLyricsFrame::UnsynchronizedLyricsFrame(const ByteVector &data, Header *h) - : Frame(h) +UnsynchronizedLyricsFrame::UnsynchronizedLyricsFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new UnsynchronizedLyricsFramePrivate()) { - d = new UnsynchronizedLyricsFramePrivate(); parseFields(fieldData(data)); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h index 0f8260e47..dad67c7a3 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h @@ -104,7 +104,7 @@ namespace TagLib { /*! * Sets the description of the unsynchronized lyrics frame to \a s. * - * \see decription() + * \see description() */ void setDescription(const String &s); @@ -134,6 +134,28 @@ namespace TagLib { */ void setTextEncoding(String::Type encoding); + + /*! Parses this frame as PropertyMap with a single key. + * - if description() is empty or "LYRICS", the key will be "LYRICS" + * - if description() is not a valid PropertyMap key, the frame will be + * marked unsupported by an entry "USLT/<description>" in the unsupportedData() + * attribute of the returned map. + * - otherwise, the key will be "LYRICS:<description>" + * - The single value will be the frame's text(). + * Note that currently the language() field is not supported by the PropertyMap + * interface. + */ + PropertyMap asProperties() const; + + /*! + * LyricsFrames each have a unique description. This searches for a lyrics + * frame with the description \a d and returns a pointer to it. If no + * frame is found that matches the given description null is returned. + * + * \see description() + */ + static UnsynchronizedLyricsFrame *findByDescription(const Tag *tag, const String &d); + protected: // Reimplementations. diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/urllinkframe.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/urllinkframe.cpp index 7756c4ad9..543b5184d 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/urllinkframe.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/urllinkframe.cpp @@ -1,6 +1,7 @@ /*************************************************************************** copyright : (C) 2002 - 2008 by Scott Wheeler email : wheeler@kde.org + copyright : (C) 2006 by Urs Fleisch email : ufleisch@users.sourceforge.net ***************************************************************************/ @@ -26,8 +27,10 @@ ***************************************************************************/ #include "urllinkframe.h" +#include "id3v2tag.h" #include <tdebug.h> #include <tstringlist.h> +#include <tpropertymap.h> using namespace TagLib; using namespace ID3v2; @@ -46,10 +49,14 @@ public: String description; }; +//////////////////////////////////////////////////////////////////////////////// +// UrlLinkFrame public members +//////////////////////////////////////////////////////////////////////////////// + UrlLinkFrame::UrlLinkFrame(const ByteVector &data) : - Frame(data) + Frame(data), + d(new UrlLinkFramePrivate()) { - d = new UrlLinkFramePrivate; setData(data); } @@ -78,6 +85,22 @@ String UrlLinkFrame::toString() const return url(); } +PropertyMap UrlLinkFrame::asProperties() const +{ + String key = frameIDToKey(frameID()); + PropertyMap map; + if(key.isEmpty()) + // unknown W*** frame - this normally shouldn't happen + map.unsupportedData().append(frameID()); + else + map.insert(key, url()); + return map; +} + +//////////////////////////////////////////////////////////////////////////////// +// UrlLinkFrame protected members +//////////////////////////////////////////////////////////////////////////////// + void UrlLinkFrame::parseFields(const ByteVector &data) { d->url = String(data); @@ -88,24 +111,28 @@ ByteVector UrlLinkFrame::renderFields() const return d->url.data(String::Latin1); } -UrlLinkFrame::UrlLinkFrame(const ByteVector &data, Header *h) : Frame(h) +UrlLinkFrame::UrlLinkFrame(const ByteVector &data, Header *h) : + Frame(h), + d(new UrlLinkFramePrivate()) { - d = new UrlLinkFramePrivate; parseFields(fieldData(data)); } +//////////////////////////////////////////////////////////////////////////////// +// UserUrlLinkFrame public members +//////////////////////////////////////////////////////////////////////////////// UserUrlLinkFrame::UserUrlLinkFrame(String::Type encoding) : - UrlLinkFrame("WXXX") + UrlLinkFrame("WXXX"), + d(new UserUrlLinkFramePrivate()) { - d = new UserUrlLinkFramePrivate; d->textEncoding = encoding; } UserUrlLinkFrame::UserUrlLinkFrame(const ByteVector &data) : - UrlLinkFrame(data) + UrlLinkFrame(data), + d(new UserUrlLinkFramePrivate()) { - d = new UserUrlLinkFramePrivate; setData(data); } @@ -139,6 +166,32 @@ void UserUrlLinkFrame::setDescription(const String &s) d->description = s; } +PropertyMap UserUrlLinkFrame::asProperties() const +{ + PropertyMap map; + String key = description().upper(); + if(key.isEmpty() || key == "URL") + map.insert("URL", url()); + else + map.insert("URL:" + key, url()); + return map; +} + +UserUrlLinkFrame *UserUrlLinkFrame::find(ID3v2::Tag *tag, const String &description) // static +{ + FrameList l = tag->frameList("WXXX"); + for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) { + UserUrlLinkFrame *f = dynamic_cast<UserUrlLinkFrame *>(*it); + if(f && f->description() == description) + return f; + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// UserUrlLinkFrame protected members +//////////////////////////////////////////////////////////////////////////////// + void UserUrlLinkFrame::parseFields(const ByteVector &data) { if(data.size() < 2) { @@ -175,7 +228,7 @@ ByteVector UserUrlLinkFrame::renderFields() const { ByteVector v; - String::Type encoding = checkEncoding(d->description, d->textEncoding); + String::Type encoding = checkTextEncoding(d->description, d->textEncoding); v.append(char(encoding)); v.append(d->description.data(encoding)); @@ -185,8 +238,9 @@ ByteVector UserUrlLinkFrame::renderFields() const return v; } -UserUrlLinkFrame::UserUrlLinkFrame(const ByteVector &data, Header *h) : UrlLinkFrame(data, h) +UserUrlLinkFrame::UserUrlLinkFrame(const ByteVector &data, Header *h) : + UrlLinkFrame(data, h), + d(new UserUrlLinkFramePrivate()) { - d = new UserUrlLinkFramePrivate; parseFields(fieldData(data)); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/urllinkframe.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/urllinkframe.h index f89faad0a..d9ac1093b 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/urllinkframe.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/frames/urllinkframe.h @@ -1,6 +1,7 @@ /*************************************************************************** copyright : (C) 2002 - 2008 by Scott Wheeler email : wheeler@kde.org + copyright : (C) 2006 by Urs Fleisch email : ufleisch@users.sourceforge.net ***************************************************************************/ @@ -68,6 +69,7 @@ namespace TagLib { virtual void setText(const String &s); virtual String toString() const; + PropertyMap asProperties() const; protected: virtual void parseFields(const ByteVector &data); @@ -150,6 +152,22 @@ namespace TagLib { */ void setDescription(const String &s); + /*! + * Parses the UserUrlLinkFrame as PropertyMap. The description() is taken as key, + * and the URL as single value. + * - if description() is empty, the key will be "URL". + * - otherwise, if description() is not a valid key (e.g. containing non-ASCII + * characters), the returned map will contain an entry "WXXX/<description>" + * in its unsupportedData() list. + */ + PropertyMap asProperties() const; + + /*! + * Searches for the user defined url frame with the description \a description + * in \a tag. This returns null if no matching frames were found. + */ + static UserUrlLinkFrame *find(Tag *tag, const String &description); + protected: virtual void parseFields(const ByteVector &data); virtual ByteVector renderFields() const; diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2.h new file mode 100644 index 000000000..bef82519e --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2.h @@ -0,0 +1,24 @@ +#ifndef TAGLIB_ID3V2_H +#define TAGLIB_ID3V2_H + +namespace TagLib { + //! An ID3v2 implementation + + /*! + * This is a relatively complete and flexible framework for working with ID3v2 + * tags. + * + * \see ID3v2::Tag + */ + namespace ID3v2 { + /*! + * Used to specify which version of the ID3 standard to use when saving tags. + */ + enum Version { + v3 = 3, //<! ID3v2.3 + v4 = 4 //<! ID3v2.4 + }; + } +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2extendedheader.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2extendedheader.cpp index ddcfd452f..86eeee280 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2extendedheader.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2extendedheader.cpp @@ -34,16 +34,16 @@ class ExtendedHeader::ExtendedHeaderPrivate public: ExtendedHeaderPrivate() : size(0) {} - uint size; + unsigned int size; }; //////////////////////////////////////////////////////////////////////////////// // public methods //////////////////////////////////////////////////////////////////////////////// -ExtendedHeader::ExtendedHeader() +ExtendedHeader::ExtendedHeader() : + d(new ExtendedHeaderPrivate()) { - d = new ExtendedHeaderPrivate(); } ExtendedHeader::~ExtendedHeader() @@ -51,7 +51,7 @@ ExtendedHeader::~ExtendedHeader() delete d; } -TagLib::uint ExtendedHeader::size() const +unsigned int ExtendedHeader::size() const { return d->size; } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2extendedheader.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2extendedheader.h index d7227e9d1..646623386 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2extendedheader.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2extendedheader.h @@ -38,7 +38,7 @@ namespace TagLib { /*! * This class implements ID3v2 extended headers. It attempts to follow, - * both semantically and programatically, the structure specified in + * both semantically and programmatically, the structure specified in * the ID3v2 standard. The API is based on the properties of ID3v2 extended * headers specified there. If any of the terms used in this documentation * are unclear please check the specification in the linked section. @@ -62,7 +62,7 @@ namespace TagLib { * Returns the size of the extended header. This is variable for the * extended header. */ - uint size() const; + unsigned int size() const; /*! * Sets the data that will be used as the extended header. Since the diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2footer.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2footer.cpp index defbb17ef..3622724a8 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2footer.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2footer.cpp @@ -31,30 +31,27 @@ using namespace ID3v2; class Footer::FooterPrivate { -public: - static const uint size = 10; }; -Footer::Footer() +Footer::Footer() : + d(0) { - } Footer::~Footer() { - } -TagLib::uint Footer::size() +unsigned int Footer::size() { - return FooterPrivate::size; + return 10; } ByteVector Footer::render(const Header *header) const { - ByteVector headerData = header->render(); - headerData[0] = '3'; - headerData[1] = 'D'; - headerData[2] = 'I'; - return headerData; + ByteVector headerData = header->render(); + headerData[0] = '3'; + headerData[1] = 'D'; + headerData[2] = 'I'; + return headerData; } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2footer.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2footer.h index 1374a1495..5eb380074 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2footer.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2footer.h @@ -62,7 +62,7 @@ namespace TagLib { /*! * Returns the size of the footer. Presently this is always 10 bytes. */ - static uint size(); + static unsigned int size(); /*! * Renders the footer based on the data in \a header. diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2frame.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2frame.cpp index 786336b9f..81f351efd 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2frame.cpp @@ -23,22 +23,25 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifndef HAVE_ZLIB -#include <config.h> -#endif - -#if HAVE_ZLIB -#include <zlib.h> -#endif - #include <bitset> #include <tdebug.h> #include <tstringlist.h> +#include <tzlib.h> +#include "id3v2tag.h" #include "id3v2frame.h" #include "id3v2synchdata.h" +#include "tpropertymap.h" +#include "frames/textidentificationframe.h" +#include "frames/urllinkframe.h" +#include "frames/unsynchronizedlyricsframe.h" +#include "frames/commentsframe.h" +#include "frames/uniquefileidentifierframe.h" +#include "frames/unknownframe.h" +#include "frames/podcastframe.h" + using namespace TagLib; using namespace ID3v2; @@ -65,7 +68,7 @@ namespace return false; for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) { - if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) { + if( (*it < 'A' || *it > 'Z') && (*it < '0' || *it > '9') ) { return false; } } @@ -77,28 +80,83 @@ namespace // static methods //////////////////////////////////////////////////////////////////////////////// -TagLib::uint Frame::headerSize() +unsigned int Frame::headerSize() { return Header::size(); } -TagLib::uint Frame::headerSize(uint version) +unsigned int Frame::headerSize(unsigned int version) { return Header::size(version); } ByteVector Frame::textDelimiter(String::Type t) { - ByteVector d = char(0); if(t == String::UTF16 || t == String::UTF16BE || t == String::UTF16LE) - d.append(char(0)); - return d; + return ByteVector(2, '\0'); + else + return ByteVector(1, '\0'); } +const String Frame::instrumentPrefix("PERFORMER:"); +const String Frame::commentPrefix("COMMENT:"); +const String Frame::lyricsPrefix("LYRICS:"); +const String Frame::urlPrefix("URL:"); + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// +Frame *Frame::createTextualFrame(const String &key, const StringList &values) //static +{ + // check if the key is contained in the key<=>frameID mapping + ByteVector frameID = keyToFrameID(key); + if(!frameID.isEmpty()) { + // Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number), GRP1 (Grouping) are in fact text frames. + if(frameID[0] == 'T' || frameID == "WFED" || frameID == "MVNM" || frameID == "MVIN" || frameID == "GRP1"){ // text frame + TextIdentificationFrame *frame = new TextIdentificationFrame(frameID, String::UTF8); + frame->setText(values); + return frame; + } else if((frameID[0] == 'W') && (values.size() == 1)){ // URL frame (not WXXX); support only one value + UrlLinkFrame* frame = new UrlLinkFrame(frameID); + frame->setUrl(values.front()); + return frame; + } else if(frameID == "PCST") { + return new PodcastFrame(); + } + } + if(key == "MUSICBRAINZ_TRACKID" && values.size() == 1) { + UniqueFileIdentifierFrame *frame = new UniqueFileIdentifierFrame("http://musicbrainz.org", values.front().data(String::UTF8)); + return frame; + } + // now we check if it's one of the "special" cases: + // -LYRICS: depending on the number of values, use USLT or TXXX (with description=LYRICS) + if((key == "LYRICS" || key.startsWith(lyricsPrefix)) && values.size() == 1){ + UnsynchronizedLyricsFrame *frame = new UnsynchronizedLyricsFrame(String::UTF8); + frame->setDescription(key == "LYRICS" ? key : key.substr(lyricsPrefix.size())); + frame->setText(values.front()); + return frame; + } + // -URL: depending on the number of values, use WXXX or TXXX (with description=URL) + if((key == "URL" || key.startsWith(urlPrefix)) && values.size() == 1){ + UserUrlLinkFrame *frame = new UserUrlLinkFrame(String::UTF8); + frame->setDescription(key == "URL" ? key : key.substr(urlPrefix.size())); + frame->setUrl(values.front()); + return frame; + } + // -COMMENT: depending on the number of values, use COMM or TXXX (with description=COMMENT) + if((key == "COMMENT" || key.startsWith(commentPrefix)) && values.size() == 1){ + CommentsFrame *frame = new CommentsFrame(String::UTF8); + if (key != "COMMENT"){ + frame->setDescription(key.substr(commentPrefix.size())); + } + frame->setText(values.front()); + return frame; + } + // if non of the above cases apply, we use a TXXX frame with the key as description + return new UserTextIdentificationFrame(keyToTXXX(key), values, String::UTF8); +} + Frame::~Frame() { delete d; @@ -109,10 +167,10 @@ ByteVector Frame::frameID() const if(d->header) return d->header->frameID(); else - return ByteVector::null; + return ByteVector(); } -TagLib::uint Frame::size() const +unsigned int Frame::size() const { if(d->header) return d->header->frameSize(); @@ -143,15 +201,15 @@ ByteVector Frame::render() const // protected members //////////////////////////////////////////////////////////////////////////////// -Frame::Frame(const ByteVector &data) +Frame::Frame(const ByteVector &data) : + d(new FramePrivate()) { - d = new FramePrivate; d->header = new Header(data); } -Frame::Frame(Header *h) +Frame::Frame(Header *h) : + d(new FramePrivate()) { - d = new FramePrivate; d->header = h; } @@ -180,31 +238,31 @@ void Frame::parse(const ByteVector &data) ByteVector Frame::fieldData(const ByteVector &frameData) const { - uint headerSize = Header::size(d->header->version()); + unsigned int headerSize = Header::size(d->header->version()); - uint frameDataOffset = headerSize; - uint frameDataLength = size(); + unsigned int frameDataOffset = headerSize; + unsigned int frameDataLength = size(); if(d->header->compression() || d->header->dataLengthIndicator()) { frameDataLength = SynchData::toUInt(frameData.mid(headerSize, 4)); frameDataOffset += 4; } -#if HAVE_ZLIB - if(d->header->compression() && - !d->header->encryption()) - { - ByteVector data(frameDataLength); - uLongf uLongTmp = frameDataLength; - ::uncompress((Bytef *) data.data(), - (uLongf *) &uLongTmp, - (Bytef *) frameData.data() + frameDataOffset, - size()); - return data; + if(zlib::isAvailable() && d->header->compression() && !d->header->encryption()) { + if(frameData.size() <= frameDataOffset) { + debug("Compressed frame doesn't have enough data to decode"); + return ByteVector(); + } + + const ByteVector outData = zlib::decompress(frameData.mid(frameDataOffset)); + if(!outData.isEmpty() && frameDataLength != outData.size()) { + debug("frameDataLength does not match the data length returned by zlib"); + } + + return outData; } - else -#endif - return frameData.mid(frameDataOffset, frameDataLength); + + return frameData.mid(frameDataOffset, frameDataLength); } String Frame::readStringField(const ByteVector &data, String::Type encoding, int *position) @@ -219,9 +277,13 @@ String Frame::readStringField(const ByteVector &data, String::Type encoding, int int end = data.find(delimiter, *position, delimiter.size()); if(end < *position) - return String::null; + return String(); - String str = String(data.mid(*position, end - *position), encoding); + String str; + if(encoding == String::Latin1) + str = Tag::latin1StringHandler()->parse(data.mid(*position, end - *position)); + else + str = String(data.mid(*position, end - *position), encoding); *position = end + delimiter.size(); @@ -230,19 +292,235 @@ String Frame::readStringField(const ByteVector &data, String::Type encoding, int String::Type Frame::checkEncoding(const StringList &fields, String::Type encoding) // static { + return checkEncoding(fields, encoding, 4); +} + +String::Type Frame::checkEncoding(const StringList &fields, String::Type encoding, unsigned int version) // static +{ + if((encoding == String::UTF8 || encoding == String::UTF16BE) && version != 4) + return String::UTF16; + if(encoding != String::Latin1) return encoding; for(StringList::ConstIterator it = fields.begin(); it != fields.end(); ++it) { if(!(*it).isLatin1()) { - debug("Frame::checkEncoding() -- Rendering using UTF8."); - return String::UTF8; + if(version == 4) { + debug("Frame::checkEncoding() -- Rendering using UTF8."); + return String::UTF8; + } + else { + debug("Frame::checkEncoding() -- Rendering using UTF16."); + return String::UTF16; + } } } return String::Latin1; } +String::Type Frame::checkTextEncoding(const StringList &fields, String::Type encoding) const +{ + return checkEncoding(fields, encoding, header()->version()); +} + +namespace +{ + const char *frameTranslation[][2] = { + // Text information frames + { "TALB", "ALBUM"}, + { "TBPM", "BPM" }, + { "TCOM", "COMPOSER" }, + { "TCON", "GENRE" }, + { "TCOP", "COPYRIGHT" }, + { "TDEN", "ENCODINGTIME" }, + { "TDLY", "PLAYLISTDELAY" }, + { "TDOR", "ORIGINALDATE" }, + { "TDRC", "DATE" }, + // { "TRDA", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 + // { "TDAT", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 + // { "TYER", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 + // { "TIME", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4 + { "TDRL", "RELEASEDATE" }, + { "TDTG", "TAGGINGDATE" }, + { "TENC", "ENCODEDBY" }, + { "TEXT", "LYRICIST" }, + { "TFLT", "FILETYPE" }, + //{ "TIPL", "INVOLVEDPEOPLE" }, handled separately + { "TIT1", "CONTENTGROUP" }, // 'Work' in iTunes + { "TIT2", "TITLE"}, + { "TIT3", "SUBTITLE" }, + { "TKEY", "INITIALKEY" }, + { "TLAN", "LANGUAGE" }, + { "TLEN", "LENGTH" }, + //{ "TMCL", "MUSICIANCREDITS" }, handled separately + { "TMED", "MEDIA" }, + { "TMOO", "MOOD" }, + { "TOAL", "ORIGINALALBUM" }, + { "TOFN", "ORIGINALFILENAME" }, + { "TOLY", "ORIGINALLYRICIST" }, + { "TOPE", "ORIGINALARTIST" }, + { "TOWN", "OWNER" }, + { "TPE1", "ARTIST"}, + { "TPE2", "ALBUMARTIST" }, // id3's spec says 'PERFORMER', but most programs use 'ALBUMARTIST' + { "TPE3", "CONDUCTOR" }, + { "TPE4", "REMIXER" }, // could also be ARRANGER + { "TPOS", "DISCNUMBER" }, + { "TPRO", "PRODUCEDNOTICE" }, + { "TPUB", "LABEL" }, + { "TRCK", "TRACKNUMBER" }, + { "TRSN", "RADIOSTATION" }, + { "TRSO", "RADIOSTATIONOWNER" }, + { "TSOA", "ALBUMSORT" }, + { "TSOC", "COMPOSERSORT" }, + { "TSOP", "ARTISTSORT" }, + { "TSOT", "TITLESORT" }, + { "TSO2", "ALBUMARTISTSORT" }, // non-standard, used by iTunes + { "TSRC", "ISRC" }, + { "TSSE", "ENCODING" }, + // URL frames + { "WCOP", "COPYRIGHTURL" }, + { "WOAF", "FILEWEBPAGE" }, + { "WOAR", "ARTISTWEBPAGE" }, + { "WOAS", "AUDIOSOURCEWEBPAGE" }, + { "WORS", "RADIOSTATIONWEBPAGE" }, + { "WPAY", "PAYMENTWEBPAGE" }, + { "WPUB", "PUBLISHERWEBPAGE" }, + //{ "WXXX", "URL"}, handled specially + // Other frames + { "COMM", "COMMENT" }, + //{ "USLT", "LYRICS" }, handled specially + // Apple iTunes proprietary frames + { "PCST", "PODCAST" }, + { "TCAT", "PODCASTCATEGORY" }, + { "TDES", "PODCASTDESC" }, + { "TGID", "PODCASTID" }, + { "WFED", "PODCASTURL" }, + { "MVNM", "MOVEMENTNAME" }, + { "MVIN", "MOVEMENTNUMBER" }, + { "GRP1", "GROUPING" }, + }; + const size_t frameTranslationSize = sizeof(frameTranslation) / sizeof(frameTranslation[0]); + + const char *txxxFrameTranslation[][2] = { + { "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID" }, + { "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID" }, + { "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ_ALBUMARTISTID" }, + { "MUSICBRAINZ ALBUM RELEASE COUNTRY", "RELEASECOUNTRY" }, + { "MUSICBRAINZ ALBUM STATUS", "RELEASESTATUS" }, + { "MUSICBRAINZ ALBUM TYPE", "RELEASETYPE" }, + { "MUSICBRAINZ RELEASE GROUP ID", "MUSICBRAINZ_RELEASEGROUPID" }, + { "MUSICBRAINZ RELEASE TRACK ID", "MUSICBRAINZ_RELEASETRACKID" }, + { "MUSICBRAINZ WORK ID", "MUSICBRAINZ_WORKID" }, + { "ACOUSTID ID", "ACOUSTID_ID" }, + { "ACOUSTID FINGERPRINT", "ACOUSTID_FINGERPRINT" }, + { "MUSICIP PUID", "MUSICIP_PUID" }, + }; + const size_t txxxFrameTranslationSize = sizeof(txxxFrameTranslation) / sizeof(txxxFrameTranslation[0]); + + // list of deprecated frames and their successors + const char *deprecatedFrames[][2] = { + {"TRDA", "TDRC"}, // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3) + {"TDAT", "TDRC"}, // 2.3 -> 2.4 + {"TYER", "TDRC"}, // 2.3 -> 2.4 + {"TIME", "TDRC"}, // 2.3 -> 2.4 + }; + const size_t deprecatedFramesSize = sizeof(deprecatedFrames) / sizeof(deprecatedFrames[0]); +} + +String Frame::frameIDToKey(const ByteVector &id) +{ + ByteVector id24 = id; + for(size_t i = 0; i < deprecatedFramesSize; ++i) { + if(id24 == deprecatedFrames[i][0]) { + id24 = deprecatedFrames[i][1]; + break; + } + } + for(size_t i = 0; i < frameTranslationSize; ++i) { + if(id24 == frameTranslation[i][0]) + return frameTranslation[i][1]; + } + return String(); +} + +ByteVector Frame::keyToFrameID(const String &s) +{ + const String key = s.upper(); + for(size_t i = 0; i < frameTranslationSize; ++i) { + if(key == frameTranslation[i][1]) + return frameTranslation[i][0]; + } + return ByteVector(); +} + +String Frame::txxxToKey(const String &description) +{ + const String d = description.upper(); + for(size_t i = 0; i < txxxFrameTranslationSize; ++i) { + if(d == txxxFrameTranslation[i][0]) + return txxxFrameTranslation[i][1]; + } + return d; +} + +String Frame::keyToTXXX(const String &s) +{ + const String key = s.upper(); + for(size_t i = 0; i < txxxFrameTranslationSize; ++i) { + if(key == txxxFrameTranslation[i][1]) + return txxxFrameTranslation[i][0]; + } + return s; +} + +PropertyMap Frame::asProperties() const +{ + if(dynamic_cast< const UnknownFrame *>(this)) { + PropertyMap m; + m.unsupportedData().append("UNKNOWN/" + frameID()); + return m; + } + const ByteVector &id = frameID(); + // workaround until this function is virtual + if(id == "TXXX") + return dynamic_cast< const UserTextIdentificationFrame* >(this)->asProperties(); + // Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number), GRP1 (Grouping) are in fact text frames. + else if(id[0] == 'T' || id == "WFED" || id == "MVNM" || id == "MVIN" || id == "GRP1") + return dynamic_cast< const TextIdentificationFrame* >(this)->asProperties(); + else if(id == "WXXX") + return dynamic_cast< const UserUrlLinkFrame* >(this)->asProperties(); + else if(id[0] == 'W') + return dynamic_cast< const UrlLinkFrame* >(this)->asProperties(); + else if(id == "COMM") + return dynamic_cast< const CommentsFrame* >(this)->asProperties(); + else if(id == "USLT") + return dynamic_cast< const UnsynchronizedLyricsFrame* >(this)->asProperties(); + else if(id == "UFID") + return dynamic_cast< const UniqueFileIdentifierFrame* >(this)->asProperties(); + else if(id == "PCST") + return dynamic_cast< const PodcastFrame* >(this)->asProperties(); + PropertyMap m; + m.unsupportedData().append(id); + return m; +} + +void Frame::splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties, + PropertyMap &tiplProperties, PropertyMap &tmclProperties) +{ + singleFrameProperties.clear(); + tiplProperties.clear(); + tmclProperties.clear(); + for(PropertyMap::ConstIterator it = original.begin(); it != original.end(); ++it) { + if(TextIdentificationFrame::involvedPeopleMap().contains(it->first)) + tiplProperties.insert(it->first, it->second); + else if(it->first.startsWith(TextIdentificationFrame::instrumentPrefix)) + tmclProperties.insert(it->first, it->second); + else + singleFrameProperties.insert(it->first, it->second); + } +} + //////////////////////////////////////////////////////////////////////////////// // Frame::Header class //////////////////////////////////////////////////////////////////////////////// @@ -264,8 +542,8 @@ public: {} ByteVector frameID; - uint frameSize; - uint version; + unsigned int frameSize; + unsigned int version; // flags @@ -283,12 +561,12 @@ public: // static members (Frame::Header) //////////////////////////////////////////////////////////////////////////////// -TagLib::uint Frame::Header::size() +unsigned int Frame::Header::size() { return size(4); } -TagLib::uint Frame::Header::size(uint version) +unsigned int Frame::Header::size(unsigned int version) { switch(version) { case 0: @@ -306,15 +584,15 @@ TagLib::uint Frame::Header::size(uint version) // public members (Frame::Header) //////////////////////////////////////////////////////////////////////////////// -Frame::Header::Header(const ByteVector &data, bool synchSafeInts) +Frame::Header::Header(const ByteVector &data, bool synchSafeInts) : + d(new HeaderPrivate()) { - d = new HeaderPrivate; setData(data, synchSafeInts); } -Frame::Header::Header(const ByteVector &data, uint version) +Frame::Header::Header(const ByteVector &data, unsigned int version) : + d(new HeaderPrivate()) { - d = new HeaderPrivate; setData(data, version); } @@ -325,10 +603,10 @@ Frame::Header::~Header() void Frame::Header::setData(const ByteVector &data, bool synchSafeInts) { - setData(data, uint(synchSafeInts ? 4 : 3)); + setData(data, static_cast<unsigned int>(synchSafeInts ? 4 : 3)); } -void Frame::Header::setData(const ByteVector &data, uint version) +void Frame::Header::setData(const ByteVector &data, unsigned int version) { d->version = version; @@ -356,7 +634,7 @@ void Frame::Header::setData(const ByteVector &data, uint version) return; } - d->frameSize = data.mid(3, 3).toUInt(); + d->frameSize = data.toUInt(3, 3, true); break; } @@ -384,7 +662,7 @@ void Frame::Header::setData(const ByteVector &data, uint version) // Set the size -- the frame size is the four bytes starting at byte four in // the frame header (structure 4) - d->frameSize = data.mid(4, 4).toUInt(); + d->frameSize = data.toUInt(4U); { // read the first byte of flags std::bitset<8> flags(data[8]); @@ -431,7 +709,7 @@ void Frame::Header::setData(const ByteVector &data, uint version) // iTunes writes v2.4 tags with v2.3-like frame sizes if(d->frameSize > 127) { if(!isValidFrameID(data.mid(d->frameSize + 10, 4))) { - unsigned int uintSize = data.mid(4, 4).toUInt(); + unsigned int uintSize = data.toUInt(4U); if(isValidFrameID(data.mid(uintSize + 10, 4))) { d->frameSize = uintSize; } @@ -469,21 +747,26 @@ void Frame::Header::setFrameID(const ByteVector &id) d->frameID = id.mid(0, 4); } -TagLib::uint Frame::Header::frameSize() const +unsigned int Frame::Header::frameSize() const { return d->frameSize; } -void Frame::Header::setFrameSize(uint size) +void Frame::Header::setFrameSize(unsigned int size) { d->frameSize = size; } -TagLib::uint Frame::Header::version() const +unsigned int Frame::Header::version() const { return d->version; } +void Frame::Header::setVersion(unsigned int version) +{ + d->version = version; +} + bool Frame::Header::tagAlterPreservation() const { return d->tagAlterPreservation; @@ -538,7 +821,11 @@ ByteVector Frame::Header::render() const { ByteVector flags(2, char(0)); // just blank for the moment - ByteVector v = d->frameID + SynchData::fromUInt(d->frameSize) + flags; + ByteVector v = d->frameID + + (d->version == 3 + ? ByteVector::fromUInt(d->frameSize) + : SynchData::fromUInt(d->frameSize)) + + flags; return v; } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2frame.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2frame.h index 661be3d2b..b14f6f422 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2frame.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2frame.h @@ -33,6 +33,7 @@ namespace TagLib { class StringList; + class PropertyMap; namespace ID3v2 { @@ -46,7 +47,7 @@ namespace TagLib { * split between a collection of frames (which are in turn split into fields * (Structure, <a href="id3v2-structure.html#4">4</a>) * (<a href="id3v2-frames.html">Frames</a>). This class provides an API for - * gathering information about and modifying ID3v2 frames. Funtionallity + * gathering information about and modifying ID3v2 frames. Functionality * specific to a given frame type is handed in one of the many subclasses. */ @@ -56,6 +57,14 @@ namespace TagLib { friend class FrameFactory; public: + + /*! + * Creates a textual frame which corresponds to a single key in the PropertyMap + * interface. These are all (User)TextIdentificationFrames except TIPL and TMCL, + * all (User)URLLinkFrames, CommentsFrames, and UnsynchronizedLyricsFrame. + */ + static Frame *createTextualFrame(const String &key, const StringList &values); + /*! * Destroys this Frame instance. */ @@ -70,7 +79,7 @@ namespace TagLib { /*! * Returns the size of the frame. */ - uint size() const; + unsigned int size() const; /*! * Returns the size of the frame header @@ -80,14 +89,15 @@ namespace TagLib { * non-binary compatible release this will be made into a non-static * member that checks the internal ID3v2 version. */ - static uint headerSize(); // BIC: remove and make non-static + static unsigned int headerSize(); // BIC: make non-static /*! * Returns the size of the frame header for the given ID3v2 version. * * \deprecated Please see the explanation above. */ - static uint headerSize(uint version); // BIC: remove and make non-static + // BIC: remove + static unsigned int headerSize(unsigned int version); /*! * Sets the data that will be used as the frame. Since the length is not @@ -126,6 +136,28 @@ namespace TagLib { */ static ByteVector textDelimiter(String::Type t); + /*! + * The string with which an instrument name is prefixed to build a key in a PropertyMap; + * used to translate PropertyMaps to TMCL frames. In the current implementation, this + * is "PERFORMER:". + */ + static const String instrumentPrefix; + /*! + * The PropertyMap key prefix which triggers the use of a COMM frame instead of a TXXX + * frame for a non-standard key. In the current implementation, this is "COMMENT:". + */ + static const String commentPrefix; + /*! + * The PropertyMap key prefix which triggers the use of a USLT frame instead of a TXXX + * frame for a non-standard key. In the current implementation, this is "LYRICS:". + */ + static const String lyricsPrefix; + /*! + * The PropertyMap key prefix which triggers the use of a WXXX frame instead of a TXX + * frame for a non-standard key. In the current implementation, this is "URL:". + */ + static const String urlPrefix; + protected: class Header; @@ -134,7 +166,7 @@ namespace TagLib { * All other processing of \a data should be handled in a subclass. * * \note This need not contain anything more than a frame ID, but - * \e must constain at least that. + * \e must contain at least that. */ explicit Frame(const ByteVector &data); @@ -187,21 +219,88 @@ namespace TagLib { ByteVector fieldData(const ByteVector &frameData) const; /*! - * Reads a String of type \a encodiong from the ByteVector \a data. If \a + * Reads a String of type \a encoding from the ByteVector \a data. If \a * position is passed in it is used both as the starting point and is - * updated to replect the position just after the string that has been read. + * updated to return the position just after the string that has been read. * This is useful for reading strings sequentially. */ String readStringField(const ByteVector &data, String::Type encoding, - int *positon = 0); + int *position = 0); /*! * Checks a the list of string values to see if they can be used with the * specified encoding and returns the recommended encoding. */ + // BIC: remove and make non-static static String::Type checkEncoding(const StringList &fields, String::Type encoding); + /*! + * Checks a the list of string values to see if they can be used with the + * specified encoding and returns the recommended encoding. This method + * also checks the ID3v2 version and makes sure the encoding can be used + * in the specified version. + */ + // BIC: remove and make non-static + static String::Type checkEncoding(const StringList &fields, + String::Type encoding, unsigned int version); + + /*! + * Checks a the list of string values to see if they can be used with the + * specified encoding and returns the recommended encoding. This method + * also checks the ID3v2 version and makes sure the encoding can be used + * in the version specified by the frame's header. + */ + String::Type checkTextEncoding(const StringList &fields, + String::Type encoding) const; + + + /*! + * Parses the contents of this frame as PropertyMap. If that fails, the returned + * PropertyMap will be empty, and its unsupportedData() will contain this frame's + * ID. + * BIC: Will be a virtual function in future releases. + */ + PropertyMap asProperties() const; + + /*! + * Returns an appropriate ID3 frame ID for the given free-form tag key. This method + * will return an empty ByteVector if no specialized translation is found. + */ + static ByteVector keyToFrameID(const String &); + + /*! + * Returns a free-form tag name for the given ID3 frame ID. Note that this does not work + * for general frame IDs such as TXXX or WXXX; in such a case an empty string is returned. + */ + static String frameIDToKey(const ByteVector &); + + /*! + * Returns an appropriate TXXX frame description for the given free-form tag key. + */ + static String keyToTXXX(const String &); + + /*! + * Returns a free-form tag name for the given ID3 frame description. + */ + static String txxxToKey(const String &); + + /*! + * This helper function splits the PropertyMap \a original into three ProperytMaps + * \a singleFrameProperties, \a tiplProperties, and \a tmclProperties, such that: + * - \a singleFrameProperties contains only of keys which can be represented with + * exactly one ID3 frame per key. In the current implementation + * this is everything except for the fixed "involved people" keys and keys of the + * form "TextIdentificationFrame::instrumentPrefix" + "instrument", which are + * mapped to a TMCL frame. + * - \a tiplProperties will consist of those keys that are present in + * TextIdentificationFrame::involvedPeopleMap() + * - \a tmclProperties contains the "musician credits" keys which should be mapped + * to a TMCL frame + */ + static void splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties, + PropertyMap &tiplProperties, PropertyMap &tmclProperties); + private: Frame(const Frame &); Frame &operator=(const Frame &); @@ -236,7 +335,7 @@ namespace TagLib { * \deprecated Please use the constructor below that accepts a version * number. */ - Header(const ByteVector &data, bool synchSafeInts); + TAGLIB_DEPRECATED Header(const ByteVector &data, bool synchSafeInts); /*! * Construct a Frame Header based on \a data. \a data must at least @@ -245,7 +344,7 @@ namespace TagLib { * * \a version should be the ID3v2 version of the tag. */ - explicit Header(const ByteVector &data, uint version = 4); + explicit Header(const ByteVector &data, unsigned int version = 4); /*! * Destroys this Header instance. @@ -258,13 +357,13 @@ namespace TagLib { * \deprecated Please use the version below that accepts an ID3v2 version * number. */ - void setData(const ByteVector &data, bool synchSafeInts); + TAGLIB_DEPRECATED void setData(const ByteVector &data, bool synchSafeInts); /*! * Sets the data for the Header. \a version should indicate the ID3v2 * version number of the tag that this frame is contained in. */ - void setData(const ByteVector &data, uint version = 4); + void setData(const ByteVector &data, unsigned int version = 4); /*! * Returns the Frame ID (Structure, <a href="id3v2-structure.html#4">4</a>) @@ -286,18 +385,24 @@ namespace TagLib { * Returns the size of the frame data portion, as set when setData() was * called or set explicitly via setFrameSize(). */ - uint frameSize() const; + unsigned int frameSize() const; /*! * Sets the size of the frame data portion. */ - void setFrameSize(uint size); + void setFrameSize(unsigned int size); /*! - * Returns the ID3v2 version of the header (as passed in from the - * construction of the header). + * Returns the ID3v2 version of the header, as passed in from the + * construction of the header or set via setVersion(). */ - uint version() const; + unsigned int version() const; + + /*! + * Sets the ID3v2 version of the header, changing has impact on the + * correct parsing/rendering of frame data. + */ + void setVersion(unsigned int version); /*! * Returns the size of the frame header in bytes. @@ -307,7 +412,8 @@ namespace TagLib { * removed in the next binary incompatible release (2.0) and will be * replaced with a non-static method that checks the frame version. */ - static uint size(); + // BIC: make non-static + static unsigned int size(); /*! * Returns the size of the frame header in bytes for the ID3v2 version @@ -315,7 +421,8 @@ namespace TagLib { * * \deprecated Please see the explanation in the version above. */ - static uint size(uint version); + // BIC: remove + static unsigned int size(unsigned int version); /*! * Returns true if the flag for tag alter preservation is set. @@ -356,7 +463,7 @@ namespace TagLib { bool readOnly() const; /*! - * Returns true if the flag for the grouping identifity is set. + * Returns true if the flag for the grouping identity is set. * * \note This flag is currently ignored internally in TagLib. */ @@ -398,7 +505,7 @@ namespace TagLib { /*! * \deprecated */ - bool frameAlterPreservation() const; + TAGLIB_DEPRECATED bool frameAlterPreservation() const; private: Header(const Header &); diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2framefactory.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2framefactory.cpp index 3c829d9bd..cc276d448 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -23,11 +23,8 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifndef HAVE_ZLIB -#include <config.h> -#endif - #include <tdebug.h> +#include <tzlib.h> #include "id3v2framefactory.h" #include "id3v2synchdata.h" @@ -44,10 +41,52 @@ #include "frames/unsynchronizedlyricsframe.h" #include "frames/popularimeterframe.h" #include "frames/privateframe.h" +#include "frames/ownershipframe.h" +#include "frames/synchronizedlyricsframe.h" +#include "frames/eventtimingcodesframe.h" +#include "frames/chapterframe.h" +#include "frames/tableofcontentsframe.h" +#include "frames/podcastframe.h" using namespace TagLib; using namespace ID3v2; +namespace +{ + void updateGenre(TextIdentificationFrame *frame) + { + StringList fields = frame->fieldList(); + StringList newfields; + + for(StringList::ConstIterator it = fields.begin(); it != fields.end(); ++it) { + String s = *it; + int offset = 0; + int end = 0; + + while(s.length() > offset && s[offset] == '(' && + (end = s.find(")", offset + 1)) > offset) { + // "(12)Genre" + const String genreCode = s.substr(offset + 1, end - 1); + s = s.substr(end + 1); + bool ok; + int number = genreCode.toInt(&ok); + if((ok && number >= 0 && number <= 255 && + !(ID3v1::genre(number) == s)) || + genreCode == "RX" || genreCode == "CR") + newfields.append(genreCode); + } + if(!s.isEmpty()) + // "Genre" or "12" + newfields.append(s); + } + + if(newfields.isEmpty()) + fields.append(String()); + + frame->setText(newfields); + } +} + class FrameFactory::FrameFactoryPrivate { public: @@ -65,7 +104,7 @@ public: } }; -FrameFactory *FrameFactory::factory = 0; +FrameFactory FrameFactory::factory; //////////////////////////////////////////////////////////////////////////////// // public members @@ -73,17 +112,15 @@ FrameFactory *FrameFactory::factory = 0; FrameFactory *FrameFactory::instance() { - if(!factory) - factory = new FrameFactory; - return factory; + return &factory; } Frame *FrameFactory::createFrame(const ByteVector &data, bool synchSafeInts) const { - return createFrame(data, uint(synchSafeInts ? 4 : 3)); + return createFrame(data, static_cast<unsigned int>(synchSafeInts ? 4 : 3)); } -Frame *FrameFactory::createFrame(const ByteVector &data, uint version) const +Frame *FrameFactory::createFrame(const ByteVector &data, unsigned int version) const { Header tagHeader; tagHeader.setMajorVersion(version); @@ -91,25 +128,41 @@ Frame *FrameFactory::createFrame(const ByteVector &data, uint version) const } Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) const +{ + return createFrame(origData, const_cast<const Header *>(tagHeader)); +} + +Frame *FrameFactory::createFrame(const ByteVector &origData, const Header *tagHeader) const { ByteVector data = origData; - uint version = tagHeader->majorVersion(); + unsigned int version = tagHeader->majorVersion(); Frame::Header *header = new Frame::Header(data, version); ByteVector frameID = header->frameID(); // A quick sanity check -- make sure that the frameID is 4 uppercase Latin1 // characters. Also make sure that there is data in the frame. - if(!(frameID.size() == (version < 3 ? 3 : 4)) || - header->frameSize() <= uint(header->dataLengthIndicator() ? 4 : 0) || + if(frameID.size() != (version < 3 ? 3 : 4) || + header->frameSize() <= static_cast<unsigned int>(header->dataLengthIndicator() ? 4 : 0) || header->frameSize() > data.size()) { delete header; return 0; } +#ifndef NO_ITUNES_HACKS + if(version == 3 && frameID.size() == 4 && frameID[3] == '\0') { + // iTunes v2.3 tags store v2.2 frames - convert now + frameID = frameID.mid(0, 3); + header->setFrameID(frameID); + header->setVersion(2); + updateFrame(header); + header->setVersion(3); + } +#endif + for(ByteVector::ConstIterator it = frameID.begin(); it != frameID.end(); it++) { - if( (*it < 'A' || *it > 'Z') && (*it < '1' || *it > '9') ) { + if( (*it < 'A' || *it > 'Z') && (*it < '0' || *it > '9') ) { delete header; return 0; } @@ -126,12 +179,11 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) // TagLib doesn't mess with encrypted frames, so just treat them // as unknown frames. -#if HAVE_ZLIB == 0 - if(header->compression()) { + if(!zlib::isAvailable() && header->compression()) { debug("Compressed frames are currently not supported."); return new UnknownFrame(data, header); } -#endif + if(header->encryption()) { debug("Encrypted frames are currently not supported."); return new UnknownFrame(data, header); @@ -153,7 +205,8 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) // Text Identification (frames 4.2) - if(frameID.startsWith("T")) { + // Apple proprietary WFED (Podcast URL), MVNM (Movement Name), MVIN (Movement Number), GRP1 (Grouping) are in fact text frames. + if(frameID.startsWith("T") || frameID == "WFED" || frameID == "MVNM" || frameID == "MVIN" || frameID == "GRP1") { TextIdentificationFrame *f = frameID != "TXXX" ? new TextIdentificationFrame(data, header) @@ -185,13 +238,13 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) // ID3v2.2 Attached Picture - if(frameID == "PIC") { + if(frameID == "PIC") { AttachedPictureFrame *f = new AttachedPictureFrameV22(data, header); d->setTextEncoding(f); return f; } - // Relative Volume Adjustment (frames 4.11) + // Relative Volume Adjustment (frames 4.11) if(frameID == "RVA2") return new RelativeVolumeFrame(data, header); @@ -231,6 +284,20 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) return f; } + // Synchronized lyrics/text (frames 4.9) + + if(frameID == "SYLT") { + SynchronizedLyricsFrame *f = new SynchronizedLyricsFrame(data, header); + if(d->useDefaultEncoding) + f->setTextEncoding(d->defaultEncoding); + return f; + } + + // Event timing codes (frames 4.5) + + if(frameID == "ETCO") + return new EventTimingCodesFrame(data, header); + // Popularimeter (frames 4.17) if(frameID == "POPM") @@ -241,9 +308,64 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, Header *tagHeader) if(frameID == "PRIV") return new PrivateFrame(data, header); + // Ownership (frames 4.22) + + if(frameID == "OWNE") { + OwnershipFrame *f = new OwnershipFrame(data, header); + d->setTextEncoding(f); + return f; + } + + // Chapter (ID3v2 chapters 1.0) + + if(frameID == "CHAP") + return new ChapterFrame(tagHeader, data, header); + + // Table of contents (ID3v2 chapters 1.0) + + if(frameID == "CTOC") + return new TableOfContentsFrame(tagHeader, data, header); + + // Apple proprietary PCST (Podcast) + + if(frameID == "PCST") + return new PodcastFrame(data, header); + return new UnknownFrame(data, header); } +void FrameFactory::rebuildAggregateFrames(ID3v2::Tag *tag) const +{ + if(tag->header()->majorVersion() < 4 && + tag->frameList("TDRC").size() == 1 && + tag->frameList("TDAT").size() == 1) + { + TextIdentificationFrame *tdrc = + dynamic_cast<TextIdentificationFrame *>(tag->frameList("TDRC").front()); + UnknownFrame *tdat = static_cast<UnknownFrame *>(tag->frameList("TDAT").front()); + + if(tdrc && + tdrc->fieldList().size() == 1 && + tdrc->fieldList().front().size() == 4 && + tdat->data().size() >= 5) + { + String date(tdat->data().mid(1), String::Type(tdat->data()[0])); + if(date.length() == 4) { + tdrc->setText(tdrc->toString() + '-' + date.substr(2, 2) + '-' + date.substr(0, 2)); + if(tag->frameList("TIME").size() == 1) { + UnknownFrame *timeframe = static_cast<UnknownFrame *>(tag->frameList("TIME").front()); + if(timeframe->data().size() >= 5) { + String time(timeframe->data().mid(1), String::Type(timeframe->data()[0])); + if(time.length() == 4) { + tdrc->setText(tdrc->toString() + 'T' + time.substr(0, 2) + ':' + time.substr(2, 2)); + } + } + } + } + } + } +} + String::Type FrameFactory::defaultTextEncoding() const { return d->defaultEncoding; @@ -259,9 +381,9 @@ void FrameFactory::setDefaultTextEncoding(String::Type encoding) // protected members //////////////////////////////////////////////////////////////////////////////// -FrameFactory::FrameFactory() +FrameFactory::FrameFactory() : + d(new FrameFactoryPrivate()) { - d = new FrameFactoryPrivate; } FrameFactory::~FrameFactory() @@ -269,9 +391,97 @@ FrameFactory::~FrameFactory() delete d; } +namespace +{ + // Frame conversion table ID3v2.2 -> 2.4 + const char *frameConversion2[][2] = { + { "BUF", "RBUF" }, + { "CNT", "PCNT" }, + { "COM", "COMM" }, + { "CRA", "AENC" }, + { "ETC", "ETCO" }, + { "GEO", "GEOB" }, + { "IPL", "TIPL" }, + { "MCI", "MCDI" }, + { "MLL", "MLLT" }, + { "POP", "POPM" }, + { "REV", "RVRB" }, + { "SLT", "SYLT" }, + { "STC", "SYTC" }, + { "TAL", "TALB" }, + { "TBP", "TBPM" }, + { "TCM", "TCOM" }, + { "TCO", "TCON" }, + { "TCP", "TCMP" }, + { "TCR", "TCOP" }, + { "TDY", "TDLY" }, + { "TEN", "TENC" }, + { "TFT", "TFLT" }, + { "TKE", "TKEY" }, + { "TLA", "TLAN" }, + { "TLE", "TLEN" }, + { "TMT", "TMED" }, + { "TOA", "TOAL" }, + { "TOF", "TOFN" }, + { "TOL", "TOLY" }, + { "TOR", "TDOR" }, + { "TOT", "TOAL" }, + { "TP1", "TPE1" }, + { "TP2", "TPE2" }, + { "TP3", "TPE3" }, + { "TP4", "TPE4" }, + { "TPA", "TPOS" }, + { "TPB", "TPUB" }, + { "TRC", "TSRC" }, + { "TRD", "TDRC" }, + { "TRK", "TRCK" }, + { "TS2", "TSO2" }, + { "TSA", "TSOA" }, + { "TSC", "TSOC" }, + { "TSP", "TSOP" }, + { "TSS", "TSSE" }, + { "TST", "TSOT" }, + { "TT1", "TIT1" }, + { "TT2", "TIT2" }, + { "TT3", "TIT3" }, + { "TXT", "TOLY" }, + { "TXX", "TXXX" }, + { "TYE", "TDRC" }, + { "UFI", "UFID" }, + { "ULT", "USLT" }, + { "WAF", "WOAF" }, + { "WAR", "WOAR" }, + { "WAS", "WOAS" }, + { "WCM", "WCOM" }, + { "WCP", "WCOP" }, + { "WPB", "WPUB" }, + { "WXX", "WXXX" }, + + // Apple iTunes nonstandard frames + { "PCS", "PCST" }, + { "TCT", "TCAT" }, + { "TDR", "TDRL" }, + { "TDS", "TDES" }, + { "TID", "TGID" }, + { "WFD", "WFED" }, + { "MVN", "MVNM" }, + { "MVI", "MVIN" }, + { "GP1", "GRP1" }, + }; + const size_t frameConversion2Size = sizeof(frameConversion2) / sizeof(frameConversion2[0]); + + // Frame conversion table ID3v2.3 -> 2.4 + const char *frameConversion3[][2] = { + { "TORY", "TDOR" }, + { "TYER", "TDRC" }, + { "IPLS", "TIPL" }, + }; + const size_t frameConversion3Size = sizeof(frameConversion3) / sizeof(frameConversion3[0]); +} + bool FrameFactory::updateFrame(Frame::Header *header) const { - TagLib::ByteVector frameID = header->frameID(); + const ByteVector frameID = header->frameID(); switch(header->version()) { @@ -293,61 +503,12 @@ bool FrameFactory::updateFrame(Frame::Header *header) const // ID3v2.2 only used 3 bytes for the frame ID, so we need to convert all of // the frames to their 4 byte ID3v2.4 equivalent. - convertFrame("BUF", "RBUF", header); - convertFrame("CNT", "PCNT", header); - convertFrame("COM", "COMM", header); - convertFrame("CRA", "AENC", header); - convertFrame("ETC", "ETCO", header); - convertFrame("GEO", "GEOB", header); - convertFrame("IPL", "TIPL", header); - convertFrame("MCI", "MCDI", header); - convertFrame("MLL", "MLLT", header); - convertFrame("POP", "POPM", header); - convertFrame("REV", "RVRB", header); - convertFrame("SLT", "SYLT", header); - convertFrame("STC", "SYTC", header); - convertFrame("TAL", "TALB", header); - convertFrame("TBP", "TBPM", header); - convertFrame("TCM", "TCOM", header); - convertFrame("TCO", "TCON", header); - convertFrame("TCR", "TCOP", header); - convertFrame("TDY", "TDLY", header); - convertFrame("TEN", "TENC", header); - convertFrame("TFT", "TFLT", header); - convertFrame("TKE", "TKEY", header); - convertFrame("TLA", "TLAN", header); - convertFrame("TLE", "TLEN", header); - convertFrame("TMT", "TMED", header); - convertFrame("TOA", "TOAL", header); - convertFrame("TOF", "TOFN", header); - convertFrame("TOL", "TOLY", header); - convertFrame("TOR", "TDOR", header); - convertFrame("TOT", "TOAL", header); - convertFrame("TP1", "TPE1", header); - convertFrame("TP2", "TPE2", header); - convertFrame("TP3", "TPE3", header); - convertFrame("TP4", "TPE4", header); - convertFrame("TPA", "TPOS", header); - convertFrame("TPB", "TPUB", header); - convertFrame("TRC", "TSRC", header); - convertFrame("TRD", "TDRC", header); - convertFrame("TRK", "TRCK", header); - convertFrame("TSS", "TSSE", header); - convertFrame("TT1", "TIT1", header); - convertFrame("TT2", "TIT2", header); - convertFrame("TT3", "TIT3", header); - convertFrame("TXT", "TOLY", header); - convertFrame("TXX", "TXXX", header); - convertFrame("TYE", "TDRC", header); - convertFrame("UFI", "UFID", header); - convertFrame("ULT", "USLT", header); - convertFrame("WAF", "WOAF", header); - convertFrame("WAR", "WOAR", header); - convertFrame("WAS", "WOAS", header); - convertFrame("WCM", "WCOM", header); - convertFrame("WCP", "WCOP", header); - convertFrame("WPB", "WPUB", header); - convertFrame("WXX", "WXXX", header); + for(size_t i = 0; i < frameConversion2Size; ++i) { + if(frameID == frameConversion2[i][0]) { + header->setFrameID(frameConversion2[i][1]); + break; + } + } break; } @@ -366,8 +527,12 @@ bool FrameFactory::updateFrame(Frame::Header *header) const return false; } - convertFrame("TORY", "TDOR", header); - convertFrame("TYER", "TDRC", header); + for(size_t i = 0; i < frameConversion3Size; ++i) { + if(frameID == frameConversion3[i][0]) { + header->setFrameID(frameConversion3[i][1]); + break; + } + } break; } @@ -377,57 +542,11 @@ bool FrameFactory::updateFrame(Frame::Header *header) const // This should catch a typo that existed in TagLib up to and including // version 1.1 where TRDC was used for the year rather than TDRC. - convertFrame("TRDC", "TDRC", header); + if(frameID == "TRDC") + header->setFrameID("TDRC"); + break; } return true; } - -//////////////////////////////////////////////////////////////////////////////// -// private members -//////////////////////////////////////////////////////////////////////////////// - -void FrameFactory::convertFrame(const char *from, const char *to, - Frame::Header *header) const -{ - if(header->frameID() != from) - return; - - // debug("ID3v2.4 no longer supports the frame type " + String(from) + " It has" + - // "been converted to the type " + String(to) + "."); - - header->setFrameID(to); -} - -void FrameFactory::updateGenre(TextIdentificationFrame *frame) const -{ - StringList fields = frame->fieldList(); - StringList newfields; - - for(StringList::Iterator it = fields.begin(); it != fields.end(); ++it) { - String s = *it; - int end = s.find(")"); - - if(s.startsWith("(") && end > 0) { - // "(12)Genre" - String text = s.substr(end + 1); - bool ok; - int number = s.substr(1, end - 1).toInt(&ok); - if(ok && number >= 0 && number <= 255 && !(ID3v1::genre(number) == text)) - newfields.append(s.substr(1, end - 1)); - if(!text.isEmpty()) - newfields.append(text); - } - else { - // "Genre" or "12" - newfields.append(s); - } - } - - if(newfields.isEmpty()) - fields.append(String::null); - - frame->setText(newfields); - -} diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2framefactory.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2framefactory.h index 34b704bf6..9605c7885 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2framefactory.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2framefactory.h @@ -46,10 +46,10 @@ namespace TagLib { * * Reimplementing this factory is the key to adding support for frame types * not directly supported by TagLib to your application. To do so you would - * subclass this factory reimplement createFrame(). Then by setting your - * factory to be the default factory in ID3v2::Tag constructor or with - * MPEG::File::setID3v2FrameFactory() you can implement behavior that will - * allow for new ID3v2::Frame subclasses (also provided by you) to be used. + * subclass this factory and reimplement createFrame(). Then by setting your + * factory to be the default factory in ID3v2::Tag constructor you can + * implement behavior that will allow for new ID3v2::Frame subclasses (also + * provided by you) to be used. * * This implements both <i>abstract factory</i> and <i>singleton</i> patterns * of which more information is available on the web and in software design @@ -74,7 +74,7 @@ namespace TagLib { * \deprecated Please use the method below that accepts a ID3v2::Header * instance in new code. */ - Frame *createFrame(const ByteVector &data, bool synchSafeInts) const; + TAGLIB_DEPRECATED Frame *createFrame(const ByteVector &data, bool synchSafeInts) const; /*! * Create a frame based on \a data. \a version should indicate the ID3v2 @@ -84,20 +84,33 @@ namespace TagLib { * \deprecated Please use the method below that accepts a ID3v2::Header * instance in new code. */ - Frame *createFrame(const ByteVector &data, uint version = 4) const; + TAGLIB_DEPRECATED Frame *createFrame(const ByteVector &data, unsigned int version = 4) const; + /*! + * \deprecated + */ + // BIC: remove + Frame *createFrame(const ByteVector &data, Header *tagHeader) const; /*! * Create a frame based on \a data. \a tagHeader should be a valid * ID3v2::Header instance. */ // BIC: make virtual - Frame *createFrame(const ByteVector &data, Header *tagHeader) const; + Frame *createFrame(const ByteVector &data, const Header *tagHeader) const; + + /*! + * After a tag has been read, this tries to rebuild some of them + * information, most notably the recording date, from frames that + * have been deprecated and can't be upgraded directly. + */ + // BIC: Make virtual + void rebuildAggregateFrames(ID3v2::Tag *tag) const; /*! * Returns the default text encoding for text frames. If setTextEncoding() * has not been explicitly called this will only be used for new text * frames. However, if this value has been set explicitly all frames will be - * converted to this type (unless it's explitly set differently for the + * converted to this type (unless it's explicitly set differently for the * individual frame) when being rendered. * * \see setDefaultTextEncoding() @@ -123,8 +136,7 @@ namespace TagLib { FrameFactory(); /*! - * Destroys the frame factory. In most cases this will never be called (as - * is typical of singletons). + * Destroys the frame factory. */ virtual ~FrameFactory(); @@ -145,17 +157,7 @@ namespace TagLib { FrameFactory(const FrameFactory &); FrameFactory &operator=(const FrameFactory &); - /*! - * This method is used internally to convert a frame from ID \a from to ID - * \a to. If the frame matches the \a from pattern and converts the frame - * ID in the \a header or simply does nothing if the frame ID does not match. - */ - void convertFrame(const char *from, const char *to, - Frame::Header *header) const; - - void updateGenre(TextIdentificationFrame *frame) const; - - static FrameFactory *factory; + static FrameFactory factory; class FrameFactoryPrivate; FrameFactoryPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2header.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2header.cpp index 31a5a1ec1..da4aba0a1 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2header.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2header.cpp @@ -23,7 +23,7 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include <ostream> +#include <iostream> #include <bitset> #include <tstring.h> @@ -39,36 +39,33 @@ using namespace ID3v2; class Header::HeaderPrivate { public: - HeaderPrivate() : majorVersion(4), - revisionNumber(0), - unsynchronisation(false), - extendedHeader(false), - experimentalIndicator(false), - footerPresent(false), - tagSize(0) {} + HeaderPrivate() : + majorVersion(4), + revisionNumber(0), + unsynchronisation(false), + extendedHeader(false), + experimentalIndicator(false), + footerPresent(false), + tagSize(0) {} - ~HeaderPrivate() {} - - uint majorVersion; - uint revisionNumber; + unsigned int majorVersion; + unsigned int revisionNumber; bool unsynchronisation; bool extendedHeader; bool experimentalIndicator; bool footerPresent; - uint tagSize; - - static const uint size = 10; + unsigned int tagSize; }; //////////////////////////////////////////////////////////////////////////////// // static members //////////////////////////////////////////////////////////////////////////////// -TagLib::uint Header::size() +unsigned int Header::size() { - return HeaderPrivate::size; + return 10; } ByteVector Header::fileIdentifier() @@ -80,14 +77,14 @@ ByteVector Header::fileIdentifier() // public members //////////////////////////////////////////////////////////////////////////////// -Header::Header() +Header::Header() : + d(new HeaderPrivate()) { - d = new HeaderPrivate; } -Header::Header(const ByteVector &data) +Header::Header(const ByteVector &data) : + d(new HeaderPrivate()) { - d = new HeaderPrivate; parse(data); } @@ -96,17 +93,17 @@ Header::~Header() delete d; } -TagLib::uint Header::majorVersion() const +unsigned int Header::majorVersion() const { return d->majorVersion; } -void Header::setMajorVersion(TagLib::uint version) +void Header::setMajorVersion(unsigned int version) { d->majorVersion = version; } -TagLib::uint Header::revisionNumber() const +unsigned int Header::revisionNumber() const { return d->revisionNumber; } @@ -131,20 +128,20 @@ bool Header::footerPresent() const return d->footerPresent; } -TagLib::uint Header::tagSize() const +unsigned int Header::tagSize() const { return d->tagSize; } -TagLib::uint Header::completeTagSize() const +unsigned int Header::completeTagSize() const { if(d->footerPresent) - return d->tagSize + d->size + Footer::size(); + return d->tagSize + size() + Footer::size(); else - return d->tagSize + d->size; + return d->tagSize + size(); } -void Header::setTagSize(uint s) +void Header::setTagSize(unsigned int s) { d->tagSize = s; } @@ -164,7 +161,7 @@ ByteVector Header::render() const // add the version number -- we always render a 2.4.0 tag regardless of what // the tag originally was. - v.append(char(4)); + v.append(char(majorVersion())); v.append(char(0)); // Currently we don't actually support writing extended headers, footers or @@ -199,7 +196,6 @@ void Header::parse(const ByteVector &data) if(data.size() < size()) return; - // do some sanity checking -- even in ID3v2.3.0 and less the tag size is a // synch-safe integer, so all bytes must be less than 128. If this is not // true then this is an invalid tag. @@ -215,8 +211,8 @@ void Header::parse(const ByteVector &data) return; } - for(ByteVector::Iterator it = sizeData.begin(); it != sizeData.end(); it++) { - if(uchar(*it) >= 128) { + for(ByteVector::ConstIterator it = sizeData.begin(); it != sizeData.end(); it++) { + if(static_cast<unsigned char>(*it) >= 128) { d->tagSize = 0; debug("TagLib::ID3v2::Header::parse() - One of the size bytes in the id3v2 header was greater than the allowed 128."); return; diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2header.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2header.h index 307ba96c8..a1f2985e4 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2header.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2header.h @@ -28,6 +28,7 @@ #include "tbytevector.h" #include "taglib_export.h" +#include "id3v2.h" namespace TagLib { @@ -37,7 +38,7 @@ namespace TagLib { /*! * This class implements ID3v2 headers. It attempts to follow, both - * semantically and programatically, the structure specified in + * semantically and programmatically, the structure specified in * the ID3v2 standard. The API is based on the properties of ID3v2 headers * specified there. If any of the terms used in this documentation are * unclear please check the specification in the linked section. @@ -67,7 +68,7 @@ namespace TagLib { * Returns the major version number. (Note: This is the 4, not the 2 in * ID3v2.4.0. The 2 is implied.) */ - uint majorVersion() const; + unsigned int majorVersion() const; /*! * Set the the major version number to \a version. (Note: This is @@ -78,13 +79,13 @@ namespace TagLib { * version which is written and in general should not be called by API * users. */ - void setMajorVersion(uint version); + void setMajorVersion(unsigned int version); /*! * Returns the revision number. (Note: This is the 0, not the 4 in * ID3v2.4.0. The 2 is implied.) */ - uint revisionNumber() const; + unsigned int revisionNumber() const; /*! * Returns true if unsynchronisation has been applied to all frames. @@ -116,7 +117,7 @@ namespace TagLib { * * \see completeTagSize() */ - uint tagSize() const; + unsigned int tagSize() const; /*! * Returns the tag size, including the header and, if present, the footer @@ -124,18 +125,18 @@ namespace TagLib { * * \see tagSize() */ - uint completeTagSize() const; + unsigned int completeTagSize() const; /*! * Set the tag size to \a s. * \see tagSize() */ - void setTagSize(uint s); + void setTagSize(unsigned int s); /*! * Returns the size of the header. Presently this is always 10 bytes. */ - static uint size(); + static unsigned int size(); /*! * Returns the string used to identify and ID3v2 tag inside of a file. diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2synchdata.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2synchdata.cpp index 12e1f5b2b..2aa999993 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2synchdata.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2synchdata.cpp @@ -23,16 +23,16 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include <ostream> +#include <iostream> #include "id3v2synchdata.h" using namespace TagLib; using namespace ID3v2; -TagLib::uint SynchData::toUInt(const ByteVector &data) +unsigned int SynchData::toUInt(const ByteVector &data) { - uint sum = 0; + unsigned int sum = 0; bool notSynchSafe = false; int last = data.size() > 4 ? 3 : data.size() - 1; @@ -49,29 +49,50 @@ TagLib::uint SynchData::toUInt(const ByteVector &data) // Invalid data; assume this was created by some buggy software that just // put normal integers here rather than syncsafe ones, and try it that // way. - sum = (data.size() > 4) ? data.mid(0, 4).toUInt() : data.toUInt(); + if(data.size() >= 4) { + sum = data.toUInt(0, true); + } + else { + ByteVector tmp(data); + tmp.resize(4); + sum = tmp.toUInt(0, true); + } } return sum; } -ByteVector SynchData::fromUInt(uint value) +ByteVector SynchData::fromUInt(unsigned int value) { ByteVector v(4, 0); for(int i = 0; i < 4; i++) - v[i] = uchar(value >> ((3 - i) * 7) & 0x7f); + v[i] = static_cast<unsigned char>(value >> ((3 - i) * 7) & 0x7f); return v; } ByteVector SynchData::decode(const ByteVector &data) { - ByteVector result = data; + // We have this optimized method instead of using ByteVector::replace(), + // since it makes a great difference when decoding huge unsynchronized frames. - ByteVector pattern(2, char(0)); - pattern[0] = '\xFF'; - pattern[1] = '\x00'; + ByteVector result(data.size()); - return result.replace(pattern, '\xFF'); + ByteVector::ConstIterator src = data.begin(); + ByteVector::Iterator dst = result.begin(); + + while(src < data.end() - 1) { + *dst++ = *src++; + + if(*(src - 1) == '\xff' && *src == '\x00') + src++; + } + + if(src < data.end()) + *dst++ = *src++; + + result.resize(static_cast<unsigned int>(dst - result.begin())); + + return result; } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2synchdata.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2synchdata.h index 4a1f596a9..13e07161b 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2synchdata.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2synchdata.h @@ -51,12 +51,12 @@ namespace TagLib { * <a href="id3v2-structure.html#6.2">6.2</a>). The default \a length of * 4 is used if another value is not specified. */ - TAGLIB_EXPORT uint toUInt(const ByteVector &data); + TAGLIB_EXPORT unsigned int toUInt(const ByteVector &data); /*! * Returns a 4 byte (32 bit) synchsafe integer based on \a value. */ - TAGLIB_EXPORT ByteVector fromUInt(uint value); + TAGLIB_EXPORT ByteVector fromUInt(unsigned int value); /*! * Convert the data from unsynchronized data to its original format. diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp index 5287481d8..54dddf72f 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.cpp @@ -23,7 +23,11 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include <algorithm> + #include <tfile.h> +#include <tbytevector.h> +#include <tpropertymap.h> #include <tdebug.h> #include "id3v2tag.h" @@ -31,60 +35,104 @@ #include "id3v2extendedheader.h" #include "id3v2footer.h" #include "id3v2synchdata.h" - #include "id3v1genres.h" #include "frames/textidentificationframe.h" #include "frames/commentsframe.h" +#include "frames/urllinkframe.h" +#include "frames/uniquefileidentifierframe.h" +#include "frames/unsynchronizedlyricsframe.h" +#include "frames/unknownframe.h" using namespace TagLib; using namespace ID3v2; +namespace +{ + const ID3v2::Latin1StringHandler defaultStringHandler; + const ID3v2::Latin1StringHandler *stringHandler = &defaultStringHandler; + + const long MinPaddingSize = 1024; + const long MaxPaddingSize = 1024 * 1024; + + bool contains(const char **a, const ByteVector &v) + { + for(int i = 0; a[i]; i++) + { + if(v == a[i]) + return true; + } + return false; + } +} + class ID3v2::Tag::TagPrivate { public: - TagPrivate() : file(0), tagOffset(-1), extendedHeader(0), footer(0), paddingSize(0) + TagPrivate() : + factory(0), + file(0), + tagOffset(0), + extendedHeader(0), + footer(0) { frameList.setAutoDelete(true); } + ~TagPrivate() { delete extendedHeader; delete footer; } + const FrameFactory *factory; + File *file; long tagOffset; - const FrameFactory *factory; Header header; ExtendedHeader *extendedHeader; Footer *footer; - int paddingSize; - FrameListMap frameListMap; FrameList frameList; }; +//////////////////////////////////////////////////////////////////////////////// +// StringHandler implementation +//////////////////////////////////////////////////////////////////////////////// + +Latin1StringHandler::Latin1StringHandler() +{ +} + +Latin1StringHandler::~Latin1StringHandler() +{ +} + +String Latin1StringHandler::parse(const ByteVector &data) const +{ + return String(data, String::Latin1); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -ID3v2::Tag::Tag() : TagLib::Tag() +ID3v2::Tag::Tag() : + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; d->factory = FrameFactory::instance(); } ID3v2::Tag::Tag(File *file, long tagOffset, const FrameFactory *factory) : - TagLib::Tag() + TagLib::Tag(), + d(new TagPrivate()) { - d = new TagPrivate; - + d->factory = factory; d->file = file; d->tagOffset = tagOffset; - d->factory = factory; read(); } @@ -94,26 +142,25 @@ ID3v2::Tag::~Tag() delete d; } - String ID3v2::Tag::title() const { if(!d->frameListMap["TIT2"].isEmpty()) return d->frameListMap["TIT2"].front()->toString(); - return String::null; + return String(); } String ID3v2::Tag::artist() const { if(!d->frameListMap["TPE1"].isEmpty()) return d->frameListMap["TPE1"].front()->toString(); - return String::null; + return String(); } String ID3v2::Tag::album() const { if(!d->frameListMap["TALB"].isEmpty()) return d->frameListMap["TALB"].front()->toString(); - return String::null; + return String(); } String ID3v2::Tag::comment() const @@ -121,7 +168,7 @@ String ID3v2::Tag::comment() const const FrameList &comments = d->frameListMap["COMM"]; if(comments.isEmpty()) - return String::null; + return String(); for(FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it) { @@ -143,7 +190,7 @@ String ID3v2::Tag::genre() const if(d->frameListMap["TCON"].isEmpty() || !dynamic_cast<TextIdentificationFrame *>(d->frameListMap["TCON"].front())) { - return String::null; + return String(); } // ID3v2.4 lists genres as the fields in its frames field list. If the field @@ -177,54 +224,20 @@ String ID3v2::Tag::genre() const return genres.toString(); } -TagLib::uint ID3v2::Tag::year() const +unsigned int ID3v2::Tag::year() const { if(!d->frameListMap["TDRC"].isEmpty()) return d->frameListMap["TDRC"].front()->toString().substr(0, 4).toInt(); return 0; } -TagLib::uint ID3v2::Tag::track() const +unsigned int ID3v2::Tag::track() const { if(!d->frameListMap["TRCK"].isEmpty()) return d->frameListMap["TRCK"].front()->toString().toInt(); return 0; } -float ID3v2::Tag::rg(const String &type) const -{ - const FrameList &list = d->frameListMap["TXXX"]; - if (!list.isEmpty()) { - for (FrameList::ConstIterator it = list.begin(); it != list.end(); ++it) { - UserTextIdentificationFrame const* frame = static_cast<UserTextIdentificationFrame *>(*it); - if (!frame->description().isNull() && frame->description() == type) { - return frame->toString().toFloat(); - } - } - } - return 0; -} - -float ID3v2::Tag::rgAlbumGain() const -{ - return rg("replaygain_album_gain"); -} - -float ID3v2::Tag::rgAlbumPeak() const -{ - return rg("replaygain_album_peak"); -} - -float ID3v2::Tag::rgTrackGain() const -{ - return rg("replaygain_track_gain"); -} - -float ID3v2::Tag::rgTrackPeak() const -{ - return rg("replaygain_track_peak"); -} - void ID3v2::Tag::setTitle(const String &s) { setTextFrame("TIT2", s); @@ -247,13 +260,24 @@ void ID3v2::Tag::setComment(const String &s) return; } - if(!d->frameListMap["COMM"].isEmpty()) - d->frameListMap["COMM"].front()->setText(s); - else { - CommentsFrame *f = new CommentsFrame(d->factory->defaultTextEncoding()); - addFrame(f); - f->setText(s); + const FrameList &comments = d->frameListMap["COMM"]; + + if(!comments.isEmpty()) { + for(FrameList::ConstIterator it = comments.begin(); it != comments.end(); ++it) { + CommentsFrame *frame = dynamic_cast<CommentsFrame *>(*it); + if(frame && frame->description().isEmpty()) { + (*it)->setText(s); + return; + } + } + + comments.front()->setText(s); + return; } + + CommentsFrame *f = new CommentsFrame(d->factory->defaultTextEncoding()); + addFrame(f); + f->setText(s); } void ID3v2::Tag::setGenre(const String &s) @@ -282,70 +306,24 @@ void ID3v2::Tag::setGenre(const String &s) #endif } -void ID3v2::Tag::setYear(uint i) +void ID3v2::Tag::setYear(unsigned int i) { - if(i <= 0) { + if(i == 0) { removeFrames("TDRC"); return; } setTextFrame("TDRC", String::number(i)); } -void ID3v2::Tag::setTrack(uint i) +void ID3v2::Tag::setTrack(unsigned int i) { - if(i <= 0) { + if(i == 0) { removeFrames("TRCK"); return; } setTextFrame("TRCK", String::number(i)); } -void ID3v2::Tag::setRG(const String &type, float f, bool peak) -{ - bool createdFrame = false; - UserTextIdentificationFrame * frame = NULL; - FrameList &list = d->frameListMap["TXXX"]; - for (FrameList::Iterator it = list.begin(); it != list.end(); ++it) { - if (static_cast<UserTextIdentificationFrame *>(*it)->description() == type) { - frame = static_cast<UserTextIdentificationFrame*>(*it); - break; - } - } - if (f == 0) { - if (frame) - removeFrame(frame); - return; - } - if (frame == NULL) { - frame = new UserTextIdentificationFrame; - frame->setDescription(type); - createdFrame = true; - } - frame->setText(String::number(f) + (peak ? "" : " dB")); - if (createdFrame) - addFrame(frame); -} - -void ID3v2::Tag::setRGAlbumGain(float f) -{ - setRG("replaygain_album_gain", f, false); -} - -void ID3v2::Tag::setRGAlbumPeak(float f) -{ - setRG("replaygain_album_peak", f, true); -} - -void ID3v2::Tag::setRGTrackGain(float f) -{ - setRG("replaygain_track_gain", f, false); -} - -void ID3v2::Tag::setRGTrackPeak(float f) -{ - setRG("replaygain_track_peak", f, true); -} - bool ID3v2::Tag::isEmpty() const { return d->frameList.isEmpty(); @@ -404,51 +382,341 @@ void ID3v2::Tag::removeFrame(Frame *frame, bool del) void ID3v2::Tag::removeFrames(const ByteVector &id) { - FrameList l = d->frameListMap[id]; - for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) - removeFrame(*it, true); + FrameList l = d->frameListMap[id]; + for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it) + removeFrame(*it, true); +} + +PropertyMap ID3v2::Tag::properties() const +{ + PropertyMap properties; + for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) { + PropertyMap props = (*it)->asProperties(); + properties.merge(props); + } + return properties; +} + +void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties) +{ + for(StringList::ConstIterator it = properties.begin(); it != properties.end(); ++it){ + if(it->startsWith("UNKNOWN/")) { + String frameID = it->substr(String("UNKNOWN/").size()); + if(frameID.size() != 4) + continue; // invalid specification + ByteVector id = frameID.data(String::Latin1); + // delete all unknown frames of given type + FrameList l = frameList(id); + for(FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++) + if (dynamic_cast<const UnknownFrame *>(*fit) != 0) + removeFrame(*fit); + } + else if(it->size() == 4){ + ByteVector id = it->data(String::Latin1); + removeFrames(id); + } + else { + ByteVector id = it->substr(0,4).data(String::Latin1); + if(it->size() <= 5) + continue; // invalid specification + String description = it->substr(5); + Frame *frame = 0; + if(id == "TXXX") + frame = UserTextIdentificationFrame::find(this, description); + else if(id == "WXXX") + frame = UserUrlLinkFrame::find(this, description); + else if(id == "COMM") + frame = CommentsFrame::findByDescription(this, description); + else if(id == "USLT") + frame = UnsynchronizedLyricsFrame::findByDescription(this, description); + else if(id == "UFID") + frame = UniqueFileIdentifierFrame::findByOwner(this, description); + if(frame) + removeFrame(frame); + } + } +} + +PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps) +{ + FrameList framesToDelete; + // we split up the PropertyMap into the "normal" keys and the "complicated" ones, + // which are those according to TIPL or TMCL frames. + PropertyMap properties; + PropertyMap tiplProperties; + PropertyMap tmclProperties; + Frame::splitProperties(origProps, properties, tiplProperties, tmclProperties); + for(FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it){ + for(FrameList::ConstIterator lit = it->second.begin(); lit != it->second.end(); ++lit){ + PropertyMap frameProperties = (*lit)->asProperties(); + if(it->first == "TIPL") { + if (tiplProperties != frameProperties) + framesToDelete.append(*lit); + else + tiplProperties.erase(frameProperties); + } else if(it->first == "TMCL") { + if (tmclProperties != frameProperties) + framesToDelete.append(*lit); + else + tmclProperties.erase(frameProperties); + } else if(!properties.contains(frameProperties)) + framesToDelete.append(*lit); + else + properties.erase(frameProperties); + } + } + for(FrameList::ConstIterator it = framesToDelete.begin(); it != framesToDelete.end(); ++it) + removeFrame(*it); + + // now create remaining frames: + // start with the involved people list (TIPL) + if(!tiplProperties.isEmpty()) + addFrame(TextIdentificationFrame::createTIPLFrame(tiplProperties)); + // proceed with the musician credit list (TMCL) + if(!tmclProperties.isEmpty()) + addFrame(TextIdentificationFrame::createTMCLFrame(tmclProperties)); + // now create the "one key per frame" frames + for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it) + addFrame(Frame::createTextualFrame(it->first, it->second)); + return PropertyMap(); // ID3 implements the complete PropertyMap interface, so an empty map is returned } ByteVector ID3v2::Tag::render() const +{ + return render(ID3v2::v4); +} + +void ID3v2::Tag::downgradeFrames(FrameList *frames, FrameList *newFrames) const +{ +#ifdef NO_ITUNES_HACKS + static const char *unsupportedFrames[] = { + "ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG", + "TMOO", "TPRO", "TSOA", "TSOT", "TSST", "TSOP", 0 + }; +#else + // iTunes writes and reads TSOA, TSOT, TSOP to ID3v2.3. + static const char *unsupportedFrames[] = { + "ASPI", "EQU2", "RVA2", "SEEK", "SIGN", "TDRL", "TDTG", + "TMOO", "TPRO", "TSST", 0 + }; +#endif + ID3v2::TextIdentificationFrame *frameTDOR = 0; + ID3v2::TextIdentificationFrame *frameTDRC = 0; + ID3v2::TextIdentificationFrame *frameTIPL = 0; + ID3v2::TextIdentificationFrame *frameTMCL = 0; + ID3v2::TextIdentificationFrame *frameTCON = 0; + + for(FrameList::ConstIterator it = d->frameList.begin(); it != d->frameList.end(); it++) { + ID3v2::Frame *frame = *it; + ByteVector frameID = frame->header()->frameID(); + + if(contains(unsupportedFrames, frameID)) + { + debug("A frame that is not supported in ID3v2.3 \'" + String(frameID) + + "\' has been discarded"); + continue; + } + + if(frameID == "TDOR") + frameTDOR = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame); + else if(frameID == "TDRC") + frameTDRC = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame); + else if(frameID == "TIPL") + frameTIPL = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame); + else if(frameID == "TMCL") + frameTMCL = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame); + else if(frame && frameID == "TCON") + frameTCON = dynamic_cast<ID3v2::TextIdentificationFrame *>(frame); + else + frames->append(frame); + } + + if(frameTDOR) { + String content = frameTDOR->toString(); + + if(content.size() >= 4) { + ID3v2::TextIdentificationFrame *frameTORY = + new ID3v2::TextIdentificationFrame("TORY", String::Latin1); + frameTORY->setText(content.substr(0, 4)); + frames->append(frameTORY); + newFrames->append(frameTORY); + } + } + + if(frameTDRC) { + String content = frameTDRC->toString(); + if(content.size() >= 4) { + ID3v2::TextIdentificationFrame *frameTYER = + new ID3v2::TextIdentificationFrame("TYER", String::Latin1); + frameTYER->setText(content.substr(0, 4)); + frames->append(frameTYER); + newFrames->append(frameTYER); + if(content.size() >= 10 && content[4] == '-' && content[7] == '-') { + ID3v2::TextIdentificationFrame *frameTDAT = + new ID3v2::TextIdentificationFrame("TDAT", String::Latin1); + frameTDAT->setText(content.substr(8, 2) + content.substr(5, 2)); + frames->append(frameTDAT); + newFrames->append(frameTDAT); + if(content.size() >= 16 && content[10] == 'T' && content[13] == ':') { + ID3v2::TextIdentificationFrame *frameTIME = + new ID3v2::TextIdentificationFrame("TIME", String::Latin1); + frameTIME->setText(content.substr(11, 2) + content.substr(14, 2)); + frames->append(frameTIME); + newFrames->append(frameTIME); + } + } + } + } + + if(frameTIPL || frameTMCL) { + ID3v2::TextIdentificationFrame *frameIPLS = + new ID3v2::TextIdentificationFrame("IPLS", String::Latin1); + + StringList people; + + if(frameTMCL) { + StringList v24People = frameTMCL->fieldList(); + for(unsigned int i = 0; i + 1 < v24People.size(); i += 2) { + people.append(v24People[i]); + people.append(v24People[i+1]); + } + } + if(frameTIPL) { + StringList v24People = frameTIPL->fieldList(); + for(unsigned int i = 0; i + 1 < v24People.size(); i += 2) { + people.append(v24People[i]); + people.append(v24People[i+1]); + } + } + + frameIPLS->setText(people); + frames->append(frameIPLS); + newFrames->append(frameIPLS); + } + + if(frameTCON) { + StringList genres = frameTCON->fieldList(); + String combined; + String genreText; + const bool hasMultipleGenres = genres.size() > 1; + + // If there are multiple genres, add them as multiple references to ID3v1 + // genres if such a reference exists. The first genre for which no ID3v1 + // genre number exists can be finally added as a refinement. + for(StringList::ConstIterator it = genres.begin(); it != genres.end(); ++it) { + bool ok = false; + int number = it->toInt(&ok); + if((ok && number >= 0 && number <= 255) || *it == "RX" || *it == "CR") + combined += '(' + *it + ')'; + else if(hasMultipleGenres && (number = ID3v1::genreIndex(*it)) != 255) + combined += '(' + String::number(number) + ')'; + else if(genreText.isEmpty()) + genreText = *it; + } + if(!genreText.isEmpty()) + combined += genreText; + + frameTCON = new ID3v2::TextIdentificationFrame("TCON", String::Latin1); + frameTCON->setText(combined); + frames->append(frameTCON); + newFrames->append(frameTCON); + } +} + +ByteVector ID3v2::Tag::render(int version) const +{ + return render(version == 3 ? v3 : v4); +} + +ByteVector ID3v2::Tag::render(Version version) const { // We need to render the "tag data" first so that we have to correct size to // render in the tag's header. The "tag data" -- everything that is included // in ID3v2::Header::tagSize() -- includes the extended header, frames and // padding, but does not include the tag's header or footer. - ByteVector tagData; - // TODO: Render the extended header. + // Downgrade the frames that ID3v2.3 doesn't support. + + FrameList newFrames; + newFrames.setAutoDelete(true); + + FrameList frameList; + if(version == v4) { + frameList = d->frameList; + } + else { + downgradeFrames(&frameList, &newFrames); + } + + // Reserve a 10-byte blank space for an ID3v2 tag header. + + ByteVector tagData(Header::size(), '\0'); + // Loop through the frames rendering them and adding them to the tagData. - for(FrameList::Iterator it = d->frameList.begin(); it != d->frameList.end(); it++) { + for(FrameList::ConstIterator it = frameList.begin(); it != frameList.end(); it++) { + (*it)->header()->setVersion(version == v3 ? 3 : 4); if((*it)->header()->frameID().size() != 4) { - debug("A frame of unsupported or unknown type \'" + debug("An ID3v2 frame of unsupported or unknown type \'" + String((*it)->header()->frameID()) + "\' has been discarded"); continue; } - if(!(*it)->header()->tagAlterPreservation()) - tagData.append((*it)->render()); + if(!(*it)->header()->tagAlterPreservation()) { + const ByteVector frameData = (*it)->render(); + if(frameData.size() == Frame::headerSize((*it)->header()->version())) { + debug("An empty ID3v2 frame \'" + + String((*it)->header()->frameID()) + "\' has been discarded"); + continue; + } + tagData.append(frameData); + } } // Compute the amount of padding, and append that to tagData. - uint paddingSize = 0; - uint originalSize = d->header.tagSize(); + long originalSize = d->header.tagSize(); + long paddingSize = originalSize - (tagData.size() - Header::size()); - if(tagData.size() < originalSize) - paddingSize = originalSize - tagData.size(); - else - paddingSize = 1024; + if(paddingSize <= 0) { + paddingSize = MinPaddingSize; + } + else { + // Padding won't increase beyond 1% of the file size or 1MB. - tagData.append(ByteVector(paddingSize, char(0))); + long threshold = d->file ? d->file->length() / 100 : 0; + threshold = std::max(threshold, MinPaddingSize); + threshold = std::min(threshold, MaxPaddingSize); - // Set the tag size. - d->header.setTagSize(tagData.size()); + if(paddingSize > threshold) + paddingSize = MinPaddingSize; + } + + tagData.resize(static_cast<unsigned int>(tagData.size() + paddingSize), '\0'); + + // Set the version and data size. + d->header.setMajorVersion(version); + d->header.setTagSize(tagData.size() - Header::size()); // TODO: This should eventually include d->footer->render(). - return d->header.render() + tagData; + const ByteVector headerData = d->header.render(); + std::copy(headerData.begin(), headerData.end(), tagData.begin()); + + return tagData; +} + +Latin1StringHandler const *ID3v2::Tag::latin1StringHandler() +{ + return stringHandler; +} + +void ID3v2::Tag::setLatin1StringHandler(const Latin1StringHandler *handler) +{ + if(handler) + stringHandler = handler; + else + stringHandler = &defaultStringHandler; } //////////////////////////////////////////////////////////////////////////////// @@ -457,18 +725,43 @@ ByteVector ID3v2::Tag::render() const void ID3v2::Tag::read() { - if(d->file && d->file->isOpen()) { + if(!d->file) + return; - d->file->seek(d->tagOffset); - d->header.setData(d->file->readBlock(Header::size())); + if(!d->file->isOpen()) + return; - // if the tag size is 0, then this is an invalid tag (tags must contain at - // least one frame) + d->file->seek(d->tagOffset); + d->header.setData(d->file->readBlock(Header::size())); - if(d->header.tagSize() == 0) - return; + // If the tag size is 0, then this is an invalid tag (tags must contain at + // least one frame) + if(d->header.tagSize() != 0) parse(d->file->readBlock(d->header.tagSize())); + + // Look for duplicate ID3v2 tags and treat them as an extra blank of this one. + // It leads to overwriting them with zero when saving the tag. + + // This is a workaround for some faulty files that have duplicate ID3v2 tags. + // Unfortunately, TagLib itself may write such duplicate tags until v1.10. + + unsigned int extraSize = 0; + + while(true) { + + d->file->seek(d->tagOffset + d->header.completeTagSize() + extraSize); + + const ByteVector data = d->file->readBlock(Header::size()); + if(data.size() < Header::size() || !data.startsWith(Header::fileIdentifier())) + break; + + extraSize += Header(data).completeTagSize(); + } + + if(extraSize != 0) { + debug("ID3v2::Tag::read() - Duplicate ID3v2 tags found."); + d->header.setTagSize(d->header.tagSize() + extraSize); } } @@ -479,14 +772,14 @@ void ID3v2::Tag::parse(const ByteVector &origData) if(d->header.unsynchronisation() && d->header.majorVersion() <= 3) data = SynchData::decode(data); - uint frameDataPosition = 0; - uint frameDataLength = data.size(); + unsigned int frameDataPosition = 0; + unsigned int frameDataLength = data.size(); // check for extended header if(d->header.extendedHeader()) { if(!d->extendedHeader) - d->extendedHeader = new ExtendedHeader; + d->extendedHeader = new ExtendedHeader(); d->extendedHeader->setData(data); if(d->extendedHeader->size() <= data.size()) { frameDataPosition += d->extendedHeader->size(); @@ -512,11 +805,11 @@ void ID3v2::Tag::parse(const ByteVector &origData) // portion of the frame data. if(data.at(frameDataPosition) == 0) { - if(d->header.footerPresent()) + if(d->header.footerPresent()) { debug("Padding *and* a footer found. This is not allowed by the spec."); + } - d->paddingSize = frameDataLength - frameDataPosition; - return; + break; } Frame *frame = d->factory->createFrame(data.mid(frameDataPosition), @@ -535,6 +828,8 @@ void ID3v2::Tag::parse(const ByteVector &origData) frameDataPosition += frame->size() + Frame::headerSize(d->header.majorVersion()); addFrame(frame); } + + d->factory->rebuildAggregateFrames(this); } void ID3v2::Tag::setTextFrame(const ByteVector &id, const String &value) diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h index b913a08b0..74d1df1e7 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/id3v2/id3v2tag.h @@ -33,21 +33,13 @@ #include "tmap.h" #include "taglib_export.h" +#include "id3v2.h" #include "id3v2framefactory.h" namespace TagLib { class File; - //! An ID3v2 implementation - - /*! - * This is a relatively complete and flexible framework for working with ID3v2 - * tags. - * - * \see ID3v2::Tag - */ - namespace ID3v2 { class Header; @@ -57,6 +49,36 @@ namespace TagLib { typedef List<Frame *> FrameList; typedef Map<ByteVector, FrameList> FrameListMap; + //! An abstraction for the ISO-8859-1 string to data encoding in ID3v2 tags. + + /*! + * ID3v2 tag can store strings in ISO-8859-1 (Latin1), and TagLib only + * supports genuine ISO-8859-1 by default. However, in practice, non + * ISO-8859-1 encodings are often used instead of ISO-8859-1, such as + * Windows-1252 for western languages, Shift_JIS for Japanese and so on. + * + * Here is an option to read such tags by subclassing this class, + * reimplementing parse() and setting your reimplementation as the default + * with ID3v2::Tag::setStringHandler(). + * + * \note Writing non-ISO-8859-1 tags is not implemented intentionally. + * Use UTF-16 or UTF-8 instead. + * + * \see ID3v2::Tag::setStringHandler() + */ + class TAGLIB_EXPORT Latin1StringHandler + { + public: + Latin1StringHandler(); + virtual ~Latin1StringHandler(); + + /*! + * Decode a string from \a data. The default implementation assumes that + * \a data is an ISO-8859-1 (Latin1) character array. + */ + virtual String parse(const ByteVector &data) const; + }; + //! The main class in the ID3v2 implementation /*! @@ -68,7 +90,7 @@ namespace TagLib { * split into data components. * * ID3v2 tags have several parts, TagLib attempts to provide an interface - * for them all. header(), footer() and extendedHeader() corespond to those + * for them all. header(), footer() and extendedHeader() correspond to those * data structures in the ID3v2 standard and the APIs for the classes that * they return attempt to reflect this. * @@ -85,7 +107,7 @@ namespace TagLib { * class. * * read() and parse() pass binary data to the other ID3v2 class structures, - * they do not handle parsing of flags or fields, for instace. Those are + * they do not handle parsing of flags or fields, for instance. Those are * handled by similar functions within those classes. * * \note All pointers to data structures within the tag will become invalid @@ -96,7 +118,7 @@ namespace TagLib { * rather long, but if you're planning on messing with this class and others * that deal with the details of ID3v2 (rather than the nice, safe, abstract * TagLib::Tag and friends), it's worth your time to familiarize yourself - * with said spec (which is distrubuted with the TagLib sources). TagLib + * with said spec (which is distributed with the TagLib sources). TagLib * tries to do most of the work, but with a little luck, you can still * convince it to generate invalid ID3v2 tags. The APIs for ID3v2 assume a * working knowledge of ID3v2 structure. You're been warned. @@ -120,7 +142,7 @@ namespace TagLib { * \note You should be able to ignore the \a factory parameter in almost * all situations. You would want to specify your own FrameFactory * subclass in the case that you are extending TagLib to support additional - * frame types, which would be incorperated into your factory. + * frame types, which would be incorporated into your factory. * * \see FrameFactory */ @@ -139,28 +161,16 @@ namespace TagLib { virtual String album() const; virtual String comment() const; virtual String genre() const; - virtual uint year() const; - virtual uint track() const; + virtual unsigned int year() const; + virtual unsigned int track() const; - float rg(const String &type) const; - virtual float rgAlbumGain() const; - virtual float rgAlbumPeak() const; - virtual float rgTrackGain() const; - virtual float rgTrackPeak() const; - virtual void setTitle(const String &s); virtual void setArtist(const String &s); virtual void setAlbum(const String &s); virtual void setComment(const String &s); virtual void setGenre(const String &s); - virtual void setYear(uint i); - virtual void setTrack(uint i); - - void setRG(const String &type, float f, bool peak); - virtual void setRGAlbumGain(float f); - virtual void setRGAlbumPeak(float f); - virtual void setRGTrackGain(float f); - virtual void setRGTrackPeak(float f); + virtual void setYear(unsigned int i); + virtual void setTrack(unsigned int i); virtual bool isEmpty() const; @@ -183,7 +193,7 @@ namespace TagLib { * prone to change my mind, so this gets to stay around until near a * release. */ - Footer *footer() const; + TAGLIB_DEPRECATED Footer *footer() const; /*! * Returns a reference to the frame list map. This is an FrameListMap of @@ -272,11 +282,95 @@ namespace TagLib { */ void removeFrames(const ByteVector &id); + /*! + * Implements the unified property interface -- export function. + * This function does some work to translate the hard-specified ID3v2 + * frame types into a free-form string-to-stringlist PropertyMap: + * - if ID3v2 frame ID is known by Frame::frameIDToKey(), the returned + * key is used + * - if the frame ID is "TXXX" (user text frame), the description() is + * used as key + * - if the frame ID is "WXXX" (user url frame), + * - if the description is empty or "URL", the key "URL" is used + * - otherwise, the key "URL:<description>" is used; + * - if the frame ID is "COMM" (comments frame), + * - if the description is empty or "COMMENT", the key "COMMENT" + * is used + * - otherwise, the key "COMMENT:<description>" is used; + * - if the frame ID is "USLT" (unsynchronized lyrics), + * - if the description is empty or "LYRICS", the key "LYRICS" is used + * - otherwise, the key "LYRICS:<description>" is used; + * - if the frame ID is "TIPL" (involved peoples list), and if all the + * roles defined in the frame are known in TextIdentificationFrame::involvedPeopleMap(), + * then "<role>=<name>" will be contained in the returned object for each + * - if the frame ID is "TMCL" (musician credit list), then + * "PERFORMER:<instrument>=<name>" will be contained in the returned + * PropertyMap for each defined musician + * In any other case, the unsupportedData() of the returned object will contain + * the frame's ID and, in case of a frame ID which is allowed to appear more than + * once, the description, separated by a "/". + * + */ + PropertyMap properties() const; + + /*! + * Removes unsupported frames given by \a properties. The elements of + * \a properties must be taken from properties().unsupportedData(); they + * are of one of the following forms: + * - a four-character frame ID, if the ID3 specification allows only one + * frame with that ID (thus, the frame is uniquely determined) + * - frameID + "/" + description(), when the ID is one of "TXXX", "WXXX", + * "COMM", or "USLT", + * - "UNKNOWN/" + frameID, for frames that could not be parsed by TagLib. + * In that case, *all* unknown frames with the given ID will be removed. + */ + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + * See the comments in properties(). + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Render the tag back to binary data, suitable to be written to disk. */ ByteVector render() const; + /*! + * \deprecated + */ + TAGLIB_DEPRECATED ByteVector render(int version) const; + + /*! + * Render the tag back to binary data, suitable to be written to disk. + * + * The \a version parameter specifies whether ID3v2.4 (default) or ID3v2.3 + * should be used. + */ + ByteVector render(Version version) const; + + /*! + * Gets the current string handler that decides how the "Latin-1" data + * will be converted to and from binary data. + * + * \see Latin1StringHandler + */ + static Latin1StringHandler const *latin1StringHandler(); + + /*! + * Sets the string handler that decides how the "Latin-1" data will be + * converted to and from binary data. + * If the parameter \a handler is null, the previous handler is + * released and default ISO-8859-1 handler is restored. + * + * \note The caller is responsible for deleting the previous handler + * as needed after it is released. + * + * \see Latin1StringHandler + */ + static void setLatin1StringHandler(const Latin1StringHandler *handler); + protected: /*! * Reads data from the file specified in the constructor. It does basic @@ -298,6 +392,11 @@ namespace TagLib { */ void setTextFrame(const ByteVector &id, const String &value); + /*! + * Downgrade frames from ID3v2.4 (used internally and by default) to ID3v2.3. + */ + void downgradeFrames(FrameList *existingFrames, FrameList *newFrames) const; + private: Tag(const Tag &); Tag &operator=(const Tag &); diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/mpegfile.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/mpegfile.cpp index 2fc1b3807..a2ce02a7c 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/mpegfile.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/mpegfile.cpp @@ -24,6 +24,7 @@ ***************************************************************************/ #include <tagunion.h> +#include <tagutils.h> #include <id3v2tag.h> #include <id3v2header.h> #include <id3v1tag.h> @@ -31,10 +32,10 @@ #include <apetag.h> #include <tdebug.h> -#include <bitset> - #include "mpegfile.h" #include "mpegheader.h" +#include "mpegutils.h" +#include "tpropertymap.h" using namespace TagLib; @@ -46,21 +47,14 @@ namespace class MPEG::File::FilePrivate { public: - FilePrivate(ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) : + FilePrivate(const ID3v2::FrameFactory *frameFactory = ID3v2::FrameFactory::instance()) : ID3v2FrameFactory(frameFactory), ID3v2Location(-1), ID3v2OriginalSize(0), APELocation(-1), - APEFooterLocation(-1), APEOriginalSize(0), ID3v1Location(-1), - hasID3v2(false), - hasID3v1(false), - hasAPE(false), - properties(0) - { - - } + properties(0) {} ~FilePrivate() { @@ -70,47 +64,98 @@ public: const ID3v2::FrameFactory *ID3v2FrameFactory; long ID3v2Location; - uint ID3v2OriginalSize; + long ID3v2OriginalSize; long APELocation; - long APEFooterLocation; - uint APEOriginalSize; + long APEOriginalSize; long ID3v1Location; TagUnion tag; - // These indicate whether the file *on disk* has these tags, not if - // this data structure does. This is used in computing offsets. - - bool hasID3v2; - bool hasID3v1; - bool hasAPE; - Properties *properties; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +namespace +{ + // Dummy file class to make a stream work with MPEG::Header. + + class AdapterFile : public TagLib::File + { + public: + AdapterFile(IOStream *stream) : File(stream) {} + + Tag *tag() const { return 0; } + AudioProperties *audioProperties() const { return 0; } + bool save() { return false; } + }; +} + +bool MPEG::File::isSupported(IOStream *stream) +{ + if(!stream || !stream->isOpen()) + return false; + + // An MPEG file has MPEG frame headers. An ID3v2 tag may precede. + + // MPEG frame headers are really confusing with irrelevant binary data. + // So we check if a frame header is really valid. + + long headerOffset; + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), true, &headerOffset); + + if(buffer.isEmpty()) + return false; + + const long originalPosition = stream->tell(); + AdapterFile file(stream); + + for(unsigned int i = 0; i < buffer.size() - 1; ++i) { + if(isFrameSync(buffer, i)) { + const Header header(&file, headerOffset + i, true); + if(header.isValid()) { + stream->seek(originalPosition); + return true; + } + } + } + + stream->seek(originalPosition); + return false; +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -MPEG::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(file) +MPEG::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } MPEG::File::File(FileName file, ID3v2::FrameFactory *frameFactory, - bool readProperties, Properties::ReadStyle propertiesStyle) : - TagLib::File(file) + bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate(frameFactory)) { - d = new FilePrivate(frameFactory); - if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); +} + +MPEG::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, + bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate(frameFactory)) +{ + if(isOpen()) + read(readProperties); } MPEG::File::~File() @@ -123,6 +168,26 @@ TagLib::Tag *MPEG::File::tag() const return &d->tag; } +PropertyMap MPEG::File::properties() const +{ + return d->tag.properties(); +} + +void MPEG::File::removeUnsupportedProperties(const StringList &properties) +{ + d->tag.removeUnsupportedProperties(properties); +} + +PropertyMap MPEG::File::setProperties(const PropertyMap &properties) +{ + // update ID3v1 tag if it exists, but ignore the return value + + if(ID3v1Tag()) + ID3v1Tag()->setProperties(properties); + + return ID3v2Tag(true)->setProperties(properties); +} + MPEG::Properties *MPEG::File::audioProperties() const { return d->properties; @@ -135,108 +200,137 @@ bool MPEG::File::save() bool MPEG::File::save(int tags) { - return save(tags, true); + return save(tags, StripOthers); } bool MPEG::File::save(int tags, bool stripOthers) { - if(tags == NoTags && stripOthers) - return strip(AllTags); + return save(tags, stripOthers ? StripOthers : StripNone, ID3v2::v4); +} - if(!ID3v2Tag() && !ID3v1Tag() && !APETag()) { +bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version) +{ + return save(tags, + stripOthers ? StripOthers : StripNone, + id3v2Version == 3 ? ID3v2::v3 : ID3v2::v4); +} - if((d->hasID3v1 || d->hasID3v2 || d->hasAPE) && stripOthers) - return strip(AllTags); - - return true; - } +bool MPEG::File::save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags) +{ + return save(tags, + stripOthers ? StripOthers : StripNone, + id3v2Version == 3 ? ID3v2::v3 : ID3v2::v4, + duplicateTags ? Duplicate : DoNotDuplicate); +} +bool MPEG::File::save(int tags, StripTags strip, ID3v2::Version version, DuplicateTags duplicate) +{ if(readOnly()) { debug("MPEG::File::save() -- File is read only."); return false; } - // Create the tags if we've been asked to. Copy the values from the tag that - // does exist into the new tag. + // Create the tags if we've been asked to. - if((tags & ID3v2) && ID3v1Tag()) - Tag::duplicate(ID3v1Tag(), ID3v2Tag(true), false); + if(duplicate == Duplicate) { - if((tags & ID3v1) && d->tag[ID3v2Index]) - Tag::duplicate(ID3v2Tag(), ID3v1Tag(true), false); + // Copy the values from the tag that does exist into the new tag, + // except if the existing tag is to be stripped. - bool success = true; + if((tags & ID3v2) && ID3v1Tag() && !(strip == StripOthers && !(tags & ID3v1))) + Tag::duplicate(ID3v1Tag(), ID3v2Tag(true), false); + + if((tags & ID3v1) && d->tag[ID3v2Index] && !(strip == StripOthers && !(tags & ID3v2))) + Tag::duplicate(ID3v2Tag(), ID3v1Tag(true), false); + } + + // Remove all the tags not going to be saved. + + if(strip == StripOthers) + File::strip(~tags, false); if(ID3v2 & tags) { if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) { - if(!d->hasID3v2) + // ID3v2 tag is not empty. Update the old one or create a new one. + + if(d->ID3v2Location < 0) d->ID3v2Location = 0; - insert(ID3v2Tag()->render(), d->ID3v2Location, d->ID3v2OriginalSize); + const ByteVector data = ID3v2Tag()->render(version); + insert(data, d->ID3v2Location, d->ID3v2OriginalSize); - d->hasID3v2 = true; + if(d->APELocation >= 0) + d->APELocation += (static_cast<long>(data.size()) - d->ID3v2OriginalSize); - // v1 tag location has changed, update if it exists + if(d->ID3v1Location >= 0) + d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize); - if(ID3v1Tag()) - d->ID3v1Location = findID3v1(); - - // APE tag location has changed, update if it exists - - if(APETag()) - findAPE(); + d->ID3v2OriginalSize = data.size(); + } + else { + + // ID3v2 tag is empty. Remove the old one. + + File::strip(ID3v2, false); } - else if(stripOthers) - success = strip(ID3v2, false) && success; } - else if(d->hasID3v2 && stripOthers) - success = strip(ID3v2) && success; if(ID3v1 & tags) { + if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) { - int offset = d->hasID3v1 ? -128 : 0; - seek(offset, End); - writeBlock(ID3v1Tag()->render()); - d->hasID3v1 = true; - d->ID3v1Location = findID3v1(); - } - else if(stripOthers) - success = strip(ID3v1) && success; - } - else if(d->hasID3v1 && stripOthers) - success = strip(ID3v1, false) && success; - // Dont save an APE-tag unless one has been created + // ID3v1 tag is not empty. Update the old one or create a new one. - if((APE & tags) && APETag()) { - if(d->hasAPE) - insert(APETag()->render(), d->APELocation, d->APEOriginalSize); - else { - if(d->hasID3v1) { - insert(APETag()->render(), d->ID3v1Location, 0); - d->APEOriginalSize = APETag()->footer()->completeTagSize(); - d->hasAPE = true; - d->APELocation = d->ID3v1Location; - d->ID3v1Location += d->APEOriginalSize; + if(d->ID3v1Location >= 0) { + seek(d->ID3v1Location); } else { seek(0, End); - d->APELocation = tell(); - d->APEFooterLocation = d->APELocation - + d->tag.access<APE::Tag>(APEIndex, false)->footer()->completeTagSize() - - APE::Footer::size(); - writeBlock(APETag()->render()); - d->APEOriginalSize = APETag()->footer()->completeTagSize(); - d->hasAPE = true; + d->ID3v1Location = tell(); } + + writeBlock(ID3v1Tag()->render()); + } + else { + + // ID3v1 tag is empty. Remove the old one. + + File::strip(ID3v1, false); } } - else if(d->hasAPE && stripOthers) - success = strip(APE, false) && success; - return success; + if(APE & tags) { + + if(APETag() && !APETag()->isEmpty()) { + + // APE tag is not empty. Update the old one or create a new one. + + if(d->APELocation < 0) { + if(d->ID3v1Location >= 0) + d->APELocation = d->ID3v1Location; + else + d->APELocation = length(); + } + + const ByteVector data = APETag()->render(); + insert(data, d->APELocation, d->APEOriginalSize); + + if(d->ID3v1Location >= 0) + d->ID3v1Location += (static_cast<long>(data.size()) - d->APEOriginalSize); + + d->APEOriginalSize = data.size(); + } + else { + + // APE tag is empty. Remove the old one. + + File::strip(APE, false); + } + } + + return true; } ID3v2::Tag *MPEG::File::ID3v2Tag(bool create) @@ -266,44 +360,39 @@ bool MPEG::File::strip(int tags, bool freeMemory) return false; } - if((tags & ID3v2) && d->hasID3v2) { + if((tags & ID3v2) && d->ID3v2Location >= 0) { removeBlock(d->ID3v2Location, d->ID3v2OriginalSize); + + if(d->APELocation >= 0) + d->APELocation -= d->ID3v2OriginalSize; + + if(d->ID3v1Location >= 0) + d->ID3v1Location -= d->ID3v2OriginalSize; + d->ID3v2Location = -1; d->ID3v2OriginalSize = 0; - d->hasID3v2 = false; if(freeMemory) d->tag.set(ID3v2Index, 0); - - // v1 tag location has changed, update if it exists - - if(ID3v1Tag()) - d->ID3v1Location = findID3v1(); - - // APE tag location has changed, update if it exists - - if(APETag()) - findAPE(); } - if((tags & ID3v1) && d->hasID3v1) { + if((tags & ID3v1) && d->ID3v1Location >= 0) { truncate(d->ID3v1Location); + d->ID3v1Location = -1; - d->hasID3v1 = false; if(freeMemory) d->tag.set(ID3v1Index, 0); } - if((tags & APE) && d->hasAPE) { + if((tags & APE) && d->APELocation >= 0) { removeBlock(d->APELocation, d->APEOriginalSize); + + if(d->ID3v1Location >= 0) + d->ID3v1Location -= d->APEOriginalSize; + d->APELocation = -1; - d->APEFooterLocation = -1; - d->hasAPE = false; - if(d->hasID3v1) { - if(d->ID3v1Location > d->APELocation) - d->ID3v1Location -= d->APEOriginalSize; - } + d->APEOriginalSize = 0; if(freeMemory) d->tag.set(APEIndex, 0); @@ -319,55 +408,50 @@ void MPEG::File::setID3v2FrameFactory(const ID3v2::FrameFactory *factory) long MPEG::File::nextFrameOffset(long position) { - bool foundLastSyncPattern = false; - - ByteVector buffer; + ByteVector frameSyncBytes(2, '\0'); while(true) { seek(position); - buffer = readBlock(bufferSize()); - - if(buffer.size() <= 0) + const ByteVector buffer = readBlock(bufferSize()); + if(buffer.isEmpty()) return -1; - if(foundLastSyncPattern && secondSynchByte(buffer[0])) - return position - 1; - - for(uint i = 0; i < buffer.size() - 1; i++) { - if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1])) - return position + i; + for(unsigned int i = 0; i < buffer.size(); ++i) { + frameSyncBytes[0] = frameSyncBytes[1]; + frameSyncBytes[1] = buffer[i]; + if(isFrameSync(frameSyncBytes)) { + const Header header(this, position + i - 1, true); + if(header.isValid()) + return position + i - 1; + } } - foundLastSyncPattern = uchar(buffer[buffer.size() - 1]) == 0xff; - position += buffer.size(); + position += bufferSize(); } } long MPEG::File::previousFrameOffset(long position) { - bool foundFirstSyncPattern = false; - ByteVector buffer; + ByteVector frameSyncBytes(2, '\0'); - while (position > 0) { - long size = ulong(position) < bufferSize() ? position : bufferSize(); - position -= size; + while(position > 0) { + const long bufferLength = std::min<long>(position, bufferSize()); + position -= bufferLength; seek(position); - buffer = readBlock(size); + const ByteVector buffer = readBlock(bufferLength); - if(buffer.size() <= 0) - break; - - if(foundFirstSyncPattern && uchar(buffer[buffer.size() - 1]) == 0xff) - return position + buffer.size() - 1; - - for(int i = buffer.size() - 2; i >= 0; i--) { - if(uchar(buffer[i]) == 0xff && secondSynchByte(buffer[i + 1])) - return position + i; + for(int i = buffer.size() - 1; i >= 0; --i) { + frameSyncBytes[1] = frameSyncBytes[0]; + frameSyncBytes[0] = buffer[i]; + if(isFrameSync(frameSyncBytes)) { + const Header header(this, position + i, true); + if(header.isValid()) + return position + i + header.frameLength(); + } } - - foundFirstSyncPattern = secondSynchByte(buffer[0]); } + return -1; } @@ -375,7 +459,7 @@ long MPEG::File::firstFrameOffset() { long position = 0; - if(ID3v2Tag()) + if(hasID3v2Tag()) position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize(); return nextFrameOffset(position); @@ -383,53 +467,67 @@ long MPEG::File::firstFrameOffset() long MPEG::File::lastFrameOffset() { - return previousFrameOffset(ID3v1Tag() ? d->ID3v1Location - 1 : length()); + long position; + + if(hasAPETag()) + position = d->APELocation - 1; + else if(hasID3v1Tag()) + position = d->ID3v1Location - 1; + else + position = length(); + + return previousFrameOffset(position); +} + +bool MPEG::File::hasID3v1Tag() const +{ + return (d->ID3v1Location >= 0); +} + +bool MPEG::File::hasID3v2Tag() const +{ + return (d->ID3v2Location >= 0); +} + +bool MPEG::File::hasAPETag() const +{ + return (d->APELocation >= 0); } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void MPEG::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void MPEG::File::read(bool readProperties) { // Look for an ID3v2 tag d->ID3v2Location = findID3v2(); if(d->ID3v2Location >= 0) { - d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory)); - d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize(); - - if(ID3v2Tag()->header()->tagSize() <= 0) - d->tag.set(ID3v2Index, 0); - else - d->hasID3v2 = true; } // Look for an ID3v1 tag - d->ID3v1Location = findID3v1(); + d->ID3v1Location = Utils::findID3v1(this); - if(d->ID3v1Location >= 0) { + if(d->ID3v1Location >= 0) d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); - d->hasID3v1 = true; - } // Look for an APE tag - findAPE(); + d->APELocation = Utils::findAPE(this, d->ID3v1Location); if(d->APELocation >= 0) { - - d->tag.set(APEIndex, new APE::Tag(this, d->APEFooterLocation)); + d->tag.set(APEIndex, new APE::Tag(this, d->APELocation)); d->APEOriginalSize = APETag()->footer()->completeTagSize(); - d->hasAPE = true; + d->APELocation = d->APELocation + APE::Footer::size() - d->APEOriginalSize; } if(readProperties) - d->properties = new Properties(this, propertiesStyle); + d->properties = new Properties(this); // Make sure that we have our default tag types available. @@ -439,155 +537,49 @@ void MPEG::File::read(bool readProperties, Properties::ReadStyle propertiesStyle long MPEG::File::findID3v2() { - // This method is based on the contents of TagLib::File::find(), but because - // of some subtlteies -- specifically the need to look for the bit pattern of - // an MPEG sync, it has been modified for use here. + if(!isValid()) + return -1; - if(isValid() && ID3v2::Header::fileIdentifier().size() <= bufferSize()) { + // An ID3v2 tag or MPEG frame is most likely be at the beginning of the file. - // The position in the file that the current buffer starts at. + const ByteVector headerID = ID3v2::Header::fileIdentifier(); - long bufferOffset = 0; - ByteVector buffer; + seek(0); + if(readBlock(headerID.size()) == headerID) + return 0; - // These variables are used to keep track of a partial match that happens at - // the end of a buffer. + const Header firstHeader(this, 0, true); + if(firstHeader.isValid()) + return -1; - int previousPartialMatch = -1; - bool previousPartialSynchMatch = false; + // Look for an ID3v2 tag until reaching the first valid MPEG frame. - // Save the location of the current read pointer. We will restore the - // position using seek() before all returns. + ByteVector frameSyncBytes(2, '\0'); + ByteVector tagHeaderBytes(3, '\0'); + long position = 0; - long originalPosition = tell(); + while(true) { + seek(position); + const ByteVector buffer = readBlock(bufferSize()); + if(buffer.isEmpty()) + return -1; - // Start the search at the beginning of the file. - - seek(0); - - // This loop is the crux of the find method. There are three cases that we - // want to account for: - // (1) The previously searched buffer contained a partial match of the search - // pattern and we want to see if the next one starts with the remainder of - // that pattern. - // - // (2) The search pattern is wholly contained within the current buffer. - // - // (3) The current buffer ends with a partial match of the pattern. We will - // note this for use in the next itteration, where we will check for the rest - // of the pattern. - - for(buffer = readBlock(bufferSize()); buffer.size() > 0; buffer = readBlock(bufferSize())) { - - // (1) previous partial match - - if(previousPartialSynchMatch && secondSynchByte(buffer[0])) - return -1; - - if(previousPartialMatch >= 0 && int(bufferSize()) > previousPartialMatch) { - const int patternOffset = (bufferSize() - previousPartialMatch); - if(buffer.containsAt(ID3v2::Header::fileIdentifier(), 0, patternOffset)) { - seek(originalPosition); - return bufferOffset - bufferSize() + previousPartialMatch; - } + for(unsigned int i = 0; i < buffer.size(); ++i) { + frameSyncBytes[0] = frameSyncBytes[1]; + frameSyncBytes[1] = buffer[i]; + if(isFrameSync(frameSyncBytes)) { + const Header header(this, position + i - 1, true); + if(header.isValid()) + return -1; } - // (2) pattern contained in current buffer - - long location = buffer.find(ID3v2::Header::fileIdentifier()); - if(location >= 0) { - seek(originalPosition); - return bufferOffset + location; - } - - int firstSynchByte = buffer.find(char(uchar(255))); - - // Here we have to loop because there could be several of the first - // (11111111) byte, and we want to check all such instances until we find - // a full match (11111111 111) or hit the end of the buffer. - - while(firstSynchByte >= 0) { - - // if this *is not* at the end of the buffer - - if(firstSynchByte < int(buffer.size()) - 1) { - if(secondSynchByte(buffer[firstSynchByte + 1])) { - // We've found the frame synch pattern. - seek(originalPosition); - return -1; - } - else { - - // We found 11111111 at the end of the current buffer indicating a - // partial match of the synch pattern. The find() below should - // return -1 and break out of the loop. - - previousPartialSynchMatch = true; - } - } - - // Check in the rest of the buffer. - - firstSynchByte = buffer.find(char(uchar(255)), firstSynchByte + 1); - } - - // (3) partial match - - previousPartialMatch = buffer.endsWithPartialMatch(ID3v2::Header::fileIdentifier()); - - bufferOffset += bufferSize(); + tagHeaderBytes[0] = tagHeaderBytes[1]; + tagHeaderBytes[1] = tagHeaderBytes[2]; + tagHeaderBytes[2] = buffer[i]; + if(tagHeaderBytes == headerID) + return position + i - 2; } - // Since we hit the end of the file, reset the status before continuing. - - clear(); - - seek(originalPosition); + position += bufferSize(); } - - return -1; -} - -long MPEG::File::findID3v1() -{ - if(isValid()) { - seek(-128, End); - long p = tell(); - - if(readBlock(3) == ID3v1::Tag::fileIdentifier()) - return p; - } - return -1; -} - -void MPEG::File::findAPE() -{ - if(isValid()) { - seek(d->hasID3v1 ? -160 : -32, End); - - long p = tell(); - - if(readBlock(8) == APE::Tag::fileIdentifier()) { - d->APEFooterLocation = p; - seek(d->APEFooterLocation); - APE::Footer footer(readBlock(APE::Footer::size())); - d->APELocation = d->APEFooterLocation - footer.completeTagSize() - + APE::Footer::size(); - return; - } - } - - d->APELocation = -1; - d->APEFooterLocation = -1; -} - -bool MPEG::File::secondSynchByte(char byte) -{ - if(uchar(byte) == 0xff) - return false; - - std::bitset<8> b(byte); - - // check to see if the byte matches 111xxxxx - return b.test(7) && b.test(6) && b.test(5); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/mpegfile.h b/Frameworks/TagLib/taglib/taglib/mpeg/mpegfile.h index 282af775a..3fcb72722 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/mpegfile.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/mpegfile.h @@ -28,9 +28,12 @@ #include "taglib_export.h" #include "tfile.h" +#include "tag.h" #include "mpegproperties.h" +#include "id3v2.h" + namespace TagLib { namespace ID3v2 { class Tag; class FrameFactory; } @@ -70,9 +73,10 @@ namespace TagLib { }; /*! - * Contructs an MPEG file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs an MPEG file from \a file. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. * * \deprecated This constructor will be dropped in favor of the one below * in a future version. @@ -81,16 +85,35 @@ namespace TagLib { Properties::ReadStyle propertiesStyle = Properties::Average); /*! - * Contructs an MPEG file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. The frames will be created using + * Constructs an MPEG file from \a file. If \a readProperties is true the + * file's audio properties will also be read. + * + * If this file contains and ID3v2 tag the frames will be created using * \a frameFactory. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ // BIC: merge with the above constructor File(FileName file, ID3v2::FrameFactory *frameFactory, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs an MPEG file from \a stream. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * If this file contains and ID3v2 tag the frames will be created using + * \a frameFactory. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, ID3v2::FrameFactory *frameFactory, + bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ @@ -115,6 +138,26 @@ namespace TagLib { */ virtual Tag *tag() const; + /*! + * Implements the reading part of the unified property interface. + * If the file contains more than one tag, only the + * first one (in the order ID3v2, APE, ID3v1) will be converted to the + * PropertyMap. + */ + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the writing part of the unified tag dictionary interface. + * In order to avoid problems with deprecated tag formats, this method + * always creates an ID3v2 tag if necessary. + * If an ID3v1 tag exists, it will be updated as well, within the + * limitations of that format. + * The returned PropertyMap refers to the ID3v2 tag only. + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the MPEG::Properties for this file. If no audio properties * were read then this will return a null pointer. @@ -132,7 +175,7 @@ namespace TagLib { * This is the same as calling save(AllTags); * * If you would like more granular control over the content of the tags, - * with the concession of generality, use paramaterized save call below. + * with the concession of generality, use parameterized save call below. * * \see save(int tags) */ @@ -150,53 +193,94 @@ namespace TagLib { bool save(int tags); /*! - * Save the file. This will attempt to save all of the tag types that are - * specified by OR-ing together TagTypes values. The save() method above - * uses AllTags. This returns true if saving was successful. - * - * If \a stripOthers is true this strips all tags not included in the mask, - * but does not modify them in memory, so later calls to save() which make - * use of these tags will remain valid. This also strips empty tags. + * \deprecated */ // BIC: combine with the above method - bool save(int tags, bool stripOthers); + TAGLIB_DEPRECATED bool save(int tags, bool stripOthers); + + /*! + * \deprecated + */ + // BIC: combine with the above method + TAGLIB_DEPRECATED bool save(int tags, bool stripOthers, int id3v2Version); + + /*! + * \deprecated + */ + // BIC: combine with the above method + TAGLIB_DEPRECATED bool save(int tags, bool stripOthers, int id3v2Version, bool duplicateTags); + + /*! + * Save the file. This will attempt to save all of the tag types that are + * specified by OR-ing together TagTypes values. + * + * \a strip can be set to strip all tags except those in \a tags. Those + * tags will not be modified in memory, and thus remain valid. + * + * \a version specifies the ID3v2 version to be used for writing tags. By + * default, the latest standard, ID3v2.4 is used. + * + * If \a duplicate is set to DuplicateTags and at least one tag -- ID3v1 + * or ID3v2 -- exists this will duplicate its content into the other tag. + */ + bool save(int tags, StripTags strip, + ID3v2::Version version = ID3v2::v4, + DuplicateTags duplicate = Duplicate); /*! * Returns a pointer to the ID3v2 tag of the file. * - * If \a create is false (the default) this will return a null pointer + * If \a create is false (the default) this may return a null pointer * if there is no valid ID3v2 tag. If \a create is true it will create - * an ID3v2 tag if one does not exist. + * an ID3v2 tag if one does not exist and returns a valid pointer. + * + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file + * on disk actually has an ID3v2 tag. * * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasID3v2Tag() */ ID3v2::Tag *ID3v2Tag(bool create = false); /*! * Returns a pointer to the ID3v1 tag of the file. * - * If \a create is false (the default) this will return a null pointer + * If \a create is false (the default) this may return a null pointer * if there is no valid ID3v1 tag. If \a create is true it will create - * an ID3v1 tag if one does not exist. + * an ID3v1 tag if one does not exist and returns a valid pointer. + * + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * on disk actually has an ID3v1 tag. * * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasID3v1Tag() */ ID3v1::Tag *ID3v1Tag(bool create = false); /*! * Returns a pointer to the APE tag of the file. * - * If \a create is false (the default) this will return a null pointer + * If \a create is false (the default) this may return a null pointer * if there is no valid APE tag. If \a create is true it will create - * an APE tag if one does not exist. + * an APE tag if one does not exist and returns a valid pointer. + * + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file + * on disk actually has an APE tag. * * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasAPETag() */ APE::Tag *APETag(bool create = false); @@ -209,6 +293,8 @@ namespace TagLib { * * \note This will also invalidate pointers to the ID3 and APE tags * as their memory will be freed. + * + * \note This will update the file immediately. */ bool strip(int tags = AllTags); @@ -219,6 +305,8 @@ namespace TagLib { * * If \a freeMemory is true the ID3 and APE tags will be deleted and * pointers to them will be invalidated. + * + * \note This will update the file immediately. */ // BIC: merge with the method above bool strip(int tags, bool freeMemory); @@ -227,8 +315,9 @@ namespace TagLib { * Set the ID3v2::FrameFactory to something other than the default. * * \see ID3v2FrameFactory + * \deprecated This value should be passed in via the constructor */ - void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); + TAGLIB_DEPRECATED void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); /*! * Returns the position in the file of the first MPEG frame. @@ -252,21 +341,42 @@ namespace TagLib { */ long lastFrameOffset(); + /*! + * Returns whether or not the file on disk actually has an ID3v1 tag. + * + * \see ID3v1Tag() + */ + bool hasID3v1Tag() const; + + /*! + * Returns whether or not the file on disk actually has an ID3v2 tag. + * + * \see ID3v2Tag() + */ + bool hasID3v2Tag() const; + + /*! + * Returns whether or not the file on disk actually has an APE tag. + * + * \see APETag() + */ + bool hasAPETag() const; + + /*! + * Returns whether or not the given \a stream can be opened as an MPEG + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); long findID3v2(); - long findID3v1(); - void findAPE(); - - /*! - * MPEG frames can be recognized by the bit pattern 11111111 111, so the - * first byte is easy to check for, however checking to see if the second byte - * starts with \e 111 is a bit more tricky, hence this member function. - */ - static bool secondSynchByte(char byte); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/mpegheader.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/mpegheader.cpp index c715dbc14..5a5015d61 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/mpegheader.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/mpegheader.cpp @@ -23,13 +23,14 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include <bitset> - #include <tbytevector.h> #include <tstring.h> +#include <tfile.h> #include <tdebug.h> +#include <trefcounter.h> #include "mpegheader.h" +#include "mpegutils.h" using namespace TagLib; @@ -41,6 +42,7 @@ public: version(Version1), layer(0), protectionEnabled(false), + bitrate(0), sampleRate(0), isPadded(false), channelMode(Stereo), @@ -67,20 +69,27 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MPEG::Header::Header(const ByteVector &data) +MPEG::Header::Header(const ByteVector &data) : + d(new HeaderPrivate()) { - d = new HeaderPrivate; - parse(data); + debug("MPEG::Header::Header() - This constructor is no longer used."); } -MPEG::Header::Header(const Header &h) : d(h.d) +MPEG::Header::Header(File *file, long offset, bool checkLength) : + d(new HeaderPrivate()) +{ + parse(file, offset, checkLength); +} + +MPEG::Header::Header(const Header &h) : + d(h.d) { d->ref(); } MPEG::Header::~Header() { - if (d->deref()) + if(d->deref()) delete d; } @@ -161,41 +170,50 @@ MPEG::Header &MPEG::Header::operator=(const Header &h) // private members //////////////////////////////////////////////////////////////////////////////// -void MPEG::Header::parse(const ByteVector &data) +void MPEG::Header::parse(File *file, long offset, bool checkLength) { - if(data.size() < 4 || uchar(data[0]) != 0xff) { - debug("MPEG::Header::parse() -- First byte did not match MPEG synch."); + file->seek(offset); + const ByteVector data = file->readBlock(4); + + if(data.size() < 4) { + debug("MPEG::Header::parse() -- data is too short for an MPEG frame header."); return; } - std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.toUInt())); + // Check for the MPEG synch bytes. - // Check for the second byte's part of the MPEG synch - - if(!flags[23] || !flags[22] || !flags[21]) { - debug("MPEG::Header::parse() -- Second byte did not match MPEG synch."); + if(!isFrameSync(data)) { + debug("MPEG::Header::parse() -- MPEG header did not match MPEG synch."); return; } // Set the MPEG version - if(!flags[20] && !flags[19]) + const int versionBits = (static_cast<unsigned char>(data[1]) >> 3) & 0x03; + + if(versionBits == 0) d->version = Version2_5; - else if(flags[20] && !flags[19]) + else if(versionBits == 2) d->version = Version2; - else if(flags[20] && flags[19]) + else if(versionBits == 3) d->version = Version1; + else + return; // Set the MPEG layer - if(!flags[18] && flags[17]) - d->layer = 3; - else if(flags[18] && !flags[17]) - d->layer = 2; - else if(flags[18] && flags[17]) - d->layer = 1; + const int layerBits = (static_cast<unsigned char>(data[1]) >> 1) & 0x03; - d->protectionEnabled = !flags[16]; + if(layerBits == 1) + d->layer = 3; + else if(layerBits == 2) + d->layer = 2; + else if(layerBits == 3) + d->layer = 1; + else + return; + + d->protectionEnabled = (static_cast<unsigned char>(data[1] & 0x01) == 0); // Set the bitrate @@ -212,15 +230,18 @@ void MPEG::Header::parse(const ByteVector &data) } }; - const int versionIndex = d->version == Version1 ? 0 : 1; - const int layerIndex = d->layer > 0 ? d->layer - 1 : 0; + const int versionIndex = (d->version == Version1) ? 0 : 1; + const int layerIndex = (d->layer > 0) ? d->layer - 1 : 0; // The bitrate index is encoded as the first 4 bits of the 3rd byte, // i.e. 1111xxxx - int i = uchar(data[2]) >> 4; + const int bitrateIndex = (static_cast<unsigned char>(data[2]) >> 4) & 0x0F; - d->bitrate = bitrates[versionIndex][layerIndex][i]; + d->bitrate = bitrates[versionIndex][layerIndex][bitrateIndex]; + + if(d->bitrate == 0) + return; // Set the sample rate @@ -232,32 +253,24 @@ void MPEG::Header::parse(const ByteVector &data) // The sample rate index is encoded as two bits in the 3nd byte, i.e. xxxx11xx - i = uchar(data[2]) >> 2 & 0x03; + const int samplerateIndex = (static_cast<unsigned char>(data[2]) >> 2) & 0x03; - d->sampleRate = sampleRates[d->version][i]; + d->sampleRate = sampleRates[d->version][samplerateIndex]; if(d->sampleRate == 0) { - debug("MPEG::Header::parse() -- Invalid sample rate."); return; } // The channel mode is encoded as a 2 bit value at the end of the 3nd byte, // i.e. xxxxxx11 - d->channelMode = ChannelMode((uchar(data[3]) & 0xC0) >> 6); + d->channelMode = static_cast<ChannelMode>((static_cast<unsigned char>(data[3]) >> 6) & 0x03); // TODO: Add mode extension for completeness - d->isOriginal = flags[2]; - d->isCopyrighted = flags[3]; - d->isPadded = flags[9]; - - // Calculate the frame length - - if(d->layer == 1) - d->frameLength = 24000 * 2 * d->bitrate / d->sampleRate + int(d->isPadded); - else - d->frameLength = 72000 * d->bitrate / d->sampleRate + int(d->isPadded); + d->isOriginal = ((static_cast<unsigned char>(data[3]) & 0x04) != 0); + d->isCopyrighted = ((static_cast<unsigned char>(data[3]) & 0x08) != 0); + d->isPadded = ((static_cast<unsigned char>(data[2]) & 0x02) != 0); // Samples per frame @@ -270,6 +283,39 @@ void MPEG::Header::parse(const ByteVector &data) d->samplesPerFrame = samplesPerFrame[layerIndex][versionIndex]; + // Calculate the frame length + + static const int paddingSize[3] = { 4, 1, 1 }; + + d->frameLength = d->samplesPerFrame * d->bitrate * 125 / d->sampleRate; + + if(d->isPadded) + d->frameLength += paddingSize[layerIndex]; + + if(checkLength) { + + // Check if the frame length has been calculated correctly, or the next frame + // header is right next to the end of this frame. + + // The MPEG versions, layers and sample rates of the two frames should be + // consistent. Otherwise, we assume that either or both of the frames are + // broken. + + file->seek(offset + d->frameLength); + const ByteVector nextData = file->readBlock(4); + + if(nextData.size() < 4) + return; + + const unsigned int HeaderMask = 0xfffe0c00; + + const unsigned int header = data.toUInt(0, true) & HeaderMask; + const unsigned int nextHeader = nextData.toUInt(0, true) & HeaderMask; + + if(header != nextHeader) + return; + } + // Now that we're done parsing, set this to be a valid frame. d->isValid = true; diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/mpegheader.h b/Frameworks/TagLib/taglib/taglib/mpeg/mpegheader.h index 020ebd060..ca51184cf 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/mpegheader.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/mpegheader.h @@ -31,6 +31,7 @@ namespace TagLib { class ByteVector; + class File; namespace MPEG { @@ -48,8 +49,19 @@ namespace TagLib { public: /*! * Parses an MPEG header based on \a data. + * + * \deprecated */ - Header(const ByteVector &data); + TAGLIB_DEPRECATED Header(const ByteVector &data); + + /*! + * Parses an MPEG header based on \a file and \a offset. + * + * \note If \a checkLength is true, this requires the next MPEG frame to + * check if the frame length is parsed and calculated correctly. So it's + * suitable for seeking for the first valid frame. + */ + Header(File *file, long offset, bool checkLength = true); /*! * Does a shallow copy of \a h. @@ -140,7 +152,7 @@ namespace TagLib { bool isOriginal() const; /*! - * Returns the frame length. + * Returns the frame length in bytes. */ int frameLength() const; @@ -155,7 +167,7 @@ namespace TagLib { Header &operator=(const Header &h); private: - void parse(const ByteVector &data); + void parse(File *file, long offset, bool checkLength); class HeaderPrivate; HeaderPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/mpegproperties.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/mpegproperties.cpp index 028ee0615..5eec84f7d 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/mpegproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/mpegproperties.cpp @@ -29,16 +29,16 @@ #include "mpegproperties.h" #include "mpegfile.h" #include "xingheader.h" +#include "apetag.h" +#include "apefooter.h" using namespace TagLib; class MPEG::Properties::PropertiesPrivate { public: - PropertiesPrivate(File *f, ReadStyle s) : - file(f), + PropertiesPrivate() : xingHeader(0), - style(s), length(0), bitrate(0), sampleRate(0), @@ -55,9 +55,7 @@ public: delete xingHeader; } - File *file; XingHeader *xingHeader; - ReadStyle style; int length; int bitrate; int sampleRate; @@ -74,12 +72,11 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -MPEG::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +MPEG::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file, style); - - if(file && file->isOpen()) - read(); + read(file); } MPEG::Properties::~Properties() @@ -88,6 +85,16 @@ MPEG::Properties::~Properties() } int MPEG::Properties::length() const +{ + return lengthInSeconds(); +} + +int MPEG::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int MPEG::Properties::lengthInMilliseconds() const { return d->length; } @@ -146,109 +153,69 @@ bool MPEG::Properties::isOriginal() const // private members //////////////////////////////////////////////////////////////////////////////// -void MPEG::Properties::read() +void MPEG::Properties::read(File *file) { - // Since we've likely just looked for the ID3v1 tag, start at the end of the - // file where we're least likely to have to have to move the disk head. + // Only the first valid frame is required if we have a VBR header. - long last = d->file->lastFrameOffset(); - - if(last < 0) { - debug("MPEG::Properties::read() -- Could not find a valid last MPEG frame in the stream."); + const long firstFrameOffset = file->firstFrameOffset(); + if(firstFrameOffset < 0) { + debug("MPEG::Properties::read() -- Could not find an MPEG frame in the stream."); return; } - d->file->seek(last); - Header lastHeader(d->file->readBlock(4)); + const Header firstHeader(file, firstFrameOffset, false); - long first = d->file->firstFrameOffset(); - - if(first < 0) { - debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream."); - return; - } - - if(!lastHeader.isValid()) { - - long pos = last; - - while(pos > first) { - - pos = d->file->previousFrameOffset(pos); - - if(pos < 0) - break; - - d->file->seek(pos); - Header header(d->file->readBlock(4)); - - if(header.isValid()) { - lastHeader = header; - last = pos; - break; - } - } - } - - // Now jump back to the front of the file and read what we need from there. - - d->file->seek(first); - Header firstHeader(d->file->readBlock(4)); - - if(!firstHeader.isValid() || !lastHeader.isValid()) { - debug("MPEG::Properties::read() -- Page headers were invalid."); - return; - } - - // Check for a Xing header that will help us in gathering information about a + // Check for a VBR header that will help us in gathering information about a // VBR stream. - int xingHeaderOffset = MPEG::XingHeader::xingHeaderOffset(firstHeader.version(), - firstHeader.channelMode()); - - d->file->seek(first + xingHeaderOffset); - d->xingHeader = new XingHeader(d->file->readBlock(16)); - - // Read the length and the bitrate from the Xing header. - - if(d->xingHeader->isValid() && - firstHeader.sampleRate() > 0 && - d->xingHeader->totalFrames() > 0) - { - double timePerFrame = - double(firstHeader.samplesPerFrame()) / firstHeader.sampleRate(); - - double length = timePerFrame * d->xingHeader->totalFrames(); - - d->length = int(length); - d->bitrate = d->length > 0 ? d->xingHeader->totalSize() * 8 / length / 1000 : 0; - } - else { - // Since there was no valid Xing header found, we hope that we're in a constant - // bitrate file. - + file->seek(firstFrameOffset); + d->xingHeader = new XingHeader(file->readBlock(firstHeader.frameLength())); + if(!d->xingHeader->isValid()) { delete d->xingHeader; d->xingHeader = 0; + } + + if(d->xingHeader && firstHeader.samplesPerFrame() > 0 && firstHeader.sampleRate() > 0) { + + // Read the length and the bitrate from the VBR header. + + const double timePerFrame = firstHeader.samplesPerFrame() * 1000.0 / firstHeader.sampleRate(); + const double length = timePerFrame * d->xingHeader->totalFrames(); + + d->length = static_cast<int>(length + 0.5); + d->bitrate = static_cast<int>(d->xingHeader->totalSize() * 8.0 / length + 0.5); + } + else if(firstHeader.bitrate() > 0) { + + // Since there was no valid VBR header found, we hope that we're in a constant + // bitrate file. // TODO: Make this more robust with audio property detection for VBR without a // Xing header. - if(firstHeader.frameLength() > 0 && firstHeader.bitrate() > 0) { - int frames = (last - first) / firstHeader.frameLength() + 1; + d->bitrate = firstHeader.bitrate(); - d->length = int(float(firstHeader.frameLength() * frames) / - float(firstHeader.bitrate() * 125) + 0.5); - d->bitrate = firstHeader.bitrate(); + // Look for the last MPEG audio frame to calculate the stream length. + + const long lastFrameOffset = file->lastFrameOffset(); + if(lastFrameOffset < 0) { + debug("MPEG::Properties::read() -- Could not find an MPEG frame in the stream."); + } + else + { + const Header lastHeader(file, lastFrameOffset, false); + const long streamLength = lastFrameOffset - firstFrameOffset + lastHeader.frameLength(); + if (streamLength > 0) + d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5); } } - - d->sampleRate = firstHeader.sampleRate(); - d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2; - d->version = firstHeader.version(); - d->layer = firstHeader.layer(); + d->sampleRate = firstHeader.sampleRate(); + d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2; + d->version = firstHeader.version(); + d->layer = firstHeader.layer(); d->protectionEnabled = firstHeader.protectionEnabled(); - d->channelMode = firstHeader.channelMode(); - d->isCopyrighted = firstHeader.isCopyrighted(); - d->isOriginal = firstHeader.isOriginal(); + d->channelMode = firstHeader.channelMode(); + d->isCopyrighted = firstHeader.isCopyrighted(); + d->isOriginal = firstHeader.isOriginal(); } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/mpegproperties.h b/Frameworks/TagLib/taglib/taglib/mpeg/mpegproperties.h index 72e594ff5..a1a1af4af 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/mpegproperties.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/mpegproperties.h @@ -59,18 +59,52 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; - virtual int length() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns a pointer to the XingHeader if one exists or null if no - * XingHeader was found. + * Returns a pointer to the Xing/VBRI header if one exists or null if no + * Xing/VBRI header was found. */ - const XingHeader *xingHeader() const; /*! @@ -107,7 +141,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/mpegutils.h b/Frameworks/TagLib/taglib/taglib/mpeg/mpegutils.h new file mode 100644 index 000000000..31b45a43b --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/mpeg/mpegutils.h @@ -0,0 +1,63 @@ +/*************************************************************************** + copyright : (C) 2015 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_MPEGUTILS_H +#define TAGLIB_MPEGUTILS_H + +// THIS FILE IS NOT A PART OF THE TAGLIB API + +#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header + +namespace TagLib +{ + namespace MPEG + { + namespace + { + + /*! + * MPEG frames can be recognized by the bit pattern 11111111 111, so the + * first byte is easy to check for, however checking to see if the second byte + * starts with \e 111 is a bit more tricky, hence these functions. + * + * \note This does not check the length of the vector, since this is an + * internal utility function. + */ + inline bool isFrameSync(const ByteVector &bytes, unsigned int offset = 0) + { + // 0xFF in the second byte is possible in theory, but it's very unlikely. + + const unsigned char b1 = bytes[offset + 0]; + const unsigned char b2 = bytes[offset + 1]; + return (b1 == 0xFF && b2 != 0xFF && (b2 & 0xE0) == 0xE0); + } + + } + } +} + +#endif + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/xingheader.cpp b/Frameworks/TagLib/taglib/taglib/mpeg/xingheader.cpp index 1ba932de3..6c2b25dc5 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/xingheader.cpp +++ b/Frameworks/TagLib/taglib/taglib/mpeg/xingheader.cpp @@ -28,6 +28,7 @@ #include <tdebug.h> #include "xingheader.h" +#include "mpegfile.h" using namespace TagLib; @@ -37,17 +38,21 @@ public: XingHeaderPrivate() : frames(0), size(0), - valid(false) - {} + type(MPEG::XingHeader::Invalid) {} - uint frames; - uint size; - bool valid; + unsigned int frames; + unsigned int size; + + MPEG::XingHeader::HeaderType type; }; -MPEG::XingHeader::XingHeader(const ByteVector &data) +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +MPEG::XingHeader::XingHeader(const ByteVector &data) : + d(new XingHeaderPrivate()) { - d = new XingHeaderPrivate; parse(data); } @@ -58,58 +63,78 @@ MPEG::XingHeader::~XingHeader() bool MPEG::XingHeader::isValid() const { - return d->valid; + return (d->type != Invalid && d->frames > 0 && d->size > 0); } -TagLib::uint MPEG::XingHeader::totalFrames() const +unsigned int MPEG::XingHeader::totalFrames() const { return d->frames; } -TagLib::uint MPEG::XingHeader::totalSize() const +unsigned int MPEG::XingHeader::totalSize() const { return d->size; } -int MPEG::XingHeader::xingHeaderOffset(TagLib::MPEG::Header::Version v, - TagLib::MPEG::Header::ChannelMode c) +MPEG::XingHeader::HeaderType MPEG::XingHeader::type() const { - if(v == MPEG::Header::Version1) { - if(c == MPEG::Header::SingleChannel) - return 0x15; - else - return 0x24; - } - else { - if(c == MPEG::Header::SingleChannel) - return 0x0D; - else - return 0x15; - } + return d->type; } +int MPEG::XingHeader::xingHeaderOffset(TagLib::MPEG::Header::Version /*v*/, + TagLib::MPEG::Header::ChannelMode /*c*/) +{ + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + void MPEG::XingHeader::parse(const ByteVector &data) { - // Check to see if a valid Xing header is available. + // Look for a Xing header. - if(!data.startsWith("Xing") && !data.startsWith("Info")) - return; + long offset = data.find("Xing"); + if(offset < 0) + offset = data.find("Info"); - // If the XingHeader doesn't contain the number of frames and the total stream - // info it's invalid. + if(offset >= 0) { - if(!(data[7] & 0x01)) { - debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total number of frames."); - return; + // Xing header found. + + if(data.size() < static_cast<unsigned long>(offset + 16)) { + debug("MPEG::XingHeader::parse() -- Xing header found but too short."); + return; + } + + if((data[offset + 7] & 0x03) != 0x03) { + debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the required information."); + return; + } + + d->frames = data.toUInt(offset + 8, true); + d->size = data.toUInt(offset + 12, true); + d->type = Xing; } + else { - if(!(data[7] & 0x02)) { - debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total stream size."); - return; + // Xing header not found. Then look for a VBRI header. + + offset = data.find("VBRI"); + + if(offset >= 0) { + + // VBRI header found. + + if(data.size() < static_cast<unsigned long>(offset + 32)) { + debug("MPEG::XingHeader::parse() -- VBRI header found but too short."); + return; + } + + d->frames = data.toUInt(offset + 14, true); + d->size = data.toUInt(offset + 10, true); + d->type = VBRI; + } } - - d->frames = data.mid(8, 4).toUInt(); - d->size = data.mid(12, 4).toUInt(); - - d->valid = true; } diff --git a/Frameworks/TagLib/taglib/taglib/mpeg/xingheader.h b/Frameworks/TagLib/taglib/taglib/mpeg/xingheader.h index ffe7494d5..ce7561909 100644 --- a/Frameworks/TagLib/taglib/taglib/mpeg/xingheader.h +++ b/Frameworks/TagLib/taglib/taglib/mpeg/xingheader.h @@ -35,24 +35,47 @@ namespace TagLib { namespace MPEG { - //! An implementation of the Xing VBR headers + class File; + + //! An implementation of the Xing/VBRI headers /*! - * This is a minimalistic implementation of the Xing VBR headers. Xing - * headers are often added to VBR (variable bit rate) MP3 streams to make it - * easy to compute the length and quality of a VBR stream. Our implementation - * is only concerned with the total size of the stream (so that we can - * calculate the total playing time and the average bitrate). It uses - * <a href="http://home.pcisys.net/~melanson/codecs/mp3extensions.txt">this text</a> - * and the XMMS sources as references. + * This is a minimalistic implementation of the Xing/VBRI VBR headers. + * Xing/VBRI headers are often added to VBR (variable bit rate) MP3 streams + * to make it easy to compute the length and quality of a VBR stream. Our + * implementation is only concerned with the total size of the stream (so + * that we can calculate the total playing time and the average bitrate). + * It uses <a href="http://home.pcisys.net/~melanson/codecs/mp3extensions.txt"> + * this text</a> and the XMMS sources as references. */ class TAGLIB_EXPORT XingHeader { public: /*! - * Parses a Xing header based on \a data. The data must be at least 16 - * bytes long (anything longer than this is discarded). + * The type of the VBR header. + */ + enum HeaderType + { + /*! + * Invalid header or no VBR header found. + */ + Invalid = 0, + + /*! + * Xing header. + */ + Xing = 1, + + /*! + * VBRI header. + */ + VBRI = 2, + }; + + /*! + * Parses an Xing/VBRI header based on \a data which contains the entire + * first MPEG frame. */ XingHeader(const ByteVector &data); @@ -63,27 +86,33 @@ namespace TagLib { /*! * Returns true if the data was parsed properly and if there is a valid - * Xing header present. + * Xing/VBRI header present. */ bool isValid() const; /*! * Returns the total number of frames. */ - uint totalFrames() const; + unsigned int totalFrames() const; /*! * Returns the total size of stream in bytes. */ - uint totalSize() const; + unsigned int totalSize() const; + + /*! + * Returns the type of the VBR header. + */ + HeaderType type() const; /*! * Returns the offset for the start of this Xing header, given the * version and channels of the frame + * + * \deprecated Always returns 0. */ - // BIC: rename to offset() - static int xingHeaderOffset(TagLib::MPEG::Header::Version v, - TagLib::MPEG::Header::ChannelMode c); + TAGLIB_DEPRECATED static int xingHeaderOffset(TagLib::MPEG::Header::Version v, + TagLib::MPEG::Header::ChannelMode c); private: XingHeader(const XingHeader &); diff --git a/Frameworks/TagLib/taglib/taglib/ogg/flac/oggflacfile.cpp b/Frameworks/TagLib/taglib/taglib/ogg/flac/oggflacfile.cpp index 22d7b81f6..07ea9dccc 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/flac/oggflacfile.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/flac/oggflacfile.cpp @@ -26,6 +26,8 @@ #include <tbytevector.h> #include <tstring.h> #include <tdebug.h> +#include <tpropertymap.h> +#include <tagutils.h> #include <xiphcomment.h> #include "oggflacfile.h" @@ -64,15 +66,38 @@ public: int commentPacket; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool Ogg::FLAC::File::isSupported(IOStream *stream) +{ + // An Ogg FLAC file has IDs "OggS" and "fLaC" somewhere. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false); + return (buffer.find("OggS") >= 0 && buffer.find("fLaC") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// Ogg::FLAC::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : Ogg::File(file) + Properties::ReadStyle propertiesStyle) : + Ogg::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties, propertiesStyle); +} + +Ogg::FLAC::File::File(IOStream *stream, bool readProperties, + Properties::ReadStyle propertiesStyle) : + Ogg::File(stream), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties, propertiesStyle); } Ogg::FLAC::File::~File() @@ -85,6 +110,16 @@ Ogg::XiphComment *Ogg::FLAC::File::tag() const return d->comment; } +PropertyMap Ogg::FLAC::File::properties() const +{ + return d->comment->properties(); +} + +PropertyMap Ogg::FLAC::File::setProperties(const PropertyMap &properties) +{ + return d->comment->setProperties(properties); +} + Properties *Ogg::FLAC::File::audioProperties() const { return d->properties; @@ -117,6 +152,11 @@ bool Ogg::FLAC::File::save() return Ogg::File::save(); } +bool Ogg::FLAC::File::hasXiphComment() const +{ + return d->hasXiphComment; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// @@ -147,7 +187,7 @@ void Ogg::FLAC::File::read(bool readProperties, Properties::ReadStyle properties if(d->hasXiphComment) d->comment = new Ogg::XiphComment(xiphCommentData()); else - d->comment = new Ogg::XiphComment; + d->comment = new Ogg::XiphComment(); if(readProperties) @@ -186,29 +226,40 @@ void Ogg::FLAC::File::scan() long overhead = 0; ByteVector metadataHeader = packet(ipacket); - if(metadataHeader.isNull()) + if(metadataHeader.isEmpty()) return; - ByteVector header; - - if (!metadataHeader.startsWith("fLaC")) { + if(!metadataHeader.startsWith("fLaC")) { // FLAC 1.1.2+ - if (metadataHeader.mid(1,4) != "FLAC") return; + // See https://xiph.org/flac/ogg_mapping.html for the header specification. + if(metadataHeader.size() < 13) + return; - if (metadataHeader[5] != 1) return; // not version 1 + if(metadataHeader[0] != 0x7f) + return; + + if(metadataHeader.mid(1, 4) != "FLAC") + return; + + if(metadataHeader[5] != 1 && metadataHeader[6] != 0) + return; // not version 1.0 + + if(metadataHeader.mid(9, 4) != "fLaC") + return; metadataHeader = metadataHeader.mid(13); } else { // FLAC 1.1.0 & 1.1.1 metadataHeader = packet(++ipacket); - - if(metadataHeader.isNull()) - return; - } - header = metadataHeader.mid(0,4); + ByteVector header = metadataHeader.mid(0, 4); + if(header.size() != 4) { + debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header"); + return; + } + // Header format (from spec): // <1> Last-metadata-block flag // <7> BLOCK_TYPE @@ -221,7 +272,7 @@ void Ogg::FLAC::File::scan() char blockType = header[0] & 0x7f; bool lastBlock = (header[0] & 0x80) != 0; - uint length = header.mid(1, 3).toUInt(); + unsigned int length = header.toUInt(1, 3, true); overhead += length; // Sanity: First block should be the stream_info metadata @@ -231,20 +282,21 @@ void Ogg::FLAC::File::scan() return; } - d->streamInfoData = metadataHeader.mid(4,length); + d->streamInfoData = metadataHeader.mid(4, length); // Search through the remaining metadata while(!lastBlock) { metadataHeader = packet(++ipacket); - - if(metadataHeader.isNull()) - return; - header = metadataHeader.mid(0, 4); + if(header.size() != 4) { + debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header"); + return; + } + blockType = header[0] & 0x7f; lastBlock = (header[0] & 0x80) != 0; - length = header.mid(1, 3).toUInt(); + length = header.toUInt(1, 3, true); overhead += length; if(blockType == 1) { @@ -256,9 +308,9 @@ void Ogg::FLAC::File::scan() d->hasXiphComment = true; d->commentPacket = ipacket; } - else if(blockType > 5) + else if(blockType > 5) { debug("Ogg::FLAC::File::scan() -- Unknown metadata block"); - + } } // End of metadata, now comes the datastream diff --git a/Frameworks/TagLib/taglib/taglib/ogg/flac/oggflacfile.h b/Frameworks/TagLib/taglib/taglib/ogg/flac/oggflacfile.h index 5882a696d..b2686e457 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/flac/oggflacfile.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/flac/oggflacfile.h @@ -42,7 +42,7 @@ namespace TagLib { /*! * This is implementation of FLAC metadata for Ogg FLAC files. For "pure" - * FLAC files look under the FLAC hiearchy. + * FLAC files look under the FLAC hierarchy. * * Unlike "pure" FLAC-files, Ogg FLAC only supports Xiph-comments, * while the audio-properties are the same. @@ -64,13 +64,26 @@ namespace TagLib { { public: /*! - * Contructs an Ogg/FLAC file from \a file. If \a readProperties is true - * the file's audio properties will also be read using \a propertiesStyle. - * If false, \a propertiesStyle is ignored. + * Constructs an Ogg/FLAC file from \a file. If \a readProperties is true + * the file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs an Ogg/FLAC file from \a stream. If \a readProperties is true + * the file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ @@ -78,6 +91,16 @@ namespace TagLib { /*! * Returns the Tag for this file. This will always be a XiphComment. + * + * \note This always returns a valid pointer regardless of whether or not + * the file on disk has a XiphComment. Use hasXiphComment() to check if + * the file on disk actually has a XiphComment. + * + * \note The Tag <b>is still</b> owned by the FLAC::File and should not be + * deleted by the user. It will be deleted when the file (object) is + * destroyed. + * + * \see hasXiphComment() */ virtual XiphComment *tag() const; @@ -87,6 +110,20 @@ namespace TagLib { */ virtual Properties *audioProperties() const; + + /*! + * Implements the unified property interface -- export function. + * This forwards directly to XiphComment::properties(). + */ + PropertyMap properties() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Like properties(), this is a forwarder to the file's XiphComment. + */ + PropertyMap setProperties(const PropertyMap &); + + /*! * Save the file. This will primarily save and update the XiphComment. * Returns true if the save is successful. @@ -99,6 +136,21 @@ namespace TagLib { */ long streamLength(); + /*! + * Returns whether or not the file on disk actually has a XiphComment. + * + * \see tag() + */ + bool hasXiphComment() const; + + /*! + * Check if the given \a stream can be opened as an Ogg FLAC file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); diff --git a/Frameworks/TagLib/taglib/taglib/ogg/oggfile.cpp b/Frameworks/TagLib/taglib/taglib/ogg/oggfile.cpp index f29ba14a8..c36e4d46c 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/oggfile.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/oggfile.cpp @@ -34,15 +34,24 @@ using namespace TagLib; +namespace +{ + // Returns the first packet index of the right next page to the given one. + unsigned int nextPacketIndex(const Ogg::Page *page) + { + if(page->header()->lastPacketCompleted()) + return page->firstPacketIndex() + page->packetCount(); + else + return page->firstPacketIndex() + page->packetCount() - 1; + } +} + class Ogg::File::FilePrivate { public: FilePrivate() : - streamSerialNumber(0), firstPageHeader(0), - lastPageHeader(0), - currentPage(0), - currentPacketPage(0) + lastPageHeader(0) { pages.setAutoDelete(true); } @@ -53,20 +62,11 @@ public: delete lastPageHeader; } - uint streamSerialNumber; + unsigned int streamSerialNumber; List<Page *> pages; PageHeader *firstPageHeader; PageHeader *lastPageHeader; - std::vector< List<int> > packetToPageMap; - Map<int, ByteVector> dirtyPackets; - List<int> dirtyPages; - - //! The current page for the reader -- used by nextPage() - Page *currentPage; - //! The current page for the packet parser -- used by packet() - Page *currentPacketPage; - //! The packets for the currentPacketPage -- used by packet() - ByteVectorList currentPackets; + Map<unsigned int, ByteVector> dirtyPackets; }; //////////////////////////////////////////////////////////////////////////////// @@ -78,7 +78,7 @@ Ogg::File::~File() delete d; } -ByteVector Ogg::File::packet(uint i) +ByteVector Ogg::File::packet(unsigned int i) { // Check to see if we're called setPacket() for this packet since the last // save: @@ -89,94 +89,67 @@ ByteVector Ogg::File::packet(uint i) // If we haven't indexed the page where the packet we're interested in starts, // begin reading pages until we have. - while(d->packetToPageMap.size() <= i) { - if(!nextPage()) { - debug("Ogg::File::packet() -- Could not find the requested packet."); - return ByteVector::null; - } + if(!readPages(i)) { + debug("Ogg::File::packet() -- Could not find the requested packet."); + return ByteVector(); } - // Start reading at the first page that contains part (or all) of this packet. - // If the last read stopped at the packet that we're interested in, don't - // reread its packet list. (This should make sequential packet reads fast.) + // Look for the first page in which the requested packet starts. - uint pageIndex = d->packetToPageMap[i].front(); - if(d->currentPacketPage != d->pages[pageIndex]) { - d->currentPacketPage = d->pages[pageIndex]; - d->currentPackets = d->currentPacketPage->packets(); - } + List<Page *>::ConstIterator it = d->pages.begin(); + while((*it)->containsPacket(i) == Page::DoesNotContainPacket) + ++it; - // If the packet is completely contained in the first page that it's in, then - // just return it now. - - if(d->currentPacketPage->containsPacket(i) & Page::CompletePacket) - return d->currentPackets[i - d->currentPacketPage->firstPacketIndex()]; + // If the packet is completely contained in the first page that it's in. // If the packet is *not* completely contained in the first page that it's a // part of then that packet trails off the end of the page. Continue appending // the pages' packet data until we hit a page that either does not end with the // packet that we're fetching or where the last packet is complete. - ByteVector packet = d->currentPackets.back(); - while(d->currentPacketPage->containsPacket(i) & Page::EndsWithPacket && - !d->currentPacketPage->header()->lastPacketCompleted()) - { - pageIndex++; - if(pageIndex == d->pages.size()) { - if(!nextPage()) { - debug("Ogg::File::packet() -- Could not find the requested packet."); - return ByteVector::null; - } - } - d->currentPacketPage = d->pages[pageIndex]; - d->currentPackets = d->currentPacketPage->packets(); - packet.append(d->currentPackets.front()); + ByteVector packet = (*it)->packets()[i - (*it)->firstPacketIndex()]; + + while(nextPacketIndex(*it) <= i) { + ++it; + packet.append((*it)->packets().front()); } return packet; } -void Ogg::File::setPacket(uint i, const ByteVector &p) +void Ogg::File::setPacket(unsigned int i, const ByteVector &p) { - while(d->packetToPageMap.size() <= i) { - if(!nextPage()) { - debug("Ogg::File::setPacket() -- Could not set the requested packet."); - return; - } + if(!readPages(i)) { + debug("Ogg::File::setPacket() -- Could not set the requested packet."); + return; } - List<int>::ConstIterator it = d->packetToPageMap[i].begin(); - for(; it != d->packetToPageMap[i].end(); ++it) - d->dirtyPages.sortedInsert(*it, true); - - d->dirtyPackets.insert(i, p); + d->dirtyPackets[i] = p; } const Ogg::PageHeader *Ogg::File::firstPageHeader() { - if(d->firstPageHeader) - return d->firstPageHeader->isValid() ? d->firstPageHeader : 0; + if(!d->firstPageHeader) { + const long firstPageHeaderOffset = find("OggS"); + if(firstPageHeaderOffset < 0) + return 0; - long firstPageHeaderOffset = find("OggS"); + d->firstPageHeader = new PageHeader(this, firstPageHeaderOffset); + } - if(firstPageHeaderOffset < 0) - return 0; - - d->firstPageHeader = new PageHeader(this, firstPageHeaderOffset); return d->firstPageHeader->isValid() ? d->firstPageHeader : 0; } const Ogg::PageHeader *Ogg::File::lastPageHeader() { - if(d->lastPageHeader) - return d->lastPageHeader->isValid() ? d->lastPageHeader : 0; + if(!d->lastPageHeader) { + const long lastPageHeaderOffset = rfind("OggS"); + if(lastPageHeaderOffset < 0) + return 0; - long lastPageHeaderOffset = rfind("OggS"); + d->lastPageHeader = new PageHeader(this, lastPageHeaderOffset); + } - if(lastPageHeaderOffset < 0) - return 0; - - d->lastPageHeader = new PageHeader(this, lastPageHeaderOffset); return d->lastPageHeader->isValid() ? d->lastPageHeader : 0; } @@ -187,18 +160,10 @@ bool Ogg::File::save() return false; } - List<int> pageGroup; + Map<unsigned int, ByteVector>::ConstIterator it; + for(it = d->dirtyPackets.begin(); it != d->dirtyPackets.end(); ++it) + writePacket(it->first, it->second); - for(List<int>::ConstIterator it = d->dirtyPages.begin(); it != d->dirtyPages.end(); ++it) { - if(!pageGroup.isEmpty() && pageGroup.back() + 1 != *it) { - writePageGroup(pageGroup); - pageGroup.clear(); - } - else - pageGroup.append(*it); - } - writePageGroup(pageGroup); - d->dirtyPages.clear(); d->dirtyPackets.clear(); return true; @@ -208,220 +173,141 @@ bool Ogg::File::save() // protected members //////////////////////////////////////////////////////////////////////////////// -Ogg::File::File(FileName file) : TagLib::File(file) +Ogg::File::File(FileName file) : + TagLib::File(file), + d(new FilePrivate()) +{ +} + +Ogg::File::File(IOStream *stream) : + TagLib::File(stream), + d(new FilePrivate()) { - d = new FilePrivate; } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -bool Ogg::File::nextPage() +bool Ogg::File::readPages(unsigned int i) { - long nextPageOffset; - int currentPacket; + while(true) { + unsigned int packetIndex; + long offset; - if(d->pages.isEmpty()) { - currentPacket = 0; - nextPageOffset = find("OggS"); - if(nextPageOffset < 0) + if(d->pages.isEmpty()) { + packetIndex = 0; + offset = find("OggS"); + if(offset < 0) + return false; + } + else { + const Page *page = d->pages.back(); + packetIndex = nextPacketIndex(page); + offset = page->fileOffset() + page->size(); + } + + // Enough pages have been fetched. + + if(packetIndex > i) + return true; + + // Read the next page and add it to the page list. + + Page *nextPage = new Page(this, offset); + if(!nextPage->header()->isValid()) { + delete nextPage; + return false; + } + + nextPage->setFirstPacketIndex(packetIndex); + d->pages.append(nextPage); + + if(nextPage->header()->lastPageOfStream()) return false; } - else { - if(d->currentPage->header()->lastPageOfStream()) - return false; - - if(d->currentPage->header()->lastPacketCompleted()) - currentPacket = d->currentPage->firstPacketIndex() + d->currentPage->packetCount(); - else - currentPacket = d->currentPage->firstPacketIndex() + d->currentPage->packetCount() - 1; - - nextPageOffset = d->currentPage->fileOffset() + d->currentPage->size(); - } - - // Read the next page and add it to the page list. - - d->currentPage = new Page(this, nextPageOffset); - - if(!d->currentPage->header()->isValid()) { - delete d->currentPage; - d->currentPage = 0; - return false; - } - - d->currentPage->setFirstPacketIndex(currentPacket); - - if(d->pages.isEmpty()) - d->streamSerialNumber = d->currentPage->header()->streamSerialNumber(); - - d->pages.append(d->currentPage); - - // Loop through the packets in the page that we just read appending the - // current page number to the packet to page map for each packet. - - for(uint i = 0; i < d->currentPage->packetCount(); i++) { - uint currentPacket = d->currentPage->firstPacketIndex() + i; - if(d->packetToPageMap.size() <= currentPacket) - d->packetToPageMap.push_back(List<int>()); - d->packetToPageMap[currentPacket].append(d->pages.size() - 1); - } - - return true; } -void Ogg::File::writePageGroup(const List<int> &thePageGroup) +void Ogg::File::writePacket(unsigned int i, const ByteVector &packet) { - if(thePageGroup.isEmpty()) + if(!readPages(i)) { + debug("Ogg::File::writePacket() -- Could not find the requested packet."); return; - - - // pages in the pageGroup and packets must be equivalent - // (originalSize and size of packets would not work together), - // therefore we sometimes have to add pages to the group - List<int> pageGroup(thePageGroup); - while (!d->pages[pageGroup.back()]->header()->lastPacketCompleted()) { - if (d->currentPage->header()->pageSequenceNumber() == pageGroup.back()) { - if (nextPage() == false) { - debug("broken ogg file"); - return; - } - pageGroup.append(d->currentPage->header()->pageSequenceNumber()); - } else { - pageGroup.append(pageGroup.back() + 1); - } } - ByteVectorList packets; + // Look for the pages where the requested packet should belong to. - // If the first page of the group isn't dirty, append its partial content here. + List<Page *>::ConstIterator it = d->pages.begin(); + while((*it)->containsPacket(i) == Page::DoesNotContainPacket) + ++it; - if(!d->dirtyPages.contains(d->pages[pageGroup.front()]->firstPacketIndex())) - packets.append(d->pages[pageGroup.front()]->packets().front()); + const Page *firstPage = *it; - int previousPacket = -1; - int originalSize = 0; + while(nextPacketIndex(*it) <= i) + ++it; - for(List<int>::ConstIterator it = pageGroup.begin(); it != pageGroup.end(); ++it) { - uint firstPacket = d->pages[*it]->firstPacketIndex(); - uint lastPacket = firstPacket + d->pages[*it]->packetCount() - 1; + const Page *lastPage = *it; - List<int>::ConstIterator last = --pageGroup.end(); + // Replace the requested packet and create new pages to replace the located pages. - for(uint i = firstPacket; i <= lastPacket; i++) { + ByteVectorList packets = firstPage->packets(); + packets[i - firstPage->firstPacketIndex()] = packet; - if(it == last && i == lastPacket && !d->dirtyPages.contains(i)) - packets.append(d->pages[*it]->packets().back()); - else if(int(i) != previousPacket) { - previousPacket = i; - packets.append(packet(i)); - } - } - originalSize += d->pages[*it]->size(); + if(firstPage != lastPage && lastPage->packetCount() > 1) { + ByteVectorList lastPagePackets = lastPage->packets(); + lastPagePackets.erase(lastPagePackets.begin()); + packets.append(lastPagePackets); } - const bool continued = d->pages[pageGroup.front()]->header()->firstPacketContinued(); - const bool completed = d->pages[pageGroup.back()]->header()->lastPacketCompleted(); - // TODO: This pagination method isn't accurate for what's being done here. // This should account for real possibilities like non-aligned packets and such. - List<Page *> pages = Page::paginate(packets, Page::SinglePagePerGroup, - d->streamSerialNumber, pageGroup.front(), - continued, completed); + List<Page *> pages = Page::paginate(packets, + Page::SinglePagePerGroup, + firstPage->header()->streamSerialNumber(), + firstPage->pageSequenceNumber(), + firstPage->header()->firstPacketContinued(), + lastPage->header()->lastPacketCompleted()); + pages.setAutoDelete(true); - List<Page *> renumberedPages; - - // Correct the page numbering of following pages - - if (pages.back()->header()->pageSequenceNumber() != pageGroup.back()) { - - // TODO: change the internal data structure so that we don't need to hold the - // complete file in memory (is unavoidable at the moment) - - // read the complete stream - while(!d->currentPage->header()->lastPageOfStream()) { - if(nextPage() == false) { - debug("broken ogg file"); - break; - } - } - - // create a gap for the new pages - int numberOfNewPages = pages.back()->header()->pageSequenceNumber() - pageGroup.back(); - List<Page *>::Iterator pageIter = d->pages.begin(); - for(int i = 0; i < pageGroup.back(); i++) { - if(pageIter != d->pages.end()) { - ++pageIter; - } - else { - debug("Ogg::File::writePageGroup() -- Page sequence is broken in original file."); - break; - } - } - - ++pageIter; - for(; pageIter != d->pages.end(); ++pageIter) { - Ogg::Page *newPage = - (*pageIter)->getCopyWithNewPageSequenceNumber( - (*pageIter)->header()->pageSequenceNumber() + numberOfNewPages); - - ByteVector data; - data.append(newPage->render()); - insert(data, newPage->fileOffset(), data.size()); - - renumberedPages.append(newPage); - } - } - - // insert the new data + // Write the pages. ByteVector data; - for(List<Page *>::ConstIterator it = pages.begin(); it != pages.end(); ++it) + for(it = pages.begin(); it != pages.end(); ++it) data.append((*it)->render()); - // The insertion algorithms could also be improve to queue and prioritize data - // on the way out. Currently it requires rewriting the file for every page - // group rather than just once; however, for tagging applications there will - // generally only be one page group, so it's not worth the time for the - // optimization at the moment. + const unsigned long originalOffset = firstPage->fileOffset(); + const unsigned long originalLength = lastPage->fileOffset() + lastPage->size() - originalOffset; - insert(data, d->pages[pageGroup.front()]->fileOffset(), originalSize); + insert(data, originalOffset, originalLength); - // Update the page index to include the pages we just created and to delete the - // old pages. + // Renumber the following pages if the pages have been split or merged. - // First step: Pages that contain the comment data + const int numberOfNewPages + = pages.back()->pageSequenceNumber() - lastPage->pageSequenceNumber(); - for(List<Page *>::ConstIterator it = pages.begin(); it != pages.end(); ++it) { - const unsigned int index = (*it)->header()->pageSequenceNumber(); - if(index < d->pages.size()) { - delete d->pages[index]; - d->pages[index] = *it; - } - else if(index == d->pages.size()) { - d->pages.append(*it); - } - else { - // oops - there's a hole in the sequence - debug("Ogg::File::writePageGroup() -- Page sequence is broken."); + if(numberOfNewPages != 0) { + long pageOffset = originalOffset + data.size(); + + while(true) { + Page page(this, pageOffset); + if(!page.header()->isValid()) + break; + + page.setPageSequenceNumber(page.pageSequenceNumber() + numberOfNewPages); + const ByteVector data = page.render(); + + seek(pageOffset + 18); + writeBlock(data.mid(18, 8)); + + if(page.header()->lastPageOfStream()) + break; + + pageOffset += page.size(); } } - // Second step: the renumbered pages + // Discard all the pages to keep them up-to-date by fetching them again. - for(List<Page *>::ConstIterator it = renumberedPages.begin(); it != renumberedPages.end(); ++it) { - const unsigned int index = (*it)->header()->pageSequenceNumber(); - if(index < d->pages.size()) { - delete d->pages[index]; - d->pages[index] = *it; - } - else if(index == d->pages.size()) { - d->pages.append(*it); - } - else { - // oops - there's a hole in the sequence - debug("Ogg::File::writePageGroup() -- Page sequence is broken."); - } - } + d->pages.clear(); } diff --git a/Frameworks/TagLib/taglib/taglib/ogg/oggfile.h b/Frameworks/TagLib/taglib/taglib/ogg/oggfile.h index da1fcb69b..7d889c2f2 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/oggfile.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/oggfile.h @@ -56,15 +56,15 @@ namespace TagLib { * Returns the packet contents for the i-th packet (starting from zero) * in the Ogg bitstream. * - * \warning The requires reading at least the packet header for every page + * \warning This requires reading at least the packet header for every page * up to the requested page. */ - ByteVector packet(uint i); + ByteVector packet(unsigned int i); /*! * Sets the packet with index \a i to the value \a p. */ - void setPacket(uint i, const ByteVector &p); + void setPacket(unsigned int i, const ByteVector &p); /*! * Returns a pointer to the PageHeader for the first page in the stream or @@ -82,9 +82,7 @@ namespace TagLib { protected: /*! - * Contructs an Ogg file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs an Ogg file from \a file. * * \note This constructor is protected since Ogg::File shouldn't be * instantiated directly but rather should be used through the codec @@ -92,15 +90,32 @@ namespace TagLib { */ File(FileName file); + /*! + * Constructs an Ogg file from \a stream. + * + * \note This constructor is protected since Ogg::File shouldn't be + * instantiated directly but rather should be used through the codec + * specific subclasses. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + File(IOStream *stream); + private: File(const File &); File &operator=(const File &); /*! - * Reads the next page and updates the internal "current page" pointer. + * Reads the pages from the beginning of the file until enough to compose + * the requested packet. */ - bool nextPage(); - void writePageGroup(const List<int> &group); + bool readPages(unsigned int i); + + /*! + * Writes the requested packet to the file. + */ + void writePacket(unsigned int i, const ByteVector &packet); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/ogg/oggpage.cpp b/Frameworks/TagLib/taglib/taglib/ogg/oggpage.cpp index 732f01dda..414d3d530 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/oggpage.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/oggpage.cpp @@ -23,6 +23,8 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include <algorithm> + #include <tstring.h> #include <tdebug.h> @@ -38,22 +40,11 @@ public: PagePrivate(File *f = 0, long pageOffset = -1) : file(f), fileOffset(pageOffset), - packetOffset(0), header(f, pageOffset), - firstPacketIndex(-1) - { - if(file) { - packetOffset = fileOffset + header.size(); - packetSizes = header.packetSizes(); - dataSize = header.dataSize(); - } - } + firstPacketIndex(-1) {} File *file; long fileOffset; - long packetOffset; - int dataSize; - List<int> packetSizes; PageHeader header; int firstPacketIndex; ByteVectorList packets; @@ -63,9 +54,9 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Ogg::Page::Page(Ogg::File *file, long pageOffset) +Ogg::Page::Page(Ogg::File *file, long pageOffset) : + d(new PagePrivate(file, pageOffset)) { - d = new PagePrivate(file, pageOffset); } Ogg::Page::~Page() @@ -83,6 +74,16 @@ const Ogg::PageHeader *Ogg::Page::header() const return &d->header; } +int Ogg::Page::pageSequenceNumber() const +{ + return d->header.pageSequenceNumber(); +} + +void Ogg::Page::setPageSequenceNumber(int sequenceNumber) +{ + d->header.setPageSequenceNumber(sequenceNumber); +} + int Ogg::Page::firstPacketIndex() const { return d->firstPacketIndex; @@ -95,7 +96,7 @@ void Ogg::Page::setFirstPacketIndex(int index) Ogg::Page::ContainsPacketFlags Ogg::Page::containsPacket(int index) const { - int lastPacketIndex = d->firstPacketIndex + packetCount() - 1; + const int lastPacketIndex = d->firstPacketIndex + packetCount() - 1; if(index < d->firstPacketIndex || index > lastPacketIndex) return DoesNotContainPacket; @@ -116,9 +117,9 @@ Ogg::Page::ContainsPacketFlags Ogg::Page::containsPacket(int index) const flags = ContainsPacketFlags(flags | CompletePacket); } - // Or if there is more than one page and the page is - // (a) the first page and it's complete or - // (b) the last page and it's complete or + // Or if there is more than one page and the page is + // (a) the first page and it's complete or + // (b) the last page and it's complete or // (c) a page in the middle. else if(packetCount() > 1 && ((flags & BeginsWithPacket && !d->header.firstPacketContinued()) || @@ -131,7 +132,7 @@ Ogg::Page::ContainsPacketFlags Ogg::Page::containsPacket(int index) const return flags; } -TagLib::uint Ogg::Page::packetCount() const +unsigned int Ogg::Page::packetCount() const { return d->header.packetSizes().size(); } @@ -145,7 +146,7 @@ ByteVectorList Ogg::Page::packets() const if(d->file && d->header.isValid()) { - d->file->seek(d->packetOffset); + d->file->seek(d->fileOffset + d->header.size()); List<int> packetSizes = d->header.packetSizes(); @@ -172,8 +173,8 @@ ByteVector Ogg::Page::render() const if(d->packets.isEmpty()) { if(d->file) { - d->file->seek(d->packetOffset); - data.append(d->file->readBlock(d->dataSize)); + d->file->seek(d->fileOffset + d->header.size()); + data.append(d->file->readBlock(d->header.dataSize())); } else debug("Ogg::Page::render() -- this page is empty!"); @@ -188,121 +189,90 @@ ByteVector Ogg::Page::render() const // the entire page with the 4 bytes reserved for the checksum zeroed and then // inserted in bytes 22-25 of the page header. - ByteVector checksum = ByteVector::fromUInt(data.checksum(), false); - for(int i = 0; i < 4; i++) - data[i + 22] = checksum[i]; + const ByteVector checksum = ByteVector::fromUInt(data.checksum(), false); + std::copy(checksum.begin(), checksum.end(), data.begin() + 22); return data; } List<Ogg::Page *> Ogg::Page::paginate(const ByteVectorList &packets, PaginationStrategy strategy, - uint streamSerialNumber, + unsigned int streamSerialNumber, int firstPage, bool firstPacketContinued, bool lastPacketCompleted, bool containsLastPacket) { + // SplitSize must be a multiple of 255 in order to get the lacing values right + // create pages of about 8KB each + + static const unsigned int SplitSize = 32 * 255; + + // Force repagination if the segment table will exceed the size limit. + + if(strategy != Repaginate) { + + size_t tableSize = 0; + for(ByteVectorList::ConstIterator it = packets.begin(); it != packets.end(); ++it) + tableSize += it->size() / 255 + 1; + + if(tableSize > 255) + strategy = Repaginate; + } + List<Page *> l; - int totalSize = 0; - - for(ByteVectorList::ConstIterator it = packets.begin(); it != packets.end(); ++it) - totalSize += (*it).size(); - // Handle creation of multiple pages with appropriate pagination. - if(strategy == Repaginate || totalSize + packets.size() > 255 * 255) { - // SPLITSIZE must be a multiple of 255 in order to get the lacing values right - // create pages of about 8KB each -#define SPLITSIZE (32*255) + if(strategy == Repaginate) { - int pageIndex = 0; + int pageIndex = firstPage; for(ByteVectorList::ConstIterator it = packets.begin(); it != packets.end(); ++it) { - bool continued = false; + + const bool lastPacketInList = (it == --packets.end()); // mark very first packet? - if(firstPacketContinued && it==packets.begin()) { - continued = true; - } - // append to buf - ByteVector packetBuf; - packetBuf.append(*it); + bool continued = (firstPacketContinued && it == packets.begin()); + unsigned int pos = 0; - while(packetBuf.size() > SPLITSIZE) { - // output a Page - ByteVector packetForOnePage; - packetForOnePage.resize(SPLITSIZE); - std::copy(packetBuf.begin(), packetBuf.begin() + SPLITSIZE, packetForOnePage.begin()); + while(pos < it->size()) { + + const bool lastSplit = (pos + SplitSize >= it->size()); ByteVectorList packetList; - packetList.append(packetForOnePage); - Page *p = new Page(packetList, streamSerialNumber, firstPage+pageIndex, continued, false, false); - l.append(p); + packetList.append(it->mid(pos, SplitSize)); + l.append(new Page(packetList, + streamSerialNumber, + pageIndex, + continued, + lastSplit && (lastPacketInList ? lastPacketCompleted : true), + lastSplit && (containsLastPacket && lastPacketInList))); pageIndex++; continued = true; - packetBuf = packetBuf.mid(SPLITSIZE); + + pos += SplitSize; } - - ByteVectorList::ConstIterator jt = it; - ++jt; - bool lastPacketInList = (jt == packets.end()); - - // output a page for the rest (we output one packet per page, so this one should be completed) - ByteVectorList packetList; - packetList.append(packetBuf); - - bool isVeryLastPacket = false; - if(containsLastPacket) { - // mark the very last output page as last of stream - ByteVectorList::ConstIterator jt = it; - ++jt; - if(jt == packets.end()) { - isVeryLastPacket = true; - } - } - - Page *p = new Page(packetList, streamSerialNumber, firstPage+pageIndex, continued, - lastPacketInList ? lastPacketCompleted : true, - isVeryLastPacket); - pageIndex++; - - l.append(p); } } else { - Page *p = new Page(packets, streamSerialNumber, firstPage, firstPacketContinued, - lastPacketCompleted, containsLastPacket); - l.append(p); + l.append(new Page(packets, + streamSerialNumber, + firstPage, + firstPacketContinued, + lastPacketCompleted, + containsLastPacket)); } return l; } -Ogg::Page* Ogg::Page::getCopyWithNewPageSequenceNumber(int sequenceNumber) +Ogg::Page* Ogg::Page::getCopyWithNewPageSequenceNumber(int /*sequenceNumber*/) { - Page *pResultPage = NULL; - - // TODO: a copy constructor would be helpful - - if(d->file == 0) { - pResultPage = new Page( - d->packets, - d->header.streamSerialNumber(), - sequenceNumber, - d->header.firstPacketContinued(), - d->header.lastPacketCompleted(), - d->header.lastPageOfStream()); - } - else - { - pResultPage = new Page(d->file, d->fileOffset); - pResultPage->d->header.setPageSequenceNumber(sequenceNumber); - } - return pResultPage; + debug("Ogg::Page::getCopyWithNewPageSequenceNumber() -- This function is obsolete. Returning null."); + return 0; } //////////////////////////////////////////////////////////////////////////////// @@ -310,17 +280,13 @@ Ogg::Page* Ogg::Page::getCopyWithNewPageSequenceNumber(int sequenceNumber) //////////////////////////////////////////////////////////////////////////////// Ogg::Page::Page(const ByteVectorList &packets, - uint streamSerialNumber, + unsigned int streamSerialNumber, int pageNumber, bool firstPacketContinued, bool lastPacketCompleted, - bool containsLastPacket) + bool containsLastPacket) : + d(new PagePrivate()) { - d = new PagePrivate; - - ByteVector data; - List<int> packetSizes; - d->header.setFirstPageOfStream(pageNumber == 0 && !firstPacketContinued); d->header.setLastPageOfStream(containsLastPacket); d->header.setFirstPacketContinued(firstPacketContinued); @@ -330,6 +296,9 @@ Ogg::Page::Page(const ByteVectorList &packets, // Build a page from the list of packets. + ByteVector data; + List<int> packetSizes; + for(ByteVectorList::ConstIterator it = packets.begin(); it != packets.end(); ++it) { packetSizes.append((*it).size()); data.append(*it); @@ -337,4 +306,3 @@ Ogg::Page::Page(const ByteVectorList &packets, d->packets = packets; d->header.setPacketSizes(packetSizes); } - diff --git a/Frameworks/TagLib/taglib/taglib/ogg/oggpage.h b/Frameworks/TagLib/taglib/taglib/ogg/oggpage.h index a8d5c1a3d..af3fe2c9e 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/oggpage.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/oggpage.h @@ -41,7 +41,7 @@ namespace TagLib { /*! * This is an implementation of the pages that make up an Ogg stream. * This handles parsing pages and breaking them down into packets and handles - * the details of packets spanning multiple pages and pages that contiain + * the details of packets spanning multiple pages and pages that contain * multiple packets. * * In most Xiph.org formats the comments are found in the first few packets, @@ -70,13 +70,30 @@ namespace TagLib { */ const PageHeader *header() const; - /*! + /*! + * Returns the index of the page within the Ogg stream. This helps make it + * possible to determine if pages have been lost. + * + * \see setPageSequenceNumber() + */ + int pageSequenceNumber() const; + + /*! + * Sets the page's position in the stream to \a sequenceNumber. + * + * \see pageSequenceNumber() + */ + void setPageSequenceNumber(int sequenceNumber); + + /*! * Returns a copy of the page with \a sequenceNumber set as sequence number. - * + * * \see header() * \see PageHeader::setPageSequenceNumber() + * + * \deprecated Always returns null. */ - Page* getCopyWithNewPageSequenceNumber(int sequenceNumber); + TAGLIB_DEPRECATED Page *getCopyWithNewPageSequenceNumber(int sequenceNumber); /*! * Returns the index of the first packet wholly or partially contained in @@ -121,7 +138,7 @@ namespace TagLib { /*! * Returns the number of packets (whole or partial) in this page. */ - uint packetCount() const; + unsigned int packetCount() const; /*! * Returns a list of the packets in this page. @@ -162,7 +179,7 @@ namespace TagLib { /*! * Pack \a packets into Ogg pages using the \a strategy for pagination. - * The page number indicater inside of the rendered packets will start + * The page number indicator inside of the rendered packets will start * with \a firstPage and be incremented for each page rendered. * \a containsLastPacket should be set to true if \a packets contains the * last page in the stream and will set the appropriate flag in the last @@ -181,7 +198,7 @@ namespace TagLib { */ static List<Page *> paginate(const ByteVectorList &packets, PaginationStrategy strategy, - uint streamSerialNumber, + unsigned int streamSerialNumber, int firstPage, bool firstPacketContinued = false, bool lastPacketCompleted = true, @@ -193,7 +210,7 @@ namespace TagLib { * for each page will be set to \a pageNumber. */ Page(const ByteVectorList &packets, - uint streamSerialNumber, + unsigned int streamSerialNumber, int pageNumber, bool firstPacketContinued = false, bool lastPacketCompleted = true, diff --git a/Frameworks/TagLib/taglib/taglib/ogg/oggpageheader.cpp b/Frameworks/TagLib/taglib/taglib/ogg/oggpageheader.cpp index f9608ab7f..b867567cb 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/oggpageheader.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/oggpageheader.cpp @@ -23,8 +23,6 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include <stdlib.h> - #include <bitset> #include <tstring.h> @@ -39,9 +37,7 @@ using namespace TagLib; class Ogg::PageHeader::PageHeaderPrivate { public: - PageHeaderPrivate(File *f, long pageOffset) : - file(f), - fileOffset(pageOffset), + PageHeaderPrivate() : isValid(false), firstPacketContinued(false), lastPacketCompleted(false), @@ -51,11 +47,8 @@ public: streamSerialNumber(0), pageSequenceNumber(-1), size(0), - dataSize(0) - {} + dataSize(0) {} - File *file; - long fileOffset; bool isValid; List<int> packetSizes; bool firstPacketContinued; @@ -63,7 +56,7 @@ public: bool firstPageOfStream; bool lastPageOfStream; long long absoluteGranularPosition; - uint streamSerialNumber; + unsigned int streamSerialNumber; int pageSequenceNumber; int size; int dataSize; @@ -73,11 +66,11 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Ogg::PageHeader::PageHeader(Ogg::File *file, long pageOffset) +Ogg::PageHeader::PageHeader(Ogg::File *file, long pageOffset) : + d(new PageHeaderPrivate()) { - d = new PageHeaderPrivate(file, pageOffset); if(file && pageOffset >= 0) - read(); + read(file, pageOffset); } Ogg::PageHeader::~PageHeader() @@ -160,12 +153,12 @@ void Ogg::PageHeader::setPageSequenceNumber(int sequenceNumber) d->pageSequenceNumber = sequenceNumber; } -TagLib::uint Ogg::PageHeader::streamSerialNumber() const +unsigned int Ogg::PageHeader::streamSerialNumber() const { return d->streamSerialNumber; } -void Ogg::PageHeader::setStreamSerialNumber(uint n) +void Ogg::PageHeader::setStreamSerialNumber(unsigned int n) { d->streamSerialNumber = n; } @@ -184,7 +177,7 @@ ByteVector Ogg::PageHeader::render() const { ByteVector data; - // capture patern + // capture pattern data.append("OggS"); @@ -222,7 +215,7 @@ ByteVector Ogg::PageHeader::render() const ByteVector pageSegments = lacingValues(); - data.append(char(uchar(pageSegments.size()))); + data.append(static_cast<unsigned char>(pageSegments.size())); data.append(pageSegments); return data; @@ -232,14 +225,14 @@ ByteVector Ogg::PageHeader::render() const // private members //////////////////////////////////////////////////////////////////////////////// -void Ogg::PageHeader::read() +void Ogg::PageHeader::read(Ogg::File *file, long pageOffset) { - d->file->seek(d->fileOffset); + file->seek(pageOffset); // An Ogg page header is at least 27 bytes, so we'll go ahead and read that // much and then get the rest when we're ready for it. - ByteVector data = d->file->readBlock(27); + const ByteVector data = file->readBlock(27); // Sanity check -- make sure that we were in fact able to read as much data as // we asked for and that the page begins with "OggS". @@ -249,23 +242,23 @@ void Ogg::PageHeader::read() return; } - std::bitset<8> flags(data[5]); + const std::bitset<8> flags(data[5]); d->firstPacketContinued = flags.test(0); - d->firstPageOfStream = flags.test(1); - d->lastPageOfStream = flags.test(2); + d->firstPageOfStream = flags.test(1); + d->lastPageOfStream = flags.test(2); - d->absoluteGranularPosition = data.mid(6, 8).toLongLong(false); - d->streamSerialNumber = data.mid(14, 4).toUInt(false); - d->pageSequenceNumber = data.mid(18, 4).toUInt(false); + d->absoluteGranularPosition = data.toLongLong(6, false); + d->streamSerialNumber = data.toUInt(14, false); + d->pageSequenceNumber = data.toUInt(18, false); // Byte number 27 is the number of page segments, which is the only variable // length portion of the page header. After reading the number of page // segments we'll then read in the corresponding data for this count. - int pageSegmentCount = uchar(data[26]); + int pageSegmentCount = static_cast<unsigned char>(data[26]); - ByteVector pageSegments = d->file->readBlock(pageSegmentCount); + const ByteVector pageSegments = file->readBlock(pageSegmentCount); // Another sanity check. @@ -279,10 +272,10 @@ void Ogg::PageHeader::read() int packetSize = 0; for(int i = 0; i < pageSegmentCount; i++) { - d->dataSize += uchar(pageSegments[i]); - packetSize += uchar(pageSegments[i]); + d->dataSize += static_cast<unsigned char>(pageSegments[i]); + packetSize += static_cast<unsigned char>(pageSegments[i]); - if(uchar(pageSegments[i]) < 255) { + if(static_cast<unsigned char>(pageSegments[i]) < 255) { d->packetSizes.append(packetSize); packetSize = 0; } @@ -302,21 +295,17 @@ ByteVector Ogg::PageHeader::lacingValues() const { ByteVector data; - List<int> sizes = d->packetSizes; - for(List<int>::ConstIterator it = sizes.begin(); it != sizes.end(); ++it) { + for(List<int>::ConstIterator it = d->packetSizes.begin(); it != d->packetSizes.end(); ++it) { // The size of a packet in an Ogg page is indicated by a series of "lacing // values" where the sum of the values is the packet size in bytes. Each of // these values is a byte. A value of less than 255 (0xff) indicates the end // of the packet. - div_t n = div(*it, 255); + data.resize(data.size() + (*it / 255), '\xff'); - for(int i = 0; i < n.quot; i++) - data.append(char(uchar(255))); - - if(it != --sizes.end() || d->lastPacketCompleted) - data.append(char(uchar(n.rem))); + if(it != --d->packetSizes.end() || d->lastPacketCompleted) + data.append(static_cast<unsigned char>(*it % 255)); } return data; diff --git a/Frameworks/TagLib/taglib/taglib/ogg/oggpageheader.h b/Frameworks/TagLib/taglib/taglib/ogg/oggpageheader.h index 742710a4c..571155fca 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/oggpageheader.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/oggpageheader.h @@ -156,7 +156,7 @@ namespace TagLib { /*! * A special value of containing the position of the packet to be * interpreted by the codec. It is only supported here so that it may be - * coppied from one page to another. + * copied from one page to another. * * \see absoluteGranularPosition() */ @@ -169,7 +169,7 @@ namespace TagLib { * * \see setStreamSerialNumber() */ - uint streamSerialNumber() const; + unsigned int streamSerialNumber() const; /*! * Every Ogg logical stream is given a random serial number which is common @@ -179,7 +179,7 @@ namespace TagLib { * * \see streamSerialNumber() */ - void setStreamSerialNumber(uint n); + void setStreamSerialNumber(unsigned int n); /*! * Returns the index of the page within the Ogg stream. This helps make it @@ -219,7 +219,7 @@ namespace TagLib { PageHeader(const PageHeader &); PageHeader &operator=(const PageHeader &); - void read(); + void read(Ogg::File *file, long pageOffset); ByteVector lacingValues() const; class PageHeaderPrivate; diff --git a/Frameworks/TagLib/taglib/taglib/ogg/opus/opusfile.cpp b/Frameworks/TagLib/taglib/taglib/ogg/opus/opusfile.cpp index bb882e583..d4f191ad1 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/opus/opusfile.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/opus/opusfile.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - copyright : (C) 2006 by Lukáš Lalinský + copyright : (C) 2012 by Lukáš Lalinský email : lalinsky@gmail.com copyright : (C) 2002 - 2008 by Scott Wheeler @@ -19,18 +19,18 @@ * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * - * USA * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * * * * Alternatively, this file is available under the Mozilla Public * * License Version 1.1. You may obtain a copy of the License at * * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include <bitset> - #include <tstring.h> #include <tdebug.h> +#include <tpropertymap.h> +#include <tagutils.h> #include "opusfile.h" @@ -54,15 +54,36 @@ public: Properties *properties; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool Ogg::Opus::File::isSupported(IOStream *stream) +{ + // An Opus file has IDs "OggS" and "OpusHead" somewhere. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false); + return (buffer.find("OggS") >= 0 && buffer.find("OpusHead") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -Opus::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : Ogg::File(file) +Opus::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + Ogg::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties); +} + +Opus::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + Ogg::File(stream), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties); } Opus::File::~File() @@ -75,6 +96,16 @@ Ogg::XiphComment *Opus::File::tag() const return d->comment; } +PropertyMap Opus::File::properties() const +{ + return d->comment->properties(); +} + +PropertyMap Opus::File::setProperties(const PropertyMap &properties) +{ + return d->comment->setProperties(properties); +} + Opus::Properties *Opus::File::audioProperties() const { return d->properties; @@ -83,9 +114,9 @@ Opus::Properties *Opus::File::audioProperties() const bool Opus::File::save() { if(!d->comment) - d->comment = new Ogg::XiphComment; + d->comment = new Ogg::XiphComment(); - setPacket(1, d->comment->render()); + setPacket(1, ByteVector("OpusTags", 8) + d->comment->render(false)); return Ogg::File::save(); } @@ -94,23 +125,26 @@ bool Opus::File::save() // private members //////////////////////////////////////////////////////////////////////////////// -void Opus::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void Opus::File::read(bool readProperties) { ByteVector opusHeaderData = packet(0); if(!opusHeaderData.startsWith("OpusHead")) { + setValid(false); debug("Opus::File::read() -- invalid Opus identification header"); return; } ByteVector commentHeaderData = packet(1); - if (!commentHeaderData.startsWith("OpusTags")) { - debug("Opus::File::read() -- invalid Opus Comments header"); + + if(!commentHeaderData.startsWith("OpusTags")) { + setValid(false); + debug("Opus::File::read() -- invalid Opus tags header"); return; } d->comment = new Ogg::XiphComment(commentHeaderData.mid(8)); if(readProperties) - d->properties = new Properties(this, propertiesStyle); + d->properties = new Properties(this); } diff --git a/Frameworks/TagLib/taglib/taglib/ogg/opus/opusfile.h b/Frameworks/TagLib/taglib/taglib/ogg/opus/opusfile.h index 806cdb65a..0e094eae8 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/opus/opusfile.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/opus/opusfile.h @@ -1,5 +1,5 @@ /*************************************************************************** - copyright : (C) 2006 by Lukáš Lalinský + copyright : (C) 2012 by Lukáš Lalinský email : lalinsky@gmail.com copyright : (C) 2002 - 2008 by Scott Wheeler @@ -19,8 +19,8 @@ * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * - * USA * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * * * * Alternatively, this file is available under the Mozilla Public * * License Version 1.1. You may obtain a copy of the License at * @@ -30,8 +30,8 @@ #ifndef TAGLIB_OPUSFILE_H #define TAGLIB_OPUSFILE_H -#include <oggfile.h> -#include <xiphcomment.h> +#include "oggfile.h" +#include "xiphcomment.h" #include "opusproperties.h" @@ -56,13 +56,26 @@ namespace TagLib { { public: /*! - * Contructs an Opus file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs an Opus file from \a file. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs an Opus file from \a stream. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ @@ -75,19 +88,45 @@ namespace TagLib { */ virtual Ogg::XiphComment *tag() const; + /*! + * Implements the unified property interface -- export function. + * This forwards directly to XiphComment::properties(). + */ + PropertyMap properties() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Like properties(), this is a forwarder to the file's XiphComment. + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the Opus::Properties for this file. If no audio properties * were read then this will return a null pointer. */ virtual Properties *audioProperties() const; + /*! + * Save the file. + * + * This returns true if the save was successful. + */ virtual bool save(); + /*! + * Returns whether or not the given \a stream can be opened as an Opus + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/ogg/opus/opusproperties.cpp b/Frameworks/TagLib/taglib/taglib/ogg/opus/opusproperties.cpp index 9ad4d314e..b60cc01d6 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/opus/opusproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/opus/opusproperties.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - copyright : (C) 2006 by Lukáš Lalinský + copyright : (C) 2012 by Lukáš Lalinský email : lalinsky@gmail.com copyright : (C) 2002 - 2008 by Scott Wheeler @@ -19,8 +19,8 @@ * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * - * USA * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * * * * Alternatively, this file is available under the Mozilla Public * * License Version 1.1. You may obtain a copy of the License at * @@ -41,36 +41,29 @@ using namespace TagLib::Ogg; class Opus::Properties::PropertiesPrivate { public: - PropertiesPrivate(File *f, ReadStyle s) : - file(f), - style(s), + PropertiesPrivate() : length(0), - inputSampleRate(0), bitrate(0), - preSkip(0), + inputSampleRate(0), channels(0), - opusVersion(0), - outputGain(0) {} + opusVersion(0) {} - File *file; - ReadStyle style; int length; - int inputSampleRate; int bitrate; - int preSkip; + int inputSampleRate; int channels; int opusVersion; - int outputGain; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -Opus::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +Opus::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file, style); - read(); + read(file); } Opus::Properties::~Properties() @@ -79,17 +72,30 @@ Opus::Properties::~Properties() } int Opus::Properties::length() const +{ + return lengthInSeconds(); +} + +int Ogg::Opus::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int Ogg::Opus::Properties::lengthInMilliseconds() const { return d->length; } int Opus::Properties::bitrate() const { - return int(float(d->bitrate) / float(1000) + 0.5); + return d->bitrate; } int Opus::Properties::sampleRate() const { + // Opus can decode any stream at a sample rate of 8, 12, 16, 24, or 48 kHz, + // so there is no single sample rate. Let's assume it's the highest + // possible. return 48000; } @@ -98,6 +104,11 @@ int Opus::Properties::channels() const return d->channels; } +int Opus::Properties::inputSampleRate() const +{ + return d->inputSampleRate; +} + int Opus::Properties::opusVersion() const { return d->opusVersion; @@ -107,52 +118,65 @@ int Opus::Properties::opusVersion() const // private members //////////////////////////////////////////////////////////////////////////////// -void Opus::Properties::read() +void Opus::Properties::read(File *file) { // Get the identification header from the Ogg implementation. - ByteVector data = d->file->packet(0); + // http://tools.ietf.org/html/draft-terriberry-oggopus-01#section-5.1 - int pos = 8; + const ByteVector data = file->packet(0); - // opus_version_id; /**< Version for Opus (for checking compatibility) */ - d->opusVersion = data.mid(pos, 1).toUInt(false); + // *Magic Signature* + unsigned int pos = 8; + + // *Version* (8 bits, unsigned) + d->opusVersion = static_cast<unsigned char>(data.at(pos)); pos += 1; - // nb_channels; /**< Number of channels encoded */ - d->channels = data.mid(pos, 1).toUInt(false); + // *Output Channel Count* 'C' (8 bits, unsigned) + d->channels = static_cast<unsigned char>(data.at(pos)); pos += 1; - // pre_skip - d->preSkip = data.mid(pos, 2).toUInt(false); + // *Pre-skip* (16 bits, unsigned, little endian) + const unsigned short preSkip = data.toUShort(pos, false); pos += 2; - // rate; /**< Sampling rate used */ - d->inputSampleRate = data.mid(pos, 4).toUInt(false); + // *Input Sample Rate* (32 bits, unsigned, little endian) + d->inputSampleRate = data.toUInt(pos, false); pos += 4; - // output_gain; - d->outputGain = data.mid(pos, 2).toUInt(false); + // *Output Gain* (16 bits, signed, little endian) pos += 2; - - // frames_per_packet; /**< Number of frames stored per Ogg packet */ - // unsigned int framesPerPacket = data.mid(pos, 4).toUInt(false); - const Ogg::PageHeader *first = d->file->firstPageHeader(); - const Ogg::PageHeader *last = d->file->lastPageHeader(); + // *Channel Mapping Family* (8 bits, unsigned) + pos += 1; + + const Ogg::PageHeader *first = file->firstPageHeader(); + const Ogg::PageHeader *last = file->lastPageHeader(); if(first && last) { - long long start = first->absoluteGranularPosition(); - long long end = last->absoluteGranularPosition(); + const long long start = first->absoluteGranularPosition(); + const long long end = last->absoluteGranularPosition(); - if(start >= 0 && end >= 0) - d->length = (int) ((end - start) / (long long) 48000); - else - debug("Opus::Properties::read() -- Either the PCM values for the start or " - "end of this file was incorrect or the sample rate is zero."); - - ByteVector comments = d->file->packet(1); - d->bitrate = ( d->file->length() - comments.size() ) * 8 / d->length; + if(start >= 0 && end >= 0) { + const long long frameCount = (end - start - preSkip); + + if(frameCount > 0) { + const double length = frameCount * 1000.0 / 48000.0; + long fileLengthWithoutOverhead = file->length(); + // Ignore the two mandatory header packets, see "3. Packet Organization" + // in https://tools.ietf.org/html/rfc7845.html + for (unsigned int i = 0; i < 2; ++i) { + fileLengthWithoutOverhead -= file->packet(i).size(); + } + d->length = static_cast<int>(length + 0.5); + d->bitrate = static_cast<int>(fileLengthWithoutOverhead * 8.0 / length + 0.5); + } + } + else { + debug("Opus::Properties::read() -- The PCM values for the start or " + "end of this file was incorrect."); + } } else debug("Opus::Properties::read() -- Could not find valid first and last Ogg pages."); diff --git a/Frameworks/TagLib/taglib/taglib/ogg/opus/opusproperties.h b/Frameworks/TagLib/taglib/taglib/ogg/opus/opusproperties.h index 50dcae1b9..2e44dea5c 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/opus/opusproperties.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/opus/opusproperties.h @@ -1,5 +1,5 @@ /*************************************************************************** - copyright : (C) 2006 by Lukáš Lalinský + copyright : (C) 2012 by Lukáš Lalinský email : lalinsky@gmail.com copyright : (C) 2002 - 2008 by Scott Wheeler @@ -19,8 +19,8 @@ * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the Free Software * - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * - * USA * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * * * * Alternatively, this file is available under the Mozilla Public * * License Version 1.1. You may obtain a copy of the License at * @@ -30,7 +30,7 @@ #ifndef TAGLIB_OPUSPROPERTIES_H #define TAGLIB_OPUSPROPERTIES_H -#include <audioproperties.h> +#include "audioproperties.h" namespace TagLib { @@ -61,15 +61,60 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; - virtual int length() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + * + * \note Always returns 48000, because Opus can decode any stream at a + * sample rate of 8, 12, 16, 24, or 48 kHz, + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns the Opus version, currently "0" (as specified by the spec). + * The Opus codec supports decoding at multiple sample rates, there is no + * single sample rate of the encoded stream. This returns the sample rate + * of the original audio stream. + */ + int inputSampleRate() const; + + /*! + * Returns the Opus version, in the range 0...255. */ int opusVersion() const; @@ -77,7 +122,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/ogg/speex/speexfile.cpp b/Frameworks/TagLib/taglib/taglib/ogg/speex/speexfile.cpp index 604ac7c64..394cb17be 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/speex/speexfile.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/speex/speexfile.cpp @@ -27,10 +27,10 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include <bitset> - #include <tstring.h> #include <tdebug.h> +#include <tpropertymap.h> +#include <tagutils.h> #include "speexfile.h" @@ -54,15 +54,36 @@ public: Properties *properties; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool Ogg::Speex::File::isSupported(IOStream *stream) +{ + // A Speex file has IDs "OggS" and "Speex " somewhere. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false); + return (buffer.find("OggS") >= 0 && buffer.find("Speex ") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -Speex::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : Ogg::File(file) +Speex::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + Ogg::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties); +} + +Speex::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + Ogg::File(stream), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties); } Speex::File::~File() @@ -75,6 +96,16 @@ Ogg::XiphComment *Speex::File::tag() const return d->comment; } +PropertyMap Speex::File::properties() const +{ + return d->comment->properties(); +} + +PropertyMap Speex::File::setProperties(const PropertyMap &properties) +{ + return d->comment->setProperties(properties); +} + Speex::Properties *Speex::File::audioProperties() const { return d->properties; @@ -83,7 +114,7 @@ Speex::Properties *Speex::File::audioProperties() const bool Speex::File::save() { if(!d->comment) - d->comment = new Ogg::XiphComment; + d->comment = new Ogg::XiphComment(); setPacket(1, d->comment->render()); @@ -94,12 +125,13 @@ bool Speex::File::save() // private members //////////////////////////////////////////////////////////////////////////////// -void Speex::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void Speex::File::read(bool readProperties) { ByteVector speexHeaderData = packet(0); if(!speexHeaderData.startsWith("Speex ")) { debug("Speex::File::read() -- invalid Speex identification header"); + setValid(false); return; } @@ -108,5 +140,5 @@ void Speex::File::read(bool readProperties, Properties::ReadStyle propertiesStyl d->comment = new Ogg::XiphComment(commentHeaderData); if(readProperties) - d->properties = new Properties(this, propertiesStyle); + d->properties = new Properties(this); } diff --git a/Frameworks/TagLib/taglib/taglib/ogg/speex/speexfile.h b/Frameworks/TagLib/taglib/taglib/ogg/speex/speexfile.h index 508b7aaaf..1be7113cb 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/speex/speexfile.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/speex/speexfile.h @@ -56,13 +56,26 @@ namespace TagLib { { public: /*! - * Contructs a Speex file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs a Speex file from \a file. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs a Speex file from \a stream. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ @@ -75,19 +88,45 @@ namespace TagLib { */ virtual Ogg::XiphComment *tag() const; + /*! + * Implements the unified property interface -- export function. + * This forwards directly to XiphComment::properties(). + */ + PropertyMap properties() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Like properties(), this is a forwarder to the file's XiphComment. + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the Speex::Properties for this file. If no audio properties * were read then this will return a null pointer. */ virtual Properties *audioProperties() const; + /*! + * Save the file. + * + * This returns true if the save was successful. + */ virtual bool save(); + /*! + * Returns whether or not the given \a stream can be opened as a Speex + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/ogg/speex/speexproperties.cpp b/Frameworks/TagLib/taglib/taglib/ogg/speex/speexproperties.cpp index 980f12dfd..b7a11cc6e 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/speex/speexproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/speex/speexproperties.cpp @@ -41,21 +41,19 @@ using namespace TagLib::Ogg; class Speex::Properties::PropertiesPrivate { public: - PropertiesPrivate(File *f, ReadStyle s) : - file(f), - style(s), + PropertiesPrivate() : length(0), bitrate(0), + bitrateNominal(0), sampleRate(0), channels(0), speexVersion(0), vbr(false), mode(0) {} - File *file; - ReadStyle style; int length; int bitrate; + int bitrateNominal; int sampleRate; int channels; int speexVersion; @@ -67,10 +65,11 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -Speex::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +Speex::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file, style); - read(); + read(file); } Speex::Properties::~Properties() @@ -79,13 +78,28 @@ Speex::Properties::~Properties() } int Speex::Properties::length() const +{ + return lengthInSeconds(); +} + +int Speex::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int Speex::Properties::lengthInMilliseconds() const { return d->length; } int Speex::Properties::bitrate() const { - return int(float(d->bitrate) / float(1000) + 0.5); + return d->bitrate; +} + +int Speex::Properties::bitrateNominal() const +{ + return d->bitrateNominal; } int Speex::Properties::sampleRate() const @@ -107,38 +121,42 @@ int Speex::Properties::speexVersion() const // private members //////////////////////////////////////////////////////////////////////////////// -void Speex::Properties::read() +void Speex::Properties::read(File *file) { // Get the identification header from the Ogg implementation. - ByteVector data = d->file->packet(0); + const ByteVector data = file->packet(0); + if(data.size() < 64) { + debug("Speex::Properties::read() -- data is too short."); + return; + } - int pos = 28; + unsigned int pos = 28; // speex_version_id; /**< Version for Speex (for checking compatibility) */ - d->speexVersion = data.mid(pos, 4).toUInt(false); + d->speexVersion = data.toUInt(pos, false); pos += 4; // header_size; /**< Total size of the header ( sizeof(SpeexHeader) ) */ pos += 4; // rate; /**< Sampling rate used */ - d->sampleRate = data.mid(pos, 4).toUInt(false); + d->sampleRate = data.toUInt(pos, false); pos += 4; // mode; /**< Mode used (0 for narrowband, 1 for wideband) */ - d->mode = data.mid(pos, 4).toUInt(false); + d->mode = data.toUInt(pos, false); pos += 4; // mode_bitstream_version; /**< Version ID of the bit-stream */ pos += 4; // nb_channels; /**< Number of channels encoded */ - d->channels = data.mid(pos, 4).toUInt(false); + d->channels = data.toUInt(pos, false); pos += 4; // bitrate; /**< Bit-rate used */ - d->bitrate = data.mid(pos, 4).toUInt(false); + d->bitrateNominal = data.toUInt(pos, false); pos += 4; // frame_size; /**< Size of frames */ @@ -146,25 +164,44 @@ void Speex::Properties::read() pos += 4; // vbr; /**< 1 for a VBR encoding, 0 otherwise */ - d->vbr = data.mid(pos, 4).toUInt(false) == 1; + d->vbr = data.toUInt(pos, false) == 1; pos += 4; // frames_per_packet; /**< Number of frames stored per Ogg packet */ // unsigned int framesPerPacket = data.mid(pos, 4).toUInt(false); - const Ogg::PageHeader *first = d->file->firstPageHeader(); - const Ogg::PageHeader *last = d->file->lastPageHeader(); + const Ogg::PageHeader *first = file->firstPageHeader(); + const Ogg::PageHeader *last = file->lastPageHeader(); if(first && last) { - long long start = first->absoluteGranularPosition(); - long long end = last->absoluteGranularPosition(); + const long long start = first->absoluteGranularPosition(); + const long long end = last->absoluteGranularPosition(); - if(start >= 0 && end >= 0 && d->sampleRate > 0) - d->length = (int) ((end - start) / (long long) d->sampleRate); - else + if(start >= 0 && end >= 0 && d->sampleRate > 0) { + const long long frameCount = end - start; + + if(frameCount > 0) { + const double length = frameCount * 1000.0 / d->sampleRate; + long fileLengthWithoutOverhead = file->length(); + // Ignore the two header packets, see "Ogg file format" in + // https://www.speex.org/docs/manual/speex-manual/node8.html + for (unsigned int i = 0; i < 2; ++i) { + fileLengthWithoutOverhead -= file->packet(i).size(); + } + d->length = static_cast<int>(length + 0.5); + d->bitrate = static_cast<int>(fileLengthWithoutOverhead * 8.0 / length + 0.5); + } + } + else { debug("Speex::Properties::read() -- Either the PCM values for the start or " "end of this file was incorrect or the sample rate is zero."); + } } else debug("Speex::Properties::read() -- Could not find valid first and last Ogg pages."); + + // Alternative to the actual average bitrate. + + if(d->bitrate == 0 && d->bitrateNominal > 0) + d->bitrate = static_cast<int>(d->bitrateNominal / 1000.0 + 0.5); } diff --git a/Frameworks/TagLib/taglib/taglib/ogg/speex/speexproperties.h b/Frameworks/TagLib/taglib/taglib/ogg/speex/speexproperties.h index 4720bd881..3f5a20f30 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/speex/speexproperties.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/speex/speexproperties.h @@ -61,11 +61,51 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; - virtual int length() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the nominal bit rate as read from the Speex header in kb/s. + */ + int bitrateNominal() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! @@ -77,7 +117,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisfile.cpp b/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisfile.cpp index 1098ec389..b4f221ab1 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisfile.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisfile.cpp @@ -27,6 +27,8 @@ #include <tstring.h> #include <tdebug.h> +#include <tpropertymap.h> +#include <tagutils.h> #include "vorbisfile.h" @@ -57,15 +59,36 @@ namespace TagLib { static const char vorbisCommentHeaderID[] = { 0x03, 'v', 'o', 'r', 'b', 'i', 's', 0 }; } +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool Vorbis::File::isSupported(IOStream *stream) +{ + // An Ogg Vorbis file has IDs "OggS" and "\x01vorbis" somewhere. + + const ByteVector buffer = Utils::readHeader(stream, bufferSize(), false); + return (buffer.find("OggS") >= 0 && buffer.find("\x01vorbis") >= 0); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -Vorbis::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : Ogg::File(file) +Vorbis::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + Ogg::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties); +} + +Vorbis::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + Ogg::File(stream), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties); } Vorbis::File::~File() @@ -78,6 +101,16 @@ Ogg::XiphComment *Vorbis::File::tag() const return d->comment; } +PropertyMap Vorbis::File::properties() const +{ + return d->comment->properties(); +} + +PropertyMap Vorbis::File::setProperties(const PropertyMap &properties) +{ + return d->comment->setProperties(properties); +} + Vorbis::Properties *Vorbis::File::audioProperties() const { return d->properties; @@ -88,7 +121,7 @@ bool Vorbis::File::save() ByteVector v(vorbisCommentHeaderID); if(!d->comment) - d->comment = new Ogg::XiphComment; + d->comment = new Ogg::XiphComment(); v.append(d->comment->render()); setPacket(1, v); @@ -100,7 +133,7 @@ bool Vorbis::File::save() // private members //////////////////////////////////////////////////////////////////////////////// -void Vorbis::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void Vorbis::File::read(bool readProperties) { ByteVector commentHeaderData = packet(1); @@ -113,5 +146,5 @@ void Vorbis::File::read(bool readProperties, Properties::ReadStyle propertiesSty d->comment = new Ogg::XiphComment(commentHeaderData.mid(7)); if(readProperties) - d->properties = new Properties(this, propertiesStyle); + d->properties = new Properties(this); } diff --git a/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisfile.h b/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisfile.h index 3e33c113c..04c0c04ea 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisfile.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisfile.h @@ -63,13 +63,26 @@ namespace TagLib { { public: /*! - * Contructs a Vorbis file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs a Vorbis file from \a file. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs a Vorbis file from \a stream. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ @@ -82,19 +95,45 @@ namespace TagLib { */ virtual Ogg::XiphComment *tag() const; + + /*! + * Implements the unified property interface -- export function. + * This forwards directly to XiphComment::properties(). + */ + PropertyMap properties() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Like properties(), this is a forwarder to the file's XiphComment. + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the Vorbis::Properties for this file. If no audio properties * were read then this will return a null pointer. */ virtual Properties *audioProperties() const; + /*! + * Save the file. + * + * This returns true if the save was successful. + */ virtual bool save(); + /*! + * Check if the given \a stream can be opened as an Ogg Vorbis file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisproperties.cpp b/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisproperties.cpp index 6de6a5998..4000c254d 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisproperties.cpp @@ -36,9 +36,7 @@ using namespace TagLib; class Vorbis::Properties::PropertiesPrivate { public: - PropertiesPrivate(File *f, ReadStyle s) : - file(f), - style(s), + PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), @@ -48,8 +46,6 @@ public: bitrateNominal(0), bitrateMinimum(0) {} - File *file; - ReadStyle style; int length; int bitrate; int sampleRate; @@ -72,10 +68,11 @@ namespace TagLib { // public members //////////////////////////////////////////////////////////////////////////////// -Vorbis::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +Vorbis::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(file, style); - read(); + read(file); } Vorbis::Properties::~Properties() @@ -84,13 +81,23 @@ Vorbis::Properties::~Properties() } int Vorbis::Properties::length() const +{ + return lengthInSeconds(); +} + +int Vorbis::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int Vorbis::Properties::lengthInMilliseconds() const { return d->length; } int Vorbis::Properties::bitrate() const { - return int(float(d->bitrate) / float(1000) + 0.5); + return d->bitrate; } int Vorbis::Properties::sampleRate() const @@ -127,13 +134,17 @@ int Vorbis::Properties::bitrateMinimum() const // private members //////////////////////////////////////////////////////////////////////////////// -void Vorbis::Properties::read() +void Vorbis::Properties::read(File *file) { // Get the identification header from the Ogg implementation. - ByteVector data = d->file->packet(0); + const ByteVector data = file->packet(0); + if(data.size() < 28) { + debug("Vorbis::Properties::read() -- data is too short."); + return; + } - int pos = 0; + unsigned int pos = 0; if(data.mid(pos, 7) != vorbisSetupHeaderID) { debug("Vorbis::Properties::read() -- invalid Vorbis identification header"); @@ -142,42 +153,59 @@ void Vorbis::Properties::read() pos += 7; - d->vorbisVersion = data.mid(pos, 4).toUInt(false); + d->vorbisVersion = data.toUInt(pos, false); pos += 4; - d->channels = uchar(data[pos]); + d->channels = static_cast<unsigned char>(data[pos]); pos += 1; - d->sampleRate = data.mid(pos, 4).toUInt(false); + d->sampleRate = data.toUInt(pos, false); pos += 4; - d->bitrateMaximum = data.mid(pos, 4).toUInt(false); + d->bitrateMaximum = data.toUInt(pos, false); pos += 4; - d->bitrateNominal = data.mid(pos, 4).toUInt(false); + d->bitrateNominal = data.toUInt(pos, false); pos += 4; - d->bitrateMinimum = data.mid(pos, 4).toUInt(false); - - // TODO: Later this should be only the "fast" mode. - d->bitrate = d->bitrateNominal; + d->bitrateMinimum = data.toUInt(pos, false); + pos += 4; // Find the length of the file. See http://wiki.xiph.org/VorbisStreamLength/ // for my notes on the topic. - const Ogg::PageHeader *first = d->file->firstPageHeader(); - const Ogg::PageHeader *last = d->file->lastPageHeader(); + const Ogg::PageHeader *first = file->firstPageHeader(); + const Ogg::PageHeader *last = file->lastPageHeader(); if(first && last) { - long long start = first->absoluteGranularPosition(); - long long end = last->absoluteGranularPosition(); + const long long start = first->absoluteGranularPosition(); + const long long end = last->absoluteGranularPosition(); - if(start >= 0 && end >= 0 && d->sampleRate > 0) - d->length = (end - start) / (long long) d->sampleRate; - else + if(start >= 0 && end >= 0 && d->sampleRate > 0) { + const long long frameCount = end - start; + + if(frameCount > 0) { + const double length = frameCount * 1000.0 / d->sampleRate; + long fileLengthWithoutOverhead = file->length(); + // Ignore the three initial header packets, see "1.3.1. Decode Setup" in + // https://xiph.org/vorbis/doc/Vorbis_I_spec.html + for (unsigned int i = 0; i < 3; ++i) { + fileLengthWithoutOverhead -= file->packet(i).size(); + } + d->length = static_cast<int>(length + 0.5); + d->bitrate = static_cast<int>(fileLengthWithoutOverhead * 8.0 / length + 0.5); + } + } + else { debug("Vorbis::Properties::read() -- Either the PCM values for the start or " "end of this file was incorrect or the sample rate is zero."); + } } else debug("Vorbis::Properties::read() -- Could not find valid first and last Ogg pages."); + + // Alternative to the actual average bitrate. + + if(d->bitrate == 0 && d->bitrateNominal > 0) + d->bitrate = static_cast<int>(d->bitrateNominal / 1000.0 + 0.5); } diff --git a/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisproperties.h b/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisproperties.h index de46985b6..472e0390a 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisproperties.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/vorbis/vorbisproperties.h @@ -67,11 +67,46 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; - virtual int length() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! @@ -101,7 +136,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp index 1d34abd56..f56bf810c 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp +++ b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.cpp @@ -26,30 +26,50 @@ #include <tbytevector.h> #include <tdebug.h> +#include <flacpicture.h> #include <xiphcomment.h> +#include <tpropertymap.h> using namespace TagLib; +namespace +{ + typedef Ogg::FieldListMap::Iterator FieldIterator; + typedef Ogg::FieldListMap::ConstIterator FieldConstIterator; + + typedef List<FLAC::Picture *> PictureList; + typedef PictureList::Iterator PictureIterator; + typedef PictureList::Iterator PictureConstIterator; +} + class Ogg::XiphComment::XiphCommentPrivate { public: + XiphCommentPrivate() + { + pictureList.setAutoDelete(true); + } + FieldListMap fieldListMap; String vendorID; String commentField; + PictureList pictureList; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -Ogg::XiphComment::XiphComment() : TagLib::Tag() +Ogg::XiphComment::XiphComment() : + TagLib::Tag(), + d(new XiphCommentPrivate()) { - d = new XiphCommentPrivate; } -Ogg::XiphComment::XiphComment(const ByteVector &data) : TagLib::Tag() +Ogg::XiphComment::XiphComment(const ByteVector &data) : + TagLib::Tag(), + d(new XiphCommentPrivate()) { - d = new XiphCommentPrivate; parse(data); } @@ -61,47 +81,47 @@ Ogg::XiphComment::~XiphComment() String Ogg::XiphComment::title() const { if(d->fieldListMap["TITLE"].isEmpty()) - return String::null; - return d->fieldListMap["TITLE"].front(); + return String(); + return d->fieldListMap["TITLE"].toString(); } String Ogg::XiphComment::artist() const { if(d->fieldListMap["ARTIST"].isEmpty()) - return String::null; - return d->fieldListMap["ARTIST"].front(); + return String(); + return d->fieldListMap["ARTIST"].toString(); } String Ogg::XiphComment::album() const { if(d->fieldListMap["ALBUM"].isEmpty()) - return String::null; - return d->fieldListMap["ALBUM"].front(); + return String(); + return d->fieldListMap["ALBUM"].toString(); } String Ogg::XiphComment::comment() const { if(!d->fieldListMap["DESCRIPTION"].isEmpty()) { d->commentField = "DESCRIPTION"; - return d->fieldListMap["DESCRIPTION"].front(); + return d->fieldListMap["DESCRIPTION"].toString(); } if(!d->fieldListMap["COMMENT"].isEmpty()) { d->commentField = "COMMENT"; - return d->fieldListMap["COMMENT"].front(); + return d->fieldListMap["COMMENT"].toString(); } - return String::null; + return String(); } String Ogg::XiphComment::genre() const { if(d->fieldListMap["GENRE"].isEmpty()) - return String::null; - return d->fieldListMap["GENRE"].front(); + return String(); + return d->fieldListMap["GENRE"].toString(); } -TagLib::uint Ogg::XiphComment::year() const +unsigned int Ogg::XiphComment::year() const { if(!d->fieldListMap["DATE"].isEmpty()) return d->fieldListMap["DATE"].front().toInt(); @@ -110,7 +130,7 @@ TagLib::uint Ogg::XiphComment::year() const return 0; } -TagLib::uint Ogg::XiphComment::track() const +unsigned int Ogg::XiphComment::track() const { if(!d->fieldListMap["TRACKNUMBER"].isEmpty()) return d->fieldListMap["TRACKNUMBER"].front().toInt(); @@ -119,34 +139,6 @@ TagLib::uint Ogg::XiphComment::track() const return 0; } -float Ogg::XiphComment::rgAlbumGain() const -{ - if(d->fieldListMap["REPLAYGAIN_ALBUM_GAIN"].isEmpty()) - return 0; - return d->fieldListMap["REPLAYGAIN_ALBUM_GAIN"].front().toFloat(); -} - -float Ogg::XiphComment::rgAlbumPeak() const -{ - if(d->fieldListMap["REPLAYGAIN_ALBUM_PEAK"].isEmpty()) - return 0; - return d->fieldListMap["REPLAYGAIN_ALBUM_PEAK"].front().toFloat(); -} - -float Ogg::XiphComment::rgTrackGain() const -{ - if(d->fieldListMap["REPLAYGAIN_TRACK_GAIN"].isEmpty()) - return 0; - return d->fieldListMap["REPLAYGAIN_TRACK_GAIN"].front().toFloat(); -} - -float Ogg::XiphComment::rgTrackPeak() const -{ - if(d->fieldListMap["REPLAYGAIN_TRACK_PEAK"].isEmpty()) - return 0; - return d->fieldListMap["REPLAYGAIN_TRACK_PEAK"].front().toFloat(); -} - void Ogg::XiphComment::setTitle(const String &s) { addField("TITLE", s); @@ -164,7 +156,14 @@ void Ogg::XiphComment::setAlbum(const String &s) void Ogg::XiphComment::setComment(const String &s) { - addField(d->commentField.isEmpty() ? "DESCRIPTION" : d->commentField, s); + if(d->commentField.isEmpty()) { + if(!d->fieldListMap["DESCRIPTION"].isEmpty()) + d->commentField = "DESCRIPTION"; + else + d->commentField = "COMMENT"; + } + + addField(d->commentField, s); } void Ogg::XiphComment::setGenre(const String &s) @@ -172,74 +171,43 @@ void Ogg::XiphComment::setGenre(const String &s) addField("GENRE", s); } -void Ogg::XiphComment::setYear(uint i) +void Ogg::XiphComment::setYear(unsigned int i) { - removeField("YEAR"); + removeFields("YEAR"); if(i == 0) - removeField("DATE"); + removeFields("DATE"); else addField("DATE", String::number(i)); } -void Ogg::XiphComment::setTrack(uint i) +void Ogg::XiphComment::setTrack(unsigned int i) { - removeField("TRACKNUM"); + removeFields("TRACKNUM"); if(i == 0) - removeField("TRACKNUMBER"); + removeFields("TRACKNUMBER"); else addField("TRACKNUMBER", String::number(i)); } -void Ogg::XiphComment::setRGAlbumGain(float f) -{ - if (f == 0) - removeField("REPLAYGAIN_ALBUM_GAIN"); - else - addField("REPLAYGAIN_ALBUM_GAIN", String::number(f) + " dB"); -} - -void Ogg::XiphComment::setRGAlbumPeak(float f) -{ - if (f == 0) - removeField("REPLAYGAIN_ALBUM_PEAK"); - else - addField("REPLAYGAIN_ALBUM_PEAK", String::number(f)); -} - -void Ogg::XiphComment::setRGTrackGain(float f) -{ - if (f == 0) - removeField("REPLAYGAIN_TRACK_GAIN"); - else - addField("REPLAYGAIN_TRACK_GAIN", String::number(f) + " dB"); -} - -void Ogg::XiphComment::setRGTrackPeak(float f) -{ - if (f == 0) - removeField("REPLAYGAIN_TRACK_PEAK"); - else - addField("REPLAYGAIN_TRACK_PEAK", String::number(f)); -} - bool Ogg::XiphComment::isEmpty() const { - FieldListMap::ConstIterator it = d->fieldListMap.begin(); - for(; it != d->fieldListMap.end(); ++it) + for(FieldConstIterator it = d->fieldListMap.begin(); it != d->fieldListMap.end(); ++it) { if(!(*it).second.isEmpty()) return false; + } return true; } -TagLib::uint Ogg::XiphComment::fieldCount() const +unsigned int Ogg::XiphComment::fieldCount() const { - uint count = 0; + unsigned int count = 0; - FieldListMap::ConstIterator it = d->fieldListMap.begin(); - for(; it != d->fieldListMap.end(); ++it) + for(FieldConstIterator it = d->fieldListMap.begin(); it != d->fieldListMap.end(); ++it) count += (*it).second.size(); + count += d->pictureList.size(); + return count; } @@ -248,6 +216,62 @@ const Ogg::FieldListMap &Ogg::XiphComment::fieldListMap() const return d->fieldListMap; } +PropertyMap Ogg::XiphComment::properties() const +{ + return d->fieldListMap; +} + +PropertyMap Ogg::XiphComment::setProperties(const PropertyMap &properties) +{ + // check which keys are to be deleted + StringList toRemove; + for(FieldConstIterator it = d->fieldListMap.begin(); it != d->fieldListMap.end(); ++it) + if (!properties.contains(it->first)) + toRemove.append(it->first); + + for(StringList::ConstIterator it = toRemove.begin(); it != toRemove.end(); ++it) + removeFields(*it); + + // now go through keys in \a properties and check that the values match those in the xiph comment + PropertyMap invalid; + PropertyMap::ConstIterator it = properties.begin(); + for(; it != properties.end(); ++it) + { + if(!checkKey(it->first)) + invalid.insert(it->first, it->second); + else if(!d->fieldListMap.contains(it->first) || !(it->second == d->fieldListMap[it->first])) { + const StringList &sl = it->second; + if(sl.isEmpty()) + // zero size string list -> remove the tag with all values + removeFields(it->first); + else { + // replace all strings in the list for the tag + StringList::ConstIterator valueIterator = sl.begin(); + addField(it->first, *valueIterator, true); + ++valueIterator; + for(; valueIterator != sl.end(); ++valueIterator) + addField(it->first, *valueIterator, false); + } + } + } + return invalid; +} + +bool Ogg::XiphComment::checkKey(const String &key) +{ + if(key.size() < 1) + return false; + + // A key may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded. + + for(String::ConstIterator it = key.begin(); it != key.end(); it++) { + if(*it < 0x20 || *it > 0x7D || *it == 0x3D) + return false; + } + + return true; +} + String Ogg::XiphComment::vendorID() const { return d->vendorID; @@ -255,31 +279,77 @@ String Ogg::XiphComment::vendorID() const void Ogg::XiphComment::addField(const String &key, const String &value, bool replace) { + if(!checkKey(key)) { + debug("Ogg::XiphComment::addField() - Invalid key. Field not added."); + return; + } + + const String upperKey = key.upper(); + if(replace) - removeField(key.upper()); + removeFields(upperKey); if(!key.isEmpty() && !value.isEmpty()) - d->fieldListMap[key.upper()].append(value); + d->fieldListMap[upperKey].append(value); } void Ogg::XiphComment::removeField(const String &key, const String &value) { - if(!value.isNull()) { - StringList::Iterator it = d->fieldListMap[key].begin(); - while(it != d->fieldListMap[key].end()) { - if(value == *it) - it = d->fieldListMap[key].erase(it); - else - it++; - } - } + if(!value.isNull()) + removeFields(key, value); else - d->fieldListMap.erase(key); + removeFields(key); +} + +void Ogg::XiphComment::removeFields(const String &key) +{ + d->fieldListMap.erase(key.upper()); +} + +void Ogg::XiphComment::removeFields(const String &key, const String &value) +{ + StringList &fields = d->fieldListMap[key.upper()]; + for(StringList::Iterator it = fields.begin(); it != fields.end(); ) { + if(*it == value) + it = fields.erase(it); + else + ++it; + } +} + +void Ogg::XiphComment::removeAllFields() +{ + d->fieldListMap.clear(); } bool Ogg::XiphComment::contains(const String &key) const { - return d->fieldListMap.contains(key) && !d->fieldListMap[key].isEmpty(); + return !d->fieldListMap[key.upper()].isEmpty(); +} + +void Ogg::XiphComment::removePicture(FLAC::Picture *picture, bool del) +{ + PictureIterator it = d->pictureList.find(picture); + if(it != d->pictureList.end()) + d->pictureList.erase(it); + + if(del) + delete picture; +} + +void Ogg::XiphComment::removeAllPictures() +{ + d->pictureList.clear(); +} + +void Ogg::XiphComment::addPicture(FLAC::Picture * picture) +{ + d->pictureList.append(picture); +} + +List<FLAC::Picture *> Ogg::XiphComment::pictureList() +{ + return d->pictureList; } ByteVector Ogg::XiphComment::render() const @@ -328,6 +398,13 @@ ByteVector Ogg::XiphComment::render(bool addFramingBit) const } } + for(PictureConstIterator it = d->pictureList.begin(); it != d->pictureList.end(); ++it) { + ByteVector picture = (*it)->render().toBase64(); + data.append(ByteVector::fromUInt(picture.size() + 23, false)); + data.append("METADATA_BLOCK_PICTURE="); + data.append(picture); + } + // Append the "framing bit". if(addFramingBit) @@ -345,9 +422,9 @@ void Ogg::XiphComment::parse(const ByteVector &data) // The first thing in the comment data is the vendor ID length, followed by a // UTF8 string with the vendor ID. - int pos = 0; + unsigned int pos = 0; - int vendorLength = data.mid(0, 4).toUInt(false); + const unsigned int vendorLength = data.toUInt(0, false); pos += 4; d->vendorID = String(data.mid(pos, vendorLength), String::UTF8); @@ -355,25 +432,84 @@ void Ogg::XiphComment::parse(const ByteVector &data) // Next the number of fields in the comment vector. - int commentFields = data.mid(pos, 4).toUInt(false); + const unsigned int commentFields = data.toUInt(pos, false); pos += 4; - for(int i = 0; i < commentFields; i++) { + if(commentFields > (data.size() - 8) / 4) { + return; + } + + for(unsigned int i = 0; i < commentFields; i++) { // Each comment field is in the format "KEY=value" in a UTF8 string and has // 4 bytes before the text starts that gives the length. - int commentLength = data.mid(pos, 4).toUInt(false); + const unsigned int commentLength = data.toUInt(pos, false); pos += 4; - String comment = String(data.mid(pos, commentLength), String::UTF8); + const ByteVector entry = data.mid(pos, commentLength); pos += commentLength; - int commentSeparatorPosition = comment.find("="); + // Don't go past data end - String key = comment.substr(0, commentSeparatorPosition); - String value = comment.substr(commentSeparatorPosition + 1); + if(pos > data.size()) + break; - addField(key, value, false); + // Check for field separator + + const int sep = entry.find('='); + if(sep < 1) { + debug("Ogg::XiphComment::parse() - Discarding a field. Separator not found."); + continue; + } + + // Parse the key + + const String key = String(entry.mid(0, sep), String::UTF8).upper(); + if(!checkKey(key)) { + debug("Ogg::XiphComment::parse() - Discarding a field. Invalid key."); + continue; + } + + if(key == "METADATA_BLOCK_PICTURE" || key == "COVERART") { + + // Handle Pictures separately + + const ByteVector picturedata = ByteVector::fromBase64(entry.mid(sep + 1)); + if(picturedata.isEmpty()) { + debug("Ogg::XiphComment::parse() - Discarding a field. Invalid base64 data"); + continue; + } + + if(key[0] == L'M') { + + // Decode FLAC Picture + + FLAC::Picture * picture = new FLAC::Picture(); + if(picture->parse(picturedata)) { + d->pictureList.append(picture); + } + else { + delete picture; + debug("Ogg::XiphComment::parse() - Failed to decode FLAC Picture block"); + } + } + else { + + // Assume it's some type of image file + + FLAC::Picture * picture = new FLAC::Picture(); + picture->setData(picturedata); + picture->setMimeType("image/"); + picture->setType(FLAC::Picture::Other); + d->pictureList.append(picture); + } + } + else { + + // Parse the text + + addField(key, String(entry.mid(sep + 1), String::UTF8), false); + } } } diff --git a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h index ba994f552..674d61697 100644 --- a/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h +++ b/Frameworks/TagLib/taglib/taglib/ogg/xiphcomment.h @@ -32,6 +32,7 @@ #include "tstring.h" #include "tstringlist.h" #include "tbytevector.h" +#include "flacpicture.h" #include "taglib_export.h" namespace TagLib { @@ -84,31 +85,23 @@ namespace TagLib { virtual String album() const; virtual String comment() const; virtual String genre() const; - virtual uint year() const; - virtual uint track() const; - virtual float rgAlbumGain() const; - virtual float rgAlbumPeak() const; - virtual float rgTrackGain() const; - virtual float rgTrackPeak() const; + virtual unsigned int year() const; + virtual unsigned int track() const; virtual void setTitle(const String &s); virtual void setArtist(const String &s); virtual void setAlbum(const String &s); virtual void setComment(const String &s); virtual void setGenre(const String &s); - virtual void setYear(uint i); - virtual void setTrack(uint i); - virtual void setRGAlbumGain(float f); - virtual void setRGAlbumPeak(float f); - virtual void setRGTrackGain(float f); - virtual void setRGTrackPeak(float f); + virtual void setYear(unsigned int i); + virtual void setTrack(unsigned int i); virtual bool isEmpty() const; /*! * Returns the number of fields present in the comment. */ - uint fieldCount() const; + unsigned int fieldCount() const; /*! * Returns a reference to the map of field lists. Because Xiph comments @@ -148,6 +141,29 @@ namespace TagLib { */ const FieldListMap &fieldListMap() const; + /*! + * Implements the unified property interface -- export function. + * The result is a one-to-one match of the Xiph comment, since it is + * completely compatible with the property interface (in fact, a Xiph + * comment is nothing more than a map from tag names to list of values, + * as is the dict interface). + */ + PropertyMap properties() const; + + /*! + * Implements the unified property interface -- import function. + * The tags from the given map will be stored one-to-one in the file, + * except for invalid keys (less than one character, non-ASCII, or + * containing '=' or '~') in which case the according values will + * be contained in the returned PropertyMap. + */ + PropertyMap setProperties(const PropertyMap&); + + /*! + * Check if the given String is a valid Xiph comment key. + */ + static bool checkKey(const String&); + /*! * Returns the vendor ID of the Ogg Vorbis encoder. libvorbis 1.0 as the * most common case always returns "Xiph.Org libVorbis I 20020717". @@ -166,8 +182,32 @@ namespace TagLib { /*! * Remove the field specified by \a key with the data \a value. If * \a value is null, all of the fields with the given key will be removed. + * + * \deprecated Using this method may lead to a linkage error. */ - void removeField(const String &key, const String &value = String::null); + // BIC: remove and merge with below + TAGLIB_DEPRECATED void removeField(const String &key, const String &value = String()); + + /*! + * Remove all the fields specified by \a key. + * + * \see removeAllFields() + */ + void removeFields(const String &key); + + /*! + * Remove all the fields specified by \a key with the data \a value. + * + * \see removeAllFields() + */ + void removeFields(const String &key, const String &value); + + /*! + * Remove all the fields in the comment. + * + * \see removeFields() + */ + void removeAllFields(); /*! * Returns true if the field is contained within the comment. @@ -190,6 +230,31 @@ namespace TagLib { */ ByteVector render(bool addFramingBit) const; + + /*! + * Returns a list of pictures attached to the xiph comment. + */ + List<FLAC::Picture *> pictureList(); + + /*! + * Removes an picture. If \a del is true the picture's memory + * will be freed; if it is false, it must be deleted by the user. + */ + void removePicture(FLAC::Picture *picture, bool del = true); + + /*! + * Remove all pictures. + */ + void removeAllPictures(); + + /*! + * Add a new picture to the comment block. The comment block takes ownership of the + * picture and will handle freeing its memory. + * + * \note The file will be saved only after calling save(). + */ + void addPicture(FLAC::Picture *picture); + protected: /*! * Reads the tag from the file specified in the constructor and fills the diff --git a/Frameworks/TagLib/taglib/taglib/riff/aiff/aifffile.cpp b/Frameworks/TagLib/taglib/taglib/riff/aiff/aifffile.cpp index 425bfa020..6ef4c9be5 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/aiff/aifffile.cpp +++ b/Frameworks/TagLib/taglib/taglib/riff/aiff/aifffile.cpp @@ -26,6 +26,9 @@ #include <tbytevector.h> #include <tdebug.h> #include <id3v2tag.h> +#include <tstringlist.h> +#include <tpropertymap.h> +#include <tagutils.h> #include "aifffile.h" @@ -37,10 +40,7 @@ public: FilePrivate() : properties(0), tag(0), - tagChunkID("ID3 ") - { - - } + hasID3v2(false) {} ~FilePrivate() { @@ -50,19 +50,40 @@ public: Properties *properties; ID3v2::Tag *tag; - ByteVector tagChunkID; + + bool hasID3v2; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool RIFF::AIFF::File::isSupported(IOStream *stream) +{ + // An AIFF file has to start with "FORM????AIFF" or "FORM????AIFC". + + const ByteVector id = Utils::readHeader(stream, 12, false); + return (id.startsWith("FORM") && (id.containsAt("AIFF", 8) || id.containsAt("AIFC", 8))); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::AIFF::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : RIFF::File(file, BigEndian) +RIFF::AIFF::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + RIFF::File(file, BigEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); +} + +RIFF::AIFF::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + RIFF::File(stream, BigEndian), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties); } RIFF::AIFF::File::~File() @@ -75,38 +96,84 @@ ID3v2::Tag *RIFF::AIFF::File::tag() const return d->tag; } +PropertyMap RIFF::AIFF::File::properties() const +{ + return d->tag->properties(); +} + +void RIFF::AIFF::File::removeUnsupportedProperties(const StringList &unsupported) +{ + d->tag->removeUnsupportedProperties(unsupported); +} + +PropertyMap RIFF::AIFF::File::setProperties(const PropertyMap &properties) +{ + return d->tag->setProperties(properties); +} + RIFF::AIFF::Properties *RIFF::AIFF::File::audioProperties() const { return d->properties; } bool RIFF::AIFF::File::save() +{ + return save(ID3v2::v4); +} + +bool RIFF::AIFF::File::save(ID3v2::Version version) { if(readOnly()) { debug("RIFF::AIFF::File::save() -- File is read only."); return false; } - setChunkData(d->tagChunkID, d->tag->render()); + if(!isValid()) { + debug("RIFF::AIFF::File::save() -- Trying to save invalid file."); + return false; + } + + if(d->hasID3v2) { + removeChunk("ID3 "); + removeChunk("id3 "); + d->hasID3v2 = false; + } + + if(tag() && !tag()->isEmpty()) { + setChunkData("ID3 ", d->tag->render(version)); + d->hasID3v2 = true; + } return true; } +bool RIFF::AIFF::File::hasID3v2Tag() const +{ + return d->hasID3v2; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::AIFF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void RIFF::AIFF::File::read(bool readProperties) { - for(uint i = 0; i < chunkCount(); i++) { - if(chunkName(i) == "ID3 " || chunkName(i) == "id3 ") { - d->tagChunkID = chunkName(i); - d->tag = new ID3v2::Tag(this, chunkOffset(i)); + for(unsigned int i = 0; i < chunkCount(); ++i) { + const ByteVector name = chunkName(i); + if(name == "ID3 " || name == "id3 ") { + if(!d->tag) { + d->tag = new ID3v2::Tag(this, chunkOffset(i)); + d->hasID3v2 = true; + } + else { + debug("RIFF::AIFF::File::read() - Duplicate ID3v2 tag found."); + } } - else if(chunkName(i) == "COMM" && readProperties) - d->properties = new Properties(chunkData(i), propertiesStyle); } if(!d->tag) - d->tag = new ID3v2::Tag; + d->tag = new ID3v2::Tag(); + + if(readProperties) + d->properties = new Properties(this, Properties::Average); } diff --git a/Frameworks/TagLib/taglib/taglib/riff/aiff/aifffile.h b/Frameworks/TagLib/taglib/taglib/riff/aiff/aifffile.h index b9b0809f9..ceac5769e 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/aiff/aifffile.h +++ b/Frameworks/TagLib/taglib/taglib/riff/aiff/aifffile.h @@ -58,13 +58,26 @@ namespace TagLib { { public: /*! - * Contructs an AIFF file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs an AIFF file from \a file. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs an AIFF file from \a stream. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ @@ -72,9 +85,29 @@ namespace TagLib { /*! * Returns the Tag for this file. + * + * \note This always returns a valid pointer regardless of whether or not + * the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file + * on disk actually has an ID3v2 tag. + * + * \see hasID3v2Tag() */ virtual ID3v2::Tag *tag() const; + /*! + * Implements the unified property interface -- export function. + * This method forwards to ID3v2::Tag::properties(). + */ + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + * This method forwards to ID3v2::Tag::setProperties(). + */ + PropertyMap setProperties(const PropertyMap &); + /*! * Returns the AIFF::Properties for this file. If no audio properties * were read then this will return a null pointer. @@ -86,11 +119,33 @@ namespace TagLib { */ virtual bool save(); + /*! + * Save using a specific ID3v2 version (e.g. v3) + */ + bool save(ID3v2::Version version); + + /*! + * Returns whether or not the file on disk actually has an ID3v2 tag. + * + * \see ID3v2Tag() + */ + bool hasID3v2Tag() const; + + /*! + * Check if the given \a stream can be opened as an AIFF file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); + + friend class Properties; class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/riff/aiff/aiffproperties.cpp b/Frameworks/TagLib/taglib/taglib/riff/aiff/aiffproperties.cpp index 77c3d2776..e1d954feb 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/aiff/aiffproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/riff/aiff/aiffproperties.cpp @@ -25,56 +25,9 @@ #include <tstring.h> #include <tdebug.h> -#include <cmath> -// ldexp is a c99 function, which might not be defined in <cmath> -// so we pull in math.h too and hope it does the right (wrong) thing -// wrt. c99 functions in C++ -#include <math.h> - +#include "aifffile.h" #include "aiffproperties.h" -//////////////////////////////////////////////////////////////////////////////// -// nasty 80-bit float helpers -//////////////////////////////////////////////////////////////////////////////// - -#define UnsignedToFloat(u) (((double)((long)(u - 2147483647L - 1))) + 2147483648.0) - -static double ConvertFromIeeeExtended(unsigned char *bytes) -{ - double f; - int expon; - unsigned long hiMant, loMant; - - expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF); - - hiMant = ((unsigned long)(bytes[2] & 0xFF) << 24) | - ((unsigned long)(bytes[3] & 0xFF) << 16) | - ((unsigned long)(bytes[4] & 0xFF) << 8) | - ((unsigned long)(bytes[5] & 0xFF)); - - loMant = ((unsigned long)(bytes[6] & 0xFF) << 24) | - ((unsigned long)(bytes[7] & 0xFF) << 16) | - ((unsigned long)(bytes[8] & 0xFF) << 8) | - ((unsigned long)(bytes[9] & 0xFF)); - - if (expon == 0 && hiMant == 0 && loMant == 0) - f = 0; - else { - if(expon == 0x7FFF) /* Infinity or NaN */ - f = HUGE_VAL; - else { - expon -= 16383; - f = ldexp(UnsignedToFloat(hiMant), expon -= 31); - f += ldexp(UnsignedToFloat(loMant), expon -= 32); - } - } - - if(bytes[0] & 0x80) - return -f; - else - return f; -} - using namespace TagLib; class RIFF::AIFF::Properties::PropertiesPrivate @@ -85,26 +38,37 @@ public: bitrate(0), sampleRate(0), channels(0), - sampleWidth(0) - { - - } + bitsPerSample(0), + sampleFrames(0) {} int length; int bitrate; int sampleRate; int channels; - int sampleWidth; + int bitsPerSample; + + ByteVector compressionType; + String compressionName; + + unsigned int sampleFrames; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::AIFF::Properties::Properties(const ByteVector &data, ReadStyle style) : AudioProperties(style) +RIFF::AIFF::Properties::Properties(const ByteVector &, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate; - read(data); + debug("RIFF::AIFF::Properties::Properties() - This constructor is no longer used."); +} + +RIFF::AIFF::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) +{ + read(file); } RIFF::AIFF::Properties::~Properties() @@ -113,6 +77,16 @@ RIFF::AIFF::Properties::~Properties() } int RIFF::AIFF::Properties::length() const +{ + return lengthInSeconds(); +} + +int RIFF::AIFF::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int RIFF::AIFF::Properties::lengthInMilliseconds() const { return d->length; } @@ -132,22 +106,87 @@ int RIFF::AIFF::Properties::channels() const return d->channels; } +int RIFF::AIFF::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + int RIFF::AIFF::Properties::sampleWidth() const { - return d->sampleWidth; + return bitsPerSample(); +} + +unsigned int RIFF::AIFF::Properties::sampleFrames() const +{ + return d->sampleFrames; +} + +bool RIFF::AIFF::Properties::isAiffC() const +{ + return (!d->compressionType.isEmpty()); +} + +ByteVector RIFF::AIFF::Properties::compressionType() const +{ + return d->compressionType; +} + +String RIFF::AIFF::Properties::compressionName() const +{ + return d->compressionName; } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::AIFF::Properties::read(const ByteVector &data) +void RIFF::AIFF::Properties::read(File *file) { - d->channels = data.mid(0, 2).toShort(); - uint sampleFrames = data.mid(2, 4).toUInt(); - d->sampleWidth = data.mid(6, 2).toShort(); - double sampleRate = ConvertFromIeeeExtended(reinterpret_cast<unsigned char *>(data.mid(8, 10).data())); - d->sampleRate = sampleRate; - d->bitrate = (sampleRate * d->sampleWidth * d->channels) / 1000.0; - d->length = sampleFrames / d->sampleRate; + ByteVector data; + unsigned int streamLength = 0; + for(unsigned int i = 0; i < file->chunkCount(); i++) { + const ByteVector name = file->chunkName(i); + if(name == "COMM") { + if(data.isEmpty()) + data = file->chunkData(i); + else + debug("RIFF::AIFF::Properties::read() - Duplicate 'COMM' chunk found."); + } + else if(name == "SSND") { + if(streamLength == 0) + streamLength = file->chunkDataSize(i) + file->chunkPadding(i); + else + debug("RIFF::AIFF::Properties::read() - Duplicate 'SSND' chunk found."); + } + } + + if(data.size() < 18) { + debug("RIFF::AIFF::Properties::read() - 'COMM' chunk not found or too short."); + return; + } + + if(streamLength == 0) { + debug("RIFF::AIFF::Properties::read() - 'SSND' chunk not found."); + return; + } + + d->channels = data.toShort(0U); + d->sampleFrames = data.toUInt(2U); + d->bitsPerSample = data.toShort(6U); + + const long double sampleRate = data.toFloat80BE(8); + if(sampleRate >= 1.0) + d->sampleRate = static_cast<int>(sampleRate + 0.5); + + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / sampleRate; + d->length = static_cast<int>(length + 0.5); + d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5); + } + + if(data.size() >= 23) { + d->compressionType = data.mid(18, 4); + d->compressionName + = String(data.mid(23, static_cast<unsigned char>(data[22])), String::Latin1); + } } diff --git a/Frameworks/TagLib/taglib/taglib/riff/aiff/aiffproperties.h b/Frameworks/TagLib/taglib/taglib/riff/aiff/aiffproperties.h index 4c578dc68..dbb4c2146 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/aiff/aiffproperties.h +++ b/Frameworks/TagLib/taglib/taglib/riff/aiff/aiffproperties.h @@ -46,34 +46,118 @@ namespace TagLib { class TAGLIB_EXPORT Properties : public AudioProperties { public: - /*! - * Create an instance of AIFF::Properties with the data read from the - * ByteVector \a data. - */ - Properties(const ByteVector &data, ReadStyle style); + /*! + * Create an instance of AIFF::Properties with the data read from the + * ByteVector \a data. + * + * \deprecated + */ + TAGLIB_DEPRECATED Properties(const ByteVector &data, ReadStyle style); - /*! - * Destroys this AIFF::Properties instance. - */ - virtual ~Properties(); + /*! + * Create an instance of AIFF::Properties with the data read from the + * AIFF::File \a file. + */ + Properties(File *file, ReadStyle style); - // Reimplementations. + /*! + * Destroys this AIFF::Properties instance. + */ + virtual ~Properties(); - virtual int length() const; - virtual int bitrate() const; - virtual int sampleRate() const; - virtual int channels() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; - int sampleWidth() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ + virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ + virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ + virtual int channels() const; + + /*! + * Returns the number of bits per audio sample. + */ + int bitsPerSample() const; + + /*! + * Returns the number of bits per audio sample. + * + * \note This method is just an alias of bitsPerSample(). + * + * \deprecated + */ + TAGLIB_DEPRECATED int sampleWidth() const; + + /*! + * Returns the number of sample frames + */ + unsigned int sampleFrames() const; + + /*! + * Returns true if the file is in AIFF-C format, false if AIFF format. + */ + bool isAiffC() const; + + /*! + * Returns the compression type of the AIFF-C file. For example, "NONE" for + * not compressed, "ACE2" for ACE 2-to-1. + * + * If the file is in AIFF format, always returns an empty vector. + * + * \see isAiffC() + */ + ByteVector compressionType() const; + + /*! + * Returns the concrete compression name of the AIFF-C file. + * + * If the file is in AIFF format, always returns an empty string. + * + * \see isAiffC() + */ + String compressionName() const; private: - Properties(const Properties &); - Properties &operator=(const Properties &); + Properties(const Properties &); + Properties &operator=(const Properties &); - void read(const ByteVector &data); + void read(File *file); - class PropertiesPrivate; - PropertiesPrivate *d; + class PropertiesPrivate; + PropertiesPrivate *d; }; } } diff --git a/Frameworks/TagLib/taglib/taglib/riff/rifffile.cpp b/Frameworks/TagLib/taglib/taglib/riff/rifffile.cpp index 8d23bcd69..005551f49 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/rifffile.cpp +++ b/Frameworks/TagLib/taglib/taglib/riff/rifffile.cpp @@ -23,36 +23,38 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include <algorithm> +#include <vector> + #include <tbytevector.h> #include <tdebug.h> #include <tstring.h> #include "rifffile.h" -#include <vector> +#include "riffutils.h" using namespace TagLib; struct Chunk { - ByteVector name; - TagLib::uint offset; - TagLib::uint size; - char padding; + ByteVector name; + unsigned int offset; + unsigned int size; + unsigned int padding; }; class RIFF::File::FilePrivate { public: - FilePrivate() : - endianness(BigEndian), - size(0) - { + FilePrivate(Endianness endianness) : + endianness(endianness), + size(0), + sizeOffset(0) {} - } - Endianness endianness; - ByteVector type; - TagLib::uint size; - ByteVector format; + const Endianness endianness; + + unsigned int size; + long sizeOffset; std::vector<Chunk> chunks; }; @@ -70,124 +72,207 @@ RIFF::File::~File() // protected members //////////////////////////////////////////////////////////////////////////////// -RIFF::File::File(FileName file, Endianness endianness) : TagLib::File(file) +RIFF::File::File(FileName file, Endianness endianness) : + TagLib::File(file), + d(new FilePrivate(endianness)) { - d = new FilePrivate; - d->endianness = endianness; - if(isOpen()) read(); } -TagLib::uint RIFF::File::riffSize() const +RIFF::File::File(IOStream *stream, Endianness endianness) : + TagLib::File(stream), + d(new FilePrivate(endianness)) +{ + if(isOpen()) + read(); +} + +unsigned int RIFF::File::riffSize() const { return d->size; } -TagLib::uint RIFF::File::chunkCount() const +unsigned int RIFF::File::chunkCount() const { - return d->chunks.size(); + return static_cast<unsigned int>(d->chunks.size()); } -TagLib::uint RIFF::File::chunkDataSize(uint i) const +unsigned int RIFF::File::chunkDataSize(unsigned int i) const { + if(i >= d->chunks.size()) { + debug("RIFF::File::chunkDataSize() - Index out of range. Returning 0."); + return 0; + } + return d->chunks[i].size; } -TagLib::uint RIFF::File::chunkOffset(uint i) const +unsigned int RIFF::File::chunkOffset(unsigned int i) const { + if(i >= d->chunks.size()) { + debug("RIFF::File::chunkOffset() - Index out of range. Returning 0."); + return 0; + } + return d->chunks[i].offset; } -TagLib::uint RIFF::File::chunkPadding(uint i) const +unsigned int RIFF::File::chunkPadding(unsigned int i) const { + if(i >= d->chunks.size()) { + debug("RIFF::File::chunkPadding() - Index out of range. Returning 0."); + return 0; + } + return d->chunks[i].padding; } -ByteVector RIFF::File::chunkName(uint i) const +ByteVector RIFF::File::chunkName(unsigned int i) const { - if(i >= chunkCount()) - return ByteVector::null; + if(i >= d->chunks.size()) { + debug("RIFF::File::chunkName() - Index out of range. Returning an empty vector."); + return ByteVector(); + } return d->chunks[i].name; } -ByteVector RIFF::File::chunkData(uint i) +ByteVector RIFF::File::chunkData(unsigned int i) { - if(i >= chunkCount()) - return ByteVector::null; - - // Offset for the first subchunk's data - - long begin = 12 + 8; - - for(uint it = 0; it < i; it++) - begin += 8 + d->chunks[it].size + d->chunks[it].padding; - - seek(begin); + if(i >= d->chunks.size()) { + debug("RIFF::File::chunkData() - Index out of range. Returning an empty vector."); + return ByteVector(); + } + seek(d->chunks[i].offset); return readBlock(d->chunks[i].size); } +void RIFF::File::setChunkData(unsigned int i, const ByteVector &data) +{ + if(i >= d->chunks.size()) { + debug("RIFF::File::setChunkData() - Index out of range."); + return; + } + + // Now update the specific chunk + + std::vector<Chunk>::iterator it = d->chunks.begin(); + std::advance(it, i); + + const long long originalSize = static_cast<long long>(it->size) + it->padding; + + writeChunk(it->name, data, it->offset - 8, it->size + it->padding + 8); + + it->size = data.size(); + it->padding = data.size() % 2; + + const long long diff = static_cast<long long>(it->size) + it->padding - originalSize; + + // Now update the internal offsets + + for(++it; it != d->chunks.end(); ++it) + it->offset += static_cast<int>(diff); + + // Update the global size. + + updateGlobalSize(); +} + void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data) { - if(d->chunks.size() == 0) { + setChunkData(name, data, false); +} + +void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data, bool alwaysCreate) +{ + if(d->chunks.empty()) { debug("RIFF::File::setChunkData - No valid chunks found."); return; } - for(uint i = 0; i < d->chunks.size(); i++) { - if(d->chunks[i].name == name) { + if(alwaysCreate && name != "LIST") { + debug("RIFF::File::setChunkData - alwaysCreate should be used for only \"LIST\" chunks."); + return; + } - // First we update the global size - - d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding); - insert(ByteVector::fromUInt(d->size, d->endianness == BigEndian), 4, 4); - - // Now update the specific chunk - - writeChunk(name, data, d->chunks[i].offset - 8, d->chunks[i].size + d->chunks[i].padding + 8); - - d->chunks[i].size = data.size(); - d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0; - - // Now update the internal offsets - - for(i++; i < d->chunks.size(); i++) - d->chunks[i].offset = d->chunks[i-1].offset + 8 + d->chunks[i-1].size + d->chunks[i-1].padding; - - return; + if(!alwaysCreate) { + for(unsigned int i = 0; i < d->chunks.size(); i++) { + if(d->chunks[i].name == name) { + setChunkData(i, data); + return; + } } } // Couldn't find an existing chunk, so let's create a new one. - uint i = d->chunks.size() - 1; - ulong offset = d->chunks[i].offset + d->chunks[i].size; + // Adjust the padding of the last chunk to place the new chunk at even position. - // First we update the global size + Chunk &last = d->chunks.back(); - d->size += (offset & 1) + data.size() + 8; - insert(ByteVector::fromUInt(d->size, d->endianness == BigEndian), 4, 4); + long offset = last.offset + last.size + last.padding; + if(offset & 1) { + if(last.padding == 1) { + last.padding = 0; // This should not happen unless the file is corrupted. + offset--; + removeBlock(offset, 1); + } + else { + insert(ByteVector("\0", 1), offset, 0); + last.padding = 1; + offset++; + } + } - // Now add the chunk to the file + // Now add the chunk to the file. - writeChunk(name, data, offset, std::max(ulong(0), length() - offset), (offset & 1) ? 1 : 0); + writeChunk(name, data, offset, 0); // And update our internal structure - if (offset & 1) { - d->chunks[i].padding = 1; - offset++; - } - Chunk chunk; - chunk.name = name; - chunk.size = data.size(); - chunk.offset = offset + 8; - chunk.padding = (data.size() & 0x01) ? 1 : 0; + chunk.name = name; + chunk.size = data.size(); + chunk.offset = offset + 8; + chunk.padding = data.size() % 2; d->chunks.push_back(chunk); + + // Update the global size. + + updateGlobalSize(); +} + +void RIFF::File::removeChunk(unsigned int i) +{ + if(i >= d->chunks.size()) { + debug("RIFF::File::removeChunk() - Index out of range."); + return; + } + + std::vector<Chunk>::iterator it = d->chunks.begin(); + std::advance(it, i); + + const unsigned int removeSize = it->size + it->padding + 8; + removeBlock(it->offset - 8, removeSize); + it = d->chunks.erase(it); + + for(; it != d->chunks.end(); ++it) + it->offset -= removeSize; + + // Update the global size. + + updateGlobalSize(); +} + +void RIFF::File::removeChunk(const ByteVector &name) +{ + for(int i = static_cast<int>(d->chunks.size()) - 1; i >= 0; --i) { + if(d->chunks[i].name == name) + removeChunk(i); + } } //////////////////////////////////////////////////////////////////////////////// @@ -196,59 +281,90 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data) void RIFF::File::read() { - bool bigEndian = (d->endianness == BigEndian); + const bool bigEndian = (d->endianness == BigEndian); - d->type = readBlock(4); + long offset = tell(); + + offset += 4; + d->sizeOffset = offset; + + seek(offset); d->size = readBlock(4).toUInt(bigEndian); - d->format = readBlock(4); + + offset += 8; // + 8: chunk header at least, fix for additional junk bytes - while(tell() + 8 <= length()) { - ByteVector chunkName = readBlock(4); - uint chunkSize = readBlock(4).toUInt(bigEndian); + while(offset + 8 <= length()) { - if(tell() + chunkSize > uint(length())) { - // something wrong + seek(offset); + const ByteVector chunkName = readBlock(4); + const unsigned int chunkSize = readBlock(4).toUInt(bigEndian); + + if(!isValidChunkName(chunkName)) { + debug("RIFF::File::read() -- Chunk '" + chunkName + "' has invalid ID"); + break; + } + + if(static_cast<long long>(offset) + 8 + chunkSize > length()) { + debug("RIFF::File::read() -- Chunk '" + chunkName + "' has invalid size (larger than the file size)"); break; } Chunk chunk; - chunk.name = chunkName; - chunk.size = chunkSize; - chunk.offset = tell(); - - seek(chunk.size, Current); - - // check padding + chunk.name = chunkName; + chunk.size = chunkSize; + chunk.offset = offset + 8; chunk.padding = 0; - long uPosNotPadded = tell(); - if((uPosNotPadded & 0x01) != 0) { - ByteVector iByte = readBlock(1); - if((iByte.size() != 1) || (iByte[0] != 0)) { - // not well formed, re-seek - seek(uPosNotPadded, Beginning); - } - else { - chunk.padding = 1; + + offset = chunk.offset + chunk.size; + + // Check padding + + if(offset & 1) { + seek(offset); + const ByteVector iByte = readBlock(1); + if(iByte.size() == 1) { + bool skipPadding = iByte[0] == '\0'; + if(!skipPadding) { + // Padding byte is not zero, check if it is good to ignore it + const ByteVector fourCcAfterPadding = readBlock(4); + if(isValidChunkName(fourCcAfterPadding)) { + // Use the padding, it is followed by a valid chunk name. + skipPadding = true; + } + } + if(skipPadding) { + chunk.padding = 1; + offset++; + } } } - d->chunks.push_back(chunk); + d->chunks.push_back(chunk); } } void RIFF::File::writeChunk(const ByteVector &name, const ByteVector &data, - ulong offset, ulong replace, uint leadingPadding) + unsigned long offset, unsigned long replace) { ByteVector combined; - if(leadingPadding) { - combined.append(ByteVector(leadingPadding, '\x00')); - } + combined.append(name); combined.append(ByteVector::fromUInt(data.size(), d->endianness == BigEndian)); combined.append(data); - if((data.size() & 0x01) != 0) { - combined.append('\x00'); - } + + if(data.size() & 1) + combined.resize(combined.size() + 1, '\0'); + insert(combined, offset, replace); } + +void RIFF::File::updateGlobalSize() +{ + const Chunk first = d->chunks.front(); + const Chunk last = d->chunks.back(); + d->size = last.offset + last.size + last.padding - first.offset + 12; + + const ByteVector data = ByteVector::fromUInt(d->size, d->endianness == BigEndian); + insert(data, d->sizeOffset, 4); +} diff --git a/Frameworks/TagLib/taglib/taglib/riff/rifffile.h b/Frameworks/TagLib/taglib/taglib/riff/rifffile.h index 992825642..5c606b4a7 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/rifffile.h +++ b/Frameworks/TagLib/taglib/taglib/riff/rifffile.h @@ -56,43 +56,51 @@ namespace TagLib { enum Endianness { BigEndian, LittleEndian }; File(FileName file, Endianness endianness); + File(IOStream *stream, Endianness endianness); /*! * \return The size of the main RIFF chunk. */ - uint riffSize() const; + unsigned int riffSize() const; /*! * \return The number of chunks in the file. */ - uint chunkCount() const; + unsigned int chunkCount() const; /*! * \return The offset within the file for the selected chunk number. */ - uint chunkOffset(uint i) const; + unsigned int chunkOffset(unsigned int i) const; /*! * \return The size of the chunk data. */ - uint chunkDataSize(uint i) const; + unsigned int chunkDataSize(unsigned int i) const; /*! * \return The size of the padding after the chunk (can be either 0 or 1). */ - uint chunkPadding(uint i) const; + unsigned int chunkPadding(unsigned int i) const; /*! * \return The name of the specified chunk, for instance, "COMM" or "ID3 " */ - ByteVector chunkName(uint i) const; + ByteVector chunkName(unsigned int i) const; /*! * Reads the chunk data from the file and returns it. * * \note This \e will move the read pointer for the file. */ - ByteVector chunkData(uint i); + ByteVector chunkData(unsigned int i); + + /*! + * Sets the data for the specified chunk to \a data. + * + * \warning This will update the file immediately. + */ + void setChunkData(unsigned int i, const ByteVector &data); /*! * Sets the data for the chunk \a name to \a data. If a chunk with the @@ -103,14 +111,46 @@ namespace TagLib { */ void setChunkData(const ByteVector &name, const ByteVector &data); + /*! + * Sets the data for the chunk \a name to \a data. If a chunk with the + * given name already exists it will be overwritten, otherwise it will be + * created after the existing chunks. + * + * \note If \a alwaysCreate is true, a new chunk is created regardless of + * whether or not the chunk \a name exists. It should only be used for + * "LIST" chunks. + * + * \warning This will update the file immediately. + */ + void setChunkData(const ByteVector &name, const ByteVector &data, bool alwaysCreate); + + /*! + * Removes the specified chunk. + * + * \warning This will update the file immediately. + */ + void removeChunk(unsigned int i); + + /*! + * Removes the chunk \a name. + * + * \warning This will update the file immediately. + * \warning This removes all the chunks with the given name. + */ + void removeChunk(const ByteVector &name); + private: File(const File &); File &operator=(const File &); void read(); void writeChunk(const ByteVector &name, const ByteVector &data, - ulong offset, ulong replace = 0, - uint leadingPadding = 0); + unsigned long offset, unsigned long replace = 0); + + /*! + * Update the global RIFF size based on the current internal structure. + */ + void updateGlobalSize(); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/riff/riffutils.h b/Frameworks/TagLib/taglib/taglib/riff/riffutils.h new file mode 100644 index 000000000..ecb985a4b --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/riff/riffutils.h @@ -0,0 +1,60 @@ +/*************************************************************************** + copyright : (C) 2015 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_RIFFUTILS_H +#define TAGLIB_RIFFUTILS_H + +// THIS FILE IS NOT A PART OF THE TAGLIB API + +#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header + +namespace TagLib +{ + namespace RIFF + { + namespace + { + + inline bool isValidChunkName(const ByteVector &name) + { + if(name.size() != 4) + return false; + + for(ByteVector::ConstIterator it = name.begin(); it != name.end(); ++it) { + const int c = static_cast<unsigned char>(*it); + if(c < 32 || 127 < c) + return false; + } + + return true; + } + + } + } +} + +#endif + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.cpp b/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.cpp new file mode 100644 index 000000000..df1cfbeef --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.cpp @@ -0,0 +1,256 @@ +/*************************************************************************** + copyright : (C) 2012 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tdebug.h> +#include <tfile.h> + +#include "infotag.h" +#include "riffutils.h" + +using namespace TagLib; +using namespace RIFF::Info; + +namespace +{ + const RIFF::Info::StringHandler defaultStringHandler; + const RIFF::Info::StringHandler *stringHandler = &defaultStringHandler; +} + +class RIFF::Info::Tag::TagPrivate +{ +public: + FieldListMap fieldListMap; +}; + +//////////////////////////////////////////////////////////////////////////////// +// StringHandler implementation +//////////////////////////////////////////////////////////////////////////////// + +StringHandler::StringHandler() +{ +} + +StringHandler::~StringHandler() +{ +} + +String RIFF::Info::StringHandler::parse(const ByteVector &data) const +{ + return String(data, String::UTF8); +} + +ByteVector RIFF::Info::StringHandler::render(const String &s) const +{ + return s.data(String::UTF8); +} + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +RIFF::Info::Tag::Tag(const ByteVector &data) : + TagLib::Tag(), + d(new TagPrivate()) +{ + parse(data); +} + +RIFF::Info::Tag::Tag() : + TagLib::Tag(), + d(new TagPrivate()) +{ +} + +RIFF::Info::Tag::~Tag() +{ + delete d; +} + +String RIFF::Info::Tag::title() const +{ + return fieldText("INAM"); +} + +String RIFF::Info::Tag::artist() const +{ + return fieldText("IART"); +} + +String RIFF::Info::Tag::album() const +{ + return fieldText("IPRD"); +} + +String RIFF::Info::Tag::comment() const +{ + return fieldText("ICMT"); +} + +String RIFF::Info::Tag::genre() const +{ + return fieldText("IGNR"); +} + +unsigned int RIFF::Info::Tag::year() const +{ + return fieldText("ICRD").substr(0, 4).toInt(); +} + +unsigned int RIFF::Info::Tag::track() const +{ + return fieldText("IPRT").toInt(); +} + +void RIFF::Info::Tag::setTitle(const String &s) +{ + setFieldText("INAM", s); +} + +void RIFF::Info::Tag::setArtist(const String &s) +{ + setFieldText("IART", s); +} + +void RIFF::Info::Tag::setAlbum(const String &s) +{ + setFieldText("IPRD", s); +} + +void RIFF::Info::Tag::setComment(const String &s) +{ + setFieldText("ICMT", s); +} + +void RIFF::Info::Tag::setGenre(const String &s) +{ + setFieldText("IGNR", s); +} + +void RIFF::Info::Tag::setYear(unsigned int i) +{ + if(i != 0) + setFieldText("ICRD", String::number(i)); + else + d->fieldListMap.erase("ICRD"); +} + +void RIFF::Info::Tag::setTrack(unsigned int i) +{ + if(i != 0) + setFieldText("IPRT", String::number(i)); + else + d->fieldListMap.erase("IPRT"); +} + +bool RIFF::Info::Tag::isEmpty() const +{ + return d->fieldListMap.isEmpty(); +} + +FieldListMap RIFF::Info::Tag::fieldListMap() const +{ + return d->fieldListMap; +} + +String RIFF::Info::Tag::fieldText(const ByteVector &id) const +{ + if(d->fieldListMap.contains(id)) + return String(d->fieldListMap[id]); + else + return String(); +} + +void RIFF::Info::Tag::setFieldText(const ByteVector &id, const String &s) +{ + // id must be four-byte long pure ascii string. + if(!isValidChunkName(id)) + return; + + if(!s.isEmpty()) + d->fieldListMap[id] = s; + else + removeField(id); +} + +void RIFF::Info::Tag::removeField(const ByteVector &id) +{ + if(d->fieldListMap.contains(id)) + d->fieldListMap.erase(id); +} + +ByteVector RIFF::Info::Tag::render() const +{ + ByteVector data("INFO"); + + FieldListMap::ConstIterator it = d->fieldListMap.begin(); + for(; it != d->fieldListMap.end(); ++it) { + ByteVector text = stringHandler->render(it->second); + if(text.isEmpty()) + continue; + + data.append(it->first); + data.append(ByteVector::fromUInt(text.size() + 1, false)); + data.append(text); + + do { + data.append('\0'); + } while(data.size() & 1); + } + + if(data.size() == 4) + return ByteVector(); + else + return data; +} + +void RIFF::Info::Tag::setStringHandler(const StringHandler *handler) +{ + if(handler) + stringHandler = handler; + else + stringHandler = &defaultStringHandler; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void RIFF::Info::Tag::parse(const ByteVector &data) +{ + unsigned int p = 4; + while(p < data.size()) { + const unsigned int size = data.toUInt(p + 4, false); + if(size > data.size() - p - 8) + break; + + const ByteVector id = data.mid(p, 4); + if(isValidChunkName(id)) { + const String text = stringHandler->parse(data.mid(p + 8, size)); + d->fieldListMap[id] = text; + } + + p += ((size + 1) & ~1) + 8; + } +} diff --git a/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.h b/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.h new file mode 100644 index 000000000..d1f0297db --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/riff/wav/infotag.h @@ -0,0 +1,192 @@ +/*************************************************************************** + copyright : (C) 2012 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_INFOTAG_H +#define TAGLIB_INFOTAG_H + +#include "tag.h" +#include "tmap.h" +#include "tstring.h" +#include "tstringlist.h" +#include "tbytevector.h" +#include "taglib_export.h" + +namespace TagLib { + + class File; + + //! A RIFF INFO tag implementation. + namespace RIFF { + namespace Info { + + typedef Map<ByteVector, String> FieldListMap; + + //! A abstraction for the string to data encoding in Info tags. + + /*! + * RIFF INFO tag has no clear definitions about character encodings. + * In practice, local encoding of each system is largely used and UTF-8 is + * popular too. + * + * Here is an option to read and write tags in your preferred encoding + * by subclassing this class, reimplementing parse() and render() and setting + * your reimplementation as the default with Info::Tag::setStringHandler(). + * + * \see ID3v1::Tag::setStringHandler() + */ + + class TAGLIB_EXPORT StringHandler + { + public: + StringHandler(); + ~StringHandler(); + + /*! + * Decode a string from \a data. The default implementation assumes that + * \a data is an UTF-8 character array. + */ + virtual String parse(const ByteVector &data) const; + + /*! + * Encode a ByteVector with the data from \a s. The default implementation + * assumes that \a s is an UTF-8 string. + */ + virtual ByteVector render(const String &s) const; + }; + + //! The main class in the ID3v2 implementation + + /*! + * This is the main class in the INFO tag implementation. RIFF INFO tag is a + * metadata format found in WAV audio and AVI video files. Though it is a part + * of Microsoft/IBM's RIFF specification, the author could not find the official + * documents about it. So, this implementation is referring to unofficial documents + * online and some applications' behaviors especially Windows Explorer. + */ + class TAGLIB_EXPORT Tag : public TagLib::Tag + { + public: + /*! + * Constructs an empty INFO tag. + */ + Tag(); + + /*! + * Constructs an INFO tag read from \a data which is contents of "LIST" chunk. + */ + Tag(const ByteVector &data); + + virtual ~Tag(); + + // Reimplementations + + virtual String title() const; + virtual String artist() const; + virtual String album() const; + virtual String comment() const; + virtual String genre() const; + virtual unsigned int year() const; + virtual unsigned int track() const; + + virtual void setTitle(const String &s); + virtual void setArtist(const String &s); + virtual void setAlbum(const String &s); + virtual void setComment(const String &s); + virtual void setGenre(const String &s); + virtual void setYear(unsigned int i); + virtual void setTrack(unsigned int i); + + virtual bool isEmpty() const; + + /*! + * Returns a copy of the internal fields of the tag. The returned map directly + * reflects the contents of the "INFO" chunk. + * + * \note Modifying this map does not affect the tag's internal data. + * Use setFieldText() and removeField() instead. + * + * \see setFieldText() + * \see removeField() + */ + FieldListMap fieldListMap() const; + + /* + * Gets the value of the field with the ID \a id. + */ + String fieldText(const ByteVector &id) const; + + /* + * Sets the value of the field with the ID \a id to \a s. + * If the field does not exist, it is created. + * If \s is empty, the field is removed. + * + * \note fieldId must be four-byte long pure ASCII string. This function + * performs nothing if fieldId is invalid. + */ + void setFieldText(const ByteVector &id, const String &s); + + /* + * Removes the field with the ID \a id. + */ + void removeField(const ByteVector &id); + + /*! + * Render the tag back to binary data, suitable to be written to disk. + * + * \note Returns empty ByteVector is the tag contains no fields. + */ + ByteVector render() const; + + /*! + * Sets the string handler that decides how the text data will be + * converted to and from binary data. + * If the parameter \a handler is null, the previous handler is + * released and default UTF-8 handler is restored. + * + * \note The caller is responsible for deleting the previous handler + * as needed after it is released. + * + * \see StringHandler + */ + static void setStringHandler(const StringHandler *handler); + + protected: + /*! + * Pareses the body of the tag in \a data. + */ + void parse(const ByteVector &data); + + + private: + Tag(const Tag &); + Tag &operator=(const Tag &); + + class TagPrivate; + TagPrivate *d; + }; + }} +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/riff/wav/wavfile.cpp b/Frameworks/TagLib/taglib/taglib/riff/wav/wavfile.cpp index 9ec3b5108..3dce70b1c 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/wav/wavfile.cpp +++ b/Frameworks/TagLib/taglib/taglib/riff/wav/wavfile.cpp @@ -25,44 +25,72 @@ #include <tbytevector.h> #include <tdebug.h> -#include <id3v2tag.h> +#include <tstringlist.h> +#include <tpropertymap.h> +#include <tagutils.h> #include "wavfile.h" +#include "id3v2tag.h" +#include "infotag.h" +#include "tagunion.h" using namespace TagLib; +namespace +{ + enum { ID3v2Index = 0, InfoIndex = 1 }; +} + class RIFF::WAV::File::FilePrivate { public: FilePrivate() : properties(0), - tag(0), - tagChunkID("ID3 ") - { - - } + hasID3v2(false), + hasInfo(false) {} ~FilePrivate() { delete properties; - delete tag; } Properties *properties; - ID3v2::Tag *tag; - ByteVector tagChunkID; + TagUnion tag; + + bool hasID3v2; + bool hasInfo; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool RIFF::WAV::File::isSupported(IOStream *stream) +{ + // A WAV file has to start with "RIFF????WAVE". + + const ByteVector id = Utils::readHeader(stream, 12, false); + return (id.startsWith("RIFF") && id.containsAt("WAVE", 8)); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::WAV::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : RIFF::File(file, LittleEndian) +RIFF::WAV::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + RIFF::File(file, LittleEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); +} + +RIFF::WAV::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + RIFF::File(stream, LittleEndian), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties); } RIFF::WAV::File::~File() @@ -72,7 +100,44 @@ RIFF::WAV::File::~File() ID3v2::Tag *RIFF::WAV::File::tag() const { - return d->tag; + return ID3v2Tag(); +} + +ID3v2::Tag *RIFF::WAV::File::ID3v2Tag() const +{ + return d->tag.access<ID3v2::Tag>(ID3v2Index, false); +} + +RIFF::Info::Tag *RIFF::WAV::File::InfoTag() const +{ + return d->tag.access<RIFF::Info::Tag>(InfoIndex, false); +} + +void RIFF::WAV::File::strip(TagTypes tags) +{ + removeTagChunks(tags); + + if(tags & ID3v2) + d->tag.set(ID3v2Index, new ID3v2::Tag()); + + if(tags & Info) + d->tag.set(InfoIndex, new RIFF::Info::Tag()); +} + +PropertyMap RIFF::WAV::File::properties() const +{ + return d->tag.properties(); +} + +void RIFF::WAV::File::removeUnsupportedProperties(const StringList &unsupported) +{ + d->tag.removeUnsupportedProperties(unsupported); +} + +PropertyMap RIFF::WAV::File::setProperties(const PropertyMap &properties) +{ + InfoTag()->setProperties(properties); + return ID3v2Tag()->setProperties(properties); } RIFF::WAV::Properties *RIFF::WAV::File::audioProperties() const @@ -81,39 +146,119 @@ RIFF::WAV::Properties *RIFF::WAV::File::audioProperties() const } bool RIFF::WAV::File::save() +{ + return RIFF::WAV::File::save(AllTags); +} + +bool RIFF::WAV::File::save(TagTypes tags, bool stripOthers, int id3v2Version) +{ + return save(tags, + stripOthers ? StripOthers : StripNone, + id3v2Version == 3 ? ID3v2::v3 : ID3v2::v4); +} + +bool RIFF::WAV::File::save(TagTypes tags, StripTags strip, ID3v2::Version version) { if(readOnly()) { debug("RIFF::WAV::File::save() -- File is read only."); return false; } - setChunkData(d->tagChunkID, d->tag->render()); + if(!isValid()) { + debug("RIFF::WAV::File::save() -- Trying to save invalid file."); + return false; + } + + if(strip == StripOthers) + File::strip(static_cast<TagTypes>(AllTags & ~tags)); + + if(tags & ID3v2) { + removeTagChunks(ID3v2); + + if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) { + setChunkData("ID3 ", ID3v2Tag()->render(version)); + d->hasID3v2 = true; + } + } + + if(tags & Info) { + removeTagChunks(Info); + + if(InfoTag() && !InfoTag()->isEmpty()) { + setChunkData("LIST", InfoTag()->render(), true); + d->hasInfo = true; + } + } return true; } +bool RIFF::WAV::File::hasID3v2Tag() const +{ + return d->hasID3v2; +} + +bool RIFF::WAV::File::hasInfoTag() const +{ + return d->hasInfo; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void RIFF::WAV::File::read(bool readProperties) { - ByteVector formatData; - uint streamLength = 0; - for(uint i = 0; i < chunkCount(); i++) { - if(chunkName(i) == "ID3 " || chunkName(i) == "id3 ") { - d->tagChunkID = chunkName(i); - d->tag = new ID3v2::Tag(this, chunkOffset(i)); + for(unsigned int i = 0; i < chunkCount(); ++i) { + const ByteVector name = chunkName(i); + if(name == "ID3 " || name == "id3 ") { + if(!d->tag[ID3v2Index]) { + d->tag.set(ID3v2Index, new ID3v2::Tag(this, chunkOffset(i))); + d->hasID3v2 = true; + } + else { + debug("RIFF::WAV::File::read() - Duplicate ID3v2 tag found."); + } + } + else if(name == "LIST") { + const ByteVector data = chunkData(i); + if(data.startsWith("INFO")) { + if(!d->tag[InfoIndex]) { + d->tag.set(InfoIndex, new RIFF::Info::Tag(data)); + d->hasInfo = true; + } + else { + debug("RIFF::WAV::File::read() - Duplicate INFO tag found."); + } + } } - else if(chunkName(i) == "fmt " && readProperties) - formatData = chunkData(i); - else if(chunkName(i) == "data" && readProperties) - streamLength = chunkDataSize(i); } - if(!formatData.isEmpty()) - d->properties = new Properties(formatData, streamLength, propertiesStyle); + if(!d->tag[ID3v2Index]) + d->tag.set(ID3v2Index, new ID3v2::Tag()); - if(!d->tag) - d->tag = new ID3v2::Tag; + if(!d->tag[InfoIndex]) + d->tag.set(InfoIndex, new RIFF::Info::Tag()); + + if(readProperties) + d->properties = new Properties(this, Properties::Average); +} + +void RIFF::WAV::File::removeTagChunks(TagTypes tags) +{ + if((tags & ID3v2) && d->hasID3v2) { + removeChunk("ID3 "); + removeChunk("id3 "); + + d->hasID3v2 = false; + } + + if((tags & Info) && d->hasInfo) { + for(int i = static_cast<int>(chunkCount()) - 1; i >= 0; --i) { + if(chunkName(i) == "LIST" && chunkData(i).startsWith("INFO")) + removeChunk(i); + } + + d->hasInfo = false; + } } diff --git a/Frameworks/TagLib/taglib/taglib/riff/wav/wavfile.h b/Frameworks/TagLib/taglib/taglib/riff/wav/wavfile.h index b44668c32..bc9ce3126 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/wav/wavfile.h +++ b/Frameworks/TagLib/taglib/taglib/riff/wav/wavfile.h @@ -28,6 +28,7 @@ #include "rifffile.h" #include "id3v2tag.h" +#include "infotag.h" #include "wavproperties.h" namespace TagLib { @@ -57,23 +58,95 @@ namespace TagLib { class TAGLIB_EXPORT File : public TagLib::RIFF::File { public: + enum TagTypes { + //! Empty set. Matches no tag types. + NoTags = 0x0000, + //! Matches ID3v2 tags. + ID3v2 = 0x0001, + //! Matches INFO tags. + Info = 0x0002, + //! Matches all tag types. + AllTags = 0xffff + }; + /*! - * Contructs an WAV file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs a WAV file from \a file. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs a WAV file from \a stream. If \a readProperties is true the + * file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ virtual ~File(); /*! - * Returns the Tag for this file. + * Returns the ID3v2 Tag for this file. + * + * \note This method does not return all the tags for this file for + * backward compatibility. Will be fixed in TagLib 2.0. */ - virtual ID3v2::Tag *tag() const; + ID3v2::Tag *tag() const; + + /*! + * Returns the ID3v2 Tag for this file. + * + * \note This always returns a valid pointer regardless of whether or not + * the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the + * file on disk actually has an ID3v2 tag. + * + * \see hasID3v2Tag() + */ + ID3v2::Tag *ID3v2Tag() const; + + /*! + * Returns the RIFF INFO Tag for this file. + * + * \note This always returns a valid pointer regardless of whether or not + * the file on disk has a RIFF INFO tag. Use hasInfoTag() to check if the + * file on disk actually has a RIFF INFO tag. + * + * \see hasInfoTag() + */ + Info::Tag *InfoTag() const; + + /*! + * This will strip the tags that match the OR-ed together TagTypes from the + * file. By default it strips all tags. It returns true if the tags are + * successfully stripped. + * + * \note This will update the file immediately. + */ + void strip(TagTypes tags = AllTags); + + /*! + * Implements the unified property interface -- export function. + * This method forwards to ID3v2::Tag::properties(). + */ + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + * This method forwards to ID3v2::Tag::setProperties(). + */ + PropertyMap setProperties(const PropertyMap &); /*! * Returns the WAV::Properties for this file. If no audio properties @@ -86,11 +159,51 @@ namespace TagLib { */ virtual bool save(); + /*! + * \deprecated + */ + TAGLIB_DEPRECATED bool save(TagTypes tags, bool stripOthers, int id3v2Version = 4); + + /*! + * Save the file. If \a strip is specified, it is possible to choose if + * tags not specified in \a tags should be stripped from the file or + * retained. With \a version, it is possible to specify whether ID3v2.4 + * or ID3v2.3 should be used. + */ + bool save(TagTypes tags, StripTags strip = StripOthers, + ID3v2::Version version = ID3v2::v4); + + /*! + * Returns whether or not the file on disk actually has an ID3v2 tag. + * + * \see ID3v2Tag() + */ + bool hasID3v2Tag() const; + + /*! + * Returns whether or not the file on disk actually has a RIFF INFO tag. + * + * \see InfoTag() + */ + bool hasInfoTag() const; + + /*! + * Returns whether or not the given \a stream can be opened as a WAV + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); + void removeTagChunks(TagTypes tags); + + friend class Properties; class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/riff/wav/wavproperties.cpp b/Frameworks/TagLib/taglib/taglib/riff/wav/wavproperties.cpp index 372168605..812da7d25 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/wav/wavproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/riff/wav/wavproperties.cpp @@ -23,53 +23,67 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include <tdebug.h> +#include "wavfile.h" #include "wavproperties.h" -#include <tstring.h> -#include <tdebug.h> -#include <cmath> -#include <math.h> - using namespace TagLib; +namespace +{ + // Quoted from RFC 2361. + enum WaveFormat + { + FORMAT_UNKNOWN = 0x0000, + FORMAT_PCM = 0x0001, + FORMAT_IEEE_FLOAT = 0x0003 + }; +} + class RIFF::WAV::Properties::PropertiesPrivate { public: - PropertiesPrivate(uint streamLength = 0) : + PropertiesPrivate() : format(0), length(0), bitrate(0), sampleRate(0), channels(0), - sampleWidth(0), - streamLength(streamLength) - { + bitsPerSample(0), + sampleFrames(0) {} - } - - short format; + int format; int length; int bitrate; int sampleRate; int channels; - int sampleWidth; - uint streamLength; + int bitsPerSample; + unsigned int sampleFrames; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::WAV::Properties::Properties(const ByteVector &data, ReadStyle style) : AudioProperties(style) +RIFF::WAV::Properties::Properties(const ByteVector &, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(); - read(data); + debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used."); } -RIFF::WAV::Properties::Properties(const ByteVector &data, uint streamLength, ReadStyle style) : AudioProperties(style) +RIFF::WAV::Properties::Properties(const ByteVector &, unsigned int, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(streamLength); - read(data); + debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used."); +} + +TagLib::RIFF::WAV::Properties::Properties(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) +{ + read(file); } RIFF::WAV::Properties::~Properties() @@ -78,6 +92,16 @@ RIFF::WAV::Properties::~Properties() } int RIFF::WAV::Properties::length() const +{ + return lengthInSeconds(); +} + +int RIFF::WAV::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int RIFF::WAV::Properties::lengthInMilliseconds() const { return d->length; } @@ -97,24 +121,101 @@ int RIFF::WAV::Properties::channels() const return d->channels; } +int RIFF::WAV::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + int RIFF::WAV::Properties::sampleWidth() const { - return d->sampleWidth; + return bitsPerSample(); +} + +unsigned int RIFF::WAV::Properties::sampleFrames() const +{ + return d->sampleFrames; +} + +int RIFF::WAV::Properties::format() const +{ + return d->format; } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::WAV::Properties::read(const ByteVector &data) +void RIFF::WAV::Properties::read(File *file) { - d->format = data.mid(0, 2).toShort(false); - d->channels = data.mid(2, 2).toShort(false); - d->sampleRate = data.mid(4, 4).toUInt(false); - d->sampleWidth = data.mid(14, 2).toShort(false); + ByteVector data; + unsigned int streamLength = 0; + unsigned int totalSamples = 0; - uint byteRate = data.mid(8, 4).toUInt(false); - d->bitrate = byteRate * 8 / 1000; + for(unsigned int i = 0; i < file->chunkCount(); ++i) { + const ByteVector name = file->chunkName(i); + if(name == "fmt ") { + if(data.isEmpty()) + data = file->chunkData(i); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'fmt ' chunk found."); + } + else if(name == "data") { + if(streamLength == 0) + streamLength = file->chunkDataSize(i) + file->chunkPadding(i); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'data' chunk found."); + } + else if(name == "fact") { + if(totalSamples == 0) + totalSamples = file->chunkData(i).toUInt(0, false); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'fact' chunk found."); + } + } - d->length = byteRate > 0 ? d->streamLength / byteRate : 0; + if(data.size() < 16) { + debug("RIFF::WAV::Properties::read() - 'fmt ' chunk not found or too short."); + return; + } + + if(streamLength == 0) { + debug("RIFF::WAV::Properties::read() - 'data' chunk not found."); + return; + } + + d->format = data.toShort(0, false); + if((d->format & 0xffff) == 0xfffe) { + // if extensible then read the format from the subformat + if(data.size() != 40) { + debug("RIFF::WAV::Properties::read() - extensible size incorrect"); + return; + } + d->format = data.toShort(24, false); + } + if(d->format != FORMAT_PCM && d->format != FORMAT_IEEE_FLOAT && totalSamples == 0) { + debug("RIFF::WAV::Properties::read() - Non-PCM format, but 'fact' chunk not found."); + return; + } + + d->channels = data.toShort(2, false); + d->sampleRate = data.toUInt(4, false); + d->bitsPerSample = data.toShort(14, false); + + if(d->format != FORMAT_PCM && !(d->format == FORMAT_IEEE_FLOAT && totalSamples == 0)) + d->sampleFrames = totalSamples; + else if(d->channels > 0 && d->bitsPerSample > 0) + d->sampleFrames = streamLength / (d->channels * ((d->bitsPerSample + 7) / 8)); + + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast<int>(length + 0.5); + d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5); + } + else { + const unsigned int byteRate = data.toUInt(8, false); + if(byteRate > 0) { + d->length = static_cast<int>(streamLength * 1000.0 / byteRate + 0.5); + d->bitrate = static_cast<int>(byteRate * 8.0 / 1000.0 + 0.5); + } + } } diff --git a/Frameworks/TagLib/taglib/taglib/riff/wav/wavproperties.h b/Frameworks/TagLib/taglib/taglib/riff/wav/wavproperties.h index bf87ffe2a..1ca73100b 100644 --- a/Frameworks/TagLib/taglib/taglib/riff/wav/wavproperties.h +++ b/Frameworks/TagLib/taglib/taglib/riff/wav/wavproperties.h @@ -49,40 +49,112 @@ namespace TagLib { class TAGLIB_EXPORT Properties : public AudioProperties { public: - /*! - * Create an instance of WAV::Properties with the data read from the - * ByteVector \a data. - */ - Properties(const ByteVector &data, ReadStyle style); + /*! + * Create an instance of WAV::Properties with the data read from the + * ByteVector \a data. + * + * \deprecated + */ + TAGLIB_DEPRECATED Properties(const ByteVector &data, ReadStyle style); - /*! - * Create an instance of WAV::Properties with the data read from the - * ByteVector \a data and the length calculated using \a streamLength. - */ - Properties(const ByteVector &data, uint streamLength, ReadStyle style); + /*! + * Create an instance of WAV::Properties with the data read from the + * ByteVector \a data and the length calculated using \a streamLength. + * + * \deprecated + */ + TAGLIB_DEPRECATED Properties(const ByteVector &data, unsigned int streamLength, ReadStyle style); - /*! - * Destroys this WAV::Properties instance. - */ - virtual ~Properties(); + /*! + * Create an instance of WAV::Properties with the data read from the + * WAV::File \a file. + */ + Properties(File *file, ReadStyle style); - // Reimplementations. + /*! + * Destroys this WAV::Properties instance. + */ + virtual ~Properties(); - virtual int length() const; - virtual int bitrate() const; - virtual int sampleRate() const; - virtual int channels() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; - int sampleWidth() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ + virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ + virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ + virtual int channels() const; + + /*! + * Returns the number of bits per audio sample. + */ + int bitsPerSample() const; + + /*! + * Returns the number of bits per audio sample. + * + * \note This method is just an alias of bitsPerSample(). + * + * \deprecated + */ + TAGLIB_DEPRECATED int sampleWidth() const; + + /*! + * Returns the number of sample frames. + */ + unsigned int sampleFrames() const; + + /*! + * Returns the format ID of the file. + * 0 for unknown, 1 for PCM, 2 for ADPCM, 3 for 32/64-bit IEEE754, and + * so forth. + * + * \note For further information, refer to the WAVE Form Registration + * Numbers in RFC 2361. + */ + int format() const; private: - Properties(const Properties &); - Properties &operator=(const Properties &); + Properties(const Properties &); + Properties &operator=(const Properties &); - void read(const ByteVector &data); + void read(File *file); - class PropertiesPrivate; - PropertiesPrivate *d; + class PropertiesPrivate; + PropertiesPrivate *d; }; } } diff --git a/Frameworks/TagLib/taglib/taglib/s3m/s3mfile.cpp b/Frameworks/TagLib/taglib/taglib/s3m/s3mfile.cpp new file mode 100644 index 000000000..b353f880a --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/s3m/s3mfile.cpp @@ -0,0 +1,248 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include "s3mfile.h" +#include "tstringlist.h" +#include "tdebug.h" +#include "modfileprivate.h" +#include "tpropertymap.h" + +#include <iostream> + +using namespace TagLib; +using namespace S3M; + +class S3M::File::FilePrivate +{ +public: + FilePrivate(AudioProperties::ReadStyle propertiesStyle) + : properties(propertiesStyle) + { + } + + Mod::Tag tag; + S3M::Properties properties; +}; + +S3M::File::File(FileName file, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + Mod::FileBase(file), + d(new FilePrivate(propertiesStyle)) +{ + if(isOpen()) + read(readProperties); +} + +S3M::File::File(IOStream *stream, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + Mod::FileBase(stream), + d(new FilePrivate(propertiesStyle)) +{ + if(isOpen()) + read(readProperties); +} + +S3M::File::~File() +{ + delete d; +} + +Mod::Tag *S3M::File::tag() const +{ + return &d->tag; +} + +PropertyMap S3M::File::properties() const +{ + return d->tag.properties(); +} + +PropertyMap S3M::File::setProperties(const PropertyMap &properties) +{ + return d->tag.setProperties(properties); +} + +S3M::Properties *S3M::File::audioProperties() const +{ + return &d->properties; +} + +bool S3M::File::save() +{ + if(readOnly()) { + debug("S3M::File::save() - Cannot save to a read only file."); + return false; + } + // note: if title starts with "Extended Module: " + // the file would look like an .xm file + seek(0); + writeString(d->tag.title(), 27); + // string terminating NUL is not optional: + writeByte(0); + + seek(32); + + unsigned short length = 0; + unsigned short sampleCount = 0; + + if(!readU16L(length) || !readU16L(sampleCount)) + return false; + + seek(28, Current); + + int channels = 0; + for(int i = 0; i < 32; ++ i) { + unsigned char setting = 0; + if(!readByte(setting)) + return false; + // or if(setting >= 128)? + // or channels = i + 1;? + // need a better spec! + if(setting != 0xff) ++ channels; + } + + seek(channels, Current); + + StringList lines = d->tag.comment().split("\n"); + // write comment as sample names: + for(unsigned short i = 0; i < sampleCount; ++ i) { + seek(96L + length + ((long)i << 1)); + + unsigned short instrumentOffset = 0; + if(!readU16L(instrumentOffset)) + return false; + seek(((long)instrumentOffset << 4) + 48); + + if(i < lines.size()) + writeString(lines[i], 27); + else + writeString(String(), 27); + // string terminating NUL is not optional: + writeByte(0); + } + return true; +} + +void S3M::File::read(bool) +{ + if(!isOpen()) + return; + + READ_STRING(d->tag.setTitle, 28); + READ_BYTE_AS(mark); + READ_BYTE_AS(type); + + READ_ASSERT(mark == 0x1A && type == 0x10); + + seek(32); + + READ_U16L_AS(length); + READ_U16L_AS(sampleCount); + + d->properties.setSampleCount(sampleCount); + + READ_U16L(d->properties.setPatternCount); + READ_U16L(d->properties.setFlags); + READ_U16L(d->properties.setTrackerVersion); + READ_U16L(d->properties.setFileFormatVersion); + + READ_ASSERT(readBlock(4) == "SCRM"); + + READ_BYTE(d->properties.setGlobalVolume); + READ_BYTE(d->properties.setBpmSpeed); + READ_BYTE(d->properties.setTempo); + + READ_BYTE_AS(masterVolume); + d->properties.setMasterVolume(masterVolume & 0x7f); + d->properties.setStereo((masterVolume & 0x80) != 0); + + // I've seen players who call the next two bytes + // "ultra click" and "use panning values" (if == 0xFC). + // I don't see them in any spec, though. + // Hm, but there is "UltraClick-removal" and some other + // variables in ScreamTracker III's GUI. + + seek(12, Current); + + int channels = 0; + for(int i = 0; i < 32; ++ i) { + READ_BYTE_AS(setting); + // or if(setting >= 128)? + // or channels = i + 1;? + // need a better spec! + if(setting != 0xff) ++ channels; + } + d->properties.setChannels(channels); + + seek(96); + unsigned short realLength = 0; + for(unsigned short i = 0; i < length; ++ i) { + READ_BYTE_AS(order); + if(order == 255) break; + if(order != 254) ++ realLength; + } + d->properties.setLengthInPatterns(realLength); + + seek(channels, Current); + + // Note: The S3M spec mentions samples and instruments, but in + // the header there are only pointers to instruments. + // However, there I never found instruments (SCRI) but + // instead samples (SCRS). + StringList comment; + for(unsigned short i = 0; i < sampleCount; ++ i) { + seek(96L + length + ((long)i << 1)); + + READ_U16L_AS(sampleHeaderOffset); + seek((long)sampleHeaderOffset << 4); + + READ_BYTE_AS(sampleType); + READ_STRING_AS(dosFileName, 13); + READ_U16L_AS(sampleDataOffset); + READ_U32L_AS(sampleLength); + READ_U32L_AS(repeatStart); + READ_U32L_AS(repeatStop); + READ_BYTE_AS(sampleVolume); + + seek(1, Current); + + READ_BYTE_AS(packing); + READ_BYTE_AS(sampleFlags); + READ_U32L_AS(baseFrequency); + + seek(12, Current); + + READ_STRING_AS(sampleName, 28); + // The next 4 bytes should be "SCRS", but I've found + // files that are otherwise ok with 4 nils instead. + // READ_ASSERT(readBlock(4) == "SCRS"); + + comment.append(sampleName); + } + + d->tag.setComment(comment.toString("\n")); + d->tag.setTrackerName("ScreamTracker III"); +} diff --git a/Frameworks/TagLib/taglib/taglib/s3m/s3mfile.h b/Frameworks/TagLib/taglib/taglib/s3m/s3mfile.h new file mode 100644 index 000000000..4011bd14a --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/s3m/s3mfile.h @@ -0,0 +1,112 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_S3MFILE_H +#define TAGLIB_S3MFILE_H + +#include "tfile.h" +#include "audioproperties.h" +#include "taglib_export.h" +#include "modfilebase.h" +#include "modtag.h" +#include "s3mproperties.h" + +namespace TagLib { + + namespace S3M { + + class TAGLIB_EXPORT File : public Mod::FileBase { + public: + /*! + * Constructs a ScreamTracker III from \a file. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + */ + File(FileName file, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Constructs a ScreamTracker III file from \a stream. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + File(IOStream *stream, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + Mod::Tag *tag() const; + + /*! + * Implements the unified property interface -- export function. + * Forwards to Mod::Tag::properties(). + */ + PropertyMap properties() const; + + /*! + * Implements the unified property interface -- import function. + * Forwards to Mod::Tag::setProperties(). + */ + PropertyMap setProperties(const PropertyMap &); + + /*! + * Returns the S3M::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + S3M::Properties *audioProperties() const; + + /*! + * Save the file. + * This is the same as calling save(AllTags); + * + * \note Saving ScreamTracker III tags is not supported. + */ + bool save(); + + private: + File(const File &); + File &operator=(const File &); + + void read(bool readProperties); + + class FilePrivate; + FilePrivate *d; + }; + } +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/s3m/s3mproperties.cpp b/Frameworks/TagLib/taglib/taglib/s3m/s3mproperties.cpp new file mode 100644 index 000000000..e3e443cb8 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/s3m/s3mproperties.cpp @@ -0,0 +1,219 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include "s3mproperties.h" + +using namespace TagLib; +using namespace S3M; + +class S3M::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate() : + lengthInPatterns(0), + channels(0), + stereo(false), + sampleCount(0), + patternCount(0), + flags(0), + trackerVersion(0), + fileFormatVersion(0), + globalVolume(0), + masterVolume(0), + tempo(0), + bpmSpeed(0) + { + } + + unsigned short lengthInPatterns; + int channels; + bool stereo; + unsigned short sampleCount; + unsigned short patternCount; + unsigned short flags; + unsigned short trackerVersion; + unsigned short fileFormatVersion; + unsigned char globalVolume; + unsigned char masterVolume; + unsigned char tempo; + unsigned char bpmSpeed; +}; + +S3M::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) : + AudioProperties(propertiesStyle), + d(new PropertiesPrivate()) +{ +} + +S3M::Properties::~Properties() +{ + delete d; +} + +int S3M::Properties::length() const +{ + return 0; +} + +int S3M::Properties::lengthInSeconds() const +{ + return 0; +} + +int S3M::Properties::lengthInMilliseconds() const +{ + return 0; +} + +int S3M::Properties::bitrate() const +{ + return 0; +} + +int S3M::Properties::sampleRate() const +{ + return 0; +} + +int S3M::Properties::channels() const +{ + return d->channels; +} + +unsigned short S3M::Properties::lengthInPatterns() const +{ + return d->lengthInPatterns; +} + +bool S3M::Properties::stereo() const +{ + return d->stereo; +} + +unsigned short S3M::Properties::sampleCount() const +{ + return d->sampleCount; +} + +unsigned short S3M::Properties::patternCount() const +{ + return d->patternCount; +} + +unsigned short S3M::Properties::flags() const +{ + return d->flags; +} + +unsigned short S3M::Properties::trackerVersion() const +{ + return d->trackerVersion; +} + +unsigned short S3M::Properties::fileFormatVersion() const +{ + return d->fileFormatVersion; +} + +unsigned char S3M::Properties::globalVolume() const +{ + return d->globalVolume; +} + +unsigned char S3M::Properties::masterVolume() const +{ + return d->masterVolume; +} + +unsigned char S3M::Properties::tempo() const +{ + return d->tempo; +} + +unsigned char S3M::Properties::bpmSpeed() const +{ + return d->bpmSpeed; +} + +void S3M::Properties::setLengthInPatterns(unsigned short lengthInPatterns) +{ + d->lengthInPatterns = lengthInPatterns; +} + +void S3M::Properties::setChannels(int channels) +{ + d->channels = channels; +} + +void S3M::Properties::setStereo(bool stereo) +{ + d->stereo = stereo; +} + +void S3M::Properties::setSampleCount(unsigned short sampleCount) +{ + d->sampleCount = sampleCount; +} + +void S3M::Properties::setPatternCount(unsigned short patternCount) +{ + d->patternCount = patternCount; +} + +void S3M::Properties::setFlags(unsigned short flags) +{ + d->flags = flags; +} + +void S3M::Properties::setTrackerVersion(unsigned short trackerVersion) +{ + d->trackerVersion = trackerVersion; +} + +void S3M::Properties::setFileFormatVersion(unsigned short fileFormatVersion) +{ + d->fileFormatVersion = fileFormatVersion; +} + +void S3M::Properties::setGlobalVolume(unsigned char globalVolume) +{ + d->globalVolume = globalVolume; +} + +void S3M::Properties::setMasterVolume(unsigned char masterVolume) +{ + d->masterVolume = masterVolume; +} + +void S3M::Properties::setTempo(unsigned char tempo) +{ + d->tempo = tempo; +} + +void S3M::Properties::setBpmSpeed(unsigned char bpmSpeed) +{ + d->bpmSpeed = bpmSpeed; +} diff --git a/Frameworks/TagLib/taglib/taglib/s3m/s3mproperties.h b/Frameworks/TagLib/taglib/taglib/s3m/s3mproperties.h new file mode 100644 index 000000000..92b2a2746 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/s3m/s3mproperties.h @@ -0,0 +1,94 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_S3MPROPERTIES_H +#define TAGLIB_S3MPROPERTIES_H + +#include "taglib.h" +#include "audioproperties.h" + +namespace TagLib { + namespace S3M { + class TAGLIB_EXPORT Properties : public AudioProperties { + friend class File; + public: + /*! Flag bits. */ + enum { + ST2Vibrato = 1, + ST2Tempo = 2, + AmigaSlides = 4, + Vol0MixOptimizations = 8, + AmigaLimits = 16, + EnableFilter = 32, + CustomData = 128 + }; + + Properties(AudioProperties::ReadStyle propertiesStyle); + virtual ~Properties(); + + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; + + unsigned short lengthInPatterns() const; + bool stereo() const; + unsigned short sampleCount() const; + unsigned short patternCount() const; + unsigned short flags() const; + unsigned short trackerVersion() const; + unsigned short fileFormatVersion() const; + unsigned char globalVolume() const; + unsigned char masterVolume() const; + unsigned char tempo() const; + unsigned char bpmSpeed() const; + + void setChannels(int channels); + + void setLengthInPatterns (unsigned short lengthInPatterns); + void setStereo (bool stereo); + void setSampleCount (unsigned short sampleCount); + void setPatternCount (unsigned short patternCount); + void setFlags (unsigned short flags); + void setTrackerVersion (unsigned short trackerVersion); + void setFileFormatVersion(unsigned short fileFormatVersion); + void setGlobalVolume (unsigned char globalVolume); + void setMasterVolume (unsigned char masterVolume); + void setTempo (unsigned char tempo); + void setBpmSpeed (unsigned char bpmSpeed); + + private: + Properties(const Properties&); + Properties &operator=(const Properties&); + + class PropertiesPrivate; + PropertiesPrivate *d; + }; + } +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/tag.cpp b/Frameworks/TagLib/taglib/taglib/tag.cpp index 8be33c80f..3526226f8 100644 --- a/Frameworks/TagLib/taglib/taglib/tag.cpp +++ b/Frameworks/TagLib/taglib/taglib/tag.cpp @@ -24,6 +24,8 @@ ***************************************************************************/ #include "tag.h" +#include "tstringlist.h" +#include "tpropertymap.h" using namespace TagLib; @@ -53,6 +55,101 @@ bool Tag::isEmpty() const track() == 0); } +PropertyMap Tag::properties() const +{ + PropertyMap map; + if(!(title().isEmpty())) + map["TITLE"].append(title()); + if(!(artist().isEmpty())) + map["ARTIST"].append(artist()); + if(!(album().isEmpty())) + map["ALBUM"].append(album()); + if(!(comment().isEmpty())) + map["COMMENT"].append(comment()); + if(!(genre().isEmpty())) + map["GENRE"].append(genre()); + if(!(year() == 0)) + map["DATE"].append(String::number(year())); + if(!(track() == 0)) + map["TRACKNUMBER"].append(String::number(track())); + return map; +} + +void Tag::removeUnsupportedProperties(const StringList&) +{ +} + +PropertyMap Tag::setProperties(const PropertyMap &origProps) +{ + PropertyMap properties(origProps); + properties.removeEmpty(); + StringList oneValueSet; + // can this be simplified by using some preprocessor defines / function pointers? + if(properties.contains("TITLE")) { + setTitle(properties["TITLE"].front()); + oneValueSet.append("TITLE"); + } else + setTitle(String()); + + if(properties.contains("ARTIST")) { + setArtist(properties["ARTIST"].front()); + oneValueSet.append("ARTIST"); + } else + setArtist(String()); + + if(properties.contains("ALBUM")) { + setAlbum(properties["ALBUM"].front()); + oneValueSet.append("ALBUM"); + } else + setAlbum(String()); + + if(properties.contains("COMMENT")) { + setComment(properties["COMMENT"].front()); + oneValueSet.append("COMMENT"); + } else + setComment(String()); + + if(properties.contains("GENRE")) { + setGenre(properties["GENRE"].front()); + oneValueSet.append("GENRE"); + } else + setGenre(String()); + + if(properties.contains("DATE")) { + bool ok; + int date = properties["DATE"].front().toInt(&ok); + if(ok) { + setYear(date); + oneValueSet.append("DATE"); + } else + setYear(0); + } + else + setYear(0); + + if(properties.contains("TRACKNUMBER")) { + bool ok; + int track = properties["TRACKNUMBER"].front().toInt(&ok); + if(ok) { + setTrack(track); + oneValueSet.append("TRACKNUMBER"); + } else + setTrack(0); + } + else + setTrack(0); + + // for each tag that has been set above, remove the first entry in the corresponding + // value list. The others will be returned as unsupported by this format. + for(StringList::ConstIterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) { + if(properties[*it].size() == 1) + properties.erase(*it); + else + properties[*it].erase( properties[*it].begin() ); + } + return properties; +} + void Tag::duplicate(const Tag *source, Tag *target, bool overwrite) // static { if(overwrite) { diff --git a/Frameworks/TagLib/taglib/taglib/tag.h b/Frameworks/TagLib/taglib/taglib/tag.h index 2c00a6490..be9628c81 100644 --- a/Frameworks/TagLib/taglib/taglib/tag.h +++ b/Frameworks/TagLib/taglib/taglib/tag.h @@ -41,15 +41,43 @@ namespace TagLib { * in TagLib::AudioProperties, TagLib::File and TagLib::FileRef. */ + class PropertyMap; + class TAGLIB_EXPORT Tag { public: /*! - * Detroys this Tag instance. + * Destroys this Tag instance. */ virtual ~Tag(); + /*! + * Exports the tags of the file as dictionary mapping (human readable) tag + * names (Strings) to StringLists of tag values. + * The default implementation in this class considers only the usual built-in + * tags (artist, album, ...) and only one value per key. + */ + PropertyMap properties() const; + + /*! + * Removes unsupported properties, or a subset of them, from the tag. + * The parameter \a properties must contain only entries from + * properties().unsupportedData(). + * BIC: Will become virtual in future releases. Currently the non-virtual + * standard implementation of TagLib::Tag does nothing, since there are + * no unsupported elements. + */ + void removeUnsupportedProperties(const StringList& properties); + + /*! + * Sets the tags of this File to those specified in \a properties. This default + * implementation sets only the tags for which setter methods exist in this class + * (artist, album, ...), and only one value per key; the rest will be contained + * in the returned PropertyMap. + */ + PropertyMap setProperties(const PropertyMap &properties); + /*! * Returns the track name; if no track name is present in the tag * String::null will be returned. @@ -83,38 +111,14 @@ namespace TagLib { /*! * Returns the year; if there is no year set, this will return 0. */ - virtual uint year() const = 0; + virtual unsigned int year() const = 0; /*! * Returns the track number; if there is no track number set, this will * return 0. */ - virtual uint track() const = 0; + virtual unsigned int track() const = 0; - /*! - * Returns the ReplayGain album gain; if there is no gain level set, this - * will return 0. - */ - virtual float rgAlbumGain() const = 0; - - /*! - * Returns the ReplayGain album peak; if there is no gain level set, this - * will return 0. - */ - virtual float rgAlbumPeak() const = 0; - - /*! - * Returns the ReplayGain track gain; if there is no gain level set, this - * will return 0. - */ - virtual float rgTrackGain() const = 0; - - /*! - * Returns the ReplayGain track peak; if there is no gain level set, this - * will return 0. - */ - virtual float rgTrackPeak() const = 0; - /*! * Sets the title to \a s. If \a s is String::null then this value will be * cleared. @@ -151,12 +155,12 @@ namespace TagLib { /*! * Sets the year to \a i. If \a s is 0 then this value will be cleared. */ - virtual void setYear(uint i) = 0; + virtual void setYear(unsigned int i) = 0; /*! * Sets the track to \a i. If \a s is 0 then this value will be cleared. */ - virtual void setTrack(uint i) = 0; + virtual void setTrack(unsigned int i) = 0; /*! * Returns true if the tag does not contain any data. This should be @@ -165,30 +169,6 @@ namespace TagLib { */ virtual bool isEmpty() const; - /*! - * Sets the ReplayGain album gain to \a f. If \a f is 0 then this value will - * be cleared. - */ - virtual void setRGAlbumGain(float i) = 0; - - /*! - * Sets the ReplayGain album peak to \a f. If \a f is 0 then this value will - * be cleared. - */ - virtual void setRGAlbumPeak(float i) = 0; - - /*! - * Sets the ReplayGain track gain to \a f. If \a f is 0 then this value will - * be cleared. - */ - virtual void setRGTrackGain(float i) = 0; - - /*! - * Sets the ReplayGain track peak to \a f. If \a f is 0 then this value will - * be cleared. - */ - virtual void setRGTrackPeak(float i) = 0; - /*! * Copies the generic data from one tag to another. * diff --git a/Frameworks/TagLib/taglib/taglib/taglib.pro b/Frameworks/TagLib/taglib/taglib/taglib.pro deleted file mode 100644 index 0124143f0..000000000 --- a/Frameworks/TagLib/taglib/taglib/taglib.pro +++ /dev/null @@ -1,275 +0,0 @@ -###################################################################### -# Automatically generated by qmake (2.01a) Fri Feb 1 15:35:13 2008 -###################################################################### - -TEMPLATE = lib -CONFIG += lib_bundle staticlib -CONFIG += x86 x86_64 ppc -CONFIG -= qt -DEFINES += HAVE_ZLIB=1 NDEBUG WITH_ASF WITH_MP4 TAGLIB_NO_CONFIG -LIBS += -lz -TARGET = TagLib -VERSION = 1.6.3 -DEPENDPATH += . \ - ape \ - asf \ - flac \ - mp4 \ - mpc \ - mpeg \ - mpeg/id3v1 \ - mpeg/id3v2 \ - mpeg/id3v2/frames \ - ogg \ - ogg/flac \ - ogg/speex \ - ogg/vorbis \ - riff \ - riff/aiff \ - riff/wav \ - toolkit \ - trueaudio \ - wavpack - -INCLUDEPATH += . \ - ape \ - asf \ - flac \ - mp4 \ - mpc \ - mpeg \ - mpeg/id3v1 \ - mpeg/id3v2 \ - mpeg/id3v2/frames \ - ogg \ - ogg/flac \ - ogg/speex \ - ogg/vorbis \ - riff \ - riff/aiff \ - riff/wav \ - toolkit \ - trueaudio \ - wavpack - -# Input -HEADERS += audioproperties.h \ - fileref.h \ - tag.h \ - taglib_export.h \ - tagunion.h \ - ape/apefooter.h \ - ape/apeitem.h \ - ape/apetag.h \ - flac/flacfile.h \ - flac/flacproperties.h \ - flac/flacpicture.h \ - mpc/mpcfile.h \ - mpc/mpcproperties.h \ - mp4/mp4atom.h \ - mp4/mp4item.h \ - mp4/mp4file.h \ - mp4/mp4properties.h \ - mpeg/mpegfile.h \ - mpeg/mpegheader.h \ - mpeg/mpegproperties.h \ - mpeg/xingheader.h \ - ogg/oggfile.h \ - ogg/oggpage.h \ - ogg/oggpageheader.h \ - ogg/xiphcomment.h \ - ogg/speex/speexfile.h \ - ogg/speex/speexproperties.h \ - toolkit/taglib.h \ - toolkit/tbytevector.h \ - toolkit/tbytevectorlist.h \ - toolkit/tdebug.h \ - toolkit/tfile.h \ - toolkit/tlist.h \ - toolkit/tmap.h \ - toolkit/tstring.h \ - toolkit/tstringlist.h \ - toolkit/unicode.h \ - trueaudio/trueaudiofile.h \ - trueaudio/trueaudioproperties.h \ - wavpack/wavpackfile.h \ - wavpack/wavpackproperties.h \ - mpeg/id3v1/id3v1genres.h \ - mpeg/id3v1/id3v1tag.h \ - mpeg/id3v2/id3v2extendedheader.h \ - mpeg/id3v2/id3v2footer.h \ - mpeg/id3v2/id3v2frame.h \ - mpeg/id3v2/id3v2framefactory.h \ - mpeg/id3v2/id3v2header.h \ - mpeg/id3v2/id3v2synchdata.h \ - mpeg/id3v2/id3v2tag.h \ - ogg/flac/oggflacfile.h \ - ogg/vorbis/vorbisfile.h \ - ogg/vorbis/vorbisproperties.h \ - mpeg/id3v2/frames/attachedpictureframe.h \ - mpeg/id3v2/frames/commentsframe.h \ - mpeg/id3v2/frames/generalencapsulatedobjectframe.h \ - mpeg/id3v2/frames/popularimeterframe.h \ - mpeg/id3v2/frames/relativevolumeframe.h \ - mpeg/id3v2/frames/textidentificationframe.h \ - mpeg/id3v2/frames/uniquefileidentifierframe.h \ - mpeg/id3v2/frames/unknownframe.h \ - mpeg/id3v2/frames/unsynchronizedlyricsframe.h \ - mpeg/id3v2/frames/urllinkframe.h \ - toolkit/tlist.tcc \ - toolkit/tmap.tcc -SOURCES += ape/apefooter.cpp \ - ape/apeitem.cpp \ - ape/apetag.cpp \ - asf/asfattribute.cpp \ - asf/asffile.cpp \ - asf/asfproperties.cpp \ - asf/asftag.cpp \ - asf/asfpicture.cpp \ - audioproperties.cpp \ - fileref.cpp \ - flac/flacfile.cpp \ - flac/flacproperties.cpp \ - flac/flacpicture.cpp \ - mp4/mp4atom.cpp \ - mp4/mp4coverart.cpp \ - mp4/mp4file.cpp \ - mp4/mp4item.cpp \ - mp4/mp4properties.cpp \ - mp4/mp4tag.cpp \ - mpc/mpcfile.cpp \ - mpc/mpcproperties.cpp \ - mpeg/id3v1/id3v1genres.cpp \ - mpeg/id3v1/id3v1tag.cpp \ - mpeg/id3v2/frames/attachedpictureframe.cpp \ - mpeg/id3v2/frames/commentsframe.cpp \ - mpeg/id3v2/frames/generalencapsulatedobjectframe.cpp \ - mpeg/id3v2/frames/popularimeterframe.cpp \ - mpeg/id3v2/frames/privateframe.cpp \ - mpeg/id3v2/frames/relativevolumeframe.cpp \ - mpeg/id3v2/frames/textidentificationframe.cpp \ - mpeg/id3v2/frames/uniquefileidentifierframe.cpp \ - mpeg/id3v2/frames/unknownframe.cpp \ - mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp \ - mpeg/id3v2/frames/urllinkframe.cpp \ - mpeg/id3v2/id3v2extendedheader.cpp \ - mpeg/id3v2/id3v2footer.cpp \ - mpeg/id3v2/id3v2frame.cpp \ - mpeg/id3v2/id3v2framefactory.cpp \ - mpeg/id3v2/id3v2header.cpp \ - mpeg/id3v2/id3v2synchdata.cpp \ - mpeg/id3v2/id3v2tag.cpp \ - mpeg/mpegfile.cpp \ - mpeg/mpegheader.cpp \ - mpeg/mpegproperties.cpp \ - mpeg/xingheader.cpp \ - ogg/flac/oggflacfile.cpp \ - ogg/oggfile.cpp \ - ogg/oggpage.cpp \ - ogg/oggpageheader.cpp \ - ogg/speex/speexfile.cpp \ - ogg/speex/speexproperties.cpp \ - ogg/vorbis/vorbisfile.cpp \ - ogg/vorbis/vorbisproperties.cpp \ - ogg/xiphcomment.cpp \ - riff/aiff/aifffile.cpp \ - riff/aiff/aiffproperties.cpp \ - riff/rifffile.cpp \ - riff/wav/wavfile.cpp \ - riff/wav/wavproperties.cpp \ - tag.cpp \ - tagunion.cpp \ - toolkit/tbytevector.cpp \ - toolkit/tbytevectorlist.cpp \ - toolkit/tdebug.cpp \ - toolkit/tfile.cpp \ - toolkit/tstring.cpp \ - toolkit/tstringlist.cpp \ - toolkit/unicode.cpp \ - trueaudio/trueaudiofile.cpp \ - trueaudio/trueaudioproperties.cpp \ - wavpack/wavpackfile.cpp \ - wavpack/wavpackproperties.cpp - -FRAMEWORK_HEADERS.version = Versions -FRAMEWORK_HEADERS.files = \ - ape/apefooter.h \ - ape/apeitem.h \ - ape/apetag.h \ - asf/asfattribute.h \ - asf/asffile.h \ - asf/asfproperties.h \ - asf/asftag.h \ - asf/asfpicture.h \ - audioproperties.h \ - fileref.h \ - flac/flacfile.h \ - flac/flacproperties.h \ - mp4/mp4atom.h \ - mp4/mp4coverart.h \ - mp4/mp4file.h \ - mp4/mp4item.h \ - mp4/mp4properties.h \ - mp4/mp4tag.h \ - mpc/mpcfile.h \ - mpc/mpcproperties.h \ - mpeg/id3v1/id3v1genres.h \ - mpeg/id3v1/id3v1tag.h \ - mpeg/id3v2/frames/attachedpictureframe.h \ - mpeg/id3v2/frames/commentsframe.h \ - mpeg/id3v2/frames/generalencapsulatedobjectframe.h \ - mpeg/id3v2/frames/popularimeterframe.h \ - mpeg/id3v2/frames/privateframe.h \ - mpeg/id3v2/frames/relativevolumeframe.h \ - mpeg/id3v2/frames/textidentificationframe.h \ - mpeg/id3v2/frames/uniquefileidentifierframe.h \ - mpeg/id3v2/frames/unknownframe.h \ - mpeg/id3v2/frames/unsynchronizedlyricsframe.h \ - mpeg/id3v2/frames/urllinkframe.h \ - mpeg/id3v2/id3v2extendedheader.h \ - mpeg/id3v2/id3v2footer.h \ - mpeg/id3v2/id3v2frame.h \ - mpeg/id3v2/id3v2framefactory.h \ - mpeg/id3v2/id3v2header.h \ - mpeg/id3v2/id3v2synchdata.h \ - mpeg/id3v2/id3v2tag.h \ - mpeg/mpegfile.h \ - mpeg/mpegheader.h \ - mpeg/mpegproperties.h \ - mpeg/xingheader.h \ - ogg/flac/oggflacfile.h \ - ogg/oggfile.h \ - ogg/oggpage.h \ - ogg/oggpageheader.h \ - ogg/speex/speexfile.h \ - ogg/speex/speexproperties.h \ - ogg/vorbis/vorbisfile.h \ - ogg/vorbis/vorbisproperties.h \ - ogg/xiphcomment.h \ - riff/aiff/aifffile.h \ - riff/aiff/aiffproperties.h \ - riff/rifffile.h \ - riff/wav/wavfile.h \ - riff/wav/wavproperties.h \ - tag.h \ - taglib_export.h \ - tagunion.h \ - toolkit/taglib.h \ - toolkit/tbytevector.h \ - toolkit/tbytevectorlist.h \ - toolkit/tdebug.h \ - toolkit/tfile.h \ - toolkit/tlist.h \ - toolkit/tlist.tcc \ - toolkit/tmap.h \ - toolkit/tmap.tcc \ - toolkit/tstring.h \ - toolkit/tstringlist.h \ - toolkit/unicode.h \ - trueaudio/trueaudiofile.h \ - trueaudio/trueaudioproperties.h \ - wavpack/wavpackfile.h \ - wavpack/wavpackproperties.h - - FRAMEWORK_HEADERS.path = Headers - QMAKE_BUNDLE_DATA += FRAMEWORK_HEADERS diff --git a/Frameworks/TagLib/taglib/taglib/taglib_config.h.cmake b/Frameworks/TagLib/taglib/taglib/taglib_config.h.cmake new file mode 100644 index 000000000..915f130aa --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/taglib_config.h.cmake @@ -0,0 +1,11 @@ +/* taglib_config.h. Generated by cmake from taglib_config.h.cmake */ + +#ifndef TAGLIB_TAGLIB_CONFIG_H +#define TAGLIB_TAGLIB_CONFIG_H + +/* These values are no longer used. This file is present only for compatibility reasons. */ + +#define TAGLIB_WITH_ASF 1 +#define TAGLIB_WITH_MP4 1 + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/taglib_export.h b/Frameworks/TagLib/taglib/taglib/taglib_export.h index 3e8685517..737ae6442 100644 --- a/Frameworks/TagLib/taglib/taglib/taglib_export.h +++ b/Frameworks/TagLib/taglib/taglib/taglib_export.h @@ -40,8 +40,4 @@ #define TAGLIB_EXPORT #endif -#ifndef TAGLIB_NO_CONFIG -#include "taglib_config.h" -#endif - #endif diff --git a/Frameworks/TagLib/taglib/taglib/tagunion.cpp b/Frameworks/TagLib/taglib/taglib/tagunion.cpp index 315983f6e..64d786b9a 100644 --- a/Frameworks/TagLib/taglib/taglib/tagunion.cpp +++ b/Frameworks/TagLib/taglib/taglib/tagunion.cpp @@ -23,18 +23,26 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include "tagunion.h" +#include <tagunion.h> +#include <tstringlist.h> +#include <tpropertymap.h> + +#include "id3v1tag.h" +#include "id3v2tag.h" +#include "apetag.h" +#include "xiphcomment.h" +#include "infotag.h" using namespace TagLib; #define stringUnion(method) \ - if(tag(0) && !tag(0)->method().isNull() && !tag(0)->method().isEmpty()) \ + if(tag(0) && !tag(0)->method().isEmpty()) \ return tag(0)->method(); \ - if(tag(1) && !tag(1)->method().isNull() && !tag(1)->method().isEmpty()) \ + if(tag(1) && !tag(1)->method().isEmpty()) \ return tag(1)->method(); \ - if(tag(2) && !tag(2)->method().isNull() && !tag(2)->method().isEmpty()) \ + if(tag(2) && !tag(2)->method().isEmpty()) \ return tag(2)->method(); \ - return String::null \ + return String(); \ #define numberUnion(method) \ if(tag(0) && tag(0)->method() > 0) \ @@ -45,15 +53,6 @@ using namespace TagLib; return tag(2)->method(); \ return 0 -#define floatUnion(method) \ - if(tag(0) && tag(0)->method() != 0) \ - return tag(0)->method(); \ - if(tag(1) && tag(1)->method() != 0) \ - return tag(1)->method(); \ - if(tag(2) && tag(2)->method() != 0) \ - return tag(2)->method(); \ - return 0 - #define setUnion(method, value) \ if(tag(0)) \ tag(0)->set##method(value); \ @@ -80,10 +79,9 @@ public: std::vector<Tag *> tags; }; -TagUnion::TagUnion(Tag *first, Tag *second, Tag *third) +TagUnion::TagUnion(Tag *first, Tag *second, Tag *third) : + d(new TagUnionPrivate()) { - d = new TagUnionPrivate; - d->tags[0] = first; d->tags[1] = second; d->tags[2] = third; @@ -110,6 +108,62 @@ void TagUnion::set(int index, Tag *tag) d->tags[index] = tag; } +PropertyMap TagUnion::properties() const +{ + // This is an ugly workaround but we can't add a virtual function. + // Should be virtual in taglib2. + + for(size_t i = 0; i < 3; ++i) { + + if(d->tags[i] && !d->tags[i]->isEmpty()) { + + if(dynamic_cast<const ID3v1::Tag *>(d->tags[i])) + return dynamic_cast<const ID3v1::Tag *>(d->tags[i])->properties(); + + else if(dynamic_cast<const ID3v2::Tag *>(d->tags[i])) + return dynamic_cast<const ID3v2::Tag *>(d->tags[i])->properties(); + + else if(dynamic_cast<const APE::Tag *>(d->tags[i])) + return dynamic_cast<const APE::Tag *>(d->tags[i])->properties(); + + else if(dynamic_cast<const Ogg::XiphComment *>(d->tags[i])) + return dynamic_cast<const Ogg::XiphComment *>(d->tags[i])->properties(); + + else if(dynamic_cast<const RIFF::Info::Tag *>(d->tags[i])) + return dynamic_cast<const RIFF::Info::Tag *>(d->tags[i])->properties(); + } + } + + return PropertyMap(); +} + +void TagUnion::removeUnsupportedProperties(const StringList &unsupported) +{ + // This is an ugly workaround but we can't add a virtual function. + // Should be virtual in taglib2. + + for(size_t i = 0; i < 3; ++i) { + + if(d->tags[i]) { + + if(dynamic_cast<ID3v1::Tag *>(d->tags[i])) + dynamic_cast<ID3v1::Tag *>(d->tags[i])->removeUnsupportedProperties(unsupported); + + else if(dynamic_cast<ID3v2::Tag *>(d->tags[i])) + dynamic_cast<ID3v2::Tag *>(d->tags[i])->removeUnsupportedProperties(unsupported); + + else if(dynamic_cast<APE::Tag *>(d->tags[i])) + dynamic_cast<APE::Tag *>(d->tags[i])->removeUnsupportedProperties(unsupported); + + else if(dynamic_cast<Ogg::XiphComment *>(d->tags[i])) + dynamic_cast<Ogg::XiphComment *>(d->tags[i])->removeUnsupportedProperties(unsupported); + + else if(dynamic_cast<RIFF::Info::Tag *>(d->tags[i])) + dynamic_cast<RIFF::Info::Tag *>(d->tags[i])->removeUnsupportedProperties(unsupported); + } + } +} + String TagUnion::title() const { stringUnion(title); @@ -135,36 +189,16 @@ String TagUnion::genre() const stringUnion(genre); } -TagLib::uint TagUnion::year() const +unsigned int TagUnion::year() const { numberUnion(year); } -TagLib::uint TagUnion::track() const +unsigned int TagUnion::track() const { numberUnion(track); } -float TagUnion::rgAlbumGain() const -{ - floatUnion(rgAlbumGain); -} - -float TagUnion::rgAlbumPeak() const -{ - floatUnion(rgAlbumPeak); -} - -float TagUnion::rgTrackGain() const -{ - floatUnion(rgTrackGain); -} - -float TagUnion::rgTrackPeak() const -{ - floatUnion(rgTrackPeak); -} - void TagUnion::setTitle(const String &s) { setUnion(Title, s); @@ -190,36 +224,16 @@ void TagUnion::setGenre(const String &s) setUnion(Genre, s); } -void TagUnion::setYear(uint i) +void TagUnion::setYear(unsigned int i) { setUnion(Year, i); } -void TagUnion::setTrack(uint i) +void TagUnion::setTrack(unsigned int i) { setUnion(Track, i); } -void TagUnion::setRGAlbumGain(float f) -{ - setUnion(RGAlbumGain, f); -} - -void TagUnion::setRGAlbumPeak(float f) -{ - setUnion(RGAlbumPeak, f); -} - -void TagUnion::setRGTrackGain(float f) -{ - setUnion(RGTrackGain, f); -} - -void TagUnion::setRGTrackPeak(float f) -{ - setUnion(RGTrackPeak, f); -} - bool TagUnion::isEmpty() const { if(d->tags[0] && !d->tags[0]->isEmpty()) diff --git a/Frameworks/TagLib/taglib/taglib/tagunion.h b/Frameworks/TagLib/taglib/taglib/tagunion.h index 298e6955c..1eb38a01d 100644 --- a/Frameworks/TagLib/taglib/taglib/tagunion.h +++ b/Frameworks/TagLib/taglib/taglib/tagunion.h @@ -56,29 +56,24 @@ namespace TagLib { void set(int index, Tag *tag); + PropertyMap properties() const; + void removeUnsupportedProperties(const StringList &unsupported); + virtual String title() const; virtual String artist() const; virtual String album() const; virtual String comment() const; virtual String genre() const; - virtual uint year() const; - virtual uint track() const; - virtual float rgAlbumGain() const; - virtual float rgAlbumPeak() const; - virtual float rgTrackGain() const; - virtual float rgTrackPeak() const; + virtual unsigned int year() const; + virtual unsigned int track() const; virtual void setTitle(const String &s); virtual void setArtist(const String &s); virtual void setAlbum(const String &s); virtual void setComment(const String &s); virtual void setGenre(const String &s); - virtual void setYear(uint i); - virtual void setTrack(uint i); - virtual void setRGAlbumGain(float f); - virtual void setRGAlbumPeak(float f); - virtual void setRGTrackGain(float f); - virtual void setRGTrackPeak(float f); + virtual void setYear(unsigned int i); + virtual void setTrack(unsigned int i); virtual bool isEmpty() const; template <class T> T *access(int index, bool create) diff --git a/Frameworks/TagLib/taglib/taglib/tagutils.cpp b/Frameworks/TagLib/taglib/taglib/tagutils.cpp new file mode 100644 index 000000000..d6d924064 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/tagutils.cpp @@ -0,0 +1,105 @@ +/*************************************************************************** + copyright : (C) 2015 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tfile.h> + +#include "id3v1tag.h" +#include "id3v2header.h" +#include "apetag.h" + +#include "tagutils.h" + +using namespace TagLib; + +long Utils::findID3v1(File *file) +{ + if(!file->isValid()) + return -1; + + file->seek(-128, File::End); + const long p = file->tell(); + + if(file->readBlock(3) == ID3v1::Tag::fileIdentifier()) + return p; + + return -1; +} + +long Utils::findID3v2(File *file) +{ + if(!file->isValid()) + return -1; + + file->seek(0); + + if(file->readBlock(3) == ID3v2::Header::fileIdentifier()) + return 0; + + return -1; +} + +long Utils::findAPE(File *file, long id3v1Location) +{ + if(!file->isValid()) + return -1; + + if(id3v1Location >= 0) + file->seek(id3v1Location - 32, File::Beginning); + else + file->seek(-32, File::End); + + const long p = file->tell(); + + if(file->readBlock(8) == APE::Tag::fileIdentifier()) + return p; + + return -1; +} + +ByteVector TagLib::Utils::readHeader(IOStream *stream, unsigned int length, + bool skipID3v2, long *headerOffset) +{ + if(!stream || !stream->isOpen()) + return ByteVector(); + + const long originalPosition = stream->tell(); + long bufferOffset = 0; + + if(skipID3v2) { + stream->seek(0); + const ByteVector data = stream->readBlock(ID3v2::Header::size()); + if(data.startsWith(ID3v2::Header::fileIdentifier())) + bufferOffset = ID3v2::Header(data).completeTagSize(); + } + + stream->seek(bufferOffset); + const ByteVector header = stream->readBlock(length); + stream->seek(originalPosition); + + if(headerOffset) + *headerOffset = bufferOffset; + + return header; +} diff --git a/Frameworks/TagLib/taglib/taglib/tagutils.h b/Frameworks/TagLib/taglib/taglib/tagutils.h new file mode 100644 index 000000000..4488a32ba --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/tagutils.h @@ -0,0 +1,55 @@ +/*************************************************************************** + copyright : (C) 2015 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_TAGUTILS_H +#define TAGLIB_TAGUTILS_H + +// THIS FILE IS NOT A PART OF THE TAGLIB API + +#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header + +#include <tbytevector.h> + +namespace TagLib { + + class File; + class IOStream; + + namespace Utils { + + long findID3v1(File *file); + + long findID3v2(File *file); + + long findAPE(File *file, long id3v1Location); + + ByteVector readHeader(IOStream *stream, unsigned int length, bool skipID3v2, + long *headerOffset = 0); + } +} + +#endif + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/taglib.h b/Frameworks/TagLib/taglib/taglib/toolkit/taglib.h index 65a0c0b2d..ffce61f7c 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/taglib.h +++ b/Frameworks/TagLib/taglib/taglib/toolkit/taglib.h @@ -26,11 +26,13 @@ #ifndef TAGLIB_H #define TAGLIB_H +#include "taglib_config.h" + #define TAGLIB_MAJOR_VERSION 1 -#define TAGLIB_MINOR_VERSION 7 +#define TAGLIB_MINOR_VERSION 12 #define TAGLIB_PATCH_VERSION 0 -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1)) +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1)) || defined(__clang__) #define TAGLIB_IGNORE_MISSING_DESTRUCTOR _Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"") #else #define TAGLIB_IGNORE_MISSING_DESTRUCTOR @@ -42,13 +44,23 @@ #define TAGLIB_CONSTRUCT_BITSET(x) static_cast<unsigned long>(x) #endif +#if __cplusplus >= 201402 +#define TAGLIB_DEPRECATED [[deprecated]] +#elif defined(__GNUC__) || defined(__clang__) +#define TAGLIB_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define TAGLIB_DEPRECATED __declspec(deprecated) +#else +#define TAGLIB_DEPRECATED +#endif + #include <string> //! A namespace for all TagLib related classes and functions /*! * This namespace contains everything in TagLib. For projects working with - * TagLib extensively it may be conveniten to add a + * TagLib extensively it may be convenient to add a * \code * using namespace TagLib; * \endcode @@ -58,38 +70,20 @@ namespace TagLib { class String; - typedef wchar_t wchar; - typedef unsigned char uchar; - typedef unsigned int uint; - typedef unsigned long ulong; + // These integer types are deprecated. Do not use them. + + typedef wchar_t wchar; // Assumed to be sufficient to store a UTF-16 char. + typedef unsigned char uchar; + typedef unsigned short ushort; + typedef unsigned int uint; + typedef unsigned long ulong; + typedef unsigned long long ulonglong; /*! * Unfortunately std::wstring isn't defined on some systems, (i.e. GCC < 3) * so I'm providing something here that should be constant. */ - typedef std::basic_string<wchar> wstring; - -#ifndef DO_NOT_DOCUMENT // Tell Doxygen to skip this class. - /*! - * \internal - * This is just used as a base class for shared classes in TagLib. - * - * \warning This <b>is not</b> part of the TagLib public API! - */ - - class RefCounter - { - public: - RefCounter() : refCount(1) {} - void ref() { refCount++; } - bool deref() { return ! --refCount ; } - int count() { return refCount; } - private: - uint refCount; - }; - -#endif // DO_NOT_DOCUMENT - + typedef std::basic_string<wchar_t> wstring; } /*! @@ -103,14 +97,14 @@ namespace TagLib { * - A clean, high level, C++ API to handling audio meta data. * - Format specific APIs for advanced API users. * - ID3v1, ID3v2, APE, FLAC, Xiph, iTunes-style MP4 and WMA tag formats. - * - MP3, MPC, FLAC, MP4, ASF, AIFF, WAV, TrueAudio, WavPack, Ogg FLAC, Ogg Vorbis and Speex file formats. + * - MP3, MPC, FLAC, MP4, ASF, AIFF, WAV, TrueAudio, WavPack, Ogg FLAC, Ogg Vorbis, Speex and Opus file formats. * - Basic audio file properties such as length, sample rate, etc. * - Long term binary and source compatibility. * - Extensible design, notably the ability to add other formats or extend current formats as a library user. * - Full support for unicode and internationalized tags. * - Dual <a href="http://www.mozilla.org/MPL/MPL-1.1.html">MPL</a> and * <a href="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">LGPL</a> licenses. - * - No external toolkit dependancies. + * - No external toolkit dependencies. * * \section why Why TagLib? * @@ -131,7 +125,7 @@ namespace TagLib { * * \section installing Installing TagLib * - * Please see the <a href="http://developer.kde.org/~wheeler/taglib.html">TagLib website</a> for the latest + * Please see the <a href="http://taglib.org/">TagLib website</a> for the latest * downloads. * * TagLib can be built using the CMake build system. TagLib installs a taglib-config and pkg-config file to @@ -176,11 +170,10 @@ namespace TagLib { * * Questions about TagLib should be directed to the TagLib mailing list, not directly to the author. * - * - <a href="http://developer.kde.org/~wheeler/taglib/">TagLib Homepage</a> + * - <a href="http://taglib.org/">TagLib Homepage</a> * - <a href="https://mail.kde.org/mailman/listinfo/taglib-devel">TagLib Mailing List (taglib-devel@kde.org)</a> * - * \author Scott Wheeler <wheeler@kde.org> et al. - * + * \author <a href="https://github.com/taglib/taglib/blob/master/AUTHORS">TagLib authors</a>. */ #endif diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tbytevector.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tbytevector.cpp index 9fb77b123..d272057f6 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tbytevector.cpp +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tbytevector.cpp @@ -23,28 +23,658 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#include <ostream> +#include <algorithm> +#include <iostream> +#include <limits> +#include <cmath> +#include <cstdio> +#include <cstring> #include <tstring.h> #include <tdebug.h> - -#include <string.h> +#include <trefcounter.h> +#include <tutils.h> #include "tbytevector.h" // This is a bit ugly to keep writing over and over again. // A rather obscure feature of the C++ spec that I hadn't thought of that makes -// working with C libs much more effecient. There's more here: +// working with C libs much more efficient. There's more here: // // http://www.informit.com/isapi/product_id~{9C84DAB4-FE6E-49C5-BB0A-FB50331233EA}/content/index.asp -#define DATA(x) (&(x->data[0])) - namespace TagLib { - static const char hexTable[17] = "0123456789abcdef"; - static const uint crcTable[256] = { +template <class TIterator> +int findChar( + const TIterator dataBegin, const TIterator dataEnd, + char c, unsigned int offset, int byteAlign) +{ + const size_t dataSize = dataEnd - dataBegin; + if(offset + 1 > dataSize) + return -1; + + // n % 0 is invalid + + if(byteAlign == 0) + return -1; + + for(TIterator it = dataBegin + offset; it < dataEnd; it += byteAlign) { + if(*it == c) + return static_cast<int>(it - dataBegin); + } + + return -1; +} + +template <class TIterator> +int findVector( + const TIterator dataBegin, const TIterator dataEnd, + const TIterator patternBegin, const TIterator patternEnd, + unsigned int offset, int byteAlign) +{ + const size_t dataSize = dataEnd - dataBegin; + const size_t patternSize = patternEnd - patternBegin; + if(patternSize == 0 || offset + patternSize > dataSize) + return -1; + + // Special case that pattern contains just single char. + + if(patternSize == 1) + return findChar(dataBegin, dataEnd, *patternBegin, offset, byteAlign); + + // n % 0 is invalid + + if(byteAlign == 0) + return -1; + + // We don't use sophisticated algorithms like Knuth-Morris-Pratt here. + + // In the current implementation of TagLib, data and patterns are too small + // for such algorithms to work effectively. + + for(TIterator it = dataBegin + offset; it < dataEnd - patternSize + 1; it += byteAlign) { + + TIterator itData = it; + TIterator itPattern = patternBegin; + + while(*itData == *itPattern) { + ++itData; + ++itPattern; + + if(itPattern == patternEnd) + return static_cast<int>(it - dataBegin); + } + } + + return -1; +} + +template <class T> +T toNumber(const ByteVector &v, size_t offset, size_t length, bool mostSignificantByteFirst) +{ + if(offset >= v.size()) { + debug("toNumber<T>() -- No data to convert. Returning 0."); + return 0; + } + + length = std::min(length, v.size() - offset); + + T sum = 0; + for(size_t i = 0; i < length; i++) { + const size_t shift = (mostSignificantByteFirst ? length - 1 - i : i) * 8; + sum |= static_cast<T>(static_cast<unsigned char>(v[static_cast<int>(offset + i)])) << shift; + } + + return sum; +} + +template <class T> +T toNumber(const ByteVector &v, size_t offset, bool mostSignificantByteFirst) +{ + const bool isBigEndian = (Utils::systemByteOrder() == Utils::BigEndian); + const bool swap = (mostSignificantByteFirst != isBigEndian); + + if(offset + sizeof(T) > v.size()) + return toNumber<T>(v, offset, v.size() - offset, mostSignificantByteFirst); + + // Uses memcpy instead of reinterpret_cast to avoid an alignment exception. + T tmp; + ::memcpy(&tmp, v.data() + offset, sizeof(T)); + + if(swap) + return Utils::byteSwap(tmp); + else + return tmp; +} + +template <class T> +ByteVector fromNumber(T value, bool mostSignificantByteFirst) +{ + const bool isBigEndian = (Utils::systemByteOrder() == Utils::BigEndian); + const bool swap = (mostSignificantByteFirst != isBigEndian); + + if(swap) + value = Utils::byteSwap(value); + + return ByteVector(reinterpret_cast<const char *>(&value), sizeof(T)); +} + +template <typename TFloat, typename TInt, Utils::ByteOrder ENDIAN> +TFloat toFloat(const ByteVector &v, size_t offset) +{ + if(offset > v.size() - sizeof(TInt)) { + debug("toFloat() - offset is out of range. Returning 0."); + return 0.0; + } + + union { + TInt i; + TFloat f; + } tmp; + ::memcpy(&tmp, v.data() + offset, sizeof(TInt)); + + if(ENDIAN != Utils::systemByteOrder()) + tmp.i = Utils::byteSwap(tmp.i); + + return tmp.f; +} + +template <typename TFloat, typename TInt, Utils::ByteOrder ENDIAN> +ByteVector fromFloat(TFloat value) +{ + union { + TInt i; + TFloat f; + } tmp; + tmp.f = value; + + if(ENDIAN != Utils::systemByteOrder()) + tmp.i = Utils::byteSwap(tmp.i); + + return ByteVector(reinterpret_cast<char *>(&tmp), sizeof(TInt)); +} + +template <Utils::ByteOrder ENDIAN> +long double toFloat80(const ByteVector &v, size_t offset) +{ + using std::swap; + + if(offset > v.size() - 10) { + debug("toFloat80() - offset is out of range. Returning 0."); + return 0.0; + } + + unsigned char bytes[10]; + ::memcpy(bytes, v.data() + offset, 10); + + if(ENDIAN == Utils::LittleEndian) { + swap(bytes[0], bytes[9]); + swap(bytes[1], bytes[8]); + swap(bytes[2], bytes[7]); + swap(bytes[3], bytes[6]); + swap(bytes[4], bytes[5]); + } + + // 1-bit sign + const bool negative = ((bytes[0] & 0x80) != 0); + + // 15-bit exponent + const int exponent = ((bytes[0] & 0x7F) << 8) | bytes[1]; + + // 64-bit fraction. Leading 1 is explicit. + const unsigned long long fraction + = (static_cast<unsigned long long>(bytes[2]) << 56) + | (static_cast<unsigned long long>(bytes[3]) << 48) + | (static_cast<unsigned long long>(bytes[4]) << 40) + | (static_cast<unsigned long long>(bytes[5]) << 32) + | (static_cast<unsigned long long>(bytes[6]) << 24) + | (static_cast<unsigned long long>(bytes[7]) << 16) + | (static_cast<unsigned long long>(bytes[8]) << 8) + | (static_cast<unsigned long long>(bytes[9])); + + long double val; + if(exponent == 0 && fraction == 0) + val = 0; + else { + if(exponent == 0x7FFF) { + debug("toFloat80() - can't handle the infinity or NaN. Returning 0."); + return 0.0; + } + else + val = ::ldexp(static_cast<long double>(fraction), exponent - 16383 - 63); + } + + if(negative) + return -val; + else + return val; +} + +class ByteVector::ByteVectorPrivate +{ +public: + ByteVectorPrivate(unsigned int l, char c) : + counter(new RefCounter()), + data(new std::vector<char>(l, c)), + offset(0), + length(l) {} + + ByteVectorPrivate(const char *s, unsigned int l) : + counter(new RefCounter()), + data(new std::vector<char>(s, s + l)), + offset(0), + length(l) {} + + ByteVectorPrivate(const ByteVectorPrivate &d, unsigned int o, unsigned int l) : + counter(d.counter), + data(d.data), + offset(d.offset + o), + length(l) + { + counter->ref(); + } + + ~ByteVectorPrivate() + { + if(counter->deref()) { + delete counter; + delete data; + } + } + + RefCounter *counter; + std::vector<char> *data; + unsigned int offset; + unsigned int length; +}; + +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +ByteVector ByteVector::null; + +ByteVector ByteVector::fromCString(const char *s, unsigned int length) +{ + if(length == 0xffffffff) + return ByteVector(s, static_cast<unsigned int>(::strlen(s))); + else + return ByteVector(s, length); +} + +ByteVector ByteVector::fromUInt(unsigned int value, bool mostSignificantByteFirst) +{ + return fromNumber<unsigned int>(value, mostSignificantByteFirst); +} + +ByteVector ByteVector::fromShort(short value, bool mostSignificantByteFirst) +{ + return fromNumber<unsigned short>(value, mostSignificantByteFirst); +} + +ByteVector ByteVector::fromLongLong(long long value, bool mostSignificantByteFirst) +{ + return fromNumber<unsigned long long>(value, mostSignificantByteFirst); +} + +ByteVector ByteVector::fromFloat32LE(float value) +{ + return fromFloat<float, unsigned int, Utils::LittleEndian>(value); +} + +ByteVector ByteVector::fromFloat32BE(float value) +{ + return fromFloat<float, unsigned int, Utils::BigEndian>(value); +} + +ByteVector ByteVector::fromFloat64LE(double value) +{ + return fromFloat<double, unsigned long long, Utils::LittleEndian>(value); +} + +ByteVector ByteVector::fromFloat64BE(double value) +{ + return fromFloat<double, unsigned long long, Utils::BigEndian>(value); +} + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +ByteVector::ByteVector() : + d(new ByteVectorPrivate(0, '\0')) +{ +} + +ByteVector::ByteVector(unsigned int size, char value) : + d(new ByteVectorPrivate(size, value)) +{ +} + +ByteVector::ByteVector(const ByteVector &v) : + d(new ByteVectorPrivate(*v.d, 0, v.d->length)) +{ +} + +ByteVector::ByteVector(const ByteVector &v, unsigned int offset, unsigned int length) : + d(new ByteVectorPrivate(*v.d, offset, length)) +{ +} + +ByteVector::ByteVector(char c) : + d(new ByteVectorPrivate(1, c)) +{ +} + +ByteVector::ByteVector(const char *data, unsigned int length) : + d(new ByteVectorPrivate(data, length)) +{ +} + +ByteVector::ByteVector(const char *data) : + d(new ByteVectorPrivate(data, static_cast<unsigned int>(::strlen(data)))) +{ +} + +ByteVector::~ByteVector() +{ + delete d; +} + +ByteVector &ByteVector::setData(const char *s, unsigned int length) +{ + ByteVector(s, length).swap(*this); + return *this; +} + +ByteVector &ByteVector::setData(const char *data) +{ + ByteVector(data).swap(*this); + return *this; +} + +char *ByteVector::data() +{ + detach(); + return (size() > 0) ? (&(*d->data)[d->offset]) : 0; +} + +const char *ByteVector::data() const +{ + return (size() > 0) ? (&(*d->data)[d->offset]) : 0; +} + +ByteVector ByteVector::mid(unsigned int index, unsigned int length) const +{ + index = std::min(index, size()); + length = std::min(length, size() - index); + + return ByteVector(*this, index, length); +} + +char ByteVector::at(unsigned int index) const +{ + return (index < size()) ? (*d->data)[d->offset + index] : 0; +} + +int ByteVector::find(const ByteVector &pattern, unsigned int offset, int byteAlign) const +{ + return findVector<ConstIterator>( + begin(), end(), pattern.begin(), pattern.end(), offset, byteAlign); +} + +int ByteVector::find(char c, unsigned int offset, int byteAlign) const +{ + return findChar<ConstIterator>(begin(), end(), c, offset, byteAlign); +} + +int ByteVector::rfind(const ByteVector &pattern, unsigned int offset, int byteAlign) const +{ + if(offset > 0) { + offset = size() - offset - pattern.size(); + if(offset >= size()) + offset = 0; + } + + const int pos = findVector<ConstReverseIterator>( + rbegin(), rend(), pattern.rbegin(), pattern.rend(), offset, byteAlign); + + if(pos == -1) + return -1; + else + return size() - pos - pattern.size(); +} + +bool ByteVector::containsAt(const ByteVector &pattern, unsigned int offset, unsigned int patternOffset, unsigned int patternLength) const +{ + if(pattern.size() < patternLength) + patternLength = pattern.size(); + + // do some sanity checking -- all of these things are needed for the search to be valid + const unsigned int compareLength = patternLength - patternOffset; + if(offset + compareLength > size() || patternOffset >= pattern.size() || patternLength == 0) + return false; + + return (::memcmp(data() + offset, pattern.data() + patternOffset, compareLength) == 0); +} + +bool ByteVector::startsWith(const ByteVector &pattern) const +{ + return containsAt(pattern, 0); +} + +bool ByteVector::endsWith(const ByteVector &pattern) const +{ + return containsAt(pattern, size() - pattern.size()); +} + +ByteVector &ByteVector::replace(char oldByte, char newByte) +{ + detach(); + + for(ByteVector::Iterator it = begin(); it != end(); ++it) { + if(*it == oldByte) + *it = newByte; + } + + return *this; +} + +ByteVector &ByteVector::replace(const ByteVector &pattern, const ByteVector &with) +{ + if(pattern.size() == 1 && with.size() == 1) + return replace(pattern[0], with[0]); + + // Check if there is at least one occurrence of the pattern. + + int offset = find(pattern, 0); + if(offset == -1) + return *this; + + if(pattern.size() == with.size()) { + + // We think this case might be common enough to optimize it. + + detach(); + do + { + ::memcpy(data() + offset, with.data(), with.size()); + offset = find(pattern, offset + pattern.size()); + } while(offset != -1); + } + else { + + // Loop once to calculate the result size. + + unsigned int dstSize = size(); + do + { + dstSize += with.size() - pattern.size(); + offset = find(pattern, offset + pattern.size()); + } while(offset != -1); + + // Loop again to copy modified data to the new vector. + + ByteVector dst(dstSize); + int dstOffset = 0; + + offset = 0; + while(true) { + const int next = find(pattern, offset); + if(next == -1) { + ::memcpy(dst.data() + dstOffset, data() + offset, size() - offset); + break; + } + + ::memcpy(dst.data() + dstOffset, data() + offset, next - offset); + dstOffset += next - offset; + + ::memcpy(dst.data() + dstOffset, with.data(), with.size()); + dstOffset += with.size(); + + offset = next + pattern.size(); + } + + swap(dst); + } + + return *this; +} + +int ByteVector::endsWithPartialMatch(const ByteVector &pattern) const +{ + if(pattern.size() > size()) + return -1; + + const int startIndex = size() - pattern.size(); + + // try to match the last n-1 bytes from the vector (where n is the pattern + // size) -- continue trying to match n-2, n-3...1 bytes + + for(unsigned int i = 1; i < pattern.size(); i++) { + if(containsAt(pattern, startIndex + i, 0, pattern.size() - i)) + return startIndex + i; + } + + return -1; +} + +ByteVector &ByteVector::append(const ByteVector &v) +{ + if(v.isEmpty()) + return *this; + + detach(); + + const unsigned int originalSize = size(); + const unsigned int appendSize = v.size(); + + resize(originalSize + appendSize); + ::memcpy(data() + originalSize, v.data(), appendSize); + + return *this; +} + +ByteVector &ByteVector::append(char c) +{ + resize(size() + 1, c); + return *this; +} + +ByteVector &ByteVector::clear() +{ + ByteVector().swap(*this); + return *this; +} + +unsigned int ByteVector::size() const +{ + return d->length; +} + +ByteVector &ByteVector::resize(unsigned int size, char padding) +{ + if(size != d->length) { + detach(); + + // Remove the excessive length of the internal buffer first to pad correctly. + // This doesn't reallocate the buffer, since std::vector::resize() doesn't + // reallocate the buffer when shrinking. + + d->data->resize(d->offset + d->length); + d->data->resize(d->offset + size, padding); + + d->length = size; + } + + return *this; +} + +ByteVector::Iterator ByteVector::begin() +{ + detach(); + return d->data->begin() + d->offset; +} + +ByteVector::ConstIterator ByteVector::begin() const +{ + return d->data->begin() + d->offset; +} + +ByteVector::Iterator ByteVector::end() +{ + detach(); + return d->data->begin() + d->offset + d->length; +} + +ByteVector::ConstIterator ByteVector::end() const +{ + return d->data->begin() + d->offset + d->length; +} + +ByteVector::ReverseIterator ByteVector::rbegin() +{ + detach(); + return d->data->rbegin() + (d->data->size() - (d->offset + d->length)); +} + +ByteVector::ConstReverseIterator ByteVector::rbegin() const +{ + // Workaround for the Solaris Studio 12.4 compiler. + // We need a const reference to the data vector so we can ensure the const version of rbegin() is called. + const std::vector<char> &v = *d->data; + return v.rbegin() + (v.size() - (d->offset + d->length)); +} + +ByteVector::ReverseIterator ByteVector::rend() +{ + detach(); + return d->data->rbegin() + (d->data->size() - d->offset); +} + +ByteVector::ConstReverseIterator ByteVector::rend() const +{ + // Workaround for the Solaris Studio 12.4 compiler. + // We need a const reference to the data vector so we can ensure the const version of rbegin() is called. + const std::vector<char> &v = *d->data; + return v.rbegin() + (v.size() - d->offset); +} + +bool ByteVector::isNull() const +{ + return (d == null.d); +} + +bool ByteVector::isEmpty() const +{ + return (d->length == 0); +} + +unsigned int ByteVector::checksum() const +{ + static const unsigned int crcTable[256] = { 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, @@ -90,533 +720,127 @@ namespace TagLib { 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 }; - /*! - * A templatized KMP find that works both with a ByteVector and a ByteVectorMirror. - */ - - template <class Vector> - int vectorFind(const Vector &v, const Vector &pattern, uint offset, int byteAlign) - { - if(pattern.size() > v.size() || offset > v.size() - 1) - return -1; - - // Let's go ahead and special case a pattern of size one since that's common - // and easy to make fast. - - if(pattern.size() == 1) { - char p = pattern[0]; - for(uint i = offset; i < v.size(); i++) { - if(v[i] == p && (i - offset) % byteAlign == 0) - return i; - } - return -1; - } - - uchar lastOccurrence[256]; - - for(uint i = 0; i < 256; ++i) - lastOccurrence[i] = uchar(pattern.size()); - - for(uint i = 0; i < pattern.size() - 1; ++i) - lastOccurrence[uchar(pattern[i])] = uchar(pattern.size() - i - 1); - - for(uint i = pattern.size() - 1 + offset; i < v.size(); i += lastOccurrence[uchar(v.at(i))]) { - int iBuffer = i; - int iPattern = pattern.size() - 1; - - while(iPattern >= 0 && v.at(iBuffer) == pattern[iPattern]) { - --iBuffer; - --iPattern; - } - - if(-1 == iPattern && (iBuffer + 1 - offset) % byteAlign == 0) - return iBuffer + 1; - } - - return -1; - } - - /*! - * Wraps the accessors to a ByteVector to make the search algorithm access the - * elements in reverse. - * - * \see vectorFind() - * \see ByteVector::rfind() - */ - - class ByteVectorMirror - { - public: - ByteVectorMirror(const ByteVector &source) : v(source) {} - - char operator[](int index) const - { - return v[v.size() - index - 1]; - } - - char at(int index) const - { - return v.at(v.size() - index - 1); - } - - ByteVectorMirror mid(uint index, uint length = 0xffffffff) const - { - return length == 0xffffffff ? v.mid(0, index) : v.mid(index - length, length); - } - - uint size() const - { - return v.size(); - } - - int find(const ByteVectorMirror &pattern, uint offset = 0, int byteAlign = 1) const - { - ByteVectorMirror v(*this); - - if(offset > 0) { - offset = size() - offset - pattern.size(); - if(offset >= size()) - offset = 0; - } - - const int pos = vectorFind<ByteVectorMirror>(v, pattern, offset, byteAlign); - - // If the offset is zero then we need to adjust the location in the search - // to be appropriately reversed. If not we need to account for the fact - // that the recursive call (called from the above line) has already ajusted - // for this but that the normal templatized find above will add the offset - // to the returned value. - // - // This is a little confusing at first if you don't first stop to think - // through the logic involved in the forward search. - - if(pos == -1) - return -1; - - return size() - pos - pattern.size(); - } - - private: - const ByteVector &v; - }; - - template <class T> - T toNumber(const std::vector<char> &data, bool mostSignificantByteFirst) - { - T sum = 0; - - if(data.size() <= 0) { - debug("ByteVectorMirror::toNumber<T>() -- data is empty, returning 0"); - return sum; - } - - uint size = sizeof(T); - uint last = data.size() > size ? size - 1 : data.size() - 1; - - for(uint i = 0; i <= last; i++) - sum |= (T) uchar(data[i]) << ((mostSignificantByteFirst ? last - i : i) * 8); - - return sum; - } - - template <class T> - ByteVector fromNumber(T value, bool mostSignificantByteFirst) - { - int size = sizeof(T); - - ByteVector v(size, 0); - - for(int i = 0; i < size; i++) - v[i] = uchar(value >> ((mostSignificantByteFirst ? size - 1 - i : i) * 8) & 0xff); - - return v; - } -} - -using namespace TagLib; - -class ByteVector::ByteVectorPrivate : public RefCounter -{ -public: - ByteVectorPrivate() : RefCounter(), size(0) {} - ByteVectorPrivate(const std::vector<char> &v) : RefCounter(), data(v), size(v.size()) {} - ByteVectorPrivate(TagLib::uint len, char value) : RefCounter(), data(len, value), size(len) {} - - std::vector<char> data; - - // std::vector<T>::size() is very slow, so we'll cache the value - - uint size; -}; - -//////////////////////////////////////////////////////////////////////////////// -// static members -//////////////////////////////////////////////////////////////////////////////// - -ByteVector ByteVector::null; - -ByteVector ByteVector::fromCString(const char *s, uint length) -{ - ByteVector v; - - if(length == 0xffffffff) - v.setData(s); - else - v.setData(s, length); - - return v; -} - -ByteVector ByteVector::fromUInt(uint value, bool mostSignificantByteFirst) -{ - return fromNumber<uint>(value, mostSignificantByteFirst); -} - -ByteVector ByteVector::fromShort(short value, bool mostSignificantByteFirst) -{ - return fromNumber<short>(value, mostSignificantByteFirst); -} - -ByteVector ByteVector::fromLongLong(long long value, bool mostSignificantByteFirst) -{ - return fromNumber<long long>(value, mostSignificantByteFirst); -} - -//////////////////////////////////////////////////////////////////////////////// -// public members -//////////////////////////////////////////////////////////////////////////////// - -ByteVector::ByteVector() -{ - d = new ByteVectorPrivate; -} - -ByteVector::ByteVector(uint size, char value) -{ - d = new ByteVectorPrivate(size, value); -} - -ByteVector::ByteVector(const ByteVector &v) : d(v.d) -{ - d->ref(); -} - -ByteVector::ByteVector(char c) -{ - d = new ByteVectorPrivate; - d->data.push_back(c); - d->size = 1; -} - -ByteVector::ByteVector(const char *data, uint length) -{ - d = new ByteVectorPrivate; - setData(data, length); -} - -ByteVector::ByteVector(const char *data) -{ - d = new ByteVectorPrivate; - setData(data); -} - -ByteVector::~ByteVector() -{ - if(d->deref()) - delete d; -} - -ByteVector &ByteVector::setData(const char *data, uint length) -{ - detach(); - - resize(length); - - if(length > 0) - ::memcpy(DATA(d), data, length); - - return *this; -} - -ByteVector &ByteVector::setData(const char *data) -{ - return setData(data, ::strlen(data)); -} - -char *ByteVector::data() -{ - detach(); - return size() > 0 ? DATA(d) : 0; -} - -const char *ByteVector::data() const -{ - return size() > 0 ? DATA(d) : 0; -} - -ByteVector ByteVector::mid(uint index, uint length) const -{ - ByteVector v; - - if(index > size()) - return v; - - ConstIterator endIt; - - if(length < 0xffffffff && length + index < size()) - endIt = d->data.begin() + index + length; - else - endIt = d->data.end(); - - v.d->data.insert(v.d->data.begin(), ConstIterator(d->data.begin() + index), endIt); - v.d->size = v.d->data.size(); - - return v; -} - -char ByteVector::at(uint index) const -{ - return index < size() ? d->data[index] : 0; -} - -int ByteVector::find(const ByteVector &pattern, uint offset, int byteAlign) const -{ - return vectorFind<ByteVector>(*this, pattern, offset, byteAlign); -} - -int ByteVector::rfind(const ByteVector &pattern, uint offset, int byteAlign) const -{ - // Ok, this is a little goofy, but pretty cool after it sinks in. Instead of - // reversing the find method's Boyer-Moore search algorithm I created a "mirror" - // for a ByteVector to reverse the behavior of the accessors. - - ByteVectorMirror v(*this); - ByteVectorMirror p(pattern); - - return v.find(p, offset, byteAlign); -} - -bool ByteVector::containsAt(const ByteVector &pattern, uint offset, uint patternOffset, uint patternLength) const -{ - if(pattern.size() < patternLength) - patternLength = pattern.size(); - - // do some sanity checking -- all of these things are needed for the search to be valid - - if(patternLength > size() || offset >= size() || patternOffset >= pattern.size() || patternLength == 0) - return false; - - // loop through looking for a mismatch - - for(uint i = 0; i < patternLength - patternOffset; i++) { - if(at(i + offset) != pattern[i + patternOffset]) - return false; - } - - return true; -} - -bool ByteVector::startsWith(const ByteVector &pattern) const -{ - return containsAt(pattern, 0); -} - -bool ByteVector::endsWith(const ByteVector &pattern) const -{ - return containsAt(pattern, size() - pattern.size()); -} - -ByteVector &ByteVector::replace(const ByteVector &pattern, const ByteVector &with) -{ - if(pattern.size() == 0 || pattern.size() > size()) - return *this; - - const int patternSize = pattern.size(); - const int withSize = with.size(); - - int offset = find(pattern); - - while(offset >= 0) { - - const int originalSize = size(); - - if(withSize > patternSize) - resize(originalSize + withSize - patternSize); - - if(patternSize != withSize) - ::memcpy(data() + offset + withSize, mid(offset + patternSize).data(), originalSize - offset - patternSize); - - if(withSize < patternSize) - resize(originalSize + withSize - patternSize); - - ::memcpy(data() + offset, with.data(), withSize); - - offset = find(pattern, offset + withSize); - } - - return *this; -} - -int ByteVector::endsWithPartialMatch(const ByteVector &pattern) const -{ - if(pattern.size() > size()) - return -1; - - const int startIndex = size() - pattern.size(); - - // try to match the last n-1 bytes from the vector (where n is the pattern - // size) -- continue trying to match n-2, n-3...1 bytes - - for(uint i = 1; i < pattern.size(); i++) { - if(containsAt(pattern, startIndex + i, 0, pattern.size() - i)) - return startIndex + i; - } - - return -1; -} - -ByteVector &ByteVector::append(const ByteVector &v) -{ - if(v.d->size == 0) - return *this; // Simply return if appending nothing. - - detach(); - - uint originalSize = d->size; - resize(d->size + v.d->size); - ::memcpy(DATA(d) + originalSize, DATA(v.d), v.size()); - - return *this; -} - -ByteVector &ByteVector::clear() -{ - detach(); - d->data.clear(); - d->size = 0; - - return *this; -} - -TagLib::uint ByteVector::size() const -{ - return d->size; -} - -ByteVector &ByteVector::resize(uint size, char padding) -{ - if(d->size < size) { - d->data.reserve(size); - d->data.insert(d->data.end(), size - d->size, padding); - } - else - d->data.erase(d->data.begin() + size, d->data.end()); - - d->size = size; - - return *this; -} - -ByteVector::Iterator ByteVector::begin() -{ - return d->data.begin(); -} - -ByteVector::ConstIterator ByteVector::begin() const -{ - return d->data.begin(); -} - -ByteVector::Iterator ByteVector::end() -{ - return d->data.end(); -} - -ByteVector::ConstIterator ByteVector::end() const -{ - return d->data.end(); -} - -bool ByteVector::isNull() const -{ - return d == null.d; -} - -bool ByteVector::isEmpty() const -{ - return d->data.size() == 0; -} - -TagLib::uint ByteVector::checksum() const -{ - uint sum = 0; + unsigned int sum = 0; for(ByteVector::ConstIterator it = begin(); it != end(); ++it) - sum = (sum << 8) ^ crcTable[((sum >> 24) & 0xff) ^ uchar(*it)]; + sum = (sum << 8) ^ crcTable[((sum >> 24) & 0xff) ^ static_cast<unsigned char>(*it)]; return sum; } -TagLib::uint ByteVector::toUInt(bool mostSignificantByteFirst) const +unsigned int ByteVector::toUInt(bool mostSignificantByteFirst) const { - return toNumber<uint>(d->data, mostSignificantByteFirst); + return toNumber<unsigned int>(*this, 0, mostSignificantByteFirst); +} + +unsigned int ByteVector::toUInt(unsigned int offset, bool mostSignificantByteFirst) const +{ + return toNumber<unsigned int>(*this, offset, mostSignificantByteFirst); +} + +unsigned int ByteVector::toUInt(unsigned int offset, unsigned int length, bool mostSignificantByteFirst) const +{ + return toNumber<unsigned int>(*this, offset, length, mostSignificantByteFirst); } short ByteVector::toShort(bool mostSignificantByteFirst) const { - return toNumber<unsigned short>(d->data, mostSignificantByteFirst); + return toNumber<unsigned short>(*this, 0, mostSignificantByteFirst); +} + +short ByteVector::toShort(unsigned int offset, bool mostSignificantByteFirst) const +{ + return toNumber<unsigned short>(*this, offset, mostSignificantByteFirst); } unsigned short ByteVector::toUShort(bool mostSignificantByteFirst) const { - return toNumber<unsigned short>(d->data, mostSignificantByteFirst); + return toNumber<unsigned short>(*this, 0, mostSignificantByteFirst); +} + +unsigned short ByteVector::toUShort(unsigned int offset, bool mostSignificantByteFirst) const +{ + return toNumber<unsigned short>(*this, offset, mostSignificantByteFirst); } long long ByteVector::toLongLong(bool mostSignificantByteFirst) const { - return toNumber<unsigned long long>(d->data, mostSignificantByteFirst); + return toNumber<unsigned long long>(*this, 0, mostSignificantByteFirst); +} + +long long ByteVector::toLongLong(unsigned int offset, bool mostSignificantByteFirst) const +{ + return toNumber<unsigned long long>(*this, offset, mostSignificantByteFirst); +} + +float ByteVector::toFloat32LE(size_t offset) const +{ + return toFloat<float, unsigned int, Utils::LittleEndian>(*this, offset); +} + +float ByteVector::toFloat32BE(size_t offset) const +{ + return toFloat<float, unsigned int, Utils::BigEndian>(*this, offset); +} + +double ByteVector::toFloat64LE(size_t offset) const +{ + return toFloat<double, unsigned long long, Utils::LittleEndian>(*this, offset); +} + +double ByteVector::toFloat64BE(size_t offset) const +{ + return toFloat<double, unsigned long long, Utils::BigEndian>(*this, offset); +} + +long double ByteVector::toFloat80LE(size_t offset) const +{ + return toFloat80<Utils::LittleEndian>(*this, offset); +} + +long double ByteVector::toFloat80BE(size_t offset) const +{ + return toFloat80<Utils::BigEndian>(*this, offset); } const char &ByteVector::operator[](int index) const { - return d->data[index]; + return (*d->data)[d->offset + index]; } char &ByteVector::operator[](int index) { detach(); - - return d->data[index]; + return (*d->data)[d->offset + index]; } bool ByteVector::operator==(const ByteVector &v) const { - if(d->size != v.d->size) + if(size() != v.size()) return false; - return ::memcmp(data(), v.data(), size()) == 0; + return (::memcmp(data(), v.data(), size()) == 0); } bool ByteVector::operator!=(const ByteVector &v) const { - return !operator==(v); + return !(*this == v); } bool ByteVector::operator==(const char *s) const { - if(d->size != ::strlen(s)) + if(size() != ::strlen(s)) return false; - return ::memcmp(data(), s, d->size) == 0; + return (::memcmp(data(), s, size()) == 0); } bool ByteVector::operator!=(const char *s) const { - return !operator==(s); + return !(*this == s); } bool ByteVector::operator<(const ByteVector &v) const { - int result = ::memcmp(data(), v.data(), d->size < v.d->size ? d->size : v.d->size); - + const int result = ::memcmp(data(), v.data(), std::min(size(), v.size())); if(result != 0) return result < 0; else @@ -625,7 +849,7 @@ bool ByteVector::operator<(const ByteVector &v) const bool ByteVector::operator>(const ByteVector &v) const { - return v < *this; + return (v < *this); } ByteVector ByteVector::operator+(const ByteVector &v) const @@ -637,62 +861,184 @@ ByteVector ByteVector::operator+(const ByteVector &v) const ByteVector &ByteVector::operator=(const ByteVector &v) { - if(&v == this) - return *this; - - if(d->deref()) - delete d; - - d = v.d; - d->ref(); + ByteVector(v).swap(*this); return *this; } ByteVector &ByteVector::operator=(char c) { - *this = ByteVector(c); + ByteVector(c).swap(*this); return *this; } ByteVector &ByteVector::operator=(const char *data) { - *this = ByteVector(data); + ByteVector(data).swap(*this); return *this; } +void ByteVector::swap(ByteVector &v) +{ + using std::swap; + + swap(d, v.d); +} + ByteVector ByteVector::toHex() const { - ByteVector encoded(size() * 2); + static const char hexTable[17] = "0123456789abcdef"; - uint j = 0; - for(uint i = 0; i < size(); i++) { - unsigned char c = d->data[i]; - encoded[j++] = hexTable[(c >> 4) & 0x0F]; - encoded[j++] = hexTable[(c ) & 0x0F]; + ByteVector encoded(size() * 2); + char *p = encoded.data(); + + for(unsigned int i = 0; i < size(); i++) { + unsigned char c = data()[i]; + *p++ = hexTable[(c >> 4) & 0x0F]; + *p++ = hexTable[(c ) & 0x0F]; } return encoded; } +ByteVector ByteVector::fromBase64(const ByteVector & input) +{ + static const unsigned char base64[256] = { + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x3e,0x80,0x80,0x80,0x3f, + 0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x80,0x80,0x80,0x80,0x80,0x80, + 0x80,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e, + 0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x80,0x80,0x80,0x80,0x80, + 0x80,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0x80,0x80,0x80,0x80,0x80, + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80 + }; + + unsigned int len = input.size(); + + ByteVector output(len); + + const unsigned char * src = (const unsigned char*) input.data(); + unsigned char * dst = (unsigned char*) output.data(); + + while(4 <= len) { + + // Check invalid character + if(base64[src[0]] == 0x80) + break; + + // Check invalid character + if(base64[src[1]] == 0x80) + break; + + // Decode first byte + *dst++ = ((base64[src[0]] << 2) & 0xfc) | ((base64[src[1]] >> 4) & 0x03); + + if(src[2] != '=') { + + // Check invalid character + if(base64[src[2]] == 0x80) + break; + + // Decode second byte + *dst++ = ((base64[src[1]] & 0x0f) << 4) | ((base64[src[2]] >> 2) & 0x0f); + + if(src[3] != '=') { + + // Check invalid character + if(base64[src[3]] == 0x80) + break; + + // Decode third byte + *dst++ = ((base64[src[2]] & 0x03) << 6) | (base64[src[3]] & 0x3f); + } + else { + // assume end of data + len -= 4; + break; + } + } + else { + // assume end of data + len -= 4; + break; + } + src += 4; + len -= 4; + } + + // Only return output if we processed all bytes + if(len == 0) { + output.resize(static_cast<unsigned int>(dst - (unsigned char*) output.data())); + return output; + } + return ByteVector(); +} + +ByteVector ByteVector::toBase64() const +{ + static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if(!isEmpty()) { + unsigned int len = size(); + ByteVector output(4 * ((len - 1) / 3 + 1)); // note roundup + + const char * src = data(); + char * dst = output.data(); + while(3 <= len) { + *dst++ = alphabet[(src[0] >> 2) & 0x3f]; + *dst++ = alphabet[((src[0] & 0x03) << 4) | ((src[1] >> 4) & 0x0f)]; + *dst++ = alphabet[((src[1] & 0x0f) << 2) | ((src[2] >> 6) & 0x03)]; + *dst++ = alphabet[src[2] & 0x3f]; + src += 3; + len -= 3; + } + if(len) { + *dst++ = alphabet[(src[0] >> 2) & 0x3f]; + if(len>1) { + *dst++ = alphabet[((src[0] & 0x03) << 4) | ((src[1] >> 4) & 0x0f)]; + *dst++ = alphabet[((src[1] & 0x0f) << 2)]; + } + else { + *dst++ = alphabet[(src[0] & 0x03) << 4]; + *dst++ = '='; + } + *dst++ = '='; + } + return output; + } + return ByteVector(); +} + + //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// void ByteVector::detach() { - if(d->count() > 1) { - d->deref(); - d = new ByteVectorPrivate(d->data); + if(d->counter->count() > 1) { + if(!isEmpty()) + ByteVector(&d->data->front() + d->offset, d->length).swap(*this); + else + ByteVector().swap(*this); } } +} //////////////////////////////////////////////////////////////////////////////// // related functions //////////////////////////////////////////////////////////////////////////////// -std::ostream &operator<<(std::ostream &s, const ByteVector &v) +std::ostream &operator<<(std::ostream &s, const TagLib::ByteVector &v) { - for(TagLib::uint i = 0; i < v.size(); i++) + for(unsigned int i = 0; i < v.size(); i++) s << v[i]; return s; } diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tbytevector.h b/Frameworks/TagLib/taglib/taglib/toolkit/tbytevector.h index b7fffdded..41373c720 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tbytevector.h +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tbytevector.h @@ -30,7 +30,7 @@ #include "taglib_export.h" #include <vector> -#include <ostream> +#include <iostream> namespace TagLib { @@ -39,7 +39,7 @@ namespace TagLib { /*! * This class provides a byte vector with some methods that are useful for * tagging purposes. Many of the search functions are tailored to what is - * useful for finding tag related paterns in a data array. + * useful for finding tag related patterns in a data array. */ class TAGLIB_EXPORT ByteVector @@ -48,6 +48,8 @@ namespace TagLib { #ifndef DO_NOT_DOCUMENT typedef std::vector<char>::iterator Iterator; typedef std::vector<char>::const_iterator ConstIterator; + typedef std::vector<char>::reverse_iterator ReverseIterator; + typedef std::vector<char>::const_reverse_iterator ConstReverseIterator; #endif /*! @@ -59,28 +61,34 @@ namespace TagLib { * Construct a vector of size \a size with all values set to \a value by * default. */ - ByteVector(uint size, char value = 0); + ByteVector(unsigned int size, char value = 0); /*! - * Contructs a byte vector that is a copy of \a v. + * Constructs a byte vector that is a copy of \a v. */ ByteVector(const ByteVector &v); /*! - * Contructs a byte vector that contains \a c. + * Constructs a byte vector that is a copy of \a v. + */ + ByteVector(const ByteVector &v, unsigned int offset, unsigned int length); + + /*! + * Constructs a byte vector that contains \a c. */ ByteVector(char c); /*! * Constructs a byte vector that copies \a data for up to \a length bytes. */ - ByteVector(const char *data, uint length); + ByteVector(const char *data, unsigned int length); /*! * Constructs a byte vector that copies \a data up to the first null - * byte. The behavior is undefined if \a data is not null terminated. - * This is particularly useful for constructing byte arrays from string - * constants. + * byte. This is particularly useful for constructing byte arrays from + * string constants. + * + * \warning The behavior is undefined if \a data is not null terminated. */ ByteVector(const char *data); @@ -92,7 +100,7 @@ namespace TagLib { /*! * Sets the data for the byte array using the first \a length bytes of \a data */ - ByteVector &setData(const char *data, uint length); + ByteVector &setData(const char *data, unsigned int length); /*! * Sets the data for the byte array copies \a data up to the first null @@ -119,13 +127,13 @@ namespace TagLib { * for \a length bytes. If \a length is not specified it will return the bytes * from \a index to the end of the vector. */ - ByteVector mid(uint index, uint length = 0xffffffff) const; + ByteVector mid(unsigned int index, unsigned int length = 0xffffffff) const; /*! * This essentially performs the same as operator[](), but instead of causing * a runtime error if the index is out of bounds, it will return a null byte. */ - char at(uint index) const; + char at(unsigned int index) const; /*! * Searches the ByteVector for \a pattern starting at \a offset and returns @@ -133,7 +141,15 @@ namespace TagLib { * specified the pattern will only be matched if it starts on a byte divisible * by \a byteAlign (starting from \a offset). */ - int find(const ByteVector &pattern, uint offset = 0, int byteAlign = 1) const; + int find(const ByteVector &pattern, unsigned int offset = 0, int byteAlign = 1) const; + + /*! + * Searches the char for \a c starting at \a offset and returns + * the offset. Returns \a -1 if the pattern was not found. If \a byteAlign is + * specified the pattern will only be matched if it starts on a byte divisible + * by \a byteAlign (starting from \a offset). + */ + int find(char c, unsigned int offset = 0, int byteAlign = 1) const; /*! * Searches the ByteVector for \a pattern starting from either the end of the @@ -141,7 +157,7 @@ namespace TagLib { * not found. If \a byteAlign is specified the pattern will only be matched * if it starts on a byte divisible by \a byteAlign (starting from \a offset). */ - int rfind(const ByteVector &pattern, uint offset = 0, int byteAlign = 1) const; + int rfind(const ByteVector &pattern, unsigned int offset = 0, int byteAlign = 1) const; /*! * Checks to see if the vector contains the \a pattern starting at position @@ -150,7 +166,8 @@ namespace TagLib { * specify to only check for the first \a patternLength bytes of \a pattern with * the \a patternLength argument. */ - bool containsAt(const ByteVector &pattern, uint offset, uint patternOffset = 0, uint patternLength = 0xffffffff) const; + bool containsAt(const ByteVector &pattern, unsigned int offset, + unsigned int patternOffset = 0, unsigned int patternLength = 0xffffffff) const; /*! * Returns true if the vector starts with \a pattern. @@ -162,6 +179,12 @@ namespace TagLib { */ bool endsWith(const ByteVector &pattern) const; + /*! + * Replaces \a oldByte with \a newByte and returns a reference to the + * ByteVector after the operation. This \e does modify the vector. + */ + ByteVector &replace(char oldByte, char newByte); + /*! * Replaces \a pattern with \a with and returns a reference to the ByteVector * after the operation. This \e does modify the vector. @@ -185,6 +208,11 @@ namespace TagLib { */ ByteVector &append(const ByteVector &v); + /*! + * Appends \a c to the end of the ByteVector. + */ + ByteVector &append(char c); + /*! * Clears the data. */ @@ -193,14 +221,14 @@ namespace TagLib { /*! * Returns the size of the array. */ - uint size() const; + unsigned int size() const; /*! * Resize the vector to \a size. If the vector is currently less than * \a size, pad the remaining spaces with \a padding. Returns a reference * to the resized vector. */ - ByteVector &resize(uint size, char padding = 0); + ByteVector &resize(unsigned int size, char padding = 0); /*! * Returns an Iterator that points to the front of the vector. @@ -222,13 +250,38 @@ namespace TagLib { */ ConstIterator end() const; + /*! + * Returns a ReverseIterator that points to the front of the vector. + */ + ReverseIterator rbegin(); + + /*! + * Returns a ConstReverseIterator that points to the front of the vector. + */ + ConstReverseIterator rbegin() const; + + /*! + * Returns a ReverseIterator that points to the back of the vector. + */ + ReverseIterator rend(); + + /*! + * Returns a ConstReverseIterator that points to the back of the vector. + */ + ConstReverseIterator rend() const; + /*! * Returns true if the vector is null. * - * \note A vector may be empty without being null. + * \note A vector may be empty without being null. So do not use this + * method to check if the vector is empty. + * * \see isEmpty() + * + * \deprecated */ - bool isNull() const; + // BIC: remove + TAGLIB_DEPRECATED bool isNull() const; /*! * Returns true if the ByteVector is empty. @@ -240,8 +293,11 @@ namespace TagLib { /*! * Returns a CRC checksum of the byte vector's data. + * + * \note This uses an uncommon variant of CRC32 specializes in Ogg. */ - uint checksum() const; + // BIC: Remove or make generic. + unsigned int checksum() const; /*! * Converts the first 4 bytes of the vector to an unsigned integer. @@ -253,10 +309,36 @@ namespace TagLib { * * \see fromUInt() */ - uint toUInt(bool mostSignificantByteFirst = true) const; + unsigned int toUInt(bool mostSignificantByteFirst = true) const; /*! - * Converts the first 2 bytes of the vector to a short. + * Converts the 4 bytes at \a offset of the vector to an unsigned integer. + * + * If \a mostSignificantByteFirst is true this will operate left to right + * evaluating the integer. For example if \a mostSignificantByteFirst is + * true then $00 $00 $00 $01 == 0x00000001 == 1, if false, $01 00 00 00 == + * 0x01000000 == 1. + * + * \see fromUInt() + */ + unsigned int toUInt(unsigned int offset, bool mostSignificantByteFirst = true) const; + + /*! + * Converts the \a length bytes at \a offset of the vector to an unsigned + * integer. If \a length is larger than 4, the excess is ignored. + * + * If \a mostSignificantByteFirst is true this will operate left to right + * evaluating the integer. For example if \a mostSignificantByteFirst is + * true then $00 $00 $00 $01 == 0x00000001 == 1, if false, $01 00 00 00 == + * 0x01000000 == 1. + * + * \see fromUInt() + */ + unsigned int toUInt(unsigned int offset, unsigned int length, + bool mostSignificantByteFirst = true) const; + + /*! + * Converts the first 2 bytes of the vector to a (signed) short. * * If \a mostSignificantByteFirst is true this will operate left to right * evaluating the integer. For example if \a mostSignificantByteFirst is @@ -266,6 +348,17 @@ namespace TagLib { */ short toShort(bool mostSignificantByteFirst = true) const; + /*! + * Converts the 2 bytes at \a offset of the vector to a (signed) short. + * + * If \a mostSignificantByteFirst is true this will operate left to right + * evaluating the integer. For example if \a mostSignificantByteFirst is + * true then $00 $01 == 0x0001 == 1, if false, $01 00 == 0x01000000 == 1. + * + * \see fromShort() + */ + short toShort(unsigned int offset, bool mostSignificantByteFirst = true) const; + /*! * Converts the first 2 bytes of the vector to a unsigned short. * @@ -277,6 +370,17 @@ namespace TagLib { */ unsigned short toUShort(bool mostSignificantByteFirst = true) const; + /*! + * Converts the 2 bytes at \a offset of the vector to a unsigned short. + * + * If \a mostSignificantByteFirst is true this will operate left to right + * evaluating the integer. For example if \a mostSignificantByteFirst is + * true then $00 $01 == 0x0001 == 1, if false, $01 00 == 0x01000000 == 1. + * + * \see fromShort() + */ + unsigned short toUShort(unsigned int offset, bool mostSignificantByteFirst = true) const; + /*! * Converts the first 8 bytes of the vector to a (signed) long long. * @@ -289,6 +393,58 @@ namespace TagLib { */ long long toLongLong(bool mostSignificantByteFirst = true) const; + /*! + * Converts the 8 bytes at \a offset of the vector to a (signed) long long. + * + * If \a mostSignificantByteFirst is true this will operate left to right + * evaluating the integer. For example if \a mostSignificantByteFirst is + * true then $00 00 00 00 00 00 00 01 == 0x0000000000000001 == 1, + * if false, $01 00 00 00 00 00 00 00 == 0x0100000000000000 == 1. + * + * \see fromUInt() + */ + long long toLongLong(unsigned int offset, bool mostSignificantByteFirst = true) const; + + /* + * Converts the 4 bytes at \a offset of the vector to a float as an IEEE754 + * 32-bit little-endian floating point number. + */ + float toFloat32LE(size_t offset) const; + + /* + * Converts the 4 bytes at \a offset of the vector to a float as an IEEE754 + * 32-bit big-endian floating point number. + */ + float toFloat32BE(size_t offset) const; + + /* + * Converts the 8 bytes at \a offset of the vector to a double as an IEEE754 + * 64-bit little-endian floating point number. + */ + double toFloat64LE(size_t offset) const; + + /* + * Converts the 8 bytes at \a offset of the vector to a double as an IEEE754 + * 64-bit big-endian floating point number. + */ + double toFloat64BE(size_t offset) const; + + /* + * Converts the 10 bytes at \a offset of the vector to a long double as an + * IEEE754 80-bit little-endian floating point number. + * + * \note This may compromise the precision depends on the size of long double. + */ + long double toFloat80LE(size_t offset) const; + + /* + * Converts the 10 bytes at \a offset of the vector to a long double as an + * IEEE754 80-bit big-endian floating point number. + * + * \note This may compromise the precision depends on the size of long double. + */ + long double toFloat80BE(size_t offset) const; + /*! * Creates a 4 byte ByteVector based on \a value. If * \a mostSignificantByteFirst is true, then this will operate left to right @@ -298,7 +454,7 @@ namespace TagLib { * * \see toUInt() */ - static ByteVector fromUInt(uint value, bool mostSignificantByteFirst = true); + static ByteVector fromUInt(unsigned int value, bool mostSignificantByteFirst = true); /*! * Creates a 2 byte ByteVector based on \a value. If @@ -322,12 +478,44 @@ namespace TagLib { static ByteVector fromLongLong(long long value, bool mostSignificantByteFirst = true); /*! - * Returns a ByteVector based on the CString \a s. + * Creates a 4 byte ByteVector based on \a value as an IEEE754 32-bit + * little-endian floating point number. + * + * \see fromFloat32BE() */ - static ByteVector fromCString(const char *s, uint length = 0xffffffff); + static ByteVector fromFloat32LE(float value); /*! - * Returns a const refernence to the byte at \a index. + * Creates a 4 byte ByteVector based on \a value as an IEEE754 32-bit + * big-endian floating point number. + * + * \see fromFloat32LE() + */ + static ByteVector fromFloat32BE(float value); + + /*! + * Creates a 8 byte ByteVector based on \a value as an IEEE754 64-bit + * little-endian floating point number. + * + * \see fromFloat64BE() + */ + static ByteVector fromFloat64LE(double value); + + /*! + * Creates a 8 byte ByteVector based on \a value as an IEEE754 64-bit + * big-endian floating point number. + * + * \see fromFloat64LE() + */ + static ByteVector fromFloat64BE(double value); + + /*! + * Returns a ByteVector based on the CString \a s. + */ + static ByteVector fromCString(const char *s, unsigned int length = 0xffffffff); + + /*! + * Returns a const reference to the byte at \a index. */ const char &operator[](int index) const; @@ -381,26 +569,49 @@ namespace TagLib { ByteVector &operator=(const ByteVector &v); /*! - * Copies ByteVector \a v. + * Copies a byte \a c. */ ByteVector &operator=(char c); /*! - * Copies ByteVector \a v. + * Copies \a data up to the first null byte. + * + * \warning The behavior is undefined if \a data is not null terminated. */ ByteVector &operator=(const char *data); /*! - * A static, empty ByteVector which is convenient and fast (since returning - * an empty or "null" value does not require instantiating a new ByteVector). + * Exchanges the content of the ByteVector by the content of \a v. */ - static ByteVector null; + void swap(ByteVector &v); /*! - * Returns a hex-encoded copy of the byte vector. - */ + * A static, empty ByteVector which is convenient and fast (since returning + * an empty or "null" value does not require instantiating a new ByteVector). + * + * \warning Do not modify this variable. It will mess up the internal state + * of TagLib. + * + * \deprecated + */ + // BIC: remove + TAGLIB_DEPRECATED static ByteVector null; + + /*! + * Returns a hex-encoded copy of the byte vector. + */ ByteVector toHex() const; + /*! + * Returns a base64 encoded copy of the byte vector + */ + ByteVector toBase64() const; + + /*! + * Decodes the base64 encoded byte vector. + */ + static ByteVector fromBase64(const ByteVector &); + protected: /* * If this ByteVector is being shared via implicit sharing, do a deep copy @@ -413,7 +624,6 @@ namespace TagLib { class ByteVectorPrivate; ByteVectorPrivate *d; }; - } /*! diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorlist.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorlist.cpp index 7ea893f11..c4fdf5933 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorlist.cpp +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorlist.cpp @@ -47,7 +47,7 @@ ByteVectorList ByteVectorList::split(const ByteVector &v, const ByteVector &patt { ByteVectorList l; - uint previousOffset = 0; + unsigned int previousOffset = 0; for(int offset = v.find(pattern, 0, byteAlign); offset != -1 && (max == 0 || max > int(l.size()) + 1); offset = v.find(pattern, offset + pattern.size(), byteAlign)) @@ -55,7 +55,7 @@ ByteVectorList ByteVectorList::split(const ByteVector &v, const ByteVector &patt if(offset - previousOffset >= 1) l.append(v.mid(previousOffset, offset - previousOffset)); else - l.append(ByteVector::null); + l.append(ByteVector()); previousOffset = offset + pattern.size(); } diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorstream.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorstream.cpp new file mode 100644 index 000000000..333f528c1 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorstream.cpp @@ -0,0 +1,167 @@ +/*************************************************************************** + copyright : (C) 2011 by Lukas Lalinsky + email : lalinsky@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "tbytevectorstream.h" +#include "tstring.h" +#include "tdebug.h" + +#include <stdio.h> +#include <string.h> + +#include <stdlib.h> + +using namespace TagLib; + +class ByteVectorStream::ByteVectorStreamPrivate +{ +public: + ByteVectorStreamPrivate(const ByteVector &data); + + ByteVector data; + long position; +}; + +ByteVectorStream::ByteVectorStreamPrivate::ByteVectorStreamPrivate(const ByteVector &data) : + data(data), + position(0) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +ByteVectorStream::ByteVectorStream(const ByteVector &data) : + d(new ByteVectorStreamPrivate(data)) +{ +} + +ByteVectorStream::~ByteVectorStream() +{ + delete d; +} + +FileName ByteVectorStream::name() const +{ + return FileName(""); // XXX do we need a name? +} + +ByteVector ByteVectorStream::readBlock(unsigned long length) +{ + if(length == 0) + return ByteVector(); + + ByteVector v = d->data.mid(d->position, length); + d->position += v.size(); + return v; +} + +void ByteVectorStream::writeBlock(const ByteVector &data) +{ + unsigned int size = data.size(); + if(long(d->position + size) > length()) { + truncate(d->position + size); + } + memcpy(d->data.data() + d->position, data.data(), size); + d->position += size; +} + +void ByteVectorStream::insert(const ByteVector &data, unsigned long start, unsigned long replace) +{ + long sizeDiff = data.size() - replace; + if(sizeDiff < 0) { + removeBlock(start + data.size(), -sizeDiff); + } + else if(sizeDiff > 0) { + truncate(length() + sizeDiff); + unsigned long readPosition = start + replace; + unsigned long writePosition = start + data.size(); + memmove(d->data.data() + writePosition, d->data.data() + readPosition, length() - sizeDiff - readPosition); + } + seek(start); + writeBlock(data); +} + +void ByteVectorStream::removeBlock(unsigned long start, unsigned long length) +{ + unsigned long readPosition = start + length; + unsigned long writePosition = start; + if(readPosition < static_cast<unsigned long>(ByteVectorStream::length())) { + unsigned long bytesToMove = ByteVectorStream::length() - readPosition; + memmove(d->data.data() + writePosition, d->data.data() + readPosition, bytesToMove); + writePosition += bytesToMove; + } + d->position = writePosition; + truncate(writePosition); +} + +bool ByteVectorStream::readOnly() const +{ + return false; +} + +bool ByteVectorStream::isOpen() const +{ + return true; +} + +void ByteVectorStream::seek(long offset, Position p) +{ + switch(p) { + case Beginning: + d->position = offset; + break; + case Current: + d->position += offset; + break; + case End: + d->position = length() + offset; // offset is expected to be negative + break; + } +} + +void ByteVectorStream::clear() +{ +} + +long ByteVectorStream::tell() const +{ + return d->position; +} + +long ByteVectorStream::length() +{ + return d->data.size(); +} + +void ByteVectorStream::truncate(long length) +{ + d->data.resize(length); +} + +ByteVector *ByteVectorStream::data() +{ + return &d->data; +} diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorstream.h b/Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorstream.h new file mode 100644 index 000000000..84327c46d --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tbytevectorstream.h @@ -0,0 +1,145 @@ +/*************************************************************************** + copyright : (C) 2011 by Lukas Lalinsky + email : lalinsky@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_BYTEVECTORSTREAM_H +#define TAGLIB_BYTEVECTORSTREAM_H + +#include "taglib_export.h" +#include "taglib.h" +#include "tbytevector.h" +#include "tiostream.h" + +namespace TagLib { + + class String; + class Tag; + class AudioProperties; + + //! In-memory Stream class using ByteVector for its storage. + + class TAGLIB_EXPORT ByteVectorStream : public IOStream + { + public: + /*! + * Construct a File object and opens the \a file. \a file should be a + * be a C-string in the local file system encoding. + */ + ByteVectorStream(const ByteVector &data); + + /*! + * Destroys this ByteVectorStream instance. + */ + virtual ~ByteVectorStream(); + + /*! + * Returns the file name in the local file system encoding. + */ + FileName name() const; + + /*! + * Reads a block of size \a length at the current get pointer. + */ + ByteVector readBlock(unsigned long length); + + /*! + * Attempts to write the block \a data at the current get pointer. If the + * file is currently only opened read only -- i.e. readOnly() returns true -- + * this attempts to reopen the file in read/write mode. + * + * \note This should be used instead of using the streaming output operator + * for a ByteVector. And even this function is significantly slower than + * doing output with a char[]. + */ + void writeBlock(const ByteVector &data); + + /*! + * Insert \a data at position \a start in the file overwriting \a replace + * bytes of the original content. + * + * \note This method is slow since it requires rewriting all of the file + * after the insertion point. + */ + void insert(const ByteVector &data, unsigned long start = 0, unsigned long replace = 0); + + /*! + * Removes a block of the file starting a \a start and continuing for + * \a length bytes. + * + * \note This method is slow since it involves rewriting all of the file + * after the removed portion. + */ + void removeBlock(unsigned long start = 0, unsigned long length = 0); + + /*! + * Returns true if the file is read only (or if the file can not be opened). + */ + bool readOnly() const; + + /*! + * Since the file can currently only be opened as an argument to the + * constructor (sort-of by design), this returns if that open succeeded. + */ + bool isOpen() const; + + /*! + * Move the I/O pointer to \a offset in the file from position \a p. This + * defaults to seeking from the beginning of the file. + * + * \see Position + */ + void seek(long offset, Position p = Beginning); + + /*! + * Reset the end-of-file and error flags on the file. + */ + void clear(); + + /*! + * Returns the current offset within the file. + */ + long tell() const; + + /*! + * Returns the length of the file. + */ + long length(); + + /*! + * Truncates the file to a \a length. + */ + void truncate(long length); + + ByteVector *data(); + + protected: + + private: + class ByteVectorStreamPrivate; + ByteVectorStreamPrivate *d; + }; + +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tdebug.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tdebug.cpp index 522b68c97..b2efc4cb5 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tdebug.cpp +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tdebug.cpp @@ -23,33 +23,42 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ -#ifndef NDEBUG -#include <iostream> -#include <bitset> +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if !defined(NDEBUG) || defined(TRACE_IN_RELEASE) #include "tdebug.h" #include "tstring.h" +#include "tdebuglistener.h" +#include "tutils.h" -using namespace TagLib; +#include <bitset> +#include <cstdio> +#include <cstdarg> -void TagLib::debug(const String &s) +namespace TagLib { - std::cerr << "TagLib: " << s << std::endl; -} + // The instance is defined in tdebuglistener.cpp. + extern DebugListener *debugListener; -void TagLib::debugData(const ByteVector &v) -{ - for(uint i = 0; i < v.size(); i++) { + void debug(const String &s) + { + debugListener->printMessage("TagLib: " + s + "\n"); + } - std::cout << "*** [" << i << "] - '" << char(v[i]) << "' - int " << int(v[i]) - << std::endl; + void debugData(const ByteVector &v) + { + for(unsigned int i = 0; i < v.size(); ++i) { + const std::string bits = std::bitset<8>(v[i]).to_string(); + const String msg = Utils::formatString( + "*** [%u] - char '%c' - int %d, 0x%02x, 0b%s\n", + i, v[i], v[i], v[i], bits.c_str()); - std::bitset<8> b(v[i]); - - for(int j = 0; j < 8; j++) - std::cout << i << ":" << j << " " << b.test(j) << std::endl; - - std::cout << std::endl; + debugListener->printMessage(msg); + } } } + #endif diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tdebug.h b/Frameworks/TagLib/taglib/taglib/toolkit/tdebug.h index 5204fe707..80d00d39e 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tdebug.h +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tdebug.h @@ -32,11 +32,12 @@ namespace TagLib { class ByteVector; #ifndef DO_NOT_DOCUMENT -#ifndef NDEBUG +#if !defined(NDEBUG) || defined(TRACE_IN_RELEASE) /*! - * A simple function that prints debugging output to cerr if debugging is - * not disabled. + * A simple function that outputs the debug messages to the listener. + * The default listener redirects the messages to \a stderr when NDEBUG is + * not defined. * * \warning Do not use this outside of TagLib, it could lead to undefined * symbols in your build if TagLib is built with NDEBUG defined and your @@ -59,13 +60,11 @@ namespace TagLib { #else - // Define these to an empty statement if debugging is disabled. + #define debug(x) ((void)0) + #define debugData(x) ((void)0) -#define debug(x) -#define debugData(x) - -#endif #endif } #endif +#endif diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tdebuglistener.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tdebuglistener.cpp new file mode 100644 index 000000000..48912222d --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tdebuglistener.cpp @@ -0,0 +1,85 @@ +/*************************************************************************** + copyright : (C) 2013 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "tdebuglistener.h" + +#include <iostream> +#include <bitset> + +#ifdef _WIN32 +# include <windows.h> +#endif + +using namespace TagLib; + +namespace +{ + class DefaultListener : public DebugListener + { + public: + virtual void printMessage(const String &msg) + { +#ifdef _WIN32 + + const wstring wstr = msg.toWString(); + const int len = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL); + if(len != 0) { + std::vector<char> buf(len); + WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &buf[0], len, NULL, NULL); + + std::cerr << std::string(&buf[0]); + } + +#else + + std::cerr << msg; + +#endif + } + }; + + DefaultListener defaultListener; +} + +namespace TagLib +{ + DebugListener *debugListener = &defaultListener; + + DebugListener::DebugListener() + { + } + + DebugListener::~DebugListener() + { + } + + void setDebugListener(DebugListener *listener) + { + if(listener) + debugListener = listener; + else + debugListener = &defaultListener; + } +} diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tdebuglistener.h b/Frameworks/TagLib/taglib/taglib/toolkit/tdebuglistener.h new file mode 100644 index 000000000..3c8e1185c --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tdebuglistener.h @@ -0,0 +1,74 @@ +/*************************************************************************** + copyright : (C) 2013 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_DEBUGLISTENER_H +#define TAGLIB_DEBUGLISTENER_H + +#include "taglib_export.h" +#include "tstring.h" + +namespace TagLib +{ + //! An abstraction for the listener to the debug messages. + + /*! + * This class enables you to handle the debug messages in your preferred + * way by subclassing this class, reimplementing printMessage() and setting + * your reimplementation as the default with setDebugListener(). + * + * \see setDebugListener() + */ + class TAGLIB_EXPORT DebugListener + { + public: + DebugListener(); + virtual ~DebugListener(); + + /*! + * When overridden in a derived class, redirects \a msg to your preferred + * channel such as stderr, Windows debugger or so forth. + */ + virtual void printMessage(const String &msg) = 0; + + private: + // Noncopyable + DebugListener(const DebugListener &); + DebugListener &operator=(const DebugListener &); + }; + + /*! + * Sets the listener that decides how the debug messages are redirected. + * If the parameter \a listener is null, the previous listener is released + * and default stderr listener is restored. + * + * \note The caller is responsible for deleting the previous listener + * as needed after it is released. + * + * \see DebugListener + */ + TAGLIB_EXPORT void setDebugListener(DebugListener *listener); +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tfile.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tfile.cpp index f0b3c80d5..aff1684d9 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tfile.cpp +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tfile.cpp @@ -24,24 +24,19 @@ ***************************************************************************/ #include "tfile.h" +#include "tfilestream.h" #include "tstring.h" #include "tdebug.h" - -#include <stdio.h> -#include <string.h> -#include <sys/stat.h> +#include "tpropertymap.h" #ifdef _WIN32 -# include <wchar.h> # include <windows.h> # include <io.h> -# define ftruncate _chsize #else +# include <stdio.h> # include <unistd.h> #endif -#include <stdlib.h> - #ifndef R_OK # define R_OK 4 #endif @@ -49,134 +44,198 @@ # define W_OK 2 #endif +#include "asffile.h" +#include "mpegfile.h" +#include "vorbisfile.h" +#include "flacfile.h" +#include "oggflacfile.h" +#include "mpcfile.h" +#include "mp4file.h" +#include "wavpackfile.h" +#include "speexfile.h" +#include "opusfile.h" +#include "trueaudiofile.h" +#include "aifffile.h" +#include "wavfile.h" +#include "apefile.h" +#include "modfile.h" +#include "s3mfile.h" +#include "itfile.h" +#include "xmfile.h" +#include "mp4file.h" + using namespace TagLib; -#ifdef _WIN32 - -typedef FileName FileNameHandle; - -#else - -struct FileNameHandle : public std::string -{ - FileNameHandle(FileName name) : std::string(name) {} - operator FileName () const { return c_str(); } -}; - -#endif - class File::FilePrivate { public: - FilePrivate(FileName fileName); - - FILE *file; - - FileNameHandle name; - - bool readOnly; - bool valid; - ulong size; - static const uint bufferSize = 1024; -}; - -File::FilePrivate::FilePrivate(FileName fileName) : - file(0), - name(fileName), - readOnly(true), - valid(true), - size(0) -{ - // First try with read / write mode, if that fails, fall back to read only. - -#ifdef _WIN32 - - if(wcslen((const wchar_t *) fileName) > 0) { - - file = _wfopen(name, L"rb+"); - - if(file) - readOnly = false; - else - file = _wfopen(name, L"rb"); - - if(file) - return; + FilePrivate(IOStream *stream, bool owner) : + stream(stream), + streamOwner(owner), + valid(true) {} + ~FilePrivate() + { + if(streamOwner) + delete stream; } -#endif - - file = fopen(name, "rb+"); - - if(file) - readOnly = false; - else - file = fopen(name, "rb"); - - if(!file) - debug("Could not open file " + String((const char *) name)); -} + IOStream *stream; + bool streamOwner; + bool valid; +}; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -File::File(FileName file) +File::File(FileName fileName) : + d(new FilePrivate(new FileStream(fileName), true)) +{ +} + +File::File(IOStream *stream) : + d(new FilePrivate(stream, false)) { - d = new FilePrivate(file); } File::~File() { - if(d->file) - fclose(d->file); delete d; } FileName File::name() const { - return d->name; + return d->stream->name(); } -ByteVector File::readBlock(ulong length) +PropertyMap File::properties() const { - if(!d->file) { - debug("File::readBlock() -- Invalid File"); - return ByteVector::null; - } + // ugly workaround until this method is virtual + if(dynamic_cast<const APE::File* >(this)) + return dynamic_cast<const APE::File* >(this)->properties(); + if(dynamic_cast<const FLAC::File* >(this)) + return dynamic_cast<const FLAC::File* >(this)->properties(); + if(dynamic_cast<const IT::File* >(this)) + return dynamic_cast<const IT::File* >(this)->properties(); + if(dynamic_cast<const Mod::File* >(this)) + return dynamic_cast<const Mod::File* >(this)->properties(); + if(dynamic_cast<const MPC::File* >(this)) + return dynamic_cast<const MPC::File* >(this)->properties(); + if(dynamic_cast<const MPEG::File* >(this)) + return dynamic_cast<const MPEG::File* >(this)->properties(); + if(dynamic_cast<const Ogg::FLAC::File* >(this)) + return dynamic_cast<const Ogg::FLAC::File* >(this)->properties(); + if(dynamic_cast<const Ogg::Speex::File* >(this)) + return dynamic_cast<const Ogg::Speex::File* >(this)->properties(); + if(dynamic_cast<const Ogg::Opus::File* >(this)) + return dynamic_cast<const Ogg::Opus::File* >(this)->properties(); + if(dynamic_cast<const Ogg::Vorbis::File* >(this)) + return dynamic_cast<const Ogg::Vorbis::File* >(this)->properties(); + if(dynamic_cast<const RIFF::AIFF::File* >(this)) + return dynamic_cast<const RIFF::AIFF::File* >(this)->properties(); + if(dynamic_cast<const RIFF::WAV::File* >(this)) + return dynamic_cast<const RIFF::WAV::File* >(this)->properties(); + if(dynamic_cast<const S3M::File* >(this)) + return dynamic_cast<const S3M::File* >(this)->properties(); + if(dynamic_cast<const TrueAudio::File* >(this)) + return dynamic_cast<const TrueAudio::File* >(this)->properties(); + if(dynamic_cast<const WavPack::File* >(this)) + return dynamic_cast<const WavPack::File* >(this)->properties(); + if(dynamic_cast<const XM::File* >(this)) + return dynamic_cast<const XM::File* >(this)->properties(); + if(dynamic_cast<const MP4::File* >(this)) + return dynamic_cast<const MP4::File* >(this)->properties(); + if(dynamic_cast<const ASF::File* >(this)) + return dynamic_cast<const ASF::File* >(this)->properties(); + return tag()->properties(); +} - if(length == 0) - return ByteVector::null; +void File::removeUnsupportedProperties(const StringList &properties) +{ + // here we only consider those formats that could possibly contain + // unsupported properties + if(dynamic_cast<APE::File* >(this)) + dynamic_cast<APE::File* >(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast<FLAC::File* >(this)) + dynamic_cast<FLAC::File* >(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast<MPC::File* >(this)) + dynamic_cast<MPC::File* >(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast<MPEG::File* >(this)) + dynamic_cast<MPEG::File* >(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast<Ogg::Vorbis::File* >(this)) + dynamic_cast<Ogg::Vorbis::File* >(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast<RIFF::AIFF::File* >(this)) + dynamic_cast<RIFF::AIFF::File* >(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast<RIFF::WAV::File* >(this)) + dynamic_cast<RIFF::WAV::File* >(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast<TrueAudio::File* >(this)) + dynamic_cast<TrueAudio::File* >(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast<WavPack::File* >(this)) + dynamic_cast<WavPack::File* >(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast<MP4::File* >(this)) + dynamic_cast<MP4::File* >(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast<ASF::File* >(this)) + dynamic_cast<ASF::File* >(this)->removeUnsupportedProperties(properties); + else + tag()->removeUnsupportedProperties(properties); +} - if(length > FilePrivate::bufferSize && - length > ulong(File::length())) - { - length = File::length(); - } +PropertyMap File::setProperties(const PropertyMap &properties) +{ + if(dynamic_cast<APE::File* >(this)) + return dynamic_cast<APE::File* >(this)->setProperties(properties); + else if(dynamic_cast<FLAC::File* >(this)) + return dynamic_cast<FLAC::File* >(this)->setProperties(properties); + else if(dynamic_cast<IT::File* >(this)) + return dynamic_cast<IT::File* >(this)->setProperties(properties); + else if(dynamic_cast<Mod::File* >(this)) + return dynamic_cast<Mod::File* >(this)->setProperties(properties); + else if(dynamic_cast<MPC::File* >(this)) + return dynamic_cast<MPC::File* >(this)->setProperties(properties); + else if(dynamic_cast<MPEG::File* >(this)) + return dynamic_cast<MPEG::File* >(this)->setProperties(properties); + else if(dynamic_cast<Ogg::FLAC::File* >(this)) + return dynamic_cast<Ogg::FLAC::File* >(this)->setProperties(properties); + else if(dynamic_cast<Ogg::Speex::File* >(this)) + return dynamic_cast<Ogg::Speex::File* >(this)->setProperties(properties); + else if(dynamic_cast<Ogg::Opus::File* >(this)) + return dynamic_cast<Ogg::Opus::File* >(this)->setProperties(properties); + else if(dynamic_cast<Ogg::Vorbis::File* >(this)) + return dynamic_cast<Ogg::Vorbis::File* >(this)->setProperties(properties); + else if(dynamic_cast<RIFF::AIFF::File* >(this)) + return dynamic_cast<RIFF::AIFF::File* >(this)->setProperties(properties); + else if(dynamic_cast<RIFF::WAV::File* >(this)) + return dynamic_cast<RIFF::WAV::File* >(this)->setProperties(properties); + else if(dynamic_cast<S3M::File* >(this)) + return dynamic_cast<S3M::File* >(this)->setProperties(properties); + else if(dynamic_cast<TrueAudio::File* >(this)) + return dynamic_cast<TrueAudio::File* >(this)->setProperties(properties); + else if(dynamic_cast<WavPack::File* >(this)) + return dynamic_cast<WavPack::File* >(this)->setProperties(properties); + else if(dynamic_cast<XM::File* >(this)) + return dynamic_cast<XM::File* >(this)->setProperties(properties); + else if(dynamic_cast<MP4::File* >(this)) + return dynamic_cast<MP4::File* >(this)->setProperties(properties); + else if(dynamic_cast<ASF::File* >(this)) + return dynamic_cast<ASF::File* >(this)->setProperties(properties); + else + return tag()->setProperties(properties); +} - ByteVector v(static_cast<uint>(length)); - const int count = fread(v.data(), sizeof(char), length, d->file); - v.resize(count); - return v; +ByteVector File::readBlock(unsigned long length) +{ + return d->stream->readBlock(length); } void File::writeBlock(const ByteVector &data) { - if(!d->file) - return; - - if(d->readOnly) { - debug("File::writeBlock() -- attempted to write to a file that is not writable"); - return; - } - - fwrite(data.data(), sizeof(char), data.size(), d->file); + d->stream->writeBlock(data); } long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &before) { - if(!d->file || pattern.size() > d->bufferSize) + if(!d->stream || pattern.size() > bufferSize()) return -1; // The position in the file that the current buffer starts at. @@ -209,7 +268,7 @@ long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &be // (2) The search pattern is wholly contained within the current buffer. // // (3) The current buffer ends with a partial match of the pattern. We will - // note this for use in the next itteration, where we will check for the rest + // note this for use in the next iteration, where we will check for the rest // of the pattern. // // All three of these are done in two steps. First we check for the pattern @@ -217,20 +276,20 @@ long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &be // then check for "before". The order is important because it gives priority // to "real" matches. - for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) { + for(buffer = readBlock(bufferSize()); buffer.size() > 0; buffer = readBlock(bufferSize())) { // (1) previous partial match - if(previousPartialMatch >= 0 && int(d->bufferSize) > previousPartialMatch) { - const int patternOffset = (d->bufferSize - previousPartialMatch); + if(previousPartialMatch >= 0 && int(bufferSize()) > previousPartialMatch) { + const int patternOffset = (bufferSize() - previousPartialMatch); if(buffer.containsAt(pattern, 0, patternOffset)) { seek(originalPosition); - return bufferOffset - d->bufferSize + previousPartialMatch; + return bufferOffset - bufferSize() + previousPartialMatch; } } - if(!before.isNull() && beforePreviousPartialMatch >= 0 && int(d->bufferSize) > beforePreviousPartialMatch) { - const int beforeOffset = (d->bufferSize - beforePreviousPartialMatch); + if(!before.isEmpty() && beforePreviousPartialMatch >= 0 && int(bufferSize()) > beforePreviousPartialMatch) { + const int beforeOffset = (bufferSize() - beforePreviousPartialMatch); if(buffer.containsAt(before, 0, beforeOffset)) { seek(originalPosition); return -1; @@ -245,7 +304,7 @@ long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &be return bufferOffset + location; } - if(!before.isNull() && buffer.find(before) >= 0) { + if(!before.isEmpty() && buffer.find(before) >= 0) { seek(originalPosition); return -1; } @@ -254,10 +313,10 @@ long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &be previousPartialMatch = buffer.endsWithPartialMatch(pattern); - if(!before.isNull()) + if(!before.isEmpty()) beforePreviousPartialMatch = buffer.endsWithPartialMatch(before); - bufferOffset += d->bufferSize; + bufferOffset += bufferSize(); } // Since we hit the end of the file, reset the status before continuing. @@ -272,7 +331,7 @@ long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &be long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &before) { - if(!d->file || pattern.size() > d->bufferSize) + if(!d->stream || pattern.size() > bufferSize()) return -1; // The position in the file that the current buffer starts at. @@ -294,39 +353,45 @@ long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &b // Start the search at the offset. - long bufferOffset; - if(fromOffset == 0) { - seek(-1 * int(d->bufferSize), End); - bufferOffset = tell(); - } - else { - seek(fromOffset + -1 * int(d->bufferSize), Beginning); - bufferOffset = tell(); - } + if(fromOffset == 0) + fromOffset = length(); + + long bufferLength = bufferSize(); + long bufferOffset = fromOffset + pattern.size(); // See the notes in find() for an explanation of this algorithm. - for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) { + while(true) { + + if(bufferOffset > bufferLength) { + bufferOffset -= bufferLength; + } + else { + bufferLength = bufferOffset; + bufferOffset = 0; + } + seek(bufferOffset); + + buffer = readBlock(bufferLength); + if(buffer.isEmpty()) + break; // TODO: (1) previous partial match // (2) pattern contained in current buffer - long location = buffer.rfind(pattern); + const long location = buffer.rfind(pattern); if(location >= 0) { seek(originalPosition); return bufferOffset + location; } - if(!before.isNull() && buffer.find(before) >= 0) { + if(!before.isEmpty() && buffer.find(before) >= 0) { seek(originalPosition); return -1; } // TODO: (3) partial match - - bufferOffset -= d->bufferSize; - seek(bufferOffset); } // Since we hit the end of the file, reset the status before continuing. @@ -338,149 +403,24 @@ long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &b return -1; } -void File::insert(const ByteVector &data, ulong start, ulong replace) +void File::insert(const ByteVector &data, unsigned long start, unsigned long replace) { - if(!d->file) - return; - - if(data.size() == replace) { - seek(start); - writeBlock(data); - return; - } - else if(data.size() < replace) { - seek(start); - writeBlock(data); - removeBlock(start + data.size(), replace - data.size()); - return; - } - - // Woohoo! Faster (about 20%) than id3lib at last. I had to get hardcore - // and avoid TagLib's high level API for rendering just copying parts of - // the file that don't contain tag data. - // - // Now I'll explain the steps in this ugliness: - - // First, make sure that we're working with a buffer that is longer than - // the *differnce* in the tag sizes. We want to avoid overwriting parts - // that aren't yet in memory, so this is necessary. - - ulong bufferLength = bufferSize(); - - while(data.size() - replace > bufferLength) - bufferLength += bufferSize(); - - // Set where to start the reading and writing. - - long readPosition = start + replace; - long writePosition = start; - - ByteVector buffer; - ByteVector aboutToOverwrite(static_cast<uint>(bufferLength)); - - // This is basically a special case of the loop below. Here we're just - // doing the same steps as below, but since we aren't using the same buffer - // size -- instead we're using the tag size -- this has to be handled as a - // special case. We're also using File::writeBlock() just for the tag. - // That's a bit slower than using char *'s so, we're only doing it here. - - seek(readPosition); - int bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file); - readPosition += bufferLength; - - seek(writePosition); - writeBlock(data); - writePosition += data.size(); - - buffer = aboutToOverwrite; - - // In case we've already reached the end of file... - - buffer.resize(bytesRead); - - // Ok, here's the main loop. We want to loop until the read fails, which - // means that we hit the end of the file. - - while(!buffer.isEmpty()) { - - // Seek to the current read position and read the data that we're about - // to overwrite. Appropriately increment the readPosition. - - seek(readPosition); - bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file); - aboutToOverwrite.resize(bytesRead); - readPosition += bufferLength; - - // Check to see if we just read the last block. We need to call clear() - // if we did so that the last write succeeds. - - if(ulong(bytesRead) < bufferLength) - clear(); - - // Seek to the write position and write our buffer. Increment the - // writePosition. - - seek(writePosition); - fwrite(buffer.data(), sizeof(char), buffer.size(), d->file); - writePosition += buffer.size(); - - // Make the current buffer the data that we read in the beginning. - - buffer = aboutToOverwrite; - - // Again, we need this for the last write. We don't want to write garbage - // at the end of our file, so we need to set the buffer size to the amount - // that we actually read. - - bufferLength = bytesRead; - } + d->stream->insert(data, start, replace); } -void File::removeBlock(ulong start, ulong length) +void File::removeBlock(unsigned long start, unsigned long length) { - if(!d->file) - return; - - ulong bufferLength = bufferSize(); - - long readPosition = start + length; - long writePosition = start; - - ByteVector buffer(static_cast<uint>(bufferLength)); - - ulong bytesRead = 1; - - while(bytesRead != 0) { - seek(readPosition); - bytesRead = fread(buffer.data(), sizeof(char), bufferLength, d->file); - readPosition += bytesRead; - - // Check to see if we just read the last block. We need to call clear() - // if we did so that the last write succeeds. - - if(bytesRead < bufferLength) - clear(); - - seek(writePosition); - fwrite(buffer.data(), sizeof(char), bytesRead, d->file); - writePosition += bytesRead; - } - truncate(writePosition); + d->stream->removeBlock(start, length); } bool File::readOnly() const { - return d->readOnly; -} - -bool File::isReadable(const char *file) -{ - return access(file, R_OK) == 0; + return d->stream->readOnly(); } bool File::isOpen() const { - return (d->file != NULL); + return d->stream->isOpen(); } bool File::isValid() const @@ -490,75 +430,70 @@ bool File::isValid() const void File::seek(long offset, Position p) { - if(!d->file) { - debug("File::seek() -- trying to seek in a file that isn't opened."); - return; - } + d->stream->seek(offset, IOStream::Position(p)); +} - switch(p) { - case Beginning: - fseek(d->file, offset, SEEK_SET); - break; - case Current: - fseek(d->file, offset, SEEK_CUR); - break; - case End: - fseek(d->file, offset, SEEK_END); - break; - } +void File::truncate(long length) +{ + d->stream->truncate(length); } void File::clear() { - clearerr(d->file); + d->stream->clear(); } long File::tell() const { - return ftell(d->file); + return d->stream->tell(); } long File::length() { - // Do some caching in case we do multiple calls. + return d->stream->length(); +} - if(d->size > 0) - return d->size; +bool File::isReadable(const char *file) +{ - if(!d->file) - return 0; +#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later - long curpos = tell(); + return _access_s(file, R_OK) == 0; - seek(0, End); - long endpos = tell(); +#else - seek(curpos, Beginning); + return access(file, R_OK) == 0; + +#endif - d->size = endpos; - return endpos; } bool File::isWritable(const char *file) { + +#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later + + return _access_s(file, W_OK) == 0; + +#else + return access(file, W_OK) == 0; + +#endif + } //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// +unsigned int File::bufferSize() +{ + return 1024; +} + void File::setValid(bool valid) { d->valid = valid; } -void File::truncate(long length) -{ - ftruncate(fileno(d->file), length); -} - -TagLib::uint File::bufferSize() -{ - return FilePrivate::bufferSize; -} diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tfile.h b/Frameworks/TagLib/taglib/taglib/toolkit/tfile.h index da3228097..bdc4f1241 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tfile.h +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tfile.h @@ -28,29 +28,16 @@ #include "taglib_export.h" #include "taglib.h" +#include "tag.h" #include "tbytevector.h" +#include "tiostream.h" namespace TagLib { class String; class Tag; class AudioProperties; - -#ifdef _WIN32 - class TAGLIB_EXPORT FileName - { - public: - FileName(const wchar_t *name) : m_wname(name) {} - FileName(const char *name) : m_name(name) {} - operator const wchar_t *() const { return m_wname.c_str(); } - operator const char *() const { return m_name.c_str(); } - private: - std::string m_name; - std::wstring m_wname; - }; -#else - typedef const char *FileName; -#endif + class PropertyMap; //! A file class with some useful methods for tag manipulation @@ -75,6 +62,23 @@ namespace TagLib { End }; + /*! + * Specify which tags to strip either explicitly, or on save. + */ + enum StripTags { + StripNone, //<! Don't strip any tags + StripOthers //<! Strip all tags not explicitly referenced in method call + }; + + /*! + * Used to specify if when saving files, if values between different tag + * types should be synchronized. + */ + enum DuplicateTags { + Duplicate, //<! Synchronize values between different tag types + DoNotDuplicate //<! Do not synchronize values between different tag types + }; + /*! * Destroys this File instance. */ @@ -91,6 +95,44 @@ namespace TagLib { */ virtual Tag *tag() const = 0; + /*! + * Exports the tags of the file as dictionary mapping (human readable) tag + * names (uppercase Strings) to StringLists of tag values. Calls the according + * specialization in the File subclasses. + * For each metadata object of the file that could not be parsed into the PropertyMap + * format, the returned map's unsupportedData() list will contain one entry identifying + * that object (e.g. the frame type for ID3v2 tags). Use removeUnsupportedProperties() + * to remove (a subset of) them. + * For files that contain more than one tag (e.g. an MP3 with both an ID3v1 and an ID3v2 + * tag) only the most "modern" one will be exported (ID3v2 in this case). + * BIC: Will be made virtual in future releases. + */ + PropertyMap properties() const; + + /*! + * Removes unsupported properties, or a subset of them, from the file's metadata. + * The parameter \a properties must contain only entries from + * properties().unsupportedData(). + * BIC: Will be mad virtual in future releases. + */ + void removeUnsupportedProperties(const StringList& properties); + + /*! + * Sets the tags of this File to those specified in \a properties. Calls the + * according specialization method in the subclasses of File to do the translation + * into the format-specific details. + * If some value(s) could not be written imported to the specific metadata format, + * the returned PropertyMap will contain those value(s). Otherwise it will be empty, + * indicating that no problems occurred. + * With file types that support several tag formats (for instance, MP3 files can have + * ID3v1, ID3v2, and APEv2 tags), this function will create the most appropriate one + * (ID3v2 for MP3 files). Older formats will be updated as well, if they exist, but won't + * be taken into account for the return value of this function. + * See the documentation of the subclass implementations for detailed descriptions. + * BIC: will become pure virtual in the future + */ + PropertyMap setProperties(const PropertyMap &properties); + /*! * Returns a pointer to this file's audio properties. This should be * reimplemented in the concrete subclasses. If no audio properties were @@ -113,7 +155,7 @@ namespace TagLib { /*! * Reads a block of size \a length at the current get pointer. */ - ByteVector readBlock(ulong length); + ByteVector readBlock(unsigned long length); /*! * Attempts to write the block \a data at the current get pointer. If the @@ -130,33 +172,33 @@ namespace TagLib { * Returns the offset in the file that \a pattern occurs at or -1 if it can * not be found. If \a before is set, the search will only continue until the * pattern \a before is found. This is useful for tagging purposes to search - * for a tag before the synch frame. + * for a tag before the sync frame. * * Searching starts at \a fromOffset, which defaults to the beginning of the * file. * - * \note This has the practial limitation that \a pattern can not be longer + * \note This has the practical limitation that \a pattern can not be longer * than the buffer size used by readBlock(). Currently this is 1024 bytes. */ long find(const ByteVector &pattern, long fromOffset = 0, - const ByteVector &before = ByteVector::null); + const ByteVector &before = ByteVector()); /*! * Returns the offset in the file that \a pattern occurs at or -1 if it can * not be found. If \a before is set, the search will only continue until the * pattern \a before is found. This is useful for tagging purposes to search - * for a tag before the synch frame. + * for a tag before the sync frame. * * Searching starts at \a fromOffset and proceeds from the that point to the * beginning of the file and defaults to the end of the file. * - * \note This has the practial limitation that \a pattern can not be longer + * \note This has the practical limitation that \a pattern can not be longer * than the buffer size used by readBlock(). Currently this is 1024 bytes. */ long rfind(const ByteVector &pattern, long fromOffset = 0, - const ByteVector &before = ByteVector::null); + const ByteVector &before = ByteVector()); /*! * Insert \a data at position \a start in the file overwriting \a replace @@ -165,7 +207,7 @@ namespace TagLib { * \note This method is slow since it requires rewriting all of the file * after the insertion point. */ - void insert(const ByteVector &data, ulong start = 0, ulong replace = 0); + void insert(const ByteVector &data, unsigned long start = 0, unsigned long replace = 0); /*! * Removes a block of the file starting a \a start and continuing for @@ -174,7 +216,7 @@ namespace TagLib { * \note This method is slow since it involves rewriting all of the file * after the removed portion. */ - void removeBlock(ulong start = 0, ulong length = 0); + void removeBlock(unsigned long start = 0, unsigned long length = 0); /*! * Returns true if the file is read only (or if the file can not be opened). @@ -188,7 +230,7 @@ namespace TagLib { bool isOpen() const; /*! - * Returns true if the file is open and readble. + * Returns true if the file is open and readable. */ bool isValid() const; @@ -221,14 +263,14 @@ namespace TagLib { * * \deprecated */ - static bool isReadable(const char *file); + TAGLIB_DEPRECATED static bool isReadable(const char *file); /*! * Returns true if \a file can be opened for writing. * * \deprecated */ - static bool isWritable(const char *name); + TAGLIB_DEPRECATED static bool isWritable(const char *name); protected: /*! @@ -240,6 +282,17 @@ namespace TagLib { */ File(FileName file); + /*! + * Construct a File object and use the \a stream instance. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note Constructor is protected since this class should only be + * instantiated through subclasses. + */ + File(IOStream *stream); + /*! * Marks the file as valid or invalid. * @@ -255,7 +308,7 @@ namespace TagLib { /*! * Returns the buffer size that is used for internal buffering. */ - static uint bufferSize(); + static unsigned int bufferSize(); private: File(const File &); diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tfilestream.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tfilestream.cpp new file mode 100644 index 000000000..0ea3be5b1 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tfilestream.cpp @@ -0,0 +1,507 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "tfilestream.h" +#include "tstring.h" +#include "tdebug.h" + +#ifdef _WIN32 +# include <windows.h> +#else +# include <stdio.h> +# include <unistd.h> +#endif + +using namespace TagLib; + +namespace +{ +#ifdef _WIN32 + + // Uses Win32 native API instead of POSIX API to reduce the resource consumption. + + typedef FileName FileNameHandle; + typedef HANDLE FileHandle; + + const FileHandle InvalidFileHandle = INVALID_HANDLE_VALUE; + + FileHandle openFile(const FileName &path, bool readOnly) + { + const DWORD access = readOnly ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE); + +#if defined (PLATFORM_WINRT) + return CreateFile2(path.wstr().c_str(), access, FILE_SHARE_READ, OPEN_EXISTING, NULL); +#else + return CreateFileW(path.wstr().c_str(), access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); +#endif + } + + FileHandle openFile(const int fileDescriptor, bool readOnly) + { + return InvalidFileHandle; + } + + void closeFile(FileHandle file) + { + CloseHandle(file); + } + + size_t readFile(FileHandle file, ByteVector &buffer) + { + DWORD length; + if(ReadFile(file, buffer.data(), static_cast<DWORD>(buffer.size()), &length, NULL)) + return static_cast<size_t>(length); + else + return 0; + } + + size_t writeFile(FileHandle file, const ByteVector &buffer) + { + DWORD length; + if(WriteFile(file, buffer.data(), static_cast<DWORD>(buffer.size()), &length, NULL)) + return static_cast<size_t>(length); + else + return 0; + } + +#else // _WIN32 + + struct FileNameHandle : public std::string + { + FileNameHandle(FileName name) : std::string(name) {} + operator FileName () const { return c_str(); } + }; + + typedef FILE* FileHandle; + + const FileHandle InvalidFileHandle = 0; + + FileHandle openFile(const FileName &path, bool readOnly) + { + return fopen(path, readOnly ? "rb" : "rb+"); + } + + FileHandle openFile(const int fileDescriptor, bool readOnly) + { + return fdopen(fileDescriptor, readOnly ? "rb" : "rb+"); + } + + void closeFile(FileHandle file) + { + fclose(file); + } + + size_t readFile(FileHandle file, ByteVector &buffer) + { + return fread(buffer.data(), sizeof(char), buffer.size(), file); + } + + size_t writeFile(FileHandle file, const ByteVector &buffer) + { + return fwrite(buffer.data(), sizeof(char), buffer.size(), file); + } + +#endif // _WIN32 +} + +class FileStream::FileStreamPrivate +{ +public: + FileStreamPrivate(const FileName &fileName) + : file(InvalidFileHandle) + , name(fileName) + , readOnly(true) + { + } + + FileHandle file; + FileNameHandle name; + bool readOnly; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +FileStream::FileStream(FileName fileName, bool openReadOnly) + : d(new FileStreamPrivate(fileName)) +{ + // First try with read / write mode, if that fails, fall back to read only. + + if(!openReadOnly) + d->file = openFile(fileName, false); + + if(d->file != InvalidFileHandle) + d->readOnly = false; + else + d->file = openFile(fileName, true); + + if(d->file == InvalidFileHandle) +# ifdef _WIN32 + debug("Could not open file " + fileName.toString()); +# else + debug("Could not open file " + String(static_cast<const char *>(d->name))); +# endif +} + +FileStream::FileStream(int fileDescriptor, bool openReadOnly) + : d(new FileStreamPrivate("")) +{ + // First try with read / write mode, if that fails, fall back to read only. + + if(!openReadOnly) + d->file = openFile(fileDescriptor, false); + + if(d->file != InvalidFileHandle) + d->readOnly = false; + else + d->file = openFile(fileDescriptor, true); + + if(d->file == InvalidFileHandle) + debug("Could not open file using file descriptor"); +} + +FileStream::~FileStream() +{ + if(isOpen()) + closeFile(d->file); + + delete d; +} + +FileName FileStream::name() const +{ + return d->name; +} + +ByteVector FileStream::readBlock(unsigned long length) +{ + if(!isOpen()) { + debug("FileStream::readBlock() -- invalid file."); + return ByteVector(); + } + + if(length == 0) + return ByteVector(); + + const unsigned long streamLength = static_cast<unsigned long>(FileStream::length()); + if(length > bufferSize() && length > streamLength) + length = streamLength; + + ByteVector buffer(static_cast<unsigned int>(length)); + + const size_t count = readFile(d->file, buffer); + buffer.resize(static_cast<unsigned int>(count)); + + return buffer; +} + +void FileStream::writeBlock(const ByteVector &data) +{ + if(!isOpen()) { + debug("FileStream::writeBlock() -- invalid file."); + return; + } + + if(readOnly()) { + debug("FileStream::writeBlock() -- read only file."); + return; + } + + writeFile(d->file, data); +} + +void FileStream::insert(const ByteVector &data, unsigned long start, unsigned long replace) +{ + if(!isOpen()) { + debug("FileStream::insert() -- invalid file."); + return; + } + + if(readOnly()) { + debug("FileStream::insert() -- read only file."); + return; + } + + if(data.size() == replace) { + seek(start); + writeBlock(data); + return; + } + else if(data.size() < replace) { + seek(start); + writeBlock(data); + removeBlock(start + data.size(), replace - data.size()); + return; + } + + // Woohoo! Faster (about 20%) than id3lib at last. I had to get hardcore + // and avoid TagLib's high level API for rendering just copying parts of + // the file that don't contain tag data. + // + // Now I'll explain the steps in this ugliness: + + // First, make sure that we're working with a buffer that is longer than + // the *difference* in the tag sizes. We want to avoid overwriting parts + // that aren't yet in memory, so this is necessary. + + unsigned long bufferLength = bufferSize(); + + while(data.size() - replace > bufferLength) + bufferLength += bufferSize(); + + // Set where to start the reading and writing. + + long readPosition = start + replace; + long writePosition = start; + + ByteVector buffer = data; + ByteVector aboutToOverwrite(static_cast<unsigned int>(bufferLength)); + + while(true) { + // Seek to the current read position and read the data that we're about + // to overwrite. Appropriately increment the readPosition. + + seek(readPosition); + const unsigned int bytesRead = static_cast<unsigned int>(readFile(d->file, aboutToOverwrite)); + aboutToOverwrite.resize(bytesRead); + readPosition += bufferLength; + + // Check to see if we just read the last block. We need to call clear() + // if we did so that the last write succeeds. + + if(bytesRead < bufferLength) + clear(); + + // Seek to the write position and write our buffer. Increment the + // writePosition. + + seek(writePosition); + writeBlock(buffer); + + // We hit the end of the file. + + if(bytesRead == 0) + break; + + writePosition += buffer.size(); + + // Make the current buffer the data that we read in the beginning. + + buffer = aboutToOverwrite; + } +} + +void FileStream::removeBlock(unsigned long start, unsigned long length) +{ + if(!isOpen()) { + debug("FileStream::removeBlock() -- invalid file."); + return; + } + + unsigned long bufferLength = bufferSize(); + + long readPosition = start + length; + long writePosition = start; + + ByteVector buffer(static_cast<unsigned int>(bufferLength)); + + for(unsigned int bytesRead = -1; bytesRead != 0;) { + seek(readPosition); + bytesRead = static_cast<unsigned int>(readFile(d->file, buffer)); + readPosition += bytesRead; + + // Check to see if we just read the last block. We need to call clear() + // if we did so that the last write succeeds. + + if(bytesRead < buffer.size()) { + clear(); + buffer.resize(bytesRead); + } + + seek(writePosition); + writeFile(d->file, buffer); + + writePosition += bytesRead; + } + + truncate(writePosition); +} + +bool FileStream::readOnly() const +{ + return d->readOnly; +} + +bool FileStream::isOpen() const +{ + return (d->file != InvalidFileHandle); +} + +void FileStream::seek(long offset, Position p) +{ + if(!isOpen()) { + debug("FileStream::seek() -- invalid file."); + return; + } + +#ifdef _WIN32 + + if(p != Beginning && p != Current && p != End) { + debug("FileStream::seek() -- Invalid Position value."); + return; + } + + LARGE_INTEGER liOffset; + liOffset.QuadPart = offset; + + if(!SetFilePointerEx(d->file, liOffset, NULL, static_cast<DWORD>(p))) { + debug("FileStream::seek() -- Failed to set the file pointer."); + } + +#else + + int whence; + switch(p) { + case Beginning: + whence = SEEK_SET; + break; + case Current: + whence = SEEK_CUR; + break; + case End: + whence = SEEK_END; + break; + default: + debug("FileStream::seek() -- Invalid Position value."); + return; + } + + fseek(d->file, offset, whence); + +#endif +} + +void FileStream::clear() +{ +#ifdef _WIN32 + + // NOP + +#else + + clearerr(d->file); + +#endif +} + +long FileStream::tell() const +{ +#ifdef _WIN32 + + const LARGE_INTEGER zero = {}; + LARGE_INTEGER position; + + if(SetFilePointerEx(d->file, zero, &position, FILE_CURRENT) && + position.QuadPart <= LONG_MAX) { + return static_cast<long>(position.QuadPart); + } + else { + debug("FileStream::tell() -- Failed to get the file pointer."); + return 0; + } + +#else + + return ftell(d->file); + +#endif +} + +long FileStream::length() +{ + if(!isOpen()) { + debug("FileStream::length() -- invalid file."); + return 0; + } + +#ifdef _WIN32 + + LARGE_INTEGER fileSize; + + if(GetFileSizeEx(d->file, &fileSize) && fileSize.QuadPart <= LONG_MAX) { + return static_cast<long>(fileSize.QuadPart); + } + else { + debug("FileStream::length() -- Failed to get the file size."); + return 0; + } + +#else + + const long curpos = tell(); + + seek(0, End); + const long endpos = tell(); + + seek(curpos, Beginning); + + return endpos; + +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void FileStream::truncate(long length) +{ +#ifdef _WIN32 + + const long currentPos = tell(); + + seek(length); + + if(!SetEndOfFile(d->file)) { + debug("FileStream::truncate() -- Failed to truncate the file."); + } + + seek(currentPos); + +#else + + fflush(d->file); + const int error = ftruncate(fileno(d->file), length); + if(error != 0) + debug("FileStream::truncate() -- Couldn't truncate the file."); + +#endif +} + +unsigned int FileStream::bufferSize() +{ + return 1024; +} diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tfilestream.h b/Frameworks/TagLib/taglib/taglib/toolkit/tfilestream.h new file mode 100644 index 000000000..aa4d71b30 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tfilestream.h @@ -0,0 +1,159 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_FILESTREAM_H +#define TAGLIB_FILESTREAM_H + +#include "taglib_export.h" +#include "taglib.h" +#include "tbytevector.h" +#include "tiostream.h" + +namespace TagLib { + + class String; + class Tag; + class AudioProperties; + + //! A file class with some useful methods for tag manipulation + + /*! + * This class is a basic file class with some methods that are particularly + * useful for tag editors. It has methods to take advantage of + * ByteVector and a binary search method for finding patterns in a file. + */ + + class TAGLIB_EXPORT FileStream : public IOStream + { + public: + /*! + * Construct a File object and opens the \a file. \a file should be a + * be a C-string in the local file system encoding. + */ + FileStream(FileName file, bool openReadOnly = false); + + /*! + * Construct a File object and opens the \a file using file descriptor. + */ + FileStream(int fileDescriptor, bool openReadOnly = false); + + /*! + * Destroys this FileStream instance. + */ + virtual ~FileStream(); + + /*! + * Returns the file name in the local file system encoding. + */ + FileName name() const; + + /*! + * Reads a block of size \a length at the current get pointer. + */ + ByteVector readBlock(unsigned long length); + + /*! + * Attempts to write the block \a data at the current get pointer. If the + * file is currently only opened read only -- i.e. readOnly() returns true -- + * this attempts to reopen the file in read/write mode. + * + * \note This should be used instead of using the streaming output operator + * for a ByteVector. And even this function is significantly slower than + * doing output with a char[]. + */ + void writeBlock(const ByteVector &data); + + /*! + * Insert \a data at position \a start in the file overwriting \a replace + * bytes of the original content. + * + * \note This method is slow since it requires rewriting all of the file + * after the insertion point. + */ + void insert(const ByteVector &data, unsigned long start = 0, unsigned long replace = 0); + + /*! + * Removes a block of the file starting a \a start and continuing for + * \a length bytes. + * + * \note This method is slow since it involves rewriting all of the file + * after the removed portion. + */ + void removeBlock(unsigned long start = 0, unsigned long length = 0); + + /*! + * Returns true if the file is read only (or if the file can not be opened). + */ + bool readOnly() const; + + /*! + * Since the file can currently only be opened as an argument to the + * constructor (sort-of by design), this returns if that open succeeded. + */ + bool isOpen() const; + + /*! + * Move the I/O pointer to \a offset in the file from position \a p. This + * defaults to seeking from the beginning of the file. + * + * \see Position + */ + void seek(long offset, Position p = Beginning); + + /*! + * Reset the end-of-file and error flags on the file. + */ + void clear(); + + /*! + * Returns the current offset within the file. + */ + long tell() const; + + /*! + * Returns the length of the file. + */ + long length(); + + /*! + * Truncates the file to a \a length. + */ + void truncate(long length); + + protected: + + /*! + * Returns the buffer size that is used for internal buffering. + */ + static unsigned int bufferSize(); + + private: + class FileStreamPrivate; + FileStreamPrivate *d; + }; + +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tiostream.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tiostream.cpp new file mode 100644 index 000000000..de0bd5053 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tiostream.cpp @@ -0,0 +1,114 @@ +/*************************************************************************** + copyright : (C) 2011 by Lukas Lalinsky + email : lalinsky@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifdef _WIN32 +# include <windows.h> +# include <tstring.h> +#endif + +#include "tiostream.h" + +using namespace TagLib; + +#ifdef _WIN32 + +namespace +{ + std::wstring ansiToUnicode(const char *str) + { + const int len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); + if(len == 0) + return std::wstring(); + + std::wstring wstr(len - 1, L'\0'); + MultiByteToWideChar(CP_ACP, 0, str, -1, &wstr[0], len); + + return wstr; + } +} + +// m_name is no longer used, but kept for backward compatibility. + +FileName::FileName(const wchar_t *name) : + m_name(), + m_wname(name) +{ +} + +FileName::FileName(const char *name) : + m_name(), + m_wname(ansiToUnicode(name)) +{ +} + +FileName::FileName(const FileName &name) : + m_name(), + m_wname(name.m_wname) +{ +} + +FileName::operator const wchar_t *() const +{ + return m_wname.c_str(); +} + +FileName::operator const char *() const +{ + return m_name.c_str(); +} + +const std::wstring &FileName::wstr() const +{ + return m_wname; +} + +const std::string &FileName::str() const +{ + return m_name; +} + +String FileName::toString() const +{ + return String(m_wname.c_str()); +} + +#endif // _WIN32 + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +IOStream::IOStream() +{ +} + +IOStream::~IOStream() +{ +} + +void IOStream::clear() +{ +} + diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tiostream.h b/Frameworks/TagLib/taglib/taglib/toolkit/tiostream.h new file mode 100644 index 000000000..110531644 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tiostream.h @@ -0,0 +1,170 @@ +/*************************************************************************** + copyright : (C) 2011 by Lukas Lalinsky + email : lalinsky@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_IOSTREAM_H +#define TAGLIB_IOSTREAM_H + +#include "taglib_export.h" +#include "taglib.h" +#include "tbytevector.h" + +namespace TagLib { + +#ifdef _WIN32 + class TAGLIB_EXPORT FileName + { + public: + FileName(const wchar_t *name); + FileName(const char *name); + + FileName(const FileName &name); + + operator const wchar_t *() const; + operator const char *() const; + + const std::wstring &wstr() const; + const std::string &str() const; + + String toString() const; + + private: + const std::string m_name; + const std::wstring m_wname; + }; +#else + typedef const char *FileName; +#endif + + //! An abstract class that provides operations on a sequence of bytes + + class TAGLIB_EXPORT IOStream + { + public: + /*! + * Position in the file used for seeking. + */ + enum Position { + //! Seek from the beginning of the file. + Beginning, + //! Seek from the current position in the file. + Current, + //! Seek from the end of the file. + End + }; + + IOStream(); + + /*! + * Destroys this IOStream instance. + */ + virtual ~IOStream(); + + /*! + * Returns the stream name in the local file system encoding. + */ + virtual FileName name() const = 0; + + /*! + * Reads a block of size \a length at the current get pointer. + */ + virtual ByteVector readBlock(unsigned long length) = 0; + + /*! + * Attempts to write the block \a data at the current get pointer. If the + * file is currently only opened read only -- i.e. readOnly() returns true -- + * this attempts to reopen the file in read/write mode. + * + * \note This should be used instead of using the streaming output operator + * for a ByteVector. And even this function is significantly slower than + * doing output with a char[]. + */ + virtual void writeBlock(const ByteVector &data) = 0; + + /*! + * Insert \a data at position \a start in the file overwriting \a replace + * bytes of the original content. + * + * \note This method is slow since it requires rewriting all of the file + * after the insertion point. + */ + virtual void insert(const ByteVector &data, + unsigned long start = 0, unsigned long replace = 0) = 0; + + /*! + * Removes a block of the file starting a \a start and continuing for + * \a length bytes. + * + * \note This method is slow since it involves rewriting all of the file + * after the removed portion. + */ + virtual void removeBlock(unsigned long start = 0, unsigned long length = 0) = 0; + + /*! + * Returns true if the file is read only (or if the file can not be opened). + */ + virtual bool readOnly() const = 0; + + /*! + * Since the file can currently only be opened as an argument to the + * constructor (sort-of by design), this returns if that open succeeded. + */ + virtual bool isOpen() const = 0; + + /*! + * Move the I/O pointer to \a offset in the stream from position \a p. This + * defaults to seeking from the beginning of the stream. + * + * \see Position + */ + virtual void seek(long offset, Position p = Beginning) = 0; + + /*! + * Reset the end-of-stream and error flags on the stream. + */ + virtual void clear(); + + /*! + * Returns the current offset within the stream. + */ + virtual long tell() const = 0; + + /*! + * Returns the length of the stream. + */ + virtual long length() = 0; + + /*! + * Truncates the stream to a \a length. + */ + virtual void truncate(long length) = 0; + + private: + IOStream(const IOStream &); + IOStream &operator=(const IOStream &); + }; + +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tlist.h b/Frameworks/TagLib/taglib/taglib/toolkit/tlist.h index dce0e1c69..377b82481 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tlist.h +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tlist.h @@ -72,7 +72,7 @@ namespace TagLib { /*! * Destroys this List instance. If auto deletion is enabled and this list - * contains a pointer type all of the memebers are also deleted. + * contains a pointer type all of the members are also deleted. */ virtual ~List(); @@ -146,8 +146,16 @@ namespace TagLib { /*! * Returns the number of elements in the list. + * + * \see isEmpty() + */ + unsigned int size() const; + + /*! + * Returns whether or not the list is empty. + * + * \see size() */ - uint size() const; bool isEmpty() const; /*! @@ -205,14 +213,14 @@ namespace TagLib { * * \warning This method is slow. Use iterators to loop through the list. */ - T &operator[](uint i); + T &operator[](unsigned int i); /*! * Returns a const reference to item \a i in the list. * * \warning This method is slow. Use iterators to loop through the list. */ - const T &operator[](uint i) const; + const T &operator[](unsigned int i) const; /*! * Make a shallow, implicitly shared, copy of \a l. Because this is @@ -221,12 +229,22 @@ namespace TagLib { */ List<T> &operator=(const List<T> &l); + /*! + * Exchanges the content of this list by the content of \a l. + */ + void swap(List<T> &l); + /*! * Compares this list with \a l and returns true if all of the elements are * the same. */ bool operator==(const List<T> &l) const; + /*! + * Compares this list with \a l and returns true if the lists differ. + */ + bool operator!=(const List<T> &l) const; + protected: /* * If this List is being shared via implicit sharing, do a deep copy of the diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tlist.tcc b/Frameworks/TagLib/taglib/taglib/toolkit/tlist.tcc index a11887d85..478f09834 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tlist.tcc +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tlist.tcc @@ -24,6 +24,7 @@ ***************************************************************************/ #include <algorithm> +#include "trefcounter.h" namespace TagLib { @@ -38,7 +39,8 @@ namespace TagLib { // A base for the generic and specialized private class types. New // non-templatized members should be added here. -class ListPrivateBase : public RefCounter +// BIC change to RefCounter +class ListPrivateBase : public RefCounterOld { public: ListPrivateBase() : autoDelete(false) {} @@ -87,9 +89,9 @@ public: //////////////////////////////////////////////////////////////////////////////// template <class T> -List<T>::List() +List<T>::List() : + d(new ListPrivate<T>()) { - d = new ListPrivate<T>; } template <class T> @@ -192,9 +194,9 @@ List<T> &List<T>::clear() } template <class T> -TagLib::uint List<T>::size() const +unsigned int List<T>::size() const { - return d->list.size(); + return static_cast<unsigned int>(d->list.size()); } template <class T> @@ -206,6 +208,7 @@ bool List<T>::isEmpty() const template <class T> typename List<T>::Iterator List<T>::find(const T &value) { + detach(); return std::find(d->list.begin(), d->list.end(), value); } @@ -260,23 +263,19 @@ T &List<T>::back() } template <class T> -T &List<T>::operator[](uint i) +T &List<T>::operator[](unsigned int i) { Iterator it = d->list.begin(); - - for(uint j = 0; j < i; j++) - ++it; + std::advance(it, i); return *it; } template <class T> -const T &List<T>::operator[](uint i) const +const T &List<T>::operator[](unsigned int i) const { ConstIterator it = d->list.begin(); - - for(uint j = 0; j < i; j++) - ++it; + std::advance(it, i); return *it; } @@ -284,22 +283,30 @@ const T &List<T>::operator[](uint i) const template <class T> List<T> &List<T>::operator=(const List<T> &l) { - if(&l == this) - return *this; - - if(d->deref()) - delete d; - d = l.d; - d->ref(); + List<T>(l).swap(*this); return *this; } +template <class T> +void List<T>::swap(List<T> &l) +{ + using std::swap; + + swap(d, l.d); +} + template <class T> bool List<T>::operator==(const List<T> &l) const { return d->list == l.d->list; } +template <class T> +bool List<T>::operator!=(const List<T> &l) const +{ + return d->list != l.d->list; +} + //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tmap.h b/Frameworks/TagLib/taglib/taglib/toolkit/tmap.h index f2f8364cd..f54e5a2a9 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tmap.h +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tmap.h @@ -27,7 +27,6 @@ #define TAGLIB_MAP_H #include <map> -using namespace std; #include "taglib.h" @@ -120,7 +119,7 @@ namespace TagLib { * * \see isEmpty() */ - uint size() const; + unsigned int size() const; /*! * Returns true if the map is empty. @@ -175,6 +174,11 @@ namespace TagLib { */ Map<Key, T> &operator=(const Map<Key, T> &m); + /*! + * Exchanges the content of this map by the content of \a m. + */ + void swap(Map<Key, T> &m); + protected: /* * If this List is being shared via implicit sharing, do a deep copy of the diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tmap.tcc b/Frameworks/TagLib/taglib/taglib/toolkit/tmap.tcc index 0f2b99332..2e4ed5ebf 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tmap.tcc +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tmap.tcc @@ -23,31 +23,34 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include "trefcounter.h" + namespace TagLib { //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// +// BIC change to RefCounter template <class Key, class T> template <class KeyP, class TP> -class Map<Key, T>::MapPrivate : public RefCounter +class Map<Key, T>::MapPrivate : public RefCounterOld { public: - MapPrivate() : RefCounter() {} + MapPrivate() : RefCounterOld() {} #ifdef WANT_CLASS_INSTANTIATION_OF_MAP - MapPrivate(const std::map<class KeyP, class TP>& m) : RefCounter(), map(m) {} + MapPrivate(const std::map<class KeyP, class TP>& m) : RefCounterOld(), map(m) {} std::map<class KeyP, class TP> map; #else - MapPrivate(const std::map<KeyP, TP>& m) : RefCounter(), map(m) {} + MapPrivate(const std::map<KeyP, TP>& m) : RefCounterOld(), map(m) {} std::map<KeyP, TP> map; #endif }; template <class Key, class T> -Map<Key, T>::Map() +Map<Key, T>::Map() : + d(new MapPrivate<Key, T>()) { - d = new MapPrivate<Key, T>; } template <class Key, class T> @@ -142,16 +145,14 @@ template <class Key, class T> Map<Key, T> &Map<Key,T>::erase(const Key &key) { detach(); - Iterator it = d->map.find(key); - if(it != d->map.end()) - d->map.erase(it); + d->map.erase(key); return *this; } template <class Key, class T> -TagLib::uint Map<Key, T>::size() const +unsigned int Map<Key, T>::size() const { - return d->map.size(); + return static_cast<unsigned int>(d->map.size()); } template <class Key, class T> @@ -170,16 +171,18 @@ T &Map<Key, T>::operator[](const Key &key) template <class Key, class T> Map<Key, T> &Map<Key, T>::operator=(const Map<Key, T> &m) { - if(&m == this) - return *this; - - if(d->deref()) - delete(d); - d = m.d; - d->ref(); + Map<Key, T>(m).swap(*this); return *this; } +template <class Key, class T> +void Map<Key, T>::swap(Map<Key, T> &m) +{ + using std::swap; + + swap(d, m.d); +} + //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tpropertymap.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tpropertymap.cpp new file mode 100644 index 000000000..b3e1ec3ad --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tpropertymap.cpp @@ -0,0 +1,179 @@ +/*************************************************************************** + copyright : (C) 2012 by Michael Helmling + email : helmling@mathematik.uni-kl.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include "tpropertymap.h" +using namespace TagLib; + + +PropertyMap::PropertyMap() : SimplePropertyMap() +{ +} + +PropertyMap::PropertyMap(const PropertyMap &m) : SimplePropertyMap(m), unsupported(m.unsupported) +{ +} + +PropertyMap::PropertyMap(const SimplePropertyMap &m) +{ + for(SimplePropertyMap::ConstIterator it = m.begin(); it != m.end(); ++it){ + String key = it->first.upper(); + if(!key.isEmpty()) + insert(it->first, it->second); + else + unsupported.append(it->first); + } +} + +PropertyMap::~PropertyMap() +{ +} + +bool PropertyMap::insert(const String &key, const StringList &values) +{ + String realKey = key.upper(); + Iterator result = SimplePropertyMap::find(realKey); + if(result == end()) + SimplePropertyMap::insert(realKey, values); + else + SimplePropertyMap::operator[](realKey).append(values); + return true; +} + +bool PropertyMap::replace(const String &key, const StringList &values) +{ + String realKey = key.upper(); + SimplePropertyMap::erase(realKey); + SimplePropertyMap::insert(realKey, values); + return true; +} + +PropertyMap::Iterator PropertyMap::find(const String &key) +{ + return SimplePropertyMap::find(key.upper()); +} + +PropertyMap::ConstIterator PropertyMap::find(const String &key) const +{ + return SimplePropertyMap::find(key.upper()); +} + +bool PropertyMap::contains(const String &key) const +{ + return SimplePropertyMap::contains(key.upper()); +} + +bool PropertyMap::contains(const PropertyMap &other) const +{ + for(ConstIterator it = other.begin(); it != other.end(); ++it) { + if(!SimplePropertyMap::contains(it->first)) + return false; + if ((*this)[it->first] != it->second) + return false; + } + return true; +} + +PropertyMap &PropertyMap::erase(const String &key) +{ + SimplePropertyMap::erase(key.upper()); + return *this; +} + +PropertyMap &PropertyMap::erase(const PropertyMap &other) +{ + for(ConstIterator it = other.begin(); it != other.end(); ++it) + erase(it->first); + return *this; +} + +PropertyMap &PropertyMap::merge(const PropertyMap &other) +{ + for(PropertyMap::ConstIterator it = other.begin(); it != other.end(); ++it) + insert(it->first, it->second); + unsupported.append(other.unsupported); + return *this; +} + +const StringList &PropertyMap::operator[](const String &key) const +{ + return SimplePropertyMap::operator[](key.upper()); +} + +StringList &PropertyMap::operator[](const String &key) +{ + return SimplePropertyMap::operator[](key.upper()); +} + +bool PropertyMap::operator==(const PropertyMap &other) const +{ + for(ConstIterator it = other.begin(); it != other.end(); ++it) { + ConstIterator thisFind = find(it->first); + if( thisFind == end() || (thisFind->second != it->second) ) + return false; + } + for(ConstIterator it = begin(); it != end(); ++it) { + ConstIterator otherFind = other.find(it->first); + if( otherFind == other.end() || (otherFind->second != it->second) ) + return false; + } + return unsupported == other.unsupported; +} + +bool PropertyMap::operator!=(const PropertyMap &other) const +{ + return !(*this == other); +} + +String PropertyMap::toString() const +{ + String ret; + + for(ConstIterator it = begin(); it != end(); ++it) + ret += it->first+"="+it->second.toString(", ") + "\n"; + if(!unsupported.isEmpty()) + ret += "Unsupported Data: " + unsupported.toString(", ") + "\n"; + return ret; +} + +void PropertyMap::removeEmpty() +{ + PropertyMap m; + for(ConstIterator it = begin(); it != end(); ++it) { + if(!it->second.isEmpty()) + m.insert(it->first, it->second); + } + *this = m; +} + +StringList &PropertyMap::unsupportedData() +{ + return unsupported; +} + +const StringList &PropertyMap::unsupportedData() const +{ + return unsupported; +} diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tpropertymap.h b/Frameworks/TagLib/taglib/taglib/toolkit/tpropertymap.h new file mode 100644 index 000000000..d491efe8c --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tpropertymap.h @@ -0,0 +1,242 @@ +/*************************************************************************** + copyright : (C) 2012 by Michael Helmling + email : helmling@mathematik.uni-kl.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_PROPERTYMAP_H_ +#define TAGLIB_PROPERTYMAP_H_ + +#include "tmap.h" +#include "tstringlist.h" + +namespace TagLib { + + typedef Map<String,StringList> SimplePropertyMap; + + //! A map for format-independent <key,valuelist> tag representations. + + /*! + * This map implements a generic representation of textual audio metadata + * ("tags") realized as pairs of a case-insensitive key + * and a nonempty list of corresponding values, each value being an arbitrary + * unicode String. + * + * Note that most metadata formats pose additional conditions on the tag keys. The + * most popular ones (Vorbis, APE, ID3v2) should support all ASCII only words of + * length between 2 and 16. + * + * This class can contain any tags, but here is a list of "well-known" tags that + * you might want to use: + * + * Basic tags: + * + * - TITLE + * - ALBUM + * - ARTIST + * - ALBUMARTIST + * - SUBTITLE + * - TRACKNUMBER + * - DISCNUMBER + * - DATE + * - ORIGINALDATE + * - GENRE + * - COMMENT + * + * Sort names: + * + * - TITLESORT + * - ALBUMSORT + * - ARTISTSORT + * - ALBUMARTISTSORT + * - COMPOSERSORT + * + * Credits: + * + * - COMPOSER + * - LYRICIST + * - CONDUCTOR + * - REMIXER + * - PERFORMER:<XXXX> + * + * Other tags: + * + * - ISRC + * - ASIN + * - BPM + * - COPYRIGHT + * - ENCODEDBY + * - MOOD + * - COMMENT + * - MEDIA + * - LABEL + * - CATALOGNUMBER + * - BARCODE + * - RELEASECOUNTRY + * - RELEASESTATUS + * - RELEASETYPE + * + * MusicBrainz identifiers: + * + * - MUSICBRAINZ_TRACKID + * - MUSICBRAINZ_ALBUMID + * - MUSICBRAINZ_RELEASEGROUPID + * - MUSICBRAINZ_RELEASETRACKID + * - MUSICBRAINZ_WORKID + * - MUSICBRAINZ_ARTISTID + * - MUSICBRAINZ_ALBUMARTISTID + * - ACOUSTID_ID + * - ACOUSTID_FINGERPRINT + * - MUSICIP_PUID + * + */ + + class TAGLIB_EXPORT PropertyMap: public SimplePropertyMap + { + public: + + typedef SimplePropertyMap::Iterator Iterator; + typedef SimplePropertyMap::ConstIterator ConstIterator; + + PropertyMap(); + + PropertyMap(const PropertyMap &m); + + /*! + * Creates a PropertyMap initialized from a SimplePropertyMap. Copies all + * entries from \a m that have valid keys. + * Invalid keys will be appended to the unsupportedData() list. + */ + PropertyMap(const SimplePropertyMap &m); + + virtual ~PropertyMap(); + + /*! + * Inserts \a values under \a key in the map. If \a key already exists, + * then \a values will be appended to the existing StringList. + * The returned value indicates success, i.e. whether \a key is a + * valid key. + */ + bool insert(const String &key, const StringList &values); + + /*! + * Replaces any existing values for \a key with the given \a values, + * and simply insert them if \a key did not exist before. + * The returned value indicates success, i.e. whether \a key is a + * valid key. + */ + bool replace(const String &key, const StringList &values); + + /*! + * Find the first occurrence of \a key. + */ + Iterator find(const String &key); + + /*! + * Find the first occurrence of \a key. + */ + ConstIterator find(const String &key) const; + + /*! + * Returns true if the map contains values for \a key. + */ + bool contains(const String &key) const; + + /*! + * Returns true if this map contains all keys of \a other + * and the values coincide for that keys. Does not take + * the unsupportedData list into account. + */ + bool contains(const PropertyMap &other) const; + + /*! + * Erase the \a key and its values from the map. + */ + PropertyMap &erase(const String &key); + + /*! + * Erases from this map all keys that appear in \a other. + */ + PropertyMap &erase(const PropertyMap &other); + + /*! + * Merge the contents of \a other into this PropertyMap. + * If a key is contained in both maps, the values of the second + * are appended to that of the first. + * The unsupportedData() lists are concatenated as well. + */ + PropertyMap &merge(const PropertyMap &other); + + /*! + * Returns a reference to the value associated with \a key. + * + * \note: If \a key is not contained in the map, an empty + * StringList is returned without error. + */ + const StringList &operator[](const String &key) const; + + /*! + * Returns a reference to the value associated with \a key. + * + * \note: If \a key is not contained in the map, an empty + * StringList is returned. You can also directly add entries + * by using this function as an lvalue. + */ + StringList &operator[](const String &key); + + /*! + * Returns true if and only if \other has the same contents as this map. + */ + bool operator==(const PropertyMap &other) const; + + /*! + * Returns false if and only \other has the same contents as this map. + */ + bool operator!=(const PropertyMap &other) const; + + /*! + * If a PropertyMap is read from a File object using File::properties(), + * the StringList returned from this function will represent metadata + * that could not be parsed into the PropertyMap representation. This could + * be e.g. binary data, unknown ID3 frames, etc. + * You can remove items from the returned list, which tells TagLib to remove + * those unsupported elements if you call File::setProperties() with the + * same PropertyMap as argument. + */ + StringList &unsupportedData(); + const StringList &unsupportedData() const; + + /*! + * Removes all entries which have an empty value list. + */ + void removeEmpty(); + + String toString() const; + + private: + + + StringList unsupported; + }; + +} +#endif /* TAGLIB_PROPERTYMAP_H_ */ diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/trefcounter.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/trefcounter.cpp new file mode 100644 index 000000000..18cb596c0 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/trefcounter.cpp @@ -0,0 +1,96 @@ +/*************************************************************************** + copyright : (C) 2013 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "trefcounter.h" + +#if defined(HAVE_GCC_ATOMIC) +# define ATOMIC_INT int +# define ATOMIC_INC(x) __sync_add_and_fetch(&x, 1) +# define ATOMIC_DEC(x) __sync_sub_and_fetch(&x, 1) +#elif defined(HAVE_WIN_ATOMIC) +# if !defined(NOMINMAX) +# define NOMINMAX +# endif +# include <windows.h> +# define ATOMIC_INT long +# define ATOMIC_INC(x) InterlockedIncrement(&x) +# define ATOMIC_DEC(x) InterlockedDecrement(&x) +#elif defined(HAVE_MAC_ATOMIC) +# include <libkern/OSAtomic.h> +# define ATOMIC_INT int32_t +# define ATOMIC_INC(x) OSAtomicIncrement32Barrier(&x) +# define ATOMIC_DEC(x) OSAtomicDecrement32Barrier(&x) +#elif defined(HAVE_IA64_ATOMIC) +# include <ia64intrin.h> +# define ATOMIC_INT int +# define ATOMIC_INC(x) __sync_add_and_fetch(&x, 1) +# define ATOMIC_DEC(x) __sync_sub_and_fetch(&x, 1) +#else +# define ATOMIC_INT volatile int +# define ATOMIC_INC(x) (++x) +# define ATOMIC_DEC(x) (--x) +#endif + +namespace TagLib +{ + + class RefCounter::RefCounterPrivate + { + public: + RefCounterPrivate() : + refCount(1) {} + + volatile ATOMIC_INT refCount; + }; + + RefCounter::RefCounter() : + d(new RefCounterPrivate()) + { + } + + RefCounter::~RefCounter() + { + delete d; + } + + void RefCounter::ref() + { + ATOMIC_INC(d->refCount); + } + + bool RefCounter::deref() + { + return (ATOMIC_DEC(d->refCount) == 0); + } + + int RefCounter::count() const + { + return static_cast<int>(d->refCount); + } +} diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/trefcounter.h b/Frameworks/TagLib/taglib/taglib/toolkit/trefcounter.h new file mode 100644 index 000000000..db97c5385 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/trefcounter.h @@ -0,0 +1,114 @@ +/*************************************************************************** + copyright : (C) 2013 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_REFCOUNTER_H +#define TAGLIB_REFCOUNTER_H + +#include "taglib_export.h" +#include "taglib.h" + +#ifdef __APPLE__ +# define OSATOMIC_DEPRECATED 0 +# include <libkern/OSAtomic.h> +# define TAGLIB_ATOMIC_MAC +#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__) +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include <windows.h> +# define TAGLIB_ATOMIC_WIN +#elif defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 401) \ + && (defined(__i386__) || defined(__i486__) || defined(__i586__) || \ + defined(__i686__) || defined(__x86_64) || defined(__ia64)) \ + && !defined(__INTEL_COMPILER) +# define TAGLIB_ATOMIC_GCC +#elif defined(__ia64) && defined(__INTEL_COMPILER) +# include <ia64intrin.h> +# define TAGLIB_ATOMIC_GCC +#endif + +#ifndef DO_NOT_DOCUMENT // Tell Doxygen to skip this class. +/*! + * \internal + * This is just used as a base class for shared classes in TagLib. + * + * \warning This <b>is not</b> part of the TagLib public API! + */ +namespace TagLib +{ + + class TAGLIB_EXPORT RefCounter + { + public: + RefCounter(); + virtual ~RefCounter(); + + void ref(); + bool deref(); + int count() const; + + private: + class RefCounterPrivate; + RefCounterPrivate *d; + }; + + // BIC this old class is needed by tlist.tcc and tmap.tcc + class RefCounterOld + { + public: + RefCounterOld() : refCount(1) {} + +#ifdef TAGLIB_ATOMIC_MAC + void ref() { OSAtomicIncrement32Barrier(const_cast<int32_t*>(&refCount)); } + bool deref() { return ! OSAtomicDecrement32Barrier(const_cast<int32_t*>(&refCount)); } + int32_t count() { return refCount; } + private: + volatile int32_t refCount; +#elif defined(TAGLIB_ATOMIC_WIN) + void ref() { InterlockedIncrement(&refCount); } + bool deref() { return ! InterlockedDecrement(&refCount); } + long count() { return refCount; } + private: + volatile long refCount; +#elif defined(TAGLIB_ATOMIC_GCC) + void ref() { __sync_add_and_fetch(&refCount, 1); } + bool deref() { return ! __sync_sub_and_fetch(&refCount, 1); } + int count() { return refCount; } + private: + volatile int refCount; +#else + void ref() { refCount++; } + bool deref() { return ! --refCount; } + int count() { return refCount; } + private: + unsigned int refCount; +#endif + }; + +} + +#endif // DO_NOT_DOCUMENT +#endif + diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tstring.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tstring.cpp index b20b2b625..c60a3e2ed 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tstring.cpp +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tstring.cpp @@ -23,237 +23,252 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include <cerrno> +#include <climits> + +#include <utf8-cpp/checked.h> + +#include <tdebug.h> +#include <tstringlist.h> +#include <trefcounter.h> +#include <tutils.h> + #include "tstring.h" -#include "unicode.h" -#include "tdebug.h" -#include <ostream> +namespace +{ + using namespace TagLib; -#include <string.h> -#include <math.h> - -#include <iconv.h> - - -namespace TagLib { - char default_ascii_encoding[] = "latin1"; - char ascii_encoding[256] = ""; - - inline unsigned short byteSwap(unsigned short x) + // Returns the native format of std::wstring. + String::Type wcharByteOrder() { - return (((x) >> 8) & 0xff) | (((x) & 0xff) << 8); + if(Utils::systemByteOrder() == Utils::LittleEndian) + return String::UTF16LE; + else + return String::UTF16BE; } - inline unsigned short combine(unsigned char c1, unsigned char c2) + // Converts a Latin-1 string into UTF-16(without BOM/CPU byte order) + // and copies it to the internal buffer. + void copyFromLatin1(std::wstring &data, const char *s, size_t length) { - return (c1 << 8) | c2; + data.resize(length); + + for(size_t i = 0; i < length; ++i) + data[i] = static_cast<unsigned char>(s[i]); + } + + // Converts a UTF-8 string into UTF-16(without BOM/CPU byte order) + // and copies it to the internal buffer. + void copyFromUTF8(std::wstring &data, const char *s, size_t length) + { + data.resize(length); + + try { + const std::wstring::iterator dstEnd = utf8::utf8to16(s, s + length, data.begin()); + data.resize(dstEnd - data.begin()); + } + catch(const utf8::exception &e) { + const String message(e.what()); + debug("String::copyFromUTF8() - UTF8-CPP error: " + message); + data.clear(); + } + } + + // Helper functions to read a UTF-16 character from an array. + template <typename T> + unsigned short nextUTF16(const T **p); + + template <> + unsigned short nextUTF16<wchar_t>(const wchar_t **p) + { + return static_cast<unsigned short>(*(*p)++); + } + + template <> + unsigned short nextUTF16<char>(const char **p) + { + union { + unsigned short w; + char c[2]; + } u; + u.c[0] = *(*p)++; + u.c[1] = *(*p)++; + return u.w; + } + + // Converts a UTF-16 (with BOM), UTF-16LE or UTF16-BE string into + // UTF-16(without BOM/CPU byte order) and copies it to the internal buffer. + template <typename T> + void copyFromUTF16(std::wstring &data, const T *s, size_t length, String::Type t) + { + bool swap; + if(t == String::UTF16) { + if(length < 1) { + debug("String::copyFromUTF16() - Invalid UTF16 string. Too short to have a BOM."); + return; + } + + const unsigned short bom = nextUTF16(&s); + if(bom == 0xfeff) + swap = false; // Same as CPU endian. No need to swap bytes. + else if(bom == 0xfffe) + swap = true; // Not same as CPU endian. Need to swap bytes. + else { + debug("String::copyFromUTF16() - Invalid UTF16 string. BOM is broken."); + return; + } + + length--; + } + else { + swap = (t != wcharByteOrder()); + } + + data.resize(length); + for(size_t i = 0; i < length; ++i) { + const unsigned short c = nextUTF16(&s); + if(swap) + data[i] = Utils::byteSwap(c); + else + data[i] = c; + } } } -using namespace TagLib; +namespace TagLib { class String::StringPrivate : public RefCounter { public: - StringPrivate(const wstring &s) : - RefCounter(), - data(s), - CString(0) {} - StringPrivate() : - RefCounter(), - CString(0) {} - - ~StringPrivate() { - delete [] CString; - } - - wstring data; + RefCounter() {} /*! - * This is only used to hold the a pointer to the most recent value of - * toCString. + * Stores string in UTF-16. The byte order depends on the CPU endian. */ - char *CString; + TagLib::wstring data; + + /*! + * This is only used to hold the the most recent value of toCString(). + */ + std::string cstring; }; String String::null; +//////////////////////////////////////////////////////////////////////////////// +// public members //////////////////////////////////////////////////////////////////////////////// -String::String() +String::String() : + d(new StringPrivate()) { - d = new StringPrivate; } -String::String(const String &s) : d(s.d) +String::String(const String &s) : + d(s.d) { d->ref(); } -String::String(const std::string &s, Type t) +String::String(const std::string &s, Type t) : + d(new StringPrivate()) { - d = new StringPrivate; + if(t == Latin1) + copyFromLatin1(d->data, s.c_str(), s.length()); + else if(t == String::UTF8) + copyFromUTF8(d->data, s.c_str(), s.length()); + else { + debug("String::String() -- std::string should not contain UTF16."); + } +} +String::String(const wstring &s, Type t) : + d(new StringPrivate()) +{ if(t == UTF16 || t == UTF16BE || t == UTF16LE) { - debug("String::String() -- A std::string should not contain UTF16."); - return; + // This looks ugly but needed for the compatibility with TagLib1.8. + // Should be removed in TabLib2.0. + if (t == UTF16BE) + t = wcharByteOrder(); + else if (t == UTF16LE) + t = (wcharByteOrder() == UTF16LE ? UTF16BE : UTF16LE); + + copyFromUTF16(d->data, s.c_str(), s.length(), t); } - - int length = s.length(); - d->data.resize(length); - wstring::iterator targetIt = d->data.begin(); - - for(std::string::const_iterator it = s.begin(); it != s.end(); it++) { - *targetIt = uchar(*it); - ++targetIt; + else { + debug("String::String() -- TagLib::wstring should not contain Latin1 or UTF-8."); } - - prepare(t); } -String::String(const wstring &s, Type t) +String::String(const wchar_t *s, Type t) : + d(new StringPrivate()) { - d = new StringPrivate(s); - prepare(t); -} - -String::String(const wchar_t *s, Type t) -{ - d = new StringPrivate(s); - prepare(t); -} - -String::String(const char *s, Type t) -{ - d = new StringPrivate; - if(t == UTF16 || t == UTF16BE || t == UTF16LE) { - debug("String::String() -- A const char * should not contain UTF16."); - return; + // This looks ugly but needed for the compatibility with TagLib1.8. + // Should be removed in TabLib2.0. + if (t == UTF16BE) + t = wcharByteOrder(); + else if (t == UTF16LE) + t = (wcharByteOrder() == UTF16LE ? UTF16BE : UTF16LE); + + copyFromUTF16(d->data, s, ::wcslen(s), t); } - - int length = ::strlen(s); - d->data.resize(length); - - wstring::iterator targetIt = d->data.begin(); - - for(int i = 0; i < length; i++) { - *targetIt = uchar(s[i]); - ++targetIt; + else { + debug("String::String() -- const wchar_t * should not contain Latin1 or UTF-8."); } - - prepare(t); } -String::String(wchar_t c, Type t) +String::String(const char *s, Type t) : + d(new StringPrivate()) { - d = new StringPrivate; - d->data += c; - prepare(t); -} - -String::String(char c, Type t) -{ - d = new StringPrivate; - - if(t == UTF16 || t == UTF16BE || t == UTF16LE) { - debug("String::String() -- A std::string should not contain UTF16."); - return; + if(t == Latin1) + copyFromLatin1(d->data, s, ::strlen(s)); + else if(t == String::UTF8) + copyFromUTF8(d->data, s, ::strlen(s)); + else { + debug("String::String() -- const char * should not contain UTF16."); } - - d->data += uchar(c); - prepare(t); } - -String::String(const ByteVector &v, Type t) +String::String(wchar_t c, Type t) : + d(new StringPrivate()) { - d = new StringPrivate; + if(t == UTF16 || t == UTF16BE || t == UTF16LE) + copyFromUTF16(d->data, &c, 1, t); + else { + debug("String::String() -- wchar_t should not contain Latin1 or UTF-8."); + } +} +String::String(char c, Type t) : + d(new StringPrivate()) +{ + if(t == Latin1) + copyFromLatin1(d->data, &c, 1); + else if(t == String::UTF8) + copyFromUTF8(d->data, &c, 1); + else { + debug("String::String() -- char should not contain UTF16."); + } +} + +String::String(const ByteVector &v, Type t) : + d(new StringPrivate()) +{ if(v.isEmpty()) return; - - iconv_t encoder = (iconv_t)-1; - if ( t == Latin1 ) { - encoder = iconv_open("utf-8",(*ascii_encoding)?ascii_encoding:default_ascii_encoding); - if ( encoder == (iconv_t)-1 ) - encoder = iconv_open("utf-8",default_ascii_encoding); - } - - if ( t == Latin1 && encoder != (iconv_t)-1 ) { - size_t srclen = v.size(); - char *src = new char[srclen+1]; - size_t dstlen = v.size()*6; - char *dst = new char[dstlen+1]; - int n=0; - - char *src_param = src; - char *dst_param = dst; - size_t src_remaining = srclen; - size_t dst_remaining = dstlen; - - for(ByteVector::ConstIterator it = v.begin(); it != v.end() && (*it); ++it) - src[n++] = *it; - src[n++] = 0; - - iconv(encoder, &src_param, &src_remaining, &dst_param, &dst_remaining); - t = UTF8; - - int length = 0; - - d->data.resize(dstlen); - wstring::iterator targetIt = d->data.begin(); - for ( int i=0; i<dstlen-dst_remaining; i++ ) { - *targetIt = dst[i]; - ++targetIt; - ++length; - } - - d->data.resize(length); - - delete[] src; - delete[] dst; - - iconv_close(encoder); - - t = UTF8; - } else if ( t == UTF8 || ( t == Latin1 && encoder == (iconv_t)-1) ) { // UTF8 string or encoder failed to start - int length = 0; - d->data.resize(v.size()*2); - wstring::iterator targetIt = d->data.begin(); - for(ByteVector::ConstIterator it = v.begin(); it != v.end() && (*it); ++it) { - *targetIt = uchar(*it); - ++targetIt; - ++length; - } - d->data.resize(length); - } - else { - d->data.resize(v.size() / 2); - wstring::iterator targetIt = d->data.begin(); - - // Cure some faulty UTF16 headers without endianness: insert endianness byte into the beginning of the dst string. - if ( v.size() > 1 ) { - wchar w = combine(v.data()[0], v.data()[1]); - if ( w != 0xfeff && w != 0xfffe ) { - d->data.resize(v.size()/2 + 1); - targetIt = d->data.begin(); - *targetIt = 0xfffe; - ++targetIt; - // String append will continue in the loop below. - } - } - - for(ByteVector::ConstIterator it = v.begin(); - it != v.end() && it + 1 != v.end() && combine(*it, *(it + 1)); - it += 2) - { - *targetIt = combine(*it, *(it + 1)); - ++targetIt; - } - } - prepare(t); + + if(t == Latin1) + copyFromLatin1(d->data, v.data(), v.size()); + else if(t == UTF8) + copyFromUTF8(d->data, v.data(), v.size()); + else + copyFromUTF16(d->data, v.data(), v.size() / 2, t); + + // If we hit a null in the ByteVector, shrink the string again. + d->data.resize(::wcslen(d->data.c_str())); } //////////////////////////////////////////////////////////////////////////////// @@ -266,47 +281,8 @@ String::~String() std::string String::to8Bit(bool unicode) const { - std::string s; - s.resize(d->data.size()); - - if(!unicode) { - std::string::iterator targetIt = s.begin(); - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) { - *targetIt = char(*it); - ++targetIt; - } - return s; - } - - const int outputBufferSize = d->data.size() * 3 + 1; - - Unicode::UTF16 *sourceBuffer = new Unicode::UTF16[d->data.size() + 1]; - Unicode::UTF8 *targetBuffer = new Unicode::UTF8[outputBufferSize]; - - for(unsigned int i = 0; i < d->data.size(); i++) - sourceBuffer[i] = Unicode::UTF16(d->data[i]); - - const Unicode::UTF16 *source = sourceBuffer; - Unicode::UTF8 *target = targetBuffer; - - Unicode::ConversionResult result = - Unicode::ConvertUTF16toUTF8(&source, sourceBuffer + d->data.size(), - &target, targetBuffer + outputBufferSize, - Unicode::lenientConversion); - - if(result != Unicode::conversionOK) - debug("String::to8Bit() - Unicode conversion error."); - - int newSize = target - targetBuffer; - s.resize(newSize); - targetBuffer[newSize] = 0; - - s = (char *) targetBuffer; - - delete [] sourceBuffer; - delete [] targetBuffer; - - return s; + const ByteVector v = data(unicode ? UTF8 : Latin1); + return std::string(v.data(), v.size()); } TagLib::wstring String::toWString() const @@ -316,17 +292,18 @@ TagLib::wstring String::toWString() const const char *String::toCString(bool unicode) const { - delete [] d->CString; + d->cstring = to8Bit(unicode); + return d->cstring.c_str(); +} - std::string buffer = to8Bit(unicode); - d->CString = new char[buffer.size() + 1]; - strcpy(d->CString, buffer.c_str()); - - return d->CString; +const wchar_t *String::toCWString() const +{ + return d->data.c_str(); } String::Iterator String::begin() { + detach(); return d->data.begin(); } @@ -337,6 +314,7 @@ String::ConstIterator String::begin() const String::Iterator String::end() { + detach(); return d->data.end(); } @@ -347,23 +325,29 @@ String::ConstIterator String::end() const int String::find(const String &s, int offset) const { - wstring::size_type position = d->data.find(s.d->data, offset); - - if(position != wstring::npos) - return position; - else - return -1; + return static_cast<int>(d->data.find(s.d->data, offset)); } int String::rfind(const String &s, int offset) const { - wstring::size_type position = - d->data.rfind(s.d->data, offset == -1 ? wstring::npos : offset); + return static_cast<int>(d->data.rfind(s.d->data, offset)); +} - if(position != wstring::npos) - return position; - else - return -1; +StringList String::split(const String &separator) const +{ + StringList list; + for(int index = 0;;) { + int sep = find(separator, index); + if(sep < 0) { + list.append(substr(index, size() - index)); + break; + } + else { + list.append(substr(index, sep - index)); + index = sep + separator.size(); + } + } + return list; } bool String::startsWith(const String &s) const @@ -374,14 +358,12 @@ bool String::startsWith(const String &s) const return substr(0, s.length()) == s; } -String String::substr(uint position, uint n) const +String String::substr(unsigned int position, unsigned int n) const { - if(n > position + d->data.size()) - n = d->data.size() - position; - - String s; - s.d->data = d->data.substr(position, n); - return s; + if(position == 0 && n >= size()) + return *this; + else + return String(d->data.substr(position, n)); } String &String::append(const String &s) @@ -391,15 +373,20 @@ String &String::append(const String &s) return *this; } +String & String::clear() +{ + *this = String(); + return *this; +} + String String::upper() const { String s; + s.d->data.reserve(size()); - static int shift = 'A' - 'a'; - - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); ++it) { + for(ConstIterator it = begin(); it != end(); ++it) { if(*it >= 'a' && *it <= 'z') - s.d->data.push_back(*it + shift); + s.d->data.push_back(*it + 'A' - 'a'); else s.d->data.push_back(*it); } @@ -407,19 +394,19 @@ String String::upper() const return s; } -TagLib::uint String::size() const +unsigned int String::size() const { - return d->data.size(); + return static_cast<unsigned int>(d->data.size()); } -TagLib::uint String::length() const +unsigned int String::length() const { return size(); } bool String::isEmpty() const { - return d->data.size() == 0; + return d->data.empty(); } bool String::isNull() const @@ -429,67 +416,81 @@ bool String::isNull() const ByteVector String::data(Type t) const { - ByteVector v; - - switch(t) { - + switch(t) + { case Latin1: - { - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) - v.append(char(*it)); - break; - } + { + ByteVector v(size(), 0); + char *p = v.data(); + + for(ConstIterator it = begin(); it != end(); ++it) + *p++ = static_cast<char>(*it); + + return v; + } case UTF8: - { - std::string s = to8Bit(true); - v.setData(s.c_str(), s.length()); - break; - } + { + ByteVector v(size() * 4, 0); + + try { + const ByteVector::Iterator dstEnd = utf8::utf16to8(begin(), end(), v.begin()); + v.resize(static_cast<unsigned int>(dstEnd - v.begin())); + } + catch(const utf8::exception &e) { + const String message(e.what()); + debug("String::data() - UTF8-CPP error: " + message); + v.clear(); + } + + return v; + } case UTF16: - { - // Assume that if we're doing UTF16 and not UTF16BE that we want little - // endian encoding. (Byte Order Mark) + { + ByteVector v(2 + size() * 2, 0); + char *p = v.data(); - v.append(char(0xff)); - v.append(char(0xfe)); + // We use little-endian encoding here and need a BOM. - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) { + *p++ = '\xff'; + *p++ = '\xfe'; - char c1 = *it & 0xff; - char c2 = *it >> 8; + for(ConstIterator it = begin(); it != end(); ++it) { + *p++ = static_cast<char>(*it & 0xff); + *p++ = static_cast<char>(*it >> 8); + } - v.append(c1); - v.append(c2); + return v; } - break; - } case UTF16BE: - { - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) { + { + ByteVector v(size() * 2, 0); + char *p = v.data(); - char c1 = *it >> 8; - char c2 = *it & 0xff; + for(ConstIterator it = begin(); it != end(); ++it) { + *p++ = static_cast<char>(*it >> 8); + *p++ = static_cast<char>(*it & 0xff); + } - v.append(c1); - v.append(c2); + return v; } - break; - } case UTF16LE: - { - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) { + { + ByteVector v(size() * 2, 0); + char *p = v.data(); - char c1 = *it & 0xff; - char c2 = *it >> 8; + for(ConstIterator it = begin(); it != end(); ++it) { + *p++ = static_cast<char>(*it & 0xff); + *p++ = static_cast<char>(*it >> 8); + } - v.append(c1); - v.append(c2); + return v; + } + default: + { + debug("String::data() - Invalid Type value."); + return ByteVector(); } - break; } - } - - return v; } int String::toInt() const @@ -499,89 +500,35 @@ int String::toInt() const int String::toInt(bool *ok) const { - int value = 0; + const wchar_t *begin = d->data.c_str(); + wchar_t *end; + errno = 0; + const long value = ::wcstol(begin, &end, 10); - uint size = d->data.size(); - bool negative = size > 0 && d->data[0] == '-'; - uint start = negative ? 1 : 0; - uint i = start; + // Has wcstol() consumed the entire string and not overflowed? + if(ok) { + *ok = (errno == 0 && end > begin && *end == L'\0'); + *ok = (*ok && value > INT_MIN && value < INT_MAX); + } - for(; i < size && d->data[i] >= '0' && d->data[i] <= '9'; i++) - value = value * 10 + (d->data[i] - '0'); - - if(negative) - value = value * -1; - - if(ok) - *ok = (size > start && i == size); - - return value; -} - -float String::toFloat() const -{ - return toFloat(0); -} - -float String::toFloat(bool *ok) const -{ - float value = 0; - float decimal_value = 1; - - uint size = d->data.size(); - bool negative = size > 0 && d->data[0] == '-'; - uint start = negative ? 1 : 0; - uint i = start; - - for(; i < size && d->data[i] >= '0' && d->data[i] <= '9'; i++) - value = value * 10 + (d->data[i] - '0'); - - if (i < size && d->data[i] == '.') - for(++i; i < size && d->data[i] >= '0' && d->data[i] <= '9'; i++) { - value = value * 10 + (d->data[i] - '0'); - decimal_value *= 0.1; - } - - if(negative) - value = value * -1; - - value *= decimal_value; - - if (ok) - *ok = (size > start && i == size); - - return value; + return static_cast<int>(value); } String String::stripWhiteSpace() const { - wstring::const_iterator begin = d->data.begin(); - wstring::const_iterator end = d->data.end(); + static const wchar_t *WhiteSpaceChars = L"\t\n\f\r "; - while(begin != end && - (*begin == '\t' || *begin == '\n' || *begin == '\f' || - *begin == '\r' || *begin == ' ')) - { - ++begin; - } + const size_t pos1 = d->data.find_first_not_of(WhiteSpaceChars); + if(pos1 == std::wstring::npos) + return String(); - if(begin == end) - return null; - - // There must be at least one non-whitespace character here for us to have - // gotten this far, so we should be safe not doing bounds checking. - - do { - --end; - } while(*end == '\t' || *end == '\n' || - *end == '\f' || *end == '\r' || *end == ' '); - - return String(wstring(begin, end + 1)); + const size_t pos2 = d->data.find_last_not_of(WhiteSpaceChars); + return substr(static_cast<unsigned int>(pos1), static_cast<unsigned int>(pos2 - pos1 + 1)); } bool String::isLatin1() const { - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) { + for(ConstIterator it = begin(); it != end(); ++it) { if(*it >= 256) return false; } @@ -590,104 +537,63 @@ bool String::isLatin1() const bool String::isAscii() const { - for(wstring::const_iterator it = d->data.begin(); it != d->data.end(); it++) { + for(ConstIterator it = begin(); it != end(); ++it) { if(*it >= 128) return false; } return true; } -String String::number(uint n) -{ - return number((int)n); -} - String String::number(int n) // static { - if(n == 0) - return String("0"); - - String charStack; - - bool negative = n < 0; - - if(negative) - n = n * -1; - - while(n > 0) { - int remainder = n % 10; - charStack += char(remainder + '0'); - n = (n - remainder) / 10; - } - - String s; - - if(negative) - s += '-'; - - for(int i = charStack.d->data.size() - 1; i >= 0; i--) - s += charStack.d->data[i]; - - return s; + return Utils::formatString("%d", n); } -String String::number(float n) // static -{ - if(n == 0) - return String("0"); - - String charStack; - - bool negative = n < 0; - - if(negative) - n = n * -1; - - float decimal = fmod(n, 1); - n -= decimal; - - while(n > 0) { - float remainder = fmod(n, 10); - charStack += char(remainder + '0'); - n = (n - remainder) / 10; - } - - String s; - - if(negative) - s += '-'; - - for(int i = charStack.d->data.size() - 1; i >= 0; i--) - s += charStack.d->data[i]; - - if (decimal > 0) { - s += '.'; - while (decimal > 0) { - decimal *= 10; - float remainder = fmod(decimal, 1); - s += char(decimal + '0'); - decimal = remainder; - } - } - - return s; -} - -TagLib::wchar &String::operator[](int i) +wchar_t &String::operator[](int i) { detach(); - return d->data[i]; } -const TagLib::wchar &String::operator[](int i) const +const wchar_t &String::operator[](int i) const { return d->data[i]; } bool String::operator==(const String &s) const { - return d == s.d || d->data == s.d->data; + return (d == s.d || d->data == s.d->data); +} + +bool String::operator!=(const String &s) const +{ + return !(*this == s); +} + +bool String::operator==(const char *s) const +{ + const wchar_t *p = toCWString(); + + while(*p != L'\0' || *s != '\0') { + if(*p++ != static_cast<unsigned char>(*s++)) + return false; + } + return true; +} + +bool String::operator!=(const char *s) const +{ + return !(*this == s); +} + +bool String::operator==(const wchar_t *s) const +{ + return (d->data == s); +} + +bool String::operator!=(const wchar_t *s) const +{ + return !(*this == s); } String &String::operator+=(const String &s) @@ -711,7 +617,7 @@ String &String::operator+=(const char *s) detach(); for(int i = 0; s[i] != 0; i++) - d->data += uchar(s[i]); + d->data += static_cast<unsigned char>(s[i]); return *this; } @@ -727,120 +633,68 @@ String &String::operator+=(char c) { detach(); - d->data += uchar(c); + d->data += static_cast<unsigned char>(c); return *this; } String &String::operator=(const String &s) { - if(&s == this) - return *this; - - if(d->deref()) - delete d; - d = s.d; - d->ref(); + String(s).swap(*this); return *this; } String &String::operator=(const std::string &s) { - if(d->deref()) - delete d; - - d = new StringPrivate; - - d->data.resize(s.size()); - - wstring::iterator targetIt = d->data.begin(); - for(std::string::const_iterator it = s.begin(); it != s.end(); it++) { - *targetIt = uchar(*it); - ++targetIt; - } - + String(s).swap(*this); return *this; } String &String::operator=(const wstring &s) { - if(d->deref()) - delete d; - d = new StringPrivate(s); + String(s).swap(*this); return *this; } String &String::operator=(const wchar_t *s) { - if(d->deref()) - delete d; - d = new StringPrivate(s); + String(s).swap(*this); return *this; } String &String::operator=(char c) { - if(d->deref()) - delete d; - d = new StringPrivate; - d->data += uchar(c); + String(c).swap(*this); return *this; } String &String::operator=(wchar_t c) { - if(d->deref()) - delete d; - d = new StringPrivate; - d->data += c; + String(c, wcharByteOrder()).swap(*this); return *this; } String &String::operator=(const char *s) { - if(d->deref()) - delete d; - - d = new StringPrivate; - - int length = ::strlen(s); - d->data.resize(length); - - wstring::iterator targetIt = d->data.begin(); - for(int i = 0; i < length; i++) { - *targetIt = uchar(s[i]); - ++targetIt; - } - + String(s).swap(*this); return *this; } String &String::operator=(const ByteVector &v) { - if(d->deref()) - delete d; - - d = new StringPrivate; - d->data.resize(v.size()); - wstring::iterator targetIt = d->data.begin(); - - uint i = 0; - - for(ByteVector::ConstIterator it = v.begin(); it != v.end() && (*it); ++it) { - *targetIt = uchar(*it); - ++targetIt; - ++i; - } - - // If we hit a null in the ByteVector, shrink the string again. - - d->data.resize(i); - + String(v).swap(*this); return *this; } +void String::swap(String &s) +{ + using std::swap; + + swap(d, s.d); +} + bool String::operator<(const String &s) const { - return d->data < s.d->data; + return (d->data < s.d->data); } //////////////////////////////////////////////////////////////////////////////// @@ -849,107 +703,45 @@ bool String::operator<(const String &s) const void String::detach() { - if(d->count() > 1) { - d->deref(); - d = new StringPrivate(d->data); - } + if(d->count() > 1) + String(d->data.c_str()).swap(*this); } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void String::prepare(Type t) -{ - switch(t) { - case UTF16: - { - if(d->data.size() >= 1 && (d->data[0] == 0xfeff || d->data[0] == 0xfffe)) { - bool swap = d->data[0] != 0xfeff; - d->data.erase(d->data.begin(), d->data.begin() + 1); - if(swap) { - for(uint i = 0; i < d->data.size(); i++) - d->data[i] = byteSwap((unsigned short)d->data[i]); - } - } - else { - debug("String::prepare() - Invalid UTF16 string."); - d->data.erase(d->data.begin(), d->data.end()); - } - break; - } - case UTF8: - { - int bufferSize = d->data.size() + 1; - Unicode::UTF8 *sourceBuffer = new Unicode::UTF8[bufferSize]; - Unicode::UTF16 *targetBuffer = new Unicode::UTF16[bufferSize]; - - unsigned int i = 0; - for(; i < d->data.size(); i++) - sourceBuffer[i] = Unicode::UTF8(d->data[i]); - sourceBuffer[i] = 0; - - const Unicode::UTF8 *source = sourceBuffer; - Unicode::UTF16 *target = targetBuffer; - - Unicode::ConversionResult result = - Unicode::ConvertUTF8toUTF16(&source, sourceBuffer + bufferSize, - &target, targetBuffer + bufferSize, - Unicode::lenientConversion); - - if(result != Unicode::conversionOK) - debug("String::prepare() - Unicode conversion error."); - - - int newSize = target != targetBuffer ? target - targetBuffer - 1 : 0; - d->data.resize(newSize); - - for(int i = 0; i < newSize; i++) - d->data[i] = targetBuffer[i]; - - delete [] sourceBuffer; - delete [] targetBuffer; - - break; - } - case UTF16LE: - { - for(uint i = 0; i < d->data.size(); i++) - d->data[i] = byteSwap((unsigned short)d->data[i]); - break; - } - default: - break; - } +const String::Type String::WCharByteOrder = wcharByteOrder(); } //////////////////////////////////////////////////////////////////////////////// -// related functions +// related non-member functions //////////////////////////////////////////////////////////////////////////////// const TagLib::String operator+(const TagLib::String &s1, const TagLib::String &s2) { - String s(s1); + TagLib::String s(s1); s.append(s2); return s; } const TagLib::String operator+(const char *s1, const TagLib::String &s2) { - String s(s1); + TagLib::String s(s1); s.append(s2); return s; } const TagLib::String operator+(const TagLib::String &s1, const char *s2) { - String s(s1); + TagLib::String s(s1); s.append(s2); return s; } -std::ostream &operator<<(std::ostream &s, const String &str) +std::ostream &operator<<(std::ostream &s, const TagLib::String &str) { s << str.to8Bit(); return s; } + diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tstring.h b/Frameworks/TagLib/taglib/taglib/toolkit/tstring.h index 665e4a64c..7028aab2f 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tstring.h +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tstring.h @@ -31,7 +31,7 @@ #include "tbytevector.h" #include <string> -#include <ostream> +#include <iostream> /*! * \relates TagLib::String @@ -41,7 +41,12 @@ * \note consider conversion via usual char-by-char for loop to avoid UTF16->UTF8->UTF16 * conversion happening in the background */ + +#if defined(QT_VERSION) && (QT_VERSION >= 0x040000) +#define QStringToTString(s) TagLib::String(s.toUtf8().data(), TagLib::String::UTF8) +#else #define QStringToTString(s) TagLib::String(s.utf8().data(), TagLib::String::UTF8) +#endif /*! * \relates TagLib::String @@ -52,18 +57,20 @@ * conversion happening in the background * */ + #define TStringToQString(s) QString::fromUtf8(s.toCString(true)) namespace TagLib { - extern char ascii_encoding[]; + + class StringList; //! A \e wide string class suitable for unicode. /*! * This is an implicitly shared \e wide string. For storage it uses * TagLib::wstring, but as this is an <i>implementation detail</i> this of - * course could change. Strings are stored internally as UTF-16BE. (Without - * the BOM (Byte Order Mark) + * course could change. Strings are stored internally as UTF-16(without BOM/ + * CPU byte order) * * The use of implicit sharing means that copying a string is cheap, the only * \e cost comes into play when the copy is modified. Prior to that the string @@ -79,8 +86,8 @@ namespace TagLib { public: #ifndef DO_NOT_DOCUMENT - typedef std::basic_string<wchar>::iterator Iterator; - typedef std::basic_string<wchar>::const_iterator ConstIterator; + typedef TagLib::wstring::iterator Iterator; + typedef TagLib::wstring::const_iterator ConstIterator; #endif /** @@ -133,11 +140,19 @@ namespace TagLib { /*! * Makes a deep copy of the data in \a s. + * + * /note If \a t is UTF16LE, the byte order of \a s will be swapped regardless + * of the CPU byte order. If UTF16BE, it will not be swapped. This behavior + * will be changed in TagLib2.0. */ String(const wstring &s, Type t = UTF16BE); /*! * Makes a deep copy of the data in \a s. + * + * /note If \a t is UTF16LE, the byte order of \a s will be swapped regardless + * of the CPU byte order. If UTF16BE, it will not be swapped. This behavior + * will be changed in TagLib2.0. */ String(const wchar_t *s, Type t = UTF16BE); @@ -154,7 +169,6 @@ namespace TagLib { */ String(wchar_t c, Type t = Latin1); - /*! * Makes a deep copy of the data in \a s. * @@ -164,10 +178,7 @@ namespace TagLib { String(const char *s, Type t = Latin1); /*! - * Makes a deep copy of the data in \a s. - * - * \note This should only be used with the 8-bit codecs Latin1 and UTF8, when - * used with other codecs it will simply print a warning and exit. + * Makes a deep copy of the data in \a v. */ String(const ByteVector &v, Type t = Latin1); @@ -177,34 +188,60 @@ namespace TagLib { virtual ~String(); /*! - * If \a unicode if false (the default) this will return a \e Latin1 encoded - * std::string. If it is true the returned std::wstring will be UTF-8 - * encoded. + * Returns a deep copy of this String as an std::string. The returned string + * is encoded in UTF8 if \a unicode is true, otherwise Latin1. + * + * \see toCString() */ std::string to8Bit(bool unicode = false) const; /*! - * Returns a wstring version of the TagLib string as a wide string. + * Returns a deep copy of this String as a wstring. The returned string is + * encoded in UTF-16 (without BOM/CPU byte order), not UTF-32 even if wchar_t + * is 32-bit wide. + * + * \see toCWString() */ wstring toWString() const; /*! - * Creates and returns a C-String based on the data. This string is still - * owned by the String (class) and as such should not be deleted by the user. + * Creates and returns a standard C-style (null-terminated) version of this + * String. The returned string is encoded in UTF8 if \a unicode is true, + * otherwise Latin1. * - * If \a unicode if false (the default) this string will be encoded in - * \e Latin1. If it is true the returned C-String will be UTF-8 encoded. + * The returned string is still owned by this String and should not be deleted + * by the user. * - * This string remains valid until the String instance is destroyed or - * another export method is called. + * The returned pointer remains valid until this String instance is destroyed + * or toCString() is called again. * - * \warning This however has the side effect that this C-String will remain - * in memory <b>in addition to</b> other memory that is consumed by the + * \warning This however has the side effect that the returned string will remain + * in memory <b>in addition to</b> other memory that is consumed by this * String instance. So, this method should not be used on large strings or - * where memory is critical. + * where memory is critical. Consider using to8Bit() instead to avoid it. + * + * \see to8Bit() */ const char *toCString(bool unicode = false) const; + /*! + * Returns a standard C-style (null-terminated) wide character version of + * this String. The returned string is encoded in UTF-16 (without BOM/CPU byte + * order), not UTF-32 even if wchar_t is 32-bit wide. + * + * The returned string is still owned by this String and should not be deleted + * by the user. + * + * The returned pointer remains valid until this String instance is destroyed + * or any other method of this String is called. + * + * \note This returns a pointer to the String's internal data without any + * conversions. + * + * \see toWString() + */ + const wchar_t *toCWString() const; + /*! * Returns an iterator pointing to the beginning of the string. */ @@ -240,6 +277,11 @@ namespace TagLib { */ int rfind(const String &s, int offset = -1) const; + /*! + * Splits the string on each occurrence of \a separator. + */ + StringList split(const String &separator = " ") const; + /*! * Returns true if the strings starts with the substring \a s. */ @@ -249,7 +291,7 @@ namespace TagLib { * Extract a substring from this string starting at \a position and * continuing for \a n characters. */ - String substr(uint position, uint n = 0xffffffff) const; + String substr(unsigned int position, unsigned int n = 0xffffffff) const; /*! * Append \a s to the current string and return a reference to the current @@ -257,6 +299,11 @@ namespace TagLib { */ String &append(const String &s); + /*! + * Clears the string. + */ + String &clear(); + /*! * Returns an upper case version of the string. * @@ -267,12 +314,12 @@ namespace TagLib { /*! * Returns the size of the string. */ - uint size() const; + unsigned int size() const; /*! * Returns the length of the string. Equivalent to size(). */ - uint length() const; + unsigned int length() const; /*! * Returns true if the string is empty. @@ -285,22 +332,32 @@ namespace TagLib { * Returns true if this string is null -- i.e. it is a copy of the * String::null string. * - * \note A string can be empty and not null. + * \note A string can be empty and not null. So do not use this method to + * check if the string is empty. + * * \see isEmpty() + * + * \deprecated */ - bool isNull() const; + // BIC: remove + TAGLIB_DEPRECATED bool isNull() const; /*! * Returns a ByteVector containing the string's data. If \a t is Latin1 or * UTF8, this will return a vector of 8 bit characters, otherwise it will use * 16 bit characters. + * + * \note If \a t is UTF16, the returned data is encoded in little-endian + * format and has a BOM. + * + * \note The returned data is not null terminated. */ ByteVector data(Type t) const; /*! * Convert the string to an integer. * - * Returns the integer if the conversion was successfull or 0 if the + * Returns the integer if the conversion was successful or 0 if the * string does not represent a number. */ // BIC: merge with the method below @@ -309,30 +366,12 @@ namespace TagLib { /*! * Convert the string to an integer. * - * If the conversion was successfull, it sets the value of \a *ok to + * If the conversion was successful, it sets the value of \a *ok to * true and returns the integer. Otherwise it sets \a *ok to false * and the result is undefined. */ int toInt(bool *ok) const; - /*! - * Convert the string to a float. - * - * Returns the float if the conversion was successfull or 0 if the - * string does not represent a number. - */ - // BIC: merge with the method below - float toFloat() const; - - /*! - * Convert the string to a float. - * - * If the conversion was successfull, it sets the value of \a *ok to - * true and returns the float. Otherwise it sets \a *ok to false - * and the result is undefined. - */ - float toFloat(bool *ok) const; - /*! * Returns a string with the leading and trailing whitespace stripped. */ @@ -348,30 +387,20 @@ namespace TagLib { */ bool isAscii() const; - /*! - * Converts the base-10 unsigned integer \a n to a string. - */ - static String number(uint n); - /*! * Converts the base-10 integer \a n to a string. */ static String number(int n); - /*! - * Converts the base-10 float \a n to a string. - */ - static String number(float n); - /*! * Returns a reference to the character at position \a i. */ - wchar &operator[](int i); + wchar_t &operator[](int i); /*! * Returns a const reference to the character at position \a i. */ - const wchar &operator[](int i) const; + const wchar_t &operator[](int i) const; /*! * Compares each character of the String with each character of \a s and @@ -379,6 +408,36 @@ namespace TagLib { */ bool operator==(const String &s) const; + /*! + * Compares each character of the String with each character of \a s and + * returns false if the strings match. + */ + bool operator!=(const String &s) const; + + /*! + * Compares each character of the String with each character of \a s and + * returns true if the strings match. + */ + bool operator==(const char *s) const; + + /*! + * Compares each character of the String with each character of \a s and + * returns false if the strings match. + */ + bool operator!=(const char *s) const; + + /*! + * Compares each character of the String with each character of \a s and + * returns true if the strings match. + */ + bool operator==(const wchar_t *s) const; + + /*! + * Compares each character of the String with each character of \a s and + * returns false if the strings match. + */ + bool operator!=(const wchar_t *s) const; + /*! * Appends \a s to the end of the String. */ @@ -445,17 +504,28 @@ namespace TagLib { */ String &operator=(const ByteVector &v); + /*! + * Exchanges the content of the String by the content of \a s. + */ + void swap(String &s); + /*! * To be able to use this class in a Map, this operator needed to be - * implemented. Returns true if \a s is less than this string in a bytewise + * implemented. Returns true if \a s is less than this string in a byte-wise * comparison. */ bool operator<(const String &s) const; /*! * A null string provided for convenience. + * + * \warning Do not modify this variable. It will mess up the internal state + * of TagLib. + * + * \deprecated */ - static String null; + // BIC: remove + TAGLIB_DEPRECATED static String null; protected: /*! @@ -467,17 +537,15 @@ namespace TagLib { private: /*! - * This checks to see if the string is in \e UTF-16 (with BOM) or \e UTF-8 - * format and if so converts it to \e UTF-16BE for internal use. \e Latin1 - * does not require conversion since it is a subset of \e UTF-16BE and - * \e UTF16-BE requires no conversion since it is used internally. + * \deprecated This variable is no longer used, but NEVER remove this. It + * may lead to a linkage error. */ - void prepare(Type t); + // BIC: remove + TAGLIB_DEPRECATED static const Type WCharByteOrder; class StringPrivate; StringPrivate *d; }; - } /*! diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tstringlist.h b/Frameworks/TagLib/taglib/taglib/toolkit/tstringlist.h index 3ef131dcb..41b7f6ecd 100644 --- a/Frameworks/TagLib/taglib/taglib/toolkit/tstringlist.h +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tstringlist.h @@ -31,14 +31,14 @@ #include "tbytevectorlist.h" #include "taglib_export.h" -#include <ostream> +#include <iostream> namespace TagLib { //! A list of strings /*! - * This is a spcialization of the List class with some members convention for + * This is a specialization of the List class with some members convention for * string operations. */ diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tutils.h b/Frameworks/TagLib/taglib/taglib/toolkit/tutils.h new file mode 100644 index 000000000..6d96cd12d --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tutils.h @@ -0,0 +1,243 @@ +/*************************************************************************** + copyright : (C) 2013 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_TUTILS_H +#define TAGLIB_TUTILS_H + +// THIS FILE IS NOT A PART OF THE TAGLIB API + +#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(HAVE_MSC_BYTESWAP) +# include <stdlib.h> +#elif defined(HAVE_GLIBC_BYTESWAP) +# include <byteswap.h> +#elif defined(HAVE_MAC_BYTESWAP) +# include <libkern/OSByteOrder.h> +#elif defined(HAVE_OPENBSD_BYTESWAP) +# include <sys/endian.h> +#endif + +#include <tstring.h> +#include <cstdio> +#include <cstdarg> +#include <cstring> + +namespace TagLib +{ + namespace Utils + { + namespace + { + + /*! + * Reverses the order of bytes in an 16-bit integer. + */ + inline unsigned short byteSwap(unsigned short x) + { +#if defined(HAVE_GCC_BYTESWAP) + + return __builtin_bswap16(x); + +#elif defined(HAVE_MSC_BYTESWAP) + + return _byteswap_ushort(x); + +#elif defined(HAVE_GLIBC_BYTESWAP) + + return __bswap_16(x); + +#elif defined(HAVE_MAC_BYTESWAP) + + return OSSwapInt16(x); + +#elif defined(HAVE_OPENBSD_BYTESWAP) + + return swap16(x); + +#else + + return ((x >> 8) & 0xff) | ((x & 0xff) << 8); + +#endif + } + + /*! + * Reverses the order of bytes in an 32-bit integer. + */ + inline unsigned int byteSwap(unsigned int x) + { +#if defined(HAVE_GCC_BYTESWAP) + + return __builtin_bswap32(x); + +#elif defined(HAVE_MSC_BYTESWAP) + + return _byteswap_ulong(x); + +#elif defined(HAVE_GLIBC_BYTESWAP) + + return __bswap_32(x); + +#elif defined(HAVE_MAC_BYTESWAP) + + return OSSwapInt32(x); + +#elif defined(HAVE_OPENBSD_BYTESWAP) + + return swap32(x); + +#else + + return ((x & 0xff000000u) >> 24) + | ((x & 0x00ff0000u) >> 8) + | ((x & 0x0000ff00u) << 8) + | ((x & 0x000000ffu) << 24); + +#endif + } + + /*! + * Reverses the order of bytes in an 64-bit integer. + */ + inline unsigned long long byteSwap(unsigned long long x) + { +#if defined(HAVE_GCC_BYTESWAP) + + return __builtin_bswap64(x); + +#elif defined(HAVE_MSC_BYTESWAP) + + return _byteswap_uint64(x); + +#elif defined(HAVE_GLIBC_BYTESWAP) + + return __bswap_64(x); + +#elif defined(HAVE_MAC_BYTESWAP) + + return OSSwapInt64(x); + +#elif defined(HAVE_OPENBSD_BYTESWAP) + + return swap64(x); + +#else + + return ((x & 0xff00000000000000ull) >> 56) + | ((x & 0x00ff000000000000ull) >> 40) + | ((x & 0x0000ff0000000000ull) >> 24) + | ((x & 0x000000ff00000000ull) >> 8) + | ((x & 0x00000000ff000000ull) << 8) + | ((x & 0x0000000000ff0000ull) << 24) + | ((x & 0x000000000000ff00ull) << 40) + | ((x & 0x00000000000000ffull) << 56); + +#endif + } + + /*! + * Returns a formatted string just like standard sprintf(), but makes use of + * safer functions such as snprintf() if available. + */ + inline String formatString(const char *format, ...) + { + // Sufficient buffer size for the current internal uses. + // Consider changing this value when you use this function. + + static const size_t BufferSize = 128; + + va_list args; + va_start(args, format); + + char buf[BufferSize]; + int length; + +#if defined(HAVE_VSNPRINTF) + + length = vsnprintf(buf, BufferSize, format, args); + +#elif defined(HAVE_VSPRINTF_S) + + length = vsprintf_s(buf, format, args); + +#else + + // The last resort. May cause a buffer overflow. + + length = vsprintf(buf, format, args); + if(length >= BufferSize) { + debug("Utils::formatString() - Buffer overflow! Returning an empty string."); + length = -1; + } + +#endif + + va_end(args); + + if(length > 0) + return String(buf); + else + return String(); + } + + /*! + * The types of byte order of the running system. + */ + enum ByteOrder + { + //! Little endian systems. + LittleEndian, + //! Big endian systems. + BigEndian + }; + + /*! + * Returns the byte order of the system. + */ + inline ByteOrder systemByteOrder() + { + union { + int i; + char c; + } u; + + u.i = 1; + if(u.c == 1) + return LittleEndian; + else + return BigEndian; + } + } + } +} + +#endif + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tzlib.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/tzlib.cpp new file mode 100644 index 000000000..4b348df5d --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tzlib.cpp @@ -0,0 +1,106 @@ +/*************************************************************************** + copyright : (C) 2016 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifdef HAVE_ZLIB +# include <zlib.h> +# include <tstring.h> +# include <tdebug.h> +#endif + +#include "tzlib.h" + +using namespace TagLib; + +bool zlib::isAvailable() +{ +#ifdef HAVE_ZLIB + + return true; + +#else + + return false; + +#endif +} + +ByteVector zlib::decompress(const ByteVector &data) +{ +#ifdef HAVE_ZLIB + + z_stream stream = {}; + + if(inflateInit(&stream) != Z_OK) { + debug("zlib::decompress() - Failed to initialize zlib."); + return ByteVector(); + } + + ByteVector inData = data; + + stream.avail_in = static_cast<uInt>(inData.size()); + stream.next_in = reinterpret_cast<Bytef *>(inData.data()); + + const unsigned int chunkSize = 1024; + + ByteVector outData; + + do { + const size_t offset = outData.size(); + outData.resize(outData.size() + chunkSize); + + stream.avail_out = static_cast<uInt>(chunkSize); + stream.next_out = reinterpret_cast<Bytef *>(outData.data() + offset); + + const int result = inflate(&stream, Z_NO_FLUSH); + + if(result == Z_STREAM_ERROR || + result == Z_NEED_DICT || + result == Z_DATA_ERROR || + result == Z_MEM_ERROR) + { + if(result != Z_STREAM_ERROR) + inflateEnd(&stream); + + debug("zlib::decompress() - Error reading compressed stream."); + return ByteVector(); + } + + outData.resize(outData.size() - stream.avail_out); + } while(stream.avail_out == 0); + + inflateEnd(&stream); + + return outData; + +#else + + return ByteVector(); + +#endif +} diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/tzlib.h b/Frameworks/TagLib/taglib/taglib/toolkit/tzlib.h new file mode 100644 index 000000000..b1f1fcaf8 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/toolkit/tzlib.h @@ -0,0 +1,54 @@ +/*************************************************************************** + copyright : (C) 2016 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_TZLIB_H +#define TAGLIB_TZLIB_H + +#include <tbytevector.h> + +// THIS FILE IS NOT A PART OF THE TAGLIB API + +#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header + +namespace TagLib { + + namespace zlib { + + /*! + * Returns whether or not zlib is installed and ready to use. + */ + bool isAvailable(); + + /*! + * Decompress \a data by zlib. + */ + ByteVector decompress(const ByteVector &data); + + } +} + +#endif + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/unicode.cpp b/Frameworks/TagLib/taglib/taglib/toolkit/unicode.cpp deleted file mode 100644 index b60264d91..000000000 --- a/Frameworks/TagLib/taglib/taglib/toolkit/unicode.cpp +++ /dev/null @@ -1,303 +0,0 @@ -/******************************************************************************* - * * - * THIS FILE IS INCLUDED IN TAGLIB, BUT IS NOT COPYRIGHTED BY THE TAGLIB * - * AUTHORS, NOT PART OF THE TAGLIB API AND COULD GO AWAY AT ANY POINT IN TIME. * - * AS SUCH IT SHOULD BE CONSIERED FOR INTERNAL USE ONLY. * - * * - *******************************************************************************/ - -/* - * Copyright 2001 Unicode, Inc. - * - * Disclaimer - * - * This source code is provided as is by Unicode, Inc. No claims are - * made as to fitness for any particular purpose. No warranties of any - * kind are expressed or implied. The recipient agrees to determine - * applicability of information provided. If this file has been - * purchased on magnetic or optical media from Unicode, Inc., the - * sole remedy for any claim will be exchange of defective media - * within 90 days of receipt. - * - * Limitations on Rights to Redistribute This Code - * - * Unicode, Inc. hereby grants the right to freely use the information - * supplied in this file in the creation of products supporting the - * Unicode Standard, and to make copies of this file in any form - * for internal or external distribution as long as this notice - * remains attached. - */ - -/* - * This file has been modified by Scott Wheeler <wheeler@kde.org> to remove - * the UTF32 conversion functions and to place the appropriate functions - * in their own C++ namespace. - */ - -/* --------------------------------------------------------------------- - - Conversions between UTF32, UTF-16, and UTF-8. Source code file. - Author: Mark E. Davis, 1994. - Rev History: Rick McGowan, fixes & updates May 2001. - Sept 2001: fixed const & error conditions per - mods suggested by S. Parent & A. Lillich. - - See the header file "ConvertUTF.h" for complete documentation. - ------------------------------------------------------------------------- */ - - -#include "unicode.h" -#include <stdio.h> - -#define UNI_SUR_HIGH_START (UTF32)0xD800 -#define UNI_SUR_HIGH_END (UTF32)0xDBFF -#define UNI_SUR_LOW_START (UTF32)0xDC00 -#define UNI_SUR_LOW_END (UTF32)0xDFFF -#define false 0 -#define true 1 - -namespace Unicode { - -static const int halfShift = 10; /* used for shifting by 10 bits */ - -static const UTF32 halfBase = 0x0010000UL; -static const UTF32 halfMask = 0x3FFUL; - -/* - * Index into the table below with the first byte of a UTF-8 sequence to - * get the number of trailing bytes that are supposed to follow it. - */ -static const char trailingBytesForUTF8[256] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 -}; - -/* - * Magic values subtracted from a buffer value during UTF8 conversion. - * This table contains as many values as there might be trailing bytes - * in a UTF-8 sequence. - */ -static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, - 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; - -/* - * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed - * into the first byte, depending on how many bytes follow. There are - * as many entries in this table as there are UTF-8 sequence types. - * (I.e., one byte sequence, two byte... six byte sequence.) - */ -static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; - -/* --------------------------------------------------------------------- */ - -/* The interface converts a whole buffer to avoid function-call overhead. - * Constants have been gathered. Loops & conditionals have been removed as - * much as possible for efficiency, in favor of drop-through switches. - * (See "Note A" at the bottom of the file for equivalent code.) - * If your compiler supports it, the "isLegalUTF8" call can be turned - * into an inline function. - */ - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF16toUTF8 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END && source < sourceEnd) { - UTF32 ch2 = *source; - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else if ((flags == strictConversion) && (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END)) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - /* Figure out how many bytes the result will require */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch < (UTF32)0x200000) { bytesToWrite = 4; - } else { bytesToWrite = 2; - ch = UNI_REPLACEMENT_CHAR; - } - // printf("bytes to write = %i\n", bytesToWrite); - target += bytesToWrite; - if (target > targetEnd) { - source = oldSource; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (ch | byteMark) & byteMask; ch >>= 6; - case 3: *--target = (ch | byteMark) & byteMask; ch >>= 6; - case 2: *--target = (ch | byteMark) & byteMask; ch >>= 6; - case 1: *--target = ch | firstByteMark[bytesToWrite]; - } - target += bytesToWrite; - } - *sourceStart = source; - *targetStart = target; - return result; -} - -/* --------------------------------------------------------------------- */ - -/* - * Utility routine to tell whether a sequence of bytes is legal UTF-8. - * This must be called with the length pre-determined by the first byte. - * If not calling this from ConvertUTF8to*, then the length can be set by: - * length = trailingBytesForUTF8[*source]+1; - * and the sequence is illegal right away if there aren't that many bytes - * available. - * If presented with a length > 4, this returns false. The Unicode - * definition of UTF-8 goes up to 4-byte sequences. - */ - -static Boolean isLegalUTF8(const UTF8 *source, int length) { - UTF8 a; - const UTF8 *srcptr = source+length; - switch (length) { - default: return false; - /* Everything else falls through when "true"... */ - case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 2: if ((a = (*--srcptr)) > 0xBF) return false; - switch (*source) { - /* no fall-through in this inner switch */ - case 0xE0: if (a < 0xA0) return false; break; - case 0xF0: if (a < 0x90) return false; break; - case 0xF4: if (a > 0x8F) return false; break; - default: if (a < 0x80) return false; - } - case 1: if (*source >= 0x80 && *source < 0xC2) return false; - if (*source > 0xF4) return false; - } - return true; -} - -/* --------------------------------------------------------------------- */ - -/* - * Exported function to return whether a UTF-8 sequence is legal or not. - * This is not used here; it's just exported. - */ -Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { - int length = trailingBytesForUTF8[*source]+1; - if (source+length > sourceEnd) { - return false; - } - return isLegalUTF8(source, length); -} - -/* --------------------------------------------------------------------- */ - -ConversionResult ConvertUTF8toUTF16 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; - - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - if ((flags == strictConversion) && (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END)) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = ch; /* normal case */ - } - } else if (ch > UNI_MAX_UTF16) { - if (flags == strictConversion) { - result = sourceIllegal; - source -= (extraBytesToRead+1); /* return to the start */ - break; /* Bail out; shouldn't continue */ - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (ch >> halfShift) + UNI_SUR_HIGH_START; - *target++ = (ch & halfMask) + UNI_SUR_LOW_START; - } - } - *sourceStart = source; - *targetStart = target; - return result; -} - -} - -/* --------------------------------------------------------------------- - - Note A. - The fall-through switches in UTF-8 reading code save a - temp variable, some decrements & conditionals. The switches - are equivalent to the following loop: - { - int tmpBytesToRead = extraBytesToRead+1; - do { - ch += *source++; - --tmpBytesToRead; - if (tmpBytesToRead) ch <<= 6; - } while (tmpBytesToRead > 0); - } - In UTF-8 writing code, the switches on "bytesToWrite" are - similarly unrolled loops. - - --------------------------------------------------------------------- */ - - diff --git a/Frameworks/TagLib/taglib/taglib/toolkit/unicode.h b/Frameworks/TagLib/taglib/taglib/toolkit/unicode.h deleted file mode 100644 index cf7eb3c56..000000000 --- a/Frameworks/TagLib/taglib/taglib/toolkit/unicode.h +++ /dev/null @@ -1,149 +0,0 @@ -#ifndef TAGLIB_UNICODE_H -#define TAGLIB_UNICODE_H - -/******************************************************************************* - * * - * THIS FILE IS INCLUDED IN TAGLIB, BUT IS NOT COPYRIGHTED BY THE TAGLIB * - * AUTHORS, NOT PART OF THE TAGLIB API AND COULD GO AWAY AT ANY POINT IN TIME. * - * AS SUCH IT SHOULD BE CONSIERED FOR INTERNAL USE ONLY. * - * * - *******************************************************************************/ - -#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header - -/* - * Copyright 2001 Unicode, Inc. - * - * Disclaimer - * - * This source code is provided as is by Unicode, Inc. No claims are - * made as to fitness for any particular purpose. No warranties of any - * kind are expressed or implied. The recipient agrees to determine - * applicability of information provided. If this file has been - * purchased on magnetic or optical media from Unicode, Inc., the - * sole remedy for any claim will be exchange of defective media - * within 90 days of receipt. - * - * Limitations on Rights to Redistribute This Code - * - * Unicode, Inc. hereby grants the right to freely use the information - * supplied in this file in the creation of products supporting the - * Unicode Standard, and to make copies of this file in any form - * for internal or external distribution as long as this notice - * remains attached. - */ - -/* - * This file has been modified by Scott Wheeler <wheeler@kde.org> to remove - * the UTF32 conversion functions and to place the appropriate functions - * in their own C++ namespace. - */ - -/* --------------------------------------------------------------------- - - Conversions between UTF32, UTF-16, and UTF-8. Header file. - - Several functions are included here, forming a complete set of - conversions between the three formats. UTF-7 is not included - here, but is handled in a separate source file. - - Each of these routines takes pointers to input buffers and output - buffers. The input buffers are const. - - Each routine converts the text between *sourceStart and sourceEnd, - putting the result into the buffer between *targetStart and - targetEnd. Note: the end pointers are *after* the last item: e.g. - *(sourceEnd - 1) is the last item. - - The return result indicates whether the conversion was successful, - and if not, whether the problem was in the source or target buffers. - (Only the first encountered problem is indicated.) - - After the conversion, *sourceStart and *targetStart are both - updated to point to the end of last text successfully converted in - the respective buffers. - - Input parameters: - sourceStart - pointer to a pointer to the source buffer. - The contents of this are modified on return so that - it points at the next thing to be converted. - targetStart - similarly, pointer to pointer to the target buffer. - sourceEnd, targetEnd - respectively pointers to the ends of the - two buffers, for overflow checking only. - - These conversion functions take a ConversionFlags argument. When this - flag is set to strict, both irregular sequences and isolated surrogates - will cause an error. When the flag is set to lenient, both irregular - sequences and isolated surrogates are converted. - - Whether the flag is strict or lenient, all illegal sequences will cause - an error return. This includes sequences such as: <F4 90 80 80>, <C0 80>, - or <A0> in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code - must check for illegal sequences. - - When the flag is set to lenient, characters over 0x10FFFF are converted - to the replacement character; otherwise (when the flag is set to strict) - they constitute an error. - - Output parameters: - The value "sourceIllegal" is returned from some routines if the input - sequence is malformed. When "sourceIllegal" is returned, the source - value will point to the illegal value that caused the problem. E.g., - in UTF-8 when a sequence is malformed, it points to the start of the - malformed sequence. - - Author: Mark E. Davis, 1994. - Rev History: Rick McGowan, fixes & updates May 2001. - Fixes & updates, Sept 2001. - ------------------------------------------------------------------------- */ - -/* --------------------------------------------------------------------- - The following 4 definitions are compiler-specific. - The C standard does not guarantee that wchar_t has at least - 16 bits, so wchar_t is no less portable than unsigned short! - All should be unsigned values to avoid sign extension during - bit mask & shift operations. ------------------------------------------------------------------------- */ - -/* Some fundamental constants */ -#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD -#define UNI_MAX_BMP (UTF32)0x0000FFFF -#define UNI_MAX_UTF16 (UTF32)0x0010FFFF -#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF - -namespace Unicode { - -typedef unsigned long UTF32; /* at least 32 bits */ -typedef unsigned short UTF16; /* at least 16 bits */ -typedef unsigned char UTF8; /* typically 8 bits */ -typedef unsigned char Boolean; /* 0 or 1 */ - -typedef enum { - conversionOK = 0, /* conversion successful */ - sourceExhausted = 1, /* partial character in source, but hit end */ - targetExhausted = 2, /* insuff. room in target for conversion */ - sourceIllegal = 3 /* source sequence is illegal/malformed */ -} ConversionResult; - -typedef enum { - strictConversion = 0, - lenientConversion -} ConversionFlags; - -ConversionResult ConvertUTF8toUTF16 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); - -ConversionResult ConvertUTF16toUTF8 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); - -Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); - -} // namespace Unicode - -/* --------------------------------------------------------------------- */ - -#endif -#endif diff --git a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.cpp b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.cpp index 0f362da9f..e4de436ed 100644 --- a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.cpp +++ b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.cpp @@ -31,6 +31,9 @@ #include <tstring.h> #include <tdebug.h> #include <tagunion.h> +#include <tstringlist.h> +#include <tpropertymap.h> +#include <tagutils.h> #include "trueaudiofile.h" #include "id3v1tag.h" @@ -41,7 +44,7 @@ using namespace TagLib; namespace { - enum { ID3v2Index = 0, ID3v1Index = 1 }; + enum { TrueAudioID3v2Index = 0, TrueAudioID3v1Index = 1 }; } class TrueAudio::File::FilePrivate @@ -52,9 +55,7 @@ public: ID3v2Location(-1), ID3v2OriginalSize(0), ID3v1Location(-1), - properties(0), - hasID3v1(false), - hasID3v2(false) {} + properties(0) {} ~FilePrivate() { @@ -63,40 +64,63 @@ public: const ID3v2::FrameFactory *ID3v2FrameFactory; long ID3v2Location; - uint ID3v2OriginalSize; + long ID3v2OriginalSize; long ID3v1Location; TagUnion tag; Properties *properties; - - // These indicate whether the file *on disk* has these tags, not if - // this data structure does. This is used in computing offsets. - - bool hasID3v1; - bool hasID3v2; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool TrueAudio::File::isSupported(IOStream *stream) +{ + // A TrueAudio file has to start with "TTA". An ID3v2 tag may precede. + + const ByteVector id = Utils::readHeader(stream, 3, true); + return (id == "TTA"); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -TrueAudio::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(file) +TrueAudio::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } TrueAudio::File::File(FileName file, ID3v2::FrameFactory *frameFactory, - bool readProperties, Properties::ReadStyle propertiesStyle) : - TagLib::File(file) + bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate(frameFactory)) { - d = new FilePrivate(frameFactory); if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); +} + +TrueAudio::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties); +} + +TrueAudio::File::File(IOStream *stream, ID3v2::FrameFactory *frameFactory, + bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate(frameFactory)) +{ + if(isOpen()) + read(readProperties); } TrueAudio::File::~File() @@ -109,6 +133,24 @@ TagLib::Tag *TrueAudio::File::tag() const return &d->tag; } +PropertyMap TrueAudio::File::properties() const +{ + return d->tag.properties(); +} + +void TrueAudio::File::removeUnsupportedProperties(const StringList &unsupported) +{ + d->tag.removeUnsupportedProperties(unsupported); +} + +PropertyMap TrueAudio::File::setProperties(const PropertyMap &properties) +{ + if(ID3v1Tag()) + ID3v1Tag()->setProperties(properties); + + return ID3v2Tag(true)->setProperties(properties); +} + TrueAudio::Properties *TrueAudio::File::audioProperties() const { return d->properties; @@ -129,40 +171,59 @@ bool TrueAudio::File::save() // Update ID3v2 tag if(ID3v2Tag() && !ID3v2Tag()->isEmpty()) { - if(!d->hasID3v2) { + + // ID3v2 tag is not empty. Update the old one or create a new one. + + if(d->ID3v2Location < 0) d->ID3v2Location = 0; + + const ByteVector data = ID3v2Tag()->render(); + insert(data, d->ID3v2Location, d->ID3v2OriginalSize); + + if(d->ID3v1Location >= 0) + d->ID3v1Location += (static_cast<long>(data.size()) - d->ID3v2OriginalSize); + + d->ID3v2OriginalSize = data.size(); + } + else { + + // ID3v2 tag is empty. Remove the old one. + + if(d->ID3v2Location >= 0) { + removeBlock(d->ID3v2Location, d->ID3v2OriginalSize); + + if(d->ID3v1Location >= 0) + d->ID3v1Location -= d->ID3v2OriginalSize; + + d->ID3v2Location = -1; d->ID3v2OriginalSize = 0; } - ByteVector data = ID3v2Tag()->render(); - insert(data, d->ID3v2Location, d->ID3v2OriginalSize); - d->ID3v1Location -= d->ID3v2OriginalSize - data.size(); - d->ID3v2OriginalSize = data.size(); - d->hasID3v2 = true; - } - else if(d->hasID3v2) { - removeBlock(d->ID3v2Location, d->ID3v2OriginalSize); - d->ID3v1Location -= d->ID3v2OriginalSize; - d->ID3v2Location = -1; - d->ID3v2OriginalSize = 0; - d->hasID3v2 = false; } // Update ID3v1 tag if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) { - if(!d->hasID3v1) { + + // ID3v1 tag is not empty. Update the old one or create a new one. + + if(d->ID3v1Location >= 0) { + seek(d->ID3v1Location); + } + else { seek(0, End); d->ID3v1Location = tell(); } - else - seek(d->ID3v1Location); + writeBlock(ID3v1Tag()->render()); - d->hasID3v1 = true; } - else if(d->hasID3v1) { - removeBlock(d->ID3v1Location, 128); - d->ID3v1Location = -1; - d->hasID3v1 = false; + else { + + // ID3v1 tag is empty. Remove the old one. + + if(d->ID3v1Location >= 0) { + truncate(d->ID3v1Location); + d->ID3v1Location = -1; + } } return true; @@ -170,103 +231,80 @@ bool TrueAudio::File::save() ID3v1::Tag *TrueAudio::File::ID3v1Tag(bool create) { - return d->tag.access<ID3v1::Tag>(ID3v1Index, create); + return d->tag.access<ID3v1::Tag>(TrueAudioID3v1Index, create); } ID3v2::Tag *TrueAudio::File::ID3v2Tag(bool create) { - return d->tag.access<ID3v2::Tag>(ID3v2Index, create); + return d->tag.access<ID3v2::Tag>(TrueAudioID3v2Index, create); } void TrueAudio::File::strip(int tags) { - if(tags & ID3v1) { - d->tag.set(ID3v1Index, 0); + if(tags & ID3v1) + d->tag.set(TrueAudioID3v1Index, 0); + + if(tags & ID3v2) + d->tag.set(TrueAudioID3v2Index, 0); + + if(!ID3v1Tag()) ID3v2Tag(true); - } - - if(tags & ID3v2) { - d->tag.set(ID3v2Index, 0); - - if(!ID3v1Tag()) - ID3v2Tag(true); - } } +bool TrueAudio::File::hasID3v1Tag() const +{ + return (d->ID3v1Location >= 0); +} + +bool TrueAudio::File::hasID3v2Tag() const +{ + return (d->ID3v2Location >= 0); +} //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void TrueAudio::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) +void TrueAudio::File::read(bool readProperties) { // Look for an ID3v2 tag - d->ID3v2Location = findID3v2(); + d->ID3v2Location = Utils::findID3v2(this); if(d->ID3v2Location >= 0) { - - d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory)); - + d->tag.set(TrueAudioID3v2Index, new ID3v2::Tag(this, d->ID3v2Location, d->ID3v2FrameFactory)); d->ID3v2OriginalSize = ID3v2Tag()->header()->completeTagSize(); - - if(ID3v2Tag()->header()->tagSize() <= 0) - d->tag.set(ID3v2Index, 0); - else - d->hasID3v2 = true; } // Look for an ID3v1 tag - d->ID3v1Location = findID3v1(); + d->ID3v1Location = Utils::findID3v1(this); - if(d->ID3v1Location >= 0) { - d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); - d->hasID3v1 = true; - } + if(d->ID3v1Location >= 0) + d->tag.set(TrueAudioID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); - if(!d->hasID3v1) + if(d->ID3v1Location < 0) ID3v2Tag(true); // Look for TrueAudio metadata if(readProperties) { + + long streamLength; + + if(d->ID3v1Location >= 0) + streamLength = d->ID3v1Location; + else + streamLength = length(); + if(d->ID3v2Location >= 0) { seek(d->ID3v2Location + d->ID3v2OriginalSize); - d->properties = new Properties(readBlock(TrueAudio::HeaderSize), - length() - d->ID3v2OriginalSize); + streamLength -= (d->ID3v2Location + d->ID3v2OriginalSize); } else { seek(0); - d->properties = new Properties(readBlock(TrueAudio::HeaderSize), - length()); } + + d->properties = new Properties(readBlock(TrueAudio::HeaderSize), streamLength); } } - -long TrueAudio::File::findID3v1() -{ - if(!isValid()) - return -1; - - seek(-128, End); - long p = tell(); - - if(readBlock(3) == ID3v1::Tag::fileIdentifier()) - return p; - - return -1; -} - -long TrueAudio::File::findID3v2() -{ - if(!isValid()) - return -1; - - seek(0); - - if(readBlock(3) == ID3v2::Header::fileIdentifier()) - return 0; - - return -1; -} diff --git a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.h b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.h index 32cbf4b1a..1f664d25d 100644 --- a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.h +++ b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudiofile.h @@ -79,23 +79,55 @@ namespace TagLib { }; /*! - * Contructs an TrueAudio file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * Constructs a TrueAudio file from \a file. If \a readProperties is true + * the file's audio properties will also be read. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); /*! - * Contructs an TrueAudio file from \a file. If \a readProperties is true the - * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. The frames will be created using + * Constructs a TrueAudio file from \a file. If \a readProperties is true + * the file's audio properties will also be read. + * + * If this file contains and ID3v2 tag the frames will be created using * \a frameFactory. + * + * \note In the current implementation, \a propertiesStyle is ignored. */ File(FileName file, ID3v2::FrameFactory *frameFactory, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs a TrueAudio file from \a stream. If \a readProperties is true + * the file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Constructs a TrueAudio file from \a stream. If \a readProperties is true + * the file's audio properties will also be read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + * + * If this file contains and ID3v2 tag the frames will be created using + * \a frameFactory. + * + * \note In the current implementation, \a propertiesStyle is ignored. + */ + File(IOStream *stream, ID3v2::FrameFactory *frameFactory, + bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ @@ -106,6 +138,22 @@ namespace TagLib { */ virtual TagLib::Tag *tag() const; + /*! + * Implements the unified property interface -- export function. + * If the file contains both ID3v1 and v2 tags, only ID3v2 will be + * converted to the PropertyMap. + */ + PropertyMap properties() const; + + /*! + * Implements the unified property interface -- import function. + * Creates in ID3v2 tag if necessary. If an ID3v1 tag exists, it will + * be updated as well, within the limitations of ID3v1. + */ + PropertyMap setProperties(const PropertyMap &); + + void removeUnsupportedProperties(const StringList &properties); + /*! * Returns the TrueAudio::Properties for this file. If no audio properties * were read then this will return a null pointer. @@ -116,8 +164,9 @@ namespace TagLib { * Set the ID3v2::FrameFactory to something other than the default. * * \see ID3v2FrameFactory + * \deprecated This value should be passed in via the constructor */ - void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); + TAGLIB_DEPRECATED void setID3v2FrameFactory(const ID3v2::FrameFactory *factory); /*! * Saves the file. @@ -125,30 +174,40 @@ namespace TagLib { virtual bool save(); /*! - * Returns a pointer to the ID3v2 tag of the file. + * Returns a pointer to the ID3v1 tag of the file. * - * If \a create is false (the default) this will return a null pointer - * if there is no valid ID3v2 tag. If \a create is true it will create - * an ID3v1 tag if one does not exist. If there is already an APE tag, the - * new ID3v1 tag will be placed after it. + * If \a create is false (the default) this may return a null pointer + * if there is no valid ID3v1 tag. If \a create is true it will create + * an ID3v1 tag if one does not exist and returns a valid pointer. * - * \note The Tag <b>is still</b> owned by the TrueAudio::File and should not be + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * on disk actually has an ID3v1 tag. + * + * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasID3v1Tag() */ ID3v1::Tag *ID3v1Tag(bool create = false); /*! - * Returns a pointer to the ID3v1 tag of the file. + * Returns a pointer to the ID3v2 tag of the file. * - * If \a create is false (the default) this will return a null pointer - * if there is no valid ID3v1 tag. If \a create is true it will create - * an ID3v1 tag if one does not exist. If there is already an APE tag, the - * new ID3v1 tag will be placed after it. + * If \a create is false (the default) this may return a null pointer + * if there is no valid ID3v2 tag. If \a create is true it will create + * an ID3v2 tag if one does not exist and returns a valid pointer. * - * \note The Tag <b>is still</b> owned by the TrueAudio::File and should not be + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file + * on disk actually has an ID3v2 tag. + * + * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasID3v2Tag() */ ID3v2::Tag *ID3v2Tag(bool create = false); @@ -162,14 +221,34 @@ namespace TagLib { */ void strip(int tags = AllTags); + /*! + * Returns whether or not the file on disk actually has an ID3v1 tag. + * + * \see ID3v1Tag() + */ + bool hasID3v1Tag() const; + + /*! + * Returns whether or not the file on disk actually has an ID3v2 tag. + * + * \see ID3v2Tag() + */ + bool hasID3v2Tag() const; + + /*! + * Returns whether or not the given \a stream can be opened as a TrueAudio + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); - void scan(); - long findID3v1(); - long findID3v2(); + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudioproperties.cpp b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudioproperties.cpp index 5b1bf12d4..0aab24193 100644 --- a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudioproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudioproperties.cpp @@ -39,36 +39,33 @@ using namespace TagLib; class TrueAudio::Properties::PropertiesPrivate { public: - PropertiesPrivate(const ByteVector &d, long length, ReadStyle s) : - data(d), - streamLength(length), - style(s), + PropertiesPrivate() : version(0), length(0), bitrate(0), sampleRate(0), channels(0), - bitsPerSample(0) {} + bitsPerSample(0), + sampleFrames(0) {} - ByteVector data; - long streamLength; - ReadStyle style; int version; int length; int bitrate; int sampleRate; int channels; int bitsPerSample; + unsigned int sampleFrames; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -TrueAudio::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style) +TrueAudio::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(data, streamLength, style); - read(); + read(data, streamLength); } TrueAudio::Properties::~Properties() @@ -77,6 +74,16 @@ TrueAudio::Properties::~Properties() } int TrueAudio::Properties::length() const +{ + return lengthInSeconds(); +} + +int TrueAudio::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int TrueAudio::Properties::lengthInMilliseconds() const { return d->length; } @@ -101,6 +108,11 @@ int TrueAudio::Properties::channels() const return d->channels; } +unsigned int TrueAudio::Properties::sampleFrames() const +{ + return d->sampleFrames; +} + int TrueAudio::Properties::ttaVersion() const { return d->version; @@ -110,27 +122,50 @@ int TrueAudio::Properties::ttaVersion() const // private members //////////////////////////////////////////////////////////////////////////////// -void TrueAudio::Properties::read() +void TrueAudio::Properties::read(const ByteVector &data, long streamLength) { - if(!d->data.startsWith("TTA")) + if(data.size() < 4) { + debug("TrueAudio::Properties::read() -- data is too short."); return; + } - int pos = 3; + if(!data.startsWith("TTA")) { + debug("TrueAudio::Properties::read() -- invalid header signature."); + return; + } - d->version = d->data[pos] - '0'; - pos += 1 + 2; + unsigned int pos = 3; - d->channels = d->data.mid(pos, 2).toShort(false); - pos += 2; + d->version = data[pos] - '0'; + pos += 1; - d->bitsPerSample = d->data.mid(pos, 2).toShort(false); - pos += 2; + // According to http://en.true-audio.com/TTA_Lossless_Audio_Codec_-_Format_Description + // TTA2 headers are in development, and have a different format + if(1 == d->version) { + if(data.size() < 18) { + debug("TrueAudio::Properties::read() -- data is too short."); + return; + } - d->sampleRate = d->data.mid(pos, 4).toUInt(false); - pos += 4; + // Skip the audio format + pos += 2; - unsigned long samples = d->data.mid(pos, 4).toUInt(false); - d->length = samples / d->sampleRate; + d->channels = data.toShort(pos, false); + pos += 2; - d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; + d->bitsPerSample = data.toShort(pos, false); + pos += 2; + + d->sampleRate = data.toUInt(pos, false); + pos += 4; + + d->sampleFrames = data.toUInt(pos, false); + pos += 4; + + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast<int>(length + 0.5); + d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5); + } + } } diff --git a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudioproperties.h b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudioproperties.h index f66fd2e9a..d25c7a775 100644 --- a/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudioproperties.h +++ b/Frameworks/TagLib/taglib/taglib/trueaudio/trueaudioproperties.h @@ -38,7 +38,7 @@ namespace TagLib { class File; - static const uint HeaderSize = 18; + static const unsigned int HeaderSize = 18; //! An implementation of audio property reading for TrueAudio @@ -61,18 +61,58 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; - virtual int length() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns number of bits per sample. + * Returns the number of bits per audio sample. */ int bitsPerSample() const; + /*! + * Returns the total number of sample frames + */ + unsigned int sampleFrames() const; + /*! * Returns the major version number. */ @@ -82,7 +122,7 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void read(const ByteVector &data, long streamLength); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/wavpack/wavpackfile.cpp b/Frameworks/TagLib/taglib/taglib/wavpack/wavpackfile.cpp index 999a5445d..56b993934 100644 --- a/Frameworks/TagLib/taglib/taglib/wavpack/wavpackfile.cpp +++ b/Frameworks/TagLib/taglib/taglib/wavpack/wavpackfile.cpp @@ -31,6 +31,8 @@ #include <tstring.h> #include <tdebug.h> #include <tagunion.h> +#include <tpropertymap.h> +#include <tagutils.h> #include "wavpackfile.h" #include "id3v1tag.h" @@ -42,7 +44,7 @@ using namespace TagLib; namespace { - enum { APEIndex, ID3v1Index }; + enum { WavAPEIndex, WavID3v1Index }; } class WavPack::File::FilePrivate @@ -52,9 +54,7 @@ public: APELocation(-1), APESize(0), ID3v1Location(-1), - properties(0), - hasAPE(false), - hasID3v1(false) {} + properties(0) {} ~FilePrivate() { @@ -62,30 +62,45 @@ public: } long APELocation; - uint APESize; + long APESize; long ID3v1Location; TagUnion tag; Properties *properties; - - // These indicate whether the file *on disk* has these tags, not if - // this data structure does. This is used in computing offsets. - - bool hasAPE; - bool hasID3v1; }; +//////////////////////////////////////////////////////////////////////////////// +// static members +//////////////////////////////////////////////////////////////////////////////// + +bool WavPack::File::isSupported(IOStream *stream) +{ + // A WavPack file has to start with "wvpk". + + const ByteVector id = Utils::readHeader(stream, 4, false); + return (id == "wvpk"); +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -WavPack::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : TagLib::File(file) +WavPack::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + TagLib::File(file), + d(new FilePrivate()) { - d = new FilePrivate; - read(readProperties, propertiesStyle); + if(isOpen()) + read(readProperties); +} + +WavPack::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + TagLib::File(stream), + d(new FilePrivate()) +{ + if(isOpen()) + read(readProperties); } WavPack::File::~File() @@ -98,6 +113,24 @@ TagLib::Tag *WavPack::File::tag() const return &d->tag; } +PropertyMap WavPack::File::properties() const +{ + return d->tag.properties(); +} + +void WavPack::File::removeUnsupportedProperties(const StringList &unsupported) +{ + d->tag.removeUnsupportedProperties(unsupported); +} + +PropertyMap WavPack::File::setProperties(const PropertyMap &properties) +{ + if(ID3v1Tag()) + ID3v1Tag()->setProperties(properties); + + return APETag(true)->setProperties(properties); +} + WavPack::Properties *WavPack::File::audioProperties() const { return d->properties; @@ -112,156 +145,140 @@ bool WavPack::File::save() // Update ID3v1 tag - if(ID3v1Tag()) { - if(d->hasID3v1) { + if(ID3v1Tag() && !ID3v1Tag()->isEmpty()) { + + // ID3v1 tag is not empty. Update the old one or create a new one. + + if(d->ID3v1Location >= 0) { seek(d->ID3v1Location); - writeBlock(ID3v1Tag()->render()); } else { seek(0, End); d->ID3v1Location = tell(); - writeBlock(ID3v1Tag()->render()); - d->hasID3v1 = true; } + + writeBlock(ID3v1Tag()->render()); } else { - if(d->hasID3v1) { - removeBlock(d->ID3v1Location, 128); - d->hasID3v1 = false; - if(d->hasAPE) { - if(d->APELocation > d->ID3v1Location) - d->APELocation -= 128; - } + + // ID3v1 tag is empty. Remove the old one. + + if(d->ID3v1Location >= 0) { + truncate(d->ID3v1Location); + d->ID3v1Location = -1; } } // Update APE tag - if(APETag()) { - if(d->hasAPE) - insert(APETag()->render(), d->APELocation, d->APESize); - else { - if(d->hasID3v1) { - insert(APETag()->render(), d->ID3v1Location, 0); - d->APESize = APETag()->footer()->completeTagSize(); - d->hasAPE = true; + if(APETag() && !APETag()->isEmpty()) { + + // APE tag is not empty. Update the old one or create a new one. + + if(d->APELocation < 0) { + if(d->ID3v1Location >= 0) d->APELocation = d->ID3v1Location; - d->ID3v1Location += d->APESize; - } - else { - seek(0, End); - d->APELocation = tell(); - writeBlock(APETag()->render()); - d->APESize = APETag()->footer()->completeTagSize(); - d->hasAPE = true; - } + else + d->APELocation = length(); } + + const ByteVector data = APETag()->render(); + insert(data, d->APELocation, d->APESize); + + if(d->ID3v1Location >= 0) + d->ID3v1Location += (static_cast<long>(data.size()) - d->APESize); + + d->APESize = data.size(); } else { - if(d->hasAPE) { + + // APE tag is empty. Remove the old one. + + if(d->APELocation >= 0) { removeBlock(d->APELocation, d->APESize); - d->hasAPE = false; - if(d->hasID3v1) { - if(d->ID3v1Location > d->APELocation) { - d->ID3v1Location -= d->APESize; - } - } + + if(d->ID3v1Location >= 0) + d->ID3v1Location -= d->APESize; + + d->APELocation = -1; + d->APESize = 0; } } - return true; + return true; } ID3v1::Tag *WavPack::File::ID3v1Tag(bool create) { - return d->tag.access<ID3v1::Tag>(ID3v1Index, create); + return d->tag.access<ID3v1::Tag>(WavID3v1Index, create); } APE::Tag *WavPack::File::APETag(bool create) { - return d->tag.access<APE::Tag>(APEIndex, create); + return d->tag.access<APE::Tag>(WavAPEIndex, create); } void WavPack::File::strip(int tags) { - if(tags & ID3v1) { - d->tag.set(ID3v1Index, 0); + if(tags & ID3v1) + d->tag.set(WavID3v1Index, 0); + + if(tags & APE) + d->tag.set(WavAPEIndex, 0); + + if(!ID3v1Tag()) APETag(true); - } +} - if(tags & APE) { - d->tag.set(APEIndex, 0); +bool WavPack::File::hasID3v1Tag() const +{ + return (d->ID3v1Location >= 0); +} - if(!ID3v1Tag()) - APETag(true); - } +bool WavPack::File::hasAPETag() const +{ + return (d->APELocation >= 0); } //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void WavPack::File::read(bool readProperties, Properties::ReadStyle /* propertiesStyle */) +void WavPack::File::read(bool readProperties) { // Look for an ID3v1 tag - d->ID3v1Location = findID3v1(); + d->ID3v1Location = Utils::findID3v1(this); - if(d->ID3v1Location >= 0) { - d->tag.set(ID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); - d->hasID3v1 = true; - } + if(d->ID3v1Location >= 0) + d->tag.set(WavID3v1Index, new ID3v1::Tag(this, d->ID3v1Location)); // Look for an APE tag - d->APELocation = findAPE(); + d->APELocation = Utils::findAPE(this, d->ID3v1Location); if(d->APELocation >= 0) { - d->tag.set(APEIndex, new APE::Tag(this, d->APELocation)); + d->tag.set(WavAPEIndex, new APE::Tag(this, d->APELocation)); d->APESize = APETag()->footer()->completeTagSize(); - d->APELocation = d->APELocation + APETag()->footer()->size() - d->APESize; - d->hasAPE = true; + d->APELocation = d->APELocation + APE::Footer::size() - d->APESize; } - if(!d->hasID3v1) + if(d->ID3v1Location < 0) APETag(true); // Look for WavPack audio properties if(readProperties) { - seek(0); - d->properties = new Properties(this, length() - d->APESize); + + long streamLength; + + if(d->APELocation >= 0) + streamLength = d->APELocation; + else if(d->ID3v1Location >= 0) + streamLength = d->ID3v1Location; + else + streamLength = length(); + + d->properties = new Properties(this, streamLength); } } - -long WavPack::File::findAPE() -{ - if(!isValid()) - return -1; - - if(d->hasID3v1) - seek(-160, End); - else - seek(-32, End); - - long p = tell(); - - if(readBlock(8) == APE::Tag::fileIdentifier()) - return p; - - return -1; -} - -long WavPack::File::findID3v1() -{ - if(!isValid()) - return -1; - - seek(-128, End); - long p = tell(); - - if(readBlock(3) == ID3v1::Tag::fileIdentifier()) - return p; - - return -1; -} diff --git a/Frameworks/TagLib/taglib/taglib/wavpack/wavpackfile.h b/Frameworks/TagLib/taglib/taglib/wavpack/wavpackfile.h index 3415a329b..ccc4ef6e8 100644 --- a/Frameworks/TagLib/taglib/taglib/wavpack/wavpackfile.h +++ b/Frameworks/TagLib/taglib/taglib/wavpack/wavpackfile.h @@ -80,13 +80,24 @@ namespace TagLib { }; /*! - * Contructs an WavPack file from \a file. If \a readProperties is true the + * Constructs a WavPack file from \a file. If \a readProperties is true the * file's audio properties will also be read using \a propertiesStyle. If - * false, \a propertiesStyle is ignored. + * false, \a propertiesStyle is ignored */ File(FileName file, bool readProperties = true, Properties::ReadStyle propertiesStyle = Properties::Average); + /*! + * Constructs an WavPack file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + /*! * Destroys this instance of the File. */ @@ -98,6 +109,22 @@ namespace TagLib { */ virtual TagLib::Tag *tag() const; + /*! + * Implements the unified property interface -- export function. + * If the file contains both an APE and an ID3v1 tag, only APE + * will be converted to the PropertyMap. + */ + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. + * Creates an APE tag if it does not exists and calls setProperties() on + * that. Any existing ID3v1 tag will be updated as well. + */ + PropertyMap setProperties(const PropertyMap&); + /*! * Returns the MPC::Properties for this file. If no audio properties * were read then this will return a null pointer. @@ -106,33 +133,46 @@ namespace TagLib { /*! * Saves the file. + * + * This returns true if the save was successful. */ virtual bool save(); /*! * Returns a pointer to the ID3v1 tag of the file. * - * If \a create is false (the default) this will return a null pointer + * If \a create is false (the default) this may return a null pointer * if there is no valid ID3v1 tag. If \a create is true it will create - * an ID3v1 tag if one does not exist. If there is already an APE tag, the - * new ID3v1 tag will be placed after it. + * an ID3v1 tag if one does not exist and returns a valid pointer. * - * \note The Tag <b>is still</b> owned by the APE::File and should not be + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * on disk actually has an ID3v1 tag. + * + * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasID3v1Tag() */ ID3v1::Tag *ID3v1Tag(bool create = false); /*! * Returns a pointer to the APE tag of the file. * - * If \a create is false (the default) this will return a null pointer + * If \a create is false (the default) this may return a null pointer * if there is no valid APE tag. If \a create is true it will create - * a APE tag if one does not exist. + * an APE tag if one does not exist and returns a valid pointer. * - * \note The Tag <b>is still</b> owned by the APE::File and should not be + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file + * on disk actually has an APE tag. + * + * \note The Tag <b>is still</b> owned by the MPEG::File and should not be * deleted by the user. It will be deleted when the file (object) is * destroyed. + * + * \see hasAPETag() */ APE::Tag *APETag(bool create = false); @@ -146,14 +186,33 @@ namespace TagLib { */ void strip(int tags = AllTags); + /*! + * Returns whether or not the file on disk actually has an ID3v1 tag. + * + * \see ID3v1Tag() + */ + bool hasID3v1Tag() const; + + /*! + * Returns whether or not the file on disk actually has an APE tag. + * + * \see APETag() + */ + bool hasAPETag() const; + + /*! + * Check if the given \a stream can be opened as a WavPack file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + private: File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); - void scan(); - long findID3v1(); - long findAPE(); + void read(bool readProperties); class FilePrivate; FilePrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/wavpack/wavpackproperties.cpp b/Frameworks/TagLib/taglib/taglib/wavpack/wavpackproperties.cpp index 52552a0d0..d5808be56 100644 --- a/Frameworks/TagLib/taglib/taglib/wavpack/wavpackproperties.cpp +++ b/Frameworks/TagLib/taglib/taglib/wavpack/wavpackproperties.cpp @@ -27,57 +27,57 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include <stdint.h> #include <tstring.h> #include <tdebug.h> #include "wavpackproperties.h" #include "wavpackfile.h" +// Implementation of this class is based on the information at: +// http://www.wavpack.com/file_format.txt + using namespace TagLib; class WavPack::Properties::PropertiesPrivate { public: - PropertiesPrivate(const ByteVector &d, long length, ReadStyle s) : - data(d), - streamLength(length), - style(s), + PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), version(0), bitsPerSample(0), - file(0) {} + lossless(false), + sampleFrames(0) {} - ByteVector data; - long streamLength; - ReadStyle style; int length; int bitrate; int sampleRate; int channels; int version; int bitsPerSample; - File *file; + bool lossless; + unsigned int sampleFrames; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -WavPack::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style) +WavPack::Properties::Properties(const ByteVector &, long, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(data, streamLength, style); - read(); + debug("WavPack::Properties::Properties() -- This constructor is no longer used."); } -WavPack::Properties::Properties(File *file, long streamLength, ReadStyle style) : AudioProperties(style) +WavPack::Properties::Properties(File *file, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - ByteVector data = file->readBlock(32); - d = new PropertiesPrivate(data, streamLength, style); - d->file = file; - read(); + read(file, streamLength); } WavPack::Properties::~Properties() @@ -86,6 +86,16 @@ WavPack::Properties::~Properties() } int WavPack::Properties::length() const +{ + return lengthInSeconds(); +} + +int WavPack::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int WavPack::Properties::lengthInMilliseconds() const { return d->length; } @@ -115,15 +125,24 @@ int WavPack::Properties::bitsPerSample() const return d->bitsPerSample; } +bool WavPack::Properties::isLossless() const +{ + return d->lossless; +} + +unsigned int WavPack::Properties::sampleFrames() const +{ + return d->sampleFrames; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -static const unsigned int sample_rates[] = { 6000, 8000, 9600, 11025, 12000, - 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 }; - #define BYTES_STORED 3 #define MONO_FLAG 4 +#define HYBRID_FLAG 8 +#define DSD_FLAG 0x80000000 // block is encoded DSD (1-bit PCM) #define SHIFT_LSB 13 #define SHIFT_MASK (0x1fL << SHIFT_LSB) @@ -131,64 +150,225 @@ static const unsigned int sample_rates[] = { 6000, 8000, 9600, 11025, 12000, #define SRATE_LSB 23 #define SRATE_MASK (0xfL << SRATE_LSB) -#define MIN_STREAM_VERS 0x402 -#define MAX_STREAM_VERS 0x410 +#define MIN_STREAM_VERS 0x402 +#define MAX_STREAM_VERS 0x410 +#define INITIAL_BLOCK 0x800 #define FINAL_BLOCK 0x1000 -void WavPack::Properties::read() +#define ID_DSD_BLOCK 0x0e +#define ID_OPTIONAL_DATA 0x20 +#define ID_UNIQUE 0x3f +#define ID_ODD_SIZE 0x40 +#define ID_LARGE 0x80 +#define ID_SAMPLE_RATE (ID_OPTIONAL_DATA | 0x7) + +namespace { - if(!d->data.startsWith("wvpk")) - return; + const unsigned int sampleRates[] = { + 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000, 192000, 0 }; - d->version = d->data.mid(8, 2).toShort(false); - if(d->version < MIN_STREAM_VERS || d->version > MAX_STREAM_VERS) - return; + /*! + * Given a WavPack \a block (complete, but not including the 32-byte header), + * parse the metadata blocks until an \a id block is found and return the + * contained data, or zero if no such block is found. + * Supported values for \a id are ID_SAMPLE_RATE and ID_DSD_BLOCK. + */ + int getMetaDataChunk(const ByteVector &block, unsigned char id) + { + if(id != ID_SAMPLE_RATE && id != ID_DSD_BLOCK) + return 0; - unsigned int flags = d->data.mid(24, 4).toUInt(false); - d->bitsPerSample = ((flags & BYTES_STORED) + 1) * 8 - - ((flags & SHIFT_MASK) >> SHIFT_LSB); - d->sampleRate = sample_rates[(flags & SRATE_MASK) >> SRATE_LSB]; - d->channels = (flags & MONO_FLAG) ? 1 : 2; + const int blockSize = static_cast<int>(block.size()); + int index = 0; - unsigned int samples = d->data.mid(12, 4).toUInt(false); - if(samples == ~0u) { - if(d->file && d->style != Fast) { - samples = seekFinalIndex(); - } - else { - samples = 0; + while(index + 1 < blockSize) { + const unsigned char metaId = static_cast<unsigned char>(block[index]); + int metaBc = static_cast<unsigned char>(block[index + 1]) << 1; + index += 2; + + if(metaId & ID_LARGE) { + if(index + 2 > blockSize) + return 0; + + metaBc += (static_cast<uint32_t>(static_cast<unsigned char>(block[index])) << 9) + + (static_cast<uint32_t>(static_cast<unsigned char>(block[index + 1])) << 17); + index += 2; + } + + if(index + metaBc > blockSize) + return 0; + + // if we got a sample rate, return it + + if(id == ID_SAMPLE_RATE && (metaId & ID_UNIQUE) == ID_SAMPLE_RATE && metaBc == 4) { + int sampleRate = static_cast<int32_t>(static_cast<unsigned char>(block[index])); + sampleRate |= static_cast<int32_t>(static_cast<unsigned char>(block[index + 1])) << 8; + sampleRate |= static_cast<int32_t>(static_cast<unsigned char>(block[index + 2])) << 16; + + // only use 4th byte if it's really there + + if(!(metaId & ID_ODD_SIZE)) + sampleRate |= static_cast<int32_t>(static_cast<unsigned char>(block[index + 3]) & 0x7f) << 24; + + return sampleRate; + } + + // if we got DSD block, return the specified rate shift amount + + if(id == ID_DSD_BLOCK && (metaId & ID_UNIQUE) == ID_DSD_BLOCK && metaBc > 0) { + const unsigned char rateShift = static_cast<unsigned char>(block[index]); + if(rateShift <= 31) + return rateShift; + } + + index += metaBc; } + + return 0; + } + + /*! + * Given a WavPack block (complete, but not including the 32-byte header), + * parse the metadata blocks until an ID_SAMPLE_RATE block is found and + * return the non-standard sample rate contained there, or zero if no such + * block is found. + */ + int getNonStandardRate(const ByteVector &block) + { + return getMetaDataChunk(block, ID_SAMPLE_RATE); + } + + /*! + * Given a WavPack block (complete, but not including the 32-byte header), + * parse the metadata blocks until a DSD audio data block is found and return + * the sample-rate shift value contained there, or zero if no such block is + * found. The nominal sample rate of DSD audio files (found in the header) + * must be left-shifted by this amount to get the actual "byte" sample rate. + * Note that 8-bit bytes are the "atoms" of the DSD audio coding (for + * decoding, seeking, etc), so the shifted rate must be further multiplied by + * 8 to get the actual DSD bit sample rate. + */ + int getDsdRateShifter(const ByteVector &block) + { + return getMetaDataChunk(block, ID_DSD_BLOCK); } - d->length = d->sampleRate > 0 ? (samples + (d->sampleRate / 2)) / d->sampleRate : 0; - d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; } -unsigned int WavPack::Properties::seekFinalIndex() +void WavPack::Properties::read(File *file, long streamLength) { - ByteVector blockID("wvpk", 4); + long offset = 0; + + while(true) { + file->seek(offset); + const ByteVector data = file->readBlock(32); + + if(data.size() < 32) { + debug("WavPack::Properties::read() -- data is too short."); + break; + } + + if(!data.startsWith("wvpk")) { + debug("WavPack::Properties::read() -- Block header not found."); + break; + } + + const unsigned int blockSize = data.toUInt(4, false); + const unsigned int sampleFrames = data.toUInt(12, false); + const unsigned int blockSamples = data.toUInt(20, false); + const unsigned int flags = data.toUInt(24, false); + unsigned int sampleRate = sampleRates[(flags & SRATE_MASK) >> SRATE_LSB]; + + if(!blockSamples) { // ignore blocks with no samples + offset += blockSize + 8; + continue; + } + + if(blockSize < 24 || blockSize > 1048576) { + debug("WavPack::Properties::read() -- Invalid block header found."); + break; + } + + // For non-standard sample rates or DSD audio files, we must read and parse the block + // to actually determine the sample rate. + + if(!sampleRate || (flags & DSD_FLAG)) { + const unsigned int adjustedBlockSize = blockSize - 24; + const ByteVector block = file->readBlock(adjustedBlockSize); + + if(block.size() < adjustedBlockSize) { + debug("WavPack::Properties::read() -- block is too short."); + break; + } + + if(!sampleRate) + sampleRate = static_cast<unsigned int>(getNonStandardRate(block)); + + if(sampleRate && (flags & DSD_FLAG)) + sampleRate <<= getDsdRateShifter(block); + } + + if(flags & INITIAL_BLOCK) { + d->version = data.toShort(8, false); + if(d->version < MIN_STREAM_VERS || d->version > MAX_STREAM_VERS) + break; + + d->bitsPerSample = ((flags & BYTES_STORED) + 1) * 8 - ((flags & SHIFT_MASK) >> SHIFT_LSB); + d->sampleRate = static_cast<int>(sampleRate); + d->lossless = !(flags & HYBRID_FLAG); + d->sampleFrames = sampleFrames; + } + + d->channels += (flags & MONO_FLAG) ? 1 : 2; + + if(flags & FINAL_BLOCK) + break; + + offset += blockSize + 8; + } + + if(d->sampleFrames == ~0u) + d->sampleFrames = seekFinalIndex(file, streamLength); + + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast<int>(length + 0.5); + d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5); + } +} + +unsigned int WavPack::Properties::seekFinalIndex(File *file, long streamLength) +{ + long offset = streamLength; + + while (offset >= 32) { + offset = file->rfind("wvpk", offset - 4); - long offset = d->streamLength; - while(offset > 0) { - offset = d->file->rfind(blockID, offset); if(offset == -1) return 0; - d->file->seek(offset); - ByteVector data = d->file->readBlock(32); - if(data.size() != 32) + + file->seek(offset); + const ByteVector data = file->readBlock(32); + if(data.size() < 32) return 0; - int version = data.mid(8, 2).toShort(false); - if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS) - continue; - unsigned int flags = data.mid(24, 4).toUInt(false); - if(!(flags & FINAL_BLOCK)) - return 0; - unsigned int blockIndex = data.mid(16, 4).toUInt(false); - unsigned int blockSamples = data.mid(20, 4).toUInt(false); - return blockIndex + blockSamples; + + const unsigned int blockSize = data.toUInt(4, false); + const unsigned int blockIndex = data.toUInt(16, false); + const unsigned int blockSamples = data.toUInt(20, false); + const unsigned int flags = data.toUInt(24, false); + const int version = data.toShort(8, false); + + // try not to trigger on a spurious "wvpk" in WavPack binary block data + + if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS || (blockSize & 1) || + blockSize < 24 || blockSize >= 1048576 || blockSamples > 131072) + continue; + + if (blockSamples && (flags & FINAL_BLOCK)) + return blockIndex + blockSamples; } return 0; } - diff --git a/Frameworks/TagLib/taglib/taglib/wavpack/wavpackproperties.h b/Frameworks/TagLib/taglib/taglib/wavpack/wavpackproperties.h index 74d18ea87..e6acdcc3c 100644 --- a/Frameworks/TagLib/taglib/taglib/wavpack/wavpackproperties.h +++ b/Frameworks/TagLib/taglib/taglib/wavpack/wavpackproperties.h @@ -39,7 +39,7 @@ namespace TagLib { class File; - static const uint HeaderSize = 32; + static const unsigned int HeaderSize = 32; //! An implementation of audio property reading for WavPack @@ -58,12 +58,12 @@ namespace TagLib { * \deprecated This constructor will be dropped in favor of the one below * in a future version. */ - Properties(const ByteVector &data, long streamLength, ReadStyle style = Average); + TAGLIB_DEPRECATED Properties(const ByteVector &data, long streamLength, + ReadStyle style = Average); /*! * Create an instance of WavPack::Properties. */ - // BIC: merge with the above constructor Properties(File *file, long streamLength, ReadStyle style = Average); /*! @@ -71,18 +71,63 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ + TAGLIB_DEPRECATED virtual int length() const; - virtual int length() const; + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; + + /*! + * Returns the sample rate in Hz. 0 means unknown or custom. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns number of bits per sample. + * Returns the number of bits per audio sample. */ int bitsPerSample() const; + /*! + * Returns whether or not the file is lossless encoded. + */ + bool isLossless() const; + + /*! + * Returns the total number of audio samples in file. + */ + unsigned int sampleFrames() const; + /*! * Returns WavPack version. */ @@ -92,8 +137,8 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); - unsigned int seekFinalIndex(); + void read(File *file, long streamLength); + unsigned int seekFinalIndex(File *file, long streamLength); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/Frameworks/TagLib/taglib/taglib/xm/xmfile.cpp b/Frameworks/TagLib/taglib/taglib/xm/xmfile.cpp new file mode 100644 index 000000000..0455338d9 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/xm/xmfile.cpp @@ -0,0 +1,644 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "tstringlist.h" +#include "tdebug.h" +#include "xmfile.h" +#include "modfileprivate.h" +#include "tpropertymap.h" + +#include <string.h> +#include <algorithm> + +using namespace TagLib; +using namespace XM; + +/*! + * The Reader classes are helpers to make handling of the stripped XM + * format more easy. In the stripped XM format certain header sizes might + * be smaller than one would expect. The fields that are not included + * are then just some predefined valued (e.g. 0). + * + * Using these classes this code: + * + * if(headerSize >= 4) { + * if(!readU16L(value1)) ERROR(); + * if(headerSize >= 8) { + * if(!readU16L(value2)) ERROR(); + * if(headerSize >= 12) { + * if(!readString(value3, 22)) ERROR(); + * ... + * } + * } + * } + * + * Becomes: + * + * StructReader header; + * header.u16L(value1).u16L(value2).string(value3, 22). ...; + * if(header.read(*this, headerSize) < std::min(header.size(), headerSize)) + * ERROR(); + * + * Maybe if this is useful to other formats these classes can be moved to + * their own public files. + */ +class Reader +{ +public: + virtual ~Reader() + { + } + + /*! + * Reads associated values from \a file, but never reads more + * then \a limit bytes. + */ + virtual unsigned int read(TagLib::File &file, unsigned int limit) = 0; + + /*! + * Returns the number of bytes this reader would like to read. + */ + virtual unsigned int size() const = 0; +}; + +class SkipReader : public Reader +{ +public: + SkipReader(unsigned int size) : m_size(size) + { + } + + unsigned int read(TagLib::File &file, unsigned int limit) + { + unsigned int count = std::min(m_size, limit); + file.seek(count, TagLib::File::Current); + return count; + } + + unsigned int size() const + { + return m_size; + } + +private: + unsigned int m_size; +}; + +template<typename T> +class ValueReader : public Reader +{ +public: + ValueReader(T &value) : value(value) + { + } + +protected: + T &value; +}; + +class StringReader : public ValueReader<String> +{ +public: + StringReader(String &string, unsigned int size) : + ValueReader<String>(string), m_size(size) + { + } + + unsigned int read(TagLib::File &file, unsigned int limit) + { + ByteVector data = file.readBlock(std::min(m_size, limit)); + unsigned int count = data.size(); + int index = data.find((char) 0); + if(index > -1) { + data.resize(index); + } + data.replace('\xff', ' '); + value = data; + return count; + } + + unsigned int size() const + { + return m_size; + } + +private: + unsigned int m_size; +}; + +class ByteReader : public ValueReader<unsigned char> +{ +public: + ByteReader(unsigned char &byte) : ValueReader<unsigned char>(byte) {} + + unsigned int read(TagLib::File &file, unsigned int limit) + { + ByteVector data = file.readBlock(std::min(1U,limit)); + if(data.size() > 0) { + value = data[0]; + } + return data.size(); + } + + unsigned int size() const + { + return 1; + } +}; + +template<typename T> +class NumberReader : public ValueReader<T> +{ +public: + NumberReader(T &value, bool bigEndian) : + ValueReader<T>(value), bigEndian(bigEndian) + { + } + +protected: + bool bigEndian; +}; + +class U16Reader : public NumberReader<unsigned short> +{ +public: + U16Reader(unsigned short &value, bool bigEndian) + : NumberReader<unsigned short>(value, bigEndian) {} + + unsigned int read(TagLib::File &file, unsigned int limit) + { + ByteVector data = file.readBlock(std::min(2U,limit)); + value = data.toUShort(bigEndian); + return data.size(); + } + + unsigned int size() const + { + return 2; + } +}; + +class U32Reader : public NumberReader<unsigned long> +{ +public: + U32Reader(unsigned long &value, bool bigEndian = true) : + NumberReader<unsigned long>(value, bigEndian) + { + } + + unsigned int read(TagLib::File &file, unsigned int limit) + { + ByteVector data = file.readBlock(std::min(4U,limit)); + value = data.toUInt(bigEndian); + return data.size(); + } + + unsigned int size() const + { + return 4; + } +}; + +class StructReader : public Reader +{ +public: + StructReader() + { + m_readers.setAutoDelete(true); + } + + /*! + * Add a nested reader. This reader takes ownership. + */ + StructReader &reader(Reader *reader) + { + m_readers.append(reader); + return *this; + } + + /*! + * Don't read anything but skip \a size bytes. + */ + StructReader &skip(unsigned int size) + { + m_readers.append(new SkipReader(size)); + return *this; + } + + /*! + * Read a string of \a size characters (bytes) into \a string. + */ + StructReader &string(String &string, unsigned int size) + { + m_readers.append(new StringReader(string, size)); + return *this; + } + + /*! + * Read a byte into \a byte. + */ + StructReader &byte(unsigned char &byte) + { + m_readers.append(new ByteReader(byte)); + return *this; + } + + /*! + * Read a unsigned 16 Bit integer into \a number. The byte order + * is controlled by \a bigEndian. + */ + StructReader &u16(unsigned short &number, bool bigEndian) + { + m_readers.append(new U16Reader(number, bigEndian)); + return *this; + } + + /*! + * Read a unsigned 16 Bit little endian integer into \a number. + */ + StructReader &u16L(unsigned short &number) + { + return u16(number, false); + } + + /*! + * Read a unsigned 16 Bit big endian integer into \a number. + */ + StructReader &u16B(unsigned short &number) + { + return u16(number, true); + } + + /*! + * Read a unsigned 32 Bit integer into \a number. The byte order + * is controlled by \a bigEndian. + */ + StructReader &u32(unsigned long &number, bool bigEndian) + { + m_readers.append(new U32Reader(number, bigEndian)); + return *this; + } + + /*! + * Read a unsigned 32 Bit little endian integer into \a number. + */ + StructReader &u32L(unsigned long &number) + { + return u32(number, false); + } + + /*! + * Read a unsigned 32 Bit big endian integer into \a number. + */ + StructReader &u32B(unsigned long &number) + { + return u32(number, true); + } + + unsigned int size() const + { + unsigned int size = 0; + for(List<Reader*>::ConstIterator i = m_readers.begin(); + i != m_readers.end(); ++ i) { + size += (*i)->size(); + } + return size; + } + + unsigned int read(TagLib::File &file, unsigned int limit) + { + unsigned int sumcount = 0; + for(List<Reader*>::ConstIterator i = m_readers.begin(); + limit > 0 && i != m_readers.end(); ++ i) { + unsigned int count = (*i)->read(file, limit); + limit -= count; + sumcount += count; + } + return sumcount; + } + +private: + List<Reader*> m_readers; +}; + +class XM::File::FilePrivate +{ +public: + FilePrivate(AudioProperties::ReadStyle propertiesStyle) + : tag(), properties(propertiesStyle) + { + } + + Mod::Tag tag; + XM::Properties properties; +}; + +XM::File::File(FileName file, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + Mod::FileBase(file), + d(new FilePrivate(propertiesStyle)) +{ + if(isOpen()) + read(readProperties); +} + +XM::File::File(IOStream *stream, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + Mod::FileBase(stream), + d(new FilePrivate(propertiesStyle)) +{ + if(isOpen()) + read(readProperties); +} + +XM::File::~File() +{ + delete d; +} + +Mod::Tag *XM::File::tag() const +{ + return &d->tag; +} + +PropertyMap XM::File::properties() const +{ + return d->tag.properties(); +} + +PropertyMap XM::File::setProperties(const PropertyMap &properties) +{ + return d->tag.setProperties(properties); +} + +XM::Properties *XM::File::audioProperties() const +{ + return &d->properties; +} + +bool XM::File::save() +{ + if(readOnly()) { + debug("XM::File::save() - Cannot save to a read only file."); + return false; + } + + seek(17); + writeString(d->tag.title(), 20); + + seek(38); + writeString(d->tag.trackerName(), 20); + + seek(60); + unsigned long headerSize = 0; + if(!readU32L(headerSize)) + return false; + + seek(70); + unsigned short patternCount = 0; + unsigned short instrumentCount = 0; + if(!readU16L(patternCount) || !readU16L(instrumentCount)) + return false; + + long pos = 60 + headerSize; // should be long long in taglib2. + + // need to read patterns again in order to seek to the instruments: + for(unsigned short i = 0; i < patternCount; ++ i) { + seek(pos); + unsigned long patternHeaderLength = 0; + if(!readU32L(patternHeaderLength) || patternHeaderLength < 4) + return false; + + seek(pos + 7); + unsigned short dataSize = 0; + if (!readU16L(dataSize)) + return false; + + pos += patternHeaderLength + dataSize; + } + + const StringList lines = d->tag.comment().split("\n"); + unsigned int sampleNameIndex = instrumentCount; + for(unsigned short i = 0; i < instrumentCount; ++ i) { + seek(pos); + unsigned long instrumentHeaderSize = 0; + if(!readU32L(instrumentHeaderSize) || instrumentHeaderSize < 4) + return false; + + seek(pos + 4); + const unsigned int len = std::min(22UL, instrumentHeaderSize - 4U); + if(i >= lines.size()) + writeString(String(), len); + else + writeString(lines[i], len); + + unsigned short sampleCount = 0; + if(instrumentHeaderSize >= 29U) { + seek(pos + 27); + if(!readU16L(sampleCount)) + return false; + } + + unsigned long sampleHeaderSize = 0; + if(sampleCount > 0) { + seek(pos + 29); + if(instrumentHeaderSize < 33U || !readU32L(sampleHeaderSize)) + return false; + } + + pos += instrumentHeaderSize; + + for(unsigned short j = 0; j < sampleCount; ++ j) { + if(sampleHeaderSize > 4U) { + seek(pos); + unsigned long sampleLength = 0; + if(!readU32L(sampleLength)) + return false; + + if(sampleHeaderSize > 18U) { + seek(pos + 18); + const unsigned int len = std::min(sampleHeaderSize - 18U, 22UL); + if(sampleNameIndex >= lines.size()) + writeString(String(), len); + else + writeString(lines[sampleNameIndex ++], len); + } + } + pos += sampleHeaderSize; + } + } + + return true; +} + +void XM::File::read(bool) +{ + if(!isOpen()) + return; + + seek(0); + ByteVector magic = readBlock(17); + // it's all 0x00 for stripped XM files: + READ_ASSERT(magic == "Extended Module: " || magic == ByteVector(17, 0)); + + READ_STRING(d->tag.setTitle, 20); + READ_BYTE_AS(escape); + // in stripped XM files this is 0x00: + READ_ASSERT(escape == 0x1A || escape == 0x00); + + READ_STRING(d->tag.setTrackerName, 20); + READ_U16L(d->properties.setVersion); + + READ_U32L_AS(headerSize); + READ_ASSERT(headerSize >= 4); + + unsigned short length = 0; + unsigned short restartPosition = 0; + unsigned short channels = 0; + unsigned short patternCount = 0; + unsigned short instrumentCount = 0; + unsigned short flags = 0; + unsigned short tempo = 0; + unsigned short bpmSpeed = 0; + + StructReader header; + header.u16L(length) + .u16L(restartPosition) + .u16L(channels) + .u16L(patternCount) + .u16L(instrumentCount) + .u16L(flags) + .u16L(tempo) + .u16L(bpmSpeed); + + unsigned int count = header.read(*this, headerSize - 4U); + unsigned int size = std::min(headerSize - 4U, (unsigned long)header.size()); + + READ_ASSERT(count == size); + + d->properties.setLengthInPatterns(length); + d->properties.setRestartPosition(restartPosition); + d->properties.setChannels(channels); + d->properties.setPatternCount(patternCount); + d->properties.setInstrumentCount(instrumentCount); + d->properties.setFlags(flags); + d->properties.setTempo(tempo); + d->properties.setBpmSpeed(bpmSpeed); + + seek(60 + headerSize); + + // read patterns: + for(unsigned short i = 0; i < patternCount; ++ i) { + READ_U32L_AS(patternHeaderLength); + READ_ASSERT(patternHeaderLength >= 4); + + unsigned char packingType = 0; + unsigned short rowCount = 0; + unsigned short dataSize = 0; + StructReader pattern; + pattern.byte(packingType).u16L(rowCount).u16L(dataSize); + + unsigned int count = pattern.read(*this, patternHeaderLength - 4U); + READ_ASSERT(count == std::min(patternHeaderLength - 4U, (unsigned long)pattern.size())); + + seek(patternHeaderLength - (4 + count) + dataSize, Current); + } + + StringList instrumentNames; + StringList sampleNames; + unsigned int sumSampleCount = 0; + + // read instruments: + for(unsigned short i = 0; i < instrumentCount; ++ i) { + READ_U32L_AS(instrumentHeaderSize); + READ_ASSERT(instrumentHeaderSize >= 4); + + String instrumentName; + unsigned char instrumentType = 0; + unsigned short sampleCount = 0; + + StructReader instrument; + instrument.string(instrumentName, 22).byte(instrumentType).u16L(sampleCount); + + // 4 for instrumentHeaderSize + unsigned int count = 4 + instrument.read(*this, instrumentHeaderSize - 4U); + READ_ASSERT(count == std::min(instrumentHeaderSize, (unsigned long)instrument.size() + 4)); + + long offset = 0; + if(sampleCount > 0) { + unsigned long sampleHeaderSize = 0; + sumSampleCount += sampleCount; + // wouldn't know which header size to assume otherwise: + READ_ASSERT(instrumentHeaderSize >= count + 4 && readU32L(sampleHeaderSize)); + // skip unhandled header proportion: + seek(instrumentHeaderSize - count - 4, Current); + + for(unsigned short j = 0; j < sampleCount; ++ j) { + unsigned long sampleLength = 0; + unsigned long loopStart = 0; + unsigned long loopLength = 0; + unsigned char volume = 0; + unsigned char finetune = 0; + unsigned char sampleType = 0; + unsigned char panning = 0; + unsigned char noteNumber = 0; + unsigned char compression = 0; + String sampleName; + StructReader sample; + sample.u32L(sampleLength) + .u32L(loopStart) + .u32L(loopLength) + .byte(volume) + .byte(finetune) + .byte(sampleType) + .byte(panning) + .byte(noteNumber) + .byte(compression) + .string(sampleName, 22); + + unsigned int count = sample.read(*this, sampleHeaderSize); + READ_ASSERT(count == std::min(sampleHeaderSize, (unsigned long)sample.size())); + // skip unhandled header proportion: + seek(sampleHeaderSize - count, Current); + + offset += sampleLength; + sampleNames.append(sampleName); + } + } + else { + offset = instrumentHeaderSize - count; + } + instrumentNames.append(instrumentName); + seek(offset, Current); + } + + d->properties.setSampleCount(sumSampleCount); + String comment(instrumentNames.toString("\n")); + if(!sampleNames.isEmpty()) { + comment += "\n"; + comment += sampleNames.toString("\n"); + } + d->tag.setComment(comment); +} diff --git a/Frameworks/TagLib/taglib/taglib/xm/xmfile.h b/Frameworks/TagLib/taglib/taglib/xm/xmfile.h new file mode 100644 index 000000000..9211078ad --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/xm/xmfile.h @@ -0,0 +1,112 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_XMFILE_H +#define TAGLIB_XMFILE_H + +#include "tfile.h" +#include "audioproperties.h" +#include "taglib_export.h" +#include "modfilebase.h" +#include "modtag.h" +#include "xmproperties.h" + +namespace TagLib { + + namespace XM { + + class TAGLIB_EXPORT File : public Mod::FileBase { + public: + /*! + * Constructs an Extended Module file from \a file. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + */ + File(FileName file, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Constructs an Extended Module file from \a stream. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + File(IOStream *stream, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + Mod::Tag *tag() const; + + /*! + * Implements the unified property interface -- export function. + * Forwards to Mod::Tag::properties(). + */ + PropertyMap properties() const; + + /*! + * Implements the unified property interface -- import function. + * Forwards to Mod::Tag::setProperties(). + */ + PropertyMap setProperties(const PropertyMap &); + + /*! + * Returns the XM::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + XM::Properties *audioProperties() const; + + /*! + * Save the file. + * This is the same as calling save(AllTags); + * + * \note Saving Extended Module tags is not supported. + */ + bool save(); + + private: + File(const File &); + File &operator=(const File &); + + void read(bool readProperties); + + class FilePrivate; + FilePrivate *d; + }; + } +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib/xm/xmproperties.cpp b/Frameworks/TagLib/taglib/taglib/xm/xmproperties.cpp new file mode 100644 index 000000000..93d849868 --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/xm/xmproperties.cpp @@ -0,0 +1,195 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include "xmproperties.h" + +using namespace TagLib; +using namespace XM; + +class XM::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate() : + lengthInPatterns(0), + channels(0), + version(0), + restartPosition(0), + patternCount(0), + instrumentCount(0), + sampleCount(0), + flags(0), + tempo(0), + bpmSpeed(0) + { + } + + unsigned short lengthInPatterns; + int channels; + unsigned short version; + unsigned short restartPosition; + unsigned short patternCount; + unsigned short instrumentCount; + unsigned int sampleCount; + unsigned short flags; + unsigned short tempo; + unsigned short bpmSpeed; +}; + +XM::Properties::Properties(AudioProperties::ReadStyle propertiesStyle) : + AudioProperties(propertiesStyle), + d(new PropertiesPrivate()) +{ +} + +XM::Properties::~Properties() +{ + delete d; +} + +int XM::Properties::length() const +{ + return 0; +} + +int XM::Properties::lengthInSeconds() const +{ + return 0; +} + +int XM::Properties::lengthInMilliseconds() const +{ + return 0; +} + +int XM::Properties::bitrate() const +{ + return 0; +} + +int XM::Properties::sampleRate() const +{ + return 0; +} + +int XM::Properties::channels() const +{ + return d->channels; +} + +unsigned short XM::Properties::lengthInPatterns() const +{ + return d->lengthInPatterns; +} + +unsigned short XM::Properties::version() const +{ + return d->version; +} + +unsigned short XM::Properties::restartPosition() const +{ + return d->restartPosition; +} + +unsigned short XM::Properties::patternCount() const +{ + return d->patternCount; +} + +unsigned short XM::Properties::instrumentCount() const +{ + return d->instrumentCount; +} + +unsigned int XM::Properties::sampleCount() const +{ + return d->sampleCount; +} + +unsigned short XM::Properties::flags() const +{ + return d->flags; +} + +unsigned short XM::Properties::tempo() const +{ + return d->tempo; +} + +unsigned short XM::Properties::bpmSpeed() const +{ + return d->bpmSpeed; +} + +void XM::Properties::setLengthInPatterns(unsigned short lengthInPatterns) +{ + d->lengthInPatterns = lengthInPatterns; +} + +void XM::Properties::setChannels(int channels) +{ + d->channels = channels; +} + +void XM::Properties::setVersion(unsigned short version) +{ + d->version = version; +} + +void XM::Properties::setRestartPosition(unsigned short restartPosition) +{ + d->restartPosition = restartPosition; +} + +void XM::Properties::setPatternCount(unsigned short patternCount) +{ + d->patternCount = patternCount; +} + +void XM::Properties::setInstrumentCount(unsigned short instrumentCount) +{ + d->instrumentCount = instrumentCount; +} + +void XM::Properties::setSampleCount(unsigned int sampleCount) +{ + d->sampleCount = sampleCount; +} + +void XM::Properties::setFlags(unsigned short flags) +{ + d->flags = flags; +} + +void XM::Properties::setTempo(unsigned short tempo) +{ + d->tempo = tempo; +} + +void XM::Properties::setBpmSpeed(unsigned short bpmSpeed) +{ + d->bpmSpeed = bpmSpeed; +} diff --git a/Frameworks/TagLib/taglib/taglib/xm/xmproperties.h b/Frameworks/TagLib/taglib/taglib/xm/xmproperties.h new file mode 100644 index 000000000..24a52217a --- /dev/null +++ b/Frameworks/TagLib/taglib/taglib/xm/xmproperties.h @@ -0,0 +1,85 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_XMPROPERTIES_H +#define TAGLIB_XMPROPERTIES_H + +#include "taglib.h" +#include "tstring.h" +#include "audioproperties.h" + +namespace TagLib { + namespace XM { + class TAGLIB_EXPORT Properties : public AudioProperties { + friend class File; + public: + /*! Flag bits. */ + enum { + LinearFreqTable = 1 // otherwise its the amiga freq. table + }; + + Properties(AudioProperties::ReadStyle propertiesStyle); + virtual ~Properties(); + + int length() const; + int lengthInSeconds() const; + int lengthInMilliseconds() const; + int bitrate() const; + int sampleRate() const; + int channels() const; + + unsigned short lengthInPatterns() const; + unsigned short version() const; + unsigned short restartPosition() const; + unsigned short patternCount() const; + unsigned short instrumentCount() const; + unsigned int sampleCount() const; + unsigned short flags() const; + unsigned short tempo() const; + unsigned short bpmSpeed() const; + + void setChannels(int channels); + + void setLengthInPatterns(unsigned short lengthInPatterns); + void setVersion(unsigned short version); + void setRestartPosition(unsigned short restartPosition); + void setPatternCount(unsigned short patternCount); + void setInstrumentCount(unsigned short instrumentCount); + void setSampleCount(unsigned int sampleCount); + void setFlags(unsigned short flags); + void setTempo(unsigned short tempo); + void setBpmSpeed(unsigned short bpmSpeed); + + private: + Properties(const Properties&); + Properties &operator=(const Properties&); + + class PropertiesPrivate; + PropertiesPrivate *d; + }; + } +} + +#endif diff --git a/Frameworks/TagLib/taglib/taglib_config.h b/Frameworks/TagLib/taglib/taglib_config.h index fdc3aab87..915f130aa 100644 --- a/Frameworks/TagLib/taglib/taglib_config.h +++ b/Frameworks/TagLib/taglib/taglib_config.h @@ -1,4 +1,11 @@ /* taglib_config.h. Generated by cmake from taglib_config.h.cmake */ -#define TAGLIB_WITH_ASF 1 -#define TAGLIB_WITH_MP4 1 \ No newline at end of file +#ifndef TAGLIB_TAGLIB_CONFIG_H +#define TAGLIB_TAGLIB_CONFIG_H + +/* These values are no longer used. This file is present only for compatibility reasons. */ + +#define TAGLIB_WITH_ASF 1 +#define TAGLIB_WITH_MP4 1 + +#endif diff --git a/Frameworks/TagLib/taglib/tests/CMakeLists.txt b/Frameworks/TagLib/taglib/tests/CMakeLists.txt new file mode 100644 index 000000000..b3dbb8ee2 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/CMakeLists.txt @@ -0,0 +1,80 @@ +set(CMAKE_CXX_STANDARD 11) + +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/toolkit + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ape + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/asf + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v1 + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2 + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2/frames + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpc + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mp4 + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/riff + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/riff/aiff + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/riff/wav + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/trueaudio + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/vorbis + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/flac + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/speex + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/opus + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/flac + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/wavpack + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mod + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/s3m + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/it + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/xm +) + +SET(test_runner_SRCS + main.cpp + test_list.cpp + test_map.cpp + test_mpeg.cpp + test_synchdata.cpp + test_trueaudio.cpp + test_bytevector.cpp + test_bytevectorlist.cpp + test_bytevectorstream.cpp + test_string.cpp + test_propertymap.cpp + test_file.cpp + test_fileref.cpp + test_id3v1.cpp + test_id3v2.cpp + test_xiphcomment.cpp + test_aiff.cpp + test_riff.cpp + test_ogg.cpp + test_oggflac.cpp + test_flac.cpp + test_flacpicture.cpp + test_flacunknownmetadatablock.cpp + test_ape.cpp + test_apetag.cpp + test_wav.cpp + test_info.cpp + test_wavpack.cpp + test_mp4.cpp + test_mp4item.cpp + test_mp4coverart.cpp + test_asf.cpp + test_mod.cpp + test_s3m.cpp + test_it.cpp + test_xm.cpp + test_mpc.cpp + test_opus.cpp + test_speex.cpp +) + +INCLUDE_DIRECTORIES(${CPPUNIT_INCLUDE_DIR}) + +ADD_EXECUTABLE(test_runner ${test_runner_SRCS}) +TARGET_LINK_LIBRARIES(test_runner tag ${CPPUNIT_LIBRARIES}) + +ADD_TEST(test_runner test_runner) +ADD_CUSTOM_TARGET(check COMMAND ${CMAKE_CTEST_COMMAND} -V + DEPENDS test_runner) diff --git a/Frameworks/TagLib/taglib/tests/data/005411.id3 b/Frameworks/TagLib/taglib/tests/data/005411.id3 new file mode 100644 index 0000000000000000000000000000000000000000..ab2e0997a0bcbf27aa169a09fd3f730d8319de9d GIT binary patch literal 38402 zcmbTdWmFtb^ex&12$0}}1Rpd&aCZhta7l0{2^QR428ZAhAi)#d-Q6`1+;xD#-5G{E zzyDq9-Y@UNd#`47Pft&GSM61&tIpo%l>B=hGynjev3O`pt12Up90A~)i;IIW2Zy`6 zJNp;gFJAU`>?ZcM9A+M7CLH!(Elus6IV9EPRXC(L|0~?QS|2%_?af^r>@DqF%$(S` zIl-o8Zfq`M@3{oz_*CW9)IRa)Xi771^P7CJbr9p`<mN*j+gRFJ8=Kghnu&SXawBX0 zuWEGUW@cvrNPd);27su2<WEc6FBWDTRt^>bD)2P_v<~1a$jHk8C@27cg8TqaOMo<h zj*5nchKi2dp`)We$H2zKKo-20FR`!*@CXSB@CXQqUQ@gwdPPP|KtMu6LPkjm0)dF$ z(9+XV(Nj=?sQ!Bh3i4Kr=NPz{n7CB02wqYBe=bj*00AZnD+((r3N7%A00or*<*63{ zA=`<L@;?QM=6?#xGgLHWD>1QNyhL75hYvhMK}CIrY(EkJ@@ik?{{R{RIw1|0<a44= zUodDLUvUS-|Hh<~s_G(En>eNC`RWvi_2Ts#5>hgTw~S28?|Aw61q6kJrQd&$k(HBI zP}k7Z($>+{Gch$YxA<mh<?Q0>=I-I?6%-s28W#RNA|dfdQgX`AU#Wj`a`W;F3X6)X zYijH28ycIMyL)>3`UeJwh9{?{XJ+T-7Zx`*p<CNKu-(1=v-69~tLvNFyZirep#Z4= z8y0f^|A>nKiR&4%ThK86!-ew99l4<rprO-nJtvg>gz?3Zh?YA5^OaQm@2V~=Iv%xC z;;&8<FJ9C0ZZMqv2kn25{l5bi`2P#p{{i-YaV-Mas3^$JLnQ#fz&(Y~m&D~yYZrx^ zAf|hb9Lf-%5sN}bJr%41s2Do7(8*QqJVDa<Cpj4|wWE_eHGDBk15<C`FsRJhm#jSb z7@jCKCN#(RLs{N(v=3bUU@n<8BRs1Vl5xF{1#Kb$xD!Lri@X=yoH%kLL5Xr06xZ3$ zk6_dnauRfNleq)=Oe~$Svs^Su$7!|Tbqe#`MPXcKw<rrz<S8*3riG>F`KU`*IrgGZ zu%0vxOTg4=9+RpO<VBi&18<(})T}?v3t?!-tKR^WYKGc~r|T-OA9L&*!Ru8o^XNtd z!JW58Bh}V}+7X%)o~i;n8V%24My->V*VkNXY*RJo^h=Ne5czp%ph7`H#DX?|5zi;} zqtLNrA1BR1zx-cv8S#@nb(8a}+{MOz5jE1+T?vi>l1ixO72E2zjC2v4vSQ5hnAf<$ zc*7|p22a3M@yBd`8D`7@0Cbx7nT9L&IO1EZSg{WkmO-GbY97hG%YhQ6l;gjTA5lAv zhx7ZLJO^ge(p~#~_X4oN?hOhV1WQRWUk<TK%gmpE2M&q$>3S^XvX;R@FA<dUqWg5b z{%WG0*WAG=+7B{$g~X5Wsc|MPyLH-1O#>K!+tb0f64oue*RnvWU?q~_p2&eZ1nE-d zNMwYcBfmYeqWIVZ-k)LL8&-tWy2qe^{xU&+R+qp4f3BA0C*a-C6A=7oo?kYY*Un?p z(mBQ(wv71NqNOZQoU-u*pc%manKKJGQ=~(xEu&v$^^wOK|CkG?h-91aQ^m5B7Tj~? zm+`zB|037U9q!alRvX<M%c0g})VuX1tc>GOFYE84@3wR1WmU-D)W0cz#&lonZt#Xs zoE>OETks%YlYC>q!1w+W)Np}Lv31OGApFE%3_}^NhUhO@78-Z$8kbhAJeL6bS73_s zpf^q0hmSvEr>u6Is67GQmM@*|8KwxP#A(~WDrHCTy(hp#<K)qIW7NBUw%@HDLp_3@ zDow@fvI3KeuZ}Hwg1u&%V3T~j;->8h_#_K|>!$P0M@)o6Bw(&Q#^VMIyU2MUD}WrF z;F_$8=05?MN|1G4Pc^(dQ<%8_TnMDwFprC&^<7_+kgeT%=E8w8<n=cg%M*YZh_Cv$ zMI)(ZT+J~RTvhCjXg_o2S!w-+B^W(0t#+s8s<Vc;<6DRJHPbw-;sjK!)RBs*8mG)A ziR%B}B0klu5EOjy&Vn32e0q!o@4&(9vA?dxmZb&0t$~mK(IyI>XPs9(w|{->>rd!G z)i3efVNO46KTA*bR#8;+1+3G{5?b`r;tBXL@%a6JZja2k7CRV9skOiAd(JGY^V5%# zr7JcPE;0_+e<Tfi7=&D>6!3=iWs<)mfeZetNf4GD5JbWS>w&8*;A>%U{GAk&)nMB( zjiA*)%=IYkK*mTPeiiVB;jSkJ1*<RkWW0{|cA)1=CkU69HBcC{1AHaElXXr11f-ol z0fFKc^EWm9&QZAS({>u`)fH8I>plJAAXM;axr=gp04=Noeg=Q{;VeJm4d}G|Qi&*m z3smsvnDamg`FjiLB1${D&D@=ojrxTT2#w$}mx(o}^Yuz(HaQ}|#>1^52%>0W8H}Ds z@~9_ZoD13MN^OulAs_c#7IZ3L5}yzQUPB7&IBqLM^rl?+flfb+v#)(WDs#6A+G(l( zH1|sbV%(o&E31NYVWma=u$+Aot9KT18;}P;HiM@e`#obx#~+>Urz6ks6wyq&v|jde zR#QI#i-ydl$;SV+55(Kz$5>fkmRrcWSf8H#*=No&=GTC+LWC;5hadk-#&8!?yGDW2 z*qxajVaC|Qin=zis__fG?i6i;PE%cih&R-z(f(9&<6__!lT_l9n%l1zDSIn(xao^& zKfT2w1=5e`-3KrP3p2IJ^v1LhZSqN44YANC05}HquD*%FLC{aUS$dG7f}HJtIdc!L z#Dmi>av04OdPA$q=@74JqK+barff)`fRtP%Y+Qfl!tLAqiJ@)rsNF6BPJzcaVji(1 z&^tVZkbvy#jKTZ9Yvb=LknS%VZ;>_-oFzN{=vz@P=KBq4Z`z=9&b_|@gG6=%>VqI@ z1KuZq$W>4@2pnd|_Ig86VosY|>Mf;H$RCt8k3(mr#@jrIP0@AqTg;nuYT@AE#JPZN zi#&qPfA;ctO9$4J^JkEdh$mps>tB?s;p(r;90cJL@Nx^KT-P+Cal~ROpU0pg4E|*^ z+HY*<MDg@0w?6q6Pc*ekZX{0pgSlifEVD<DQB<A1RTuh=CV5j81EW4F;X;4EL>Sui zjC0gMNDu?HOt!L%xly?IAa4e-*_K=8P3e4F(?jEqV?=~FbP_n%o0vMWsbl%#USQbe zZr2+?PiSut6v!>|pq;V%1Q2XDwKv3mtWxyW57SB5Up*WxJd`k8FW!)+wj=HLy)YQ) z4_EGp*hrq;Kkhm%du<(3AD=@g-!|tB$mm-Y4#BpGn@4E)juo#6D=OL=;`b0jLZ|$t zVzyOXPk^O0Y99puvW-!@dFoGXQ+rFOdDM}aTn{X1XascoB6aO-6|#Xo6Uvijn`<)j z1EP1<L8-_pE`EkqX<+bv=iD*PQ<pL+BB6;g7PaD{%&GC>Q&L06-HSgedP&ELhI4h1 zB`1ev3dcL10)y0*NlB^_IWPYK7>uIkzdI)RGK5i`i@r7I<yH3Y<=47?fEC&J<Qzoo zZJ%22LU8NAMA5or`VW|YbPjva&v#M@-Q`paLZI2GY;)OLyq;SVR}9m<Zu%^>Hl^_u zXckG1@NZuDUQsus<%=Y#b<=qYe>oV7g#Yk;9?T<Epps@WrK%&PfTDsQtu9rGZ@m@g zeCS2pH@x|(;+WGuO+iVd8^rDYi64eLISHD<NNJluSi31k^m9kNk<7u$;=0?Y;sz2v zV<J?=*`Q6co3d}vy|Ev~VnXhQk<6CK+NntkwNr<7_Kcb%vi1aV6_om@u>-{H6}i@A zf_TMyWFb-SLoV29-u)T`>`0B;5m|csc(LV|EZ;9Xuyiclnm?3|Q@qtNvSh!`$kzpV zCFlgo2=%kHp1Q}nKwf1}xUy)}b~LI*TML{}H<4_~^!hAgAAKfq8`s+?i|w0KC@DDh zqZ!%p_`ECwY;E0Nq<)qA)oT+Lb-GaY1dx4;-hdDr?8Rj#Oz<1(-VK^+g=EoC5M7|Z z8+7snEXrPcCAXBU4HQ@|iU)~JXNPBpN!7#k861j5s@5&0=e+wGlr;kl%8ZWY6N$X} z`J<N|B(I+UFf8uh;TtrISihh5+7k{QEIsD41dt8GXNo~&ccZCZ=oX#W+S_A1>5zCM zzO$FAlZWE8MtjMx1INZ)|NQ(dqTv~lHVi#JU1>H6Fz~S1B;JA!Rofpk=O-5kmaCln z3>w^ecwt)lhJ4v924GBZ62^r;0m7G{?{4*zxBYnN9u^;_%Ff8%@8RT1?j(PdLmy5k zE*hc^x*!@u*Qh_UF?yw2dx9~qCwImp>s-1@<7rQ?9%!QZrCK!h$7?!(!d+OJvq+0& zVXDPz@lm8IC*0!0FIu<7tu4C1+^BmpBY9McC==3y@w+GBZJVt8ZIWe)h2q(2(igt; z0{g|}x8I;gDbe5Gffv;WvX4e@AG+l?#$V~~oh^r7X1ouwc6Mx*#2lkEh|yuD4qC>S zUr#OejLHF9gom-Lwe?jEg+K!_I|ndgaNI#JeONZeiTWV8Mc##ZaM4H)3YkA|NeEE` zbzp&lz*!*X-`MX)_!FXx8;Ux#G5SYZ_q*@sY6wLuiw#?AwO(|%proIAzHPEc-~XF^ zTc9%YDk*~by*mInzuYjTx*hy-oWqx)^uFq+u15B<Z9_)T^!TjHK?b8<`(*ii?R5$V zsf~#T8nF+gHkZPA_IGuCOUsWg6ANc|+NhuFA=_r82O(qP`7LX)!d45n-Af-|v~ZLT zi(@>OOS9ipX!et8@+InRr<tt1shw~gIy^E<%45O)Sid7kIzq!(Kyh}Yzj4NAy^$mA z=5zpQw5}WcecBO0uTFbwqGyItSQ&>abXodvATolLKq)o6-><bzxU;g!7Ii9slGWX2 zy`j<ud7V{sV8n7p{sfqh@VZ@B8;?C}?QHn9O%(I|lS8X1NLtUBYTE-&Y;R|q{E0t@ zkhh?eK{K6TeAPD3c~@WTgM%zzY1NCU1g|B<q_PU<o^`ZJxgMUl7scf}mtz?1;Gr`5 zf373pBhtV3NAb?sv$!ZR9z>^CDbAiZWdDLx@d>fE|I;4BjBoVKhMttTl-wBcpCwJA zLx}d7GckUcgu_TF{shgfwkag^U3f?EU)aM#%s)}#pBZ$ucDy`b@Y-7BzIPHZnfyHq z=h?);U17g6e7!%svUNUd`Ro<8Dv)Fn1#M?pZU<R4m~3p9H*ETTcn0EDtuw$68)ScP z10$g?#}t!eS)LguWXOtuV-)w~FYB*Kds;BY)8kU`779?4h)EY;^<!4xz%-$1lL|u% z$5<kUAi)8Ogb@mY_mhhB-zmNYu^#@d)4;x>54hr+fOMlkwW%T9d_GH_sH4FY&IgQ5 z<xZ<Ra?r?+_2Bkcvrz_*o}$%8xJ1R*TUasgD)z)y-Os%8-@X(vYDz30wnZbjMTlw< zAcf1bpT*N9QW+w8hW$%knYJ5>x@DrCd5ebnL)f#;x$?Xm{u7O#9USB5sHT^&_;qw; zDg0c{sWHR-Oy&|3yYCYRRP#i-@eTD4?qza$5BjPDubK1GJC~$TF9eF9=@++`g>b$v z*AH5aT5HKK%q%JBO)`pKi}KWq+dKm(5)Vl3)RXv_heYh{o&X4(D&R_?E;ncRFXmz0 zGf7LEIr``h5kK33^YWz)@kHVSt19x8Dc2F}pT}j#Wy3X|c=)j+)UlajXM+lr6xH!T zfkLvCGcFJFY^@`fW$bTt)OO_c_yXqr1ri&JGgb@!5yY_+pHT$~1Yv9MZ92Jn|J<$% zWwL!QJ^bZiz@~;vt`k?}MB|FKU7NG$MeSp-`HzCr-POgG5;0+M`@wggVG0E`j<uOt zb9c!I`c<o&dtNf!?5Y(*-+c0)tDQ$d9~+z|m=~Aolj1jr6MA-}aPdaEjrT70$O0Mq z+W!2t73Qr94zLJuHznGZTN1W_byBAulxEjAo@OTD^i=EZxjTPL;jRgw*Gl=;VG-xD zsJS?AL1Ft9gSg~$wMV{ju8!Q+SUI}RV_TrGMr1I&uYQb}BndlV>ljspAsm*v_4|9w zv)}~nPi>P%ZteGmR)-mhhQyJ1dOB7y+<FDK<yn)<+b$zG7#gX^9!G~9kV%6g5i_h7 zv~@zMz`j@jHGP)6hfptC?LuKPVfHK8KfMJ>`Vf^hR@+2NR_{1f<Cb_M7}Oi|Qapvd zU{hb;8K*jZl)E}A3SG;9i_!lTl?qGGmEsZgfh}Khuq{p_i@oJD-s*nLe)AtNRteC% zgp$PR1nV58z7fj5O#Q}U+JD?1R%-7`Zl*_}TB#8sS&48r_zS(54BV{T9>(L@Q)RN@ z{cP5dW&1HsPAmprPGK$BfXovRQ9K^-1W?iaegg6*XZed9#ro-68X6;B*$!t}_-0=& zk7}5%S8f49`<`X?(NpvYhRRfCEuF2YnfQN!rP$T$fr7N{EvSD-POOf{UzAo9?Hlnw z0r3JK$s@Mu41!{+vnyVmHGxkvt!)PxKgPwVf;b0^p8)Lw;v;cB!&QP-!<GgT4Apyk zN+cPUv?o6>lf!sW`O1ZO0`87<w>o-KDt4pyeIRsgvxbS+_LW<e*52q#<G}L3z!u9$ z8roJUBRGX`PxzwpG5zv~$qk}ENX(-V19ZFX88-r6>9NT49XFmWVL(u<-TYK^-Hp|L z%qq|0IneXYn0;V7=88d0q~-T3j(h@q$W#OF<!a_yHSPTGQXR=$Tij9q{&ai-{Bq!7 zvlZ~?ljijk_O9ROMq)pGOUCuP<g)8|cs4dK);XT<)Q)`{_%e&~HWoMATID8r0qQoC zOO2;q{n^){U@DfJKtMQYMXVM%Yi|rInhcl9-oK)b0<p4KOk;yCo`C%lf<SZJSbCd^ zCVJJRi2yWCv7ZESvW|uVeK^>+kbZ5NuPi1-ayMF0;>Q=GRS)=gpT$VLwE12rGIYyg zbMQ`b%Kec{ICiX)81Tz_{9>#s(<a>Ls-E{<F-Ii!`&`x%)$3D>2zGz{??(lpNA=Jh z!;o;)g3)S%xu9FEJb3KsNvB(VJy)q8<Lr#mMG|`1^ydbQY~`+3E~|=8mUGPt9g(6$ z>l9d1AL<2~<0*=q>jnpYv&829X|j)4OtgX!@ClJgoy}M4bmNg=jb!yLbO0=!T>}T~ zuRz@u97&ffqX*)^>L7bvG`nTqFC)S7>A~%>&CwVq6?s9`;ie}bI>c6-dc|NJd++*& zbZ^)mBz=Q!;Uh5nM&>L(#hv9{m(T;n);f@|jyf(uuQPOwG1;sdb8mQc5>eY_X-!{k zJTI0mXxzr>a|A8G#q~uIL+L3|{y0i`siu0)3>QvYrK-`7jYYg`bOKM%Qt8D{<c6YY zfli5kKsr0X<k3_l$pYl2u}<O}|BAZC#k1$C7nG90wYYgi@^!Ate8htNrY}mxgqFw= zNrOIyr@hVW4dOLu5_?`h`ehsh8ycOHWiD|SKKsg3I;Q;C)L=PRJ(A(SBb*p;!jloC z<T{ybX){nP^+BmkR_nIqzSQr(y9;E^U-2@@NMJg-k@b@W6r@6RJ)$MePx>L_3YS@L zBoL&xLb)DZ2Uh!J(UT**-u^5PJ)#8&)N>j!ZtE6a%jq2S4uOqnw#FwQa?ZNcgQHt- z7@c;<n5)un&)Z3EJYN#F*jj$eIBzH5JAn$rK>W(uGklzC|9wh^&CK|ICU7WmlKZIk z@!43;Zrz|IKTgU1wVqm3-O;FnfKYc8%J_QP{N*~4B3nXUNYR^XeT=4k<5wqt>Ssk! z9SeEhbvf9TzhHJM&R?$Tgq3@}Xu}Bg!GN}0&y~_Q$M7~S_{GW0Zf?|<;O`HU%41`h z_Dc#0Z8>z1Uhz)1`Z{BydFr{m{FZ}C^+jau>Rm@bIdo$XNpPJv_{N%Ph~(+D$>OBk zjy2WjhAx>2(;w#I)gpg~6lls9DW02D+pCYl4cV4g8Cn~rvqPU(xz)yu@huUT0`Tu- z*IH`i;J)-M$$dYH8e?)edBao%gI4o{P#qBwqTB;S&?F|~mIE^9gVlHhj`VT&X7b9F zmIvNB9B6SJeUlWWpV&GHCz^JF<;z#Rztt@`5C?f5y;S#Cw&mw2b$w}^xoSr9sz^*T zhS*<BQgDz|#AFL9ga5<R0&id<*qf*KtM12FsjmY~O(-Vf17n)B)Q>S~8{k+QvuEJ% z)KeMqHK=24pCQ}f05L}W^}F^)R<DTV+o|6;JbhLBZWBlKGjT0Ye|Dm<C!qaD=zURn z$GTQBn<$TX`M(`o8RzZHrcl;DCi7KpPe6cq%b91=>API_<g6X~iX5SeSiPbY&Fa%- z6s@sg^rm00aPnmaqaAk(u|Ox@8W5B#_u4b%ow#!p9QfNE21bN_ub&O>FDB3HC@*c1 zGhua(URC_pHiwOJq26eLG_jqqi&ABQPmL2-`pQOY9Bjeet{+v$UTvhDDUaM<9Ph0| zMVln;W)R*#T9SPf3y|{OZHeZj)TW4jKSAuhIn2l73Gi=(msH0b+gkS^$ZBI&XN?b| z*-7!0#Er#j2)RyzW4Px}THS%~;zE0l5G%_}=IXm6K9bp0?Q!(Zm6uOIXIv%n8L?h4 zA5MFA8@8=##lp>qxuFDI_7AOg2fU>rIxKIxg>fZ-pgF#wh@zv)k44^m`5g?mITh@I z9^v_Eitt*2-_4LCm-@7ip~w6e6v8r+xc;G%Lq@RQP)jr|a1h%G<HXn6`Z&D-{J6ig zjfBre=RZ0~2)>Ztf6g^q{=wbmdX7w{%DtfTU;l`uu))B2Xmn!sQs~FE6=2JO1$T$( z@}Z%!&6|9agi|Kn#Pg$J#OL+IOns7%LI+vG(d<@E1=qFSmU&nDY`=~-S%%Y--j~<u zFC5r{RxX6SrwzdhtqI2b8<e8?fli!Tr>GRBxSn18xQ^+X3=M59Kg;^A{;td=QQ*1q z@CZu*A`TcMNjB&hj>|gfQR!jwncTc9?+$r!sosB&|05I~*eEgJ<{T$=j&iTr#3w(> zVV~E1{i(OlK%V&xuH?{QmxlBB=+gH=@k>_TNd-2r81L206VP=~&66n{p-|>=zND;v zw{-ungFVmD(!HEj;18wv=XfJYUQ2o&6cTI_P#QHp(xNjjcPm`u`BREyCO#C+b0=qh z?K@7AnYoZy2PZ2KzO=&@p!id{W6o4YQ`eTP&txWF23>1ZO-R7(BxM*FlNZA@xGGN- zB9xo_MyX^b{T$;2br}Law^o?<P>;CLYPQ`<ZaZ#hyLXix|2pNG$IPa0f5z$oHJ-i7 zRUFANA=G6*@c!0QYo&@q!-!TtDX|W`G@+mUP@m@zs#@3C&$OJI(4XEb3G;f^i<=(k zL4CQC*qai+r0dCT+S<#)MDYWI<K7g{jdJfV<nv7lf0i#nhFnP5&d)Bo5c<y+s2)SW zxlk9yPK1W$JFY0^hh{{Em$k34;aDp!S<*MfUkeDZyRp=A%E#XC)D8%1v~U?GGxJUq z$2l<;)c$+7ocxvf#eFVJ%zBGJ%7;{@MNb_%s$XagQW7M<nVO1iqCtY9wPv`yvn4g5 z1i_Em_+yIe!F$&OByF>Qo7GqE)Fk(Fo?TOR1W|94Idiyu2?cq&p6q7rduncs2q<5= ziCOehgI+ENX2){RgQUc#@cTS0qRn1Haa;$+Oxn|nDe$1kkyNn4<qQIkB-$c+STYJq z_su7}zzf$)ZYsEK-zP0~v7FUfGiR9m6E_xyqEW+aF4Rrs6|?X>hdHT8aqd!9iD|e_ zMQ6$U%56#emVDwicB8ff&sG#v=n<n!6En+{s$)}tO)rJ%GHQV)yoH)6JjZ3T_Og^? zcE?t>S8tlhi7j(T?adS5k)+(iSO8oKoy^{*8J^09U7_Ufi-T%158qR1)J=VfH=#Fu zrcaTX?e+Plz(f7hseAYnP)h}RsiQYwMm9vB@|qL7Xl0zV{~1-C#`&!j8}snEZ^vMm zLUDfaaN71sS^EXX0Eig3B4;FLSJ<W5=M8};kHIoG7McZhuH$cNQ^&>zQc;W4izD`X zm`X=&`_wjp!Qx=G)Jgy;K(I`ZL|2aEl~)liPQJ36xL@{L?*nB^e?s@7f!{Z7y9Iv~ z3=#d`M`Q1PpJ{h@drYu}pR~vtL#+0HhyIpIE5Q7AbHN(}BKXv<XS<|KBy0=r&wPFs z6Z@D^k;WoVg31>2XG4P%!L%Gl-u@!4r_t!g2>-3|dV{UX;)SweSya`JRsr?(E1fxK zg3NT3V~KK`+q4Z`a+OFM4N%Kt^x(<kVw6YZhQcwy?sbkRWwR{f6F`aV*?aBO^YHv> z^J!z%19;?p`MVnzvm5X^yARp)#;lL+CY#;0T+TFyiB?0vvYk#;bd&b<y8%EDu`6nk zNw@iI;7#n+Rj6qyM*}HZeC0cV;wFQr^St^jM?-y&unnH*io8Watc}Z1!JCJUXjG$H zWqcHu$oHrjIr5J4@o>7;7Kd9+Y5e{EYpdCY0^_*$f(S2fX5VpmEWgjI^Cbstt|URh zF4)1N^)5$PJ5SY|s2EnqblK+_8cE_qHHAXmFsjcOAQp!ns$947*UH`-To`1oMW|8H z?~D<QNcWg&N#|CYre{wp*fGIn2=f4O?C6gNRpjiJB}<WMjnFYrKuYIo55H75GwZO| z2-ZGLRD5hp$7gB`>8Z%fZ9sEycoE1GlQY|f+5;i*5vf@4jnqq6Q*BOCFwi$l@8VE8 zawC@$ORDsZ)q|$!-HN}l+M9(thD_yCw*P=w`OuotJ&3c*3QfUa2Yn{mtT}2?%q^=T zocHw%kdY$a(c_%TTgz+<-59oKQ|6|+@Ao%6Ntk|#o^^i8QaA@u-Nv_SZhE@%UZ@Qd zZ0YnvEeV2>okZT)dBVu8|0tdWbu0HM1?XJSsiYHRsW-rP(igM3`EAk|NJX=%R&XB$ zL9nypF0-?q>FXLh)q;2qobUxTi1QOb%Mnzgx4LX?L^SPLJ<A&TuBeOMza(Hlom5Rs zv>aup*&6SK<r9E&c_iJJ;uYLnH<OOdIw|+wir)SQF4hPajr<{L<AFi%Vpb2Pn}MZ( zx+Pf@A!~g@{GvhA_vtM&RqR|-PBCdJS!?Zfp3~#jv)I=MuTx&v!(6mp3)@agj1OMW zv%ln`*pPV!6HO-wWklC<T`F+J@{y=)jI>tKzO=~jbQj>UP1t%lGUT7IDKl_l=0Lji z&(7x&-4_d*R5RKB`hZhP{m^YuDLI~#I_R7v&SE-&WgPVBR-!9xJ`p1jz3d;=&&_OZ zILW|>DG@8y68h%W6~<|KlpWpyPms#67#lL)DP9(?c<c1=tG8e{D7_Ntfjj|O7mF<% zp;Q78oaOSA;!%CcAE%Fgy}jw&PrwupT)JVEWQ&}|`!sy(PVGLO?ZkNO$BGZl`p<Ib zSjA(A@kO88X3<pdltU_HOLgY#CBksz*uws-sCLf}WRMnF;*u#u+gOF(Ws%|mP*bEA zeTkn>oU9WiDB4Z%P?T?|nqkeLsSf3rb_j?IpXM}UeCHhJY-C7s%Rw<h?^Z(x3&rxm z+!x&Wp0>VKN~Tjz0xfz|5apev!qSfzXk)+-XhVv}fqFf2ZI9{gJI;Q5P4(?($!*@Y zOsQCPSW0$n^dE`kDZ@onFpo4`4qpCM{1~Y`Rkpf+y%8S4K<PO|pZP3ch%vx0b>!6l zGkP1z<%4~}Y;#4}>wCjs;h2_eUAsiB=GVgSokgSccu_~443afg>D~6a0U?&m^bt<P zXy=8bk;0I%IUJs#+(B<4sFHfKIagezZB%=Po)YAu`rLZ9o=}zr%{t9Hw9zdT8>zdK z&@@IXK3f>ti#I5((O0F9WG_Q6mcq7tv-F8E5%qRBj*gNuGH6P~lX#Dc?-b7AU9j&* z)6tqHUs>Oj^{Mfq9t-y?*0cUxTdlkHUHZcp@~k+{5L{SwbHmx2UK~Zm;GWN+OUl)9 z(+ss%dGNxyY?F4bbXSvoYv}3iiDmHhiB`+Eru5Q;wkxZ@*IM2!ls^sZD{~h^X0E)w zIJyvTNj<hFw`(VrPNhC|HBV(*Y6wcvHhhowwD2+Ym)Z^xVlhnc;YziYVOG0d`%rDI z=&`yuMln`ea^b&qsHXe#xW`F;-wHy}!x45#E+(ti(qvg)o-C$$Nbm7$TAD@H9!rf( zFgQR1y-R3>sOkGi)m*E+XYMyFi^3)P6mOfd-YN|19Ysqj#W25O(wsYm*ygSnCv*+7 z$i6R{Hgr*6o61*P^o~|ME%T1bQ`3KzYMc?mQj}9nQ2(QqDpi!V$pTu>6~9l8ZEL*r zxW2%Q{sF@yM4ajGu;w)l@!|e9d0mzN%Zv?Y^L6mwMl5Ov$Ev|j(=nk->Pw+=zv*$Z z(g`ch(l`}{U-o=phNwXu0#>^P_d%96)N>WgEp17v3NyMj1J)4g=Gv8a!tj5?B~ilH z!%4uUh#(_vn{Y&{foaB8S+Sun`_1Z#>#^5@rj_P5VNK(q>-yNO%MN9JLJsVC<2_-& zI`_>b2u>IEH#XZ7QidNkPUW1DWd0PKbQu0v)TfM1r~M^=^mWIrdEcZekN7MmC^6Ot zF^XH;YQAekBknR-KQa2?{CtZvclr}8mN#pH{t*4pg^Z~fz|gPnxcElME`6tY4cp^E z?Q}H=J>lDRg)M=_kL4BNA-cisG!)IG=lUo}_EJDUn*@srY7<d5-mqfUH%zT*UeY>g zi+xt6x~ljKzGgff`RP<9<9T{RYFNY}wi^D3+qLBCr)gGG1*d|CLT+!@LKhIm>cKUS zL7$5)P3XpYy{Ahzc7UIjZKge{+xEK!3Vhiw9Re|;d^S&j?P)`ht-6}GL-*0qM!5d) zP~&rIJDcaEOI>3;(=q5aN3+DbbVS1Wq-#5@jzdCMLW~Dj<)h+aGc6^GDRqZynHg_j z^ZjL}skZwqF7TM{sg2Ro1Fgm2x!H-O{yq9Q-FaRkOX)V}6dJ$J!nk1+T6q1t<8X%8 zJ~n>?6F6nXZ8AfvSBFQV>T((9v&BlX&fd$A4-(>>6(n|tb%J<YNcS!)WO2R+=l)3w zcb(BSJoW``9wJjQ_}B%-eG8~9KzpCuDn!4RmWtCKq##3{4NX&Jbj{NYffD2+O;OYi z0o9s4_)96IXs!cPK~RD$9K{xrhY$Hq-oC|T=Y|lKQ2jL$tEnNT&?Qa$vG3ERA^xs= z$!M2?*ria>Et4M=D~faQm<5*MmBS9u!M%m>_1yj6-uqt(FTXRorMf>zy+tM>;j~8k zyWCX@uE(qA*Yd968Sgf`j;gB`ahGk6+9)}6=TNB;_3+r)jy4%kJX;tXZm;|nZxK(M z`g~I)m^{b+^Za_u`bL-o-m4{`dQFm?ySlcdNq!$?*~=HlOD%=fpwzejZIVC$EW=FL zWd{1H%q=-W@9{vjwFW2StSXbCmhtbmjwp`dmhU8tLPG|4CLh^jeJ0tzbnYdLAZ5^4 z?N7|hiUgUSj0Z}}-I9$79+ciYt+7Wh>f2JTF2ohWUO{(<or1IzTmHdtBi)>IHHkHn z-%17yRNYULxn;fmO+NCHwm2RCSW?I!kW;Wc>!VY%*$}1H>+#2PtFTbqCC`48-cIkV zm2&WSn@yD_<s0z^krX2H#2$r)+M1|@1y{B6b1qj!nM$SvxoVLjK`FnFct)~wu~tbg zX*<#MauYL6BwqrBV-P=12#Rb?a01))Dy|T9HGkyvmFpP7X*qFam9*-rh?ysXXUcCx zs&o-%!CfDFy+1Ijr`{;$oL7aIncncozuwkT!TKueB-jayvs&DBPQXZe?mT7ydjcej zcyXS9Z*3HI!_gh5jFiQ`iW&)HW`Rs5KG~l~ZwE$wg)d9@XUY$RUB-kg?)q8Y96C(R z945qCw@oPc67C2p1<IK&4QaSBk{t3Qb3pWRENpJnS-+*FB_0JV!ww9MGK4Z0vF4dr zBYIs)#hUK5v_pqWZz2<G(NV%2Qz+Y!%Ez3#>KpZizay^py)J?^Trp%7JgrHkhqWj5 zQBmLAf!Njej{od!V&QJ_wTihPrQhFD6nFf-LFIGSw*AB)Q<I2))D!KW4y&LO>=)MX za;pF(({_AK3`ItZWrVJGeI@&tW_tyl`wF%ixMJ~P>?!rpnhY}xujh*ECNH?z*ce(Z z@O`&?@O4ZBRRzHcq35&u@`U8k!(%N0>w!X4?b!&;qCbOKr*TH_>NRiTW0mg^9m9#e z$&=$=p_p4f-PuWMp4bBo)7om~Gwv95u_hdwN*XnopDHwE(zItj=$*Ay*ln|rliGiE z3vfrb&E+%*iV8}P9iN{xTu`<dq1aCPNeB$k%>^MK!qcbIYEpLCg}W-FJT#;aE8zPX z{XQDyQBRgEx}X@Z1Yu<C+3M$423G0(vaeryb@q0My1V~48qB&G5?POAZ%=aIe}2Eb z#ZT%O=ZEy_#<HvHteZ^Vo2`%@&qJ%IRyyA8CH>fx8W7L+1@||t;qm1E(WW+kJ1BZ- z_5>Wv@>Z!AsW`bcSM`K#Dvh;M)v36o6MD0kue6TPeP|T@PB-R+MSpRphC{f8%_2oU z=Lfmgn@jk%5#D-Usu({)ft@%R=^g1E=kqJ)$1{|Lpjd^S`)zM$7txzO&lc_LR7faz z%h~Azt-<5Z{%)us5ckHh|2YJ-H`~Ve;ViT%r^k{wIb&m;%{F;az*|>O9iKXh8w*1{ zL1k4{aL+)dx!BHz9zjQ2B#0K@1i8N|YXu~!akKKRS-Uy=row6?tPR1aDLj9xNM=-A z87B73a@uOA%Ho~-Q^&)7G<H)nE(!FcmY_8aGCEzL-rfYmHmtodU?~3<(pz=w-1DXx zr|(CK5+8{x$4aH#_IphJ7J@i|?sP!W1<I%Gc!&p9y*SV{qS2WoSw0=>BCUxk96^aK z$Ajr^oL&}730i(|VsuGPE%PG%e8zXiT@Yly@+0L#%jDy=n6xYpT(+ui2ChgeR@XKX zewpSmEMZDv7nh(XU3<Ci<gpc`g-2?c>h{~aM3QWO@n5nxoA|PzzBjqYJo~xC`e3I3 zkdTcMVvr8PvYJGuS-mVbX-N8=dGd+BvN}_P_nWENB@OEOwT*jec-fx_+cWmR6S>|J z<Pru@)Ln?=o<5Yb(c_<vPKk8&gnrG{%1x!oQCNjq>h7ZPTe(n6BkRaty>mPY7k_E< zSI)UudvSX*G4h#fB<Po=x#gM`SS_pv(oF+}Kyz(i5<kZ{46%FlNS^`J>Fax>N8a5u zRnsi`V=;w$IdpMxb&(-rkz_*?MXGQggXE@&RAw^QPTgMygN`vmO>!N-#$M4xUE zt8!^rFHQQa)UuvlXRh)C;6DB=NltKZNURf@_b!kt_RC^?wH)2~c)eAbyOT^}prokR zC8bcgMPEn=T+p^Dn5q%+_Fh}=(A{l}tLut*^tQutnua0vKH#6lZEAC+<KrfK(vR3& zQNzTIou!O5%bEcMdXJGCtDS2&f_hKuz1Y&e<H#$U`IUNE5vS#6ZWI<2Hx=*CoDw%? zDL$_(#QZ`s&q8nQXlvp6x{Wo;1A%1L$|1JiC1@t?*$BCut~`a~zaose>4-TN#LKHE zfNrnSL$5J)jMun5X)aCqji}iA8lk>ix0u_SbVs_mh)l~8TX*69aMSQLOzf68x^UGa zM%j_K&F%W@Y@!Is3t2J@%uWiKS6n@&T;s^(2pbPYnORZwO=}3g)f}khL1CEDMz&*E zB-o+c3mcGIf2QJ2_V<jxz_!#!s->;QHDMV<EA%EeQ1Cj1sy8TTFV^a$3@+qFZc?m~ zRe#-Dlft$n?_GXi-f(yW$HMCIJ!BHSgvRS+QZ=<C9}#+gXOIo9pMBYd?u+qYJaW21 z%55QP5hFHm2WLq=?dpT;+SS7_DE(ltsk^x}DdvA^Ra@&tQXZo37h4qg&s(F9G++wN z)PX)>_r9~m?WEL_F1gT~`E|$?Ux{#xzH&1uZ177Iq}p3k6+4vE;9~a6-JSIlFk2M+ zP}>F>jIezIET*3T3J5dlDvg%#*g7XaBlQ`=0po77&P6i%?L2DMN>Jy3)ySL#Vs7v= z51Eq~c!;ke$Kv7L6k66w45u%;Ju;uoJ#6G$OlhIxFrDx`Yv<#?p$FSry6v@P2S0aM zY?=DmOM&Y`WNPFHA1Ov=DWvexFUej4=?9ejZiJyGj`^9xi*dXWQUhps@xoE=b{%E5 z%8<J8DR@jf<&AUWJ@}D+;h#IsyE#T711RNpWI|QXWFI_HkuA}G`1!h=$PZYI7t~Tr z@_whmg%ezT<N+N~;X**h_fj^N!fj!x%=tPCGj7Lpq-qQIt`y#6u>8ent)wgCUz=aj zqRA!|uIn?k4<5aUAT~cu35<1>vHhJsx#KQFPy9m54J9oL^-H3@{5VWf6-0DMKfWU$ ziGD3F-<bJ4Nk;I;<>mTAD$%FO&mmtT_KBlCfM<~L+ed3s$-Q?UEfLRo9*8+lf0#DZ zuU(&LUf7KM`}2*jAS9NBJSes$qo-gSp=`Cf@1QGJH9AHbdQ3M?3Z4cHS09!i#We6u zC*(3c46gD=@vw`v_%-GjnI>;yefhScB=M{iQ?zPepQSht&pP~OPI|T=P&d^21>fVU zX@!}shWOD$)fZo!eXd~fEo$O;xA?iNe~7xoO-&qcOzNy<lLCMcGJdxqD#&lBQnE@< z@3N}`&JfWTn)zt@j8+27gLDn@q!J1u9AiHqWbWLa3)-A*Y#!(QeFAc8tR^Ctq-liM z$P#>=a-M(+gC`(NQaBUWal5144>_5ELtMx#;6X={fZsk?^%$2W>-4PukAH$Qxv&0j zL{bbDf1W19P*s3kb>E}obl?e4lxc5uEU@dMmn+DorpUUuol(W9H`>a?Bv^j})*!Hs zixP1fz%n;<>7j4S%L1ugWhVLX4x2W@NC;e^4{J~rmd6<5IStndWH=mM_5QYl*3dIB zja_Q<qldlg!QE_P6qQHkIw;S~BQ~!@a1bq_Bf<@=F!g0<S*{ZXR_OloI0Mew@gO!n z-s@nztI3=>wwwBtDR^8S@BT%TftpEB3_N~S%=6ck^QG9|-fW6Ob4H9z*$zGvZKzr! zU~v<Cigm~{b@u3``PsqY6>2(c32Eqh*F_(gSm>?}W7)e~iC>JR?o_*1w>@OU#5a1V z$`8^Z-o7#PdIF05{d}!l(fr%*z_9atOMkrQyS>n~hf(O`AoJh?ojoH}8_Q6C#cEbE zjQ#e0Z8K%3uM2~*TyW@qC+Ht6MyyE2IR>d)&E|Fimn2*1j#E#-jDa$h4xp1McL`ff z36vNU3EKOM;cyTmM8}&OH1Hd~_dT3s+w*d0oOi0*NkzO3m$`=mXtjv*s||}TpiYko zTTCT*6-;mxD6u!M&9~W_w(C^5)&%mbf-7H^NB47USVZCCdO<>*{g`B}cOz;F&t-vz zo3zQ6O2wOSm$g<T8Jfxfpy0DRQ}DSR2r2z|cJ#&)lN%Gr7f9xOyJ$;b640(+^$r{+ z384z2EB72H-d$y*aUsUItvE!E37w7BBUftm2#zz0@MIb~OIj`)T(0Y)XR_q@9^LT2 zYiJyKMj9P1kC4H8C4p*9gJRj#kq;}YBNyCo`7*25{LYp)+STLbM!i+5jop#tU$Y}M z09zTGzzvDc?sG}Lzq`FiN+yx#fYgxJT+Z$w?~3<$c$7>n>D?Gs(pV2w<i*aNQ(apV z^p<6r4F8fRciQFrhC8T2BQB9O;j&a+CpnoZGL<PL<*6y^yQ^FqrRf~}#CJ|yb~!RA zJ0zSp{t8;D=D#JUl)W2H-6z!930B5hA&&~$=VnS>WhsO=B}ScUoa3reN1UC`JSJKx z$~N8YX#On4(LSR2i^U@EZoCt<)fV$0-z?E$CP(f?wM;>Ly#;xPjKe&L4M86v#<$OA z&GRh9f^!nEqQY}2yVha%2vBH8$i=n%*hFs*#S>6jH)WJ=xv>;9Q8O{>piy}%q}el) z`eXaen>kBEFt7QoKj_GYc#n+GTK~Orvtfhgor)L5XXGM|j&U!&qom%H5#CR{@pqC= z&ks%LPmw9wlM}zIBA63Rwr|JWw!foZ5huTO^zfj+nmT`NW)!Vd@e*@LNH+DLc&;ch zn_2&%*QI|7se30-hQ0&gzsvs<Wq>nLAwAS3*1=J+p_z%;ob`^kllt{@J4}=O=h;A; zCgAz&^ATN~+c86ocfFS%-)?_!lBI?{<QC)KmgfhLud`nSM#y#9mfMy@I696O`{w!d zdmAEVms!2veES@be(vi5ir>>sjVZ7+*At3LAtGulq`n%W%f|pCibrR!jG10~>*^4V z2tPx)r)q>Br1ALlH4Po()tNDW0I%$NHl`2Y1=(d9;%d*T{Ow?~E8BIE)oo5tqj9Oj zRzdrQVth*gJ1g@QmS>DsR4J94?^`05qri@ZJOPG||7t88S$X2S_QsK~bD92#sB$wi z-sp0+%?&i7Z}mp$obKml&L_?5*k7K@2ftn%sgs*ElB=w%?k&CQlk4H1Cpr4qHy?u9 z@G0HPVtN+iI=Rf=ZhtSilU7RQqqp11?bq@}tABm@C9NY(61_^j6c3-X{Jan({pGEx zTH--OeZ&?dP7S`ROKa~lDy$)L3-yi3t7@}q<vKnSc&kO7f_MJwpEG|Jag_IMRS8C# zgkKM3{=^W%I0Gw;Pcg(CPYM~mkN#OidWm*1C3=5dqHQHWikJ8>>1E1c*R&TWc`S6b zE=(9z>^)+%y>-R3>8MbqFH>H|SF^%LUq5+_(7U@z^<Xixipu5g^)K4Tok)f8GN7tA zFc#tj%z>EqR3}~KZ4FI)CznE(>e|nJ@f?qo{_*2zvE{}Ie1?(xYjr*H+`5&TNNIf0 zdepv4XJPI&o91#kDBCQ(2YcFB-iBc)F#;I}F^^Bcz}++z30^zoCyW3ZWpZ+foD0;o zuC7*cn}Brgbd!S7cO4-TeFJDdRS)qaPe6zn=tjs3QMmoPTv8j+&M|2JU46T*|Cls< z{Tdndo`p-4g&MquiD&q-u=LI|TgS<<uG}7qLCEIgLK(%M0F|V_nuz_Z@e5JmgBxB1 z<k!#vm%@GSIht)ts1vvNKC%Xl1zJE!<+;QNHcG@nZe4YPY=Yu9Qwxf!C_+dD(q}q4 zDueoWp*aptu2&a=n9$at6@0rJXO0=a?f2-8oy1utLopWIqEF7-%I;IIJyo$ieBH7# z_3yZj$yOj#8NT}}#ezd{D;!=9IPE<-N|b?d_7gB<d;s5{brPaaoQ7sz^~Jaq`?%R7 z9<<@A7bY^2q@wp1Qy|#YP3}rVh#H9#n0@*b=+~Y?ZGc=LJY`7e9aIYnU<R^3$z>(~ zS&$%VZHHD8gAlybu<T0^RD}-d407n1Mr-zCfQ>S1XO3FLWI>1;m5+`M$kh~A$ocuB zN7o?yvDp9nsSMYJ`{k~6>X~%M(w{ElE9qP0BynO$o&-fDS;y-^x)BEBkp!s-LK3{? z7--h7k`}ZMk1*BE>3flWNtK}2Dj3%>S(y&1_okxSKYNOIt8Nqf?q$DI>6G%lOvu-e zQV>;|-^HuKAQ@i2s!%yg`TJV}&-jQ18?tW7w<SP^(_a8pHutVgm{W>DZ0e5;y~?%3 zcVooq*Y)p2W!xE;)J}q+Ac<1^<*l9z!@HQ8x*S*A6IatVWzg$|-Fwv@Ma?Sx(S7&I zm5Rmii#Ip2g{S~}C){f;OaB8yZ?|Ch7#5I;`y3*QH(B56zR7VbSC^N%$QaYT2!1!l zZ`i>|>crD!5)garFQTOtr(+do>S_+U-9XgsDL1M+>CfL6YdDhn$*U7ET16Kfv>!l> z+W%PIT+AyB)3K4-uf883fhCCH-QARDj%|_`zMp89l@i1C#6ViLLP7C7!@|X<?lF|D ztfYQnf=9@sX2wRnhbuF+Z>J3lZRTjM`&Ach>tg#jf$~gDf_@gqPhUmu9!1g*PMXn8 z(213$Up}iHQ(pI5itH);63dZ09EJVP_okZ0*y;Kz#q!TSA(@j_U5*P{a$>oIl9I?h z+3;zN`K%Ks4>cWCyB@NuOGxTZ%RDm;g`>Bp@>e4qAzn#O0KJWfnV*s1I)XU9Vqa#F z$J+i00O4|ajP-%1qt-Q#6pmOA85-%1D~5An&IJBoVW|1fyE4|;Ut2@0vPPa@6$cdP zd6^TOI}MgRcqM54R%d^#PSZnX5yG?EF+TVW9b*`X`wYuUnK~;@jULw9t5yJPw;1W8 z7q+4v0r=y-3|FB@+SkJ6V^6>&5#e=aP+s9<Zz1)3@Z-^Uybnk*B{RQG>HTCZhx4&V z3b`squ#0}1Uc{nHtiGF}=#4jlfs%QA9@z7HKPQGt&t!I#zx-CiXVp56VR=DDiYwUE zz51V!U#C_6OU^eP1Mtm8H!%0E93@W_v1s{o-;Vn$zQj(37o$>%LtW#>lgVSl-c9cv zrFWXpG75H8C-afAL!fKKCJ*W77g46Zg3!71q8)vr;X|Qgv*;KT&)@0|et4rzqJ&`S z#hMguaj6gr3q}g9ww2(s`L`5D1}Eq-lBDBQNjT9#ZM7*<oM#`#Hlkby>=4sUN#zXL z$^VA72538y$&aI#8!+NT+>t`!jA2SxSx!Tpw|>1YopU3v4Hg<M<~U^Mxfmww_cR-P zF3!?X*~x?{)24JuCkli5ijEOg{889?@0!hmmryi;b$$eA9ayHoc0U;qy`ObTyyWfK zWDF}It6Cv4d{5n+{H*)^rzQiF-@A^-cpIs1zdY)-!!MYaP;i|DS7ySB2Ih)N$Sy@p z6%(0%s<hmM4{!NoXL>cz(NQX!9h=`dyfihjd|1A`IWQ_^(@W^k@T)zot-)><T|=8} zYacmot(Y=vpA#O(PqKY0qaL4R6SI7F3(3+e+2C#|CvjOKS-&3f(j-@`dHbEOh|bB@ zqiHv9jxNiqZi5!=Bd5k+pkzW{=Xu2}tfSj?3EA>tP@+_#QvKdDZFFfRd%m`#b0+wl z<)g^!0r8%TGF{G5FWAAjYMGfwNu;&=JN{6cbSQ`&QZQMYUR*`*M}=IN8Cr2_iF^Zj zc32?KkV8MuN!^IGei7R$=?Kf`Oim|^qI*bYlkjl~F`TKYhu(&qKH0!841RVybB1Dn z#F8LK&9wz#MT#>p-5eJG<zv+JQ*P^po(<+c=DebNbLsARMww9lb69oq{@hyq8c+MA zG#=JBHB(~i$f@`PM6^HHxL33K`Exw_E9jku@JHiY$xB9b3G1b5ybAm$fR*eg7YU34 z8{xWb4(hXjk#;;B8J~gwM%HdoIQWWjT9z*pwNTw&7ub!HCslf9=DZ5C!(yBhI_h(z zGWi;R%r#ZdWaB(V2mip!wH1SB<k3gjbt&e3m5&zm?aPhlCDr9}Vr{0Aj3Bh_#R@l9 zs-@67uRz(!pW&th`P>-b5NB~#cd}r8o$@be(0q43mt$F@uqotnM{tG{f>p~_inetA z9N3q2yR48#hfD3|?)cI`m2h6*{H=vpT5cuI`&LucK|utf#vI={)D{<(9;jYF4ukIO zdJuY*m#}S&Y*<l+`fa}kTwr?60j;hbd;)h}M<3dK1$dBNmJF+13tT{c-wV9lo<5jO zrv1~wR@(XiZ^ns>?tHC=W#kJ1UtE^xvE%->aZuS*RlPT#(VVyNZ=%BxpZ|}U>P>dP zEwGcNAC{mi8vJrkWE2hAP@F)>PCJtA3?FcCOz(?1VA4NaZ8q7zBqocZ54vD6jlGc{ zxZs<C;w1ylo#pC1ay=T6+hZ%eH=T>J;P2NJj4$S9_92Gjquc}Nfqnh@js0u4gu<Tq zytwHbaiK#41`&Oy@i>;8#&^4=U}DsJvNefrmqo0R*k;GIIwx`RY1>lK4VV7~4ngt0 zb|Xfl5TxX&>Fu2NsG$i|p*Y1~RC8f4^(*0O#++Me#p&~3sw{Yp<IKEEM39Au24kP% zJ@M?n%=%Up!w1ZVkC=cmcpUN5(;w%g(#rBP%O(TJa&kJH<lyw~2l>TCBPYr;?bLM6 zM;~9W@~?W0HBJgEq59@Qm#iHKQns;L{{V@rxAILT^D62Q17mcBKwz9Rj(ExG_z&~f zjY2^Ifjsg#@0{0HuTJo359KneOu{xC6SRYo{HlKfcv8bvwVP4Z?<BXiUL!HGt7koV z2P1|6dv)U##fz&L$+mn~7RoiVCm3#$PxJjwND;gOK4N?J`jcI^gZw3^>zZ-7f=g)P zb}93pp8#{f9R6Vbb<OyPO^Z&{Wr7*S)wm$XA&jYDeL?A7!|;mTlfscm(UN76oN|6# z@t@Q5uRj%8!k;uxnWqcN%zuac6K7|qJ*2<9mCu;YeqE=i!5uNz>7RPm)Vv>Usam33 zx!f{illZ!N<NpBF#V)r6tZSRwyVhx2YDI2H11A73JLes8eznbNGHJHjoYs@g;yVkd z8BB&tmkKg6qputSe*^TdG9J=znyT3F_MLE%3V+FiYjc5+-P+Q}qJcQW<*@H^Gd z4|peET^TMec0~)}#3SZnJ<mbwjQaZ5ulNf3>RTvn*7jTf06D*O@+jZisT>~Mky{$< z`i7R~3tKHV>O{tErEW>&k<*TI)|Bzp6XdcIr5oyx8rA#(du61JN=sC?U7La+0hr{S z#GXh$;p!@twwFETp0E)gnQcFoB5VQ_bsvEv^*!@nMd+H2hpEMLHU5q?+sjmGS!awe z`G1K>;PmbM>*o~I<c{h~*yYMkIh1ZE3WWavFaEV_hpkez*HbxCP4dNRL*i+C+2FL6 z-C>&1JgG8H3EhqX1CIXy{;I^mjzIg+k~7!z{KZ?>Z^ixFO0lXRyT?|>04Lnl1wSb# zXI>BZSJvUFxKfJP{lh83eP01ejH7EuRQ2^=d1Nr$9Oq*JlDv@Jf2M11!KWf+7#RtS z9=xCazv);JoW|f|a-I1jr?2_<{P(;ZNs@9w#=tlq?SG%vu*@{W%jNl>Eyc8>46d(! zXs73GpRJlr`4)UxpX~U%9JfB7A^!kB=Z#rDU{oHwa8G}F%<+4#_GEjcP<IoI^Np?B zucc~{&v4lOmEzTZxTo{{(fL(oo*J6-m*{bt`w#6I!T$iBgboKC2e<zKUbBOOa7TZ0 z`qs9+1Ier0GVB0J{6`r$UtZkis=b7;>Ei8^<SZ9=L7sYg^yJsj(VSH{ruVV?K1-S4 z=w;YybEvtRic0$3>9O9}pdJD6%a52f?e73*A9sM?IZWp7M=bze-79|m}S7Tigi z`Sa`=1^#5zp_rV5jz%lM$NvBopO5Cs*xJA1f14ys@ss{}p!D_bGxVg7c5%tZKP-Rx z=?>DPEyg>8(;xn-;=ZSws2t#sNdWVn{V6b5<lv5fpZWahdN#q=oOAwtX&Ztw+@Hgm zWRh?(gY$LHJ?W&J0(0s(H0Z|RlYx`z??K5`BkvMFI$cIqeWVQZ$3y9k^~(6Yzv5#T z$Y1vZoPV^{)juC}3}dfD(z!nwHr-AzNFTW#qxhP=JPWl#s4L9+2jc?)@ZV7dayQ$0 z{{YW;>*w;oaxfdY>Pg3M%D#a3#tHawf8P&mfAV5F@E(dzSOK3=#yWpG(<%N7_5T1P z^_~&_1y5hlO~-cJ;CkeH<Bv+UrDW<*0g?N_5B>7T`c+9g<xjt$>5<papHJp$#+xET zsN2ars;G>`-Hr#`U`M|vA6_fated4IyFbYF_<=v{%GT}romt4v4tPI}JNKV3+<2_k z*0l0iiDX~_C4>#MpROtEtm#A$B$KBj?DBg1FR${iomgt$$o}K<AMEU2{>oo-G$IVL zBXl5+Iv%y>YCw#*Bd`NK`sDiK-}+ZkeXiTA4wj-uk&x|jr4)15oE-fJ_}4y>sL>>w z=EBb!F9Vz_vw!TOy?nl$sKvRfU8CuEZgns-lb5?GxJ6%mWvly<0B0Kl?aK_2fs>wt z82Wv3X$lflN6s(?Nd)I<UPrz->`CV&8f%79bJV6c5uf44eR0X@_3esT81BrmKl`l2 zbv$IZUc-=n=b-1+KUBi}7w+HY%*}rdrUi@=46BSE_lNj)2cM;F_)_bCg+i){8qr8- zfG3^IcM<7_{SVfvKlXFSc@^#2Tkf`JjT{`wCspU4U(k+mn#k~uqMCNC5P3>lOu{)% z(r^jJ?0bJK*N=^~<n5{XZxUChJkoNfB-2T0(@R^{=!aRkNs6p)tTJ2ffO+mtIX_zW zkB7+<cu!37f`TFk12{c-C*1!4p0(y4EbxM9HZorYc?<FgA0iBABODd<_OD3zV3Nn- zt0a*fOkjW;(>Noap&WJ<<l`q9(oQd_;!=xJX`M3Z&|E%chB<)FcZ`xSKQ5n!D=Wl0 zExhnth=WOUG6<eoWM<>#@0{c1U(>DybRY)W2|30I&Upj;d93|obWyvcT#kBip56Zd zpY^XQ6(t#H&AB<Ar5_BT&~GgY6DxVj$0LmA-|5$y)A475<h$3kc%qKwC%AEOWp=YL z3JysfdB-08y(=QZX)g72jte{6!o^408bsL}^}vrihA=w)K;xfk@chE-NrjlPmKHIR z=v3u$2=^J!BZH0&KWQjV>vF4U6VAL_XRmlu;m?RAxVks$@kIrJl<t;4HT<{9{ve@= z1op;yR~hifPtf&04Naw9TP%$-5j$fJdHcYg<F{U%^&-6s_KMV&-@&%JS}&VD{l^Lp zK2#j>-;%%Q6~z2I(HRon{6*!<w`Sr#^5cd%&ph$p@ULSlj;;4Rd~IpdN^q5=ozq>> z<{mT9C-C0CE}L@RVN<yz1Lc-8_gs4s$FT%f8}2-u9OU)y-o5YQ-h|q(g<DX9KlHsZ zOn{~`(DiUnpy7wm*Mi7K=285*SJY9TGPB(uweYTY{jrzEw7xZ@y;onF*YM~%(19oi z7(FxVTV5KtiqZ?Ki7-+m1P(IXjz2tBBp{h%1B|x>im_=Fw+kd_-*@NQJKz)gV9|)D z8jx|8+J0nlE)p1;5~osJv3!?rqEdJK(G|_i*H<rbaLUd>87H8}7#Zp9UGTwGV0x(J z_v8HjRm0iD5rnd*nHW1{+CacPK_`rnk@?q0{kX0&Y~*kIXr{c(%9N^ds%u4a;W)yb zDwV0w!bjbvt);Dg-!8sp%$W;?Cy#!dRzHh(K9q7;A3aaggU9DheQG8JO{|^B{o-e! z^*`tNRo1mAF0}TBHIg}5g9KsMo}i45zvR~@8x2+!V%$#$D#6yLjHOY>MJVg@M?qq^ z@W+OJ?nSlhlj)n4QJ=yg+xpdj32vCjVd<Z4eQK_m8?J?*NZDk28-S;;D|Yz<-y|Qz zRlE>$w6Gk5S!4eIjLsa_{qm=){KZ|l7$D<-M{WtGF!^{Np!BC_amG*m^-IYE1dMj= zTuXC+X3_Vt&(fTIOK>yL`_k<o5^;hF$^LX;?jWA$x6_Ju6@^y9XCQNqoj=d@q~rs% z0i1NDAOZ$>;PL#wPtt?)09lF0JRjHk(?qff52tJ#=iB`LRmb?xXZD;szvqDHKiX>R zU+n@|-Qje!xL5lm$ALMFg_j<{vaWlb-D{KayZ->h)-GH9K<7PvX0JyEzj<S$4F;-e zeFgE8AMma3^PXh39e?TKI`9S<CmC!Xu6Q^V@BaW9NBmoR?lJq@Z~Y$~cnREF<vYEP zC#S7zl>Yz+=6<WfKftN$`XQrn2Wxfz0Bg{UW1mjGpM^_n585tbi9-DBHlI%U`~WBL z$JP~M7;Sul2V9=x{Qm%xT3QT}KC1>BDoJCL{=;_q6V|;7^KhprF3k3`+VjKZ^=d)c zrK9by=h)pEWtspDta4)`e5tv!k5m0IPireMGHorG0o?)qf9Lh9j89CEa(Vav06)sI zY&Dy0M&v~lcL^izBYa>E22X!a^U}U*F0A6^cGBGZX-2I@DXX-%f96T3szavTNfJjF z?q3BI40Yp*<(v`WX<0}E?J3k_A0{$CKhnCbRS))E&|`z-yJy=5u&tpF;XA}%ysf*Q zNWfBmKx@#!O-7o3-d~aFWwjL;DZZ5;{_bO+B36+?XMP3`9Y_Fvw9KhU)UuttfI!Dh z$2dLm2Tp_3Vv)w>R|&YFB$JLm$G2X+DneM5!{l+Z5*~v%JoWrAJ8(^X3HxRfNL#aO z@qeLb#o86Z_%}?|Hps?Frz}9=ASj^zV4VApO5z?cyfDB#{{Z#t>FqDWcG}m%>m4rZ z3MR58WB?tk#fTst4mkWrYWdg1dM(zItXyf+hc9m~#cjJoa(jPHYs9N{JHDskc&uW? zV=F)JOYXa|-ux3kZ_%||OUG$qMvNj!%7AwaV4rTou&-0nqCpaDMMVG>%8X!p4xK+r z^B;f+q8(k>?o#lAbHKopJwML^y+==PB6Ar-jpQlpIpdC%^SI~x*(LpGbi?6H&>Rq} zxbQG|>x#qHZ>{Xej_T?t*n$-S-60q~2V5Uh`PV>O2pI{;<Z;M9=lOQ5Tg@iVRl&Tr zj#&|g*&S56f%ONj0mr>?(~`a9Ya4Yg_)}BUw5g!GzO}uz%d5w7cF7wf5&$3`H)Dg( zPi}pF7m9XUs9Zc!qOc>QFJt*}USXp6OGCfYq|om)$)Sc%o9!0z7}$_OTy+EjdUW7$ zYpv97th@u@dyAty*AQ4j64*{uypDK8;0*l44mkp?I8ELi%<DLG-|#qJA8B{KEBJ*o zGkJCzbYk8aWhVju0F0`RNy#9S$I3d^&4rR#+r@5*&$Mj>fs>!|E0FkMsp|K(a>=R6 zBxxO-FaYI=$;lmgB>pwpw-_L9P%uxwU(dJUUtNNu7*ke0OC4HLn~N>Pkj$+N(TOeQ zU9vA73gG^k`g7}FE9<sRr|S2XPTQK=X(HW>XKNJ)ABKM#`idzCh^G7uU;+4lpUS>^ z__nJL#4TkM<eQTHhBpA+zO~;)+NHdY(7Z9PC5o*>ql$lXx9W261~MiwjpH2Rwmc#i z`&k?e?JV4me(4zfYa-Fekh5fk1mpbuYk$EJKiXZ+c8JbTJ=kOU8hGh)(4V39{9jQ+ zFRq_xrSp~F;E8mi)?O#LvyIpz<O~esjm!FfTHkDvr*;kpx#?IJr4z=G6Oo;xud3t! z0Im7f<>ajuq|=}c<)bjkJP@Sw{(t)37BO?q66++7%JW6fDpfkl5x=TjzoB5U+IKtS zo=4N^{(qJ)iR}_iHd(-oI+ZTU0LUZ({KZ$&HOcR<lGaEeMPhPSAdCWXIrXhgakpt# zSshSzIT&Dn!irR<hOZdQeLogB;jtNJIuWS3W}1K3siOj__*22#ht6#8*yDgfx?Fbp z&{d8&&Ir$J^HR(6cx%CDc2D+w=-fHa`b2dTw$(1|0s)dqz#R8Jlw(!vGlMW!#LfA? z&42?NjsZN8`O=l(wixHpy{d!U&omb|7B306wF&3WNMH*dLh=E?`Hwxo`fGS+M>?*X z4x_4<G0M4)-Zo@ixgaxO<df74;162js^dy7Q+sLWuD{l&Mh_;$;+#@ePhEQyr?`$O z7BbtVeC&xb(mCS*DCC|+C8X_fZdsvdSe8~&a#V&R2l6%6+r^~l@U)S|X<-oMn*l#F z)c*iF<7Bj!;$x{@wAyux>JKD4zF8Svn3o{uXb2dAj->I@mL9AtC#9{|zWQ7K9Q3hV zIVv!UjISrZWWV9djHGdn;0mdEHNn$vE-fvtq*RH>o;XQGARn7Oz&zw+RV`Xb{zbO? zRje-P97{XPCzRxk&y%)1jNkxI2c8XjHi4&Fc#isO2)wDZO)l6^c9A&QF~JdA@Nu{0 zEZ)ki+|$HXjuI-Y)|<aC_%8NZ*yEv=QB>zvr0%1&y1$=8BKJ{;SnxKotpu>Hk#`sN z6e1#}E!ARFKr%AYM=v=!CPf_ZE6Myx1kJ2YZk{hUI3YnGW3WBI>7M;-+5BCkv|7fQ ztKQ2iN3GbwBL*8&?aLa+xQ@HLw#u$}+UKq@<eoj*ABrx~fJPEABc}z}ul_nkYmrur zllFT(eKt*entrXDQNr@k`oi72ec$GL_r_EI02b!^90M)4LH__v)6%>WUV5CK-{<`P zmG6HVFn%506M%gCY-A39e82v>@E22(GtN(@ee2T7e}nTr<AkLZMsaUe{{Th)V=<H} z2JDOgIsX7W<NV^ZJUO53h`7Q1;phF<=k=`D1f%ZzxW;?>_wQO>8f<G225<>3GoJfT z_!{oTe*%7t^|+mX!c}W|erW55E(joe$2cOm-wkb#R<;h<CwRf*oup&y{x#X)mN@E1 z<;VX3s<{sm>Cb6;ZlPEEJdd2IBV!zAo}-?h*Btp7bBv=pE!&xBer=Jeb)8%!rON6n zCidn{G@q@kbb6TIr`)TPgA0N=^#1_s5A(%k_+l?NNRlT109PXwBc@cS91eNLf6qwt zZC~w{&t-Jko62QL8~$4Gau54JbMN)*H-!m0OBEnDnVx=&#~<hUQk^-_p(x9BCHWk+ z@YQgTsYbH2l%uE0M<;gyhGvPuCNf3<{0==jR7k~IGr3%bBocl3&mi_4MoxW&Z}_I& z2BfZvcQgUqjAN+)^XxJI0PCzNN&<3DIRUZMWO37u=Z{+Y8WlaQH7@A=b0EikXAMrI zx0?R|$(E1rO6$>|LVP)!&hRFb+p<zOjFNN8$iNPsn67{0R->orz8n`xWoag*cK%FE z$&wQvIX(X9+nkJU80lDF4!mP;qxg2>#_HbUV-$u+ypRYA$^i$^WP9?*isgKHtIOiQ z76`R@qqDWNW{x<^W6M&co_Wg>7d&>yYVxraRBr?Eyw5DDLp7}#D<vk^qjz6}HGUG$ zZKun3VKu}*XuDLok72;wx0D0a6a4)v(jwYHjg$aRNXJiJpNHpPHcfX6U0Z#S7^8rV zg-<!jJG*r0_z$IeKZm?!sCl<`S4#5>Ns1W(`G+J7k5hx&t$296B&7M`c2lbxax>G? z;p4!Oi?2|_lloP=NdrisUk{wA9r^8wz|(cBT}5#Ws>dBNcsW0(Z^F922<Wz#(r<4u zke{0p5PA9^V_sEA^884*baq}BmfmY6UyzkJ$j=?S_WJW&XN?-)#yXXvTIxUXD6}kM z5uEw+X9dr)jDhtgqWHPus}BWuYD*mrO6pfInBZ?QoukiI1fCB(bmp?OtpfT_0nesf zO?6`0ZImBnxmDQfAc1$O2P6Rdm4U$o9)qVw5>dn2rA2jne_hT@L?t-BS7W2F(jc+6 zSl~m21+vUZZ@rRF;Cj>c?P48s@}K3wubEro2aBz)l~Y={QxMoxACUb)2RZexRq&sR zwQH>_`%hKXti`h8HwzH+M1WzCAjVj>2=@2jeM`mDr4;0?=y)^Bs!8sd>557W6#<6_ zac+B_KcD$E^M}O?g}Cwlw#ag#?a+U~PTXg|O81F1JFPEBxz%s1t{{%m6DcyN*u)RJ zy-6F1#~<wubH#koG?QLl$vlo;X<W$edy;Zk*Io*=E6L6eMW^I`k>S2yIu&c;YG{&5 z{WrV&tiJK6t>u6e?vQ}E?m6S%oPJf@cv{?B*amEel!7uj+BhGse>&y#J3qGHDwQH3 z>z)*HPuJ<)g?D8XAVeS{fH96S>HRC0H^eI{OVyfjrf1IMg<Id6URPF%^-<r=oYtm) z;;gqjn+4(IcjZNGYI}y6als?WfBgji0H4R}SbiZ^9w?4b`&cwwlaE86>)+{Hnwn=% zyW|YQ;Bo%TV!3OpIxoo|F_^1U!$0mX{VGQ(;e4*Nhabe;WBr!=tE<*e{7UXX;|P0u zk8ktMa=sT(b#&zQZ=m^Fy1is5(r$NPlM@{61d)-)`TljjJN@n-FU<D1vEMuY0Kk7F zXf)9$h5RQmY)1~6c+BOA!23wgxA33RtXNoEM;v;`h*}7N8?E4-z(|K&oc-rMow0$^ zvLuP;@Xv*9RI14(u~lYJKrg7s4nfEFdUvg@FG#i2rfo{$UgFuCF|@~YmpISw9OLdD zHpVcx0Fl!iS)`*0Ny=7UTly~7*Y5l0F2Y7ws+D<e)S6e>Yro=!zws`QbEZxpw!Xi# z*@+~A60!4)0?br$NZ@nrR6I2`jJk9-dX<Is<>jOz7J%FCjxD%ds(|1UM<k5rt_B5d z_<$_8I!2T@^X|lsqo<b;-gxxgv!|%{rud@R+RYz{Z5K1y0&Z8JmJEF0`+<@^p2ED! zbt!U2lv7SuYhRL2ZS`9&hqG1{Io(FnQL=h`-(Hve$3*r}I=#P!ZiI=F9y=(|f-}&T z2C3@H2EVD>>9;L+r^f?%!6iVCaQML3PE>{NMtbl&*HWymi!5bXTxDCc4#azOs4uM{ zy@1<Jq2nYFLBlI{Wh0PBt#DDra!;DpefMkc{{Vn>)Wf;Mmg%n6wf^InZ8F01Ux!7u zg_gp>e$jF;PoFytp<R#OAx}B=Bk9=qP@%H%PMZ|z1>2S@w}nwPqD2dkJsLJ&s}EY@ zw5>N()3r$~;M6UWWg};sZ@An<`8&xDGC>Sa`(DgVUAwl{v>jx4qS9My(-<v(2ig3i zaR5NFMlw!8RU91Ps3V>aUM`Gr7ow^ymo4wBx_aGP-t&FC9tJZFQoa$|7Lsl0Ygt_- z@?Kre{{Y4?THgFT)ioH#64u#u8&8|%NrSZLfD8k}B5~QVUN7T659(U?if`_8i!n4% zvcn^B_rfV7uVBDer0Q2!dY*}<cq2oE%JIxz>PB3l*&%3mo^SvW%Kn35E=!Dh2ZVGR zZw_d1>2`9wy9s%xf4@Aea`HF1C-4{@t2~pR14+(Ww%wPLw!ekfso~O`p+}MRY5om< zH~bD?<1q{H^5vLrU$oot>-*I{p4C3F;4cndcr!+aR%rC?Mmt+uM7>q{npO&`uD{2Y zJ^2~nj%qKAP_^HJ7REB7Tg^t+-Y~}-nco0^p1{^0#$9E#t#04L+HBixH2bLId6STD zTo8&opDA+ZoMepjqY)bQXF?Qa;cjU*x-Bj1eXZBzI&~`ZMN(}v+f8|;{zsSimqokq z{<U?bY6z=5Ey)MvWg{V!<DnS+M`Kxmj745Hc;!~yt>{4*KcCjUFXFx6@kQr{=8DPV z)@E6(F0GTF-z!FA3UPv`%`6Z1TbvMa!)Z3FJ>j#II{DGM9>oqf1Naa=we?sWWUFB# zQnIwIs`=T!O&z<RZ~e5Z<!)7VoNdn@>zkGOU46{i(zO)7hXe@cKzE@0w+~Q%Pv>%Y z<C-UqXv3oexPD*#ZhKc1B3$X$qaVATCMSdI{J|gRit2QYK`-rhgz~0LtcX8^_Q%v` zAJ2;6#o>L8s#ExXFYBT4dA3vaPFnO|H7Wcr^nPbCtLe|D-MByM$lV*LA2tVXagKlb z{BQV7GU?8zljmcD=&PEaUx8)PE)=ffieyh>Kl<4pjYZ*8oiUCF`riEq3($I2xQt7$ zgXWg$+weBV<}V+Y#no4-29tf&ov-J)i{jk>0BWZ4Nb&{%<2<(=eL<}2`)0G5p^!VN z3d+m(NI5EhFXTsVHN0Do?T7#!VH|<e=5PMJYFyhzb)ZM)1`<VTuNwi%keNS$!2bX^ zuG)B?V@`^DNn7$gd^TzQokIyZd@_uaw)#!kuQhkR>gk+JQcG(#=$yvkK~^1z=y?aw zAHubKcWn-#92Xiqa(Pa>o;Gxmmuo8Fm<*CmI+A$@p~YuU0Z@(uh}fNkV~%<H)RuQM zT7`J!mMIisH;*g1cLKoRf=3(<#~nDvnA#lnsSVfl_#UqiV56E+SiC#D;<ZcdCDnPZ zt97&Kemt%NM#w_~1E~iq{&@VWtkI*5^$V-Nv_O*FBD#qsRA8=~n;GDok~7p1pL);M z^odsDR)jQ!HXRV|BaT>Og1=8{p(v7j8+JjKfVIB^2Mv}^y!9{nSDM<I-2DA$(|Voe ziyo_Wsu*<Jd)wQRa?KdaBK)~yz}!b89Zq;WS9f*s0C;0c5$0JYXw}#EY`MtKL+PA# zJcEPj<kRZ@CGg$Nh26c|TU<+SRhlxcBpJue>VBm6`VCJK_;bXTo*U6TU#&g0<-V;K zmuV76e8@lrmn(oVv@>y>XPk6AbuiGWXFFKGYa<WEek!=uZXRjd%)6EMO)HKN0zi!i zWgKLlr_c_4Yf#nYgIKh*)~=+2-^4mSs@({g(e31TRZI2i8#xC&5!CWL&*5K#2aUXL zmS1O?G^=LwZ(ozPF}n%7)yUh|)b$nI{7lpA^)D8!h3>W`g4z=j6B44wg50<R?y1K> zI%6HoS9HCkX)cRj&~cO2OyxXfr+9Nrx{f_2-gz3)&!05RSQE($jFHs-zgpdkRMGDI zGi#-_g2#V_EgzDI^OM4|Il}ior;}H88wmVWuUqRj7WUET>XDNL$q~mapt`(ax^E&q z!*_Fx`tv?7v3&!?X2(yCXwfDULlp54@Q^_tDIA`dWjG)oOq!&<yNxw|Z|iexB?`ET zv86&cZ~FU+8q}U4({AtdYrB~(ZRXh0Sw%5u+Zbgm2L$jr`c(yh48S>jW=9-_8UCG5 z{{UL1p5o@>?pfrN++Iq|^Gw7XXQ|Km_NQD~HIy*NDv#eOVmcB#fBL`3`g#$K2q-tN z=iL1nRil+*A%}yz)&BsEty}*93b*~eWVYX>(Cr}8bm4I2Q+Ug}5H_hJ0H5>8_N^;R zt9ykl1nDAzLlij!oa6abPY|Zf66g)w1sNn9eChrbo#3eewv{*lZ71BE{{V;o0IIwR zQ})zlh?Ca;0OWpaQiQQsYMAPhyt$Hoe96211$}d$62vxR=8XP>u0Id+&2(Cz{{V?? z%P;)!eh+_@tDV<?{{V!dH|H6Q_4Xg(T`s6c_I=P{f#knVKZO4Phv{14U+_H_=x>@? z(Zav_cmDvlNaQ>+sx`G6^Md&q{{UsHtJQaZWL@BHZzb`b{9?I33uF6jK2wd%LH_`N zwRO6tKeI0J^B*OF8TS7GKb32af9-JjerKn}pYl5Y0Kh*aJ6eVaJTc*TBrHU_Vkj|Z zep>OLDmgob<|Ee~xU2Dawl<PGdkr2*Kmje#Tp(vr#|koVFa}0PJ!?P3WKX4d9w?&| zm(vwQDp;AA$VScwIXP2~eq&U$`+qUSQEsuek`;Nb2?gFFAD|p2LLPE(p>fpZW3cq* z)T%4|-=|09aZtwA!%A+Ovy*rG&5sV>O{jRT(^I%ju*(d#@BqM~@Euft5hYZdkTa3R zblxMI%JAl<`$5IDimHEzl_5vsNaNnT?#AZoIFb)CHF0kgw(qc#0073vAKi1X@JUwZ zrFEBDlIss^W*5p3lRfNY=V3$gCVdNh`SeliUOq1j{;>%qq<<x<`}XxcJRWA28mwwR zdspj!@_UAtsl%d6{gZL#y`*u8W06;PlnEh~faHvZAmH$M>6+b5Q%sQzvFbK#qpl`i zpP#tvT#dA%?(%!#pDpnm89h-m^8^0TEJwCK9bXQ^W%eyn=6Ikt5!^`fNbv?})C{5L zw$MGY4PlGJQ&*_(eJ^hBf5cffYg(lKuI$oUH{@8<E-db~T`p@$EiV}wL}U^NRzegU z<2@Lm6=PeJLE)W3NDkImC5-_&*yk!2{`xo{g-}aJxYq3FF*Td)@`J0Yvx23Ua)3D- zM;?qg?OjKQG~=%LiWpif>H2JmJ?oFTEbE4j5PRU^+t4b5_qfUti>U;*j<4@;{sKKr zMP){nNy~Sl^=TcqhI~C?;jJ}v>ln6`0cDr?rC<h0&T*5IjQ2lU)Y_-6<LSjYx^x87 zbGL)X=~_11yFNX;>Tq8g<!gTf$9Wb+wz`$9@t;RlI5omXk~H2Pnl=vilFO({vVz;E zbtE$!bAvWN4%n|y@wKJCrKjooUDzfI{Yoom3Oh1LPt$;T?Os7NSK2<MrD(dOZ4RSi z@>)m<;%J|kq)yBZ2+wW@2D((^IC~}WB-`|AOYnb-M^*x)BUPuqmj3|2k=7)3>EgW? zQG7^5@GqLc7G)O^C}0NzK55AvMmtp>iTVO)pAsXwf-si4n3Co&yoHl#^Fj1Gh;R58 z9V<rlC6;!%TXvEdq>E0qSjZB~cO>vg!4%+pk*0SN06FwH+xYNf7ycS;-!YcwkM>M! zllkMXt$8$YGlnvhFY!WAe<gMPUwZCinN+qWuOrdu{{Uabedn6$x_ml>K3s!jiZGH6 zqdegA$jIsZy4DAUnHN);VQ|rTE;6~pki!IV^{#_ga~_>?!yt$pf=5HN;<?WX#!~lg z+$pzmxjE0wp#K0W`g}WY2UD5p(0Dmb<+zB-e(L44eB0f8mrt3{YR#Q8=$!EcgPwbT z`l^?O8+Mxc9VYy;7Om<an;Vc<gA0Mvo)7u_Yd^wc8fz9-{{UOY1`YsG{&}op{^-~D zm*jcSm-{T&_oMkpq2kVUScp<j+1K&;+n?qC0M@Ow=Rp}zL9-xt&+d(2__K{l3=Rwc zf8)*mHN9`njHQQLQM=#dBD$-;+|d5J9;RTM%dmcv{{YgA?FJa`w3#8eQphG^r?@2) z<P+bVd-trJTSl8t%jeCv-S@wQ{d=k8clsXHtKnVW*|H&02#}M;KjoVKwEJCX{{U=F zVQ~-$WoXhw_0BLyq3Ok3YSyJ%qlT$Fv|bk-i^5{-PY$mX+mh*I?SAR2?zLW*Iiz^w z11Jf|`CNr0VDx^+p~?2^RlL1E4N^HSqPZ~5wdIVa1CYUpRvdCSFK)Twx?dC6LkdT8 zZ4xf$D9PK9NXMpe>&I_;(r7npZ|2#PBy%WiFF6<;SFhpCZ!fFXim85AEB*(oUU_b3 z6;qqpPFLS+_0?(HTSonE%X(#wh2t-YcHh}{&jyZyOPL_JCCF7mLbqNED9Fojdz{y8 zeWP4!Flt(VpBypU+moikSSrS@a8*=bATAd?E<N})<~9ka=-Q~fv5hS!2*ZBGP*H{m zAb>g^xT~`3n*6bdGia!&BiR{M7#P7I9(sPY#ak?`IVVnP$#*$cNmf4vNHyP{KdUZj zTIRXnpAVl8>2a;C)yC((4Gv^^+km6})d4`{WHB9U2HEeINYphMrIt-X@<OiPdD|x} z0qRbF8rPdz@on^;X}faDLuFHWEzit2Jmrbw9Gvy4lj%Cu%#uCClE}@whC4<{B=AVb zB$~Q-I7PQoQBCiztjZZQ6H2}&oLBghe}&0>8{&)q01x<f>r87aX`XpE$LGjpF}EW= zP&;#i-+R}Q)EYO<s9v;}w?=0&HrXW3=YBrvZi9e+QPUkN?}u$IWuACvw`k;x2N@%9 z&N5Cjoa2r@mA5_25#2$jGza&pFuqvwz#FhXG3i^%tvoGRMwBl1Z^-MOX-g}q^?WTZ zYkW=hTj^``>h8Mo+^ON+KG`hexiRKO`IUIW;PoD-zJ2(`XZXnf0K~@Ja6fbH&n=(n zT5EH1{vc~Iz}uu)^3{D6PhZ4=+Oj-gf8uQrJBV0DN#i_|pY!Qi;ko6B=9|^4{F&z9 zCsw{a)U7MZQJdFY;Uj;<VMdj7d*dUY_Se_&{OdQt%HL<WX2TN78wWW&_xyia)|yNy z;Brn^9-rs@^`}O|<|;;VP8aGk^{yD!al^V+-QNEI*P-S|87Sc;hPINDcl1qur#s>u zsp3m^ZdJUqoZ}qh`d3M+llB{vy9dgAfsvn-)(41l{k>{bbG0&ajC}2i+|<YxH$hZw z5Vttv?zj2;Yo@-`eHZ9-<`0_>h5rBp{{YhDa()zS>rv%-UF4om8(O;UQwL4E0O$8F z^~T=y$aqA5#O-hnaM(Za7OuNh1nHM0v$x57=eNsR<NpBO<MRB^TZ^o-m+2qL8Ve+L zdN+ixOcKp<G+X$sQZa+(G>nMMdj<1J&!|uZWa}<I%WD;y`H6dL8mvq5$+Z-f;~5Cs zfG2<gk=)j{sc&*TAK^)Dm*ut6E@z#A8>5KN04xV{mlz)8^Nb#OO`J&DeZ9~EQcUwq z+e*j%+_B{L80pFDiqjC=sGo1^U&6<UUT@k;%d7m^ui)zq4*O5nWz;903q3m8DH7^1 zcPL{fQ)%IY6OE^-3xYVsTUa#>Oi6O;k;QPTWehe)CIRG*S@|TMLX+LGny+Cjcec+G zF_tYt__UVs26r*r#;XV_zc$ilko{x!uFJ#T9PwqrWVX_#OKqdcmr-K?L&zfx426E_ zA1UrLipLoz1d>|Ycm93uWo>L>$8UZY%!%wLP`UlvlpuCF<N@{L>04eF(Dm(R8MQq! z!HuDbqMAA62rmahk#!74TMxqj01i%0J5LOJIIyr-<kqi0wVgRw1Y$c?ImjhBFVFXm zI&r|R>=g<XKve^10AK(*0bW%~(6y|e;r)IVHKP|wYNQ>SJe$I{bL;o>>9$hMYi$vY zukK21mM@W1LZI-nosFOF1w3Sm^(`{O&rGn2$4|G8-r5oJMwvX5l1V(X6UkypB$9Gz zEo|*<+IF^uA~!R&#Ow(zB#Zzo#PUu61JD2m6+Yk&M_wzL@}<iKq1lMVRK!b~e|JPU zj`+<pb_oGT;arD}{C%hRdPApKqG~$B;4QFxBxQ&I%H6t;lzhjIfabjZUya(<p0OnM zI(^)eCz?ydQ)WpU08%&@<2gL_HPuT4TBKBx+~>Nzk4y1Wyi@pT)k`1k`(Q%jI5AA8 zsU0)Vx35a&K091lYH&pcou{?_r=-YQd$`yIv`_;W&v_W(Nm0u6Bpd_Mc<$)g+32@x z8^~bRB9~%}mfHm600&&-827GULx6u^TcplzT6nzMnKuF^l0mvg&wa(1{ybNcT`D-L zSf}<?lJ9pb-{sS-_3U-vFkf1$C3{(ZJqbP;-Rk;`t*G00dfMWBAsR6a#rc9onrz85 zi6553bCb1MnMT#yk(R8Fh}xCbyW;CjL+rBJ*jhZ6Fh@HDo6Pd}$XYCAbCm-pkf4qm zHET+^y|cN~@9gJxO%h0=zrIk*8^&cwngYj$Cp(Db0)X&J)ISlgC)Tyk59?Cc+FHeV zYc!UKs7=dTfTlN@xE${#W5>&r^Af}Yg*6;|tIDj@mhS1=_OpF{E7@5$eObYcglW{2 zRImR42l~{&x`ii#SR9g)p;iFk;DR$!UP*nZUcr5Tdo|p!+kKiH@y3dggS%){RULcd z>)MrrkV28qQI7pTJocs#<w+`Wj!)rUh5rDr&+<Iv-o;zx#f{Si%9tGW=kA)N;a~=o z*dOxLfO0?C{3_0?JaOB}Zl}zibumK<a_-Ck0YK#S?fUUqeiyxpD|WSaNZFV=0@w@) zT;TLPgI<Iw&EX!MQ~4eA=+0P4%`Ft8_^A)^2Ay?veQz{TZa|w`1ZO*ssUD`ZQ64=Y zE3Vf|Y&Sg!Pi{L@!brlBWCJ}8M?C#%wySp(dUWT^jJw3jtLAb`WON+$G@(+ZZc1%z z$d)3tdUWVo>NmEX-PxbvB>w=3bb#QIC)5w!ulS$O(wf=>KBFbckwAHjhXkArz~B#D zamn|k@Y^I-(*}6KGH^Exj3^(^kEpEuM^x0!{L3BLjx>>#R4S2x#BfOV9=%O=#uevY zqNeTYdf0q6FA<NgO9>>K=1uf*zw4o^;;ai>XvyX70big8{6$~W!@h}bu^%m()m2*^ z4&D#uYmJ&|<-A$0rD+{cRwL!-JOPh>!}YGV%;QUnA3Y+Q?8<@nf>pMGo)7ZJZfY@f zJ&h`GzRi=H<&w*&QKdO2Cgk0^Udet%V-~&%KWd4T^bL>bgV+4=jJqbBc%Yf3%z{P^ zK_e$?0tdDL{VMC}cP!XCoP^+KGrw=g@%&F}scB}=_<;#>&%Y&$6Q7qk$?Mb)#<iy^ ze$J(udOf3I;N2L~uX)9{2eNBhuIVFF#0OC;0$4oCS(|x{N#hHTr+-1#tXs<^j+nA) zkAkBN$L1s+gN_Sy=~VUG+q*P}?1nc4pD=CRo(2bB_qub%Xj)!dT-&6C#yr4x#_Tih z2(AYidk9`Dm&<>d=FSyX7Mz?{CElxh>2%-ZVEATZKCO8(0-j)IE9gsu(x=triXRf% z$t$V_01`pK&hGyJjaJc?9XC?AN#;dWQWTzd9Iqo8>x|-}@f?v|-3jgGS;v$(2a(tH z<R9{D(sX(8Qg(c<HovMzxl0d68Jl}3J3>*ieZO~Qf1aMDy)tO-JWV^M?W~LxmCoQ5 zMsk1MHB-hhg}#iH1o<6KM|SN;hZY-at@ks<7SK)%fO2p>dG2%crSUY@lib53H!!{; zD!~{WVB}}>Kb2Hq;fzTqWUMDnjte$$gs&LItNHR<^U&7S{!J?Aul(_4{{X&+x93@& z3J2`BDy@aeo=-w@Gx`eB)Gln=!gO1!QtcB30#)GS>+6c4;iiV#8I1GA9&iR^0Gwo> z{R{N2N>P{8=NoHv`W|HmAMDD}Yu@ICzUhgtVYSO(I*ou2T(f?^=i0XQ0gEe!VtIwi zj+w%r^Y~U4vvF%Ktqh`gN+PCC@Nx5;pUaxjn^CZu8DaZ%res2ZnRpolb55-}+Ln(* zx)YAPsA1Aqv|axI@HsOJ!wa`uP^UO9bL;Z8b`1^1{jJnC7Sr51qbrz@vPN<IOgLY} zj+M@M6Hkj%@t&Edn~@q`-7HcvKGyQvhT3~E83W(HO7+hb-NUM0X;%Ijf=f*%*LE(W zfeo+O(bP*Mad27?U2@>A?X9<P+{0XSZ-|TXJzgc8E9O$GPFGfqzDW`IeJo+pbb+hb zOL1jy;hWXFw~8X}z)NZwcCS;Ad-gfwHQV?`_f7F0gKd5^L1-dofo-mB)cGY23aQ2j z$EQP(c?P-fh0{ql{3Fv!+ohMore<UJvhzeqJbKrm4hI+jdU1;La~b~i#qM~~_m$+o ztBD`({@~g_*%+k8c|Cr#<{^*xR!zn9@%XmKO;%F{#f8l5n|a=nK&nCO(nc8iSDd4E zUdEsu=p%#8ay~2ggQ93gYk0iPQuZQM;<Y1WP3a_`b#al9H#hq==l(A7hl#ZfHc1mz z`w~dO65hc|a7f{$VdfqI>9Bitt?A+AHj~ogl+<@USHwOg(KJU8U*0O+6Y|<Zrdi+V z8svfQ*c~~qH}O}F^}Sl&CDS8qCrh}E-bs<eU5HjQBT1jSm_;qMNk1>%9S=Grv$yeZ zZ9WBt(pVyaV~mWvf&`awJmrq*InOw%+xLaHwju2Foi<m!nO^~h&nL=O?qp--k~99X zvC5kCXyIc{x`nT9ujkX()QMEl*wUZE6X`m1nx3|8OcvqQ#jKn6297|ixdU@N?+G58 z0rjq1;svFoS2|7HHp;SU8eCVm2XQ;4k({v^=rhm`00O%mZuV(3Ukm8$%*zIsZFh2U zjq(`wAS2LW*yp)VYUKVQ6^=g*A=<z3AhtLs-Rst-)!Y|Do;pdSdvC;x<<o35OZT^u z<Uy_5x04tfr2X8(XvqU4p8Qu8r0aS|hjiPKs`ygmppot^V~YKyjwC-aB)p6$Vh|Nl zq!FBuN$h?lsgqC9Py>^!L`OK}Tig(P^sg%M44z$t>ZtS2tV)e4m)d-Y$=lBXbCu_> z%zZ26Rvs9MMx4{Lwd~s2zRfKa{{SP{qe3o>t2WbGe_pMmdPj`(&mDM!;nbH#>eu}y zHI`i(($8)oIAEdJmT3nB<7hY_4_pP-yfLpW)~Tq){lBcm3^rOmmeKh_Ozy!kh9LaG zjD(V^5D~!wn%@#UXEf#*rm%)qZLnKd*cqQYbG4b=1;!3@^LHqsx%;gn#5Y=<#;dF8 zf_d&FCCql#@&W30XJ&fyw3Er|YnuarsY0J7GEs$_apZP-wRF{$)3R5(eH(o^mL9Cv zJX3y%>om^@cn&QM7gN-Q{p>2KaN1l5(Z?rN21P<T=Pe@+pTan-6|nH+a@@rc(PXxb z!$TywySxjx<_;~yv1A?x%g=lfQfMPebSGRaZ=rVbmsBJe;@$FjYbOf0$3fIrS)p0p z_>S%E^l8%G!rXyzs3;5uKyNM)(Vqko#BE)}hB*{e#76|@DzcQ9S0`xvlviGg?&qSi z`f#ADRh^UQwSI>q6~(rdaF*I+o*<6qY_82OM7OoLnDfYDJlqWSU89~en#n1k#VcCL z;Mt^`besPGv~^Rvay}R`#)Ahe0s0&hiuwY_Ptm+5qug9U50eBjwAVKdOUo!#%R9OG znBadwfH7VVtlR1`S!q}613mTo%nVu1C%TYIs(TzUEBIC9`TTK`pR%T)uWoLyM)mad zdz`erqlHOPZ6v$C>w8#+E4xJdL)v&3!(t04jMox5np`UKQ!H`3fybxkU1x~aC^d%C z{4wFL4np?It#x&Gry*2k8CcW-%WWZvKi(d-xv%&WK+!xQZD$RXcN&$&{hSYdEPLgV z-r_LlKf(&`7#w4Y=Cp~VlU&s<WNrTd+EPh<7$cH`;&=u<w!;}8?+WgxpZ#YAJWUt& zxmDKowzj{id3_o<j6^BTcfXm5sn6maI@(ivpm<X16;f72(W7)22LZ9QlyQuZK<7BG zNbru8E~nx9d*2W1UL6)w41|Y43KhGn0Y3~0<Qm1E=^&OUm`fznw%B5dHj+sWGH|L1 zIQ)R`QCR4HE3m&huZ6TtS4);`eWoWi-bKbUjqx<V=e9=EUREzQ#Z{uYYbsLRH_OW} zzh9caYj*3*vbq!>?&h!A+xq*?#`nYiBsOw{z3~0~z!MZx=}wBx*c0bAJu`#Wyz5B1 zywq0Z&x8CM6p>2NOC83avP6($a=L(Ua(kX@r`E1MC~ELqTEB)RvW_|T&n1Sj91k*J zfV@8C<b#dm62AO!H}?!YODbu};yHXozq({;<GNQX{4$;GkUR6oOpI~9O^nRz!cd)P z&RectM{RoYy`J~BpO+2`4@EaBt#bCgpQ`@=hyMVBtABNEr`;5vz@8SpMS<>J?QGb2 zF{uHa#>OBG!0rQ}HG1|<J5BKAwzqZQUkt6K%EaMPbjtaIAd+CmPPiu?;;?n?S6<R* zw{1?w+6_t=4a7}rKItNi@rh97N;c9NcXR~tfNQJOJUMp<g>>skF6^B&xf<}neIfFG zT(Ti>4r7aRV~?0B<bhvPg_UPc)!)6>Rd2)fGR8)Vsd_H)e|q)mW!mbH%YP1^2f|xJ zdps*VP>A3T(Goy*%wM}5^80#^YPsTT%{#-_R`%N8!`%!|bd2!FYS7H>gNzmOLGQuF z4?OW)=Y)Jmb*AashOI63p=*0^;!Eq4uo4$zlOE?}a0WAl%C9{;)~|+i4;t!`N#YAO z(61W#$!puYrfKC|@EgkvtAT;A5EP7HV!V8AXI~3iRVui`lF?r2$@=YO<$L*b(imD^ z>!~!N`)l|i&8}!MS__NchH%>3f&)n<y|5AF^<j;`4mro;>wV9LW>sl?1g+c{MYtV% z5hC^cD<{O7H^ep5t`g@(fLWCyLty@EGWuY)>=D$ek_T$cm31Z2<JG(?aO(q&?Weq9 zyrYx2AUuK_AgCa5#VUEVO7V=T)P}CtO}P0c({_9LdLE4o9tNwIv*u}AZT(X1_?;2c zd<^F@_$N|}JP<aX-ibN>U)HEx+I%Tloo>F{r!tjNvzwGuBxG`Oql5J1b~Tq3=ZU4$ zQug;y`vs)Qx;v<J(>yGmqD9J%dV_)p#yZoqJDUv_XVvVyN2A}}63piI-Z>1z7$_Si zMaancN$b$`u9)UHh{f|-Ph_LbYx&B}5b%(3=E2GT0N_b}PX7RbzpQu%z;=>A*Lt$) z#$Zf7ZQKeZ#yL{UlhkxQ#Z;H!MUB#Vx>mak;1hz-hR@ZJkMgZQgSzgM6c;zTo~Nf+ zExnu;7O~ykqBL=#k@1Eg;0Y0P(Ci+STgLt%@cz9tnpcFZbb&EtGwSUc!?)(n*oecs z5-?bC&O78|Ebg+$PYkHOSlaXGv`zH+SvKE8pAU`9RFo-HmHk`%9ns3_e+&FYY>X}9 zwY<RN$(U#V03X}^YnIjgKdfna{?OCrfOa!NP#)b_0DE*Nt$L1|ZQ>hP0bdqsMVpWa zSx_D@0F*Oh{ss9|U+}Rn>@H`t)gjWaBZ@&8k~z!<On^_y+mxPgM;!6#UY;K`qg}Yc z-rh|5H=k!2d=#sw$$9Vp0FwUzk->a8(k(T=9BH~<tOR!V6CW{OhH$D#O#cA2v@U(m zdiIZqcVqi*(?hqDPe^Pu^M56i!yaW3lIa1<h7quUWl>x1jl*s$m5%q}hl$)pI>)5k zvwh=>b_auy>zL2BdC$FfJ_*uoH6H-arAs`ocQ|<UYl)o8H=0x{7GiU>#uO>e3}tb} zMm|=QYQ46%Z!^NqGdG#n_RcOkC2iK7pZo%!!?%h}PQb&sLNpSwXM?m{k;v<u0sjE& zuJ9ZIf<YDI*B&Rh@b0CfYBn%OeGSE&!bnxX^O3Gtjf7|BEXlV3gVViwd+MKKu$uc_ zjvX2_e17)rWaG9-0B$EBD9Jo`;CZ;}a&>u^Q_H1pQ$+O1AOJE+H~<XfpHIfTTjB?b zZ!i2pX3{S_ogKnyziN%PJjz-*B`Vwz8wWrfaNb^2;FDb-@m-IKb!}48`U}fX4rz80 z-d)D(W19ENeWe_ASn?0a!!a0cGPTE~BgR@Kp^T$=r$@JYJDV(!*Y?T|)`*;LkrbXj z@xjhAwb6yaxJAla{{UZ)<V@;W=w|A$#4VO2jxiELAdLdyLlwdRF^q0G$J6Om^;uP> zNOymuTT8lmHjS#~2#pW>L^B?HfI6NbeNJ;`AS`5=<w0+8vpHpTZ{7pZhSQPX+qRE| zBNm=0(j<d;kX?w&jvhsDLuB<MlEiy^RAK2QB^sOSZ}a}T`W*Epw2yNp>fZR<{hCF& zx4HXN5W3)pLH2l%`-8WhdJ|gLb4O?3qi1ZY8&P9?>Y%CnG)1GdPMn62Nhrp9AL12% z#C8pH<IP)1NAgAFt31+y{-y?n8fl5_6+`=O+B@Qp4%#n>{8a-<ZROj@;`Vqzz>3~7 z7TyDnfMPWF*yo;WzuxE9tNb^A*K&1wGa~%DO{lc;w)i7h{{H}*v@AfV2P6^ma56yY zlU05%LfU?-qS(F*+vwKuF%7g9QtD6UE7BwIMZf$cZK%zCB-VapQ>+YxyE-DM4&{#J z$_B<VPg9KN&ptmh4;b1gJv8(LZpJ@(C3Mh(jO6>8(Z4zCUlT>avC$IR(A~=VvZ}$n zFPZ+0a0tr~2M50%wa8d_PU~IqJ%*pILmrzY#k`jrA#l%cZ3#v4k;lvCJ4u#d(NqDB zYtejDrCn;8OkdglT51+|5#CvuPsn9Vjjh}UMs?3^+3i_(w$~c1yg%Ar57ncd-Hed5 z7f^YSx!{=PNtK8GJD)>d6)477d3!miRB5FytFGUv)hg9_eVTsm=0}aZEurZ*8cvI$ z=~kL^T=}!T@C^3|V%dd_iTlmGlbzfH$8m*yXQ<uZNv7Isnw}$3=iEGqi5~8XqBK@# zz|T+sJm)yBn^N$SYcY#$L40W>D*M(suaZ;;py0b4`gQ7gtkm#TwUmU}X&y3%fTgXb zvxGkfs0^h40KkV8$%D)4QKJ<)>Qh(M>#g^@*KV5~m|SEi!;!_=y;^^!#a%PQnoWy& zq9(hzJhjc!d9oAhlhg37r{NyAr54ouI~&Cn)xGr340zgQR>L;Y)8%c;ft~`Bjn#=K zhUaD4)?W~CK+Z1hbJTUi=Rf^_dZfMtv{VS1CyqE`z!_}YYaE@!EO6QWA=B5I#}|?; zZ3)w_T{i7!eVYEQV@$&vM-d3Zlr3#~9dE^LOYH*N!gkValJgVa6+ES?x7|PW(T3mm zL;gJJxw5v`G&_w-XyCDe{7W>haFOkbLWG{fe$IMiiemU~{wI>+8($uor3vQCaBSyy z3co7l*pZHZ5_{8*hpg>g`7;}87Y;4a+FDAb*49?7C~67fYOReT_AQnunySVYyDEy> zOQls;MNzfYQcGwwwri(o?U&eM-w7ht`{mAjzkYwg*LUX3ob#Udr)TC}X5QzVXGs{f zlRp&|ngCbtJll7A)yXRq|LIx<J|p<=TJgu!usdQ2Eo85u?bO_@Uhve%#b@qQ;6dp9 z@+URdZo~Z8Gbv9oq#zw9mv~9@i$DJGoPvQ!3<4bPu>*7Uqfn`w2}hV9t~BWzFJ0Wz zgc>8skIRQfKe{lM?FW(+@I3<!%x|CgNIUTExUSRRch#eE69h^Mdqp7^;7gZS@P6@~ zyr*fhLAF-WC$NRG=id?OPArjy7T(v9Ky@wQ;JvUaDJR*KoTyYHe*oGMF-dKpE0;J6 zG+0|()w^!)?eA9Hd6_rxT#meqste*$*A=d4^Y&K3T=`zh)WfCOXt2j*bv(Q_!{tr0 zih|FUI$Pn&6sOr~sp8sl&7Q98#CV3|wjW7d>JXD~4!GiHp*O`HPr*D5Qs9Q(2J0*V zrVe;xQ}YVVznoic7L{(EgtC{ik+Prmm<FqX9&_OA3_d@cRSgZWYS@7us5->>kH1Lb zS5UUieBR?Vx77j6eQlAqlo>6+ZPA-l8%4bB-@sp4@#@LoO&Rfvf|bnnV@a)yMi&4x z4B_s~qp+dUXW5li31l5SzAk+>{${C#g%5|U>i*WpCBxzBBt7$LcR|hRjss)RIdlj= zowJY<!0@I&XJsSfn5~g1vS;}g#6NX^xM8cGvh4Lu`Si}NN?_UM{&C^n+NtLF;__`) zM8fxk(5c6m`83@w81q6q2fvd5Bc~0ST@v9K6YPOqY8pUPOBDuTZ%Mw*`S$o3WAT(_ z$XBybNC|-`EZ8cqdF1)J`$1w^fl;jrFmkF|v0i4^YB2lj5SZu*Cd!mua+6(%Uvfhe zNGecFC$O-rP+y=9ABKq2s&CkRzim`dxRNn;d=-a5L)Vz*O`_k48MbMqfqI@#>}H0s z@QGMqg0i35N)$;{%@oa5;Rtnu;^cufp-37($$3?!|E|W%QVILYm8rYlFJ;H2(L?@f zfg7EBM<c5;->;ao*9ysN>sSwm%?czYG9+?!!<Oj;WYxI#B)X!NZQmW=$<g@+`^Yq_ zdb;X(!S+FNHM>5DmqHQbrB2TiDjVKC3QR`|@HQbK&BC#uGOkc(ed5kyo^Q=tn`s8= zXKPWZu4K#mqijl9-l-+teXznnSG*eGj_2kH0=gMtlefKMg2%kR3^Z&-70vjQiCNru zEzs3Jr1kW;#1dxLUfpUDNYtaZ7mq7XYuT>3dcicW5DvR-Q(s42KHekzifo0m#wM9p zBaZx3!_GN&eWKwMe!{8#C7YHapAM|<v!*u0FDJb@UEn)zuRaqE4AHT_HfLQ^jQ~4o zwz;VnUL5FK=Th)no*@_qutlWG>bk$fSEs4<JRA%q+lb+@y|E8x5ih?}-;p%i?}i(x zv<I!rRd^SY?4}V8PvgkyTI*3==P%QI7H|IQ-jKiiH4VwifQ|SzgL>0W!(2Z74ClTU zr`wa&Pp%5K9iOyOJKRJkeK87*v5X#=t|_=Wi$F`zNe2%^apZ4-q@GKU#|oY@!X+0h zj&`@qXp!&%^xg-Y?rm1cVpY<T!MzW55xA0XEw_C|s2&}B6(P<SS1GI43}X`ac6}xm zBhN0ch{usK(=N91>}xp44OEryeZlFWCS;&)VoxTbP=A$Og{*pwL6*wA9q&VH@+ypq z3kit&vaJ<7v7XaXB@Hxl`Zg!Xb`pG$6;u9Pak~%iw&YfPq&-mR8X|8X%`)d3$}&gu zKXvZ`Z-GzLLe--e6I>jo{A*HI$M5MbJR<(7%Ar;R)StlD4x<%{6xP6~4r-bhcD`el zU|}rOjUfOlQ#4fj961m-r1hsHtecu0Y1*4|;V>2cs)@{_-Ho2K1qe}H#iY%O`n4gh z)^B%up%`k*7f%b4GIL)-ouTynW;dK2YIxUXJEBMcu1&JFzc%Mdwdw00W9Q#gBS#!# zPk2IR>wVf;l?<KABUx^|o>+X3?6SEIbk_|K&O~xVCC=w#or}j1q3;cLgp<~D`nGOp zu#=+9&N{^+I+uxlrS{B*W=6`nh%N5PdMZ=zukGGW;>{~Jb<*c^DQaHU@5YVXpD6+P zHA_!RLNglT4_=f`jZpl<Vs`>F(u<^ezm#>ith2u>nF5X~`qjjbPYqZG1o>;RMOzFl zrT8byy$hKGr0O682V0dJM20_Da&?{ef9*0b<DD$(%xvX!N*!nQYf{tGUsrsHyKVZ* z2rCm!7Q?e7TuRU(m@!X(GhUzbVE!Y0**!J6MV`39Gw9yva#;Cjdt=~7cL>U37y^BR zUD;S2iBYuf0}J?=;Zth!F*&s6k_9O$B_q0d;*H?@Bic|_uh@Gv?@XRoMl<ot#LL+e zFt;$zFhGia4YpgEC(uJkIde9myAIrnloi=#A%7&Zx}$9;vukR0*k8h~c-h@pR)+KZ zcrg1*%sx`hTHw<CW~2{4&<fsI`?ZF5<j65jf++(g9(rw_Sk_vlCM^BD6}t=su(4TL zLAmQ5&7%eSlND@o6IZX^@{kcdd~b3&95o&lyZLfkI8l04)@ayEHKUPJfq(};Vn?>! zSfT!zDv30Q%P}$k47!z*jve#|LLi%05s7Gxzq4-8>(P*EC41E5LsMAUy(VnI+94E^ zYya`IV8eJ=Qt^+xTSf=hhac1uN1?r{XB4oz4gxTu|2b7V=!I$PNlw(v=Chk)n32!h z4zI=~K8Vz%O6~K%xADL+UlL->4XdoLhe_6pjFw{l0f?LiXoTRdZB1kbIl@P$N?OXt zFT6aBaS#ktRh%v-I*`JQ*v$(B_mrcRrrM}T$T?^t>M(x0F?lX5v6Qd9^dA7%@}U^# zp_?Q=c>e50g=JUOXm~&9SJ{D3<6h6;VF;#`)_;Eoo`5$WQYhzyGnHH_t>dR6g2bC; ztVJ(1zj)3T9$0LV!|t*U(KW`chtNfPecSXKa7P)gmB2(5F+l>kfYQ#;wWLU?QQ{WK z%*_1BvdXDmWWs<XKr=$LKZV2XUgUocCndLlL_bS}RvDQyJ#<+rSb4Z0bFat8`((w= znNw6GEK7<5=m4zOcd|o)ryFw$yOYA__Z95!z1RZNj2}#92IPvfUpIXK;P_~Z?Xu%Q zU{AdbnEgO}3X$~FI@&<Q;Cc^cl#(YaqX(wfUqX$~+qqC+?ZEOwI!{$~Z)J@(RCF;7 zfISE>jWv%t{P1x*|GWU>-9h@ds-c(w#`5e(R;l5!yr(FJs$Q(QU<%-adg~2-{Hv7T zrJF+fiB8F~Y1ID^-5s+-j*J}TaYd)>5~?~k&aMpAT8c`aBs&(+e&U)U;BZe{yKo;s zlcM~DD7r^OR5AmG9#Lci#q|ffMF<x{QA4Ih=QQ-8UjZQX3uj@oa5=W*KM8i!y?nXE zC7>keq>ytnup`*F;#MuaFbHc%-<%paz;&mH-O!-?R9UsbG+FD!Oooh*iEJ@O5@YZ& zIX(8=480csq~KNO#-Yi)q+P9}ZP?o!2GS%v*plyOu<sgafK5qmdLT~@(5I{{JZDI0 zw%hdhwBHL*i1;}ZLN7mRPim3OG$U42Q%Fd^{a)z`rf19^VXbsr0e2<yAM9r<8*njf zWMDMEv|GRCl$=m#k3DygfeY7tq-W{0Y^aMNZGK_($dj=-gsko5^ZTmX(64mBKYyFU z%;f<905{#t{5&vE+&o-G%>2Aueb7!G0CQs<IeOv(-LySDJpKQjx^JNQzt@{;J)|ek z(M=9uu5GGCcbNYTRFsqe=2~#0|Hd~VvQl*S_xO##Zv=iL@Ed{O2>eFi{|$lv0BVi3 AdjJ3c literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/64bit.mp4 b/Frameworks/TagLib/taglib/tests/data/64bit.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..0bd7f9f33dfa28317996eab962cce5cb2d2afc0b GIT binary patch literal 85 zcmZQzU|`J6&o5(u0AC=*SejCj2<6*?__?WYzAjW;F*B#Q1jv?5F38LQ(t;_8U^Sv3 Gc}4)(BMUG9 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/alaw.aifc b/Frameworks/TagLib/taglib/tests/data/alaw.aifc new file mode 100644 index 0000000000000000000000000000000000000000..33b4ea2a5734c10af92aa556de6c50aa102efac6 GIT binary patch literal 1890 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 CPx9XY literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/alaw.wav b/Frameworks/TagLib/taglib/tests/data/alaw.wav new file mode 100644 index 0000000000000000000000000000000000000000..cf548effcb450bf6a22521c2e22645597bec2d21 GIT binary patch literal 56858 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/ape-id3v1.mp3 b/Frameworks/TagLib/taglib/tests/data/ape-id3v1.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a761d6ca6f027cce2856921f84ec65681565b4c5 GIT binary patch literal 8419 zcmchcc{tQ>^!GnAD8-mTB4kUlZ`qd`YxeAsC0Vj(UrR>TY$HpEvdfYzAxnxRvS*J7 zDN-^msF2_NnUC-Dd;WR;eO#9rgK@6cIq&m6=f3T2;~XI@DF9N2@DM~tI7p!6VL-j3 zqC_X!K@tQmJbfZ`w(qF{XC?oK-`6937k09DaK2IN>gRDsXoK%<>&8r#NG?u(Wznw- zVo$GKJ-vD~zAK|T+bB?U<S<>YpK-*?cTuh1c=xs$9I1$;5R?kk$pX=Ev4}$_AXF2@ z0Tcv5QjlFcc*;GbqHbAnna1|Kh=VNGU5(pSYxb;bI&|D7Y(Z%O?g^W|Dkr619#N=g z;ZPo^cMIxc;t=;ZX>9oVDn3$Q-3Ajdl|k`l4*+3^BrlW-!486G2p|vyq2j2>fs`RG z0(iM>^?lk(#`BF+y<h6}Pva{Shqzy{4-1$-5~52_rWL8C5N%_%m@Y1psu*e}F|r$1 zS`{o-^RQWEr+?99&!FKR^h{`gftau)H>iWq4icA-#3`mafCdqiATIDtsA&4HauRNc z`MT*(^T-5_1WRAt56fw129lqBZ}(mekmyps_C%y;g5AmZa-*z&!WGI_rjz!61$0aW z>k5kl<I87QH=DL$AT}b&9i<9a2n(@@Qv#r;NFZe-a|aDGI4aXk9qIN8E}kD-2*sqB z+-b}_=h?5EqA93+R3K8)IkTZox2!)aiL$<L)e<A*!09SmWIxW~z54(c!u0a$`U@C{ zi%4<=i34#kb_mc{DoVIQ;?QOA2w^{(b=lT)%e2)?hHeT}Fdg3s(>l_CEmZhnLvPV6 zJ8j^zX4dUN%5J7-#i9mV9o*cb>IGb`O*uKZJXv`bE!gjVj9>}_@q^6m2Wm$Gr65v} zBXALL5X5%PRKaw~Jm6iB_(r#pN3(9$hEBOuNs`>#Q}N>*a|sJEVe0b9sh!)?N^8fb z`?;$fF0d<q4;;~mo9Nc8T3jiz&(nc{#IU4Lq(X=DsmV%cM-nHGRLHtSgKk9DHToOn zYPTRWuF&g>oJ3*Hq?(a-aA9`cGE0rR#LPS4SR-MjOJWZ^X=(#TggeXy=r+f7dSYfX zF0%Y_?=!=|Kr&d82~r_i0urbnk(?|76yqhh$l&UJ&`_Y*Od^0kElvU(WGrMlW;B-X zU-hLm;f~T=D<h_$&~5!FlVp3>wtR=dV$(QhQ^izN`A&1U)$9?SE*R(xmgE8A*a6Ae zQFDQkpmM3G;R;C;$dcp0WKT7WyewNhookn(AZ5MU+f&a|U>})lNjrL<xv7%pruZ=t zN3nqQkrH-K=YSPDdghI&!SuVwRr%kt-2U!Ad^Nps0S3~*!p%y^hag&5+y8g7f(j*S zofE8CFEdQuSS{**zG|N&8ys9*((AjO<jEdg!K{|~RzFUE>mOrA>2kRzPO!hlAG2De zQp>}yIxvGWjVTd5E0@ut0R!n0Np_$@cmULn04-4*pa4J$aN^>$Ja6=sHzc;@P-SvR z9x^a2N%plIu1J#5h-UgoeA;@nZ9U1N#Q59A%Qc@%(%ULt<$IccOL$+`OZk0fgiS>4 z{-)ei%aLpt$cXr#6fk(CLWWcaC@Ap|kX$>6*Q$MWAb&fLNih3`<q51gZ?5QH`J!q+ z=c}5xZ{92H&pA8RQp9M#X1o+_{mk6G;ot$RSE}HM86Cg*;-@@qO4r)tYjrS?1(9Td zNSoFUi8BS<lmJ0uh~$JT>~ekIWTmt<9@dW!?4q*RDrjN;E2uPa#!@dUR7YZSvgD&! zj^YTWoVvNb;gAjAR6tq0cY@Hvo?W$XRZO*Iv~ds8VIX_5w#lY|XTiay;J=?a#N`y! zt;?xqrj#vMEpOF_n)qEP93M1D2n&v@Vz=!Kvf<V|v~Jd)qG>r<wLQ(NxjY=<AM13O z%}7zASaXY8#LUpr>#-gT<V+;_g9@qOAi8-Lu6^?%+cuK8rvqQ}-FjrqN7DtFug&D$ zmYcMl-X;$47ynH2^_+gemtb2<p_OLw?W^&I=0nPc6y151U7oS}&YEK@$9aa_3jdr? z5G41ICy^A0G%IGmhoTTukc3?>5sK}v?maqk`HioLMQm@aket+DM)zM+iCPPvkKIkF zMt(Uh3G|<1vPwE%+?g%cb*d8z&&CWg*u0rpHCYgJ$TY^aH<2q80OlPcZ8|&)7sLah z`Y9-3$%!C9*UMFI2lPYd-^>wydwI&#l}dc7ya3hDkGBt%c|VG({rz}Ip04e%#Wcs8 zhChw+VM}b~8@y}fx+V=u4EINyK4!#D{ZNG~bR7${{Q|BKEf?Z)V@QSIX2r#&Ou5#N z2IMz7CiO@+9C&g{l4aE8^7j`FajTro$<HO5n|Zlkit*)Bg}?Sb#?&T7bMSEb!@${# zvkp_sgYx|cDvn%f^hLuJiX@V*?Dx<-H5WBd!v0`YB-r7hI4GWaR$rdsw0%s8F?%7q z&rqVxKr??sfFzHgko|a7ch>k>HR~jPW`Dm_>~@9jz<8Ot%=gYyBQrMg(;QYVihehb zDt|D9fo>5=KKq&Dq9#irj)LqyqM$;+wpHo6*Cl&d+PgFs^wI*A2G%r_Ou2_PzW1^Z zmGhqT8)8cG{9%yOl3ANx-_jK{{_|I;8)nolf=2HZZ~e&M-*zTG4)!q6ZDfGw!=?Z# z1k9%f=^<rc3ZPJZ;+n*JXA52jcg6g*rMP@SNLp3YAxA5u=&5;J$#R8&>Ri-Fxs(|u z|Hs=IKg-aA+#iSu#7`+U8*Z1c#e8`*@z{=C#v2C8#FA_QkR7Oy-M+RFDaa#0-c`DW zr60>Zf~00BU5Dph^(c2Aw(>F4kr3pl^bNd}@u<Xl(mW;FEnS0oT6CFbZK|f`Yf4N@ zvQS)nERDy=?ZJ+#b}-QW{~fG&YA#w(p(zSV6fhq!z~Olp`KslFJ3WU{wa-U0juHVm z%P)67u$2zC7&U8eevIyu;tINJhILPkJ}9a`K|{l7Ji60IQ~dG6ptRz9OGV?z1lGYN zTp|oqf|x=(nBe({`{W}7PZ7x+=2=E9QKJqW-?$VSQ)(1ozEgxVZSlDvpG-kl8hF_D z(Nt|lY1jvYsvBAs=|7sT*I5_KJdW4FR)>c$hZs1EzcxF{4g*yX!OqwLfjEcksPPa0 z8U}6(o`oQ8@CaoOe67<!N#8TDmkh^y#vGQ{(wsOaDu2Nvz#q%{TGjQ1i&+daTM3#z z15>U$QFYn?`%p5LMxw=wYi4Co1al(3ZQz;-3{;OLT>(pDK0NS{)n^F-I*l}IXm{x+ zThnLd$9s;sZ_paO(AVDn#IMhZ$Hxse(A5Or_v@pPYR-BP;Lf6~j_WoY>AUesy5RIp z%@NKEb`c*<4+dM6E5Sf5AaO_>S_0CmD@Ys(0x%y4>gX*>JE5Y_<n`4-ln&Qhaa>zB z>uSeJt+b(3WxTYI+*v2jrpeqVmV1nEf`S?)a+zQGpoS^&W4z-dRFS3vZ+%3gpU1;M z?L?9_GQlxiNQIh^O+gWnLbJYVu6MSe;nUZZ6Y*GAgGSlEf)Qhmw(8d_4_*FOvNn>( zuX)3T-75UwcBm0&wN}MOWaPn)V=tKos<d3UDng=hF+4C(H<9EAfb4)0Ko7yTy@W_x z7IxD(NF#JRaIZly&*WOQZI6|G$n*OnW|-P|cN$i;Q`sYHB39<bTINRQ&zh;;GBUbQ z(0@yxhF|uI$|G<4uDX#WiJ<gcF3VIHXn;s^-8TgY$y_~R+p+{Z0?1smcp68+U8#3y z)tD72tl6C^tI9#gxev12d}m*t^vijhDsw%3`rP&Wb_seh_iIdiZy0{sxKe!RE7=k_ z$8yufL*~pU85n2`OL7K4?VwlNf%%-^LxIGhK?JaE!*Ry=-QRrmT{)2Z$7hhqzllSD z?vB9xx$@Qrhp0n-7#u!=e)>jd$s;r+8iye~REue-U=ZkvP3>(yEtoG`T~03_TX;M; z3I>|Nl6?OILbUx5*|A{Bfo657tZL>jGkbNnK1bNM0W+=9cWiZNc-T}n^HTUsO_-iQ z<3sLj?mF%GXG@9?7(VUok-lnoUJ1$>SN86(T{FQ{zrXYs23i0vhs-;8s|N(Ac`9TM zLF8@Q^WFvR(yKK0Z#`s8@<DODsp6tjc7F3EGh|eViIGm+yuad<UjI=B`NRAZ4O)I% zUKeBC-8~+=-0wIlPP|$84J+JhdS)30TEUWHK(pc@h#{W^2kcbk_rA*^6{@^r*`HiB zvom9zcEvM<_Y~c?E+&>=@%YXQV_No_W%g8A^$F(f43!}Z5rxx<$9(Q~I7PnmsC+2! zn(MVQ4Q9m+o%#?4`ivzNfcnfsXhw(yqI7^%`4OOV5ST-WsL)f>a&eJvN6?yh;eo>! zGw3Y&zVWFt`<f>gX_xhV#I4;d;MjBiGdcZ}t?KL_oBFgcY~Vj)b<oeZUJ4>ZR-8)4 zwRZC`(5L^ioZ^7XQJ#=PLts8c!MhyUP93##SOra28fd3dkKt2?qK<IVt@5u%qT))p z)2OH$d|3@MI&ptKD`1oSO%2=4tVVvu4%e&(4BYt2$9pDL&YY!2XXSr^2pufwBU}Io zf>d_kozV_D3vuCbaapyo60VZj@pG<pbd4dmDa2T?)%2g`8+?A-9I5#Hgq}Su*-VwI zK(8+)J5nyfUK4i&zh`36E!pkcxZ775RMC7Rcr>IF23jVPk`Ozk&quc48pIUTfGGfF z1bHw9-&LS+oz#1}E^z-*)!wCE_RZ#eHb?&9nJdQ~KANy=>$;9ibHv=^TTW+PP*z!= zoqX&$zW&#EuRQM3b$+K`Y^^ZR3XznH!h@|I&Cm{%04BB)6&f9fr#?%r5ab^|J2b+= zxJ#?3_v`aoR~Ht`7v(m%*I-`$yzO`L*6QsL;t6^s12^}i_o>g*C|j@oF1zGXuyHR_ z*?c*xRPT<%#R*<DG7y#&wy$jf1ez6XD1fG*R41szD+z=$w(-l$@;O=M{%)d>eQ-xU zZzV^!;%#5o_H3rc{P!EQDx=%SwlkD&aikCUPn4zQ{8X7;iY}oOle!qHU#|P4O*phn z5(WZ^i$_d>ex4c!D}fpS0VSLT^9}$$rC#c5tL?k?`Js}>^(5?Bmw+>yzxV1+(_fdQ z@Vqr?Kgez`V`7@!nN1VuNuyNA%IY-0&*3Pu#6HM2{Dt9+V7DGqbjp4Yg(DSW;G!mH z&YA+Epu|&a5jfj%iSx|CPq&BpyH4S5ef6&8@521cbnP7*nzH@F*|=8y>tftD%Yg_3 z%|nOf2bJ1Cd2zF*7CIi73TtRvts18?wRWOo6j7~*fj$vQw*XKF3q)@Rl)wX_DM7CS z$pI_JgShlQDoos)6NDD3--PYNO@3zVosz+@)U}Yuod*t^ODRp#o%3J+uy{IzLI2vh z;>p&Tt@Y=_>+V7dB_#*eJR+RghfeDA?Mp6bKXYU|l|x%opeYFuwH~bP#O#vlOvXT} zwS=;FduP^@%hsjiF%!1SIF(6(hrw4w9}~Ulf2^=)w4`4}ZRs4p>z*!n%;I`gVu1U- zBX?@Da4unw_#%4AK;%mjU<(kAi@eLB4XMx+QFb74KnZcEv*~}s_tvVv-S?1IF49cx z*-ARTdAxj^UH-I~T^S3h(Ep+Keb=lFzF4XGf!N&9%SKbaoDcghabDV6xRzZwVVL@Q zcm2^mP%6|;=)j>tg&^7!aw^~m4rdO&UP2{8!V=f=y%bo4#$Eq-IEh+b?ZrIX{ij-5 zvdAPbXztkjyUbkDSoC$shrTA;@9XRq!&@Rd@BO}XF%SKlz&Q`U>3we0I0plLCX({t zIKUL_$mcGVDbT9~_{2)?)p_@|r}OW8cQlO+{?;o7h6fz@td;DvBDbjaKtx9HS46e; zlY1U<!CFc}xdo&)51*|xy_)MQb?KDe@|mo@t-2FaEKLP45U|rc6droPf@VO;k|<B` zqI!T7fG9LT=71DdItmpN%fdG1^yo;2nh683shA%Q1R_HsQwmnE!QtX&on~dNd8vs) z_Nw{cxtz|!nA-|cf^6ku^h0cX5sMFrp7{U9!yRA>1uSSFIau0gE@azQ13=)lD4J-q zYuNW=``*Cx8kcL|PJ<a;r@mxkjUPkyThk)(pFT8<v8yE*SMgkNA--}4e@C^pdZ{Q! z%guThqkf({dBh`J<X**+l0Y0-+MriK993Fm41w_gf$^~STGMHRsbIP_kFEdt)~1;m zHiGMV)Ilqb4Pn;HIpysyI*mQuIC(tYc3=*?_*WoFm7pZ6^{c>2vN4EN@@2fzN9;%d zxb*?!fk;Y#fp8EO51Wr1ngy9d+CbXCe7as32(lO~#13^j-KyU%$0{X-rM|WGId^HR zt=gzg^x!u!{k~4tiFMAHn+vQVsd@MQ;KCRVD)QH<E*<p~#WC?2$CKwBh;sv(tMD-e zj0dzFkTwKS8xXi4`osl?=%zuN?TdBGJIldu1vcC~<7J}hMmN=$a(}e2F>!i04f3n_ zE0i1xN@x@?(V&X=m$H`sCGB!8I=MAB;Jix8S0fBFH(A<Pk|(l}&_f823lc~R0MXNC zw{nInJks89p1WCYxoWAHu&^PBDGGbnxVq=Dr~S=WRPjPXP;vIPxt*t)%3_amXmXgb zYhReYjD#zXEwI1VS8G|?o7t~WBJhNKU^85B99}dR1q=kii31gD*gV?pv~jL2hA&6# z#{;)swsqMCOa*`M-Agw7A;t2vY4&~meJp8Pq90rejK7+o7Yh=;VA2n{pApvlnQyV_ z=96LQQM6y7{XqLieY7dCZ2|=8AQhV6i#MI958{hVI_m1Ik)DCgIV$P#D|el-$z~C~ zjni`P>RP4<Xr>G3IyrVriXfeRoM~$8Y4CxlvhvlpjM>6PUo5Hi%_kOdZE$)ZyD1AI zIZZMUsym*)v-f^Q$6Z6uh|IFPW^^g6npLIvZ)t*o83hrfCpwYMLwjm5wFQwxX4%c% zAP>d*V7tImq83{`5l-dKi5zmV5|iW#5lKW?+c=269n2vfIaCk0IR!xK1Xyy=Bhi== zFTM;D$JbRQSsOSWY;<I=P?8M~;nbtag3lpwE{7;YMMeL<^g!dr4U1n7FCGw3OF8pI z#iFyz#4TORzaYV)2L=MGTqf{RU_Oj0JrJ0ys=WUJ0XI!Vb#!EMQ(qozv9&zd%XyNa zrgHrFP=i_;e?z`iT7ar-v%8x8oQ;N?z(ux@!>%nVd=v%BjDqu-x!rq0;jSDtds}|z zZ<Duruwud5Zila&$r}k;9n3pGvfo4BOLvWT?kH6=ujR;08;H#9Qqze|y$_!uJf7Q9 z6kfk~_LQ5YKI`nyc2sYq>UY)ou2K@Z`#_IvLF#!O^D@`f&-xwY3r!Fw5=ai1&;A`c zJccO17y?Ql0H73(%1NKAD8|eF@~SahMxkbyHc>&Tht1l)zsk`A3`-sIO6vg`iCcNy zn_jQ3+_}NI!x2zxFI0+Cig}%0r8f3UL_qO8S#kgf=^-2*?2MpUk03607PuU^T_R{5 zRke=%Dxf>+nPsMNs{&J6*zdWVLs7t0Xl>z8W>zl16p?7GWB<6v=r&LETWfwLg^gnm z&dd#3@Gn_ja%#x@w+qg9<c%a6Bo4S7&JOet?54<3UjqQ);^sH}s=h3W*iOpj{0r&v z`=>knWt@(C(Z@U=?Wj<FlZ5AI<y18;5g^*(zQ_u<FZof<$`vQ8UA}UXufTd#A2X6& zTKxMv3<RW*hE#|F&jLYU+vY`FP6-Brps&W1R_6sR$FV}?3#Rh%N)4P*{ZQaX0mf|C zKM5DyCWWqk4O!c23i#!IS7N=me9t{x?s%W#L1FjGz}D)pdk@dbU-H-oBJM8=1Vjn@ zlGA`!tT^gb--GjK&U@_HOoLvz(YjfD3nR!0mXc5lm79|dqWL-z8dY>m-|wV9&{K6A zi}w`PJ42JVcdq{0^McUZ`dj0t$LXtN$vuSZjCgR=&qwUk8%TlDj#?kg2V5eG$Lm9N z)~%qPY?<|bzoNy-=}`9V?_o1>hp;)iT~|Jp@gF*N+(g@N_UZS>c7au+9+oNvE4TRK z3n~q5%ofu=cWHEsf<=M6VkMIkN1k`^L2@2BNaB&5(evMWwkDV7uj`xoo(r5tvd@h< zRuomxB6A-#+|_YKc^cELOwvVvm#x0}C%Xli<o(-=Xme4sPu8y8j(s*MM-?$}Nfl_D zyw&f!9F_pw2MjzgSW%RCU<zP>CqjNgiULfp_dcF>9<5Se|5kzJ`ru`q<Q}YfdDSUF z!_IKc?We!%1NRTvJsf+QuFcPzbjmiK`qJdNlvMd7v-n)ZMO2vWS11m#Z5%Kk5C?4t zOaUZL2bd37<*fZhS~q96uQj~)x~kH3_qWwWZ28yFpqjfQ(`z>Q+X5y(^>{|a(U%lL zlFk}Wyejr?^Ew%hy7TO<*IdxE-(Q0N7%$y&CHD~6w#lmxgq=sEzz<gl55buOaTWX4 zyhJiS2|RaPEe(J6C`8f>n_h#ZtqWDXeI?guD}!retgew$YqI&&SfluD;f;vVD@Etx zWsLi?f19S|ni`MGmJN}Cu%vLrO&NgsfF4p+C(j|UGs13)l0PKAb0E<0)&&`r9D~9V zY<ocB?b*+4yYH?rXFl(E<r&V8L67==dlGX`WNjcgPWob7f56KFW0ogM;|N!oUi7y0 zC)?x6K#2Q*zlqsjec%aBUV?$0La4~Jg@{N;A`U9C?^XZsT7;dzM0Yd(x-T!UMN}cf z5go$~k;op)o-~|fLlnoHeASsH?T4na_Mh03`rUho?5_lL6aw}o7lGW+;*h&~G%tC8 z!){6daokio#mYo4T^tdgx3J8#eo(H%!SPBuzH3C#!TnH@&tOK4yX@((YJIbv+V_lW znY-^>y306?Mo_7wLx-st8k;OC4rLzO?;-eZyB$pMc5;POej_L2{cXE~Jwo}OJNFak zP3ti+N8u}fW1O+KDJ0~_s%VuOv<{95bBA!Z>;=wy$JO!#O4t*$32VDocl0+mfBL-* zI+Im8`Fz)ZUvjX^fjA5hI7spUX~UAkLvV#~5K8Q?zQdU0x$ifs^mmCgFASG=f3tsg zdpE(<Wiuz`8c=WNtzuYrT-AnNqV}`clq{pP3N&kc^rTVJ9N{ib>CFowYp~gueIRcH z2n&Ef05sVY@YH$)vMC&;>H6&5)H@$IB=;v8n`AG@F<OTW>5OA(KTcz?IlP&yHhg}l z$k-t%eNoYCm&vQ4<(10pSaCmVSkYZMKWAGDw@bDA6@q&e0D&GNUpWuMy$T=oL9as4 ziQBG>FC+=(_5ODx(}=p$(WTfL%gcVeDlco@<=HpOr+f1SoW&}|x8D5w7BqYA(ya75 z|AfnbyJ=X9;!7MjqO&Swq9$M<a0CYvoUnjHGvb-SR!_EVbd(*W2?t7q3e31aiYjN; zCc2f|tYWXz{7ebDDfiCmiIYI9hi!76$1Uww>fSkKsgY*BZ$^uR%-&S}OgtUa9{)_* zNx)aOxlcyv=Q}bG7W68)LO5VPl;9xAj?6nShhU(P^FNtB)?2t<b%r1?*s-d3_*2DD z!&K#*hQ;tf2s#aZ0RNu{eB_fK!2{L)7pB^#dK!?atG|cS|Klx$|K0-r9{KnFd;S0U HdGCJ!#`IB_ literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/ape-id3v2.mp3 b/Frameworks/TagLib/taglib/tests/data/ape-id3v2.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..72c1291d1f8a4ce81a080448a370e283481a6101 GIT binary patch literal 9341 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/ape.mp3 b/Frameworks/TagLib/taglib/tests/data/ape.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a17e26993c2f74e714df0e46dc70de148e527dd6 GIT binary patch literal 8291 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/bladeenc.mp3 b/Frameworks/TagLib/taglib/tests/data/bladeenc.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e3d1a4b51f46b13dbb62105a896466f71b2d71e6 GIT binary patch literal 28422 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 TQ{Lgz37b4<6&cL`>)-zY=z)&) literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/blank_video.m4v b/Frameworks/TagLib/taglib/tests/data/blank_video.m4v new file mode 100644 index 0000000000000000000000000000000000000000..4bb15ded33f50b99b8887adbc62b2236fa18b860 GIT binary patch literal 15018 zcmeI2?`s@I7{{O8q^4rAXSvjfQ8y5%7O%{OgcPZ8sX<$)=o_VqN-nqCyQ8<+b9e7F zvFg02(3iebTZMoH75odtA1C-iMEXJm!B<hy7kya@`a(FLXZAL;-JL=TQc8J-+net^ zGdn*%`^@az?g7Bmsd&BXcjxB-LpuumMDPCD4}w0LI)1<9Qr@M%Coj?gV4uX}I9Na* z9j-~BIhU^Kk|s5V#>sZzMwD5afM<roM<$2Z&>#-$r_uIOd|Gv6FBM2Sk~U#4lkJ+2 zYdkHPmZ_nzCHAqUEVG^)Q^V6<CyJFtb>DtWX;qhf*mv7?Ogp}t0HM63AD34Ee*fXQ z@5lm`mfH!H!mobY^-@jo^Oz2f)z{pPM`f%({Px-@6y?6JD$RC#)=l!J-O!^l6exdB zFYGvpwmy#H)eh=UqBv4lDY3(Q^?sv@nnJctWw0d<`xr_(C=^O0=FIoh5HAH(u+}H6 zqk{*Bzrp?{#=qL~n?KJ2?7awMpUER9zmOJ;w=Nx#Sh&0zhse_YF{x&zgz;1qE6!x1 zMjrr}_zB}Oc3)lik)84$Mq2}83N{{OqiatwrSBf;b69QzbsDfv)#hDjxS0oztr3hm zf&JTk6=3!R_T__||5|z?sd3;bIGHkm2B=1vvE|dXY#!JPdEeA+e2#PDQyBT?iL7r% zL2oTv&G9IB+Cv|Ao@{qK=h-lDdkxQZR@a@Uf}q*)9(5Kf)oNu<RLQffZ%3|MiW1jN ztJ;a%zB6*$cFt{Iz{RINsz_l_+6M9+ppjr$@S<dq7(0Ne@rP=YVBM>7to}c{O8Zn< zl~!MOSmZ2u$2*8;Qnty-_M<&@vhqlOnR7Bc-@<a#@OHctou2c5m(A9fYlFs~b<904 zqGSGrPIsl}#t7TK?0XceFq3r<bgHo4?b7;^2K%davO?b1jMPebk6TwKtE(lj*mN_B zU5v}wjH;L;pE-;tQJ%==9i3lVTwY!j^O$j+@P0Gu_Au|=?0MHIdo#_{FaOr-mo?Rl zT6kl#N?Bgve5F!})++S{e{rL?=pAaRSxtAS;7}u=aopyi`n8Wh(Br@N&Yit;IQ;kV zGK`BeAMPl8)cfUS{xE@CiNGs3VJDW(I|l~Sw97(S({e>ispY1Y11-0<<pt2v(XyuH zik4E#O)Uo`XJ8j>-aj1N=1a;A4Ntr=Q(`K4WJ12iSn^PCkF>#53{+%~v6!%Q8H)i6 zQz=3LA!BxyA{QZFXDkLRZ!#4F71?JjCM+RiF<^<9ih&C6<%&>n<Im1wz#`;ZJco*0 zgyf%zMJgcpk%EZ|JBxt|{vP5vWZ=Sc$N&YMg?}X)xCq%t>%`K9t?l;!=Rr$H%bJ!e zT1qW9wH#=<wJmS_4Fzu;idRhVbJQXfoZXDYgoXFsCMvvdFi^oc^foWJMKJvGx5xw^ zcJP90#_}r9p&}T;_n{&boJ0JI#)yR<q?)ks%3`7-*;$NO&hUMx$c6u#Rb)bYIes)| Y;KG|00~B-?z7H9=2)+*)sDN1i4_CKh+5i9m literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/broken-tenc.id3 b/Frameworks/TagLib/taglib/tests/data/broken-tenc.id3 new file mode 100644 index 0000000000000000000000000000000000000000..809040506f29b83c48e055251e164c807d3b2997 GIT binary patch literal 400 zcmcJL!Ab)$5QhKNDhg73gj{+po36XAH`8gLrDn4%1ManlMT@WqrFWmnC-A*I)X6sX z4f=<ld^3LtldKlm2%?KI+lmAdmEg5)+t5P-F!?&PWwEbqMDorFzKgs5ZOi+<@{NN^ zgR+7m*y0Ft(*KYJ&}Cus2xN!o!H^jsH|1=Iw0=xaAal4d$E8?<S3DEA6Z7VyQ@&TO z1J_nd=UQykasxIm^H}SrG6y%bmO5UsXu@;D;bTa?6220b=?;hA?`PoV9_ugJfZNaT Q2S}!pD`4^ij1FheA4Fn><NyEw literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/changed.mod b/Frameworks/TagLib/taglib/tests/data/changed.mod new file mode 100644 index 0000000000000000000000000000000000000000..13dcea8bc98054152611f31515822de7c1254add GIT binary patch literal 3132 zcmYe!NX$!5O;ISxEXhe_fPj#U%wmO{%)C^E^30qZg``x4lA_W)pqv8`Ga_Vxs`9h- zAbMc3a2`6t0Vu$TE(qt3%EE&J$QTWc(a=B+0c18fZ7|j&3k?L@!r8-bAnYBrb2J1- TLtr!nMnhmU1V%$(U_$@^D@Pm& literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/changed.s3m b/Frameworks/TagLib/taglib/tests/data/changed.s3m new file mode 100644 index 0000000000000000000000000000000000000000..37bd49cdd47c365f265f3f56bf53f0cac67be090 GIT binary patch literal 544 zcmYe!NX$!5O;ISxEXhe_Km}3)3=B*RtPG4mVFh6(hG6F)UkA2Y1A#wC>NprVnYfs_ zS$J4^+4$J`|6>4720jL11_=gPBu%)O4Gp+tK;jNSyjlTBAc2sK%wh!~O3YKp%quP_ zD$PyJD^bWx%uQ89QiYxA02D?y&o@7%G$&OdEfYlK7v(0F6l2$nMHHKPiAkl!spw84 o(nzf46)WVH=9FaSWagzRB<JUXyj!e?#W7ffu$iZe8-Ps!07j!oy8r+H literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/changed.xm b/Frameworks/TagLib/taglib/tests/data/changed.xm new file mode 100644 index 0000000000000000000000000000000000000000..bb5db3ddd45e3a5bda8b016e0ae867179f26b4fc GIT binary patch literal 5471 zcmeH}%WA_g5JktP^s&z>5YV!#en6q%O&5V?)%?IDR#Jf_6-$P0O7rLKjODm-Uu07V zcm`V!&sd<ty|V9LqIRV&)xwuerEk^x3Eh_F(Zotais=%!4`$WPy6!BSWB_!*CAB-c z&*61gGQ!yv@3{cTcM_u}a6!(PyAbQfY8RD5t3&699Im_3|8@l^mg3Q&s9qgio#(Cl zX`3AHZ>j&QEr5;*ctrbL=>d>0FQq!HwF=(qXg<jSFjmgsstI&T;hrfik&PNFR>6O# z!noGnvIK)N?hy32Rk(+8bBJUxE?Tt?9`%zMjcohDR2xHL-uT*L3~4b=-u)Pq;N-6% oNkStK1VVC%C_$7UN=O7m38Dm1LQg>KAa>}*ApZ$shZD8K2XpUN-T(jq literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/click.mpc b/Frameworks/TagLib/taglib/tests/data/click.mpc new file mode 100644 index 0000000000000000000000000000000000000000..a41f14e9ea2175684ff2ab74d3682ddbd0785765 GIT binary patch literal 1588 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/click.wv b/Frameworks/TagLib/taglib/tests/data/click.wv new file mode 100644 index 0000000000000000000000000000000000000000..f8bd1a8513ca97bae632a2a144fa90c9582bcd48 GIT binary patch literal 3176 zcmXArc|g(!AI5*c4+Mt<cm#?Dh)ow2qIqI*cs9&4uVsQ)*484A)U6eWDBfo(W?os^ za+{`=E}P(;X_`7a-VRinI$e3KSz5mBeLw$vpFcj|=kF)+jOc3t8UPR=0El&f|MBm& zBy@md#_t;`2EHDyt~46}2;>B~M4yZ&V?Q|%017xa0syud3V;9>h!n;Pflm$>78vLj z;10mxZ{y#(17`oMZyp3cHUA$t;?v{5K<FSD6$Sk_X0E`218>eX)<8aKzlqM@IM)cl zRtQx@XKN$`8_logi~!iN?qEzwjZA4_4TS+F&$mgkDL|W+2tB285?mfq->Y;{CVPW1 z9`Ik&ocJE0#ja2)07&A8csY-X5N4o8521q$07!CiGn5ULnCL*hIgcj+V%&+QO6x*h znjHe?SwSX>*QH2QnW9Eg4vYT)A+zogBnaRrED1Y|jDIZoQm-2Uu**Il28WxhpnCc2 z15Nl5Qvw|{1o~wA*A#4!K{}7j<wJKifoe&g#zZLzHgV7)sP0+A`Hw$}QqnUk$Q0n{ zwjVj0_d7pqZA5Hty{Ru=-Fj2DRkhi&b$-X>R%hPxtpl&OY(IwmFgaPJvG0t~z>+g( z^*f{cz1C$*rJ9W!gPDmR%2+cAufcbOH<qT-QePU0NylaM-8Bg%t2zh<-PdHym`Lib z{3KLSS=-;bdxJkv?;dV?Mt2~EeCwaL<Mv<eFW0idBLAt=^8BZc^B!?Qr<hfJs}*_1 z5=sQ^yeaJUf)ybUOAeZ%Sj*6T`sQ`!``ABMa~?8ePa%0p<mz<XLVn>FGm!~gZn*3Z zh)`B95?(6Gy5Q`ii+EsTTZ&gY6RP*~GnY=g4=3`ycYob{B6!~F)C<pZqu+!e-ZRLg zU*7H9<2^3lH&M37(lVe%IxCr<^V-LNO0fc!se<FSGvioxyzP$vSj_eH=AwIde2lR) z8SJ|_n3)Res=YqM!^O>VuO&s()A8H?wB3B+@|~xwq{JSZ6S?8m+i?QDbz4)thjV4p zWjSJ;F3PhI#SYA&*9As&7GXR{4>RB}87ByFoPMYvLgvjhGNJ%o(WXb!A!%XC>#-xE zRa?Px|22>5@Iv8V>ZFq1l6UCr<XeGq&7T;MNqEE}dFsj#_bIMS&(T|;%O7)xZjSK> z5<e6U=ae-PgaFF%g<Z8bZD%9-V3Yv|%Z0PAa7Mv>$k?IQpuxc6riP*(wNb`&fRgGp zLBkHd=>8Cz`-t=^(Kv5$KErCtV>F;LD50UzcC#xg{)hO-(+j?k?g5v=c{1s2l7U@D z#LJSjv`3w!;fR%0o6odOAWFIK(p+65?|=e@so-0~roDU{*&WH<?Mtlh)~8auBO}UJ z(+*#?ynLx&p4PzPdvygh!}s^I_LmMgPv9_=a3uBu((To;9_)jz?q;Q9<n>V<vk2w& z?)!Dkw!Ycd(Cc6kiF?8%LbYZy`+a-hPt^snOQ&PHA2PB-F(gM3G&6U2$i%;5_8F}A z+`^VJ{*IF;BW|>UG@YvK)Qjf1l%y^Xrr$I;IPd<d4@(XYZoy$5?F;AF&<gm)yIeTa z>Edn|k!q5?X#lgyL^p<3n7*05IFyo5s_=9ws=;~^HEF83`l!05=;uPbUeaU9(mGV& zUd~($ONzTh6mej$6bkD~@I3XXv-c#_JUG8hTEu&8*+k+OsvLptrs@6<xK>I;Oh8ao zr@A>)9S0_@AxRd;`CS|N3L(InFq?w<t$c3awBW^@c%aqmH{RQQ2<4hLXsMH%Zhv6; zw5SuGLx{YC)mIytoTe9~8fxFZpYKL7Zr_@|<{UBolUmocTN_#rk275%)n7s&A{Hr) zX|BvWB|YLqI}r%$(C1iB$e9I}ueO(69-|TgNL$rNw%GX0Uiq!2ynNSR+6@xzbK?qp zm!FQpoM<LbgKP}fG+y3c*xT{h`I&Xd@wl(V8{H0ptxXlENQP9xhYtVX9sTF>J~UKM zRqm*3m1Jii#fgF!FDHYhh*N{f(w{F39WqO2D`})BEKWvM6~<`qNge(`36pW1b#=z< z$@al`4FMm+z|%lKvi8Yy{YHOjeb!QYI;9QGE%Eu5kt_=y%^*1y&T-5+4iZ)Mw1)EF zhV9>pZHG8oyv=!`c_sH5;4(I0H~Emvxe8)t+X~8q5_xh>MTE$aS%mnN$6B>~=0iAj zdvBr@8%*p0%uH&H&>8u{U8mvE5AVPo6j60Kv#<#D@>{~z>+h}UZh_OGg!iIFjgRMy zyR?Vca<?gA{9)~tWX{lAk1-3WvS;)AkMCg2$<OS1+9|x&pKssGKW%}s8gToL3nn?o z^YyLZd#=EL|LH)>`gM1L`Qa3$<uH5{h=X2+`{iE>uWqXRq3Vy`F2B~@6AK{5VFDuD z^GbwSZ4C@lzx+AEOs+}xc6>>1tku@*;7@tXgO72Ccld#;c^v^yU8wp<H3sr|S(Ak> zXFX$HR0|tHN{%HTw<|8SDRSPao1S#H*9hKbA$MQQFy@UE*wZRq!2CJn31V0N)gpJq zXui7HMVKGlLxWxe@(wdL-M6uVjA3KQ0P)0$;uNV)j?MBTo1hOzd?RHEu0D{mCDWi) zYlRAYKl?IC5UU8NLjH-1HpaPL51}To%^6mNq5V_<l@s+Jk2p{;h$In~c1#Z4TFRnT z>QgfN1kC=E>R7Bw5Pmu6IQm7Wbgs}B`rB3u)lnUYadXdI5De^l!mTCRh0nFsG_w7< z30b3Rcw^P06|dOStCfrPX3>{16O#RPXyNpwZu~5+lx-Px**+4_NkLXk!DkGQZsWZ? z$hiOsI8w3yPPXpvk-r{a^D#kP5+8v%g+0VkcZC9lk`U+Edw!`wDIHmOrA17IQxHQt zOanR}1pV})9~8O`T3)LQzzy(|N_bQ6EymZ6JQFr;&=r1k(?YgJ;e?x-fj^%59fA<o zKVs7{ZtAiS*P62q&UKX1F6PEN$)JIo_d=hf&?A@Tt0MHz;{x|&r`%M(GyWqC)~P#V z0hO0_WZqosOSazTRJ&UnJ2?7{k|S$c@C%+(KKIGcs4DW-Yh3*WY4Sk);uOn~=wLk% zqt%d|EgVeC!Z*}F?!!@&NCuqS-mqxl#lhIl##tL&YCm))qdrwDi-KNaE=T$GWLP~W zX?>k@am>3qjAPoPr%&}yo4K*Vp$l4|^V&MOc}!N?d_}jw_dVH46EZAX;xstMLcj?G z%Ajn#o4hYO=A!vk{;bxjn}hU}dh>$)_Oo}PIF!lTjyBYp%nT_m6to{GzY7iZ69-qj zkB9_)t=1L|p_DV=^`!#F#&1zFB-$1F#O}%}s$T}gI2S5Jx;Ng?-?~C5GS#lXN^+bI z8yl^DCc7z1rbw$I<BwRuJe(v)AU#w=ZN-Qi7k^Y!BX>L(FeW)ZVra+jqFGt!aRy+h z6dPJYrkEl2ZbVTflAZMG3x8)Jqw10J8ila(<%Vb9cbROx!IZ+7wyTisYadrsgSV~X z%?R6e&Qe`D+_Yo)em*^Cu*JR05Y!1>Eg$`Cs@(n9mTFr=JFyQg9MYY08`b4vsB}au z-Q-y7>?ZeOIRm)wZ(x4#d+bcQ>9_NQv9Dg|7@iSGmHkk?89a@}XqUv@NNYEB_Bf(L zQ0LLp(>10SQBn8o$$4aq<K%8r^JhPjZ2Wof43y~Xy%qs6bBa7EHS#i=O~yw6ApjGv zIdxyZxecT5jI<JKQ&VS+x5t;WN@j|%=^&B0Kd56G+{V}olxI_D8m?Em;Hl#S1qXAZ z?w(UaPpZ&xL0*V9i1<1~o~|4y#lDU0NwTK6^6&;cie()-5z`aEDhLBg1Qg0C@w?Am zPh1lo!x>&7r$$)FX_e+*eG6?+?Imk7s+93%D^lw`1Fw#RL+}vRNye?6@-I)~!+8|O zmkc|VP9>V-ebz3`Q$OD;zf-Sr_8p{)-jpYrc%s^E&ODZlE%qAos=+GH_Dw+{sj*Vy z16n$Wv>0gGI0g{0o64x^$Qtpuu9%zxT*QGQ0<lGx#B<{%C~KZFW?Z0C5-#Bj43T(~ zsI#jZfr!D@(pyDZksi*b#elafqL2fCX#nK0bbcy6)e!$_*^40n!-_0+9@?(^t9I5; zUOT2Z5HXS^F8p`9Bdr8wP%AOvlf%D%>jh4mi?$P^(WYYSg;sy$e6y8<f&AbaE{qVf Gx%FRQMM#za literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/compressed_id3_frame.mp3 b/Frameworks/TagLib/taglib/tests/data/compressed_id3_frame.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..824d036fa4459e57ad671a8c3bb910e384b94ff2 GIT binary patch literal 5000 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 D#nQzE literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/correctness_gain_silent_output.opus b/Frameworks/TagLib/taglib/tests/data/correctness_gain_silent_output.opus new file mode 100644 index 0000000000000000000000000000000000000000..00972c42f64f285610c17ab548ac0bfa6960bdff GIT binary patch literal 35506 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/covr-junk.m4a b/Frameworks/TagLib/taglib/tests/data/covr-junk.m4a new file mode 100644 index 0000000000000000000000000000000000000000..ac80cb29d2585e9d728a86b5027973690f51d174 GIT binary patch literal 5108 zcmeHK-)qxw5Wgf{e{Jnj?c7gBwr+EX>$Gmjbg*@+oe|w+6ciC=)}*ekX-1O`6_MhT zBErBui7@d&7>dvOAXFF#J}Wo};-i0nuM+Rl)U8DXVW1EBw8`D~^8Mc3_ru*cEdaD+ zj2lbp(ohT#B(u7v0@S@$Wy$#B0?1|OGm?}Rjz(ic02A;~aK&J%3&d5n6(U7PaY%3~ z!RZ303;u^L00>4@O<TdUJg2TK$aJWezMtB9dsY;InOlXr=Ka6`eRohbKy%G_d}rqG zG!~65SxwgIAzv&OJuqXMjiw3UGK_+>h~~@2qLmBtE0;<X%4Zj>&^7V@oR5oF<*bA* z1XZrtFT6+qd%I-2?^=*^g-R%0Th3Q>TXOHzY(9enURTSG><2w^+1?=+6naje6dRTc zIiaj`_jO}Fhx%3B&@Ix-cDR6VR7#Gpvn>{QGGKayV~SoTk3;gh(cnTE9&Z9@!yw-P z>gVgw!tUeUC>Y{f&<(@l8+975<02Fkf-T{-hH2a+gN-LB(HZ!h%i-W3)j&ViWEx`= zsAj1h4%?E7x5L)4w$mEw1hNY_^$h?g;_xGl)77<)eyrNZ9w7K4%d#P%Y*!VM7OHw5 z&x?Yzv_$P%0lSvX=?0FT4j@o=sETAA5NS<~(qtu?O)cjYYPxV<QWaWBmg$+<bEsa! zAIlUBlfev+*w}Lg13XJ>D|C#_*%5;j@!t9Aser%Dk7__XotQ;Q6tiBs|M>8T(mo(2 z;;HM8)3?^&mk^tDD*=Rj%*E$-w=KrSQ`3o;6IbqFm_gfp09S(f$@~iZnWUHmhG9U) z518992|l;S>v8+MUa#Nps}D9e1_Oa$N4P1}+}#<8bae_s@1fy=-h;7zL5PkG#*UAS zj*j*WOeEtclEWuQN5~1|_xpnl!S=?+_K`lJZ{)9GmLTNC{jwa>4Qz<vLX5cyO_(a> zz6%~ShxuA7&}7<H6<ltb9ICP6wm(Ay$6y#P1Y_`^_^Q?1!i9XLCTu5>jreQ|_4eI$ yky3O+h!>2kRcc)uhDNNn?w*p2$0zN7TJB4kf}&tz4jcv?1{?+)1{?<VfPtT=7Y1Aa literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/dsd_stereo.wv b/Frameworks/TagLib/taglib/tests/data/dsd_stereo.wv new file mode 100644 index 0000000000000000000000000000000000000000..80619270fd5d5851887eef497f535ece8427fe56 GIT binary patch literal 52595 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%0D}~_LAV2(o?6A%P~bc#88C^ z3SJTO^5Cpf-=?GdG~nWKmA`y9-$7%#U9!E_oj69U#;>)tnsl|y`Rq;=n7Vja{j^Fy zBfzc}2LQ(qE$+sh&y=_AI(W10L|}Vrr5d{zU$eYp_0H5j^?;}~9Df3lJpWE(lSL0l z!m>s4cH7L9Ic|WtlCS}v{UK9g(R%WifQR(_KXe9b+pZ%jVCsx8`vGqlF>^lA*Br1V zG^r$y8Bs8Fc(l?qQ+R;~cz?UYuoLz!qwec#9)o@+t?q&q#%tvL&q?8HBSbec3n(a5 z<*%We2zM#Nklg+4dM#9i)N)4{18aE7+x&Zrwz-m4Int@()gtw`bMObHGnbRyj<a`y zL(*$6DHkS9zp&8N-R~(?y7*!u%c%LXnrrOy`ddmWfrC<mVk7NG&=AzDm|uhG)q4-% ziAbOD-dE<@$y`&u1PO4Ovbs2>=2&-S52K0uu+~J`Yea5p4A?NR+V->B1svVX-o}|n zIkvpHa}8-~AGtkoevyzuD%Ey<AX3}nnM(3M=t}LY)XrqM@@HLOpBfDs?BBTql%Xqv z;eSV4n;@}@Fs6X1OtzdAAns0Q$n4C+4^qkFVn!LFXyu(OrNT{SbAcbPF)TN0G%J+> zEvtIj?qx!*rS^9RH{4)4+b;A6%L4+HRv<a#BZf#9dFhg$oraaCMQ-+EpSTYMT%AoR z_R-Wlt3I3lZ$z!37D*XTRb`HufZf#e1uta566rZ7v4MVq_s9vdh?xK%=*3whfDkN# z5Qv%cO+0`ssEua)`UnF`Js($m{-ICZ{u_?&vBU=|L0{F63Gv@~u48*vUaeQXV$*TO zbQ#@ti4)mulj5O}1NGDzQlG^J2{6i2FVgKIk4jc=Qz5j)yy*@)i-$u?<>`9j{n!3R zk2<&t*K}_L7)L{qWfcE_rzr`fvOW#$X<0`5u{Ah-!8^~2HF9-ATSjmRadUm(iGvla z>Y>)b_&p8po3oT+J*<To19|F)FhhE9F|&Qs@?s4wHX3O+L1lL-KIK+yr$>J~LmuDz zI%`oR6GZQqRr}Sg>)Cqk>=@KyH9vQ?cN>K)`}K+4(vX7Sw_6(fp{<-xJ-8NKmi!Gw zbkzRXCMAZGRt92PAYzQ>qY5Go(KWGO8a#`R^362a^J5~Mqav0Up*nL(385}6XE#Ab z^rFuuwk%9Rj0Wn^VsDBx;Alljv$d0;{^+h220q@c@pLIr<h5d&&YgbOktJpsFX<bB zkrA^=!_AZW&foJHZ47reEbdMmZ-xhB>FZ{Chp4In%@xqw${|LuPm&f)tC>UI$b=`X z+q8SgQd!b+&Y))m->CV-t1^X9K?vLT1e*5V5lgwrkgo`f5HumFE@ejeo9gl8^S#5% zO$E_!#Lf)gaqlZIzP({OVcaE$)y8j#pB;XDv@$bL_8qz#qkH2Rf#p0o_@c?>z>|Gt zc3jk1^W|!UzG1Lkz#?VX`VD#`>S|W#rHkd-8=2*J;9kc&<C^<bVX*Gu#Gi&aD|v?; zzu$}M#B+E8M>VSw=vmlYijO9f|ISB{6}@Ih@dQ4!^R@bju3oYEX$d+ugSE4%KXRYq z5NN7KQls)iVU0a?7nJh@JB%E)3bWAczZ77=^H*Z*Gyp&$C)GLnZ?OLhNitlcOdfF6 zAeKK7B@|abx;M*F@BE%MgqZ8WAqINZALs#qobx%_f3Fq(I{^Sp{{RR&2pJdzFhQaH zoP-U7{epP^X~2;|K5plAKM!F;C$y`u1<J`)Si&vT#SQHrBzf^a>psrGz5s}ik1*0t p*v!vOSl0>jAChpilZ!V1bWZ&L!^X`C4FIXCs{RjZQUA;V{ttRSp{f7? literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/duplicate_id3v2.aiff b/Frameworks/TagLib/taglib/tests/data/duplicate_id3v2.aiff new file mode 100644 index 0000000000000000000000000000000000000000..6703583f2126062657b1f854e1165d7c84968893 GIT binary patch literal 8124 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 AyZ`_I literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/duplicate_id3v2.mp3 b/Frameworks/TagLib/taglib/tests/data/duplicate_id3v2.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..34f4f158ce454d71677f450d1db8c40abbb85d30 GIT binary patch literal 10138 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/duplicate_tags.wav b/Frameworks/TagLib/taglib/tests/data/duplicate_tags.wav new file mode 100644 index 0000000000000000000000000000000000000000..b9865bbd57d28df1f68a9d91cc84fb5dca6eab50 GIT binary patch literal 17052 zcmeI(L2ANK5XSM(D%hPK!7C)X>89|C2|Q3M>7%YKS_mW?ecr?acs!5cq+L`kr3c7= z;3b*lO(4I;Fq;%U&yO$C%DrcwbsP;Pb@ZTfS2_)(o`%}2<GOmX2Lu8LAb<b@2q1s} z0tg_000IagfB*srAb<b@2q1s}0tg_000IagfB*srAb<b@2q1s}0tg_000IagfB*sr zAb<b@2q4g60Y4p&o_ms=?I=A?f)A-(^lgPQR@)@Fnf<u=b;DI3hdK$%ENQmda=TdW zW0MgGAkb2Qa`RsoN$<Nz`m2jHU57vbftCu){9BmVxBEr@>Rl182MoV(^PiE#_AHBO VlYdVn(%-eIz3-~ly39?j<rm4_JZ=C0 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/empty-seektable.flac b/Frameworks/TagLib/taglib/tests/data/empty-seektable.flac new file mode 100644 index 0000000000000000000000000000000000000000..20dd90d91665a122693ab1150771f7e11b2a136a GIT binary patch literal 4608 zcmYfENpxmlU{DfZ5CBpF3^snE9nA%ds%Am`S618!`pG=WiAO1a?vo!EnSt^w3=A3y zK)NV3Ewv~$FF92qCo{>-$I)5AP|r}$Si#7^z}&#n&>X0Qp%H8%h&2jELtr!nMnhmU z1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz5lo2>kzXP=?_+%!jv&KzzmnF#ZZR5TEHJ zjBhIi;xnIv@%{8be3siV{`BP_KI<JA|H)(!pY5s#OaMwhUoW6hQs84hv$~M^KYq}T zU;6)#dfW;c8XcH;n2s=TFeo@QGbuPSD62B5Xf#AJG$=@BDz@{yEptuLoDsORB`ipJ zv8&2`>5ukl7IUW>Rd}qpmU8NH$P&vXZ=4rS)$B?)zqqGKj5Ef2j`C%_OwDPNw3RL# zp0QBu*{U-;cb9a2)(WyRo*B5_yf;%^_;baz+}%aiS*JTbBu0C5DIU?7c;uJb+C)*M zh#t)uE0Q-wc`q^KQr(?5u}?*#Yu2HvGa7UEomj-By3<PaS?n<@m05ztTI${$D}|1A JDW1Lk3;<mc=k@>q literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/empty.aiff b/Frameworks/TagLib/taglib/tests/data/empty.aiff new file mode 100644 index 0000000000000000000000000000000000000000..849b762da5a8d2b1a366ea8f9d3daa87be7a194c GIT binary patch literal 5936 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/empty.ogg b/Frameworks/TagLib/taglib/tests/data/empty.ogg new file mode 100644 index 0000000000000000000000000000000000000000..aa533104d6fde177772775137cb7660a3be11418 GIT binary patch literal 4328 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/empty.spx b/Frameworks/TagLib/taglib/tests/data/empty.spx new file mode 100644 index 0000000000000000000000000000000000000000..70572b4587f26b1dfa1b93035b65a90f47ffe511 GIT binary patch literal 24301 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/empty.tta b/Frameworks/TagLib/taglib/tests/data/empty.tta new file mode 100644 index 0000000000000000000000000000000000000000..9cc00ba8156b8ef1b16401b0efe65b6d64a1b1de GIT binary patch literal 79538 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/empty.wav b/Frameworks/TagLib/taglib/tests/data/empty.wav new file mode 100644 index 0000000000000000000000000000000000000000..74b5a6de7b02e17ed293c677b873f446f77d84d9 GIT binary patch literal 14744 zcmeIup$&sj07cOU#g`Gf2C@Q@S_J5yG^o}w2qQ2aW56DqtFJoaXBf6Fncq{t{MT2T zq)MIRk?hZ;Nt<t3%kO$bfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ gfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5V*6z34A#SivR!s literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/empty_alac.m4a b/Frameworks/TagLib/taglib/tests/data/empty_alac.m4a new file mode 100644 index 0000000000000000000000000000000000000000..8c678321847428247a7776afb210b362301c32c1 GIT binary patch literal 5380 zcmeHL&1(}u6o1)T`#~t_he&F`p%l!;kk;J1L{RWj6;bdY>STA5Zo9L)-I)aHLE_1O zL%c}AqemeMLPh)|?4RJxLt%YyKiqAsB2-XeUNU)a-g`5@dB1&|F92X&wHs`-t}s9$ zErQm%BYi=0fO1v%{s0YE3^pzDd^}s)$MFH+)m1Q_w4ns_%;m@g-DC51o_U;Q02o^; z<l7j$ueM{%sT?!ijc^MRLyY148rL{2EQjM3OjyU)b@zP?u(D~nVFo4rzL(jVF1-<s z*Tq>yie#rWvT7y%nibj<C!sTz`=QGcyEK%l<Dz{}DjBnz_~ANtxrsJOUY7wCav<*{ zt9cH(EA`nq10wPR%zjF<m{dx}*+?tu3z$e#+;gstx`wz#9KxDR;_w<{kI~Mgjt2XO z2i6TusZ1X=$#ha{<vGB78^>pWg(1Kq!|@hi=@G!p<-Vmj>Px~V9m|m`j$IP+AfRiM z$QLB<rQ=G4)0I7s(|xjR@pv_{2et^*?!)xjRL#JlwexAl8Ok8320b32{voieL_ifR zjIV(Dp7JTeifGQAzGq9e)@-cfW37ep-bEDDHDU9(TTNdys0O={WMk|c9^6I^*F4k5 zKJBqvJmi8}rJin2Q5e`E`AXdEPzz5CI^hoaQC|E8%u)tJz&CE*-oSNR#pAPPDu3Jn zn0n9+?YPMJy!u5mpc&8%Xa+O`nt>BDFfr1ft|ul@Z=f0YI}A*g1%3}_0H<JadgkfZ vchL}k7Jav(XVK^&I@EE^PoXPxe`I%xzCZqx_Xqs9t2X@Ed))ca!T#YliGbjW literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/empty_flac.oga b/Frameworks/TagLib/taglib/tests/data/empty_flac.oga new file mode 100644 index 0000000000000000000000000000000000000000..444587fd0cf950ce092cc5540b81f973b52136d1 GIT binary patch literal 9113 zcmeI&Pe_w-9LMqB<7R)X=Hpsx*{roTTWGc!ViMF+=unb}K}D3HHir-v=4Ay7v44~u zIut>N3=gqOk|2nUC`n|P{Q)8B5O$FcQ6nW*B<uO?vxd;6YxMnHp5N=?_j{i4zCAzI z6p4gXfy#|@6U`?uEsfefX*<sEYuwwQDP?MJY-up=x^<-+7mHGQ(X;xLDu@MV5A;p% z9~+2|mW6&sAI4rc;c@wa#~J&Tcl=CTt28U+a~bLiw}-pJoo!*)$&OaM!WF2ht_rxS z{r*~iU7!}*|9lR9W2(NtTeF*#vSQ8Rq33pFaszKR)~UhM!%s?n-^^colxy*3nM(mF zAO)m=6p#W^Knh3!DIf);fE17dQs7@NkUdPnIZSroXv8>*QSMf=N6#OLaN^0Wk&6_N z0#ZN<NC7Dz1*Cu!kOERb3P^$fuR!+u*&K}VZxZ-^E;J%^nrjApmcJW%CY^MsF{T^& z3~A%k_B&9)bIPtkrb()&8#2GA!ZlFgTk2~NTJnmTd<QLkLmeB0mi?f5k3-8BD4P!| zny0o6K`Z8{(FthfEY*AoTJ?cS&Ow%sRH_3i{z65ZkUm7cyaJUBQ%4pcYl3QtK&u~7 z->i`B0c8zAYvR<!a>(9KB@)ou`&2~|a$Ke6cR}l}Q*8&K(hJnlX=r^fHFXbi-lDe7 zKxH?m$2Xx3x2em;(8jxzeim}MsY7mPlb3pW7;;xq=_zP)1$Al!^6aLreTTetl(P>i T_fuYbCLMKbvFzDNznJ+2futKO literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/empty_vorbis.oga b/Frameworks/TagLib/taglib/tests/data/empty_vorbis.oga new file mode 100644 index 0000000000000000000000000000000000000000..aa533104d6fde177772775137cb7660a3be11418 GIT binary patch literal 4328 zcmeHKeNa<Z7Qcaj0VBRZupy0Yf-jPnCQiW6B^Gy+lxT=X;6W@Ova5Uu@necac44>9 zMDnm{piDN#)TXHen52-3E3{ba&d$14sftBDD!O&MwWSM)PRCvUsI&j<p7(+u-G90> z?jN1b-nqFs_uO;Ox%YR@yZ79Jy1HT%gBHrk8-IL(CKr|8-p+c4^~;wVtE@Jf5tDa} z{zJ$<#A5zWu?py!4+}k$9{Favk>V-;_1&^4HmYek2$WhI>Q@ys)`@pnt2Q(G#rfha zl`31cTBWAdvErh_QvHjYSxAczZWGAYg>1_xOsFqRn-=Luciu_UZ{)R#vNl$XNNsLO zuuJc(knJCHTM#|rRb4Zma_Kk{O1iex<H%SGlv4vdE6v074;q7}9!7PX<<zt)hwJ!T z&V3PO(3E1pn-fz@^{4!fV;T^~vyFw0c+H%MI;)VmTF<FYEu+}kvX0iTRTn;WEY>^y zj{NNVLaI*Jyew)1*BRZ8c-;j7wN3{sh}WFrQzN=wAtl#vo~6X<npEUQsH;cj>X9X4 z!ofsz0U=CV#Q%7Y=;Cih7Y9U(IZMjo01U(dIio9R?EKp3!OJ{Srp~+;QH%^SHH(>U zM5wTcYuLkI#LTNfh|`th3YEG-^{!cGCx<$4EC$&TVuKm=i^aL?y?IwkEZ0u%zVTSC z@YSX<(*Z+l!n(aL-l>XBi)Z`*%dqoYs&n=SAln6|q@IL+%@={`uCgS*X+X0)U>s`k zyyf~(GaImtUYrSJjsn#aDC7D;ZZ>ePt1K}}Wel{8`Q<ck&*-hK0p2iBEBjmA{?~_q zitD#_`Zo*!W&E&(d}{;Ev(c)UX8!ywQ6Pox_Wt2R-&<qnfhrij_16IQP=t|VK!*Kl zu>-Gf`wPLDn5`GD<5dZ>Zt1?AQzZx;0sZ{=Tz$~Hh9f<+hcsnG0gpjij--uG8Xq)O zPRRj5<0ayqu2z%0ueYZoYeezhWu{qk=h%BmFK{!@VJ|+{e~v8Pojp~$^Bc{q-|_zD z@}2!dq=^W5$wUS}F<`HcPpfek(VDBA#5;k;M0sx%PBHBogbMy48s{y4RR1oz!jl#y zLsd>@Wlt6xEKRl7d`)E^?5numR{8C|&!OIL+E(hTE&EQ5ZXG^qF$8;bBgC-4c3Crr zd$9-G1c#yq{(2_f@(1w^6Zj_+ih_IUf|RvIe8Z&LGM-vl^po91&(}_7R~A2CY1zE{ zCExCflTA0?sJ-#+zAqlDm5Z1yCL^@Qvx4xX6Q1duPCQ>*e?gA4@$?tz`byS0o#Vmt zJyNTuuEp2;rvKKPN8}Mk1YxuYU0mU2S6{QMx4Co9>8XFy*W2fR?o#;9>EpM*uP$Z+ z97kv`zP=G(PvQm=2nJm{mqu6gQ6_F^RE$ihqk9}rSqp%^v^RQaEI0rm?!^=Qk#UhZ zS$dUJT8fodNyF7*%l8%Yq}rSUz$PF!3%RjkrE=Jx(2iG4?0C1UnoHgZ4H)HhLmynq zY!eLqT==TD2>VSRUhUFdRtCq2mHksC`Zj*>aJ4*@Od`Q~Z3y$TZZ?Rtg9@k&G6=cV zOU81So=`7c=;P*CMM13kO6)@aN&d7GYVEyTs0&KK(A`|#-8|kxUvVW?Jsz7|1gFOn zm;bbN0&L~+#&RFC1%`6R{}=mu;>G@7!H*pQ46eGH@Y^ck3kTDYGX*smlko%(`;%Cs zFV5@;|4GgA)aoX?tSa*(EN~dmO^RJ89|p$M&h$q^9vJAdVg`E<2I_?mzafygqL#QY z!{L=@R-2vadAxAKqgjtra54)T-~v~5b04lL$5qQh_URlS<OH45rOP0X@=09?Z7uW5 z;Glo9@?C?;$QgqgWMJ^)0mV(Qh+iB;zPPbmdzb~g0bv=vx)ZwMsy%i%_+wBJLh<JZ z6y_9zCusF&xbXbR+ycJUKihJ|diGu}z3rZQKV~RWn-7Pr5xL52NrimnCJnnIeC{jM zcHkHr>0^+{RaNGnX*rVN3itV=S`6cAbFn4g6K**I$&%F{jtnZy_j2hgs>EVxTVO{b z^I)ie57L&aO<&R)z+z<nG-GtK#B6D*6uZJ!e<Tt)zBsRd4>~KaHSM2$t;ZUPynA{T zW~S9ORhGrmD~#N}l%=(RrplLmEPBfA(}UbX%QYDH)grp57y;CiM35MFbRF!Fx1SiN z7BAIyOP-6d%Mdyv2=j|Csa8sYm~a@U`1+O7&;%I~2T77hrAkb4=}|AagRo#E(dSLn z%cX}%gs$(8g<o4?{RI;eFGY6MI{MPylpf?)k7_b_q$_haaL|-F18Gn`kp{Q<Y|zW3 zJfw-{X^0S$_jpMYWJ;S%gp?Z9WrVyFkRPQWRKlQr^ui2#g^q!nWFc;6H4Qf)<--|o z$^7A!`B4W%E<V8X5V(<sJ|e%C3VDIqVyPTdFj&r?R?)BlEK$%-ui8~XqgI=CvL8GG zm~FY%RQqS&e#P&P-}!71p}Xuws4M2_&Fgar;bMilMoQn8T7kn2DS0iXPn1wzk^^7G zW8t5Sj1cl215<QDHKnbC1}OVwru(QN>Cs2s|B6yVa>it6k2P`VpOzeS|7X`vtT~%M zUb+=L%CbYapAm!vHm;rsm2kiVdrQjO2lUR&Sv>e@`L6S}QJqY3XsSwa#FnHdi1VAP ziwKG&P0Ev{?WXRd9@5l*wX`F%FE~}+-alN`kx#t;X?c6*vK70`eM8rGygMMM-YHkU zFR1P~&-q2oE@k+$9ZQBeD=HMqtK~aY!-CH%+CwM(?U_Hl{~4*soZ5oWuN<)`ul4oS z$MCmPJL8`c4kjbOO^iX1K+Hf?1j!B!L`9OG(_I2QhLU(Y=;&eIKcoqG4CN;07bwg` zyiL!bpGZW=!Hi-iSt!U~cro<_y);GYVYlEYMot=p4439{y1{uSq79~nys%{=lx#|n zQpqGig!m?o1O|auVoK<yk|8*0>_LHv0Pl)&AUPK3GSV!|wjE7)>Y3=NQUf_pkC!Br zhSv0uASl?dF4b08?&@vilArXEqv9Fd=gg$M2TQVM_<F8>UevUah*usG=!XTt>x~<_ zZAow{>X6;;MktmYw;G+fiN3U9dt=z^pGJ0Z-gal+NBV~5!UT+0%+gl1>Q|wX(k;Qt zE!sC&tLDBs9xGu>I66gAmz^yNG<=lua`V|Go5l!vlNj_Ov;^IS1#Uq+-uUHhHTAn1 z_BOv_r<@&bI3S^<p;zGLD%IxYscnKZ;nSk!&pa!Zq_4o?7>~ZU#dgGIM>8t=1ZT6E f2Q~Z5ucPNV{h+3)?Z4Ys(arjy7nGRv!!G{@RvL-o literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/excessive_alloc.aif b/Frameworks/TagLib/taglib/tests/data/excessive_alloc.aif new file mode 100644 index 0000000000000000000000000000000000000000..9cb3a6e10defc7bff06bfa670aae859f687b0193 GIT binary patch literal 2170 zcmcIlT}V@57=EV``LmcKNCe}f=nrExr9=>Moi$x`oX!*NCMb8-oS8>9BPwfyBB8tP zg!C@D>ME$a0;3BtyeR4-qPmIjCdxq#wfuU&?|eIdZe<sJFt)wtd;gyIeV!AFL}h3$ zJPU+#p_JN60HA)!3$X$~w#WT6NSDz9;UIs4cU{t4Paqr$9f`;?KJelvmxoX!+SwS7 zEzkK<Qbbl1{;b7=aXc>Yw7DPb#A}pNZ&U0Ggi=wtF;1DYC2pL>B_VWYar5*4`(#~N z_<=>ZpaSz%LRFxz?*#s;%ZzLB?zWc00MlJ(PbxiV+uWDdGTP|H%-;Aug8v(&+7<j_ zhuy4|q_CopZXf=Z1JUCg$102r?`h3oB1tp?Rd`p0#HdR0gg+1W4k&2b+JQi0DeU~Q zlz^s<0k0Km5?54`AtPEqK8kMDQ#nl?9MR(EVPK#)h`r46a5PIP%lqv~LuK>#`<vj@ zy>mCyzlL8=zw%sby6T?4)GR&fpIJHN>kPkJSX<bGA(gSq=~QxPocP<@{rgF=?_Qos zt)9|snZS~%v-aZ+kqIoB9+%5xw)3#qj>d++h_!Rj(%2wt8LRHnt1cf4dlqg|<burI z7~qK1s9KjMzQE8pPRcs0+iX~3*>ZT(92Nui%@`jT1_<KBm?ejm7)gjZ^LjCJ3~>y7 zM3`MklA&1^MobLD01Lw!GQ$*GG-@TnVlKyr>l<1Q>z)=@v!ldn+RMb%6x{;fK%-lG znYVc1Z!J5#u1x0+w-T|80KQpMS^#qWXC#Fmo#;ClWUk%d=_*o6Fzle%MC;HDh5_8Q zpv~HFqRkph+S^D=I{qcl&L9=H*{Nubz}Z_#sSql%%2kB(glU@rg2_moC)*h#D-53K z<kPTa)keN0COSI#kmgqM`EioYpa)pca(Ktq;jN1f2)u`NyzQ#MSYz&bQQyQ8xea|Q ziT=OuIs>`^VD&FTXx(+ECf8$aX3eKfO-8e>YfO<7I2&qb=N5iA9UNODSZhe;^ZuE| z%5suPsa-N1Q<EtD?3@}G=M72bY?)I^upNp0hN}>IJ{|VvgPJ?yZNlZ3Ti83xrcEv= cyi`A-?dL@P#oEN=*U17yN%yzO_lEu9H<F5vLjV8( literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/excessive_alloc.mp3 b/Frameworks/TagLib/taglib/tests/data/excessive_alloc.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..cd8aa2aba10fa3391f1820ab1d9d4ece730fe888 GIT binary patch literal 925 zcmd^7O-sW-5FPx6g6K^IA$SrYb<?!i*i)M*1Wg-~E%fLn-86xAw<H^i{Vo0j|D==9 zf`#4$FAglb@6GUb_RYGEc?1}Zck0p>9*zMH2W62`|JguzhA_xdCIqAXw*fMj$arOu z#h)_KRLZrm6g043F?E6|gFF}}wMRq5r?>(>n=x*WJP*MYE-q%UTCJK&<~bLNt!2@S z<$^kajoAiDv<T_w#L`KJh9e)zpH0|a#T;N6p_9o3&*$5QTwn9Yt^*ss_x1aLr>*!P zXPH=Q{drU_G<OO~yA@A6gzE{9OucPhTY8tbx!#SU$8Izw#4;>wL|}tAl?Lv+QCYYn zN<WqySf#&t!0q0<L&a2CfJ~As&e*)Jt!%j8Tiwr9Rwyl$xe{!_VKd9|3;G1m14xxx e-u>Zp>XG3;sQ(ba1yrsMrO<0<f$W;}ztJ~!!@45? literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/float64.wav b/Frameworks/TagLib/taglib/tests/data/float64.wav new file mode 100644 index 0000000000000000000000000000000000000000..d34f692bf0a6dbe06cb223f6271d9030f0628ae3 GIT binary patch literal 68584 zcmZwQ3%plVo&Wze#yEvIQeIQaG&B8s8S|Ez&Dolz*?9SpsFlu;GQ{7!qNbIX6{Ru2 zC<-VPD&ENp7rBUpoP7?5i%NPBQ1F81906_85lhQ|rH|j|zuvF+{{6qqzsIAVy;*DT z@AvHQcdxa-`?A*HZ+!jhJ=^_VI`h~wzBc^A%Re(PrSy@sO*;PJl&Y@&X<(|-@b8^> zdB6Dg$D{DHuO0i%-QxfMUHtdZy@m}*+aA&O;Qt$N*!M2~-uPJG|L=ch-QQOJ-QGQ4 z*}hpT+bfnS+c%z8#(ZaY$&E*^R<<2$l`$Q*PRnB+_oB+}Q!guHdUJ#7V|8Uu$u^DX zRk=N(PZ{eSUn|*Gzg4#BvW=>5`)yLTnT*wDWt)%OT<SMoLdG;v+2(1K+gr(4U!iQP ze^$16i(cQ><CJZ+BkvFM&-M4&s+YeX>;2h2SpA!}U*jlk-wN{tZU4soUe(`^ssAh5 ze)d^?z6tYH`uuI`|2*~adVPNq<~R9%VY+^$>f4={E8818l<j{#rfjQUE8A(8E89W$ zwwL|Go;^?5o>VK_wM&)lH=kC<Jh;2$#<!nS#`L4Ls*m}t7gTP)@uD)Ou`jE>owY&P zez!*%^U1wh-X7eiY(F4l_4sS0yzzS*m9c(T*`~`jm2z9Zq-^uSn|VE%ez27{ld+nn zZ0pI&Hvd@J)?cF>^I!D(wwj`B)84#atS9mJ*mw2!*S3v})s@;_jZ?LKE6g*s{cRfd zs<sd2Z@i-IVL$$oGN#4rRNoGMUfDjoS{d_EtF%0(y??86J8!wNJ)~3F9{RYlUGtE# zo%VBOJNmA+vVYi}<|^A$+Li4QOO@@6r<5`OJ^Q!4_qkG@?IH9Zm>zgR%iC>fUof9U z|AFZ;`VXxBxu+~|>qC1>wrQ6>t>3;*w)d&MOjw<&_SL3M%C`P@wZ}HCQnuB;oArL| zzmqZDsBG&yl^cIa#_Bqj$8<8wW7Usxd!OD<h4mr4Uwgm)o*L6f`TMXMLVtqkCbpNI zukD+#dYbKz=}z?@jic#5?6+Rh=V3pxUK#Tg^&5?!TvN(3rt4R!zFquVWqbMxWxJ|V z*<Q6!*$$hbY`5H~Y~Q%Oz3d-$<)g~B|6*l3sY}@od`j6~`mD13w)&5R`SmrWym2V~ z2d2*TTHf}j|G<2N`j5uFdbB>QckM0NrVo@GyZf|$tj5!SV13L+EstrfvaQZidu-D( zWm|u0vzEu|-;`}0qipLtsE>In<@RS}tiMHl%-d6r)ok7$rZ(Om=EwB+*lG`Do6pzw zXuMzBE8AzaJu}vuwf${+k^TeoGWriplhl9Me1bl2n|kR#?0NdWB)dxeN3!33MxUqs z;|gVa*%D>D_&3V->RHNm+C*hrPi`ywhaELr*>){bw(l-cwj-ZZw$DGKjCp(d4@`Hc zJ+S$c>OX9{miELxNqb}8r#-^lucy48t^cOn_*!o%uQ5H|r}u~XCu%Qk^?#{7wRM-W ztxi&VZ1X~8TkoXy+@^0U+xiB|?d@btcTgYmFO+Td9c5eZ!t!>J-cOC`WaY*c`g@W+ zn2hyx+8&KhXnWO|ey8o(xD6Sr{@VUF_fvmw)AM|ub{wC#-H+o5%n#Fl*g<@sFulk3 z&ECxS(eBix&)fEULfLlleYY#7sN7DT&|dZrdo=r#y_WsWevAFlu4aF=N3%a;?%;UQ zo=*Q^pHq90F&$0+VMo*6*oA7366R;rUfHU*r~ExOKh;~Zt?%k9*;d2pKkTlwr*<yc z9;5cS!g_{s<94*~m<Q9o+fih!=qGHtQ{}cAt8DX7mbZJ7v0B0V!}MKcTdm{o!CWaf zPNe_9dJWqP(>BVs`jobB<LBA_n2u9_-?%Hs1Gbyb8}mgRPhk2J{f8aF@rWJI@rwN? zj%Vz=E`7i4NgNN^0drJtx7?v@d&aeu{ljj2MA;s>P}$DzRJMn6yl3y^co6dyI$mt- zPkVrAtd2Ju-&?2k+pp8!VE&2vkH!hFmiig%r9CA#&gxahYD{0rHvKQPm$vGs_SEKk zlx=+w?K7r(XrHm#M(w%H-&D5MwUlG#cZO|#hw!^3^LwRnIP1f-pR%o1>;2d~LfNJ_ z^!M0$u(D0RW_w`%y|$N4dnh*^rR|&S@!I~$UdQi6%xCg@((d@8K0i$J=s#?mey=9X zbLc-X?c80KZ~QI4hwYH1D!1d?mF?+smF<ALmF@UZ?PdS4r}2B<ZdssmJFrvPPFk*P zx8ZyO=KbkEY`wbF&-SbIADCvX)B0>b`VY*<sQ<9(EcG9a5B6wzdt0wE=8=7ReXPDr z|AFZpWn0~%_O)?O+Gnh9RkrzkWm_G;xxAjOf23?P=c^hypB3kQ%W^g6xomX->$8WD zF>TQ6+xiOLpZ&A`p4tv0WAy~v1JgEa56p+L{jeUa?Q8Q;ZGW2<ZP50|e4F}@#xpoy zY5z+9VSlk!pO^hm{YS=hZnxHNf57=v``%KO+w*H>J9VD2op!IX&6l*5{lhMru5712 zrfe_jP`2+aQ?|o7A8lV@|F%C|t@@b%s`kOAAJBhb?x20KAJG2TLG&M(ZtT(e?KQp1 zn7`9ka^vUJUS_ObQMT!)YF}+NQ0;N!7&7MnR<`vqo6GApUQWiubq-r|9mM8|s^54i z8LOj|ZGMB-x1-3I{z2L1AL{S3)hhmeyBitPzi9i}>OyT_o33H|V}76CANB=4KRbob z7t^tP{<fR`1M@fZ{jq5#-!J=7zJGSoGJXDb0N-D`alXp!;SVU=e44U7@c#C)zu0r< zE8CUqfA)-JDz{Iv|6(4m{k!o1`VUNV)IKzRj{d`5r}ib;#cF?&?V)|b`qG~Aej3m1 zRmSSLzLFbvpuMzDk}+Sb_SL4p+oZo2^EJx0+Nf;P5t~&X^To=I6UcU}{v+MS@>u^+ zxv`Cm`47AwtZw4{*j>n&M(gjh)h7OadoUT(Fl|4Zf1&Mb(-dw0#>45~?XLWO#QZY- zhrL0cf3~}Ee1U1AzCSkqmG2j(`&Mdu+as2<{c%QzvOREtvhDh{vaPRDwv+E^EBlAt zeV(%2zgD*MmMYr=xt@;stJ=RCdpLfy=hJ^+di4dZ&mN=pCE06VRypQ-XrHheqV}ut z#NJZB!g~L{l5P5blx;PS_7d|sw6FFZGFF#t()zJpuWZvnYTp~rCu1J3Y~wni)@P@X zG5>`6Se-yQ=D+FnZT&0W52nv4+iHsb9$UY!zu%_gl^d^Sdttsy+t;Rf+Ww7~sDDrP z4E=sgm_M!Gmo}}W|FA>(eQGyw{9#Yj@7rXL<@c{$yS!}AWKZi<wj&=`wr4%8Y-in| zZ2R5SUiJ_B+#F^5kL}8K=cUT_M$RADH#xt6c@oEunD(asuv2vWnJ~Yv^BXq(GyMnV z;p#sck5c=U?Loce{bfua@6-Fm+^1~oX=*QR{^mw4k9DuIP3NgSw)Hd0Ht(}p%VRZ6 zx$!^AnD3-MR==VgQ&zUsDU@T{mU65g;Qe4eOxacs>hG~>fU?cs*7mUV1Z^){{YKl@ z*4^6vHa$fDfq5doAMH^6zRY$YpEsuI>OUIyTcgj<PF$sIU*Y$!y?lkr?H@Xo?ZSo1 z_Kg|JHjh`fJyYAt{$a;Fs%%$0p=>YjQnp(-|7-_8t8#mi`j3Qp-I`L~I9UBhwsY63 z9McDCZ*1P5{sZehdP;qpKhdk@F}<&B^V5AQ$7&q?2c|EneYN!}Wt)enJ+}1{Wt#?V zF6(dnS2EWBq1<=}*-j;6^>byLhA7*-gL2~>-XEsplx@9`zX#Kv{JmITM1O+mA#E>P zuhI6j)tlP>HgDqhA*R*zANFoOPdh}Pcd~EOf7p@wz9id2|6zZ`^$`0BzK?do5`7*v zEmF3NXDQq1la%d(+uF<iVXN87cGMzeyLyST9sH!SeT?fNm_OA1-guMRgKW1`|B>w_ zv?rJrslBoJ_q0ctKA^qA>d!sp{n&a#Z^<^#?JL<<SE{|Vd0(}sjU8lr0_`zY3zTiz zk@g(xGnE^!Cu6={*;ZUvZyc@i3iEfVZ+9bOYS;T|JdKRi)B1aC{RQR5pV6OU)v4`e z(|>Dw+WZg7HVxGFw|OA_J*L<AJTXt<^Tu=}#}k-;!}kT#fjS;(+==5AJDcyLJ-kcb zPy6*Jl<l^UD%<W~D%<h5w3YqCZkefUkNJ(VJ(T^?&f$89-GluZ^P}3|8^1>XVY}2` zWK0Lro?!m5+MC8l)E;HqNqdF$OFiZN+Pt*4WSeg9)8AvyRePB*@2K|F*0Ypt^(D2( zHb1Ov>yN2Dx9NW=+xn-J+h38f;yQ?}$5L*;N5*O|<;JIYf0)nV{n?lJ`>;A*+2+aG z9=2Mq?Pb$Hu>COa&GyA~khXv0DeCW&-G|?km|xN7ZPPXU9>v^mtv)|YS8_aJXL7t^ zPvv@uUE8J4+m2hTY)_e^Y~R0A*>;a<FZ+ib`iQdq)8oqa=1yh16UTe@I*tc152ydI ze_dUc&zP=xUhBjB`Z|?k`V#Gpy;%K6!u$*MAGWG{%JQ~;sJCRBukX|PvHlm@OUy~_ zsZG<A8xNp8#ym~gR!Qx-O(!Ya>Ke*1{gQIa`K*N1P1MKw`^t^`ld)Q>_hZu!c)wWv zUVo3xXYlu8dPLi!af`NB!g^0-o4=y%YwK@l``bL0{=*K_@5zj5uNTYb(YS>E1M|uH zz1rBR{v+Fcy7hTtdW+w~_KKxizwK^Uwztkzw&U+swmG+z{ll(&P}$B{plsjnP_|br zSGKQlKHBci@gnBgt4n>Gj!^$$^WE!Ij_J>w&$5T8|48;<)PE$b?(Hed+xq6-k{d7V zQ^xc~`VadS+1{k~HDNV~_SsG(WB!w}t&iKR*RxlVF-=ys+0TXeeAX>0uk3|ntiQne zF!kv5ZFLpz59`0^@3GYg<;D*E{T1eI=}+uY+I|_+S=zp~8qW5|w1WNv^S$anY#PSr zi}_=G{+Mo6|6%j?e1Ghv-DUe`Ok+5oYIj*y%99;iE87k8l<hwEDcgEPd)YtiQPY*} zh{u%e$sNje?J{M10_USK|Caq5)A!YXG`^+wA=z`(e<aLv)xOyDj@loa2hx9FHCpYL zt*`7Y@6YBTeI?tp7wsih>y>T3W~1t3wf&}&ZT$;no8MHn)tA-2+xjwPn<pvTxK60| z>wbjtg!M?u?U%@y|BKhh>U!R<?N7$^WBq-$TFc*$^&sWO)3p6+%p<jZ8%JsTXWNg@ z2h&DAKl=dvhdq_gAM*zK4@_tA{lUDL?-!=e^Zm2)mX+<9>_L2g?Y?}!?c4`c-{zsp zcKUs7Wq+}snXhagV*j&8vVYnK*?;X%wSOnfgV_J=y&PX++Ewj`&6lcuv1zv2pT;$` zPxgWyy<e=(=`FeOD}BnCwxhqmYANj}rVG@*HhxIP`p28f`fSywZ1Z88OL^l(WUT2I z?AHB9zK!}=U#V=XW0h@wSFdmDn|Xg&eUkTwd93~(n|{akz<j8(P5-yHpRLDf`!?R8 z?O$W6=-)Aamfw%|cYMBfBK?Qmm*1!MZoWU5w&VE3c5(b-hb%AKKiOwHl<h?el<kQe zKiQtERc?2>r@ibSb{)rO_G7im?WCp3b^zDY?cv(L6XsPMKVmwY{==?%LF>bOF#QLn zOKE>F-?pLDZ#=a}8SCSEOK#k!PZ{&O%C?$K`-$m0w6B=|TiMnZZqoWNzo2ZZ1Ju6T zdW3SLe^;P?ci4Ig^)X+qZ0i##$NV9$kLeEO#{I}xPu1UJ^WXIM+v-HN2iBSGhxvYO z-^PV(fBO^q4|}eDKW2M?eqUz0Uj0Y57wPwD#`G5bhdoojZ<8I$?_c}w^78p4dugY# zoy+fQ`|`soxAkaco2Rwu_Xv)kqik0$R<`eTDcg%Ve_)^C`~v11bpFAn9XY<VV_(qv z?C&|BWxqiGf%!Y?KWu$yPg&lk{d%=NJD^V)>sOQ;r>ecI?Fk#TKFk}GZ8enk81qVH zn?Ac)uV>FAV;-m6==*pr=e07XpRzpGrz+d@QOfOudOr#Ck-R_q2!9XOJMj0~bJ!l3 zCu@7zs@C>xd|unX!t@yZhrNs6kC-ofscfIdPjfyC(|mrP+Aprr_QZUT`VX7_#`SbN zf%B{O6HBzb{mVjSdjaQf?dY3TZrA;)z3d<M#7C9w4NoZB)4P;yH|L-2VVs|~UsnH- z>{DyBygi=&!#=QH<#vnOn`A$w{v%<%b5E&n)Aqeu-u{tnm-ngMj#hh_Fdwn8ls7&@ z#(J>YW1BmaZMD1FcUzyWY}0=z+wA8GG2KqN<+@4o^^-GHp0WD4vQ6{#er$a_?-#4Z z`g?4>x3W!_u|2S!sqNLcUfZ*_@38$bzeE3l=~eX~jWhW??Rk9Om_MNZuvy=ijOlIq z4?Fo8eV+DEzK?d!5|!KI7Ae~_TiMRKRr&weR`!osx5_lT)#DZ^+d1ry_E4^0+R0oG zv2SaCkH4#|C;rY>KTvxS+eNQ)9{q=%srDw>S80zhJ`b%A>vwueZd~81Y-jc<V|*V< zw(&htImY*>WE<Z%m1BGlwLHf6RP`~w$0ghNo~yp~bA=fDi|S+SN2>4Z@MkN>{#O1y z_QMuqKh^RW`*F!OKcl~=@dz?TJ5lO4F4y)-_K(`0$!<@^Xs7gg810zWXSeWq+6VZ& z?eQE>*r(_}?3Z;s678+ldl<(nwwv#xJ-tiy><v#S+Y=vEw(EYSY)9YRUiJ@r!AxcQ zmxapq6YP(6!U~n!zi~W>(GQk#o4!c@Vdp=u<uQL+?MdT>YHy-HR=vAukL+V=ucBWs z<<ZZ#TK6hr94C})>vPmzHts-siun=c#v|1p$8k$pPaMaz`cbv#aokh>J&uD~jN>KM z$2g8ExpAxev*I{R<(ST6dHY#1#&MqNV;m2bY~wgl<yfD<-)pC`J?tymUU58HmP-S+ zazAa~IIb<_ah%&?90%+5FnxjFllJ%YA9gIq6L!b7dR>g;bgd85GaRqji@6?R-|N!y zcI9GaJARI`&C`@^J-V&zANJ*kmF?WemF=aS%J$vm%62HngZ51N5Bt_?)yMc9R&wL| zb;|YtwKws5PXB(c`j7a%r~DK854*5O|8DQ^RmS+eS+b4aqb1w;ovQj6zhhM&(|&4? z8}A@v{9e}b7{8~rJjU;Am1F!KS2@P-^pb6T6XkXU8RI-asc+*vf!2?4K0)ipIM1MR zjPnsC+c;lQvW@c?s*m+PY(IOPwr`vdQN2^y{`NM0FJhc;(d%KHkI{0NR?&ahv)8IV z#`zxA$2bq9ayx+Ysdmy*mD`Wi%68pcWxLZo%C_g~_OgH26CYHz7cEe>&vq!=A<LC* z7w4nxcIrRkyj-c5?_S;NzUn`+ow!cr_IEET+s~^1i1UW!-{X8@i*cT@<i>Hm${6P< zRUhL#X2~|rcd8uYJgCYs&X<;K<9uq#HqNuEKE`=i)yFtbtNIw{b5-AR9VGj@$Su^z zd=d5SVPuT+%z8bH^U!)dOdsm+X}o}palTv2W1J5!*;ZfC_G>&x+c(aqtKJ3L{>iT4 z_oAIm|ABEHzbt3tJipe1`Cj!Ojf439U|dH~efuEiQ|*z<RBj)tmF;KdE8FS!Dcd}> zt?VCm?gPqp-^Y~gK^@9=-ZEwTdCo^;T+b=xHm>)mKF0MRm18<p{YT>iYG0DwNc)4i zAN_|N)uZ*<k-f?o*SSh=9HjO#u7{O>kLzVE#&xukZPkC1%I)=JjO%r!+}2-G``$Q` zjBy=M>&Ke@!^U+)t<U9`u{_3gNWCA7>y)aGaUHW{8`n8iALDwc>SJ6dRXN7>)RJvn zXH|WS>#!x;xK69(F|OyTKE`$4l5JcERyoFXVwKys=s)cD`Tk&BkJj>-PT>1z*Dllg z?8$t8?Fhc#_NeKqZ|f1tcAxv&%l=|F%u}{w+5hY=?4NcF`!B|Q0A=~c?b-h^?jz9p zG43}|eT@4NRE}|<g32-OXHYrD{SGDDxDP_*823qNdAqIJ$GERT%XMt!2(_<qKSn8! z`!rgN`#H26#(f?o+qfS@^|888xsiS$?k6er+;7;p4@K+4`YP6Ek0WE;=c4ssHIesc z2az%EqtW{9Tl)Kx{URCb3$^_kZ`SsW`+3TG;y#}i<31p*-yTB0Z~x5ai*dhEsc+*x zBrT6|pOVTk?auLueU0N6d&P1sZ{O}vwlfwe+m#$Y*_>5w$KTyn_78h2$7i;?UFG(Q zrONg#u7_aWhyB}ja{OpdrvJdS<OQwI?nVEBai3c$ZydWp8RI^=l5N~ar*e$@>{O0% zA706gkI-IX+|Q@wG4As#*~a~VS{~y*L6u{@zuNc4?~^g^M^t@GTmAls`x&)<jQbr` zALBkqm1Eo|sd9|_C`-2K0N$^iroSid$5g%4YTVCR{@up?o~nm&AE?SP?h`H9#(ku! zk8z)A$&Fvq@5;C@RrOw1{}K1Cs@^r6*TlHbRrTy}{l1O+Vpab-e*fB?R_NdD&7I2j zPyD{NLmyGO?H;3S-@miH>>u`&Im&k2Vr9FwOWB^v`2#zX^9%M$oqve?mdkQ+KXZ$5 zzq8hdaUZnGG3}%Nqwy5_4|`CLmdCgsTgzkIudQ;7`?yQCai6#9W84R>a?CF&H=d*R zIPNP~y{EQvug&^*tiPw+IF^iYpS$W~&H1gypHd&=KKfGM#(nlB+qe(EWE=PCYyBAa z@vA<@eg3MC@f<+OjkmM?FrFh&eT?S}v_8z8>OUGE;rFBc@k?4B<9P?Y9>#MJDz{%- zqii2l{}InqXt_PO9%AQke$^hjMD^@3zfrbZIDc!$-=cEc{mZtpf7opwRkmM$LfIbP zrEF(&{@L!t`DuHg`j2?tr7RcE!?YOB%V>QV&(o+J<9QpEV?2+ea*XG6RF3f+kIFHg z^HDj*b3i5A`e(G4_6urH<9Q<0d%D$h8tt)dCu81C?Rn#O$r#T;X}y-~9`U?X`S*B^ zs>Pb?CXHvRJf6oY_2PN07UMatl5IXgxv@ikPqzD#F`gsS@)*yVm2BfVw32N+r>6QC z&$E?m<9RnNkMSIw$}wNU=WVys_aUCA({eZQeX$?Xe_(p-8LiI_=KE+@FHyN2wMf}k zvz6_F+m!9}N$q9-u#0CY+q6j8PGEnupWynX{So`K?O{K+Bh?<n^ORce+iEY89YT9z z?^b)0>}s_~@myzFK5wGE!gvm}{JV|kL`$~u9I0Lx<9Sn+V?2+la*XFyRgUo-tI9FY zQEuEp?Rh*8t9nCPjpt~~zuS7MawFH(Ys>ZbcwSfQ``4`;M8<dyxYV=poN&oDo+DN{ z#&gD6FUIr8CEIvjS<7QQ$E^Ao&pWFe<2mS(Z9Ffna*XGwwLIpz`n($l>v$rb$JTP| z=sz%?<JR(aFvlx)3*Se3d6(+j6;CMJF^?+So~g<<k8dmchkav)vR$}P+5VwZ*<Q}^ zo_&SmK|4|Vd$RkfJxF%?^I9L~fwU)fsM?!&-oMm~@c>$k@dC6w#yA2c+Zbn{WE<lU zs2pRQf|6~FV^Fe<aSpUR#yAM7k1<|C$u`DQ(DE4LEoga+aTrvN)vqYW7|)@U+v-0l zx5LO7<3*@G#yAos+Zbm;uZJ-Xg~~C;tI+Zo<5;L1W1I_>V~mGUvW;;vRBn&b_Rn@W z{XNEb99kY@oDMCIF^-4I?cVym8smPb-XxAgFulp~ioKEF!*=JTTA%&Lc4hnA9A(?@ zE@eCGhW4_5*s~s1wj&=`wx@L}+qKJ;?Xg@>w<pqn*bS>y-wvh!uq)T89AliAl5LD5 zqjGzR`i~fIM*lvqr^OhjM$2Qmx=-cyc(s=?-c9-U7zd}t7$>J>8{_C`d5rOPR3Bp; zo|0{h*Q4bz#`94*R@}GS$aRp)*G0xtAJh5DjR%o2#v#)C!5FVduaEg1{XLE6kTJ$X zQhkhZl1jEQj*`}oG0u|e+acQiF&<O-_ZY9K#dIydC+#tu&$6%bdld8eYqh-XQU8(c zSG!e?=|Ik>+IdS$d9wT0%69j8%69TS%C^3$t?VDR>(|Qmzy->7Mu)OJV!5)tkMq$O z<9(HK8{>efKIYxje>C2(PWA1}FDhf&RsBcf;q)JNN{`l$G0vHm#~24q<yal8_Ofvk z8DpF^)yHZVwa1M&kuk<|)9Yc~wz=fSACfV~i_`is&hu({JArI3CS#0Or{yumu`Aie zICokf#&~$Dk1<Z3%CUNqzu)daw#RAv#dv&Wx%ymf-^NR|{bM{oy-tt%k7O6qf7t8v z`NnvGs{c8C{>jdv|G<2JzCVrQ`F>$~lI!XAjAdG{U0EyJbLT7D1MgS1`LuQ&U*X{o zDBF$mmF<8IWjkq^vi&IMqwP%X-!X1vsTbo&wwSurJ~SRn|ABdm+Ly)`Ue@yVecC6C z@i6s%FviI&*~U1UD#vO!+Dp5Nj4@89UJqj&Pc4smlyc)6WQ=h__4=4ERc@R}#@g4> zF~%v?>tTJla^o>%jB!x)ez6*(+&GYoG0v*ik1-Cb-Y>>Dty-V`j<#Qn=c?td(e_RD zMz%lZLF(5VKj8DT9elnR<I!rp7~|CH^)daB;}81^-!D6y@1K2dnbv17;`?i-Kc;fK zXu7h^mnhq5_qLV&#ZH~4Y|m%^v+pfcx%~nAuRT}$cZ@q+>cu$3t^R`JO8ZwGe`b3I z{fE6x?N5w{tk+#c`-Cx$veu6=&T`2%#$#4F#(2$I55{=TCEI$K+SkTElkF9ow0?|n zq_sY*4%w{r*$c=R<5X*T{2%?tWR=Hw*Q)oUR%4uOtsi3??UHSbv#sT@+Ecml7X3Xj zUU#V%<9N3i<9(NGV?6MZZMsd{xA8%3|76dif5#YyT+3tXr{9&0v*<rCf0g44Tho8o z{a0yyn6}~fubs5KlqWl|Q`v4=plnZjNZF1brECY>-Cp(&d-_~uJHB1n4q2*ff6Mg{ zyEFSY#(4InzKwD2wY;78f->eEX<zJ_v_F`xQ~%L;e2<p5NA)V(J^GX}#`!PV#(V%O z$CxLeWE=Acs2pRS0WFU)4?)Q`<|$Bp%)^x%ZzN;w>mV5OBxwB@^C@Wk81pQs9Ah2^ zm1E4;P_m8r97?t^&x4l7m<K}3W6Tqw<uT@wP&wAKw0#?wYWv506k6{D`VadP{eF!3 zEVSOQ>-S}{@6mtQvHU*8+<%SMYp>An+n8@d>${WRzxHshhuGPjs%MW}sBAYrqHKG{ zDcdb~w3YqC4w$2CPg<;O=XELDf8zXs9nbj%J3{?OvVU5m<uT?{DcPoOo!@BOm43q> zw?WHe_4%HX8$Z>nj4=<4>SN3oQ?kty)m}DM8%uf2H>35eYc=Mn(fTmvvr#?Fdv4b9 zb|@KRo*dQ3#Q7^5^X#Y`^C*_LgUMKLr`$M0?<eN_(Rz;A%DMbKc2}|;PXB>14^df< zjd_YnwlR;9UI$~IBb8&!gH&?kX!;L(G@m!dJWE>Mp04jpwjJ~zn2zRph+VxxuV=rt zMA=^Z8)bX+EM+@+g0fvPrM>JQwrjSs?e~PT-Kk62-pu)D`ySUrFb|^tun*H7VA_xV z!;V|8_1ouZZ!qThD&;oj0V~<2ZF;r5{T&%&p0QHic%9nIn2$`$9jx{=*%f4aGVL?Q zJZM^<-9_zr%$rvJJ?2qsG3Hq-*(R=g*qY}G8*gBJ81uZ9<!#IZSF(+H;<SE@dE`pA zG0&XpW6VRRa*TQEv^>Uqb|u@G=T6JpziIo&e0W-}U#sbT`VIRepQpW+&l_W&KCK61 z9zVT4#(aM&$MihkH#?H=qkVUYmbYDtl<g?K@3x+-ayxBen|^=b)$C7pG5ednjQ!F6 zk^R+voBi3Y(*7RvG3s^BqyMnIYA<45$5I~iJhm9~Kx#RR`65-0F`uN$G3J>p*~UDS zs&DV_Q?}>Re_+gWS;}q9gQ@x$^JSK7V?IsQ$Cz(Z^)cq-EZOFtQEuH&U|c6ueP3T6 zLw$^SLiKtW^NFfH#yq1X+n9$`^)cotRXN6drdl3jzEjo5)UEB+_<^=(%$KU=c5OB9 zrS0E%g!=oKk5#YRpW^}BN&kU)D901_b@~sCd1AF5dnv~&b|A+yc2bw>+x{F6*_Dr~ z+`e(UvfXlHyN<7L*bHTR)k0;vs#DpX&hegI%<-VTp5sJ}d49|CjZ<{I8S?>ay&s`H zvEQP-v7>2^?47S_IgI&=OSUnOvC8dDeaaZ~A*(*de90=um`Azf#zQtKW6Z;><!wK; z=P_@y)^l>JG2gT5W6kf3#;tyj#5~ek4_5!q@^)V`#yr)kk1>yR$u{P>RyoEz*d^PT zCtKzAb+(t?g^V!|x7Lp_Pq)^GF`u{AhcVxG$u@2KqRQ<998X|AM88)X@1_5+f8VY3 zVVc43VSB_<t>2!~u55Rjt87Q#rEI7DyiNTFuHpB*J@j#v+e12)?Y!m6c5lunV9W<! z%58dfwd&i!^dENdI+bJoxcZOAZ_s~W%xACq81vlg^{|@STXN%-eacw3sl9A`mux3( zEcI(ld#gRR)f8o$|DtT`uWi=yn18HnYoDjWxK60|hl%rCjXzL%!hA66!}=w?zD<|$ zezAU+zXz-DDK|b&|AF}fZLh|I$@XMz-;C+I+Ww8R=r`<G`VY+C;C!Whi~a-CkJgs$ zVe{MSKN<&jm-3A1o19OzYnN(yds3}z&z`4j2i>b|r(NEz{R69CE8G8iOxfPpp=@_v zu57R8d^F}a*}pMezgqQefBFr~SFJ1MHa$!GV*As7VBSytN8^MZEsxcx-jW;ttxp;2 z{ncLDs+ax*^Uu}3+G<C&$F?4)Z1WanTmAE9y&l$ADBH|+jz(Wc_w|s8s-G}lqTF~S z8LQ2_KTKJ<u^-u9roS&?^;`aataoJlU^+qDuW^L7Z^G(IZGW5Ir2oMD6#a)Cr_VQI zI!vFx%^ma~_Go>7vb~e<7p6D3o^G#LR<?h#Z`R88?)l1g*8R%1>>U4}|Cx1vTS@lr z`O5Z9_CI^YGL_pm*nctK$^LDRR{s%yUs*2x{uX1qXnh#lP386}wLh_)RqsvOCydXf z{JV|MP4(;veaaZ0f5|q!A6g#cd!zcc-zH_7$r#^HEsy!g%_TQpLdN*sYkl}Xu7})8 zeT@C8)VHz!>Gd%7OT8Y(eyjQz`?cz0?DwjV(LSggqutQ*SpA!}U*jlk-)Mido(bCi z+3u(QJ=(8Qp8Bi*Z+uptZ?uD2{wjU`(LPe2<BR@yJ;xsy?XQ-@bUpWT*qxVaJ@&>9 zW&2-`Dcg$sN$j-CRc;5}+g|n$d-gnKdlLJfUAt7}_M7a#82w%;Z+!bXWsH7P%VU1) z1(n-x(7s?AtM;dH7VVS$ZjaW7(eG=0_TWBc`vDoN$7w(9_coULar{w?<C7NS_@!hU z$2BU)IQ}Ww#&MDA+ndQ4$5*A?#_^ZdhjCn{^<f;psT||DPURTKeI?sCE-cx`aif;E z@6un`HZoRMYI`+K)%K0!SH12`ZT~pV)$0u7_aWwQsQ+*LxPD*8@w4hLrvI>mIli#Z za{PgD+^*Nfv^T$h?Y!k$uRWww*&h11vR(6#vYqyGWjp$=wz7ZNo#rarQ`(j75lfZr z49*{5{(JUsdoRb2_7M6HOb@)E_1SG{Uod{xm2w-u|5P92_hHF4em|CM(=L6gZ(k?d z`_x{>?^Lbl)Qv61?^`X8@w>NV8^4cLj@7=KwY>dzGRE(3)yKGAsP$Q%D~#XoTF-T@ zrjuD7<Gg_C+57Z<;yi-Zd&pMaufHeGKa}z~Kha{GztH+H-Ng2?^R<2BJc#N&&GyH1 zC;f&UP5)uPrQesyeq_B~ALG1D$&H^}qm1c#{l0Bn%<o@&I_FpIs!pxnUbRr!4x6ED zx7?^~-?+WK>>qaJqsq4bVr4t2OW6+O{DHmnS(V#wtN)1ebmiak>uXvaO8<eWbG??g z{pmk2&KqiZyH}4g*1PtWY~#G7)@Qr>lrhe4mTcqvr<TV!KdSl|=TB81<GiZMG0wkg zd5rV3s*ib$vTdFN;krnkO1b?R8RNXM){k+1xnvvXpY?he=cP-wasFEMG0tnNKIZea zJsR)V_R98IZO=H5UY3jV>Mh3k_mXX#pD)?Q`FoXPoZnYD#(960+w=5&Np_X`kGOuI zdf$Gg)jx86)n2wl<#zFJl<n2Cl<l;M%C?@|R`w4&YPPcNTBK~>U7~D9KB;V<e?}SO z`cBC<uK%bU<NA=wF<nc0VxOeFvG3C!VeZ$X<uR^XmE8DRuQJAUuaa$CAJg&}*Uw6} zaeYn8V_bJrImY!lEst^CPW3Ua>y>Ql8z{H8lQFIvs=lv>{6aadFP49g>yItQb;*(& z7wP@Pbxf`2<gHwxzbDy)$r#s7wLbfVwpUzV)%t#??V0R0WQ^;!rJjxJx+UB6JfEi> z$LDSL<9Gt|!}K3^5Z@<E@9}-JH}ie8J9X)O+kQ_d+b+KEcEuEx+sPB!%l=`HW`DBR zvcK7Hu|L|??63A{_Gio;9533_=|AjqYA@nG1HF%<=|Ajf+8eu2?NQvnQ0mA14K2p~ z4<*~UFGA%Q_f3>+tKsw?c30X{JC|&aQF~lr+}EM?+3nPx$Ne9A-N9Qqii~l8NvUV! zzLSz|+@GRy%tKk;?n%bD|3&pN?vK&>uv*98gK=L?DQ}!e|ABE|PARu>|Blv&)u*(5 z8$ZwX$8?<f`^H^49<bef-Wc~C>HT2(6a9xB!SRS4&+&@=Cyr<Aye_?8dlJV(cEB8! z+bwq}+n#Z4W&f}nA5pePE>yO&JC*I>9PimXIUdBgudLK>>`!}uY3%b_pM7tgvi&;k z4aR+MTA!WpsxroXb0s&<>Q%<L|E^>k_ve*t<NiIBW8B|YvW@%xO15!dpq9tDe^AR~ z++V2WG44N9ImZ2oD);XW{|>>-@07;j)W^6#vMg`ozDcbg<NivmALIT@)yKFmvt%3h zZ>k*QzD_NVX%FSbqqKdKJzm>C+3Wbdi1|!@Pud+{)cP^aqyMmN`n?+WxvJhA`VUMy zckBJy-|~Cd4q2*lJHB1no<3LE4!B#{jvv)t_78g+zvu0i1uC}#JC*IE<;r#&&L?2r zpZ>$vt4sZCze@jsY1TTe&-SDLz_@=}%VRoA{YT@2JzC!0)~k$h-?o;=xUaip8~1;! zKE{3FDz|&mK4aWpUdnCUcV4oM`_r{N)<06VabLUYTh3?2d0(xU^IUPCyw-EURvtpe zxc|P?v-K6cKl^9>J+&Q1#<=fauY>V?fYyic{6NVzo+~KX#`6a%$FyjJmdAK*LFM)g z&R5#M(tp@rtW|ycA-`iWo~zLM?GHGgYTsL`a(jNQY^Tmsw$tuaw)v8_vVYh`)0OS? z$CT|w9m@8-Wy*Fq=cDZ_?BDi>t5qN4`IwSzJU^py%pJ5Z_5<1<JBa=R(~Uh^-d@wI zjPcx$UeA6`?PWYCRQ^4lA8Ik4FVgZ@4ODyFIEIYz{F2s>^)Z`EZoHg~@qCol@9Q00 zN3StYRC(j2WQ^yt%JMe9!Ry;mWQ^y(^m-W2hm~xrRs8*SH!{ZaXIdWPxiq~##`9|` z$NWCOKkN&9es&6<FQ#Mp{B1Y=2gdVvdcPRY<>~e8NBREQNy}7j2k`y18|SOs9{zx` z&8I2b1MhDy`-?qyzOr4({%6lvrgHlv`!DA4+P@nQp#Q)$N9{x7=jcD|b!uPY`Aoge zVzocX_Rv0IJoj1ZHJ;n6jPcxP$&EYEUfL(g7|*Ac`Zk_lE!oENtt!WO?p5o@cs^G3 zF<-3QIDu@p>ObQ7TrH3F50x9+$QaN6>iuAK6Yt0FLdJN$SnJ1X6Mw%wn2hoKvg%_z z->lceG)3FL@o@TgyDPsRF~3azVQ<jqpY3iOUtm1Ht=GqRu3N8<=|1k~utzLceLJH= z*&et+*>-V1iLI|vxt)AZTiHMC?(>xG{<X55w^Z34$n|v0U)BEI*u(LoJ)iyq)2lCN zefAi&FUel}vdS^W2PoObxB(?Mp4h95G5$cwHpV3=*~a(<s*m{`+E@Dy8Do3|)yEh= zLF>nKklOdg^U0XUE8DnEsP)+?WQ_42O1X{kAygk@+z6Fpj4x5LjqxW+wlOY+md6;s zLiI7mwNQO~HQNhge2h|V(>!he#!J+{$M_mr&l&pt7~^ngxlil&WsJ|E@|E--b|}A3 z?FNoN?1}n)o9wat{<UkDYrXcgPGvjtab<hf!^(En4a&CPUF~K6u+Pm=w*T0!Y<FI& zY;WZJfqj$n3z#Qy{D?8Gi<Y-jbo?3Pz?6TF@nKqw@ncjU^KkVajYp~d%J!gMEsrs7 zjh4q4-$vya<KL8QV|*NyV~n4pa*XkHO13fXj><8{=h5<54O4FXPcr5^sgE%}P+8u_ z_(6I-jPZq3jxqj_$}z?zD%r;PMJ3x9-$=`2jC-WzF~&tweT?ywO13esl9tC9cS+?K z<1>|PWBjI)8;9!mWsL8ne;>%_jcL02kH-DhXnl6#DrNf$zkluJD^za((5Y+}E>yN} z%uu#@yt3_?+E(@tJLXYkyW$CDdwG|#-NN~2JNQ|Z+mqCP#5iE(-}AaPtqxZI5#xvH z-{-DxF~%P&*~a)}D#sYNtYjPGo2eXQ{4*_&F+Q5gF~&_R*~a*4s*f@LTFExXWz+H) z<F=_B)1b{--u^2YV_Z1Zw|9`OpA*3tcdpd8X$b4X7`LvJH_qYxVT^yL_k%G$o?ahg z{5+LojIXD1jPdtWjxjEu$}z_6E7|5v>NjkR`={mY-F%*Qh(7OR-=_btBlUfW@dve@ z9{LaaBd&+oPw;)T6P9Q>n-(eC#j}*{^hwHg!ENnj|FG3;Wjkt-vR%DI*$#eE**?bg z5X>KHe{Z}=?LoHN(QnvGXiqT4uhjZ6#<wil#<-U%#~2^8WE<mVmTY5OO)ZZx{-)|< z-dF8uV+YxuKzoca?x&W=7$3A`8{>wS+;}}1WBk!lZY!><H;z_$jAN>L-`&dH$e7yo zei~0BWA(KD9vkDX>iyZD(Vt?B+gi$PjPF{qjqzXgzA(mzRXN7^u`0(HUsmN9<Ik!b zV|-ebV~k&`a*T0pRc?3Uc*V}<`)CjEQhodNCzS2Bk1E^lUn<-2x3rc0!)}?WY>)Yk zvOSdj(azy|h~0zz8DrexvV7y$=s#?i+KU*+Sg&&+?Fr@|tG#J_MD0<wowQdN<13f- z*cg9VuaD{WKD~}TSM6nt->iB&sy&Typ0%D?t;YD!<=<_L8?EIq#+BA`7~@WtY-`VB zYZ+%T#;?|TxeikKy2x18XTL|rYA=?vPx1aR#@*KYvoG=YVT|9c*Top$TlF!<{Z=`~ z_~0em7(ZO~F&(7s-*}4p`(*dw_aw&n<yszNd~>ZIWBhZKV~mTgayygb6?-bzL+siv z)wkmoE8A1%DBJh%RJPq?+ROf7hd!cg|Ma-By}47_?!@t)y^iBSjB)Er{l>qpR>pMA z^I9LqxcDl^7&l+#_G0xPF}}X){X+dm#u%5s{JV|u`%AVlzQ2~k824Z0n3LL58}kN~ z^2P&bk1^&Q(E2duCn(v*`~_MbV}1jbWBMiKb{ZLD{sh&>`uoa_`;#%|XHb2N`5W~5 z81p))9Ao~6l5KiK+oN%dwpYv_QR>C~5-rC36Ivc)UJ8|C9!vjWhw1lZ%x_WZrM+Hk zbqW0k=9Be%wXswEN4EQP>-90c#qVKz#Zs-`cDF0rTjwg<@pmiRoZHI&VOKt=Y-cP` zwr_VR+bfnU+t)ZBZFlE*5o3OoQs2h>DOw(5UKN#N`ZMRV>>=tulKmI;A2E+hSuW;x zX))$~DY@~&K4nZ_r2nvQk?l=tUt=B`t#=UZvz<uBnBS(<v-NSC^?LRyGA5oYv~itK z%lmxREy^+fj$Z%5R%8Aitsi4vpOS6N|D)wF<_A)JjCq4pZaeh%R~YjSmGZ`;wEbe9 zqVn%Ce^HAuuaVY=G5?XuG3G}q*~a`yD#w^#N#z*xFR2{!_I!WrrQNEJX$<F6?Jmo- zyd7IB+YR%S?LPM@+j>NM*+1-2)0ORr$CT~K9m;m?GG%)L=c6&^&no3MeP8`Y<6CMU zl08TLN6gcu<>sn=iFv&AI`7c#V9fuedKmMAm26|)u##=eFQ)n!^Ny8lV}3F%k1>Cl z>SN4rrgDsV&q}s2Kbq=e%%7(ESYM`WWBxVO$GA?Y^|>G6-(wy({rkwR{1O>sUO24} ztLu5cwm%tT-nmlW#{6_8+nBdbuWL`!_KW%L^g1K8eX|{<?VoKwJ|9dQ`TXnye6IFX zK7Wk)`}8^(^ZS)-W8OcNWBNSbKRa)k>f3|({@Q){e%rYZsJ_iZmF@KV+RFZ7KQmw1 zKE(cKk7WO}53>K-pKAY(c^J!j@*wtqdoRbA81p;o^)Tjr)N+_+tNm$QL;GYe=+W{R z^G=rB_?13ojCm_nA7g$?m1DX<?Q7$QWQ_SUOMM&jYifDShixvo@gg$T^b2<D{v+o9 z)bbefgX;A#<`32DVazY8`WW+$sy@cNq^gfGf2rzY%x_w<jd@R1jxj%~%CR1&?b~>V zwtvj8s(KauJI1`MTA%$LpRb)r|6%vl@kPx4s^#wH`-3rWtX{`<ar|P3ELXXGwnN!o zv_RRO$nlfyxmx9Rr+eDV{$bZ~d}cpZtK3dns%!^vJ>4Fz{X6FIE%jo4-xkx^^dENB z3tAt>{K2Y^F~6|NG2ga9<@VGbWsG@?OK#k!PZ?wWW7Wr)7rA5`^Cp*UV}51T$C!V) zWE=A{s~ltA=8|naLb=huD=@j=a6dAI`WW*|YyBAWPwVwD=BHLUraP1y_akG>f34*) z=Ev6Z81rVAY-3(+)yI6lwr}G?w!i%e{f9l5-;MSF{l1L(zxBH7Ic~5Q>Gx^OA70AS zTl63HO#Qx1b|}Ap?YqmhoV~PD+0NznwSD<vmD_r>vdz=l^m_!y&r!B37c1NMx|Hq3 zoIkM7aDD+}e)v+~rX4xHv}0e;`t0v@ek0j0(0^deU$6CH%x_<^jd}02K0BaK8S7V+ z8>gzhtnCRKwLZ)nlx;PX_89X@Wt%>`S+8f$BV!(?-01swE$6i|rk}Dr)~71l^ij&~ zgL*#+^O3wi`v`vz);sX`+H=?*m?vv{*{as|ZG2wazryqw{fE7a-;bCte5q`o#!u_> z&Y0$_-)Q{e8f{O^_o)A{>2F+5w-Y$OYCo|=%iF&!RJIpz{??AZS><-!uiDH0VNZNi z+1~JkvOT>^*>-dO*&fFEY5QgMAIUzoM$6md=|AiP>s4;IsGUjnQ|dnw);sr<`ZjIf ztL5z<$#!|4%I#>imkIL`8%ufPGi0m>t39^4L)lilt9`fi*~&KkhqBFnt`O7hlv}Qw zBws%{L**H(k1N|WU+>4($Mb%%TCBgv)_W`4bQ#+N>zUeKjq9~NYx@q{AM-o(ADCWM z|Is*;&(ogA=Z*OT`VX7+eaV>KrvI>$pV8-O59Rx4=PXgVJ#LY*O|zBltXq};k8NfD zn02d6vs*oGk+Pk`{%8;7`lX%B^$`2E_V@U^%6j7OZ1n@R7qMORI_J@U*qLf?l6{r- z2;=k6`mlbdr{u=<y~=iGpEAbxp=2B16P06pk4m=jeN#Ec_fX4Yd{0#$<9l4PjqkbY zTR&HbvA?K3#(t#wzW#o;a_n#A-(x>)G4@j}kFg(@Z1Xevdm4`*W3&^ce&cd&uVnwI z?V0TMWQ=x7uZPi&X?=DJpQn9*&)Xi)@q~Sf{=<G*$0O0+YQ2YXykfigKHAf}RL|b< zgt9&HQDwXCSITzu&Fy9Xuoui!wtrcuY(K&NXeX>tx&0f*gBbl_DYxm1^dENq^I9IG zpH+Q(q1v11k5%t3+9Ugz+N<c-OL_G3t=7HD7{>`E+xi@}myJ8no??DPx$#J~$8p?J z))U7ut$tMPc^vnYe~;s!7UOtH^)Zg4N^ach{;W6-Q#q#dSl)h?jB%W&`WVN9CEGYo zR5{is@b}uOY!Ca2wpSdFmgUlbt=v!BH;!vdc^v1q7{|kUJxpKV_oV$j{f8aP@r2!R ztzH-7I9=<*^bE%<_F}Gw*!Q}$yj{6i*^Zy1Z1Xf_TaRul`-gq`VP!k_ab<gHr?P!_ zxw0L~@t{4E{=>etTJ<r0hn3v8ex0&CK<!QZp3}ddtNtU~pU{8Ug*~cg@9$N{_`O-O zjo+aq+xWez`WU}wRUgxSYL6T5AY=Sq*76v?r?ouB?`@T1&3(Hzey^8uTi?X;b_5yY zJV2>$<9vbEk8vKMWE<xjRE}{TqGTKADN43+K121f-iPgHkJI*z^C7BtD%;=Q#_vUp z^DTNkjPo&C4$~_74}11n)yFvBqxu-<fmCh>a6Z*eTB>sUv0B-#o2zVhx<}deT-{#w z4}0Q+%J!lK%J$g~WjkcKvhCu0wB1hqN1T@{_43`TTisXvN1V^ozfWA(>hE4uwx3o1 z5$6rdzo&}+1LHhn`S-?gy~-HpE43WP`OK1Soaa<I#`#c{W1KH7*~a<Ql5L!CReg-} zv8s=8zE<@y&gZJW<vdoL_f@@HTFn<x-yTNBINz+-!#EGE^<er?e^28DWQ_COS{~zk zc*(Z<ind?lIoiH)K3(-L(DqMu4Zj!dZ2Aw3^Z8{t8|VAA9?bWu|7aY<_Xp!Tg6i7` zIiG5eT&8mSP_1k~Ghf+Gzfalbp>1XVuyY?!w);M&Y!B*Cw)2)L+s|`88smCSDYtQ* zNA)qT2dNy>sp>x(A5i;}>_*xj%>C#;?5G~C&yMU>#<<Q^a^oPimvLRJ{Civ{YcZ~) zm29j2n^bPECu3Z%E9JKSlG^vikz|bPfLcG+^dB~^Cu)5zzl`NEu1D(qU|g?MeT?gv zCEK{(srne#LscK+da24WuA`Q0<9e&=V_b(V*~WEREst?MSM@Qy!{>)_9a!ZU*NIDR ze2f0WexL6T#&u{dkLd)ye|GIMt<Rp!_t%c#`)!Y!uKKnfp=|fLuf6OqcEdbnJC^;= z?!x|Q$FTomzEt~n<M!<T821xs{TTNflx*XE1eIglr=W6-`x#V@alb>!HtvT|ImUew zTHbD}_A&0K&~hDHIYRAg+?P?x<9>}6<9-e;hjE`r$u{l>QGKi~RBohyi2F%OJ@*?n z?nBY~u)d1*+2hC<_qk|2SWV>p*+FEC`)IU&`<DLxWWPwp`a*5L#+$W$<9?p9p19wq z#kdbh>$iu{@7q7~`C{B}RO;KfA4$t&+^3{+OuKV@VqfF<#a^*o%iFg*l<kZK%628k zPc~<j+wpg|mHors%JG@)ZdbXzVyUuyi|Zkn_hJ9GozIo}ale^7nf?RQk{7f-yBGZj z#(i#D&W_!njB&qQ$u{n%Q#r={b}GlX53l6LM`$lG?&H()829^?Y~y}FEst@(pvtk{ zU+sJ2_sJOdBdR{8t$u&R{ft^a#{G_}k8wYw$}#SfR5`}|lqK7A0PojM)87;KW2#<i zHSXsu|8C=cPu0V?4^-tC_lcHl<33W=$GFe5<i;=QcV*m{s(P=h|A_lmRqq<kYhwPZ z`j5up`h6Ss!>ayu{Qk8&t<b;Qn>&^5pZI-khd!cm+dW3vzJF(X*+1+lbCm74#maVV zm$E&T^9Ob&=NIgiI{y&&Etloue&!bAerK%@<9=wBW7<dkN8>5<ANHUgEst?OwwA}Z zPg~^}_j8wQ<9=_|$G9I{<(OYmZahcraoks~dQWZTUYqsrSbtBsaV#0*K6ll}xDUSM z#-EZg?xQc|Htx4qeT@6@OSW;Je#y3;rN76<eg3MC@f<+OjkmM?FrFt+eT?S}O18OE z{YT>?{C>1Qeo4z?I*{{O7|%hd+<tA1vVBzjM?6na{ypu%^$<IU^Q-pIC8}qS`HiyO z!ueY}{uY(n?q9Z*{ljkisIvX~6Uz4RE@eBL^UroC&QIF|)o;XeFJ-xS9;U^3UPkM~ zc%DY(7|+|N9OHQ$m18`wqjHSrc~p+^oR7*eo&ze`)<2`Yv|mtr8qXD}-qWq7(`b)v zI~ntCYR?<LOU8H(O6#>;_lW1E%D=~RR4vwAH)%Xm<?%dLsTa>_wHVKHm2C3~%8ecR zd$Qe^jPX2~mdAMBtYjO{p_OdoIW^VCc%H3f8_&6Ed5q`bRF3%)K5x67z7N^n#P`L1 zNdJN9v1hbCJDBgIUA;u*cGMzeTg_Is3vN@k(<il;{lhMvrEJq8WjlfW(SCyKm-a{O z&$fsC+>TUx5YJO;y>F|%NOlPAiM?CxO|q-i9>sH=W%;~`_6p-U(DLs#o)<0I#`C0l zU5w{VRgUpIs>(5*S5-O2^Q<bzJV&{42es$%Jgn*sX*HgsE&p!osmhI9SFbJC-{ZMm zt?ysAau6BgdEipd#`D4@+jx#x<rvQ!YrPoHA(w39d1Wn+@jSEYV?6Jya*XGoOSbX6 zw8}A_qt^16=j!ur9IWGscph8Jt)u_Ic#d1k+rb>K*e!e??d4snZ&y5_Y{xvRY<s3E z+dRIl>>u`x8OnCyLS_4hPGx&J$9wh_jtA{T?eEF%r}iM(>CbC@m<Q6H*r94~;(7m4 zFUA3AF~$kd@)+X@lx$<Xfs$>EL!feu@d`?|F`hxmHpV;9@)+YFs6NIx2_@SYPeIFL zjJKfWF~(z1Iaa@-9Ai9(Qf{mNq}&c8V~iJ}`WWL$lx$<13B4Z1coZtf7_UOhV~k^= za*XjVRE{woM#(lGr0v^yl(v7i!|Cra#^ccP7~^$l{TSnTsNC+Y->Wh1hw4q@I0Vz1 z9Ix0L`8{lRUaIxke{5H_&&^S`{q9n>vu<cF`-eU2VP!k=ab<g2r?OqUT-hGW^>lk8 z{fFJKTJ`Nv`VYHuoysxBi7DB}I5H}?m#F`U@n-bz^Lkp0acZ<YrmOo@ZjV=c8ROoR ze~<BST8!~>O13eMj+Vz5XGirh#^Wj3#&|ti9%CFIm1D(yyNz52seD~zJoPc1uiSVL z8Dl&my&sJ6iuC%J-_hUGcn%q3JS5e}7$>P@8{;Tx{aBr-?b|p++dsx*D)nNVrWVt+ z{GPPOa6Zev%I{Ik=dacBwnzO(wqNa5Ii>?SpK9kVE#=AXUn|?)=PBFC_bA)?s<yI! z*sfnI+XELU+Zi3o_K4-m_CC%>V~qDz%597Xruvw7Q~%L;!#dTsFTbdaX;<|hjfc~J z*eN|)KgM`xS{`FOG?im@u-ePUO=OJm)>I#>UDO^o-bBV2&rPp~b=&5W8-GZ~7%xui z$2iZc<?RHry_k$KUY(Z57|*U`8{^$+eHi25sXoRyc`C>1N&bGj1KA#@?HA+mmF4Pl zwS5~e)%K6^{Pa3K>OYcQO#fl8)8`xG1*-n%^!X<{hyDZe0s8(lj_3P@=}E4q+cTDF zy>?}-Y|ovqY!AF&+2+&Qb$o?~KcH+k&R4brI+X3CWy<!WoR79MwSULBk)>XYC)r}^ zR{PL+Ed2-ODQaIDUwB!|+xKaoFvi2w`@tA5vt%3NX{sEn-DofEDl*16oq9ct@jSIW z=26OxZ;&y@3DxUkzErt!A{lF6N5>eiRIi8i<;so6kTJ$X)%(S2jB?{ZGRAnTT0h2k zta`r~<Fsmh_B+~sF|MnYyGGkL*&EsZm<Op}Z~TDI&vx+nVvI+t^<s=utJlZ$LykY} zD}2A~aK3-`y=7XTy@>Cxo&K20?V{<*HeaG_r`_9D_7^*Kp0Yik{m;I)ROR*u?7#M0 z?cXu(aH*F+<oMD4g5yg2R~>(5dj|c7y-n>;jEk(-T}1nYF`lws2V<P&l5LE`ta6O; znzbH`@tjMx^)R)sjejQFD>iBU7~@E5eOMi`S?jYGkTJ%o*7Ep2`j5#fk8!V6??<i1 zc-dM%#yHv~+ZbnC%VD*ra^o%fdt%(~QZL5yZZXFBF4@L-;3eC1o3?M`gWCSdo<;wT zF&?>=$J9^1D;sCge_;M9#}~Gy|FHY7()uuM!|z`^X?ZD6c3`Kn-LgR0p7xNk9Y0Fh z4!FC$>>u{@xyp8YyRsd!RN4NP>mhb$_HWE{IDW(!=U&U(c`qnq-jVjjo=N+I={ofv zjmP(Bd3#i^vfZOk8DqTvl5NZfpmL0P0!p?qpMc6S<{QxR81oU7Y-64R)yF(sx$#Ca z*1iscF<*k#k1?Nu){imYg32-GV^BH9d<`Yrn8%@H8}mG9d5rlWv^>T<5n3K&9to9W zJxklSajCX{%txX1PN4s=Khf{UnAbw<{knc%Ci@=!haJoBQ_TI>Xub9d{l1O)HnhGw z`Tc7T=X!{p-Kl!^$c4&w<0HzpXPmO#az|U)KkR@x%J!ti%649tvi&E{AK3AnU$7(8 ze<b^-HCi5HK9!Pf>el&<#$D+*>~R~kJXW9YDY@}ey~-H#!KgmQJTWEPJW=gsW3{oA z$9ywd&$?D)z8b9$V?G<z!@TEaEpLaCG3LureN3FcvN6w&$}x{(c{`YlF^^BF-#A0> zC+7XpdXCx3x%@qLSF#;W|A8?Nk=BnfUs1_6<}uRiV9a-<a*TP9N^TrY|6z~j^TwEG zNz2>Q^?k{<gZ=~4(OeI)t5@jt?6;OE+iQQLY>%F$Y$s1pwkxKzm;J+b%~rPko=~<s zbt&7MIsa_m<9Z0@LG&N?VcG*s`_X^caqG2y`#kLp#ynr8+{Qd$CEK)3ua>vJBV)`n zR>~W%Q+pZnl4-ev)t)B1f^1Kwea4svP3yC}s6CJQ(#pTbd}=MmJZmM}#B~o_^IT!$ z4Xh7ip0~2Rjrrh8wlQCv){iltT*)@(n^S#^dFWJ*F<+gQ$C$^iWLv+c?b-M@ZU2}L zPs{adHN8*2VV~slwAb=^W6am5^<d2Br`N}r=TGIBp6B~!NAi8N?=I2uwri2H9mV(E z){|9kr%i0r?+?71{mCw7f3ugdKiWUCzuIrJKigH>-(x;Tz0P^`AGTNRMa=71%45FA z7Gpk0Er&5rq{=bokyJUxe3K>Hn1@pJ?freq_I&yejQK80xsCZSRUc!X%#v-)r>XiF z^K7a<#(bP5+x#=it@{a#>x8QB>+55vk1=1UUJqkFQPszoZ?t3^^O34P#(bqJ$C%Gl z%VW%Qs`{9^wY?fY(DsaZQ?=Z#t>(S7{Tq)^e;@O*>UH~bJYYNNKQIsFc*4F;|A8@2 ztkz>M<#@#o<aowT>Qa5%pW`9B@==xBH*Qz9TW)OE@f8l6p=_^OsBBktD%;aJ-m{B2 z9<<kUoQV08^dEMLjyGdIV6FEfv?umkv^RD%?UB9nRV|0r(>*2In8#S<_NG2%jQNmN ZA7j2`m1E4KTyo<fo0Ku;Vb=2D{|5hl!8-r| literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/four_channels.wv b/Frameworks/TagLib/taglib/tests/data/four_channels.wv new file mode 100644 index 0000000000000000000000000000000000000000..de682f242fdb7eb8792bc3d33b10361a3b6c731c GIT binary patch literal 53520 zcmcG#bx<7Pw(dQ+`=9{^x8UxBd+=Z(xCThD;4t{$?l!n2BuESrg1cLggy1j`+;!-i zeeT(3-#YiZRl9E8udCjwuI_hrzkhVCetz$ImcO5;TRAZZgo6nJsb`^s{&B4GgFsL4 z+t{eC4E`{)7;37i`Idu0riv!2P9DBYTz_Xg0GJ?^dJu@o4hw_}A_Y<X9e*DHLi<Pf z-}7(~=nY7+(96Nr*H-dxtP+N)sk(^<NErjRxN*V^!u*dHVPX8obPzT=8Yu`wgocUf zh;H-uP68ed=m^7_I7Iq*tFs0Jl7ihi63*fRTBYlwu~(s?qo_bDD<g@9tHDV{CnF$a zf=ERnI7U2145Li1r(mH}Gp7t5WS^P4fn%yG<$cn^WNxY|^eL5yK0+pKmf)iINyMP{ zi{{npFjFJz1$#@p$MPcb>nm$I(|ajANdxjzwB;l%FPio?c$$^_c_HkZO_3e8Q$xhW z7cT4mHcC1X>sKMY&U#`oV%*<JZ=~S9oWq*3&dj83XN{?LF+nb$p_uRBq9=5xAuh>3 zATpBZzB<Q4O{_x?(CQw@NJs!2IJr*2vW4br^uJ``Tkz+FKUGN!l-FCfp+RWPIR^kz z$vJGHD1%-q^ja?VR2#?AB|s7KrgO=ClTIii1xR-3<X+O*605SCL!<SCe#rBzle)b( zUta_x1v$zpdl~jgxKg7D2T1}VrPcxrSuw9MA4@BqyG*2h)^C2gjP@92>)?KKGDPW4 zf2a7iH#}J<E6~<?9~IC;|A+^M<f`uRE7%hM0I1zHv^_ug5_<ouy6MU}O%{!1R9KHJ zT)nU-NKn~sd`(Q>@%R?fx$4gbH!(v(nQ9<BLu}FS08Lq&oh1D17dM7k2MK*-BK4c} z<XZjECUnm&zm3P+d|A1-7lC}g+q!<ubsKej|MT#x<KXJm5$e~`ucNV`@5_6hT<O>f zb!FfTDh#|)du%dz#^9hJK8>4A3WrD+tCS=JOsjyUL~d=b=jNgHj&a2;XxyYY3U>71 z^RT%D+zC{W(4AXf;T%!&YTXR7S#m1ac`Hq_GBXVXgtzqlZpmzR0MKs*?PF&_+cB9W z7Rzs`r3#<fRRo!zA9|oXf;&*lS1T(WhtERu6r+_CX=bG}kX#vHy6hMxl5EVUZ$xoh zxP5MYQnp3$NE<<w{u+jR-N(5;(|d_)M`3tp58|34-L0XaVf3hro6dXGO%&r5Nyu4O z?ss_-e`yc61=hXBdv)S>R)o0IS>;0%d8PTCE$>;k>xD4V?d@bWHDrG7e{TFf$8K-& z()e32Dh&4O?%?3vO3+&*4d{tWq~oAQ(mBg0?sQC6ZBZJN>QAHQ-i!%B+~bXJdZ*5~ z67sOTh?_f^2k6=3D*3LZ_ztJR?QeeS=lsvmZmsl8*?kjiXHbQWH+Azp_p-ko(HH0& zZ?`78{FSOBo@qYFhFVuYIxR)>t|{I4<xq2pDG?nc9S}}^Wy2%MDT%h=Kx^7i)ps#w zp}pPg_wmEGtCzo?ceK~bJFqfkV6YgWr_B0j3YC-_Ymh}~_@^W|yo`2v?i>fk#_5lm zb}q_D#wMH_l6skQnwTEefc5qfIlv%&4=kS<U9BLIUqVo4SBSe?=mm=KH(8yFrQY~< zEyFv<=}<Wx8tq=2*rO`qHX~!6%BpBQrSEtJGhS)fWft-5@r}60Qe^U+4i-O~st&}3 z$5?-J<^Ea<KDv6i4Gn$%Eu&XB2D-kv@2;hk!JOa1HpEPBGt_k+kddC%C-yCb*jAq0 zA*B<~>5VCJ$I5VQ)y>SH1}5>F76v@?!-0tb?Tq|>10B#cF*jhS=c@Y>vQ(3Uz*VF7 z6F$shtIPI`5se{!8P55WiT#Y(kbXI~@_Pg;E)g*jnNid{mq9ynD*EGx+J(p7)dT9_ z;^?aVOR$)#QbDm@G*-`solrSTrlCr+dUUBaxp0GJ8*3{k@5!VjA|hQ#Q1)%Wi|^o1 zl^)Y=?O*-Iq<xBy9%QMKABLWz=@7<msD}^--f!sSPDB4BM=+o)4M`*_6>IhqFStE6 z(5e*#y}^7bFAoh9(oNaJ=03Cjn(t`13bXFmdowOkh*`A@=5HFr<19DJt7JRij%OoG z7<8;_!`V*s*Jnbl86lA|Fxj-rfszvL$KB-<r|BI{_QCK^6D{a+1@#v;KYK{#A8yK4 z8(ACl%{SKZS?>4Zc%MO)G`5|>Op$AO-(ZsF3!4-oi+pll?GqN9S(|OzK0S3ldbqth z9RK!BX3Qd;6CSTkyisDvuGyXDJfe{(OjW}<<!uLFFDO2UCeSK6RzYr}g)1J<1Q>%i zDi|i-Nq88t;qqQc&6|@@HYnd4+#D;KWGLla!-2cBWRZ|M2v7ifHJIWP`t$y#)t-=x zl=bXzb)tcu^2aWR77>1-{N=XHyQ{<f-v?C*4rX?BGtj42<A&+m1a4CcV0OPC{6w~< zJVv3uuRF7DxYqKu3TyYaAgYSN#+!BPw$zcUg}D9_NsfdUG~4Puj!t^`mz`%x9kpTi zVI_$xy0FC~1OR{QKXV9IhQS{5eAe2$Ys4HeeMS7$8V};!LWkXm?VkCj<5R`&i?{4m zaU#xL)4bZWN~*V(d0Hp8g9x#gwAFNv!*cix_PFCQccYf@0<eWr%rp8Q=L*>mr5_Uy z%V~Y04V44J?Ap{Gwk0>&iA%;(f!j>f#7)N)v<9HV+21&dIjj$$NLoapecc~U;*&}z zuoPv<dnRiBhKR>PreQe6t+8URLeg9*S?A~ZS9f}TG3R`U2j)ubOMKNmZu`&q2IqoN z?w;)kp95N>H!Lr5bXB@})5pqQDf^)|u4X4fFM*xzpogH?FHyMnM<^f+m@v{kN)J}K zM?x-zC({^)J0FMKmEBturbhH(8qu@z^<jew$IPlQDT~x68y_nNo2Jtk*gtvdQ&8(P zDK<*%-F5bKb6DhbBjlBQx6GtPRkAmp0rRddt#%01FG$u35Hb;gK>|Kk!XD%{-pZZ= zE5CpvBov3dIDLiU0Rmi<7QTxmdJ`+bUgCt=mOOwvzGRC_YnC6Aw@a0mrH`iawyL(q z!^lKd@zf6ICy@m{&#G^)h2w#9*yhI)O5)ptMDITldUu`_0ag0^w()YNKKzaTjb0^T zFwfAzwLQ4|HRR{nH535$MgXKeq>a&IuPAEV<6e-K<&Uiyt72f(ZpNZbr-m|bMYIzy z88I2tXFeILY!h`;7Q+vB%*ukX@CdE1fsg%W2BQcnBVHRBCT~YNhzGZC>!v!VYAuJn zgj?(VW@#;7O$JU5fQ~gl0JsOPfPMg}EI+g!dK`b|YfU;GM_bgRv)(v}Wf-AIj)o_Z zS6esA(@K*Pd7kx_!ENT0%z<(xiyg0CSvM>F<wyJPEzGPeR6N~jbWeo-1Qu&qQI4GR zE}ew!T=neB1G~Xt>td(7ceD>3zc@jk@RH6Du0NwcLfZNmts@DxglL3VzUQEop)q3U zSN46fcQX<<^ioU;kaX&jGPn5E#5+{V5@IWg)rF4D*AURRfQT{5cD&df3xNiS2;5zZ zL2LSv9RRQm3+V^>$x8u<vZ1v&^<Hw_P{VjMHpdb5Pw{9DDd9$+TkWL9lwW;~tW%d8 zC+(?g&iLNsqT+2=sY9zPU&d|L^iuZiWc1cCANEl?vg1_h1~u`>4<&_SL>?iIfRhFM zA1L+Si>Fh@ilaXk4GTUAou;`EzM~s@8Yoy3Nt{*pMDtBZJo1ml7S*Z&ZYAWYW_F9L z$dClN)CeB29<MBeQ5GpIKzjXWKuDNnRNs?Jk+Y}ukd-^BE5I7K0HhjI+!69fXio?g zS7w9>)`6045pB2m9X=Z0%j0lTuEDqjE*_IF<qb{qY?Qsl@v^Qq6hF<}T+NBG$QaKZ zd@BPo1e><rFj}`OeIVRAVOY6qft@~hc^$31l8=Rl5x%;=-Qd}MJ*gJ>2x-)Bz>{Ke z;HdUTHrn90j*Eqx?pwQfhngJIgw%7-Y_K!i;%5(|NdtUMZaKVc0{-k^o)3MACDZ7I zej(~&4x5`PuO4my6iQ1zRetpT#EV|i$HV9owygSb{&r%+bG^cs++~WO0&e!fq{`w$ z_irBLZZ0tk`}O#$lcxhe>n#(C$z)X%BJq{`r<<Fw4jiUrJv5YQjwb3!=-VD9Zh(hp zyEmI`)OQnz6Vdge0_whClU{;`Tf>WVvnPs4(uuV$-iFmV-%+1--eXV&uqO32;TYMO zTfJ&7U?Z(NkEX(ne6nlyqnZnrM4dBbluSuw!Xg$bw|=&}cm88V3K}$72P91_u-Na% z^Ck*?ZvSJ^QX41LNy>|+A=CWf!`^rLDi3mJ6J)FKZ?yC(-_}-!nJlfYJR^E^C$L!~ z&w-@HNQwqcNg7=uH=<$SVY`x10Bh{N<ErDEDS!k*WTm|y=;?j4zL}}Z5G<F9e+&EN z-rLvJnVGp-QB?i($LsIzY8j0M#m@axe`slj?*uKfKfB~+f26@}Sz9gD(9e*~{p7b< z#jVlJ(wA-S{E=um^@`}lENutQGVa|*$I5$`dnA++`j>0$#y)9(bhxx!Txv~Gu`g3@ zIgaiu%WG=;dij}`kNibVsnIblE<5)(?8_H{zeW62%JK-xaJ2KVid9T+t_8|3ENZ29 zb;bXrtB|{h<i<aK;s5Y(Gj4gfkwDu~4he-ZzCPQygS~3bbU5XFK*kTo2BUXzbPYdo z{E+gNy*HCcC{!gPN;$=N@tcHpEO)bO0lAWaAe({LV%8{+i7~#kr*r?wWI&84G{wfl z(PQ^F+iePxnm%0tK3m*uW}cl$6?Pan%<e6>tn8TJXv_w`lL`%uHbt)7@1meLz&!Bn z5Ot53nYgZ=I72icsWnAsAGoB8@P#!;mR)_MmBS}ou)zhw2{h6?)uO6y^I|5IPk8@u zsh|<pf<JxKF?4gwXQ9CkS<fP+@%GO7Ua#ht_(k@8HJ3hCZRX)bRD;N2%;XjCH+4Vb z0=f=_^xLA%N%Z>Grb_(wNfTY_d;h<rD*6`~|3Rw%JpO}J(Zktb!gB8DAdJ5Z`Y)#Z z$DD+}GzmaGul|3DCI9!{>-3*={g3thW4-^R>s5%?)&Ii9){jo8|8nus8R#4fgNJ~K zAPOHFeSqSBa&ZC~4(OQ3`Cern<8DId6gb)fIvO4T)ZhQoTx<H-%_eYl7fAU@vhLYS zf`CG-cWA;y7a{mHnx-@yWXB-gittq~@H_9_7Y!*|dKBp_=mj9jn5s|qh9Bq3!Ck3n z5PV!7=7cTdpLXy3pz4=Fvl!HuJ$F6RB;6AqUh8}a>GHKRj8!1rtgNK5C<>k(*yy{g zED`B5PsLZ9&;N$lKmfoc(6I-g&H$J98)s8MTPn2kVrN@opY!m5)<?}C(foaem>e3p zu0vO+1TiT($hCJMSgyg5d?d3HGt0+@n9SnSTt=N%tR|O2_W&BY{&1pdik`3i<E1}Q zKcb}KB;n9XBz4PzV8>=jf2@N_#_MR8b;CFV3%Nquq(q6v`I8E2^NClJ*aRYfv+UpB z=gP<t!g|^5VqoO&TC4{yT%H7ncv$)!r(y85U>#KdfHKsYr5nv?#HSM4p^rc;>PmhM z+i6UZ+G??ZkINJ^<(xg9jZDks&L$6g`!dn5GSMHH>RU}_hIqWRZ^{d=9vq|~bm-px zVU>**KaAzLzV5w!&iJCtV#42G?DQCAbar8R2pg9|0sa8se*oOH0U?$H>9FmA)fUE& zv{PeD-&JiI>RsOZ^~_&lx$LjO(K75g3+Pk0_~s|(KRd`rM%QrJq37i}v`N1^U@&qp zzAo1yPb!PR<M;U5WM?6^g^SLSg07g{#D(|BO32=$`d+fkaGiYnAP<*8Mb5B5kF(_A zIIv@{LoNlHN*yG>OZ#>s+2ftY&er0Zll5Z#8&efh(n?lRURB1x;3H}AcyM&HnvAA~ z5i`H(%&*agK5GuXHHLC&cPYBphHT~qhEzHeecF~mW+Ik8T1Dr|kIrap*+2ES9ZIfT zlrlrZ*6IcrXdirjt~|{AO8TV&y<HtR$G?_#?@rq8u6F!7RpmLAWoE2xNYyvE{q9YI zk{Bz!U-F?|k|jCcZv0zs&GXqav1mD|r*?R;)dOnm1?tp?mPYAKT;!OPSx?K^O@iJJ zu=LDtY^eudYfkhGA>L>L|B!+m07(aI*|l&3I&c;r@&^5O-Zj}WFpO-jQmaKo1J72U z<w+C!4;JiitaGOAk6y~i$t8(Vp=>lG)q-a=&h^0wf;tH-obk0}JZAJtLi5qMd$F%M zDc|eY&}ew+M}FNHSRSfn+{(QvYQIBWAeORMLpui;m5+KuI1hdL-l|ts@Ak0Qv%h1> zF?NNstwd2R*Xr?|?Y(g0`;N@eZow>*S~Rbvz^+pQb5jva2|pjy$19K7R~BcBU$AFb zzR(!BjPi$vB$W?wCXQj8ZC>8rJUsXuKT!|{iS_3kl;-vmOTay^6nP~JgYf2OPnDf^ zGWwe&K1;k~=<xsb^=|LGhtW^_bkYhux+>|9v0N9Oc2XNyf4A#QAn7E_t6Q#Q+46!U z*PaoCPS?l{Aah&x7)^KpfYUMZ2_4kHNpjw>=DwJ1i_1nW=yg$KpC=02Iu`FwY4f*w zwhZWgb!nfsLP-O2uR46Qb1&`KUJL>516#MqyC$1bX^-mkOP^&3H8?k+i2M=?TtV5# zmCPpO2)PJcL|%2XhX#;@H2GZC-CklCm@=A(@@D_K-*``;!3|0g=`<8+^K!-FyBY4S zdDyC9$QCK0<y%k-JQ@tEJJb64G$iZxtl#=)%n!@+2_7Ds4=N?&^(WZxxE?XELmyWb z4H)CuSx!Rd7ig><q(rF*4YVr?KU&f(_$OTG`^cEIaJkvZi7!ikkGEZ;;vf|0iE{p8 zf0n;dg?w<Xd){)qmLdBKZsp05Y2A_rUnZf7nOU2LAI>^U&5HKFq7-qwIETQq2!7HY z9YXFt8UySjU;iYIAW<o)CYFwY-_A<fIZeX!_=hhxGje1vwm;@s*Az<^`nDirROwaE zH+zLAz=Q0?8<f0rw<qlLwL8w)OJUdD$dwK1_WLc{A7%lX=^y8^2A?aL`*hJgB7UDz zo%A8eS51C)FDiF6{@Qj_1FC~_$kjWfq<5pR2YXl{z2)`}ND))Hf$Z@v*{bwJZ)^2> zqX<8nH+FGcxc00eQ0{Fw^ujVEPkc{$d1De8T%gMUTL#MKNxVcfGn`1aN8o{xc<L6J zi%$DKGv-s6m2v;r2kJ&&_-n2|k%nuU<1Xmi6oiYe$BgewuX-V>Q2vX+WNK|1;|qgJ zpC_7^;9aBr#wqi9m!N`#Ga2V#Zj-`fZPq>V4z}#&WU+cjTdNyj`iA=GOQQJt=hyzM z7;SG(Zak`HD7G_;rp-9;!}^r_Cy~o*G{8evA$i1!B2(^A&CN;Rt;>B>UaIkgA3b4P zynKu_cxe)xs*x6r-&a{(w+b>+&BP~Fs8H;`QVd7OLKEC26_xYBbs;+T$$!Md23yD5 zvt=XHi<`$)Ny>=AU`-oPIHXt5WJKhnaZuz*k8&kaf~Hb+Egx1X{7K1i*V6_?cyYWD zGYnNg(yQyHZG9t5HqHrNj+g+yqMH~?UumTtZw;I|axr7_*#Yl|hN}?sqwn2^2Mr$Y zVBcpSUd`O>-8;R?CAFU*zIvq@g?tT5A`j7E@9&aCNO7VwA=xG;yyZ{#={Gh=w4{YN zv9W0hPsyT5o;(;xrB#qhGlIiawu~mb@NHZ?(eVw5_`HQ#{3b>oa7|D=3yiwzlg6+$ zM>FxKt*zU!6yLw>e*Gz-7W4hF@a=nE)YqfbmA5m?Udel3ibEHxVDCI$x(U2{B=YK+ z+YOP$)2|~gxs?;Rm^D2;01Ow4j<HKxF@cZ?_kIAzalKi&jj@ji+@-A_Ity&$Kv(p= z0pSRt&XL@;B<L+VGdQs+`hwT+fe1mIlhI8?SuEWl$aZxS4U85;g2wN(B)!UEXJIv1 z{o^h1JoS?F@3!_H`*+&}<x@-e6dB4D_Vpxkh*TRwtSF-6tZ~G_4U`FE4n7S10~Gb{ zM_|up5+-31qBEqVc9qI=lBJPo74`AT#|maQq3B(V?OtL!VOM+-Jo^RfZgIZMz;x}D zJdM=KDdJDh_r3>_t_p<@fP@iw_Jp8Lkfgq@X9fj1_s~?chv~0RZx1ehe)f3hXs*l3 zH&n@%S;>{brm2xTlUanx&xuBfPNNHQNvLMx>@vzJibOm3&A}u=qw4nEW>_#Vgjt;3 zx$M-nlC4R$J%atoJ0ImfjiETU%GMOk^eNG%(74wFs%5v5O^6>l%(es#YviVl2<;ic z`Lo+r8v`Smh2jP>vR<XR=%7D(4AOt00IW6_?~qdOyhf&6vA0X!Ftf2nu+bMcTh8Ja z3)aE7J0YSiQrct<CO%tD+`;Rm*L#8q$PpgJlSb^Yh1comjV5ia$!abeU<Fr#Mf;Qi zqaJw@SK^cN5WfY*wGSd?&1TAnaAL{XP7FJy377f}STDNB@<d`e8yZ)zUA=yv`4d9P z*#HV-shm5rfhWDKxMpXXy1Ywpr?Zs_<Skq|Fk<#B5jXMa`cz@u^hqWY^K}?k4zqdV zanEpDcs?|Y>ZL~MitK$E_RrM|UM%QSEwhsGq|_=TWzJ9TD34wloS0?e%UwpJ)cF~W zR`6$E+k`$oj=J7~s;{$mCzX9%2~Fvjk_l?m?SjR-GE{`gPan`P7F?PdK^qw`ii{#g z{)^2k?l_fmy>}A@(qUQ#GLqu6-E?-${!}uS6|Lu`T^kVTc2I3V7{A<1<Ygs^HIt-j z$eIo}Afc{4JFA{J3Lu{-VUJuv!&tZFmRj&n8tU{@;+_dt23>jz;2ywntck?@nrc$A z0kJ<2V(}`ifvnPY{!wOWxD`|i1;+e-80dwlXQg-tCwXSrC?}l%ytCpG2K?P;2|oVI z`ezrw(H;PxhydX3QwIQqcKsFC=fF|oJ#gm)09IGP5bPdkxCc&q3cn>nZz}8|VRsud zrczh&Sg?o7spH$dYkP_C3E@p4Y{`9rjhC2{j`-WCW0PQ0QdoWAvrc+RbSJsFGwrMq zEbtSV83<-Mo6y&UTHHV!%rU;L>`|zAk?WBHbQ^U3vzRfw_x1c%@9QLhq~`1}8V7IH zG?zp_U?JOD1uUMc-cPPk{;bnNAG4@U0;40jghcE8ca$kpE&v<~4dhBXHc--~zs)3E zzhy{O@=H1YTT!bFON$9Ti$pt@uns<5We@w9$u^E&NJ19zeG^tHpN@9<<WnD52tv-1 zFWjj{tX7jPuN3aQKkAtpK^K>J@|feBzh~?CB<i=o*XKdq)Y%Pg8}dg9n|z9YvLP!S zAF_^ZUhDD1XC<JZG~Smkx>oWHie|^^QcCISFJ1;YRghv?{w5K;3mBYKCVjaPu@#vd zp1woN!xzpI!F-48P2@5n44t47@*(S}mpdnLwe;6^ktz7Jsr5WvbKgEs!0a0Y;nN{M zbSfu(+^U*?v~P=HnR{j*`@+g79Kj=Ba&~i`S1}Ar!*!@vF%<fWHoZYZUtC})pR9kZ zo>yiNG7J-3RURy!EYbVGX1$+#a5irxHs8Q-WcAaD`qN4QM=j;3L6W+9+9@}fyG$+D zfmh@Q_P5;6SLQ#h7MryT<r8EEPvfHUj3Ymt@}_E+^=#l4gGeLy16^43!}UNZ#{;gD z(d$D+T-zeWQ9j<sxZ$%WSy?s44@GJ=IMG_8E_qz{g87MY7J(K8kLFaZP9p3n2<8wb ztAmmJn~NCKMVFv2N}n=Qb0y>xJ4abKDl)#>=X`$p1L3jxHsE_8b?&m+%f9bSg_b=g zG!3M&w}w)ZV>RIRJnjbVta-0LaxErA-~EgH3FlH;&Pv{({yXLV7j6Du5c2<%*ng9d zd5Hc+^_|cEMaTs<rKA5MWFN{wwClfw+;=r`g@r+k{|fI04;wxIpM>m#4eDH;-45$$ zTnX4~pq&>h_}S3vcDi3a>em_kD2qiO9Rr0<DnzsE@X{#`G$2UQw~8~;Q>H``o_d2h ztjj7H;v6)QVs!XoN7I9hhhyq}A8nYspvt1h8?+Q|D{W9r@_4=##hK=45KPgcTO8Kr z!M}ldoGrtH>l0?<Vxx%|FmwDU!Eu}HUgv%l6z{~^esOv%OM+`G$>^GfL5Wrto)=Ne z7(I$(5bny>iz%xsB}JVi`{DBzlggFL0Gs#FQ65f`CIZxfwQFigTjY;YRj$w`w~_gI z2Vei{KscLDB1JTG1g6M0YIt4B=*XCfA;R)?cFooGfEi{QVUievT4%HWj2=OJ60|x~ zuOPkESqeHmlYI+<OG0|XMk8Y1>W~`{bcprt5uK_kimx6EGo&@C{`qZl-v3L-$41{~ zsS~U;Vsvibk33>4$ZZvu`lWWi`dTn-`q&Vpu<~u*<UpVw=f%>uvd1>M&&QF~yGb_~ zeW=+Z7u`)f!l9+SJA<S$)kTt!OZrUk6YZU~<DUHpgd#>hoMH#tR@DrAvzm=^tpz?n z+w383mlLocAmq)IT&Y*#$-He}Co>5=N0g(fjLNQi$WTx_f6_1>iw&PdKc(A9SSCuB zc@n-RrBPxFE74Gig%*v_O=Awr<}gU`OO`2Gl^4%Wu407nVoY)p4oC4ehi}vJl(EBn zy8wtJrn~?xde5nQ8#i%x?;3HKs_QuW!lGB7<vmfKd7Sb>rV>O3YRcJ^lcl_l=j~ch zhH>tYTF+`|rYk$TEAqNVY2s6g(5O7Y+8<jm191nNj44fqZN4$oxHMnbDz2$TXc;(r z%9LAL`+C`UgQB&<w+REzK$z2FKqW2VnRWsFronibNUbk4cdk5x=BcP@_0!p4VC1^t z&-;8Lfu^M{N~<y+#r~g`9pJSXKX+ZTW6ea474ETX)<0quoZYgxoR*U@S@|fR%wjRW zjWyq?>9jnsj8G-f-R84Ax8a$f%wEINnw0xT4HF5qHBHG9t=D<T9f~z-{*Sfc_6E;> zuB|W;p6Ge{<UqpH+M~BP7$r!pdb-{6H0MI3OVoXVvZ2V^fg_kn3EJ>(8N1SkXsT&T zKmSM9rl^&$YRL-&1U`}d>n3m$dvUJ!3Bh-G8wmw*VI<|klAv)4(v0H6q{GDeIW)g6 z9$ZjNc&Yp-=iJ-Qk>brL7h{T{`>KDd`g60X*zO_g%A?i8p_0sgcH2%P61d(+sUZ1m zsH$ibxkF2-vIC<NcpW;mLEi(gX<L?51dyr(D0-=2pBAhWeUAGx)RbOxPfyv-=7aBl zI;8(F5T?%^L>F|;ENX-%Na4gb1vRq~q)}MJ418guWUCoIY_xWq)-{8c-hz?b!nh~1 zM4~Q|AbJeRLt{mkH|HsfWT2+2x$2|{*aILcgOcn)A`FC4Y<FZ5^#f`M8NvPn`457g z2JV}`kgX?>x9qeWBf2>guiKf#W;wRf<6M&{HO(PC`lal;QgQ=^L%ZQq`Y~-H)G8D2 zX_s-IQ>e73viqvx7&_-SZKGSU(bYk;FtCRf<F0pnx~w6*p_qm^ly_!~0THLE^Id!t z&XVWhUAp2vfjhn2E_w8i9H6GXebx#Ibmb~s_d&+YWgOUtys<(Q%MeOgm$M<=uPxNa zb}4a9<HL6Osboh@tXlD$uTM&}pA++qiK!M_(+A5XUwDnyY&h%4`MWxLJ%JofYJ?t> z=dYEcBiv~25af-d8_p(+xSuo;xz%B|yuQbb_zA*%)nq34SIE7a6Yi0_=)q{mB3lfD z%JKE8jhv|o>NP3r2>p1hM{;mx8k@oU8u{Rdx)!N>2R2=Oy1U+t0ksa_Ay^-DW#ciJ z5)q^7nPBu{8Z1C<6phd$aV%dE!n&!~$A$V!$9a?SkL`Cc#K62#6FfdAYzx1{e`T!d zhZ~i+WmG_>%!7RXlbyEJz&d1j{l+XTgY-dS!0;V#BL5pe8Y3=&&Gv}$!xc<F{iV)) z%E67H?ySJlH%us2Mx{z{B6G};oMnB5cY&UpcNYctwE<;YMLOvdVZ8%{V`k{?MVUm8 zX=8oOs$X6)jOt`oh~@RJ`;W1fCR}t^DeBlEflOY1ty)gj9K-Z+nuAFO@}tJs)6gBs z(!3gEGD)u0LL(*g++VHalAcde)qzM}wx+t0^UihPXY{tPQw}o0a;v+vGg)RMbF92z zyvD-VMe=&TMx&U3RaDtl;6;5f>_zq0TJE%+Ai8UbToZmOlwNa<&&1G!dR4E?0RE<d z_ZvYDF=g!JPd*mH9aD}O!uMA=M9?KYPrYHC(#SVR-;+fJD-mv!I8`jhWOdmE?uf4& zESp!5+U{N^*<oCYX+c{Z>4A#ZsRM~|CF&2J1h3BStHgKAl@5f;Yt(y((&VRnkj^$S z^rF4T&^;ydz^IYb!sGzDQ-Jf8oHb@BqSZ`<=jnrzDsg%~b+6A_z0t&UQgS_Sf|*Mf zvn?W3)2d%n06*8b)-m^S|L_Q~<g@x5Y`vuX;`k^n#fEx@NK;O;Yht@Mgg>@mBy~&b z_)j=3eVvWs4bCh1^2V-+{f%y4sOOHkw;0(zg4x-(8dW;3HF5hv=dyKJzK?o8f7i3d ztogd<id0Jm(_YeLsJwv2irxZCK!vppawSL&UUMQX*7gCblR=bauW<>lCkzP1NW5e* zNOgS9%evNOqP06Sv3m4I^nI}}^w9XwCG?ce8hZ*hWZ=q#!7WVSkTpnvgotnaD@k)p z7e_;0JJKR*6{x^9@<ztoWs@Ml%vm=_Ils9?{|K{IhCO@CyGc;b+{6d(i?XLaWv$XO zNhqRd^0?2kC*Q8d#_}{w7V=C<>?bqXE?B1BB~Fb^xNlMUxu_PeH<!wgqW5`6#lu>6 zc+%ZpF9E06*^(kOB(QdknlWofL_1;xuOQ|F9<NK*rVZy#Y5MdXr=6M%8wYhui`f9w zFeFlj?Q5D-yrYn+%QTEjj>S@Z0F50bRDD>UNa?R&C>m$zBi0yx<TEC4gK8CdkKcPH zzoPcEh9V+qJ8Lc~4Kh^}v@RxRz)@M;YD1z&V|?R2{rc9a2*1J~jTiHZ88%pVS@z)? z`HIVZRO~5kM|YrNIQ?WLzvySsSA0i~90o7K4yBvCuI!e{PS&shaK=moT1iCwYPt*u zyr0dAhMrD)jq77Tw>Q}s6I&9=&{mz_3rP#ds%6%j!5Hx`3tN>uKe{F`1sLi2*QIu2 z+4^XCH+CH`9;Y#Wn=EVleag&~znD4O!2PGvG*QnWs$W_}-f&hY5>;j}q(#zm96e2X zzL`27Efa-HM=MgGfDlo+;R1UG?#o73pepE@`{MF<<Yc2ccNkr;c}{tDqs^l3d}#0s z=;K82;&hex+c%iSeTX9<w`e<^ZXYuPx*$8m=<@Z^y|x76jPg57wdzaim#RV^;ffk0 z?3vWF!sQf!q&SB-(-obJT=)o#^d1t-+wg+WESQ4)aJSx)N;o_VMWJ!siKWb>)*PqD z<~^!gA$<%Tu8Yk75XVFx2wSjaW)@d7fy+8M_a5WCAxS14lfo>$_N>z@VOO(H)eR7& z55by-Ob|Q|bFrjU60(doE^bm5PE=0d7-5Xd-}fr*NuxJ<BLxpL&SY2&`LrNdqU_jQ z+GQ7kBfl9S<}O)Ai@sjgd7~VGok-@~X={{P<MUA?E|oc%pHN3>jN&K?CX{}9N|f)A zs33+Xxi4ipK!ss@pA=qUb*yjGTV}8$ML^{kOD!mHeY|=Gmu?)si5RF>*iYh-%=nPx zc_XN=@9QLJ@9g@5_vFTA+qcQAttp^OkjKh#n{>8oF{+ul(z8s`LLic7#N6;(!?nRX zO<vqFjJG)Zk`K&jB=wz85F<cAEej48l6jtNN_Ap}MQ~OX=$AyK$2#<kUUE;4z8bR$ z#>FFEa&F)Y5#%f$iJwnWHlqIqc@b+{iK}>@*}@!c$4H7LLss-icq*|^zcxO9h`4-J zp;4dkManovME{fPvXU^|1W3h$fkqrehb}Jex=RT|aWrrAc$GPJAS%?!PMnu64!@{r zQQJMF+PJx>>t(fpmPmQnev%y6Q{Dzb!*KQ~%i0lcVdxfi%@il(b&^KWMuSf9AXFjy zQF2lTx=I$^MHw4NolRCvCt!d>yr4NTFuk20%kP@~$E`?8tXaF$+2mm1L#^i1MoyHO zJZJSM5iu*>M#3!eV%k4(H2yrOAx6u!uJLsueEa@XRUK8Cfyad|7|c8}i<&M@tYj7S z=~%nnXp%l3ncF1g&u60#WgF)t=hEapeis$_u;4>Ty=bPvy;1Sn>TxXb6UhJ{i3O5~ zrhv*1QG<FXvXz0pOn%3}f2X|vBBuWf%KLv3`)^WS-ru_IKWn$8N&lj}tv6O^|DrtD z+{yvuFXjEfp1{V!sKT1S0AXOGbCdmN9h3(h#PSj(5f0}F=|meN8S25a>qHFnG7*o4 zIE6P&3Ar;#os<)6x38D@vP5g~-)*YO((lASN{4E3fz;)H3i&UU9jApOLFe(ogPw%; z_to<}n2aHcQKa;1iMyBi@$)6VCCl)i{i)Bh*1{!47ihzxhs{*g=DlT2(2Ch9F_k%; zRl|t+9`w^wMa9I2lUeNoZtG~*1N%*Sux$*ePT-<aN=gd)Yl0b(GEN-oih|*gHEN1J z(vqO>t-Q9|dpC<+@=PO(nmKiccGZK#mS}||Z@V$0Hx(a4FbmHFw;Pn?qKqX`wLPH6 z4UCN<v%0+d<yqQ0N36Bi@YZA7aI92SgTknZZuv|BSs?)v&Yko=#n_E945||nY%h@w zXO+_MUZ?k7+6g8D=85?<Zp!OscWY1$Zg27i%x7FVjH3g}!~=v8R~T3rVJTd^@_zBD z<s!Xk`~i*#=6ZNzr!om|5>^V?nt>;&)G>1`$&b@(W{pW4y5S)#890;J>T2IMQ`RUa zWmQ0bAW&Hrf2lx-bLV1ktY1nJs3u?|$c&Rx*yJCJ<v$uH#{{<bzDD}EI9zxV$J1kp zI7uRy;N<3CDT>Tecb!+%<0ew^F=I=N+$4;6^&M@P%nS7_ai?8cnuMJt;RQpT$~~OX zP%zg{jY<WH<d4bmANpvn_oMy4<y5%iaNL-ao93`rnslJ&Y1hmqDI4Shw)HxzjXgfA zY8Hk$*k`<(QY5Q5B2-?h2%*H4wxZs39G1=#o11tmb~ai4C1pu^{gdvJ=e@^fsY>=F zL{C_bk-Z;BIc?#2)J9WrD(v)xIkEGb7y%9$6LKFjX!R_^Pm9c+mKhw2T)*Hbdn9?B zpC*s4ykis5SMVfG5vzYpl>Rtzk!(vxRe4jHo|hw`D@Hw<b5<opu*li_nh-)9PnZ<d zUS?<O#OQk+&Vm?>>-J1b1@mDK4aDI^Y>;Z8m82K<>@)8=%NqMJZKbK@43mC?>I2MP z5k#rtGek9nS0pUHT&41pj99^fk6fCgNFu%9v4LHF*QAUE4E)t8+7-U60tXejhv+hO zISaIg`1QeMijow;<QPMh!J|#+Bv>>=fu$Gx_-Trl3hE@RUgb66C;G>;v$gBY?=i}C zG#u21<@E*@W)gY2aV$WRVO+9;f_+8HmrT_^(pWK-eH%MX`jwO!CE;tBgO2MrG8@@3 zLV?Vv7O;^;;z9VIhhyf2eC+++s)<T{PkdV2*4X2s7gS0Rz2>*PEwD|eacbMfyxcYl zOk{-Ksw`&n2f?2a&X3OOV+Qs7>{6AuWEG2sDtq!Ddb8f7<^9-S)lb8gi&jbtEFpLy zI18s-mkQvD(&Kkb4j80pfSV8{lgQRxsQsYLT64iQNbR;i4W}ljp)b)Ni8ABu{H^TQ zOfM&Rp2)CP^d^|eDei1YCK`L?0k}(i0KNf0@CD$^!Gwv<i2l%#`nN>?-@GONm7o62 zoN9vq|B>6!+t}U|_*4BexBYde{Ilx;tqKc-jg5}N`Rh<2#sUE(=K%09h63)WuYe=# zC7=W5@%Mts-(m5eBl54=1MBa8^t%Tj6am0i4uBQ^Ti~i4fC=0H{v$xgCjd4DKv1w# z*kQ*d<n}Ra^I7{Uh5Sy6%uD}y5C7kOdvl-MO!<qYyeEIm{rEBUV{Xp%HQhUl3X{i_ zs{BJN=h%8A^hW^`fy-xBIP}QC37O@yl5e)LLdgE&LffT$5#)HWjQ`6evAi*Hhe6W9 zQDK+3KD#C}_hxi+9c7MZ3RIee#vn>hCNR03B(5Yqag2fj6BDTds1`kD-BU05!{E6? z9Mk${^d$I-D^V>@qXk~n?2-c#C6C{3W1|KC*icz#jD<Q`<cK)>lCY}u7|oHFkDL}W zFR|*oUHh0L?iQJ%EoSVsXk~wh_U|Kwnb`WAWHDF+`NesDh^kHW$#3H_lTJkUvM&b4 z3)rXcA|H@{U3@Evz!56sYkp=hK(-!;I=}RuiwBk?W1k=FBy9EcY+sl87;R>A5c}3& z05w0b$%TQcAhL;HZ(MevhY-|0j#4UX<A;tRBm?1*b~qltrO$$M+1P1k5MO1zMYBQ! z6xD}v1*X>5B^>9_*1d|UuCIA$qFpzTa-EjtyltIN`NF#^28oJR1A-jC!tWpbq@1Xb zqdgWt?86V-DYHM5JX9?+%1z^?>yh@{<jHoO>`lc^iB)g3pfplU<jdc9^-RyU5q9)? z64*+POP4O~b&1d6@vB92ZoX=qQd8%$Wu=#p^lE%?iGK8;3cid*G$QRu$oDD|!ubD) z{ehx#nWqCd)wIx98#FCGr?B!7acn3tJzdN+dc=n*zB9w~(o+Ige|D!1d<r`|mWT?Z zrJ?*Daa6W^R@Nk*>Dfd-?kOBSiBn?rv5K1WT}wIiFz%%Fs8(`L{=W67oBp$F-~Oy< z!%Ro~i_Mzn<fZYMkq<4ivkWhX;Sa<II4yl$C0La@fMm_R`WgqzH_~<DQLg>rR<V<T zT#SY#x!yXPASP6udn3Z|C9wGszqZ^aBiWOj9OsF#LL|zWfV3aMP#rG=lY>!=r7JIe z3|GTW#_dS`Wo9!TJQl$$q=c(brnq&*Oj+fx;xgmXRF(lNe##wy)0}^IQXKGI+29Gc zy+sQO@)Z=K7lAV3WGT+iJU!F4--^hW$K1X^<SBF_9$1M2XneFBaRvwoyEf;6Btj0^ z=$NoZ+FqS#3Sz^#_&*xFSI9;75|Jx|d~|l)-DrOfg<!`1Kk5mRq@>P#U=4!>u$sz! z0H?0792S$yEkzVP^d>JVC|a-_kHMX}1)k~7$m|WNy1_KBR`y@@9UIM0!J)TJQcN?S z=>D)eg$1)Db*?D#A)n=D4D;NyPvFkVfQo$*sY{u9`1N<Iw6__&D{Z^nc(G-TF|028 z%-^;~|BgCl5%*~z2IV*A1j>806tse3GMT$X;j_e(+ds;4kL&M{K~@ZWlX5)Q8aF;z z(3|uGMSYSdN%wgQKuG`WDoKinPYD)G7QzZCYjvS>nNI49+AGHSE3adb1Hur-$P^{U z8^v~XAMLLxOD2Xe+pUL$o;;WwVo_I!i1w9^$O@rbQ8cEoF<SrwHvO7GETS7a3pQE| z9DLC|cpXi_g()_?O4B_>ajz4J>{28ox~DzIV-z8V5JEBqc%|)K#k$BdOqe!YJPyNu z2H}od!h_IFuDPTth0ou?l(kk!+$0nUl*a3x*lNxSaUQS7>Fn13DvAJFMbJ$E3aA*? zv9ns@dC&G#ToLOUNa$1#v5OOn;YsXw3_*nvr~P0HpBSGQ@JYcA)L#chsADsty%5zw z(~zBzN`&j^@6n@HQ_&o<ZqvEAcs#`;QqYxa<*;kZiasgzbFq$-yJI?DMXB-Za9ydM ztEzK2dTx7{zK$BGR;TgRMU<sU!6($u;SmKlZup89vpDau`r`W=utAHoU+wuhKo17O zn&AP2Hap=VI4r_Au`-voKX4e;<nK6t1d>tm*oxZ>_X)(9v|(7-ukJ+~m^5~~*OvN9 zVV$mR|5n^im05(167uzu+}1TsGBFH;@!8H2@Fa0$A^;UQNF;JKOJ<M=<Q;dtl5apR zn~mcTt7NTw*TNh?KRZDv*aM|}NQ2nCCKW(kixzj1X%UYPc|}M>5k?Hi6`ED4OAQ1K zlPf=f-<%}E=F^wa9Fr3)7a(bRs8t#&u-t%Gn!2YNWSYO`#LifQNZUCeeA0f}Rn-?l zI*=w-<6n!H#P|JpTOe9OW37Wz|NR>E%WzXpuZN)e31y~8?^Qxu!N*bbH_;TdiZTIA z$t;PiYy+eSSa|%&9!M{i0&{%HezZv`V09poRWYW%jF`FvYOWULsTfYt8)7j|l00uV zN{5wWG{Zis>rFw;7<PWpgnT8?%~&rmWW8z*y*f^{_lPlA(;s~Z*$St7s7S1LFnSx} z?^`kT&CR6jgt#{lIZHQMpGxrB?gFz>xJjdlrbwEK1sh1fxhMDYiF=<iNW!pL_FBjS ztQ^X8Nz0}-8IOmR80z#QWPy;Wowau}-un+`H{({?^+bp}Gmunv=1$CyW`=bAN`gQ` z^fi`BlUvfGR46fNF(!+cpb4N#7Q&2a<vOK5k?m#pK*H_GC&gmZ+9A_7IzgHXwOve+ zRM_XzXc-sI=o_6VCNqXSgmQ_9<OywBT2~H+&#n+l4DMIezj)nOD4zQQqg70N!urGZ zQPZWs(*}P&kAObM4$r>y_N+4N;1c%c-kJ5H>1Ky<R~&e^Ai9?t&*lKlH?96WDrsz| zDJfw_g%bND5^`dj)ipF(EO|{RjwL-GdWN{WNypouw?E&0z8_EVaC0-YtQ-=frT5aK z$&y}U3t&+kxE6|}5beak)t3k9lt&0E$98AI39{HZG33#3HF`mRRtJ;H1r@Y`EF@S? zj-^Gk#nM>(vbveCuY1o>Sf__-dR>d>*}^vA3S^{<WzIzwu_QI%CB((W;`<fbs#l09 zQBP)Ossf!|<3cj=i^!?{yL~S)e>~HSE<dR$5fe>(ew<LxV>B{Mrs+7#XQ%uPb%(`Y ztb(m!2Mxbp29C_=v0^WJHQmy`I?oHO-iPxb_C7pJd{6CIy+LV~k+nIItSFxVN2fO( z019}}Mupy0JY1*ZFzx9N)Mh;zU|w_5$4T?|hmAkOmBPbEtInFaEDmXZfoC+ItsL|= zIufnIfM%S#vtm}I$AXUl^NFU$W^}2P#xxU+Su*YKpY4`8yB}Nh`wNN54=1;68cyVX zqdGcH5GT+FEyl@VuT`4`bW1KS;ZVfY_YRo^k4Bwzul47z63+~}t8`4b)Cb>v|DHQ{ z*}OwTk5!r7y)zZl87bH*YwPZD`}0=@fBN)nj8jlZ#x3$oh5u@d9k2@nI)J;5SW8%l z-1^GcfLFxo|52I#o7AoG*LD0)>JI<*PwM`k&gFk9Q)P^hpR0fV_paQ3=+l36<^G>s z)c^Ioe^Vtp{4Y-Ef5o=3y~7_A|JPc=6sR#7|7v9hVh}MF2AmLsaE%Zfed%wVH-Q`n zguw`jkn>4OS@vl!T%BZ4Qs5Hm-Jesc3)fUCTK+-Pnc3j=_D5^36?*`KS4y{F`z9M+ z8L2^F`s8%E<$&~C^_W@JCWluo9EJ(!h_kDbv%lZX7vSy&x(?iGP0v8zXP;&$#_Gf9 zG2~yJal{Pm(t>H+oPKa-`+yyXdlMep3_jo4`o*1UpEL7bL7&Cqy`kA`G?)2X@T2VY zX7he2M*cuCkIPKdHSyVK2do43iV-P!(|dKjsi3<?5BkZYzW!xAzBkNPVZkZO4MR>< zh<WG_F@N=({EPRzFP+L37Nhgj=q<>p#AP+`wK=fV;|L3TTBClo1i<XZLjDSV;9?O0 zMVug)zPvxvXzazwX~;MU{UYA@Np#oc*4;WqiJpnuA`DgQ^yJ<5sqs%-wJ93pTOWBP z$%QD%z6o(De8~^Sv;H%<LIG_(g8^5t!?5-gp$6dG(@NWx6~v0Qb+rBD&D$Mi;oSw| zVJRFq8(*SMvx`sgK-)2Q67{QI?OHa+9}5(M<%4|}gkv?$5Scl9*dc%_x%&d#^qln} zQegCgKE<L+cp^-Qho#cXE#_y<reZkZ-rY$o*{cQ5-o2YMqi9TZ274*lxd!va^CGJ> zds1&nPtJv)0AtNnQ5bN6yocQZ*Xw(=?X1^NagBbH>6CIQ1qScYw^Wix55%Ak`$>u9 zfE4q|aJ91EBJIwQB*JY0u`SL~KQvhU+0$d1*0u2`ILp(h_R~97uFqs3BV!}#@mn+s zPH6bFx<Nsv11|msFEr;qDC@+**~c_672OW_Jf4<qua{U%6LK3^iLa8xZ&~p`FKUAs zjf+!5!m442dF7zCkh4D<xc0SZnvm=DFNe5EUqFR%ew-v+=o~evn`#j>GwR#T4x-Jh z%3$q;WZJH%0=LuntDXEMpW=%%R4afg?0ApbSS4zI4gp1sDX}oqji0^V!;gQiv-To_ z{*{;R2PQTiZ-a7nft-@f!RUGhP3_1vP2mzAOJQ|YBbQX)JDҴ<D3uTmTuYsz) z_d&;1&48nB?P{wro(Tq96m6~-C1|`v$9%)hKk-Ca?bBF63HQ^4Bv}<#@k4{E$vWXM z*vdRvD50u+VWMr3qC;dR^Py<0J<S68)|Kj{v7&+K6=NQ|x!sg^70cZ6Gphs^rQ>8( zn}|J)l0lWtvqN%Nl7u>vBr9(NcTb4iEJ#6{exOCa^^3v?T?V^{nu4q5$2NxF3r&u* zrDRVMPPoZ5O9Zz(vyBIM<}k+f^)8sg31jEaZfNOoq#}q3EcSif(I|;%OUyp5xwa;l zqMq!9?xB7Lf2-ac5@t6~QT5I-;0|dm!y$gW&q<oXn7(l$B{hi42w~!?;m6ZX)N{>` z<7ahPq&BbpWWDRxU=b|v%mhsLl}%P=-ZE3(D<vK^Qwh|$Dly!x;plS&goKk}QtU4z zc^N;j7_?V&Qr1;4TBouHwNJgfN?F;xOnKkJDfW!!anK>)eBqNWSW_uHtQ37cdIflr zggON^VxL6-B=aP%jUAepp}EcK_nY0s^8wlE;-lOHZ+;X91tTp&>(or-)rxivY~y+B z1M|K2A?*wK1YTkH<It-pwBTcDGuj5l?l3do^&vau5?*fU5GXI*(`&|OHNinuZ(uck zV_({6#_Ipk$7Cx<<U>RDO6#%-W;|1Yk45EP+JzRL$E8X>@<L_E%z-u?EHK2XXU|IT z`A=);_oJ{cKnd)s_0bB}%IXT#uR3alGnOkA>!5Bbr44=qej2CDCEXp-sP`movVh8@ zBCl$gMn1u%=;?Ik!XQgd6V;bigYVDgA6YjwK!1xwUSFep;5;T~FQC-iDK4Lj=$;Pj z86>}l2iHPY!fDV|y%Un@o}|tTu?8l9cWZFw2OMNsqp*K3W(;#Qyng)t?DKNyJ>{}} z{1G&9tqR-+Z@aO?&{nyqm3G9uleHdzcGx;atm*7=H-eF2Gz70CC<E$uOA{Gm&z1T< zsMzE#hAw5yQim!AyDv-1FV2!LChVJCE3v$%zK<Bb+Q1J%G-53Wz8TlM5Lt1nm&$)l zgR=Jt08_;@*){e&K5=QfpG?5x@;B)Hi8bqBy?s%T!bbXt-~EkRhXRd+DeI9g_{Xb# z!2|^9ic;;;X{_`}afntTYod|+C6|>^?679(LUJ*LU@itjQ#JbCqObr}NdbP{T8Z4U zEcS$>vE?QsSqXu5Ehd?1r;IvpSSDd2-69)e�>pUG+-CyH%x(PN!u9KA5nzmBeVI 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 Z3_louwluOe$j@hI_-7#bBkRHP{{U-F$SnW> literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/garbage.mp3 b/Frameworks/TagLib/taglib/tests/data/garbage.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..730b74e7c2babe736acc8b5922e0a0fbc1f6f33f GIT binary patch literal 8190 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 YdjCZ_?Wr7v#yEfT4BEkpaZzjfUmgnumH+?% literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/gnre.m4a b/Frameworks/TagLib/taglib/tests/data/gnre.m4a new file mode 100644 index 0000000000000000000000000000000000000000..f925ea9eb5d1cfc12a1effb51e3208737a97aeb9 GIT binary patch literal 5026 zcmeHL%Wl(95Iu1mS_G(8d9|erCuP9`Arblk4Np}<;-N}(frOmcX|39E@V%)73zR*Z zO0b7*C4PVfQg;0U7HnAa0qnyWKS&y(Zb9OXV$IC)&CK{{=H@y8mb&^?KklzLK@lCQ zBnE1qW8c%?Z6FHUUC-;dH*Yl8f!lcJ+D$yDWAngT#@f&rh6K|IW(v#{{0mdSJ$zm^ zZs6Q@oFx17qj-PUm(6|j^WL|kg}Dfo7YC?aK936IGm&Es;TeBpvhq)sMQ|xhd?h!X z;cz&IN8|CrcnsKDr`{gn=Xx)zrSiL$GK<tRC0Xj3{3q9AJFy>n4B^K9%=6;YU082M zMfm!zAEjj~l?-~NUFhmj9QL|&92Mt*iW^t`9AEX*K<<l3iDw6C<mPtut<vp?_Kwmj zBR$9QjK8c>oXF&{EI0zNJds<0%E_yeyaoZA`ohLVV1-G30B71YBG#gFimD=fyGrYf zZ`$I2;FqW*g*D>!o@KlippcK$42IL@G93J+9=OJy>@c^8^sJ1-^+HSYt`;_XyOYos z5!~g{FF<vZ?pN_U$9tb~vfk$mh|F&feC<&`jsww|G^S^t`$g*Y`!cS%JmG3|(;z4T zqYVRw0mFb{z%XDK_%9e}gpuOg6IOP5shk5A`7%|W9)L?kUdjpK#B&4}JuJf14|O7k knB^i24=KR?R_jjjcN*3XTTi!$U72366)5etQXMM&3nGA@LjV8( literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/has-tags.m4a b/Frameworks/TagLib/taglib/tests/data/has-tags.m4a new file mode 100644 index 0000000000000000000000000000000000000000..f48a28b520399cdd2184f965f4b690c68f6702d6 GIT binary patch literal 5108 zcmeHJ%WD&15TD(od9`_IYG0vjnzk0Psm2zG2HLbSHKMJQfF6Xp&2DVkY_?>#MMWft z2StQ};z5Lpx1xB~gAk!0cvi3$#G`+JS6OE^8=C|Xgn}OSm&eTf_S>2HW@o+x06KH( zwH0Y)Gztikc}120T3$;6uYR!t6!Hr>p3ibeBhgWSNqE3nqc9zU^g7!G;j*ciMKC+T zoC0$S{)Z_5@Ps8<Uc<AzAgwJ5bf{OppW1$VHk}4$ejQpm_5%a--bI6fbdx=?yYhD$ zi)3eB78JVZE0@c5nAJ3|rU6)0wZtzYeMw!`b76kvQj0?Q)Iks36#vipSbdU^=g|cx z2~GQj7fE1mRt)!{MWIltg;M0zVof)s<(-r-=FspdQq_@hp<k%-L7^nlbpoZ>wpuE1 zRo!w=Q5OoRUr|&=FM5@S3;2sl$q{z9#e$CrOpkC(RI0_ZVDb8pu%Zl1v;%ZwkZ%C3 z3oXd7`+PPO4Aq-cR8_Ax>SS-=O(-Y?TfxsmOyfEc2A-lsXW%qHW)1#P4-8>XW-&H_ zdX~oFupy~=gNBa1ot0515YOS#HvpD2njgu|HugUH(R&|ffZz<T3M!AXM-oZuXqbIG zFG~E%3XN+G?CpF(QPB!*sxk%d!au)sGdc%IB@$D{-(LuCCgw88_0%uOh>FT3N@`yB z@oX4@OM;Tf>xWEMmLe2TjN~(`MUe(KP~;^MB@i<+J9iG%rFxg#5d(X!;HDmB<uy7A z=Z%OS%H;0(nQ6DH+l8t-eL6XZk{%Q5pzj|a9#h%}(#b^T>XYn^jrSGA7JXF!{7&ZL z^E*5GQ%j$kNxq!Cd<(;j81CJ8CupCvui!hAN~eHf7!dFU+73*C(`I+rZBB>7;c_`! zJsn<;+wBPk+Wj4Uz2R`Em*WNwjSUYRj1F;JWPBugJQk0~`-dk}i4&=@lkpfiVO%bk zr_Iyj_4dRDxxv_9L#u$_f%|1GOdqg*#^PtREojG7Dfc?qkq-EpS)j$Vtp-?awAkOo v;<i6Sn}xwJ7C(%`{qn0WZ5zP0lq-pPPc%3&4YLAf1<VSV6)-EXCl&Y!Uf%vX literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/id3v22-tda.mp3 b/Frameworks/TagLib/taglib/tests/data/id3v22-tda.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b0545ea6f087d8784816eb4dcbd530f7df8a6465 GIT binary patch literal 4096 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/ilst-is-last.m4a b/Frameworks/TagLib/taglib/tests/data/ilst-is-last.m4a new file mode 100644 index 0000000000000000000000000000000000000000..c56c80498027ecb7113d0ec56042277233361ccd GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/infloop.m4a b/Frameworks/TagLib/taglib/tests/data/infloop.m4a new file mode 100644 index 0000000000000000000000000000000000000000..bbf76db8f6826e5f1837917a99f67d56361dab42 GIT binary patch literal 53192 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 D+voGE literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/infloop.mpc b/Frameworks/TagLib/taglib/tests/data/infloop.mpc new file mode 100644 index 0000000000000000000000000000000000000000..46861ab378cc3060fb759333490bb4d2852ccd36 GIT binary patch literal 434 zcmeYbaP|&9fgfCgN*5#-zhGv70Qc0qqErT_#GIVO6fuxcNKs;OHUooWQFdl=395O- z=_{dukwB*~WR(_|D5T{VDU_rZm#|@Rz9v*GB(o$Zl_3|+wT3KE>B!W?A_gM^0|UnZ j*APc{*N6)Z<QZT8A1M&=8UO$Pf1uq^CpUnc0n!KnPTh^O literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/infloop.wav b/Frameworks/TagLib/taglib/tests/data/infloop.wav new file mode 100644 index 0000000000000000000000000000000000000000..c220baa8f52a0e69f02d91a20e360ff4d1d9fb41 GIT binary patch literal 14272 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/infloop.wv b/Frameworks/TagLib/taglib/tests/data/infloop.wv new file mode 100644 index 0000000000000000000000000000000000000000..d8c720cfe06ede7b8275d051730591dc34bf3f26 GIT binary patch literal 2462 zcmXRfE68TZU|?WnVPN<V1in&IdtiJAUS<XchH{V!28Le@48A}q1<R+izQGg#)f*aG z8d<pLFfgzI4FIZQ1d%`xQk0mS&0t{y;W0SoB$eheBvzK{=4R?vD0q5$DmW(Rmll^~ zCM)<CC1<1-mlP!`g#Eu?oSVC)s3b8*H!&|WH&rh$wFGP~(3r^7#3BYG0|P^tfO~3Q zeky}TKz;!y)RvIUlAKhA)Uwnfg*2cLV}NUjqq}Rw1$G98zd$~a0(z1mmLEhjFt9Kb zm1Gu|Fg)8j=jo}w$Bj*oo7x^X?pJ}xGC1ew=BDPAFev92&(DN7N5MfMvnW|Fvm!Si z>_!atrxq1w=I1FyJBB&C=t6>q2Qz3O{`Yh-RzUT?WZ3`zK>tI`M)g0?=@|YuO3f?6 z?f;_GR7bG?Ne<ki0R-T%r)I=%p;^RYN%MFTHVq;cEn)u#C2Ux#!A$Zn8O;2Iw*;8; zbf7|z&LseCy*U$<un~ph&`c;u1uwARaRnB<IBN@RB{IB3LQ1%pMj}aq!i@H*`Vy53 qeEj7CZBumvD9DiOHQJ_XaO@46R2>7VPHB{?iOmn7`1=ncAOHX(ah;a{ literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/invalid-frames1.mp3 b/Frameworks/TagLib/taglib/tests/data/invalid-frames1.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..c076712c029bb734f1627817660a4b2a4fdf8925 GIT binary patch literal 8164 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 KB$cp(mq{A(q@Lmc literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/invalid-frames2.mp3 b/Frameworks/TagLib/taglib/tests/data/invalid-frames2.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..01976fc54f8da0d764c156a20156998f0b829991 GIT binary patch literal 7898 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/invalid-frames3.mp3 b/Frameworks/TagLib/taglib/tests/data/invalid-frames3.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..6bbd2d39720f4894cbea4358e5b85d6507ce0a0b GIT binary patch literal 8192 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/lame_cbr.mp3 b/Frameworks/TagLib/taglib/tests/data/lame_cbr.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b7badeb051c23a9f8cfe85ee0e4d6632622b7b93 GIT binary patch literal 4096 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/lame_vbr.mp3 b/Frameworks/TagLib/taglib/tests/data/lame_vbr.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..643056ef837c7f35db265db857814aa2c5ecda8b GIT binary patch literal 4096 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/longloop.ape b/Frameworks/TagLib/taglib/tests/data/longloop.ape new file mode 100644 index 0000000000000000000000000000000000000000..3800387acbae60e24fd724f466422521ee53fdf9 GIT binary patch literal 184 zcmeZubXJ(g&%j{9z`!5@#8ZHH4G`Z5Nd}S(0t<{7wrmE2Lz_3R0WuisAzTER^niuo z7$ZBw;+0$sle>5s?t2O_Y?Bve_&clAfSrL03>;tnxaN1m@@;UZhv@5~EW>mK-~7Dn z)JoT41;^5q%zS6Pw1QNiSZ+a4YH@L9ex751Ylx$}Ys3Y11_mXdLtcFO|NlP&g98Ht HkOndU{4PaD literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/lossless.wma b/Frameworks/TagLib/taglib/tests/data/lossless.wma new file mode 100644 index 0000000000000000000000000000000000000000..e29befcc837ba603af32e9cb54ff523092138d37 GIT binary patch literal 99013 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/lowercase-fields.ogg b/Frameworks/TagLib/taglib/tests/data/lowercase-fields.ogg new file mode 100644 index 0000000000000000000000000000000000000000..0ddd49357a906c70d6f3e5f222f0fa5404e4e2cb GIT binary patch literal 4477 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/mac-390-hdr.ape b/Frameworks/TagLib/taglib/tests/data/mac-390-hdr.ape new file mode 100644 index 0000000000000000000000000000000000000000..c703e2e2fd595469973c4162cfbebcc0722ab829 GIT binary patch literal 128 zcmeZubXKt8U%)TMz{KFPhJitc0Rp&yR9*@L!%cMt1`s>Q)6MOUpcX^8W0-4NZi#{b zP>d0(M_?lp0}F!yLrP*v;trsi8lap{9TP*cE-ORIPIiXhwp<L~F7Yr#X7e*VV;5q0 QIah=s$V;5zOy1sd0HO&R=>Px# literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/mac-396.ape b/Frameworks/TagLib/taglib/tests/data/mac-396.ape new file mode 100644 index 0000000000000000000000000000000000000000..fa7ae4149460cc0a94174a1e3ce72fa4f1106138 GIT binary patch literal 104 zcmeZubXKU~zrZfWz{KFPhJitc0Rk9-)PX7{FbBjA@^o`kdCkcX?il8pmRq7A02E_{ h>Jiw;#K6KJz>tzylE?s569JSH7VO>34%f}V003n#561ui literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/mac-399-id3v2.ape b/Frameworks/TagLib/taglib/tests/data/mac-399-id3v2.ape new file mode 100644 index 0000000000000000000000000000000000000000..2ea97fc45334ff952b0ccf0dacf6d5d11516dc28 GIT binary patch literal 89155 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/mac-399-tagged.ape b/Frameworks/TagLib/taglib/tests/data/mac-399-tagged.ape new file mode 100644 index 0000000000000000000000000000000000000000..3f5a656eb6f513713d7f993230ef4ccdb1c03ed3 GIT binary patch literal 91591 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 a5K0L8uK)rR)LWe4{~wkAUgUosU;hvJLZ?yy literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/mac-399.ape b/Frameworks/TagLib/taglib/tests/data/mac-399.ape new file mode 100644 index 0000000000000000000000000000000000000000..3b0661ee130f83142c18229bd5184e8b9f58ffd7 GIT binary patch literal 85212 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/mpeg2.mp3 b/Frameworks/TagLib/taglib/tests/data/mpeg2.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..13e8d53df6f32dc924cf5b153eb62b92fc13e65b GIT binary patch literal 16384 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 AumAu6 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/multiple-vc.flac b/Frameworks/TagLib/taglib/tests/data/multiple-vc.flac new file mode 100644 index 0000000000000000000000000000000000000000..93d9a8a14a2eb60b75cbb1be5c7975a095407b18 GIT binary patch literal 4754 zcmeI$%PWLY9LDi8jLQr&USo`5#&u@nGP#vYk!EC~rrc(MTpAkESO~c-l#s&SWrvl8 zVkZfOg(N#EJ3D28gq86=9=861bL#Z=>381Eclqo01!{#5IVNFJKSm)G{ki8^L`<J< z?z=zwALlpD?v<h~ebt0k2%GvuXw;jaUU!SXq19jQ9vK@P9m^{Ie_S#vlmh%Mk1QYy z$O5u}EFcTW0<wTCAPdL>vVbfg3&;Y01;U^0RuQ6<B}g+wEry|pAaxvsB73P^1=3DX z*UeDWFxBCNqNk|7V<={Zdb@&RTdDj?Nav@He2~72TDXPcIw;dN6yHd_KR||hDzFL} z-Bg<aO7K!QlaTVWPZ46^2}-nc_Ph?6lBvr`C<)6M^>mLz<{0X*9Qu7ITpq}x<IH>q zrKk%XoVi`d>YzG%pj0Pyl?SD{sMkHnmPZ9IAbSlpI}fE-P?i@cqnIiTL78RLK?any dOnrHw>=kOT6v|nkns&mUQ&xxeWQ}gC`vy%NpK<^I literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/no-extension b/Frameworks/TagLib/taglib/tests/data/no-extension new file mode 100644 index 0000000000000000000000000000000000000000..65f57c2ee985713476ac0b6e3483e6fe472e2176 GIT binary patch literal 256 LcmZQz7})>-0RR92 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/no-tags.3g2 b/Frameworks/TagLib/taglib/tests/data/no-tags.3g2 new file mode 100644 index 0000000000000000000000000000000000000000..d31a6ce96d7cb701216e29bf17e15903e3fca5c6 GIT binary patch literal 68335 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%0DOmT!Moqh7w1Y^2~HTN>C$n{#(z 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 AGXMYp literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/no-tags.flac b/Frameworks/TagLib/taglib/tests/data/no-tags.flac new file mode 100644 index 0000000000000000000000000000000000000000..41714416760792085717cb85fa1542e5a7e28981 GIT binary patch literal 4692 zcmeI$&nv@m9LMpGZGLQwubCN}`LSJ$g_Iwqw8?T<Ar7<rXtYTjoG=HqY$@FRxZ&iW zxJep?gO=Q++}xDINbF?x>Lq{0=Y9F~=<_~3kMFQA*eisnHwZ)aDHcM}-FQ+$wQ{w8 z>i!PD#`dnCm6iit_fk|SP1zMWvVbfg3&;YpfGi*j$O5u}EFcTW0<wTCAPf8#$fQTj zVvbZcpqzPXJp-viR3Zear>F)6(kxN;Ls0Gl6>va#5o$UC<*!noNvL3$YV3lvBh-Zt z(v4H`N2oAB84jVM0qW}o()UxrZK&8y`SnnVmwH%+6bm);29;W=)GlNwr|#5H8O|%& znOKC3`P6v_^tUKn9>}ESpYaK*$ev^{YsZk;PK`}Ml@2QDgsNQB#|dO{QlVSO+CxQS vP<1C|dWUSSRP!8E(@ve)pxRC9#|zbMQL}AOeViIR%A_M^yXJC-_V@h)*-@2S literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/no-tags.m4a b/Frameworks/TagLib/taglib/tests/data/no-tags.m4a new file mode 100644 index 0000000000000000000000000000000000000000..ba4e92baef225c0204347186c4aaf42e0259112f GIT binary patch literal 2898 zcmeHJJ#W)s5Psq~5L6^AKGYUEIFx|_Arblmn$m(QRYIyNu^?4WUWZ!sTL-^yD#1`? z?^3DV=~{_Dz<|icFJORynIF(SJZHO2S|L^#{3zDlJ+FOtKHZ%IV5V!|^>lBc2|{FK z5)IToX%*O?E)YlUZV+_*Cy$#8z!DC9w}}@GtR6VCm>-l%S%k6^$|)$P;D49`?(n)S zZZ7b;PLf^PvEJQMvfPKKE8mBwYhO^=JV0&w7Alb67#9rEm+Zyyoqy6Ck~2}Fj2wD{ z!Qd)hjz%{|Bfzyb4Yo<Yw%b`Ql{}Y~ALQ~VlFtM(@>2Z2=i_=>MFCy-T1~#M#Q3-j zXKz@zZ)~YJJqu-${?3_R$m&-e?R05)M(2)-g9j?-=TsWXxj<69-cMsc*Q@)+wqxpV zj5S%&a~`kwWtHRv<JWTF3BmLPPeYRzugc=xC*e}wUc3X`WsqOMRJ%sTx#?Y@s7Sqi zV{KM%>SBN3LlhFi8S*+}8t;WDa7;;OxK>_e4gOINJY-LH7+WAcE93B}kkY(!h0fmY zB-91M`@HotP+g_@P3+Fa-lw0e_c;R@>V0JcYHzz~7*5VS_e2`>dNQWxSc_uArHRIg nO(f_ncc?r}r@-@8>sj&l7xO2r^$l|OCU4jXjrCipjg0*b4AhjE literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/no_length.wv b/Frameworks/TagLib/taglib/tests/data/no_length.wv new file mode 100644 index 0000000000000000000000000000000000000000..c06d1071d5ac76d9be9d6eff977a5f8cd4c82863 GIT binary patch literal 532 zcmXRfE6C1ZU|?WpVPN<V1VAQ(QWyh+qC^ABdZ+S_ief>YZf+_+IT^wo!(7vHOB4iv zDi|4<7+lscFbHg9VqjqqU`R<UNn`-3VPaqg@>$tHroccd6NuFXB>(^Kh$sizX9DDd z>~pAN0^5abA2t(k=>@rfT)k;bjAZNWh#+6@s&(Y+J;TDpu*(J#H}CIsO5qHv`jVgM K;oRm(yj}nfOJeE( literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/noise.aif b/Frameworks/TagLib/taglib/tests/data/noise.aif new file mode 100644 index 0000000000000000000000000000000000000000..310b995e3c64878ca14d096b8e219f59389e6ef9 GIT binary patch literal 4400 zcmZ?s5AtPT5L9>cbaQj|_XV;UgculsWGaJ%1K%1KAPWfGe0+i!82ArCc%$TK2#kin zXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQQzsf#6_2V4t4R9Og=<u37ARma=Fp z{r|b*Yx0M`OLf`XqgZ_&au@QmO@GKX&0C_whUKRX@6{-2M_q2#X&izT3_(%<Y#7-8 zd|=+g|7&mNpR?0`w=Dh1eV93d|5x^<-`(w>v!*?M<o(ok%lnwaZ>Bu_cG%{JsSW!C zT}EbahC3BZKKwj0_&NXcGl(r^iH`c+{NdX@hCeqieQ&j4Ui^?<b_>7xLvG;;9x)r9 ztO_m#Z}I$guEn~%jO}di4~t7zaP9iQuHpT=j{nc^rECfh*#h{5PBQ$;KFoGim$AH^ zdD1j?&Zu8*TYf%S`b+Z?vr!b6l{aIl&5s+G{)O0l)|30SLhk33Ex&f@GBz${`}l!z z*N1P`6%3O6e^sV&>OSP+l@pvX?Z46`4woolcklnjx~%CBzp!O~dzSet@-T~~Cj$T^ C(vHsn literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/noise_odd.aif b/Frameworks/TagLib/taglib/tests/data/noise_odd.aif new file mode 100644 index 0000000000000000000000000000000000000000..bccfd7283c4be54cb38d869161148a620cc0647b GIT binary patch literal 4399 zcmZ?s5AtPT5L9>cbaQj|_XV;UgculsWGaJ%1K%1KAPWfGe0+i!82ArCc%$TK2#kin zXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQQzsf#6_2V4t4R9Og=<u37ARma=Fp z{r|b*Yx0M`OLf`XqgZ_&au@QmO@GKX&0C_whUKRX@6{-2M_q2#X&izT3_(%<Y#7-8 zd|=+g|7&mNpR?0`w=Dh1eV93d|5x^<-`(w>v!*?M<o(ok%lnwaZ>Bu_cG%{JsSW!C zT}EbahC3BZKKwj0_&NXcGl(r^iH`c+{NdX@hCeqieQ&j4Ui^?<b_>7xLvG;;9x)r9 ztO_m#Z}I$guEn~%jO}di4~t7zaP9iQuHpT=j{nc^rECfh*#h{5PBQ$;KFoGim$AH^ zdD1j?&Zu8*TYf%S`b+Z?vr!b6l{aIl&5s+G{)O0l)|30SLhk33Ex&f@GBz${`}l!z z*N1P`6%3O6e^sV&>OSP+l@pvX?Z46`4woolcklnjx~%CBzp!O~dzSet@-T~~Cjgc7 Bj?Vx9 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/non_standard_rate.wv b/Frameworks/TagLib/taglib/tests/data/non_standard_rate.wv new file mode 100644 index 0000000000000000000000000000000000000000..ccc90277e3c0b92e4840f41b2d7057e4a5cf0101 GIT binary patch literal 132 zcmXRfE6A>4U|<knVPJ^n12TXBL@P=(h^Jp>d7Hsho><19C>G@D<~G5Sfg#*6%rz~y zL_q+kj*)?h;RQ1T!vcN=1{MYZhLpsT#2la+CI)68pOp=47?en70;x=80&%q%x`6cm O`MUr0nbxeU-U0wcI2fn^ literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/pcm_with_fact_chunk.wav b/Frameworks/TagLib/taglib/tests/data/pcm_with_fact_chunk.wav new file mode 100644 index 0000000000000000000000000000000000000000..a6dc1d6c58bdbd08730cc16c71a940d581910765 GIT binary patch literal 14756 zcmeIup$)=N07cOUt*0ZfMr8#*X@VjI^be$}xIYep@fd^d!MXaXbD2wdkI8+yot9TS z4oP;|=i4QpL(*r+v$nc!j|dPTK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk q1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009F3EU?r^>o<ThpRYfvP77!N literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/rare_frames.mp3 b/Frameworks/TagLib/taglib/tests/data/rare_frames.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e485337f9e489dea7fecc899c7418cadc923c4b4 GIT binary patch literal 8320 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 N691C_K>+}&{{TD^OUM8K literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/segfault.aif b/Frameworks/TagLib/taglib/tests/data/segfault.aif new file mode 100644 index 0000000000000000000000000000000000000000..5dce192b05e0219b947cefde7573b7df1f3393b1 GIT binary patch literal 31 icmZ?s5AtP9KB4F6>E`C_@9WC|0>Qz4E<iyJ1_l6t`v*M$ literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/segfault.mpc b/Frameworks/TagLib/taglib/tests/data/segfault.mpc new file mode 100644 index 0000000000000000000000000000000000000000..2c7e29fb78fc1b5eff50e48411925d50562fd666 GIT binary patch literal 19 ZcmeYbaP|)NV4nKxpa=&85d3Fg0026+1vLNw literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/segfault.oga b/Frameworks/TagLib/taglib/tests/data/segfault.oga new file mode 100644 index 0000000000000000000000000000000000000000..e23c21706e2f87776b3b210be638f431e416c00e GIT binary patch literal 120 zcmeZIPY-5bVt|6`<8dhhK(<t+hyd5N`cNN7XGR7F{xqLNXP~H(5Q6|41A`m`15gqH vjsTeo3{d{bEFc4@hvCY$ZA(9t>-;${T;af=&?wZf>p|rsRR^Hyd>{Y-^*$$~ literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/segfault.wav b/Frameworks/TagLib/taglib/tests/data/segfault.wav new file mode 100644 index 0000000000000000000000000000000000000000..0385e99be958fdf68181244ea8cb2ecd74eb1725 GIT binary patch literal 30 kcmWIYbaR_v$-ofq80MOmTcW_kz`*eTe@bFWB9OrV0D{&CB>(^b literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/segfault2.mpc b/Frameworks/TagLib/taglib/tests/data/segfault2.mpc new file mode 100644 index 000000000..fcfa982f6 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/data/segfault2.mpc @@ -0,0 +1 @@ +MPCKSH \ No newline at end of file diff --git a/Frameworks/TagLib/taglib/tests/data/silence-1.wma b/Frameworks/TagLib/taglib/tests/data/silence-1.wma new file mode 100644 index 0000000000000000000000000000000000000000..e06f9176648836ab73928f0e15af92abd5c82f8c GIT binary patch literal 35416 zcmeI*U2Icj7zgnG>1H=(g&i@v5Dd+t1e`W(kcGvMY<7&vhmhfvIB*UrvIw%Q8zV>} zOfZXlh+a4_MsZHvT2QiJNYDfmBXL`#V~Pm|k$@p;Oi*LyoEZJT=bQ#M>y;Pl#s75Y z^z=O+@7v$$#gnJcS!Ye}Y2LFYVZU<*F|0bdZqsBqQx=z&Z}yn2bKH%{fzNw1A%ZBH zyms^S=GpkJ`P~V7&v*DZ5L^~n>un1Ub^q}0lDikQ9y~m=aY5ec<uAA!_LhX&OaC}w zUGMF8I59cHtR5J+$7i17*O?{QAJCWM&WEl5A7mV_4Viu}y8V2e>+A-5@Qe5{(|*Zq z_v0Y9bXSD8oWFc!Vb9Ex`_7fE*b#Df=2`twJjEA*2%g4WXy&^CR_^>|`u2H0&3~hI zVq{uoRqvGK-qw%(!=Jui(^9$p$=I&)kwtx*lUw*~A>gP05AZAx<9<|mTP+wlKj(6K zZA3ku;By!o*ykE98?hdBTsN%`abAxGtYeGO1s0)*%cd{=dEfR;nAdsi)p-9avF{ru zu~Cx*Bp?9^NI(J-kbndvAOQ(TKmz|up#SYlH#MFp3O~2MyS~a_^XT&bI9v9})f~mO z>v85POwPjMBh-=1AOOL0u`{=9d2Pu@Gj>#ev+qJj{=d#!+Bq<ob#c&c{}~5*AM0A$ z>Ftq$sdxm7QGsR5BP?WwA;Qc-l-UH6M=;Y=6k--aoUbltqnO!;Vs4!>>gWe~bOfuN z*&KVbcZ@mOJ%c$56C<Ot89rp1ol#nK>iJs>2kf^xImV32q4%6tplnRL#daU^*mFI{ zxSAtv;fRBL7pCue<~V{VW7fs2Z{T_f`!3`$w{hK!JILhBDrQ5XxRdL1FxP3R#AfHU z$vhc@h;wWa^EVAVW7O#(?B!p4sIy+mI(zi!H94V1Z?vVa*AFkQN!VR~VQcM*@`9zG ze{%fAx`e&6huN?r7lPhybOo|GTlj3~*UUQ0hpiv-y-wqm&&g#r3~j(99J+&h#B<^~ z7I3n8_?g`mr|jLWc)Y#0qbGSP^Jy^ItKu=4weuU@zQ$(Bl36j!YUL1CI{4KV2f!)Y ze8fz8Yyyz=?C-Y+^Ya6jfBW<0y|(Rd8M*gJTd*}d$ip;AKmrnwfCMBU0SQQ8JO$iH zwwh5gBU$IWUMG@0&F@F^fXv)7{*CAO)VBmAAOQ(TKmrnwfCN$txRGp(*;q!hPHxtT zWDA%po}oxKwV7HaAOQ(TKmrnwfCSP(z>Q@8W;T|QtaHbW6UlC4X~A4YvgvSzGzJMs zKmrnwfCMBUfz$$Cbpc~oMzZFXYqw7JE7r<ZDw0iYrdA0^KmrnwfCMBUfpie?stXvy zGLkj_kjaf?3jzRdMs(|Bb;nmaTq%t~0uqpb1SB8<2}oeHfLC3>7?zPNi(|b=cH0fW z+GHd<`Ut&}fCMBU0SQPz0uqowItX~x1uTnYB+F&8x?m!(Ns(+iTp^7?0uqpb1SB8< z2}mHdfLC3>vRFp4Tqdgv_+v?JieyupsZ|0JkbndvAOQ(TARPp}>H_{$0wY=H2NT@? zU0?(I6v?K;719_aAOQ(TKmrnwfCN$tc+~|gi)AFs+q{z11vdg+$w*dre5JNis{|w< P0SQPz0uqqGcnbUjXqG|4 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/silence-44-s.flac b/Frameworks/TagLib/taglib/tests/data/silence-44-s.flac new file mode 100644 index 0000000000000000000000000000000000000000..24e15deb8c85ef82baf22a231901d646397dd8c4 GIT binary patch literal 50904 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/sinewave.flac b/Frameworks/TagLib/taglib/tests/data/sinewave.flac new file mode 100644 index 0000000000000000000000000000000000000000..25d31b2d72ebd2d4e23c0923e06cefdd452678e4 GIT binary patch literal 64567 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/stripped.xm b/Frameworks/TagLib/taglib/tests/data/stripped.xm new file mode 100644 index 0000000000000000000000000000000000000000..57055f5f192d5858341a8a9235d0f642481d7180 GIT binary patch literal 602 zcmZQzKm#S2B{``I`DqHp`FZJRa%fypAfFM4Ilv?%0~<pv0|O^Wkplw*Q^P12(IEf; DpT-63 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/sv4_header.mpc b/Frameworks/TagLib/taglib/tests/data/sv4_header.mpc new file mode 100644 index 0000000000000000000000000000000000000000..214f7ac4eb710e8e21de093ee7959dab8fe86bca GIT binary patch literal 128 zcmV-`0Du3%Cm;Z)0P+K!E+_X8&%*;!>;D5wsil<Pw|`SHW?4+jVwPoDmSr)-7(xuW zvBWGKXl@{?UfGo!nyhFu>6)pQDvY{}wzBH4T&wTPvrFTA(>cv{&D-_Dah=cBT5|n@ iE3UO<#~#&>*ohqgTB!C%gDl*OOm-ISqH9VS7Q>v%RY5rb literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/sv5_header.mpc b/Frameworks/TagLib/taglib/tests/data/sv5_header.mpc new file mode 100644 index 0000000000000000000000000000000000000000..6d17e65f0f865303397cdb08632cca5a2aa0365a GIT binary patch literal 128 zcmV-`0Du3%FCYN&0{{RVcBcU78pg2y2S5XsQc5XpYAK~=`TMqG7PBld%R&|wvn-27 z)(~SG+b-oV^~l!Gc|2h@R~)T+MB#OHHdkBzU)gt`zIpg}+1_WT^O~=6<x2Zku3Wju i%9txZJ@=^oqLdzMqz%Mg>6~40@oh*DH|+sQguls;y*_RL literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/sv8_header.mpc b/Frameworks/TagLib/taglib/tests/data/sv8_header.mpc new file mode 100644 index 0000000000000000000000000000000000000000..3405545a29460883251a5ccd6135ae440bca5815 GIT binary patch literal 114 zcmeYbaP|)N;Crv7*~rn-9LK;a8RX8x=vo_f28=z~0~zHQg8eyM7=XYrfJKH(ED#*R L#wc_k*p(Ro^T!wZ literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/tagged.tta b/Frameworks/TagLib/taglib/tests/data/tagged.tta new file mode 100644 index 0000000000000000000000000000000000000000..1677a7edfbca692ad1e659f1d477be40d4138de7 GIT binary patch literal 81819 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 Wc?A899X%42n)rVm{C~Xn=l=qGix&X^ literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/tagged.wv b/Frameworks/TagLib/taglib/tests/data/tagged.wv new file mode 100644 index 0000000000000000000000000000000000000000..333f8687177ea8d6b9075ef35e80c54869bc0aa0 GIT binary patch literal 76627 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/test.it b/Frameworks/TagLib/taglib/tests/data/test.it new file mode 100644 index 0000000000000000000000000000000000000000..379444b91869139a9ca1b73d5b2b9bed98370e9f GIT binary patch literal 644 zcmebD4e%{VEiO?g&d*C%$V<#kWk3Qf0!%<2D+42gIFkqyCj%Qpg8^G@15|}y3da)# z3V{QK;Q#+GK-ai1GB8X5;wM0C0&+4CFf&}Yi&S_j#sH*WDj9Qv==Zx`3otNv`UV6; z)IdoG28WQ0%wh!~N>nIL%q_@C1qX#5iYhQ?wE{w{1!@yE^E~qua`RJ4fd-{zf{6U0 z+{BV%gkBhnSo4rv1$1OlszPE?X>n=_%osRLta*vW3b~~@C7C&yd8rD?`MJ5Nc_qaZ O_*Vh$0{jeY?gId>FJKJ- literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/test.mod b/Frameworks/TagLib/taglib/tests/data/test.mod new file mode 100644 index 0000000000000000000000000000000000000000..136b6119187bf574bcfdca2c4a4e9155880d553a GIT binary patch literal 3132 zcmeH@u?hk)3_#mI$u2rO9=Pb@KU6O#1)D<C&i=Z&DA?il4km>V@)AN{jrEC;7vM|} z_lXN}EiEBcppcNiZRaMbd?*Ov));39KO{uyJ*Rb%A(FJZn56b8&rQxPaZS0TQ2C%U uO82|1CtRt{Z6%|<6|?aNHvFsq<cRusI$uZJwVP(Z4445kU<S;<SOae~S1CaN literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/test.ogg b/Frameworks/TagLib/taglib/tests/data/test.ogg new file mode 100644 index 0000000000000000000000000000000000000000..220f76f0cef41c7e50736c9134ffb20f0d297961 GIT binary patch literal 4408 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/test.s3m b/Frameworks/TagLib/taglib/tests/data/test.s3m new file mode 100644 index 0000000000000000000000000000000000000000..668250bb7876f31762bb3c2d4a2cafb93cff2f9c GIT binary patch literal 544 zcmXR(EiO?g&d*C%$V<#kWk3N^0t^gH46F={Kv4x@CWc_=AYTWzS_6SUNXj@EIhnYa zxmkEvdD-~b`Tt`8P6j>(VFn2XStL!km<<iMWI*B$K)hN3NFafbjLc#MAWF<r$jmD) zDJsoP%_{->MGr|8cBTVR7~MSI{FKt1RE4xm5RqS$n^;neT{9L@Z002<l@_O>JB>&q lv6@$`kXxEll9`j4m#UDQp9}JCF=4NQU5m{;1+WZm0sx-yM0EfF literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/test.xm b/Frameworks/TagLib/taglib/tests/data/test.xm new file mode 100644 index 0000000000000000000000000000000000000000..b09d91324bb3e6edcdae4f659965e1a87b5e1954 GIT binary patch literal 5471 zcmeH}Jx{|h5QZ-i;$vq*Lb?$N!4F`{(t)89`~lNA$f`do+mT>E^q<2yPHTn$T@XX= z68kLqJowT3+FrawX<OOi(zU&ocNkUF5?zPE*=NXcu~d5f`sl@KEj^BLRGn7<^usCj zTWZJfHZGWNI!jw`0O~ErtO=ZuGwyAOzBkfFw8F?R+M<T5arC#T0ZdXnIFRb`;Oe>_ z+)vv~@$sJXPkjS)Ou&OM8!dmY`%0`-C#=Ft6i<DiQ%mfaQbjdutO-hRMn+YH29R*A zoJkT4sBGa#C)B)g>ENbBR0uVLT6gen%LI)sshQ!z#7@z5**R}S47n&x-ds<8<4Q`? tkyd^iJV|I60z=3SVI{B<SP7W`D}j~3O4t)%JFp%0V&HcI+u{H0@Cp8IS;+tZ literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/toc_many_children.mp3 b/Frameworks/TagLib/taglib/tests/data/toc_many_children.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..168c47981fb866c759a5a845175977b95ce01b23 GIT binary patch literal 11525 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 Pw_x14fA{k|!>{%ad<>qZ literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/uint8we.wav b/Frameworks/TagLib/taglib/tests/data/uint8we.wav new file mode 100644 index 0000000000000000000000000000000000000000..9623db2291a78e96fadbeeb7ca7e58d2daf89688 GIT binary patch literal 47240 zcmeIb$*--+k=XYvZwxOCuMF?Zr8ffFY8w!^Fw|&^FokMK;Mm0~vg%dU8}EGP`5Vr6 z=5y}+red**WK--xJ;*XF%Mc91_Rd<^e~SMC+rMAN{p#FTBwM{`?|8m@&zKn*84(#7 z5gD1^x4!v}Z{+{_!GrJr-uM3D<D=Hs{__V99{fN5@85aw5B{?U5Ay%|!Gpi^;6Hfq zpYZ?h^Zmca|APnr@q_%qgFk!l_kVo$$-~ye_5Vn@-#&gzf!|W#|3?ZG`J8K%FY=P- ze*P@a@<zVo>d<PIZ}UMuq{Jq_$aj>=@(KT|{E}af_${YSlbXwX%=cB^%iGi(=LP>0 zp4Zep<r)Hio=;ON9lqb?|0e&x^S{i0lz%t>R{kEZ!~Dn8U*$KH{viJts4r40V}8l` zbVBPF>B?z$0Q?2eSF~ILcMR48KJEBCr1Tk=*v%+Y@j3y=ER`(MYeuO)-%hBzqn$s> z|Cq7|wEP%IExxT%e{y=$rL1rYVD$KQK^r?LGNaWNZM9QRv-EqkvPwOf(Qclf6pj2( z^8YLU?|}4P{wV)p{-o%GNtUQO$e%#-Nq!EchqP4iOOI=wzL`@>EH}{NrdWX0us8<B z6TV&W?JASbh0PRWYAv{$MVpVG=l^^DKT+bN{5$!_+0*<v<>vgl0@?|j>(Yl!R`O{C zn<sf6vaBe7o?j=P+fQlK&RbanS{6`#oiB<xy{>4z2Xz}j`(N_^Gyg1q3jcnXKg>TV z2K-t8dk$ru)6xV=%_%#SqWn7Gw+re`!R4ZWZ-v;86TeRKW9r_(jTuz%lJY%%z0Cg= z6#Pkkn7^03M>|iztKynMlWu-koMt1in}gSYK25=V#HR~-xT8loW%^lB%z$-9-_Q7c zS?sKlEmJQ?MK{ayU(m+C0ps`cAJCtVz-F9HX=4Re$MmKR%r)33-!oc~^4t81>w-3v znvCx!lsnB%3c0bOJXE94gT%)w|Fiu6$p44b#vkS%BISn(iqbWqjf1?CRkVVf6_eD) zh&Imim-#D7v?<}(vE@#7N{h<S3LKQiaiUg{f0q4gc>nYKY4QE+UAXcL37sU)RZ#O8 z{T~AXNdlu18q)E6nY}4~0*o#gG$>utN-y=uaf_sIU1nL)rS81=arPJa|C;{`aQ{~R zZvKPpNpZ&aQp6GdoHiJF^h1o)FLHR5e~O%_O%B1pmJ2Xw(5FdW!G&e&MZxb%#x>*d zf6o6mF#k5Z_%8fDrcRNdo%8<VRBsMkWqp%mVaph~rS1s+K4XMcj14t`^=7m$rR1ct zSy1j3E&N+r`%eD->|03i5!m$eQL)Zn!^!vgBy|hkk7;2IjnA_cvQTCNq~!qmNV`6l zT5Aa&8D;oAwX{U8FY<p?{9F3=0ptE1xP1!O&Wa{wJ_pMW=*IxeDyTG~g)1=HG7^)% zD|*>M0k4ri<hfYp>ui=6z+7i*FkCTK9Zkb*O&kAxo@F1?j~~(VHe>ENI^c?N)PN4H z)ML3hVHACqU4mtg7ROn}^GV)i1T6|>ZV64MbV7M3>94xA0>d5HzRv%Y=jZwR*$)^O zPvJ)sO*Ej!ig!{71q5U@)l1|Ry#<vV?HS|1aj{^m*~XGKCiF^j#rWWJdcQ-rtn+`K z|52tC%JpY?FKZT0=!X*ChbCy9VxDb)wt#YrY>w7*%mbk8XUgCbjBKN#wP`WVhP17= z-a<X+qk=y1dv;a)dH$M~K7?)`L5Bv{A*0Lr1MNmz6KJfCsE}keihMy=0w?1e<w<#8 zpu?1JX2q<OPmB*GZwfABFzXbZ?9=?O;O!`XmVI12Lz|xFpJb2uyr$lmYgEi9SH&e5 z58w@&A{!TuOtF<lj#m3yp~0-dtd>U1lJERhiQCZIUiQ3r2==eCA7@|i&3lZ`$MC1a zxb4exBw&|#ahqR(WhXz#p7Pr|-xO2yrn=o(9E%{q&%sO#qNhk6c#Py0TAP^@Zgk*K zC+ovGbRZHsKxUmU`6X*H2J>Q;Rg`r$n`cv?b&$|OF{B-OSd?&1N~L~4X(iFmeoOh( ze|uk`-^DCuZD>}E;4C&kHYrBnBxl6G5)ydK;7XeF3uH1I@|+{Dl%}NC1iw*2u9ol; zj@swc<_s|wjvR#k1+og(!(s?ek%2TiM#X?vC2I&Z9I?**{LUXUD*wnMy(^R)`sgf+ zzT>l{5EM9;W^K@lUI|;eTF)|A2T($pn({?~Wbq4lh)qI)-~7ZAeJYUEg5FtsLVHC~ zf;Ti|M3giNK33u{=NDhnKQL`eOeR2?CHyIoe%3ybNA?Q%@KEb%#0LP1P5KKgpd?s9 zhoa&aD@^G5IPrjjwv&*~gp#<B+7Qegb;Ka5ijxocljsDvAZwzAl&!#q@?vI-$bnJ` zFQJWCCW@tBp#^<VmXI7<qm2EsC+Qoo5+f;<Na^<(1WGhiE6Gh>U_p*F`b|z7Vv#!J zM<ggv7OvR)v3?h?iR=EBx+b6Hr-ddq20STkkD_GiF>n*-EJ&L^pmHkB90`Tt1cZ=s za69omL8inwNuc(V--s_grf$;G)J(05x44Q0b)v5HgMyS~Zj<hEE@5p?GB_$d?)oTJ z!V-i+${R~#QNy9c3-B1B0hA2tK(fjX_(*l&OVmxZ6a7@ud~ytdmA#{Il0q6u?eIC_ zNE>C61Z=cBYSKpfOS1GO)^;Q?7W2eCKB^y(S<5TXRt(%z|Ls{;CO#(|l;411_v2GS z2i$~euhZ`oE3M&Yi}DASl+xxBFUhGS*>mNX0>DmknXtT*ri3#{I9i~(uql*U^Eo=4 zxGEfe1FQ7C^wRcHvJ$Ss7ZsoU&gy(r9umfYf~Vk!)?j?=55*W4+Y&u1+>lgzKu;2$ z>ZFh}XbCLk;!cjUiAu-L(?Lh2)e*Nu5~y5iRf7-=gwYn`3CZwbA>hE5^u2gXCulmM z6~U$dNurMu6<<mrYe)+PGL0+BNKFdm(-0L$M&ce^nT%xyl_*f!uRC#4C`7<sN~=(U zJj<g*K~zBM9?!OF55Phyi<MtZ`Q3Mt)H;CXp_0u|$qI)P$C&qi?YMLrN)utZEEm&x zxvZu>%E@$6j+jU>eX{PPGwF?b<KB2Q8QK>3O{u5xw#LB0MAu-+c1+Yvp5V}DvTb74 zXmTH5cC?t5nYNJDS+>UfS{LW#O?g$FPv2DA<#u(ms+RM`bkSe5muJhv^6}uH{j~Aq ztZ{sH*nN)Y*_sa)BTTI6W_i7PvD<83FJCU^^EVY{(s(l(k9%cnemZ+Re>Q(U|7dnR zJ)WG5M#CY##;iZ<E&8kBc6c>@Gg;?9DPLEM>ZjFWx-QR)K`|<t*-`#z{&e%`^25)L zfBxj=Y&$4BgK2B}yn1i-z192Mhquo^Jz2MYI(~hzeEEw{=dU+6*Vp~)(W;w0?>#y@ zX#S}4ar>zAaM&t4>(=G$D!;B?u5Xv;>-qKY)8^*%#|K|Ls}BCz$$4i!ev^;NY(82X zEuYN~W*?Rxl?VBU<Fip?a5|WdM~LluTFo}+yO-BLfAtr;FTS|AsV)XTYt5eBzPtT{ z$+xTbn~zVAo;RNi9$q~D?9opj{iN~o=z4H7-j1?mYkIgm-ZnPvSFPLrMe(!E?)JsY z&#q_JudWumY*iMm!ExuP_2~4Ilcy&iHJ*1*M#tH(9Ly%O`Mj(&_t)cBqnpwB=x3wt z<VF5jQB7Z0ODvme)|#G`r`6-d<IS^+k3K#8{OIa<HJo&NlU8+De6V_V_5S+d&GXyS zZSyDNtIhe%Pd}|*zqmQS9$zoFz1i8Y)q2`|+&OAL>>Tz_M$c!1ZD%(;uP!F%i}Tfb zF})n$4z~T5{Wsl3^NZHDyBWMf;+EsAXiR(6QTa4G89y37o1BiHB8{D_Q<l|mT2+hL zY`&XaRM*p8`KGwYF0&t()%4|jwJcW4>+Z|$uCqAI4tpovN6nKb#~&SjH2kD-I6BK3 zgZ`o{Vbb=+`SsP+`OWLg-R@@oV!Rl<7|eRJUe)b&`@?2&Qk~2X7DtPt)$yjWY|VOQ ze=?phc*oQJyua$5_pgTA!K=af=wkc|`M#d4CYsKJqKBD#Qk+gts%O<Ha(Pm9tLAiA zjiy5kEgVF~!#cmtwz!@*#in>UU03Jx-F!NoGujz-!^YsGbJjj+owQCn$K7tPHDDa( zgK0S{=hbqxTCFeF=d1JO?R+!4t}dplk}yU#L$~y~+T)XPV{$w>oHQm!lUCMdWa1vE zE6_2^Y{{s-%r@Cgc9Cs)Uh#Q@nw(RrU-XN!((AB1El>FGl+83^MobOK-30qfYMf`+ z)Vj>haT?Awg;AWiVMV7nB?xttA7y75t#h>}qw!!|j;l#Eq35)Efd;uQc4&mla#il8 z%W1_mnhvHt<n63HE)R-RG)W_CaSbO!)R?tblg;FOayi*ft|z<nZpHJGo@+)(d263c z52mNn!|55rxmga&A<A(E<=5bQo^ZdS);V~nzo-peq)#_LEzXMLg0_o8-ZgP(G2t`x z<z!CFTUx&0x}xn1AZ_Ty0?15U<)CbrXQ|brDOcG5j}FvuD#n4M<z04_om2Ocwl9FN z;hNK@oZj`})M<8%qklwz>6l(L%O05LP-TwJ+Crzx;-a`N&T;fFfVKtiIhc*J0TepR zPO^gxd8hs<j=3#s7R`!Pv8A3tqiZzw1vr4oWSv@`&@w>+>K>x8`7e;#qLo@M%BozH zoASK8qU~#17K(vG`V77EY?yU3utv%bCkK<m@zMBn+?;fQiOY&xgT61WsC`x5OfRNa z{O`&gFlS6whJ$cT>G8AdD0|N98CN&!GjBOF=?q_j@l|nCTyb3@D>}<_B#>}b)&%Ec z>K&wBTekxpoazm{oKyY^jBm<ou1orG&P+FFHXbS=NwS{fjy|XD6DZ$-{*y@wP1Z=o z6>@z`aOXO|1<nO66NqG59VW>>E}j?92u?gtlx`w3gdO1V0=kItjlolD-w<%TEY{Sm zz}T?$F*Tmj-)FRY!qsM$O|r%KgD=#0X;7(nDfS8DML9tRI*f+n;wjg2!V|}a52)G4 z`j}1@$kG*K;brzJyUku_*U}6+tuO?JMHfjrqSmA0S@A@vHUtAM8r(A|Y}uEDTW$!K z;iU7=T#!a_pXv92>tn(qPr&#LYRjEb8iiXZbd|l#-ca+$;Cv0v%VJYb%Rbjp`Mf+} zhI>{%DW9hCB#nm1M1|ztBL8o=ZYH0D_wD31TQH6cn)bld>jP?k0*nT@570(q0ueK4 zd&~b%vKRF7bLjO39b!KXW48!A9MH>$^zmay4`V=@7mScKbh(<mo_t1&U(nhMt`*wX zAXS@D{utVP2&P^Q^qr$_jGA30nSVhT_$L2C>Vornyd6)+{c&e}Jbp5HHhGvmBG~;n zN1~98VL5^V3*`N}ye+>dZ;CI9H^ns+(q5WDy$)P|1T{b6dY(OEoSvm{7GY1usdE0Z z_?#L)BY<<8|2XOCGV39q$CIe{vGtJt2B8gt>5T9-Hq}M>Y55tg{(`n&am~;d6EJGQ z!AI2iFnhuXeG1+Uu+PTh@ocOXyU2cq#Jnkf4wTQSGcVWE?zD~0d;~R~6+eQOhAR76 zgK_C&12s3<r*L4K{c)1_AH%y1`n6F=&4<Y6Ln)Fy(T3z7Yj-l6yr8X%>=)Gi2^3xA zpF1v!Ia*FV`7yHcynM*JL;go)m(gax<chgzll>ftxgGy>ycoY3Z_+H%MeCeqPodyZ z{xLcW3x!$(_~rz80Y$du7mOk-86@NtGamLS*r@5AWsi~NM=7A)KuWZYOUCyl*EwOw z9p&Fp<CSv?wL(8Wr$5L1pE8b{jGvKab|J5K=$FsXldsDc%w#X=l`cpZ&Yq&1p4j5# zgc-1f?J$NGCGzg9drm7G$2=qF2JV+>+%=J;6D0DeaCySemfA&U#~|PaiQl2gmry=t z-6h}aX1Wl@;t-P2=92CMbPEm^JhQes)qv<4Yh%NVut75|={4ad<_SVfj57nZ2GqC` zggy`mK|uqwIao7l7^z+cYY7ojTZi2GL+fMp+||FsJi>S4?qZPPN-m(RkZk7=!hp#c zG}y`9op%Wm62j!_0&RdVf`dsLb^Tc2Fd%LCm|$ND2u#3oI2jNiq<v(Zi%_eS)4{|W ztEy@~T`;0o)v8*TYp9LiGn>sSJhdG6W;_~>2EAUV+wOEbgU+ZkLDykzjph>?DreNk z-ds+BU@)B8%L4q-g%K<mC;O()+}9mbd)yuOCP6*gFU5a`tcw9Ku&bAg#cIBuZL7=a z%i=bhj|ZdHpxr;~J?}p1e%Sq}`?S~Wj|p3r6U@r;&Ghx`X1-mlz@a3lJy^6Br}Nfq zRN*(^?O~dX&WD%%^WLtv?#`jzpx+*x4Ufk-M0vC9VtQutaz0zk7d~eAd~hVuZ-Rk> zO*<-w)nL|N_BOrC-e<i(?f-PJ8g|C5$$;qoqv`jmZ_mCz|6qQ+7!aT>=Ck?B*)OVJ zRzEH;iY+9WL4nTjWbm~Atlz`}oni-0hnvxh$*07lUzO+6Dd0P^*8E`p@%*Ft^I2;) zsK$7qC|Xt+cG-5kK#B;SQgb}$agBz>Xf|G>I9}(!EdOHq1*GfD+FZ|PKbXBU`-AGc z)kBQMaXDw5<TCqw@+ae8j$VzH!-@dvWC%W^qtOv2$ry3jF+FWCL0(iZs>^DHH8`kR z)yedDddPHh$U2EmHzwwI4kq)_46894;!Waa>Ps__P!qFctX@~2R)1XmW%YSAtInn; z(+0}&d&PfSyh9B9DUNe7Sus&uj($A)`RI$$6~W{Lg9+d)Zy?;0ayAv0?R3qxo1Wu> zJJFAr>bl7+YGmz6523<mH~c)Vn09j1hXdLeFrGiEt}G$zbjG!sUNQyzvix(Vj|FNU zuA}JQ&HmBkoyiX;&w(|;SXg1ezMOnUy#5N6KqP?yoRw|3(j%}y>_6L-tLdUxPS$XB zL4h#=0nAe%Vq)b41jd8G7&OqEC^7~QHps{_X?Zj^i~wF>hRCmq%ly;HA7_6#`2yA5 z8lQ|>!)K#+NAHaP$@t;q2o*zc00VW23-{^l&HQpU#lFKMot0Iu>JVu!#)}~W*PC~z zomqF;Tlbd`$?g*i$Fy(E&j=lKw|(a2$p)RW&X<JIh`uoTv<e#N(grh31MSo=NAr9K z|9>|BtM#9*Z+7FWlWp_-^y0(Sw>R%B-m9KwXTyHKkJVqDYz|+YetPn<xn@?M;a<!c zY2Epd7N!?-oY&cf>|1PBoAqplzAyXe%trU5anw9+o^(&Ttw9%$#~2jqheagpkV z@%Jtf#S1hr{lq2dUA2DF`isNAJa}_5Jv=*ZwmO|h{dcoJD8FAmo*yk3Gpiw%<a~R3 z^Xbj&<@wcgH>=k36$0FE4%*pa@oaX=?0HnS%J!m%dc}#%iRnzMayj0#E}Iw4UFT(Q z+uaVXhVuy)Gy>Sknv*8B%TalRC3}X=kCGsMG%cpp>U{os{wJ${x%uhUX4gM&ycn&I zc0WA-_WWD3$Hl?;taa9G;Ehj@E)L%uylPyw7ya3IheXe3lU2_}Dk3(;^>~AKcROFL zcgx)zU6bdGsz%xDoiv}|(;oJZI?Z;cKftwv66mA#h*<{Rx+yOwqz2$MtfqP!%ro<x z(bV|o&A&K#`E+sII~*L3n~%Hi4Zk&ce|A_spP#G_=S`OB=Ifh_SDUNd?s~D_Y%aIU z*>uVTKb*DjNKX(yJhSR})mrq3A!Hd-z+ygKP3E0>cQIJ^E;`%hwsSp#h{YEDPH-QM zWZlyoU!f!-Fl`onhG90Ht!B5gKU)6P;%D>iyjTu)Qz9_$Uw(i4hpWTw!Q$!kS#dBL zb*jep?B?+DaCfph**30v8<&UB#<Z(&&jz$V-&NcBdU{pi2ba6WVoJMcp3dZ~f7Uu| z9CS|b=bJ=7Mg#%zG$t-ax<ofC7S*oYGM8T_A9}M~zMTGP{^#SD-35UTWa8lQ=?_kS z(0J549Y4w*PoFFr*a6jIJG-RGi|yt1Vtsyr0$R-wso9h@)X9XU`06aj1D~JFPG?RZ zBi6`D+MSQ*!$oV}s=BlOX1MFG##$rH$9VNDWic{I(<o4h*m1|@DgJZ29B%V3ia(!! zzSzutEN5rc`|A&PkJcyKCl?PlPnU=5{(?2e$#%GHouAyCT^w%@=V!C#uDuxG?3F{( zE+&idjOEwad`bjwS7N;o`PnfWFGnj3KPTO8=d6D^IvpKin>QzI=6?g>_=6=Lx?vOi z**Ua=TGj6S{PXoM7q3>^YBQdX%KlN~qmyT+PmT|d9ycC!o^_v(y40Ok%f)7KQQgco zv-OO@zTItR+v%>@q7oKN6f9Zhee7jzwbSx=epa<v79A2}Wp+`fdh-FZG}nTO%xD1@ zwl+ael;mVOoOZE*^|_CWlNkZL7yVDi7v*`iP8@C$+-z1y^V8MQ`uX}`bFgk~2Fvkc zI%E1Culmc*>TG+mJw>w4LZ(rEEZbt2Qb75g@E%(Y-)yoOuSe_NvcJGZ%bD1Eqc-u! z)5(dJFtLk1VJ$*yOeTvI6Rxmip%^4soo`;Pt_(NvOMwDzjyk=w_VMZQ*>kA&tZ~?G z5Bj4?X{=(dWn*9t1$nX9E!L2*S~1Nnu{Rf}^kPiglU9B@LB_JvQFDk6U|yLHs^M(J z)C*l_W0%3<+?04NT41Hs#Mme?ILGL6BQ0l(lk?LT&8yC?zs2s)XXANm-kvoU$JI&s zyn0GV_-N7E4%XupliG5=R4Ebm>R)s&8t0Am>GEXOoVKgJ(IIQo@@%}oCY@^84dEOy zK4f#S9xlgAex+$5wYJ`vHkkI$i0Pp<slcp<gc(Irr_<fb+3n(dzFn`-N!e;RZ!Ovt zL9P5~_<ZoReb78<bcl>j+9gKNg2A&b&Zn2t3nmtZB7y4J95Co_txeiqjh4tiE*-Of zf7ltc2hCn%(7?p$Gs}!dtl*ECY2dt3H28pFLO2<#m!88<ELN;Gu$&d^(N+JdyKQfq ziw3?;x8H9K5d@r@qv`YMv+58$yR#8AC}*>Bf&6db`{m+dN%&^Ef`tfluj-YZoXFm& zJM5#E2AC$vP#Mnpbg5TiI?smF5yliI9CJ>FuYqhaB~4j-!OFtgB+}QN_DbUsL<g{O zn3^`Yz~|+<B9s6I%(=7nv^_hmj%P>nXXyE3NZ&&O(Z4vA%i)I5@x}0RaKZC#vKlfd z(-K8lQh=ZwI82lA#52KH!i)W?U*TAAVOZ%2V;N;5ijF{6lazwsrx>6<h!|;L`v$Vk z2*eS>(rSa<wtz6;cr^$jo#cn(g9%~Rad!ekCgpgB(ppb&^s)=W(}a<5JnqO)V5ML+ zji(rySbZ3XjtiYp+_u4Fz=8`roq!ysOfo%LqM|{~EHWH;M&KC>Q=LX;ZM_gK>^azM zhSrAXCH9%FQCF8RKXafH?jj$J!AF)K=4_RmGS$+qU=m<qWQ$65Vga^GU*&{#D~&yH zOs1%L5V&f11qO69Pq9W_Xu?IMIQ3LNWQk;V7)m@=ztTDtMUv4;DQII}(S)2D{Ahd- zvpWz+GAUEW3PH%cjSHsxS7FiWItHHP)@1Zf_YpXZTx>xt;$$e}@QJCp8aMz@6ekiq zaU*pEX&h2XKO9HH!MN%SS-*pH{HG#m9g$D4+_EXZ1G*<i4_Xi;Hk=OM!H}z?HWji8 z3l}$BRI`GZB6PA+3jrEyBJoLHnF%Q^6@GXwmQt8iHaLUJ4Yz@qC<%m+MY#>XLn^<7 z6tqno01F%mF2Lik;1=U^n8)NrOStLKf|_cAd7>+78IDJurS7<kwp^kJveHc22CfG8 zb@i21Iqw5(z|PE*If7^=rMN1Tm<$FFfZ)IQ!7BdvPY-w^@9e4-zU&Vu+#zA{REOSx zwpXHzyoiyh>@(K=mxUsfFitrtj2(rOz%DKMBdg7TNCdPF{jiQwLC~3&@MmZvwV1fz z>h7Ir(4lC{)nN&v%!^%On6)q#SsL;yYT1gswpW(-A#T7+olEtsOAXh+StJN-64@>7 zI(%g9pz9L;;$^?A0ibm8Mc|Waq#j9u1evbU7u!gE17oVB@`eprh)VxTgPFz#sVih~ zN>3<6TgqYZ&R*CT`;LTiSw~8zAN)eR5=rjpoPJ~FmzEO6DQIn5(=1aq<jGxsq<plP zpeE6yJ!$9rRFAe22Wd7@SwbV>ijNc&sx|7?0*0-nK2R#!kRI_b5!0`A3$$22LK#@X z0GM>QmN=$Qsow}x;+ZY_uZV;UDp9nWu&aA1Es-<+u^Lm)qzoAs)S;h(<~MawdP{3w z(YE@HLb8A_9R}(Fdq`#+Jv7Hd+K8d_u$FqnS4XL16~_*aTfde|Sr{XRBGmQa&tkxz zU#5rexl;Gz8#y69>IUKaURc&qA2>eKm0Ex{ci%)AYo)>otXe8Hml}+EzKu4O7x`c3 z{e-R&eu6?VYbZ~0GI;I#m<`N!+hb@Q-59Q<odR2F=RD$d_iKU<ghUTRz|tjYE2tT* z_-d<iLmtQ)xi1({;@5yD(V};TP+g!En)un4nliBU8?y={=aVma1n%PJ%#!j0`8Mf3 zUW2A7R|kk><0Z&c0@E61!ZzU+TCAp~c0!U+6ofUb8**BJp?E1z(knQTdYV3q)fg=? z;NKicXE7-p@~?y`XFlYavnCh#Ih&>I5)0o^MCjI}@xYO3i`g4`s$)Qax=9j^FS%w# zRL_wjEKFH(B&E})lF>^I0#ifnCWRi#^Lb1aFl4^+AuXXBRp1wd)SpO3Gw;As(it%Z z0dRFD<vW2n;=qVGhWB~_3=+XO^Ed{<8T4on5a32w7%S9V8hdLM0Qg>OEa*m`g#c?c zMOb&laHSH)0+^T`>lB+}p=A$)*MAIa6n5$kXo-LF^a!)LhP`7=!L`|)DX=t$SHQ|{ zTZ76d7>0@)HAp2FS<p80X9RdPUX9j+3$kQxvYX<zyqs=vUrMZCQejHgFvds2qrvg; zY)tYzc=U-9DOQq^VuWvrG-1kZS&v}V6Z8F+RbM?lHhEwwHS~<rDkO>9ME8lwvrdNK zWFsw0EVb!KV&m)K>+D7sgBzk$my_!Pe{VjW5k;Q1%Vu>pYb;OKjU^62KI{*>gEsNz z0e%OS@mn=Br6VR6W+JBc4Lv!Z-eQ#K2_vq=|5(?mI_3nJB(BLd?ac<u!Ms-thm-aQ zm$Kw1JWwKuoAGA28D5R9@=bNSSd*!=o)0To&FVx&s>-sl?yVZ+gbYS)u<5mX-5~)# zQmLU!!Ri3%#W;4X*%Vt`yIpl&U1P74I1M(eMdf%txHgStXN@m*KHRmJ{bJDTkRmgD zI(}Z9mhHj>9b^JyQkQ7HSzWF+%ggyiIVD_<cYt0Z5VP#A+ndIwyQFv7V9@U195%ac z9DJNI#EFLq%8_!v#uwgz&$`6X0+(Hqc;HM21X2kW-gd9g&Vkx*oeUqd67liq$>?~} znUb50N68Y#a)nR0*{qkVb#+;+#yVfc@O(mqdQoh<=fjKMd27|Iy5qA>x6vnoW!UU= zyJWB6DdPf$8%pvzQ3~@5^iGK(;U?W6x4XQY4p!Ag_r>t_;C1V&HS6}y4muC}@3($5 z{J4A2X^+RiA)o+RcQb-tV1uBnCkw(MyY{xb9^IDn<#M;V>R!*ThUfj|g#4#gvvb@y zY##R;&F)~_FIhan!5@PU#xQ298%H#QS+D2S?HB8uc<pq$yf`1-=5NZ^?dxpO?KclP zA2i=Rd$;#N`&p+ooU|5$EyL%cydYq?oe|opro`v^tM;-vZ?F2e_KVH#=4$?8b-TLi zZ|7Bigui_ZoP*Z!S+m{m=FMuj=qx+9$~~|_H{sf%M#!LGgMrZ^79CcsXl|zG^Q&!j zy?d3vo)8@EjIyKd!{)o~KRkQ?>~W{j8g~|hUF&iHONL;RuUTrU$k-XqI&)aE>?}u& zs?F|lb$jvhoCLGo0t2dBoOTbhgU(^=tleRpm)&`DX`rZ07=YDx4Lh9Kgi(quu|xnq zMu~RI<@x39IqOs8do6(UwEJH6-Ol%#KWslc?GQ-VG%tJCgR9oXct?<)irEy^IGA=8 z&8oTRtVWCJYBRrDyx6{6-t5jV7t`#VrPIk_1wPqHuT6BQIGdd<yQ|i+w-^v#BLXu< zci?;CrlW<3E3i5TXyQYxYpvE7s~4*`*RST6M2=d6M~ip+-yMB_{6XWe(`!^GyY^M< zx_vo3@33qzT=mVq&q#$M$!XS{b*dqdHp{E!iy0WL&zB1}BKBsDi=)XQLCupc;oo-F zsyd6~RYNAv1|{o`xcoh49_9)h%cK_x-x%eCQnNWdT)terUfy18R^!F-_QTQlXWtwD zVDL$H)*N@1&5M)k)^+Q$bw1qmi0X}JeeGEispd@twL2RVh!n;xSY6Kv5R<{xBZPId zINTnNPiC!gpDhB3pv~0@IJKrNoOI$cBl51a1DUFfz7Q881hXi>49>Bp_-g)QdA^)( z+q);T_jiAo|6%!IdC=?T)Aok3cXM*pxM*#gth)^6eYD?jJTc<YstB7ApP5g0@a}eT zi&EN<Dma?8SEuvC^MlpV;%w6yk7lIQ3<(Z5SM3F~C+D&?ZjS~d);1?NmC2fObrsZ7 zP=omJb|LL=XXoJBT|S?GaQ=hE4;CM;pHDh$zgZ72#@EfO_C;fRvTiO3_*Fx)@<s!e zT=mI&#CfJ9(~)jeUC&rFVZ}3__lcsO&JLGHGgf?7eL~IEWHDnYdcev~YuYZF6Jo~W z)~GecVK!5fSP-^MrRHQ_$-r<mznI<5Zx>gyCBx!m`tkgS^Y>;y+CHt$hz>3$y9JZQ z_;S2!ZaS;R;$+Tj!)i1uN*xyeJCgx1{du*SU6xlfrh_Rf6NLVHtm2-{P7Ef_+bE=L zi`XnzOClqSSqf22kPw1OWDu}9sZ)*!UlE<cy1>lhWi|mv2IBO3c2ivudCidGr_&Fo zKde5OKdw$z1J;4oYtkdDtKwonnqLQAv<Z<mC#^xVJ8UzP_KFb^)FpxS3nrIcwXR6f zLh5=|d)}Cy&d*rk>`VtNG=m+ILr9SYR8={#?u-m!I_T0Sg<8bWSbr5c^%z;tb5^U$ z%h^qFJw2~x<zRkNJ*qyO{iyh8`izNvJfBUsEBH0LT%2dS*(O6NOc>{#aeHvuX}4SL zc5gHqxDt+$!K99X!pePtnKfVvZB>ma#zEBr+c8s}3tuai$>#)7iL$M*;4l!7x12DD z7R(vTzMZP2HAOZP#!N{n0836Y<fSC;PP)}W^-1|*^<nj>Jj7tg7{sK|O)o3rcr4Yh zM!F<MM(@W1mKpo)cDvtej9Nu^-dYcKqupprg25cV5ayiq5Cm~#Bopk9DR2(0QC6&5 zW58mHqhaS5h&*H2VF4ObW`K3d9?Q~gauGVy;bO#!GE>rSI!6FnvxC{A>S6WC^eM8# zw1N6_MVWbsD4zQ>iQ^I9!*i~PSd|@216t2BVw+1Ax;Fha>#15Vq(3+{6Dfmv;0Ts9 zKQY>A(gXi#e1a7-6Xv+_Na89uJ=*gTnR3q35;l#-#69?!G3%04cgouHqsb?eC!`3r zNDQ2^aANAqIYuILH&dF5yONf~W?AMXWfF4?0~7PfgcH`RHKmfjmn>~@HKs+12CLdb za4|6yLkUw!V;XkpPUrxLlUBACP%KRA_+j^J$KyN@n%8vR(oTi$cE2kLcBI)IlFa0e zWX$7io6<~otekI14qU=0qbW>6m~iYpBLa*XGgXol+!YBPGmJc=5L&n4g%XcUaX1rl zl23^jg5jS$CA}mGo1E)#hv-kl@aPAZK7~)X57^vO<QoGHvuVluF~;awamWJh0k}88 z$;3;HS9;~OWkEa=b&NrcNsuB%$HW?ZB0ZHEG+vTI=t2?R33(EDC&{nFKu>*O&4bIO zei7xNYbU;52|u4TzT=8tlB@M)0U&`tJ0XLk(3F@jv@o%XRR=I9vjDE5au_!zCy}0% zl5|YFOfd`abw@KXOpIrJ-N4=Gu|B5$u5y8Ugn=II1>qn_dZULcC6kNhOQ4vjkMMCN zKGa>~*Sl&utGI<ivCSn0J+Ws7>7?w9Sj5W6u_4_MTOHD4`i)4iA{&PkDf-ERsV*vG znyHh77*nf_pc=6;DImpNiRvTW#D0;J<lf<+X-9~m;womm3x^m@!NiH}Gs2lvB=F@J zh3r;ArZ7B$LCJBJEp!{;5j05ttGz<yXvUFjfrdaZMwl;6((6x0XJjGuNQN@!fzg+n zox<W9RwPS~jv#8MRI9N`=1k#`1vk{CuO#>8UWgKlC^MeTk0LAz^+1_yu70fduyVwJ z7}JbNN35&4mQB(=NinW^X$YXOSr{>Z@77JaD;MqrO@=4614E`H_Ap6TD_01>5s>44 zuh(ifT4()6tKIA4EffU?;%qrzEl8W^dAUTZIk&SfW;_{=`h&hJE`$vrl&eP=Bd)DD zD3g+7GZ1QsnHB|5L3g%0(HEAvn7o@+uVD5gfqP7xoz;B3$F#ybLwTZsxh5<<F_*(! z`(aQz#S`F<te3%Z$Xo-`&Cpl`=8-iV*9Fr@NfssZbf?{Gb=sq@@;^08V!B`&T#?d6 zS{mtaE|3pNbs$@5IA%S;#)Pj>csXi$(4M;w7RV9na`=#<#Lz0)^uVOjWi^xo5Rk=! zX`H<`EKZuYhY<4^V-<Pf2OAwhY8mv<CX}uU08vxkXvd``_ZThUn$Z}1ZITA?-kEv( zS${MjIKb@PWgo@B2_HiYX)n;^!^LnpTn$-pb!;<sn7@@MX7`8{A#^q)N@nR2bi%`D zjIx&3#|$)gkDW(E83v%{k|Ts9%CQ>EhPv>M8`qG{)+N1(>KJ**9Yq~P;0r9SWH!^A zv`EI#H-|?x?4cYTa$P&6b_7e-R3W%GS`0`~!fhhY4bbpm!bF9D6WBp(V3n9hsA$1! zl@To{*sMRgahJ8;(nUoC(>Btw4Lct&9NXAeAdOtm+sbQ&55?LeD_q0jV2J3Nj{>GC zBBPCuDV_{!*^`v7!Fi`hR4+cI$9%Xatjp3MHmW(7^eRn>PT;PVJ1HZU6a1RNXh1t7 zTFVp3U{T6k!n}$kLWa457I{vqG#N8W=$s1MWisLS44R?H(v*W@H2c#@UBwt9o#8iz zC1`}1nj^_BR-|GDA6?kiqr%&RLZR^8rPhH$W_zk9JbuMI)yOTqH!aY+6jDTHRDCW` z5@}13^rT$yCh;4@DTT=u>LCloy=lkGjBv0w$4%^&1M*Jc;69ue6I_bOp~W2bu0E%z zpaCkzno}$-GHqz+Fv@_->NoH4HjoLI`Xr|!Z-`jYaN$*rfx4kZa8Cb@Hh8VZP*pO< zkSA0MT02dW-8%Oc{H#Y!t}u(_yWz`-0U8`i%e9Fj0n29%DW`R;MW$(#iFD?_o_v$r z2EO2`?n)D`P&FDDm}t&=*_4*k0;-2Gs83n^FvBz92}BJ!IEU<_F^t047keX~V49@T z(Vl33M<J<XEXzDLc<gd7lW(7c4?6B#1U57So{N!EN0?6MKqLytZ?>0yRSJyupy8P8 zu>7%d+`g}=;lTlzO33Yy^)Tj9Xq}df6TB1%PUc-q)PbK7BurM~v1A+}za()1t(W1L zdN2cg@fabOX^@aa8Rs%^qg50!)N>faGisZ)W1cm0BcGr#i`pK5z=AXI$WA6ZRlGt5 zQcsi16_k@^P!xG_H%xK{)Z!s0Nq@13ov?E8+G8eAy=h_G1zcDPWa%WB#BK3OkukJ7 zTy*?_58R7<Z>dh29knbiPQl6Grq6s2e6){rOuuTEa7+TNlRNHl8^CZG94e$=)MrQx z^k6bh_3z+-0W%spGbCS;OVYx4Nf2C0Pt^2VU@2Z{#H3LZu*618)0N;VGO4g~=ateP zzI;ynp#VkhpgCs0dWEz}!H_tixX6#a6R)OGgn1_*u0$6oQeQR1yO99yOcV*5I|xWr zq+_H$hRh^5iBE~TN<+HfDU@P=G#CkqsRq50`UFP8`7*I1<_QTOI3>AKA{fhD&QQt3 z)VYEPtc)bA7*XJ*Cw)gL<r}KYLun50zNAw?OPKksZjTQjp*g4@uy_u<5~PrY8d42G zth|UnVDh?{0onm4X-)cehn4h(dUujQp@dV^_PK_bW}Dz!;#^<^R$ulm^jk<2l9H%| z^xV-VAW3h&OCtzT`BK}eM-%^3FV(>4qB~hq0s@Y7PQ8-iZ%I-zNba1oq?3c%_hd4- zCLA$|OWd^mH2+X3{=npnF$JZGpr+h*LUF{-F!@Q~(*D(|!qCTR@^Y?CIEl@jL?X0l z;{1|Afr~S&m#!j80FlEQVU(GDl}tv1LgZq(mIuG?eq>U_E>0XL784gnDH4GeNQ4QW z=1z)Izkn$&PUXpK6CZewKao0xrK0VnoJWKr6a_7+$&$~&v$HYtF;GM_U9wHN)%Z*G z(;N(q?;;&mrHqBNQ_LpwBKtkCxtmHuEx~+&c$s1bHdIQDN*_3J2MJR{n+Ir8CI|<= zl1C~t0S!)I#n_cRtZ=3aCn9~HOnfm<4`;AofJ{I}`Ide&Jd#L)dO!m?9-1$9HXaWA zP)JhaS06!~B+?8Ehm`fiA~kh~W%|lNt)fgqDH80P;1a68xY5=Kgiu^H6}K1!DlXrt z7;H%x=&e+Ox9O7xxb^mfI@(c*M-M`+)cx^QO8RR+bx=p^ws6<yRHhblXM(`L?t>Ga z7=0=1sr@KZW1psxd$=Zqfq(z|y(+(>A%*G^Y49d|(s>nMVqnYBH_Z%<-{69$H^uZF z#(ThVdUb+TR>SZSkD42MT%<;tHh(QWYf8pzP0N~ALfro%PC+s0;|XPV(=hO(oTgnp zDT!Cjp_<Ay-1-+~q^7iV+S6J5qjZ|B6Qo)~l$;oS(Qj$(fACmLYRc`QCpqT(sGHR9 zmoRK+|A_uZ>ouGj#~O-l)G~A*Pw5bkkQLjh`6I08UvM>y_>>ZxI1@Ci@e4Y>-A0XP zU>KhQR*hdRYhRUuU*$#3fjyn?m#KSELw8aOUfWVoA$nMwGxclqx5hO3ER>MI7|-`d zwV2j2R?BRSleO<ZqTga(mx%YZ9+BJTS=N{4z3yV<O*vjP-0vI#UUiYx7d0*94^v+0 zG>Kusg43YipAY(^4v=L^znwUTOjKhd3CWGajaTED1B=E*RMECPF{wo&BIxU*AYVM+ zDQX5enw}3#8JaH>wq)WU5i!)ja_1Lta6@zg-4KB}ZssPhn$==V8kzv1sfM0COh%g9 zwcN}`_NHcturxsM4k@g$PO7&K8mSU&EYUz`UKMNlo#qF9UE>dQ^G?R@k|&{%Ns3Ig zw2=|uo8Z`vdKGw2$gyV9qnB=@7@2FTW6t))iCY+Pqji#5uyI~-CL*{rLW&H^jGZc- zoah59H_Wk`siamgp+jpX94a{muxdsG4NDfdE9O*|gwh<tAwtZ925_cWON925%}<AD zDpA{hOH8?<sfa|79s>48XQ^T+A*ItxnF))y2Hg0EM!0PldAeHile2}8E;G2@5;rwY z!~DzPXb_HN0)ik!elgs1k(B@|(YCUM-01>J?-bog!7WjwhZ^JH4_!r)FU8$jV2>5T zCli&x8;%klG^TGlOKR4{9-_AdRLMYQ_bTg5xqQ`72CS6PGSOJOGqjQgps<5p5_(Yd z7$8>yeAY6Q?9TMiJd8`9wI;>NZlxeVvOTo1k|H%qlnj}ayhnqumL(`eV@%X)Fz64B zA|v4`FF7S8VS7eOksCjjY`L9t$V)o2C+!xuDNLZgJMalvvx1rSJF}56AxOrAJCxYG z>0S=wg<4b)4+D74DH=<+sJY-|JQQ5qam>a>()Ud|H%{eVYlfJ@N<5AQIwEq!!5j<D zZBV>o|IDJl84$G`vR{p;5mAzYwK_7C&gN(H(>ZA$^B%bztPryQf*r#!BE^i5mWVbf z?MgOLPj)T3A9_PXYzEgykb`}M?j0v<ii|B5YsOI6Wgj4=tP3$RVw}`x@*+HN)YFyl z^NBpKD9h#vcHkO)JDae_htp(QeRkQ9w_m!)X1pA)5DkOi#0k+@kZl1vNe8(1WL2>+ zmxd{kIB+$)p)+cYnyf@O*c99)U>?gbb-c}BEeclwR$`TKXq+-3pn0MsibW}dltz`@ zt4-X_ecGsF1dj4@KcZ)71A=aDN_S)qxD+1wsWpktLc5}`)P0W1r2ptMB-VRWz%{#* zt?YQ#8Q?;Q<5F6IhL%B%+(pQZaNqd?NvcIQ6=%rC!(otEC$Iov@SAX1(!Lpv3@dC# zwO!~SRW7g&oXz1(fhN|43{aX4nu2n)fu;~Jm@=x<vzEET(Qz-y9A7DH$7?7At@JOi zFRc#c!iT7aWp9V&Nmvll%1et)JDg2C=|BU8-vCTllG<w#fJz(I2t#@Hr}JrTnT0zW z&taj-^V+U4<G2pSNO}8_B!cIpx4$eYL~XR1KbF&qvwd_|YS)Zuj}}XVf@eb3e2$Z^ zJzX`OmCH87^OUq@piwNtGC?b*D{zcJVod=jv<RxD05$K``?k;6y`xjmYTt7`hp;GX zEbILqp55#D)$9XeHTIBI%|;6NX}cSQ^Eew$2;<*VdpZ3QNS=~<HvoVtog$DJeRlyx zE*XrJOY&&n?<7r7DeRTNP#LeUQ05_Z?g%0q3|fg}3)~Jk*2SZSTzMT($Kz`}bKT=> zJSJ!Z&X)B^g}b8^Qop56>~isRV=;6Kq>83Ve`^fuegvhZW<B)p#vi@+5!{xN!gPO+ z2V8h6v&YCi<K*A8uAO?VlJoEjkJu4IPlt~a(%<IZec1^)H&iL5Ef<^3Y7T}SU61yV z&=_%VKksF??z?SE$>4Uqg6%OtVz~i3avbQ7XBr#iG%%e)(|n5lN}HgJymQ1TvC=!d zy1=n+Iq<1JsYb1HA4@)_qb_EwoUw*Z_TK`9*BhU4oYeCII+8xrBNCHiZOl#BEjdVv z!-F{8I4j8%?H3&QM^Y-=lhG@dNtPwSXM<XpO6U{Dw2J{#Fk~jtx<li$j+*dM@Ki`i zlk)~y=GlmY){;&bs!1fx1Yn7jNsr`D6x)E!=Nwg*v1OQz)?F5aTx(~{m<zzk5S-iq zO)Dz1EXL+!Z5>A{w2n1&al{Z!){R(B)2=6MA{P0ST4p6V4L&AoyI!H5&zPN3=rDCy z#GF&blWjLxQ7lM$T#BdAd$Mh}AX}k08nN2Xf-i?_nRu1L;vR^ScHChhve*rl(1=aq zXn(*lq9<CBi~(?@VYfqo6E^-tses=FlPR=xUQ5%FQxc&n@HMU8O(y&&%<M>FyGmU{ z*o*{t$-k$QdsfFW*(-;h|G-HSp61T-CCh&7p39U8O#}~p$<o4vW1AzLpsb4}*wJpW z9w+Nx3xiUO9sC9lb|KRR{wJR)!jwra=usHgPD(++c#-8k_>QzO#(`<so?@NDJ@2k? zRFL0^`DQ{o{7&O$MSAqz7HMej$Wi;zy30iHKzGCOZTu{?9Q>mk8lN83`qV?$;JSvW zxH>;O(T5CZq<~h9A5)_|_X1iy@3Bw{?FZxpouVy|IPsrt@bq6d$mM3zI_(h|Hta=& zqOjFb?jl*jRPQd00D7Ndor$ATR`Ultp&sdk&eL)g_<>~#x2i3{SN<z0^u(dXE7#p4 zF~@uwa(&1&VN*6{PX|NR1HQ~gVL=pnCF(;jbbGopfq7vbN0zir3Q6CsQ9|diH|U-A zAU2g=po9v&1Q*hB9ToyIp^ss?m?g#6(Iw-<C=Go1m}DW)R&-;|(w3;_HFr>Y2ahkc z9-Xut)Mf0u0A^`s59BMLCo3FDXJms(8Vo>_)_1Z2EwseYXZl5y44VcrY6T&+waKde zMGx;;O3G^(c8U0R15#H<5yDPXYiMDbIapt~M|!_{OF{AQlWMaP6WIMJo;^U03*wy) zc?T)%d`_MtAJ(&m6fMSEGGW0$L=q`w3vcNhtpkXQ6?#sjyJn}whOYM5DBIw4E`1o! zqVu%DSOHI~eQ8pW1g;}1<3T1FCd)je<$@F7t|7BG7m2Hh(0d~TkL8yI-de#^)|@=K zeS+-ZHL2D_^nkYIoU>o%Kc$UF`S<b<fZ**ZWabW|XG7iROz0=zGNm<*hbhd_q~Tx? zN^0h**u6o@Px3#@e?f^y`FHa75NI)Rhsln%yO=EpJbR-EZzH1-XSghGloGdrknLsz zXdK>x)t^xFasE&9cj@6gsTfW|q@@$ixARsdo?^in6x7m?=}|Ks;ZG}W^7Y7XO;Kz9 zGj1YtL!EyNUTw;0{&`klM&Eu2X0tTyYlM1}kz-(a^BQl{pxX1658iykzAy4G@_z}< zPPyrXuvEeu&f<H+yGQBv4H~x^6W&Bclg*p3XvTS4oPe#7?!cRKX4${U{|MUth(3DT zjxi<Q0KMSm36J@P^g3X9GGPw(-q56=?ghA2VDOw8VibYnP5yIkA#g=4Zy@6hXQs%= zPoT*ey(G~POiE;ibJh6mIrY0h-2r>cFB-lBepfjtDO))CXSBt6xBP3qFTm^#znyS- zvPy@NFKExalvB*UQ(mtU)iUUB4Q?57hua0ve#xg}dZm%wN;JEozYWTHTLrm!4J?Oa zAC3t9oEoZ%KIJ*;2v{l;>3f_1jJ}QO#X<UQM$6aqw>D?JRS_r9(4#JW<u(+&582iD z)Ew6kabzj!m+<s8-13$;N>0u<7yK4V&a*@VT8yVN>RT%eL2qB8Del9OxrCmdgH0a_ zh_&3mpscP?n3LWtLAmk-J4ccdBpx~ix?fU2SYc+KgV~Pn92*+;o+qcNZJUNfEh%13 zHM$(WIx=-ubfUsRaiSLD1=#KQ-TIC``yoXv7o*WhMuq6a<hT|)<yF-ms#4=bJ+y-2 z*TA%O$1=B%;afF~@OnHOei4E<5mCo!*vJRnC<8N6Ehc!i&2_@eHteaG>7;mxnf*$A z!C6mdHj>Rd<%A7P(Sk!=h?x<a9Fh2QI&CjUB&Kf{Yoa4L$=(E>Fg49l<1mmRCs&YL zh#^Qom2^fE08P0oNQ@#4m`VFC=STyDrN23ceB5GA#-ba!G-z~A6~&~+AYc~owjvV@ z17-rkVi+4n+ptkVhdGL9paRTi#wa`8I6ar+!wmCc<YRD}HCV7mkUStFNSva|d<QjI zg~Vj$tY<AHv=~?rSrim4n;+soH-jn8u`;~f0*9V3X!)MxYA_{hg<b(n+GRKgNDufj z5fU9+(0Qy3x{2&yR%n`WGBC11nm?3L*6;}kD=8)EMlghQ%<mw_FhO)1DeI?13W>>q z6o8z0ne8;(OuqJ{L~nnAOxhHN7l(o{D?&f|#R;Eq5kuPy1j;e8U50f5l|CAS^>WT* zvj_9#C0J=q(-#6Q&bLSipOh2j3i?Ae%*WK0bk;~vva~08#qi<OM<|A~ObE)&+i(n; z+m^u_9VBHI7~ljf0>hX?5De<vnas|-*cQo%<!qyrGQY8laLvptGolzh<S)A0QfJ0} zJS5-&+YyZyA+2d2n}BzWB1Hwv7@fEzYm8Txz>-;O*vP<{P|Xw^vctu*64SDcrl&#$ zH%$_tciK6^<07y{NRVKaQ9@wH(zoW3<0=`1U}g>;AzU$YfdYOcJTwoHB6J%G^X9E- z`gtfuGHz)Pv4Gx=cwC^Afyl^Cr%vkL88Fuz7aub()8Y()>=c3rQnDXhV3E&KP1*yA z{3uNJl#>K3u?pQ#o3+$r-(k9dqUOEJ7NpPW&Is}XiO8hei4r8>CJAw@QA#PV4O!;r zbP}Z+Txh;ZJq>4dm%E7!JOJtLRy9CE!qjDGYTUOlMm>?+xjuq9a#lH}cTiLRFNH!4 zEuyi~1ys+MG_;wb_2gJ^G8g%}KuIS|n&M3C)?;>3)+PqD@`e1cd{qrN8o$3LU6te7 z2vcU{cXG&VI((BYA)MWVBS;87ZT~QGp0EL5pxoK^+R{Xgi8YKk&3(vzLvnV@;eZ_S z=VNw!aK|C`uyRz=2re$FZFN=MRJT0ui0tR!WLG?-OTlp?6Psn(W3d~bk9XrW>Ejs+ zsKJ>vN9++WlI9WKY*|+Aq_Dh%x!W&}a5KgcP$wrNG)w^;68vfQa<Ze#$Jxi(dt4t* zo^k?Cm-aLr&zbt)a6In|b{iDz`8na};A4(@L!~De8k`qQzuswR35Fhl%c0Fo#xb2a z;TX3HBS#}NCPEE)#Uy@JWQ|edOQFzqOaQO=pJTK?!nk@j-Sv&h6ERQ_JS6;*YfXD? z43cLUINqDjgKTe6I1I%!yj<w(10xK6%~E5EVcr-WO(~UpRtJcUGl!pi4NuAEc&BL# zCDI|#IVRNy`S<f5U?6%w#z-BE_{NrU6IwjvH}527El&1?Mwp`rq65&$+acqmyUSld z+jFSdOp!bfv(;cpiJSMlF8!~-OSLGLnlSjRP-%wBj{DtJm_Sd!?E_6$sKZuqRIspO zxFzow-e^XzF->0bzX6_Rk!F=@Kw8LKkLmWFVMF@ioeDJ>l^J86#_F65lA7hyo?=ok z+^6J^(i0D)zNVIr&4(D?kHL`*XFytmzr9frOlV)TRAX>UjaSss*)paZ5t^D8;jVbM zG4JVj45iP&L^I9rG@De~Azw8%b=~+Ze)TU)B-cAyddI?N-YXH4ROiFEZJZ`yU5zxI z932MlVCdZtSI|jS8VBe_Z-sj2U-Qn%Qo2TbNe*aIX>f&=BHcprYW#c^hTqp5mKxsu zv7n!i(?R3j3oxdM$S4txQ-sk0PpI*hBjTsoDt<bPs$$~_KB9#%73H^Pqbl9&&VEYe z{4IWuIyIKkuYL&2?<+72s*K^uBQW)Dmf^zMu8?fqcI1ye@V=H?YI=`MG1u@_wd*zq zjchk)WIMi(NmWDHHp9gh@0fIgeu0~E6=UVjkmXZM+EVD8f87-wDetc69WvEY_RQ9! zUAZORZgtn8)O2@e5(gJD5h=Zx-xzS1Z>hB~p#2oM1-3d1A>~eY0WmOP*n7LBu~Rsj z+T8~eEMAt}oQAaVj3E(+W}g?%7$#V&;CKk8eoYeQG=?hG_?N=5ewP+ziM>G^3xw8i zicEd-$v^qQ<Uy^(Kbrz)sn0E6;GG(^f=b>@!24RZ(OWaPbxs-No+#&pb$O2gaF3dq zBC)N&eP|lN99Bx)?;NVBqdoW$w~kii7+j=f-I}`?8#GblX!v;dMhr#pO9yX27cj*L zN?{ogH%lO#Bp}gg&+gK!<U|$>VZ#hL<HQh3hyLp@`3+HCLO_djBaJ0<EjPqrE}W%3 zbR3$(1`^D8=4rN_W4R6agpmwH)3~K9_iiFOtE6(%4vvBCVZANc^)*Mp%x=VRO{t)o zYv977I!o)!^K3mK-4bcRp%9WLihWCr<rOIhkF=nQ4TpcFm7k85bpK+>ksicV*`QvG zE{7K>k)}QD4SOTEsGx^2c?K+gdwP`miqsl*E@D!<$%N!oeFnB58IR+USj@bb4Azl* zz=j-*$f<^8wzABQ`6*|#TwsRwq9ms(1{RCW)CZadI&0y4hsYs8sSLuWEF!rK7wu?B z5DUu`@W&82fCHGn6kwEjVNRD7x0zv7wLE?pTC<KpR69qeSl2dt0xoc0D0bHL5Ct}M z!544m;i%HOFtnxden(a8VSEf5Vp;(!sH07hOf}3{dC+AHwK00BO)Q_n`i;m?T1L=Z z5;NLhs~NZ&TEvP=Mzk?gkVq!HdeeVh9-8A7-m^h8#$+-89hQ8NesnOR2Kb6*90UqB z0-zCOM<jlt4(|kKxBMQ`rtf_fo)1J)+sr%18g3b)t~TsboB@)V4`bf!8^#tcDfWgu zqc5=3-T*@|;FlnSyM&W}N-k|D6WCrF<E30VF_8jPgA$4arO@M0m+=f-;>ao94bl)F zSctbPV->j%xQ$3;TDd@gz$lGgK!gN^kSU^IfL5T73?+X>!HFU$gRp$0bIEN=!UlFW zgy18FbVNx_B>`hWMFPHnkmcx~pX|4g7<0Pgt}@kBmZtQIs!}+FhKwW5aieEQ8dY^O z>@pAmlJvDcMZyw%Q~RQHr>O#xI4Lr;na-Y)x;DUN=2QrU&PnQZ@*4yi0&FP+GsG+P zgl1Bg$l?|!btJN_vo3cM;X%PQ3TnXy`m40kz6kP@#DU*@kndtEQN>Ykl9s}h2o1oS z^vN=*KMDB++|qPJcmhxFL5|ka7ZOaV8X>~)=<buGiHE#FnTsKuv;Yqpxzp`nV+Vq@ z0p1e+I~=J15>$>piZGuP5qm%}oAzURpLheN_7*MyMz#`(fDQnnEtw&78#P!HXek?T znu_TrcYLB1yXS+Nj?TMhKG9n0NVG#mzVd5FH+Ty_(O3!-^_B&sL~@iMSa7RMrUy(K zDsXQeGx6Xv=>~E?bvAT_Xr|0JS{7A5Ns5F>YQwkjplQGHN8j?nK7RS&8yc%0@k=U| z>O{S$SwpdG)Y#Xn>)(G3C{Zg~uYdhb_1-R3m#$y;weI8pmEP8c)2kTlYrXZ;-}u8{ zSHH$1%G96!rl760uT#I<)BfKLr>+wvYfRt%R>}X>hh;)w-u`u8cmM8f=$2RD@8j;L zn$l74zT6}Yol01zZtv~ZYkcZfEhR+Z>|xnXJOWD85^B`5M%1j!)xY@pKJ5GN_rF-W zZaKcKdv*U))U6@J>%LrF@_vgoHZ^Y6i5j*TIQm<BU)S`T`;Wjabh%~sm_|)&$G7!w z)(-sMhI9XunCu?`!|$TreRy@L8q+XM_GRPu`|U-^`kVW(-sXOde~pQ>*<%uL?|<WO zHT~*eeXaTTHcmCPeaU?f(svj|&h)%jR1><X1uCk%y;R`QK$=><#4cd^ta48mdOyYo zUs_-DNrcep7!a{{)Xu1|ZCPFK!&agTG#)*)R6Wo5K3x_{-&!ppTkG%iA;A$5CXRF^ zdnOf4W`y`?_0WF63m|vUof@1_qCWGH=3@#Hau*+0nIfoS(1(x)s~e<EKsjnLzolq@ z0$@u*O#M#1OlcA+AYlk7?PJAY>fv^K|2-jvEAPEhbtaRMN(o&^R}Fy&C1RD7s4>P4 zZaQw<4eKXUO1NH|v9ix?P~Ghdc3tLIR}T%u5eB5Lt)?&xJhUII&EvesYNEk2*21`t z-4*cgya_Unvy*~*eSjsYBK#BJ!zJ;~tgH|!Gs);4Kr^5br8aTVGby1L&tmBPs%~&L z+>GGOw_~-IpdXMM7z*YK7^63u?SaAuZsZLKQAkY+TR{JChqc7Gk5yryPMHD=Qq-P+ zpTRLhp9UZeu`CR!AzrNV;ABEU=%Hh+EV8Oi4|KP2r=X&@h9|%ZWh%m`15)2kuspb9 zD}3REmQ=&k5{-K$g<zis6~8HdnzE)9^8>J?NCCXpw9XC7sWs+iBMma&GVWc_#OyJH zVL+J@Y7vo7t9Pbyc`nH^nbICig1wdaYXa{S{#_G3@I*#34hSfpruNTBeKT;NbLhqZ z@2$uR0&--?!GNd^iIS^<jttX%d@NT;xMSf90v6uabCD93G@%;=I~=&)WCuf<U_x4% z)s5CpgL~4-1Ws1~O<>ZD!-fM31E@Qs-%#9uuz=y@HuuqCjBzizKEvNzpu4t$%gHLJ zhYL#sxvuBoilw%x5+aVSi*t8UzTfb<Av{j#Xz^Z7bI=lX%m`QRkY_ldMd_NIXGCy= zAbY@n1%d{!44`oTQGVejI7kCBX1Y(p$dMxaG#;Rk0XTJGq_`65NWGQ%O0*In-w7zQ z2CAdEg<G)>Z-|o~rW|#w<uqyyR2lX(C>dERH5KKIAs}&AQ^XPjk`Bb2y41im7gIG2 zGN<IF9(w|UTEW1dBg6#*wTmGdd807$$5VxVFt|q+40uQjS7*~XtH7|YYGZYop-B4; zHk41}J3RxJODz`my)tMR-G75m6Iaa=HewZ;%Dx&dR42(XA*w?IYSK1vK`JIB>4D>0 zP38D81g{QAy#|Kd5j!CX(J>vt4k_rra3f$D-U@&dHCd?xPce6)Bl_#;Gb<_j95fYr zcrcOoaJSBvphu8SJ#L$}BCgWiOa?O!q@hWfVl9>~Y6#J=yXn0#Zp1X;8cMHC80l)8 zuH+dIG-#@Zv34Z52{&-{SsI9kvY51Nf+^fs)Zj<zJK7?E>8m5k#U?Ld2CmUYEL_MT zdsM@eqQW$&EA4z(Ldo@8d8ek3$M#={(lo7kCVZW(<bmx6o_m;gGJgkEnDt1|gb-$= zI!5w!tfY!(4bxdA_$`*P0&4H2sSojtp2Vu^S7Fu=qwh7f?qeB!4?GjdufY5&p1~<8 zDvhOfaNw_lDLn(rnsegm2vUnUNB?zr+Vh%6dpvEmhUr|dcKmlyGdLt)@58)L&476y z%U=gGdLCSVo9BV&o<ku?;<<;Z9PN9)m!-F<Sz}qRdq@2k-*Vkc%zsnQLq7L%bYF@B zrZ^>y3<m?xJ>E5cly>>E=g?QNw8z5pit$pz41HZw_dZ17#pwKXSOz=;+xO)?pzf{Q zkW2N1*<NqU(f#owuf<JxLcEWq@*aH;{TvchL#(lk@u;N7c-upaUjOO{3NEQN<h5f( zeG!^NNe}#lS8JL*4MQ?R9|d>StZ~KY7G_*Rj*;k`o9yxgb1&zi!|HKTkDeM{4b}Ny zZR_PiRecF_Z`q5hkV7YhOotTKJQkic{`za*I>;t`g9q|fZ6DX3)1m41weCaTf9tzn zeFP_d)ALu$*D&rwt;@uB^{3zTTDKH(Rey@ty3T#*UwvPfscYV+LeQZu@zt99&vgl3 zYdF5{AMxq!@6;plzb{wA+<z1A_E7KtV(GUZ`*Qntmayb~F8V%dTdwYNyhhDE+_&rc z=~ruhwa(kM>pFGKx2g0trtw>i^S)+aA1(j7`ui4Zn$~c&K-GO_e`~!ui-cWZ+*vzb zo6wX4ZSt_e>*aLkxZqOQTzVspK=p)=z%QujOr}4lz6sk|+bHZ)ZAkGEzODLwa6ml} z)?vW2Z}K6qk9W3HbFZ$Mn&-)Kzgjwe-P5}+y{F$EW|Xp*;#Xh7j54+udMWxD7}nIS zWhY7tYtN~W&bnm0ipd@?O9xJMsagi>Qgw-aox0@x*L`o^ZasQb;}~=j;;;6ohE}&9 zqc@)GUPXUuOoHzAC%9nQw_8s?rC&qO22Ja4qpZ&XF*s1SUvqZPhbU$F_$??D+=!kD zc@JyP3BRxVw;%0V*-@shQ@>i?hjrpn*Ya~+>i%m$4=#j#s&#xH!~37&eMo*l4vRnR zyM2$H{k;ONbPAf+rTr`{A7ZobW%}yNrzmBK`_ksm!O<vK_o4n2ulJwp9@pRPYeehy z_xE3G4EH6X)}9_wI(~oqxBK7iVZPn!{T}Sg+<*70E!Xt_DtteG^}G9!YFgj_F5XA2 z``_1C))4PYQ(f-u-+mKxA=RzaHUD;BYg~Ul)WEWS-j{hB`xt=%sU9uLZjHUAW5kC( zt)KVfzlI#OYr9W<9HXFy=I=3&IKPDLTwC>8rM0e(ceTg5_mG{X^gF_a6hCJ!?O*p> zaQ6=)o<?Jh2ZVHmr>6z1cVkS!eN1;pc_&CQYXpXJ+W3H0x~X|?ly}dJk(T{T=jhi` z*So2AM%56*D+)azRm9Q#Cwdd^u;>Ae<RIp3qa&de)DjWRcK$W)B$lR%rUS?CdMmV= zZ^BHsrUcX2;>{Ts2~TSW{hg~L^}~H(+6Tgn&FjW0@9U<}KgOs%0D$ezK;h2C#Esef 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 S5AvJ8{|DdskND>8w*Nm^KnY*~ literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/unsupported-extension.xx b/Frameworks/TagLib/taglib/tests/data/unsupported-extension.xx new file mode 100644 index 0000000000000000000000000000000000000000..65f57c2ee985713476ac0b6e3483e6fe472e2176 GIT binary patch literal 256 LcmZQz7})>-0RR92 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/unsynch.id3 b/Frameworks/TagLib/taglib/tests/data/unsynch.id3 new file mode 100644 index 0000000000000000000000000000000000000000..cfe6ee1a6f13da6901627a4543f7849dc2d99ed9 GIT binary patch literal 320 zcmeZtF=l3HU|?W02=NRtVqjn}1+xGBXJGJUsAN!JNMc9?;#43ji=mXEn4tv7N(PD* z0mX`e>@<daAX|YUmmxJIz||0_UlOX{k0FyG52#XsAsEQY1**#fs&Vvj0;&;(sxf3R z076xu8ZV&ADu$|%AZKr&JUdk0fWbJ#$JGxg!tVfal@Ws>gBe(t!T;YAco_IU9AokT z$}`9?cnSb1W(H0j5I2rd^#G9ij|`svU-M@HkGx&2`|172;{QKAZewCMWnwU>eR}Y_ dH>%vO**}Anyl%v>9odbd=d!cx(>YD)697C+bpQYW literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/w000.mp3 b/Frameworks/TagLib/taglib/tests/data/w000.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..f9c2261764526ff40aba618a1e9b9933acada5fd GIT binary patch literal 512 zcmdUqQA@)x6ore5`rwm4K<|s750-TE!M*9)BBSeCW@Pvz=}fz@G#PD5?Z5XFd=USE z2R@Q>a&K<3h^EILbb1qJX^PDTNK<)vV{1#9(F5I6-}nAu3@p6AwKcLxwXRV`=ae;M zOL9BaQmM2gtmDzvp%+~{RVXJveLl~}0Vg1sJn}GG{kYrd9gkNb+$H!mI5SISBwz3e z@(Yj{+bdJiyX|-yM@Y^9V-IXL<9QswT!UBHRLate&~^}pN}sr>8$pSbMi`#I%~5m? z*0v~=X*Y~9xGT@o-Cnf578>b(c#>bDK4>}q_ygkWFhSiB2>A1IyyefUko+%w1F}q5 ADgXcg literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/xing.mp3 b/Frameworks/TagLib/taglib/tests/data/xing.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0c880151b6b60b5f5ac4823a064320710b687449 GIT binary patch literal 8208 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 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/zero-length-mdat.m4a b/Frameworks/TagLib/taglib/tests/data/zero-length-mdat.m4a new file mode 100644 index 0000000000000000000000000000000000000000..578d2ef7a8b000addc1e6d5ad9a6b12211f840b9 GIT binary patch literal 4517 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 HgBSiA+4#i{ literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/zero-size-chunk.wav b/Frameworks/TagLib/taglib/tests/data/zero-size-chunk.wav new file mode 100644 index 0000000000000000000000000000000000000000..8517e797dc803bfbcea502588f70d68e643a6aad GIT binary patch literal 1024 zcmWIYbaUfiU|<M$40BD(Em05vvKbke7+lscFbHg9VqjqqU`R<UNdyXyg3%Bd4S``6 F0sw5+2R;A* literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/zero-sized-padding.flac b/Frameworks/TagLib/taglib/tests/data/zero-sized-padding.flac new file mode 100644 index 0000000000000000000000000000000000000000..86ab8bf7b354855713a5352887c4791dbe2310b0 GIT binary patch literal 4692 zcmeI$&nv@m9LMp`Y<_HvubCO<*R}(bkn*EQCW~4jJIwN<(I(Bo33E^jDct?I;pCvW zNg9QNmfWP=+>`?nJDKmRm;b`&U7tQZ`g~5$<2&pPb_*fwCSg*qTp<+0wfjw^Ene=O zbbW_kR(7wRl!kpn`@Bvl)#?|aQIAa#!q?~X_H_8=$O5u}EFcTW0<wTCAPdL>vVbfg z3&;Ypz&|YzQ+EWZ<gislurW}V3hR(&hFVKOX(1{eg0vG<jRNWBsk=caeU=J1p^PXs z8HX~LsLuqHHAL06L3$r`?u87a)anD29iU7HP)<Mf^$Zz%so)lr+eP_}P@adnUx1WS zYU&lrw^469kg1Tm)j|bWuc$LN2bnXevli&@qo{L37Cqn0k5G}ilEEw=K~@JfG7c3x zse}tEsiQuQp;8wWx`Aw+RBQz*Yo#pjP<bO&AAu^Gsnc?(a)bKuKvkR6bQ5G>r3MaD N$*9$#yV$1vJ-_48A29#` literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/zerodiv.ape b/Frameworks/TagLib/taglib/tests/data/zerodiv.ape new file mode 100644 index 0000000000000000000000000000000000000000..683bc2ddb5ae4f932fd7554664421bd0134f871e GIT binary patch literal 946 zcmV;j15Nx*K|>&x00960001BW0001#000220002kRAT@D000000001iN0A!y@L(fn z1FU6N?rMC{2LJ#7AOQfNG5`Q206+i`00IC+tN;KE0RR9H4gvsT-~<51$p-+VsR{s- zij4rzNe}?G9}@um{uKbw(-;7Lp%VZJXC46PH6Z}&6eIwKtR?`4NGSlc5i9^d=q>>8 zg)snlwKD*2CN==G`Zxfn+B*O$yFLK?Zb1MVG(-T(7DoUL^GN_yy-Wy#iBABVR#E^k zO;rHm?N<PPgjxXOF<k(CDPRB{^kM)~Nk&Gb%Ax>QMOH;-ZFC?I0000100IC+tN;KI zu|fa@01yCVVRT`D%Ax?3!cHIFm<<4a!=@`~=9M8_a>)?-3o?rrEy&ZL%zk)>+(y7{ zti6@zFo%OMvExu<c?#g_3~Oq;@f9bSKo4mG?WDdQn+t(|Lmb}wvtlChphNu9r<~Vd z!-p6e0e5^7zUYsZsJK&nyc}ZVE#eXiPBoWxeVJ)8Ll?G=9a_5E>M@b9ZWskn=Nx)d zp{lxNl;FO<I@j6_XJ4P~O>WNjsXPDz006ie=`z|Sc`@D|u6b|?eVW<@?#of*UN^Y1 zOL~}h7^3Oos~_tf*;Xi9HdD$1_aPTqNN9T?_8E|DYC5#x>-?NN5Ai5voy)Q3NK4cG zJuC2S8B)}7DLfa8z^x4ieZdyl&G)oQCu}+rb?<F84Ic^??thCsPmi<zQN+%?g7w~W z7$`M~ZO4#ImD9P*X&orJWh?5al$mo3zss1!=;0qcnsqQi>G;-8Vma+oiAWXea%wR_ z$qYG4i(SKF?(75aFPu{Z3be-lU%pm{{PTCgQ6?MbnuMe`Cm{S`1}090IRnYaX#b4J zgEP$xl{{Z*&K!#sAt@QUHWtKSbN?o=80P1Dx3}|w776XkW{5LLU{{;6uu0kbouJ5| zm;VK#|1S|zq>i2O-okJhkE^gBL^7Dc?{k7xXp+w3tutQlp`kcIK?MK?4tzlMFtAho z<YEMcVF(maoe(-Yw{ZO#`kcxjhBr292~)Z_dc~#@H{CRPf7NC;_5N0<9J8B3g^nGL z>x5^vD}ZcdL`aRAUqP~eRFlu_v`AWZb?g{O&9mmZd=QMX+YL*6(|ofqh$x$U<DXs= zw7g|wG&whmAZOiY-Os44IC6D5MONvan;A@(_NQIo0BzXUu5IB7h;<c(=+5SCw&RZ5 UX3%me7aYL#J4Di%Smf_tolRDj$^ZZW literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/data/zerodiv.mpc b/Frameworks/TagLib/taglib/tests/data/zerodiv.mpc new file mode 100644 index 0000000000000000000000000000000000000000..d3ea57c75ca8a58f8bcc9bf0388935d59f8d70d5 GIT binary patch literal 405 zcmeYbaP|)N;Gg>Ipa@4}XS)kS0|OX1<|LKoGB_oA<RqpPvp~cc9E(aai%S@?N{dSr z((;QGN>YnU*r0Omshf*Zff{mv8pNPtA(<sPsSLRVj4*ytVsbWvA<Q_SXh>!>Pz6wf z0!T@+LUF5wf&$P~h4RdjjQ>DT4AJ|VAp})#WNKm&gS~-)fn$Jch@-n}`~`LfhLsE) z3~X^FD3+ip)Jy}q&kSN6Lx=zalI;x9V8`SZpgQCK|B}RXJkI!o<P6RD3vD20Fff3E drGbGVF*yd|Uo@w<MqCg@Q4Cbi1k}dB005C6T2%l5 literal 0 HcmV?d00001 diff --git a/Frameworks/TagLib/taglib/tests/main.cpp b/Frameworks/TagLib/taglib/tests/main.cpp new file mode 100644 index 000000000..86a4208c7 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/main.cpp @@ -0,0 +1,90 @@ +/*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <fstream> +#include <stdexcept> +#include <cppunit/TestResult.h> +#include <cppunit/TestResultCollector.h> +#include <cppunit/TestRunner.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <cppunit/BriefTestProgressListener.h> +#include <cppunit/CompilerOutputter.h> +#include <cppunit/XmlOutputter.h> + +int main(int argc, char* argv[]) +{ + std::string testPath = (argc > 1) ? std::string(argv[1]) : ""; + + // Create the event manager and test controller + CppUnit::TestResult controller; + + // Add a listener that collects test result + CppUnit::TestResultCollector result; + controller.addListener(&result); + + // Add a listener that print dots as test run. + CppUnit::BriefTestProgressListener progress; + controller.addListener(&progress); + + // Add the top suite to the test runner + CppUnit::TestRunner runner; + runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest()); + + try { + std::cout << "Running " << testPath; + runner.run(controller, testPath); + + std::cerr << std::endl; + + // Print test in a compiler compatible format. + CppUnit::CompilerOutputter outputter(&result, std::cerr); + outputter.write(); + +#if defined(_MSC_VER) && _MSC_VER > 1500 + char *xml = NULL; + ::_dupenv_s(&xml, NULL, "CPPUNIT_XML"); +#else + char *xml = ::getenv("CPPUNIT_XML"); +#endif + if(xml && !::strcmp(xml, "1")) { + std::ofstream xmlfileout("cpptestresults.xml"); + CppUnit::XmlOutputter xmlout(&result, xmlfileout); + xmlout.write(); + } +#if defined(_MSC_VER) && _MSC_VER > 1500 + ::free(xml); +#endif + } + catch(std::invalid_argument &e){ + std::cerr << std::endl + << "ERROR: " << e.what() + << std::endl; + return 0; + } + + return result.wasSuccessful() ? 0 : 1; +} diff --git a/Frameworks/TagLib/taglib/tests/plainfile.h b/Frameworks/TagLib/taglib/tests/plainfile.h new file mode 100644 index 000000000..6147b56b5 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/plainfile.h @@ -0,0 +1,50 @@ +/*************************************************************************** + copyright : (C) 2015 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_PLAINFILE_H +#define TAGLIB_PLAINFILE_H + +#include <tfile.h> + +using namespace TagLib; + +//! File subclass that gives tests access to filesystem operations +class PlainFile : public File { +public: + explicit PlainFile(FileName name) : File(name) { } + Tag *tag() const { return NULL; } + AudioProperties *audioProperties() const { return NULL; } + bool save() { return false; } + void truncate(long length) { File::truncate(length); } + + ByteVector readAll() { + seek(0, End); + long end = tell(); + seek(0); + return readBlock(end); + } +}; + +#endif diff --git a/Frameworks/TagLib/taglib/tests/test_aiff.cpp b/Frameworks/TagLib/taglib/tests/test_aiff.cpp new file mode 100644 index 000000000..0337729f1 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_aiff.cpp @@ -0,0 +1,162 @@ +/*************************************************************************** + copyright : (C) 2009 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <tbytevectorlist.h> +#include <aifffile.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestAIFF : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestAIFF); + CPPUNIT_TEST(testAiffProperties); + CPPUNIT_TEST(testAiffCProperties); + CPPUNIT_TEST(testSaveID3v2); + CPPUNIT_TEST(testSaveID3v23); + CPPUNIT_TEST(testDuplicateID3v2); + CPPUNIT_TEST(testFuzzedFile1); + CPPUNIT_TEST(testFuzzedFile2); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testAiffProperties() + { + RIFF::AIFF::File f(TEST_FILE_PATH_C("empty.aiff")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(67, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(706, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(2941U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isAiffC()); + } + + void testAiffCProperties() + { + RIFF::AIFF::File f(TEST_FILE_PATH_C("alaw.aifc")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(37, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(355, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(1622U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isAiffC()); + CPPUNIT_ASSERT_EQUAL(ByteVector("ALAW"), f.audioProperties()->compressionType()); + CPPUNIT_ASSERT_EQUAL(String("SGI CCITT G.711 A-law"), f.audioProperties()->compressionName()); + } + + void testSaveID3v2() + { + ScopedFileCopy copy("empty", ".aiff"); + string newname = copy.fileName(); + + { + RIFF::AIFF::File f(newname.c_str()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + + f.tag()->setTitle(L"TitleXXX"); + f.save(); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + } + { + RIFF::AIFF::File f(newname.c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String(L"TitleXXX"), f.tag()->title()); + + f.tag()->setTitle(""); + f.save(); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + } + { + RIFF::AIFF::File f(newname.c_str()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + } + } + + void testSaveID3v23() + { + ScopedFileCopy copy("empty", ".aiff"); + string newname = copy.fileName(); + + String xxx = ByteVector(254, 'X'); + { + RIFF::AIFF::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); + + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(ID3v2::v3); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + } + { + RIFF::AIFF::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, f2.tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } + } + + void testDuplicateID3v2() + { + ScopedFileCopy copy("duplicate_id3v2", ".aiff"); + + // duplicate_id3v2.aiff has duplicate ID3v2 tag chunks. + // title() returns "Title2" if can't skip the second tag. + + RIFF::AIFF::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String("Title1"), f.tag()->title()); + + f.save(); + CPPUNIT_ASSERT_EQUAL(7030L, f.length()); + CPPUNIT_ASSERT_EQUAL(-1L, f.find("Title2")); + } + + void testFuzzedFile1() + { + RIFF::AIFF::File f(TEST_FILE_PATH_C("segfault.aif")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testFuzzedFile2() + { + RIFF::AIFF::File f(TEST_FILE_PATH_C("excessive_alloc.aif")); + CPPUNIT_ASSERT(f.isValid()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestAIFF); diff --git a/Frameworks/TagLib/taglib/tests/test_ape.cpp b/Frameworks/TagLib/taglib/tests/test_ape.cpp new file mode 100644 index 000000000..81b8510ff --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_ape.cpp @@ -0,0 +1,238 @@ +/*************************************************************************** + copyright : (C) 2010 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <apetag.h> +#include <id3v1tag.h> +#include <tstringlist.h> +#include <tbytevectorlist.h> +#include <tpropertymap.h> +#include <apefile.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestAPE : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestAPE); + CPPUNIT_TEST(testProperties399); + CPPUNIT_TEST(testProperties399Tagged); + CPPUNIT_TEST(testProperties399Id3v2); + CPPUNIT_TEST(testProperties396); + CPPUNIT_TEST(testProperties390); + CPPUNIT_TEST(testFuzzedFile1); + CPPUNIT_TEST(testFuzzedFile2); + CPPUNIT_TEST(testStripAndProperties); + CPPUNIT_TEST(testRepeatedSave); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testProperties399() + { + APE::File f(TEST_FILE_PATH_C("mac-399.ape")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(192, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3990, f.audioProperties()->version()); + } + + void testProperties399Tagged() + { + APE::File f(TEST_FILE_PATH_C("mac-399-tagged.ape")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(192, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3990, f.audioProperties()->version()); + } + + void testProperties399Id3v2() + { + APE::File f(TEST_FILE_PATH_C("mac-399-id3v2.ape")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(192, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3990, f.audioProperties()->version()); + } + + void testProperties396() + { + APE::File f(TEST_FILE_PATH_C("mac-396.ape")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(162496U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3960, f.audioProperties()->version()); + } + + void testProperties390() + { + APE::File f(TEST_FILE_PATH_C("mac-390-hdr.ape")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(15, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(15630, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(689262U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3900, f.audioProperties()->version()); + } + + void testFuzzedFile1() + { + APE::File f(TEST_FILE_PATH_C("longloop.ape")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testFuzzedFile2() + { + APE::File f(TEST_FILE_PATH_C("zerodiv.ape")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testStripAndProperties() + { + ScopedFileCopy copy("mac-399", ".ape"); + + { + APE::File f(copy.fileName().c_str()); + f.APETag(true)->setTitle("APE"); + f.ID3v1Tag(true)->setTitle("ID3v1"); + f.save(); + } + { + APE::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("APE"), f.properties()["TITLE"].front()); + f.strip(APE::File::APE); + CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front()); + f.strip(APE::File::ID3v1); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + + void testProperties() + { + PropertyMap tags; + tags["ALBUM"] = StringList("Album"); + tags["ALBUMARTIST"] = StringList("Album Artist"); + tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + tags["ALBUMSORT"] = StringList("Album Sort"); + tags["ARTIST"] = StringList("Artist"); + tags["ARTISTS"] = StringList("Artists"); + tags["ARTISTSORT"] = StringList("Artist Sort"); + tags["ASIN"] = StringList("ASIN"); + tags["BARCODE"] = StringList("Barcode"); + tags["CATALOGNUMBER"] = StringList("Catalog Number 1").append("Catalog Number 2"); + tags["COMMENT"] = StringList("Comment"); + tags["DATE"] = StringList("2021-01-10"); + tags["DISCNUMBER"] = StringList("3/5"); + tags["GENRE"] = StringList("Genre"); + tags["ISRC"] = StringList("UKAAA0500001"); + tags["LABEL"] = StringList("Label 1").append("Label 2"); + tags["MEDIA"] = StringList("Media"); + tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); + tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + tags["ORIGINALDATE"] = StringList("2021-01-09"); + tags["RELEASECOUNTRY"] = StringList("Release Country"); + tags["RELEASESTATUS"] = StringList("Release Status"); + tags["RELEASETYPE"] = StringList("Release Type"); + tags["SCRIPT"] = StringList("Script"); + tags["TITLE"] = StringList("Title"); + tags["TRACKNUMBER"] = StringList("2/3"); + + ScopedFileCopy copy("mac-399", ".ape"); + { + APE::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + CPPUNIT_ASSERT(properties.isEmpty()); + f.setProperties(tags); + f.save(); + } + { + const APE::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + if (tags != properties) { + CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString()); + } + CPPUNIT_ASSERT(tags == properties); + } + } + + void testRepeatedSave() + { + ScopedFileCopy copy("mac-399", ".ape"); + + { + APE::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasAPETag()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + + f.APETag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.save(); + + f.APETag()->setTitle("0"); + f.save(); + + f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.APETag()->setTitle("01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789"); + f.save(); + } + { + APE::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasAPETag()); + CPPUNIT_ASSERT(f.hasID3v1Tag()); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestAPE); diff --git a/Frameworks/TagLib/taglib/tests/test_apetag.cpp b/Frameworks/TagLib/taglib/tests/test_apetag.cpp new file mode 100644 index 000000000..2225ccbd4 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_apetag.cpp @@ -0,0 +1,171 @@ +/*************************************************************************** + copyright : (C) 2010 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <tstringlist.h> +#include <tbytevectorlist.h> +#include <tpropertymap.h> +#include <apetag.h> +#include <tdebug.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestAPETag : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestAPETag); + CPPUNIT_TEST(testIsEmpty); + CPPUNIT_TEST(testIsEmpty2); + CPPUNIT_TEST(testPropertyInterface1); + CPPUNIT_TEST(testPropertyInterface2); + CPPUNIT_TEST(testInvalidKeys); + CPPUNIT_TEST(testTextBinary); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testIsEmpty() + { + APE::Tag tag; + CPPUNIT_ASSERT(tag.isEmpty()); + tag.addValue("COMPOSER", "Mike Oldfield"); + CPPUNIT_ASSERT(!tag.isEmpty()); + } + + void testIsEmpty2() + { + APE::Tag tag; + CPPUNIT_ASSERT(tag.isEmpty()); + tag.setArtist("Mike Oldfield"); + CPPUNIT_ASSERT(!tag.isEmpty()); + } + + void testPropertyInterface1() + { + APE::Tag tag; + PropertyMap dict = tag.properties(); + CPPUNIT_ASSERT(dict.isEmpty()); + dict["ARTIST"] = String("artist 1"); + dict["ARTIST"].append("artist 2"); + dict["TRACKNUMBER"].append("17"); + tag.setProperties(dict); + CPPUNIT_ASSERT_EQUAL(String("17"), tag.itemListMap()["TRACK"].values()[0]); + CPPUNIT_ASSERT_EQUAL(2u, tag.itemListMap()["ARTIST"].values().size()); + CPPUNIT_ASSERT_EQUAL(String("artist 1 artist 2"), tag.artist()); + CPPUNIT_ASSERT_EQUAL(17u, tag.track()); + const APE::Item &textItem = tag.itemListMap()["TRACK"]; + CPPUNIT_ASSERT_EQUAL(APE::Item::Text, textItem.type()); + CPPUNIT_ASSERT(!textItem.isEmpty()); + CPPUNIT_ASSERT_EQUAL(9 + 5 + 2, textItem.size()); + } + + void testPropertyInterface2() + { + APE::Tag tag; + APE::Item item1 = APE::Item("TRACK", "17"); + tag.setItem("TRACK", item1); + + APE::Item item2 = APE::Item(); + item2.setType(APE::Item::Binary); + ByteVector binaryData1("first"); + item2.setBinaryData(binaryData1); + tag.setItem("TESTBINARY", item2); + + PropertyMap properties = tag.properties(); + CPPUNIT_ASSERT_EQUAL(1u, properties.unsupportedData().size()); + CPPUNIT_ASSERT(properties.contains("TRACKNUMBER")); + CPPUNIT_ASSERT(!properties.contains("TRACK")); + CPPUNIT_ASSERT(tag.itemListMap().contains("TESTBINARY")); + CPPUNIT_ASSERT_EQUAL(binaryData1, + tag.itemListMap()["TESTBINARY"].binaryData()); + ByteVector binaryData2("second"); + tag.setData("TESTBINARY", binaryData2); + const APE::Item &binaryItem = tag.itemListMap()["TESTBINARY"]; + CPPUNIT_ASSERT_EQUAL(APE::Item::Binary, binaryItem.type()); + CPPUNIT_ASSERT(!binaryItem.isEmpty()); + CPPUNIT_ASSERT_EQUAL(9 + 10 + static_cast<int>(binaryData2.size()), + binaryItem.size()); + CPPUNIT_ASSERT_EQUAL(binaryData2, binaryItem.binaryData()); + + tag.removeUnsupportedProperties(properties.unsupportedData()); + CPPUNIT_ASSERT(!tag.itemListMap().contains("TESTBINARY")); + + APE::Item item3 = APE::Item("TRACKNUMBER", "29"); + tag.setItem("TRACKNUMBER", item3); + properties = tag.properties(); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, properties["TRACKNUMBER"].size()); + CPPUNIT_ASSERT_EQUAL(String("17"), properties["TRACKNUMBER"][0]); + CPPUNIT_ASSERT_EQUAL(String("29"), properties["TRACKNUMBER"][1]); + + } + + void testInvalidKeys() + { + PropertyMap properties; + properties["A"] = String("invalid key: one character"); + properties["MP+"] = String("invalid key: forbidden string"); + properties[L"\x1234\x3456"] = String("invalid key: Unicode"); + properties["A B~C"] = String("valid key: space and tilde"); + properties["ARTIST"] = String("valid key: normal one"); + + APE::Tag tag; + PropertyMap unsuccessful = tag.setProperties(properties); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, unsuccessful.size()); + CPPUNIT_ASSERT(unsuccessful.contains("A")); + CPPUNIT_ASSERT(unsuccessful.contains("MP+")); + CPPUNIT_ASSERT(unsuccessful.contains(L"\x1234\x3456")); + + CPPUNIT_ASSERT_EQUAL((unsigned int)2, tag.itemListMap().size()); + tag.addValue("VALID KEY", "Test Value 1"); + tag.addValue("INVALID KEY \x7f", "Test Value 2"); + tag.addValue(L"INVALID KEY \x1234\x3456", "Test Value 3"); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, tag.itemListMap().size()); + } + + void testTextBinary() + { + APE::Item item = APE::Item("DUMMY", "Test Text"); + CPPUNIT_ASSERT_EQUAL(String("Test Text"), item.toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), item.binaryData()); + + ByteVector data("Test Data"); + item.setBinaryData(data); + CPPUNIT_ASSERT(item.values().isEmpty()); + CPPUNIT_ASSERT_EQUAL(String(), item.toString()); + CPPUNIT_ASSERT_EQUAL(data, item.binaryData()); + + item.setValue("Test Text 2"); + CPPUNIT_ASSERT_EQUAL(String("Test Text 2"), item.toString()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), item.binaryData()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestAPETag); + diff --git a/Frameworks/TagLib/taglib/tests/test_asf.cpp b/Frameworks/TagLib/taglib/tests/test_asf.cpp new file mode 100644 index 000000000..2abe9fe50 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_asf.cpp @@ -0,0 +1,401 @@ +/*************************************************************************** + copyright : (C) 2008 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <tstringlist.h> +#include <tbytevectorlist.h> +#include <tpropertymap.h> +#include <asffile.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestASF : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestASF); + CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testLosslessProperties); + CPPUNIT_TEST(testRead); + CPPUNIT_TEST(testSaveMultipleValues); + CPPUNIT_TEST(testSaveStream); + CPPUNIT_TEST(testSaveLanguage); + CPPUNIT_TEST(testDWordTrackNumber); + CPPUNIT_TEST(testSaveLargeValue); + CPPUNIT_TEST(testSavePicture); + CPPUNIT_TEST(testSaveMultiplePictures); + CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testPropertiesAllSupported); + CPPUNIT_TEST(testRepeatedSave); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testAudioProperties() + { + ASF::File f(TEST_FILE_PATH_C("silence-1.wma")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3712, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(ASF::Properties::WMA2, f.audioProperties()->codec()); + CPPUNIT_ASSERT_EQUAL(String("Windows Media Audio 9.1"), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String("64 kbps, 48 kHz, stereo 2-pass CBR"), f.audioProperties()->codecDescription()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + } + + void testLosslessProperties() + { + ASF::File f(TEST_FILE_PATH_C("lossless.wma")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3549, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1152, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(ASF::Properties::WMA9Lossless, f.audioProperties()->codec()); + CPPUNIT_ASSERT_EQUAL(String("Windows Media Audio 9.2 Lossless"), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String("VBR Quality 100, 44 kHz, 2 channel 16 bit 1-pass VBR"), f.audioProperties()->codecDescription()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + } + + void testRead() + { + ASF::File f(TEST_FILE_PATH_C("silence-1.wma")); + CPPUNIT_ASSERT_EQUAL(String("test"), f.tag()->title()); + } + + void testSaveMultipleValues() + { + ScopedFileCopy copy("silence-1", ".wma"); + string newname = copy.fileName(); + + { + ASF::File f(newname.c_str()); + ASF::AttributeList values; + values.append("Foo"); + values.append("Bar"); + f.tag()->setAttribute("WM/AlbumTitle", values); + f.save(); + } + { + ASF::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(2, (int)f.tag()->attributeListMap()["WM/AlbumTitle"].size()); + } + } + + void testDWordTrackNumber() + { + ScopedFileCopy copy("silence-1", ".wma"); + string newname = copy.fileName(); + + { + ASF::File f(newname.c_str()); + CPPUNIT_ASSERT(!f.tag()->contains("WM/TrackNumber")); + f.tag()->setAttribute("WM/TrackNumber", (unsigned int)(123)); + f.save(); + } + { + ASF::File f(newname.c_str()); + CPPUNIT_ASSERT(f.tag()->contains("WM/TrackNumber")); + CPPUNIT_ASSERT_EQUAL(ASF::Attribute::DWordType, + f.tag()->attribute("WM/TrackNumber").front().type()); + CPPUNIT_ASSERT_EQUAL((unsigned int)123, f.tag()->track()); + f.tag()->setTrack(234); + f.save(); + } + { + ASF::File f(newname.c_str()); + CPPUNIT_ASSERT(f.tag()->contains("WM/TrackNumber")); + CPPUNIT_ASSERT_EQUAL(ASF::Attribute::UnicodeType, + f.tag()->attribute("WM/TrackNumber").front().type()); + CPPUNIT_ASSERT_EQUAL((unsigned int)234, f.tag()->track()); + } + } + + void testSaveStream() + { + ScopedFileCopy copy("silence-1", ".wma"); + string newname = copy.fileName(); + + { + ASF::File f(newname.c_str()); + ASF::Attribute attr("Foo"); + attr.setStream(43); + f.tag()->setAttribute("WM/AlbumTitle", attr); + f.save(); + } + + { + ASF::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(43, f.tag()->attribute("WM/AlbumTitle").front().stream()); + } + } + + void testSaveLanguage() + { + ScopedFileCopy copy("silence-1", ".wma"); + string newname = copy.fileName(); + + { + ASF::File f(newname.c_str()); + ASF::Attribute attr("Foo"); + attr.setStream(32); + attr.setLanguage(56); + f.tag()->setAttribute("WM/AlbumTitle", attr); + f.save(); + } + { + ASF::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(32, f.tag()->attribute("WM/AlbumTitle").front().stream()); + CPPUNIT_ASSERT_EQUAL(56, f.tag()->attribute("WM/AlbumTitle").front().language()); + } + } + + void testSaveLargeValue() + { + ScopedFileCopy copy("silence-1", ".wma"); + string newname = copy.fileName(); + + { + ASF::File f(newname.c_str()); + ASF::Attribute attr(ByteVector(70000, 'x')); + f.tag()->setAttribute("WM/Blob", attr); + f.save(); + } + { + ASF::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(ByteVector(70000, 'x'), + f.tag()->attribute("WM/Blob").front().toByteVector()); + } + } + + void testSavePicture() + { + ScopedFileCopy copy("silence-1", ".wma"); + string newname = copy.fileName(); + + { + ASF::File f(newname.c_str()); + ASF::Picture picture; + picture.setMimeType("image/jpeg"); + picture.setType(ASF::Picture::FrontCover); + picture.setDescription("description"); + picture.setPicture("data"); + f.tag()->setAttribute("WM/Picture", picture); + f.save(); + } + { + ASF::File f(newname.c_str()); + ASF::AttributeList values2 = f.tag()->attribute("WM/Picture"); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, values2.size()); + ASF::Attribute attr2 = values2.front(); + ASF::Picture picture2 = attr2.toPicture(); + CPPUNIT_ASSERT(picture2.isValid()); + CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), picture2.mimeType()); + CPPUNIT_ASSERT_EQUAL(ASF::Picture::FrontCover, picture2.type()); + CPPUNIT_ASSERT_EQUAL(String("description"), picture2.description()); + CPPUNIT_ASSERT_EQUAL(ByteVector("data"), picture2.picture()); + } + } + + void testSaveMultiplePictures() + { + ScopedFileCopy copy("silence-1", ".wma"); + string newname = copy.fileName(); + + { + ASF::File f(newname.c_str()); + ASF::AttributeList values; + ASF::Picture picture; + picture.setMimeType("image/jpeg"); + picture.setType(ASF::Picture::FrontCover); + picture.setDescription("description"); + picture.setPicture("data"); + values.append(ASF::Attribute(picture)); + ASF::Picture picture2; + picture2.setMimeType("image/png"); + picture2.setType(ASF::Picture::BackCover); + picture2.setDescription("back cover"); + picture2.setPicture("PNG data"); + values.append(ASF::Attribute(picture2)); + f.tag()->setAttribute("WM/Picture", values); + f.save(); + } + { + ASF::File f(newname.c_str()); + ASF::AttributeList values2 = f.tag()->attribute("WM/Picture"); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, values2.size()); + ASF::Picture picture3 = values2[1].toPicture(); + CPPUNIT_ASSERT(picture3.isValid()); + CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), picture3.mimeType()); + CPPUNIT_ASSERT_EQUAL(ASF::Picture::FrontCover, picture3.type()); + CPPUNIT_ASSERT_EQUAL(String("description"), picture3.description()); + CPPUNIT_ASSERT_EQUAL(ByteVector("data"), picture3.picture()); + ASF::Picture picture4 = values2[0].toPicture(); + CPPUNIT_ASSERT(picture4.isValid()); + CPPUNIT_ASSERT_EQUAL(String("image/png"), picture4.mimeType()); + CPPUNIT_ASSERT_EQUAL(ASF::Picture::BackCover, picture4.type()); + CPPUNIT_ASSERT_EQUAL(String("back cover"), picture4.description()); + CPPUNIT_ASSERT_EQUAL(ByteVector("PNG data"), picture4.picture()); + } + } + + void testProperties() + { + ASF::File f(TEST_FILE_PATH_C("silence-1.wma")); + + PropertyMap tags = f.properties(); + + tags["TRACKNUMBER"] = StringList("2"); + tags["DISCNUMBER"] = StringList("3"); + tags["BPM"] = StringList("123"); + tags["ARTIST"] = StringList("Foo Bar"); + f.setProperties(tags); + + tags = f.properties(); + + CPPUNIT_ASSERT_EQUAL(String("Foo Bar"), f.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]); + + CPPUNIT_ASSERT(f.tag()->contains("WM/BeatsPerMinute")); + CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/BeatsPerMinute"].size()); + CPPUNIT_ASSERT_EQUAL(String("123"), f.tag()->attribute("WM/BeatsPerMinute").front().toString()); + CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]); + + CPPUNIT_ASSERT(f.tag()->contains("WM/TrackNumber")); + CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/TrackNumber"].size()); + CPPUNIT_ASSERT_EQUAL(String("2"), f.tag()->attribute("WM/TrackNumber").front().toString()); + CPPUNIT_ASSERT_EQUAL(StringList("2"), tags["TRACKNUMBER"]); + + CPPUNIT_ASSERT(f.tag()->contains("WM/PartOfSet")); + CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/PartOfSet"].size()); + CPPUNIT_ASSERT_EQUAL(String("3"), f.tag()->attribute("WM/PartOfSet").front().toString()); + CPPUNIT_ASSERT_EQUAL(StringList("3"), tags["DISCNUMBER"]); + } + + void testPropertiesAllSupported() + { + PropertyMap tags; + tags["ACOUSTID_ID"] = StringList("Acoustid ID"); + tags["ACOUSTID_FINGERPRINT"] = StringList("Acoustid Fingerprint"); + tags["ALBUM"] = StringList("Album"); + tags["ALBUMARTIST"] = StringList("Album Artist"); + tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + tags["ALBUMSORT"] = StringList("Album Sort"); + tags["ARTIST"] = StringList("Artist"); + tags["ARTISTS"] = StringList("Artists"); + tags["ARTISTSORT"] = StringList("Artist Sort"); + tags["ASIN"] = StringList("ASIN"); + tags["BARCODE"] = StringList("Barcode"); + tags["BPM"] = StringList("123"); + tags["CATALOGNUMBER"] = StringList("Catalog Number"); + tags["COMMENT"] = StringList("Comment"); + tags["COMPOSER"] = StringList("Composer"); + tags["CONDUCTOR"] = StringList("Conductor"); + tags["COPYRIGHT"] = StringList("2021 Copyright"); + tags["DATE"] = StringList("2021-01-03 12:29:23"); + tags["DISCNUMBER"] = StringList("3/5"); + tags["DISCSUBTITLE"] = StringList("Disc Subtitle"); + tags["ENCODEDBY"] = StringList("Encoded by"); + tags["GENRE"] = StringList("Genre"); + tags["GROUPING"] = StringList("Grouping"); + tags["ISRC"] = StringList("UKAAA0500001"); + tags["LABEL"] = StringList("Label"); + tags["LANGUAGE"] = StringList("eng"); + tags["LYRICIST"] = StringList("Lyricist"); + tags["LYRICS"] = StringList("Lyrics"); + tags["MEDIA"] = StringList("Media"); + tags["MOOD"] = StringList("Mood"); + tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); + tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + tags["MUSICBRAINZ_WORKID"] = StringList("MusicBrainz_WorkID"); + tags["MUSICIP_PUID"] = StringList("MusicIP PUID"); + tags["ORIGINALDATE"] = StringList("2021-01-03 13:52:19"); + tags["PRODUCER"] = StringList("Producer"); + tags["RELEASECOUNTRY"] = StringList("Release Country"); + tags["RELEASESTATUS"] = StringList("Release Status"); + tags["RELEASETYPE"] = StringList("Release Type"); + tags["REMIXER"] = StringList("Remixer"); + tags["SCRIPT"] = StringList("Script"); + tags["SUBTITLE"] = StringList("Subtitle"); + tags["TITLE"] = StringList("Title"); + tags["TITLESORT"] = StringList("Title Sort"); + tags["TRACKNUMBER"] = StringList("2/4"); + + ScopedFileCopy copy("silence-1", ".wma"); + { + ASF::File f(copy.fileName().c_str()); + ASF::Tag *asfTag = f.tag(); + asfTag->setTitle(""); + asfTag->attributeListMap().clear(); + f.save(); + } + { + ASF::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + CPPUNIT_ASSERT(properties.isEmpty()); + f.setProperties(tags); + f.save(); + } + { + const ASF::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + if (tags != properties) { + CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString()); + } + CPPUNIT_ASSERT(tags == properties); + } + } + + void testRepeatedSave() + { + ScopedFileCopy copy("silence-1", ".wma"); + + { + ASF::File f(copy.fileName().c_str()); + f.tag()->setTitle(longText(128 * 1024)); + f.save(); + CPPUNIT_ASSERT_EQUAL(297578L, f.length()); + f.tag()->setTitle(longText(16 * 1024)); + f.save(); + CPPUNIT_ASSERT_EQUAL(68202L, f.length()); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestASF); diff --git a/Frameworks/TagLib/taglib/tests/test_bytevector.cpp b/Frameworks/TagLib/taglib/tests/test_bytevector.cpp new file mode 100644 index 000000000..26cde765f --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_bytevector.cpp @@ -0,0 +1,599 @@ +/*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#define _USE_MATH_DEFINES +#include <cmath> +#include <tbytevector.h> +#include <tbytevectorlist.h> +#include <cppunit/extensions/HelperMacros.h> + +using namespace std; +using namespace TagLib; + +class TestByteVector : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestByteVector); + CPPUNIT_TEST(testByteVector); + CPPUNIT_TEST(testFind1); + CPPUNIT_TEST(testFind2); + CPPUNIT_TEST(testFind3); + CPPUNIT_TEST(testRfind1); + CPPUNIT_TEST(testRfind2); + CPPUNIT_TEST(testRfind3); + CPPUNIT_TEST(testToHex); + CPPUNIT_TEST(testIntegerConversion); + CPPUNIT_TEST(testFloatingPointConversion); + CPPUNIT_TEST(testReplace); + CPPUNIT_TEST(testReplaceAndDetach); + CPPUNIT_TEST(testIterator); + CPPUNIT_TEST(testResize); + CPPUNIT_TEST(testAppend1); + CPPUNIT_TEST(testAppend2); + CPPUNIT_TEST(testBase64); + CPPUNIT_TEST_SUITE_END(); + +public: + void testByteVector() + { + ByteVector s1("foo"); + CPPUNIT_ASSERT(ByteVectorList::split(s1, " ").size() == 1); + + ByteVector s2("f"); + CPPUNIT_ASSERT(ByteVectorList::split(s2, " ").size() == 1); + + CPPUNIT_ASSERT(ByteVector().isEmpty()); + CPPUNIT_ASSERT_EQUAL(0U, ByteVector().size()); + CPPUNIT_ASSERT(ByteVector("asdf").clear().isEmpty()); + CPPUNIT_ASSERT_EQUAL(0U, ByteVector("asdf").clear().size()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), ByteVector("asdf").clear()); + + ByteVector i("blah blah"); + ByteVector j("blah"); + CPPUNIT_ASSERT(i.containsAt(j, 5, 0)); + CPPUNIT_ASSERT(i.containsAt(j, 6, 1)); + CPPUNIT_ASSERT(i.containsAt(j, 6, 1, 3)); + + i.clear(); + CPPUNIT_ASSERT(i.isEmpty()); + } + + void testFind1() + { + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find("SggO")); + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find("SggO", 0)); + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find("SggO", 1)); + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find("SggO", 2)); + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find("SggO", 3)); + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find("SggO", 4)); + CPPUNIT_ASSERT_EQUAL(-1, ByteVector("....SggO."). find("SggO", 5)); + CPPUNIT_ASSERT_EQUAL(-1, ByteVector("....SggO."). find("SggO", 6)); + CPPUNIT_ASSERT_EQUAL(-1, ByteVector("....SggO."). find("SggO", 7)); + CPPUNIT_ASSERT_EQUAL(-1, ByteVector("....SggO."). find("SggO", 8)); + + // Intentional out-of-bounds access. + ByteVector v("0123456789x"); + v.resize(10); + v.data()[10] = 'x'; + CPPUNIT_ASSERT_EQUAL(-1, v.find("789x", 7)); + } + + void testFind2() + { + CPPUNIT_ASSERT_EQUAL(0, ByteVector("\x01", 1).find("\x01")); + CPPUNIT_ASSERT_EQUAL(0, ByteVector("\x01\x02", 2).find("\x01\x02")); + CPPUNIT_ASSERT_EQUAL(-1, ByteVector("\x01", 1).find("\x02")); + CPPUNIT_ASSERT_EQUAL(-1, ByteVector("\x01\x02", 2).find("\x01\x03")); + } + + void testFind3() + { + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find('S')); + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find('S', 0)); + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find('S', 1)); + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find('S', 2)); + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find('S', 3)); + CPPUNIT_ASSERT_EQUAL(4, ByteVector("....SggO."). find('S', 4)); + CPPUNIT_ASSERT_EQUAL(-1, ByteVector("....SggO."). find('S', 5)); + CPPUNIT_ASSERT_EQUAL(-1, ByteVector("....SggO."). find('S', 6)); + CPPUNIT_ASSERT_EQUAL(-1, ByteVector("....SggO."). find('S', 7)); + CPPUNIT_ASSERT_EQUAL(-1, ByteVector("....SggO."). find('S', 8)); + } + + void testRfind1() + { + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind("OggS", 0)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind("OggS", 1)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind("OggS", 2)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind("OggS", 3)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind("OggS", 4)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind("OggS", 5)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind("OggS", 6)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind("OggS", 7)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind("OggS", 8)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind("OggS")); + } + + void testRfind2() + { + ByteVector r0("**************"); + ByteVector r1("OggS**********"); + ByteVector r2("**********OggS"); + ByteVector r3("OggS******OggS"); + ByteVector r4("OggS*OggS*OggS"); + + CPPUNIT_ASSERT_EQUAL(-1, r0.find("OggS")); + CPPUNIT_ASSERT_EQUAL(-1, r0.rfind("OggS")); + CPPUNIT_ASSERT_EQUAL(0, r1.find("OggS")); + CPPUNIT_ASSERT_EQUAL(0, r1.rfind("OggS")); + CPPUNIT_ASSERT_EQUAL(10, r2.find("OggS")); + CPPUNIT_ASSERT_EQUAL(10, r2.rfind("OggS")); + CPPUNIT_ASSERT_EQUAL(0, r3.find("OggS")); + CPPUNIT_ASSERT_EQUAL(10, r3.rfind("OggS")); + CPPUNIT_ASSERT_EQUAL(10, r4.rfind("OggS")); + CPPUNIT_ASSERT_EQUAL(10, r4.rfind("OggS", 0)); + CPPUNIT_ASSERT_EQUAL(5, r4.rfind("OggS", 7)); + CPPUNIT_ASSERT_EQUAL(10, r4.rfind("OggS", 12)); + } + + void testRfind3() + { + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind('O', 0)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind('O', 1)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind('O', 2)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind('O', 3)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind('O', 4)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind('O', 5)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind('O', 6)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind('O', 7)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind('O', 8)); + CPPUNIT_ASSERT_EQUAL(1, ByteVector(".OggS....").rfind('O')); + } + + void testToHex() + { + ByteVector v("\xf0\xe1\xd2\xc3\xb4\xa5\x96\x87\x78\x69\x5a\x4b\x3c\x2d\x1e\x0f", 16); + + CPPUNIT_ASSERT_EQUAL(ByteVector("f0e1d2c3b4a5968778695a4b3c2d1e0f"), v.toHex()); + } + + void testIntegerConversion() + { + const ByteVector data("\x00\xff\x01\xff\x00\xff\x01\xff\x00\xff\x01\xff\x00\xff", 14); + + CPPUNIT_ASSERT_EQUAL((short)0x00ff, data.toShort()); + CPPUNIT_ASSERT_EQUAL((short)0xff00, data.toShort(false)); + CPPUNIT_ASSERT_EQUAL((short)0xff01, data.toShort(5U)); + CPPUNIT_ASSERT_EQUAL((short)0x01ff, data.toShort(5U, false)); + CPPUNIT_ASSERT_EQUAL((short)0xff, data.toShort(13U)); + CPPUNIT_ASSERT_EQUAL((short)0xff, data.toShort(13U, false)); + + CPPUNIT_ASSERT_EQUAL((unsigned short)0x00ff, data.toUShort()); + CPPUNIT_ASSERT_EQUAL((unsigned short)0xff00, data.toUShort(false)); + CPPUNIT_ASSERT_EQUAL((unsigned short)0xff01, data.toUShort(5U)); + CPPUNIT_ASSERT_EQUAL((unsigned short)0x01ff, data.toUShort(5U, false)); + CPPUNIT_ASSERT_EQUAL((unsigned short)0xff, data.toUShort(13U)); + CPPUNIT_ASSERT_EQUAL((unsigned short)0xff, data.toUShort(13U, false)); + + CPPUNIT_ASSERT_EQUAL(0x00ff01ffU, data.toUInt()); + CPPUNIT_ASSERT_EQUAL(0xff01ff00U, data.toUInt(false)); + CPPUNIT_ASSERT_EQUAL(0xff01ff00U, data.toUInt(5U)); + CPPUNIT_ASSERT_EQUAL(0x00ff01ffU, data.toUInt(5U, false)); + CPPUNIT_ASSERT_EQUAL(0x00ffU, data.toUInt(12U)); + CPPUNIT_ASSERT_EQUAL(0xff00U, data.toUInt(12U, false)); + + CPPUNIT_ASSERT_EQUAL(0x00ff01U, data.toUInt(0U, 3U)); + CPPUNIT_ASSERT_EQUAL(0x01ff00U, data.toUInt(0U, 3U, false)); + CPPUNIT_ASSERT_EQUAL(0xff01ffU, data.toUInt(5U, 3U)); + CPPUNIT_ASSERT_EQUAL(0xff01ffU, data.toUInt(5U, 3U, false)); + CPPUNIT_ASSERT_EQUAL(0x00ffU, data.toUInt(12U, 3U)); + CPPUNIT_ASSERT_EQUAL(0xff00U, data.toUInt(12U, 3U, false)); + + CPPUNIT_ASSERT_EQUAL((long long)0x00ff01ff00ff01ffULL, data.toLongLong()); + CPPUNIT_ASSERT_EQUAL((long long)0xff01ff00ff01ff00ULL, data.toLongLong(false)); + CPPUNIT_ASSERT_EQUAL((long long)0xff01ff00ff01ff00ULL, data.toLongLong(5U)); + CPPUNIT_ASSERT_EQUAL((long long)0x00ff01ff00ff01ffULL, data.toLongLong(5U, false)); + CPPUNIT_ASSERT_EQUAL((long long)0x00ffU, data.toLongLong(12U)); + CPPUNIT_ASSERT_EQUAL((long long)0xff00U, data.toLongLong(12U, false)); +} + + void testFloatingPointConversion() + { + const double Tolerance = 1.0e-7; + + const ByteVector pi32le("\xdb\x0f\x49\x40", 4); + CPPUNIT_ASSERT(std::abs(pi32le.toFloat32LE(0) - M_PI) < Tolerance); + CPPUNIT_ASSERT_EQUAL(pi32le, ByteVector::fromFloat32LE(pi32le.toFloat32LE(0))); + + const ByteVector pi32be("\x40\x49\x0f\xdb", 4); + CPPUNIT_ASSERT(std::abs(pi32be.toFloat32BE(0) - M_PI) < Tolerance); + CPPUNIT_ASSERT_EQUAL(pi32be, ByteVector::fromFloat32BE(pi32be.toFloat32BE(0))); + + const ByteVector pi64le("\x18\x2d\x44\x54\xfb\x21\x09\x40", 8); + CPPUNIT_ASSERT(std::abs(pi64le.toFloat64LE(0) - M_PI) < Tolerance); + CPPUNIT_ASSERT_EQUAL(pi64le, ByteVector::fromFloat64LE(pi64le.toFloat64LE(0))); + + const ByteVector pi64be("\x40\x09\x21\xfb\x54\x44\x2d\x18", 8); + CPPUNIT_ASSERT(std::abs(pi64be.toFloat64BE(0) - M_PI) < Tolerance); + CPPUNIT_ASSERT_EQUAL(pi64be, ByteVector::fromFloat64BE(pi64be.toFloat64BE(0))); + + const ByteVector pi80le("\x00\xc0\x68\x21\xa2\xda\x0f\xc9\x00\x40", 10); + CPPUNIT_ASSERT(std::abs(pi80le.toFloat80LE(0) - M_PI) < Tolerance); + + const ByteVector pi80be("\x40\x00\xc9\x0f\xda\xa2\x21\x68\xc0\x00", 10); + CPPUNIT_ASSERT(std::abs(pi80be.toFloat80BE(0) - M_PI) < Tolerance); + } + + void testReplace() + { + { + ByteVector a("abcdabf"); + a.replace(ByteVector(""), ByteVector("<a>")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), a); + } + { + ByteVector a("abcdabf"); + a.replace(ByteVector("foobartoolong"), ByteVector("<a>")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), a); + } + { + ByteVector a("abcdabf"); + a.replace(ByteVector("xx"), ByteVector("yy")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), a); + } + { + ByteVector a("abcdabf"); + a.replace(ByteVector("a"), ByteVector("x")); + CPPUNIT_ASSERT_EQUAL(ByteVector("xbcdxbf"), a); + a.replace(ByteVector("x"), ByteVector("a")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), a); + } + { + ByteVector a("abcdabf"); + a.replace('a', 'x'); + CPPUNIT_ASSERT_EQUAL(ByteVector("xbcdxbf"), a); + a.replace('x', 'a'); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), a); + } + { + ByteVector a("abcdabf"); + a.replace(ByteVector("ab"), ByteVector("xy")); + CPPUNIT_ASSERT_EQUAL(ByteVector("xycdxyf"), a); + a.replace(ByteVector("xy"), ByteVector("ab")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), a); + } + { + ByteVector a("abcdabf"); + a.replace(ByteVector("a"), ByteVector("<a>")); + CPPUNIT_ASSERT_EQUAL(ByteVector("<a>bcd<a>bf"), a); + a.replace(ByteVector("<a>"), ByteVector("a")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), a); + } + { + ByteVector a("abcdabf"); + a.replace(ByteVector("b"), ByteVector("<b>")); + CPPUNIT_ASSERT_EQUAL(ByteVector("a<b>cda<b>f"), a); + a.replace(ByteVector("<b>"), ByteVector("b")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), a); + } + { + ByteVector a("abcdabc"); + a.replace(ByteVector("c"), ByteVector("<c>")); + CPPUNIT_ASSERT_EQUAL(ByteVector("ab<c>dab<c>"), a); + a.replace(ByteVector("<c>"), ByteVector("c")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabc"), a); + } + { + ByteVector a("abcdaba"); + a.replace(ByteVector("a"), ByteVector("<a>")); + CPPUNIT_ASSERT_EQUAL(ByteVector("<a>bcd<a>b<a>"), a); + a.replace(ByteVector("<a>"), ByteVector("a")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdaba"), a); + } + } + + void testReplaceAndDetach() + { + { + ByteVector a("abcdabf"); + ByteVector b = a; + a.replace(ByteVector("a"), ByteVector("x")); + CPPUNIT_ASSERT_EQUAL(ByteVector("xbcdxbf"), a); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), b); + } + { + ByteVector a("abcdabf"); + ByteVector b = a; + a.replace('a', 'x'); + CPPUNIT_ASSERT_EQUAL(ByteVector("xbcdxbf"), a); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), b); + } + { + ByteVector a("abcdabf"); + ByteVector b = a; + a.replace(ByteVector("ab"), ByteVector("xy")); + CPPUNIT_ASSERT_EQUAL(ByteVector("xycdxyf"), a); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), b); + } + { + ByteVector a("abcdabf"); + ByteVector b = a; + a.replace(ByteVector("a"), ByteVector("<a>")); + CPPUNIT_ASSERT_EQUAL(ByteVector("<a>bcd<a>bf"), a); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), b); + } + { + ByteVector a("ab<c>dab<c>"); + ByteVector b = a; + a.replace(ByteVector("<c>"), ByteVector("c")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabc"), a); + CPPUNIT_ASSERT_EQUAL(ByteVector("ab<c>dab<c>"), b); + } + } + + void testIterator() + { + ByteVector v1("taglib"); + ByteVector v2 = v1; + + ByteVector::Iterator it1 = v1.begin(); + ByteVector::Iterator it2 = v2.begin(); + + CPPUNIT_ASSERT_EQUAL('t', *it1); + CPPUNIT_ASSERT_EQUAL('t', *it2); + + std::advance(it1, 4); + std::advance(it2, 4); + *it2 = 'I'; + CPPUNIT_ASSERT_EQUAL('i', *it1); + CPPUNIT_ASSERT_EQUAL('I', *it2); + CPPUNIT_ASSERT_EQUAL(ByteVector("taglib"), v1); + CPPUNIT_ASSERT_EQUAL(ByteVector("taglIb"), v2); + + ByteVector::ReverseIterator it3 = v1.rbegin(); + ByteVector::ReverseIterator it4 = v2.rbegin(); + + CPPUNIT_ASSERT_EQUAL('b', *it3); + CPPUNIT_ASSERT_EQUAL('b', *it4); + + std::advance(it3, 4); + std::advance(it4, 4); + *it4 = 'A'; + CPPUNIT_ASSERT_EQUAL('a', *it3); + CPPUNIT_ASSERT_EQUAL('A', *it4); + CPPUNIT_ASSERT_EQUAL(ByteVector("taglib"), v1); + CPPUNIT_ASSERT_EQUAL(ByteVector("tAglIb"), v2); + + ByteVector v3; + v3 = ByteVector("0123456789").mid(3, 4); + + it1 = v3.begin(); + it2 = v3.end() - 1; + CPPUNIT_ASSERT_EQUAL('3', *it1); + CPPUNIT_ASSERT_EQUAL('6', *it2); + + it3 = v3.rbegin(); + it4 = v3.rend() - 1; + CPPUNIT_ASSERT_EQUAL('6', *it3); + CPPUNIT_ASSERT_EQUAL('3', *it4); + } + + void testResize() + { + ByteVector a = ByteVector("0123456789"); + ByteVector b = a.mid(3, 4); + b.resize(6, 'A'); + CPPUNIT_ASSERT_EQUAL((unsigned int)6, b.size()); + CPPUNIT_ASSERT_EQUAL('6', b[3]); + CPPUNIT_ASSERT_EQUAL('A', b[4]); + CPPUNIT_ASSERT_EQUAL('A', b[5]); + b.resize(10, 'B'); + CPPUNIT_ASSERT_EQUAL((unsigned int)10, b.size()); + CPPUNIT_ASSERT_EQUAL('6', b[3]); + CPPUNIT_ASSERT_EQUAL('B', b[6]); + CPPUNIT_ASSERT_EQUAL('B', b[9]); + b.resize(3, 'C'); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, b.size()); + CPPUNIT_ASSERT_EQUAL(-1, b.find('C')); + b.resize(3); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, b.size()); + + // Check if a and b were properly detached. + + CPPUNIT_ASSERT_EQUAL((unsigned int)10, a.size()); + CPPUNIT_ASSERT_EQUAL('3', a[3]); + CPPUNIT_ASSERT_EQUAL('5', a[5]); + + // Special case that refCount == 1 and d->offset != 0. + + ByteVector c = ByteVector("0123456789").mid(3, 4); + c.resize(6, 'A'); + CPPUNIT_ASSERT_EQUAL((unsigned int)6, c.size()); + CPPUNIT_ASSERT_EQUAL('6', c[3]); + CPPUNIT_ASSERT_EQUAL('A', c[4]); + CPPUNIT_ASSERT_EQUAL('A', c[5]); + c.resize(10, 'B'); + CPPUNIT_ASSERT_EQUAL((unsigned int)10, c.size()); + CPPUNIT_ASSERT_EQUAL('6', c[3]); + CPPUNIT_ASSERT_EQUAL('B', c[6]); + CPPUNIT_ASSERT_EQUAL('B', c[9]); + c.resize(3, 'C'); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, c.size()); + CPPUNIT_ASSERT_EQUAL(-1, c.find('C')); + } + + void testAppend1() + { + ByteVector v1("foo"); + v1.append("bar"); + CPPUNIT_ASSERT_EQUAL(ByteVector("foobar"), v1); + + ByteVector v2("foo"); + v2.append("b"); + CPPUNIT_ASSERT_EQUAL(ByteVector("foob"), v2); + + ByteVector v3; + v3.append("b"); + CPPUNIT_ASSERT_EQUAL(ByteVector("b"), v3); + + ByteVector v4("foo"); + v4.append(v1); + CPPUNIT_ASSERT_EQUAL(ByteVector("foofoobar"), v4); + + ByteVector v5("foo"); + v5.append('b'); + CPPUNIT_ASSERT_EQUAL(ByteVector("foob"), v5); + + ByteVector v6; + v6.append('b'); + CPPUNIT_ASSERT_EQUAL(ByteVector("b"), v6); + + ByteVector v7("taglib"); + ByteVector v8 = v7; + + v7.append("ABC"); + CPPUNIT_ASSERT_EQUAL(ByteVector("taglibABC"), v7); + v7.append('1'); + v7.append('2'); + v7.append('3'); + CPPUNIT_ASSERT_EQUAL(ByteVector("taglibABC123"), v7); + CPPUNIT_ASSERT_EQUAL(ByteVector("taglib"), v8); + } + + void testAppend2() + { + ByteVector a("1234"); + a.append(a); + CPPUNIT_ASSERT_EQUAL(ByteVector("12341234"), a); + } + + void testBase64() + { + ByteVector sempty; + ByteVector t0("a"); // test 1 byte + ByteVector t1("any carnal pleasure."); + ByteVector t2("any carnal pleasure"); + ByteVector t3("any carnal pleasur"); + ByteVector s0("a"); // test 1 byte + ByteVector s1("any carnal pleasure."); + ByteVector s2("any carnal pleasure"); + ByteVector s3("any carnal pleasur"); + ByteVector eempty; + ByteVector e0("YQ=="); + ByteVector e1("YW55IGNhcm5hbCBwbGVhc3VyZS4="); + ByteVector e2("YW55IGNhcm5hbCBwbGVhc3VyZQ=="); + ByteVector e3("YW55IGNhcm5hbCBwbGVhc3Vy"); + + // Encode + CPPUNIT_ASSERT_EQUAL(eempty, sempty.toBase64()); + CPPUNIT_ASSERT_EQUAL(e0, s0.toBase64()); + CPPUNIT_ASSERT_EQUAL(e1, s1.toBase64()); + CPPUNIT_ASSERT_EQUAL(e2, s2.toBase64()); + CPPUNIT_ASSERT_EQUAL(e3, s3.toBase64()); + + // Decode + CPPUNIT_ASSERT_EQUAL(sempty, ByteVector::fromBase64(eempty)); + CPPUNIT_ASSERT_EQUAL(s0, ByteVector::fromBase64(e0)); + CPPUNIT_ASSERT_EQUAL(s1, ByteVector::fromBase64(e1)); + CPPUNIT_ASSERT_EQUAL(s2, ByteVector::fromBase64(e2)); + CPPUNIT_ASSERT_EQUAL(s3, ByteVector::fromBase64(e3)); + + CPPUNIT_ASSERT_EQUAL(t0, ByteVector::fromBase64(s0.toBase64())); + CPPUNIT_ASSERT_EQUAL(t1, ByteVector::fromBase64(s1.toBase64())); + CPPUNIT_ASSERT_EQUAL(t2, ByteVector::fromBase64(s2.toBase64())); + CPPUNIT_ASSERT_EQUAL(t3, ByteVector::fromBase64(s3.toBase64())); + + ByteVector all((unsigned int)256); + + // in order + { + for(int i = 0; i < 256; i++){ + all[i]=(unsigned char)i; + } + ByteVector b64 = all.toBase64(); + ByteVector original = ByteVector::fromBase64(b64); + CPPUNIT_ASSERT_EQUAL(all,original); + } + + // reverse + { + for(int i = 0; i < 256; i++){ + all[i]=(unsigned char)255-i; + } + ByteVector b64 = all.toBase64(); + ByteVector original = ByteVector::fromBase64(b64); + CPPUNIT_ASSERT_EQUAL(all,original); + } + + // all zeroes + { + for(int i = 0; i < 256; i++){ + all[i]=0; + } + ByteVector b64 = all.toBase64(); + ByteVector original = ByteVector::fromBase64(b64); + CPPUNIT_ASSERT_EQUAL(all,original); + } + + // all ones + { + for(int i = 0; i < 256; i++){ + all[i]=(unsigned char)0xff; + } + ByteVector b64 = all.toBase64(); + ByteVector original = ByteVector::fromBase64(b64); + CPPUNIT_ASSERT_EQUAL(all,original); + } + + // Missing end bytes + { + // No missing bytes + ByteVector m0("YW55IGNhcm5hbCBwbGVhc3VyZQ=="); + CPPUNIT_ASSERT_EQUAL(s2,ByteVector::fromBase64(m0)); + + // 1 missing byte + ByteVector m1("YW55IGNhcm5hbCBwbGVhc3VyZQ="); + CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64(m1)); + + // 2 missing bytes + ByteVector m2("YW55IGNhcm5hbCBwbGVhc3VyZQ"); + CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64(m2)); + + // 3 missing bytes + ByteVector m3("YW55IGNhcm5hbCBwbGVhc3VyZ"); + CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64(m3)); + } + + // Grok invalid characters + { + ByteVector invalid("abd\x00\x01\x02\x03\x04"); + CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64(invalid)); + } + + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestByteVector); + diff --git a/Frameworks/TagLib/taglib/tests/test_bytevectorlist.cpp b/Frameworks/TagLib/taglib/tests/test_bytevectorlist.cpp new file mode 100644 index 000000000..6fe325b84 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_bytevectorlist.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + copyright : (C) 2009 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tbytevector.h> +#include <tbytevectorlist.h> +#include <cppunit/extensions/HelperMacros.h> + +using namespace std; +using namespace TagLib; + +class TestByteVectorList : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestByteVectorList); + CPPUNIT_TEST(testSplitSingleChar); + CPPUNIT_TEST(testSplitSingleChar_2); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testSplitSingleChar() + { + ByteVector v("a b"); + + ByteVectorList l = ByteVectorList::split(v, " "); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, l.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("a"), l[0]); + CPPUNIT_ASSERT_EQUAL(ByteVector("b"), l[1]); + } + + void testSplitSingleChar_2() + { + ByteVector v("a"); + + ByteVectorList l = ByteVectorList::split(v, " "); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, l.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("a"), l[0]); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestByteVectorList); diff --git a/Frameworks/TagLib/taglib/tests/test_bytevectorstream.cpp b/Frameworks/TagLib/taglib/tests/test_bytevectorstream.cpp new file mode 100644 index 000000000..f8308c6dc --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_bytevectorstream.cpp @@ -0,0 +1,131 @@ +/*************************************************************************** + copyright : (C) 2011 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tbytevectorstream.h> +#include <cppunit/extensions/HelperMacros.h> + +using namespace std; +using namespace TagLib; + +class TestByteVectorStream : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestByteVectorStream); + CPPUNIT_TEST(testInitialData); + CPPUNIT_TEST(testWriteBlock); + CPPUNIT_TEST(testWriteBlockResize); + CPPUNIT_TEST(testReadBlock); + CPPUNIT_TEST(testRemoveBlock); + CPPUNIT_TEST(testInsert); + CPPUNIT_TEST(testSeekEnd); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testInitialData() + { + ByteVector v("abcd"); + ByteVectorStream stream(v); + + CPPUNIT_ASSERT_EQUAL(ByteVector("abcd"), *stream.data()); + } + + void testWriteBlock() + { + ByteVector v("abcd"); + ByteVectorStream stream(v); + + stream.seek(1); + stream.writeBlock(ByteVector("xx")); + CPPUNIT_ASSERT_EQUAL(ByteVector("axxd"), *stream.data()); + } + + void testWriteBlockResize() + { + ByteVector v("abcd"); + ByteVectorStream stream(v); + + stream.seek(3); + stream.writeBlock(ByteVector("xx")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcxx"), *stream.data()); + stream.seek(5); + stream.writeBlock(ByteVector("yy")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcxxyy"), *stream.data()); + } + + void testReadBlock() + { + ByteVector v("abcd"); + ByteVectorStream stream(v); + + CPPUNIT_ASSERT_EQUAL(ByteVector("a"), stream.readBlock(1)); + CPPUNIT_ASSERT_EQUAL(ByteVector("bc"), stream.readBlock(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("d"), stream.readBlock(3)); + CPPUNIT_ASSERT_EQUAL(ByteVector(""), stream.readBlock(3)); + } + + void testRemoveBlock() + { + ByteVector v("abcd"); + ByteVectorStream stream(v); + + stream.removeBlock(1, 1); + CPPUNIT_ASSERT_EQUAL(ByteVector("acd"), *stream.data()); + stream.removeBlock(0, 2); + CPPUNIT_ASSERT_EQUAL(ByteVector("d"), *stream.data()); + stream.removeBlock(0, 2); + CPPUNIT_ASSERT_EQUAL(ByteVector(""), *stream.data()); + } + + void testInsert() + { + ByteVector v("abcd"); + ByteVectorStream stream(v); + + stream.insert(ByteVector("xx"), 1, 1); + CPPUNIT_ASSERT_EQUAL(ByteVector("axxcd"), *stream.data()); + stream.insert(ByteVector("yy"), 0, 2); + CPPUNIT_ASSERT_EQUAL(ByteVector("yyxcd"), *stream.data()); + stream.insert(ByteVector("foa"), 3, 2); + CPPUNIT_ASSERT_EQUAL(ByteVector("yyxfoa"), *stream.data()); + stream.insert(ByteVector("123"), 3, 0); + CPPUNIT_ASSERT_EQUAL(ByteVector("yyx123foa"), *stream.data()); + } + + void testSeekEnd() + { + ByteVector v("abcdefghijklmnopqrstuvwxyz"); + ByteVectorStream stream(v); + CPPUNIT_ASSERT_EQUAL(26L, stream.length()); + + stream.seek(-4, IOStream::End); + CPPUNIT_ASSERT_EQUAL(ByteVector("w"), stream.readBlock(1)); + + stream.seek(-25, IOStream::End); + CPPUNIT_ASSERT_EQUAL(ByteVector("b"), stream.readBlock(1)); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestByteVectorStream); diff --git a/Frameworks/TagLib/taglib/tests/test_file.cpp b/Frameworks/TagLib/taglib/tests/test_file.cpp new file mode 100644 index 000000000..ef8c1b108 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_file.cpp @@ -0,0 +1,145 @@ +/*************************************************************************** + copyright : (C) 2015 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tfile.h> +#include <cppunit/extensions/HelperMacros.h> +#include "plainfile.h" +#include "utils.h" + +using namespace TagLib; + +class TestFile : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestFile); + CPPUNIT_TEST(testFindInSmallFile); + CPPUNIT_TEST(testRFindInSmallFile); + CPPUNIT_TEST(testSeek); + CPPUNIT_TEST(testTruncate); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testFindInSmallFile() + { + ScopedFileCopy copy("empty", ".ogg"); + std::string name = copy.fileName(); + { + PlainFile file(name.c_str()); + file.seek(0); + file.writeBlock(ByteVector("0123456239", 10)); + file.truncate(10); + } + { + PlainFile file(name.c_str()); + CPPUNIT_ASSERT_EQUAL(10l, file.length()); + + CPPUNIT_ASSERT_EQUAL(2l, file.find(ByteVector("23", 2))); + CPPUNIT_ASSERT_EQUAL(2l, file.find(ByteVector("23", 2), 2)); + CPPUNIT_ASSERT_EQUAL(7l, file.find(ByteVector("23", 2), 3)); + + file.seek(0); + const ByteVector v = file.readBlock(file.length()); + CPPUNIT_ASSERT_EQUAL((unsigned int)10, v.size()); + + CPPUNIT_ASSERT_EQUAL((long)v.find("23"), file.find("23")); + CPPUNIT_ASSERT_EQUAL((long)v.find("23", 2), file.find("23", 2)); + CPPUNIT_ASSERT_EQUAL((long)v.find("23", 3), file.find("23", 3)); + } + } + + void testRFindInSmallFile() + { + ScopedFileCopy copy("empty", ".ogg"); + std::string name = copy.fileName(); + { + PlainFile file(name.c_str()); + file.seek(0); + file.writeBlock(ByteVector("0123456239", 10)); + file.truncate(10); + } + { + PlainFile file(name.c_str()); + CPPUNIT_ASSERT_EQUAL(10l, file.length()); + + CPPUNIT_ASSERT_EQUAL(7l, file.rfind(ByteVector("23", 2))); + CPPUNIT_ASSERT_EQUAL(7l, file.rfind(ByteVector("23", 2), 7)); + CPPUNIT_ASSERT_EQUAL(2l, file.rfind(ByteVector("23", 2), 6)); + + file.seek(0); + const ByteVector v = file.readBlock(file.length()); + CPPUNIT_ASSERT_EQUAL((unsigned int)10, v.size()); + + CPPUNIT_ASSERT_EQUAL((long)v.rfind("23"), file.rfind("23")); + CPPUNIT_ASSERT_EQUAL((long)v.rfind("23", 7), file.rfind("23", 7)); + CPPUNIT_ASSERT_EQUAL((long)v.rfind("23", 6), file.rfind("23", 6)); + } + } + + void testSeek() + { + ScopedFileCopy copy("empty", ".ogg"); + std::string name = copy.fileName(); + + PlainFile f(name.c_str()); + CPPUNIT_ASSERT_EQUAL((long)0, f.tell()); + CPPUNIT_ASSERT_EQUAL((long)4328, f.length()); + + f.seek(100, File::Beginning); + CPPUNIT_ASSERT_EQUAL((long)100, f.tell()); + f.seek(100, File::Current); + CPPUNIT_ASSERT_EQUAL((long)200, f.tell()); + f.seek(-300, File::Current); + CPPUNIT_ASSERT_EQUAL((long)200, f.tell()); + + f.seek(-100, File::End); + CPPUNIT_ASSERT_EQUAL((long)4228, f.tell()); + f.seek(-100, File::Current); + CPPUNIT_ASSERT_EQUAL((long)4128, f.tell()); + f.seek(300, File::Current); + CPPUNIT_ASSERT_EQUAL((long)4428, f.tell()); + } + + void testTruncate() + { + ScopedFileCopy copy("empty", ".ogg"); + std::string name = copy.fileName(); + + { + PlainFile f(name.c_str()); + CPPUNIT_ASSERT_EQUAL(4328L, f.length()); + + f.truncate(2000); + CPPUNIT_ASSERT_EQUAL(2000L, f.length()); + } + { + PlainFile f(name.c_str()); + CPPUNIT_ASSERT_EQUAL(2000L, f.length()); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestFile); + diff --git a/Frameworks/TagLib/taglib/tests/test_fileref.cpp b/Frameworks/TagLib/taglib/tests/test_fileref.cpp new file mode 100644 index 000000000..1fc5def95 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_fileref.cpp @@ -0,0 +1,394 @@ +/*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <fileref.h> +#include <oggflacfile.h> +#include <vorbisfile.h> +#include <mpegfile.h> +#include <mpcfile.h> +#include <asffile.h> +#include <speexfile.h> +#include <flacfile.h> +#include <trueaudiofile.h> +#include <mp4file.h> +#include <wavfile.h> +#include <apefile.h> +#include <aifffile.h> +#include <wavpackfile.h> +#include <opusfile.h> +#include <xmfile.h> +#include <tfilestream.h> +#include <tbytevectorstream.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +namespace +{ + class DummyResolver : public FileRef::FileTypeResolver + { + public: + virtual File *createFile(FileName fileName, bool, AudioProperties::ReadStyle) const + { + return new Ogg::Vorbis::File(fileName); + } + }; +} + +class TestFileRef : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestFileRef); + CPPUNIT_TEST(testASF); + CPPUNIT_TEST(testMusepack); + CPPUNIT_TEST(testVorbis); + CPPUNIT_TEST(testSpeex); + CPPUNIT_TEST(testFLAC); + CPPUNIT_TEST(testMP3); + CPPUNIT_TEST(testOGA_FLAC); + CPPUNIT_TEST(testOGA_Vorbis); + CPPUNIT_TEST(testMP4_1); + CPPUNIT_TEST(testMP4_2); + CPPUNIT_TEST(testMP4_3); + CPPUNIT_TEST(testMP4_4); + CPPUNIT_TEST(testTrueAudio); + CPPUNIT_TEST(testAPE); + CPPUNIT_TEST(testWav); + CPPUNIT_TEST(testAIFF_1); + CPPUNIT_TEST(testAIFF_2); + CPPUNIT_TEST(testWavPack); + CPPUNIT_TEST(testOpus); + CPPUNIT_TEST(testUnsupported); + CPPUNIT_TEST(testCreate); + CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testDefaultFileExtensions); + CPPUNIT_TEST(testFileResolver); + CPPUNIT_TEST_SUITE_END(); + +public: + + template <typename T> + void fileRefSave(const string &filename, const string &ext) + { + ScopedFileCopy copy(filename, ext); + string newname = copy.fileName(); + + { + FileRef f(newname.c_str()); + CPPUNIT_ASSERT(dynamic_cast<T*>(f.file())); + CPPUNIT_ASSERT(!f.isNull()); + f.tag()->setArtist("test artist"); + f.tag()->setTitle("test title"); + f.tag()->setGenre("Test!"); + f.tag()->setAlbum("albummmm"); + f.tag()->setComment("a comment"); + f.tag()->setTrack(5); + f.tag()->setYear(2020); + f.save(); + } + { + FileRef f(newname.c_str()); + CPPUNIT_ASSERT(!f.isNull()); + CPPUNIT_ASSERT_EQUAL(f.tag()->artist(), String("test artist")); + CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("test title")); + CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("Test!")); + CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("albummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("a comment")); + CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)5); + CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2020); + f.tag()->setArtist("ttest artist"); + f.tag()->setTitle("ytest title"); + f.tag()->setGenre("uTest!"); + f.tag()->setAlbum("ialbummmm"); + f.tag()->setComment("another comment"); + f.tag()->setTrack(7); + f.tag()->setYear(2080); + f.save(); + } + { + FileRef f(newname.c_str()); + CPPUNIT_ASSERT(!f.isNull()); + CPPUNIT_ASSERT_EQUAL(f.tag()->artist(), String("ttest artist")); + CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("ytest title")); + CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("uTest!")); + CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("ialbummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("another comment")); + CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7); + CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080); + } + + { + FileStream fs(newname.c_str()); + FileRef f(&fs); + CPPUNIT_ASSERT(dynamic_cast<T*>(f.file())); + CPPUNIT_ASSERT(!f.isNull()); + CPPUNIT_ASSERT_EQUAL(f.tag()->artist(), String("ttest artist")); + CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("ytest title")); + CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("uTest!")); + CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("ialbummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("another comment")); + CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7); + CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080); + f.tag()->setArtist("test artist"); + f.tag()->setTitle("test title"); + f.tag()->setGenre("Test!"); + f.tag()->setAlbum("albummmm"); + f.tag()->setComment("a comment"); + f.tag()->setTrack(5); + f.tag()->setYear(2020); + f.save(); + } + + ByteVector fileContent; + { + FileStream fs(newname.c_str()); + FileRef f(&fs); + CPPUNIT_ASSERT(dynamic_cast<T*>(f.file())); + CPPUNIT_ASSERT(!f.isNull()); + CPPUNIT_ASSERT_EQUAL(f.tag()->artist(), String("test artist")); + CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("test title")); + CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("Test!")); + CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("albummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("a comment")); + CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)5); + CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2020); + + fs.seek(0); + fileContent = fs.readBlock(fs.length()); + } + + { + ByteVectorStream bs(fileContent); + FileRef f(&bs); + CPPUNIT_ASSERT(dynamic_cast<T*>(f.file())); + CPPUNIT_ASSERT(!f.isNull()); + CPPUNIT_ASSERT_EQUAL(f.tag()->artist(), String("test artist")); + CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("test title")); + CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("Test!")); + CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("albummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("a comment")); + CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)5); + CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2020); + f.tag()->setArtist("ttest artist"); + f.tag()->setTitle("ytest title"); + f.tag()->setGenre("uTest!"); + f.tag()->setAlbum("ialbummmm"); + f.tag()->setComment("another comment"); + f.tag()->setTrack(7); + f.tag()->setYear(2080); + f.save(); + + fileContent = *bs.data(); + } + { + ByteVectorStream bs(fileContent); + FileRef f(&bs); + CPPUNIT_ASSERT(dynamic_cast<T*>(f.file())); + CPPUNIT_ASSERT(!f.isNull()); + CPPUNIT_ASSERT_EQUAL(f.tag()->artist(), String("ttest artist")); + CPPUNIT_ASSERT_EQUAL(f.tag()->title(), String("ytest title")); + CPPUNIT_ASSERT_EQUAL(f.tag()->genre(), String("uTest!")); + CPPUNIT_ASSERT_EQUAL(f.tag()->album(), String("ialbummmm")); + CPPUNIT_ASSERT_EQUAL(f.tag()->comment(), String("another comment")); + CPPUNIT_ASSERT_EQUAL(f.tag()->track(), (unsigned int)7); + CPPUNIT_ASSERT_EQUAL(f.tag()->year(), (unsigned int)2080); + } + } + + void testMusepack() + { + fileRefSave<MPC::File>("click", ".mpc"); + } + + void testASF() + { + fileRefSave<ASF::File>("silence-1", ".wma"); + } + + void testVorbis() + { + fileRefSave<Ogg::Vorbis::File>("empty", ".ogg"); + } + + void testSpeex() + { + fileRefSave<Ogg::Speex::File>("empty", ".spx"); + } + + void testFLAC() + { + fileRefSave<FLAC::File>("no-tags", ".flac"); + } + + void testMP3() + { + fileRefSave<MPEG::File>("xing", ".mp3"); + } + + void testTrueAudio() + { + fileRefSave<TrueAudio::File>("empty", ".tta"); + } + + void testMP4_1() + { + fileRefSave<MP4::File>("has-tags", ".m4a"); + } + + void testMP4_2() + { + fileRefSave<MP4::File>("no-tags", ".m4a"); + } + + void testMP4_3() + { + fileRefSave<MP4::File>("no-tags", ".3g2"); + } + + void testMP4_4() + { + fileRefSave<MP4::File>("blank_video", ".m4v"); + } + + void testWav() + { + fileRefSave<RIFF::WAV::File>("empty", ".wav"); + } + + void testOGA_FLAC() + { + fileRefSave<Ogg::FLAC::File>("empty_flac", ".oga"); + } + + void testOGA_Vorbis() + { + fileRefSave<Ogg::Vorbis::File>("empty_vorbis", ".oga"); + } + + void testAPE() + { + fileRefSave<APE::File>("mac-399", ".ape"); + } + + void testAIFF_1() + { + fileRefSave<RIFF::AIFF::File>("empty", ".aiff"); + } + + void testAIFF_2() + { + fileRefSave<RIFF::AIFF::File>("alaw", ".aifc"); + } + + void testWavPack() + { + fileRefSave<WavPack::File>("click", ".wv"); + } + + void testOpus() + { + fileRefSave<Ogg::Opus::File>("correctness_gain_silent_output", ".opus"); + } + + void testUnsupported() + { + FileRef f1(TEST_FILE_PATH_C("no-extension")); + CPPUNIT_ASSERT(f1.isNull()); + + FileRef f2(TEST_FILE_PATH_C("unsupported-extension.xx")); + CPPUNIT_ASSERT(f2.isNull()); + } + + void testCreate() + { + // This is deprecated. But worth it to test. + + File *f = FileRef::create(TEST_FILE_PATH_C("empty_vorbis.oga")); + CPPUNIT_ASSERT(dynamic_cast<Ogg::Vorbis::File*>(f)); + delete f; + + f = FileRef::create(TEST_FILE_PATH_C("xing.mp3")); + CPPUNIT_ASSERT(dynamic_cast<MPEG::File*>(f)); + delete f; + + f = FileRef::create(TEST_FILE_PATH_C("test.xm")); + CPPUNIT_ASSERT(dynamic_cast<XM::File*>(f)); + delete f; + } + + void testAudioProperties() + { + FileRef f(TEST_FILE_PATH_C("xing.mp3")); + const AudioProperties *audioProperties = f.audioProperties(); + CPPUNIT_ASSERT_EQUAL(2, audioProperties->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(2064, audioProperties->lengthInMilliseconds()); + } + + void testDefaultFileExtensions() + { + const StringList extensions = FileRef::defaultFileExtensions(); + CPPUNIT_ASSERT(extensions.contains("mpc")); + CPPUNIT_ASSERT(extensions.contains("wma")); + CPPUNIT_ASSERT(extensions.contains("ogg")); + CPPUNIT_ASSERT(extensions.contains("spx")); + CPPUNIT_ASSERT(extensions.contains("flac")); + CPPUNIT_ASSERT(extensions.contains("mp3")); + CPPUNIT_ASSERT(extensions.contains("tta")); + CPPUNIT_ASSERT(extensions.contains("m4a")); + CPPUNIT_ASSERT(extensions.contains("3g2")); + CPPUNIT_ASSERT(extensions.contains("m4v")); + CPPUNIT_ASSERT(extensions.contains("wav")); + CPPUNIT_ASSERT(extensions.contains("oga")); + CPPUNIT_ASSERT(extensions.contains("ape")); + CPPUNIT_ASSERT(extensions.contains("aiff")); + CPPUNIT_ASSERT(extensions.contains("aifc")); + CPPUNIT_ASSERT(extensions.contains("wv")); + CPPUNIT_ASSERT(extensions.contains("opus")); + CPPUNIT_ASSERT(extensions.contains("xm")); + } + + void testFileResolver() + { + { + FileRef f(TEST_FILE_PATH_C("xing.mp3")); + CPPUNIT_ASSERT(dynamic_cast<MPEG::File *>(f.file()) != NULL); + } + + DummyResolver resolver; + FileRef::addFileTypeResolver(&resolver); + + { + FileRef f(TEST_FILE_PATH_C("xing.mp3")); + CPPUNIT_ASSERT(dynamic_cast<Ogg::Vorbis::File *>(f.file()) != NULL); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestFileRef); diff --git a/Frameworks/TagLib/taglib/tests/test_flac.cpp b/Frameworks/TagLib/taglib/tests/test_flac.cpp new file mode 100644 index 000000000..c83f1e90c --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_flac.cpp @@ -0,0 +1,672 @@ +/*************************************************************************** + copyright : (C) 2009 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <tstringlist.h> +#include <tbytevectorlist.h> +#include <tpropertymap.h> +#include <flacfile.h> +#include <xiphcomment.h> +#include <id3v1tag.h> +#include <id3v2tag.h> +#include <cppunit/extensions/HelperMacros.h> +#include "plainfile.h" +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestFLAC : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestFLAC); + CPPUNIT_TEST(testSignature); + CPPUNIT_TEST(testMultipleCommentBlocks); + CPPUNIT_TEST(testReadPicture); + CPPUNIT_TEST(testAddPicture); + CPPUNIT_TEST(testReplacePicture); + CPPUNIT_TEST(testRemoveAllPictures); + CPPUNIT_TEST(testRepeatedSave1); + CPPUNIT_TEST(testRepeatedSave2); + CPPUNIT_TEST(testRepeatedSave3); + CPPUNIT_TEST(testSaveMultipleValues); + CPPUNIT_TEST(testDict); + CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testInvalid); + CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testZeroSizedPadding1); + CPPUNIT_TEST(testZeroSizedPadding2); + CPPUNIT_TEST(testShrinkPadding); + CPPUNIT_TEST(testSaveID3v1); + CPPUNIT_TEST(testUpdateID3v2); + CPPUNIT_TEST(testEmptyID3v2); + CPPUNIT_TEST(testStripTags); + CPPUNIT_TEST(testRemoveXiphField); + CPPUNIT_TEST(testEmptySeekTable); + CPPUNIT_TEST(testPictureStoredAfterComment); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testSignature() + { + FLAC::File f(TEST_FILE_PATH_C("no-tags.flac")); + CPPUNIT_ASSERT_EQUAL(ByteVector("a1b141f766e9849ac3db1030a20a3c77"), f.audioProperties()->signature().toHex()); + } + + void testMultipleCommentBlocks() + { + ScopedFileCopy copy("multiple-vc", ".flac"); + string newname = copy.fileName(); + + { + FLAC::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("Artist 1"), f.tag()->artist()); + f.tag()->setArtist("The Artist"); + f.save(); + } + { + FLAC::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("The Artist"), f.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(69L, f.find("Artist")); + CPPUNIT_ASSERT_EQUAL(-1L, f.find("Artist", 70)); + } + } + + void testReadPicture() + { + ScopedFileCopy copy("silence-44-s", ".flac"); + string newname = copy.fileName(); + + FLAC::File f(newname.c_str()); + List<FLAC::Picture *> lst = f.pictureList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size()); + + FLAC::Picture *pic = lst.front(); + CPPUNIT_ASSERT_EQUAL(FLAC::Picture::FrontCover, pic->type()); + CPPUNIT_ASSERT_EQUAL(1, pic->width()); + CPPUNIT_ASSERT_EQUAL(1, pic->height()); + CPPUNIT_ASSERT_EQUAL(24, pic->colorDepth()); + CPPUNIT_ASSERT_EQUAL(0, pic->numColors()); + CPPUNIT_ASSERT_EQUAL(String("image/png"), pic->mimeType()); + CPPUNIT_ASSERT_EQUAL(String("A pixel."), pic->description()); + CPPUNIT_ASSERT_EQUAL((unsigned int)150, pic->data().size()); + } + + void testAddPicture() + { + ScopedFileCopy copy("silence-44-s", ".flac"); + string newname = copy.fileName(); + + { + FLAC::File f(newname.c_str()); + List<FLAC::Picture *> lst = f.pictureList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size()); + + FLAC::Picture *newpic = new FLAC::Picture(); + newpic->setType(FLAC::Picture::BackCover); + newpic->setWidth(5); + newpic->setHeight(6); + newpic->setColorDepth(16); + newpic->setNumColors(7); + newpic->setMimeType("image/jpeg"); + newpic->setDescription("new image"); + newpic->setData("JPEG data"); + f.addPicture(newpic); + f.save(); + } + { + FLAC::File f(newname.c_str()); + List<FLAC::Picture *> lst = f.pictureList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, lst.size()); + + FLAC::Picture *pic = lst[0]; + CPPUNIT_ASSERT_EQUAL(FLAC::Picture::FrontCover, pic->type()); + CPPUNIT_ASSERT_EQUAL(1, pic->width()); + CPPUNIT_ASSERT_EQUAL(1, pic->height()); + CPPUNIT_ASSERT_EQUAL(24, pic->colorDepth()); + CPPUNIT_ASSERT_EQUAL(0, pic->numColors()); + CPPUNIT_ASSERT_EQUAL(String("image/png"), pic->mimeType()); + CPPUNIT_ASSERT_EQUAL(String("A pixel."), pic->description()); + CPPUNIT_ASSERT_EQUAL((unsigned int)150, pic->data().size()); + + pic = lst[1]; + CPPUNIT_ASSERT_EQUAL(FLAC::Picture::BackCover, pic->type()); + CPPUNIT_ASSERT_EQUAL(5, pic->width()); + CPPUNIT_ASSERT_EQUAL(6, pic->height()); + CPPUNIT_ASSERT_EQUAL(16, pic->colorDepth()); + CPPUNIT_ASSERT_EQUAL(7, pic->numColors()); + CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), pic->mimeType()); + CPPUNIT_ASSERT_EQUAL(String("new image"), pic->description()); + CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), pic->data()); + } + } + + void testReplacePicture() + { + ScopedFileCopy copy("silence-44-s", ".flac"); + string newname = copy.fileName(); + + { + FLAC::File f(newname.c_str()); + List<FLAC::Picture *> lst = f.pictureList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size()); + + FLAC::Picture *newpic = new FLAC::Picture(); + newpic->setType(FLAC::Picture::BackCover); + newpic->setWidth(5); + newpic->setHeight(6); + newpic->setColorDepth(16); + newpic->setNumColors(7); + newpic->setMimeType("image/jpeg"); + newpic->setDescription("new image"); + newpic->setData("JPEG data"); + f.removePictures(); + f.addPicture(newpic); + f.save(); + } + { + FLAC::File f(newname.c_str()); + List<FLAC::Picture *> lst = f.pictureList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size()); + + FLAC::Picture *pic = lst[0]; + CPPUNIT_ASSERT_EQUAL(FLAC::Picture::BackCover, pic->type()); + CPPUNIT_ASSERT_EQUAL(5, pic->width()); + CPPUNIT_ASSERT_EQUAL(6, pic->height()); + CPPUNIT_ASSERT_EQUAL(16, pic->colorDepth()); + CPPUNIT_ASSERT_EQUAL(7, pic->numColors()); + CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), pic->mimeType()); + CPPUNIT_ASSERT_EQUAL(String("new image"), pic->description()); + CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), pic->data()); + } + } + + void testRemoveAllPictures() + { + ScopedFileCopy copy("silence-44-s", ".flac"); + string newname = copy.fileName(); + + { + FLAC::File f(newname.c_str()); + List<FLAC::Picture *> lst = f.pictureList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size()); + + f.removePictures(); + f.save(); + } + { + FLAC::File f(newname.c_str()); + List<FLAC::Picture *> lst = f.pictureList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)0, lst.size()); + } + } + + void testRepeatedSave1() + { + ScopedFileCopy copy("silence-44-s", ".flac"); + string newname = copy.fileName(); + + { + FLAC::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("Silence"), f.tag()->title()); + f.tag()->setTitle("NEW TITLE"); + f.save(); + CPPUNIT_ASSERT_EQUAL(String("NEW TITLE"), f.tag()->title()); + f.tag()->setTitle("NEW TITLE 2"); + f.save(); + CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), f.tag()->title()); + } + { + FLAC::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("NEW TITLE 2"), f.tag()->title()); + } + } + + void testRepeatedSave2() + { + ScopedFileCopy copy("no-tags", ".flac"); + + FLAC::File f(copy.fileName().c_str()); + f.ID3v2Tag(true)->setTitle("0123456789"); + f.save(); + CPPUNIT_ASSERT_EQUAL(5735L, f.length()); + f.save(); + CPPUNIT_ASSERT_EQUAL(5735L, f.length()); + CPPUNIT_ASSERT(f.find("fLaC") >= 0); + } + + void testRepeatedSave3() + { + ScopedFileCopy copy("no-tags", ".flac"); + + FLAC::File f(copy.fileName().c_str()); + f.xiphComment()->setTitle(longText(8 * 1024)); + f.save(); + CPPUNIT_ASSERT_EQUAL(12862L, f.length()); + f.save(); + CPPUNIT_ASSERT_EQUAL(12862L, f.length()); + } + + void testSaveMultipleValues() + { + ScopedFileCopy copy("silence-44-s", ".flac"); + string newname = copy.fileName(); + + { + FLAC::File f(newname.c_str()); + f.xiphComment(true)->addField("ARTIST", "artist 1", true); + f.xiphComment(true)->addField("ARTIST", "artist 2", false); + f.save(); + } + { + FLAC::File f(newname.c_str()); + Ogg::FieldListMap m = f.xiphComment()->fieldListMap(); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, m["ARTIST"].size()); + CPPUNIT_ASSERT_EQUAL(String("artist 1"), m["ARTIST"][0]); + CPPUNIT_ASSERT_EQUAL(String("artist 2"), m["ARTIST"][1]); + } + } + + void testDict() + { + // test unicode & multiple values with dict interface + ScopedFileCopy copy("silence-44-s", ".flac"); + string newname = copy.fileName(); + + { + FLAC::File f(newname.c_str()); + PropertyMap dict; + dict["ARTIST"].append("artøst 1"); + dict["ARTIST"].append("artöst 2"); + f.setProperties(dict); + f.save(); + } + { + FLAC::File f(newname.c_str()); + PropertyMap dict = f.properties(); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, dict["ARTIST"].size()); + CPPUNIT_ASSERT_EQUAL(String("artøst 1"), dict["ARTIST"][0]); + CPPUNIT_ASSERT_EQUAL(String("artöst 2"), dict["ARTIST"][1]); + } + } + + void testProperties() + { + PropertyMap tags; + tags["ALBUM"] = StringList("Album"); + tags["ALBUMARTIST"] = StringList("Album Artist"); + tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + tags["ALBUMSORT"] = StringList("Album Sort"); + tags["ARTIST"] = StringList("Artist"); + tags["ARTISTS"] = StringList("Artists"); + tags["ARTISTSORT"] = StringList("Artist Sort"); + tags["ASIN"] = StringList("ASIN"); + tags["BARCODE"] = StringList("Barcode"); + tags["CATALOGNUMBER"] = StringList("Catalog Number 1").append("Catalog Number 2"); + tags["COMMENT"] = StringList("Comment"); + tags["DATE"] = StringList("2021-01-10"); + tags["DISCNUMBER"] = StringList("3"); + tags["DISCTOTAL"] = StringList("5"); + tags["GENRE"] = StringList("Genre"); + tags["ISRC"] = StringList("UKAAA0500001"); + tags["LABEL"] = StringList("Label 1").append("Label 2"); + tags["MEDIA"] = StringList("Media"); + tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); + tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + tags["ORIGINALDATE"] = StringList("2021-01-09"); + tags["RELEASECOUNTRY"] = StringList("Release Country"); + tags["RELEASESTATUS"] = StringList("Release Status"); + tags["RELEASETYPE"] = StringList("Release Type"); + tags["SCRIPT"] = StringList("Script"); + tags["TITLE"] = StringList("Title"); + tags["TRACKNUMBER"] = StringList("2"); + tags["TRACKTOTAL"] = StringList("4"); + + ScopedFileCopy copy("no-tags", ".flac"); + { + FLAC::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + CPPUNIT_ASSERT(properties.isEmpty()); + f.setProperties(tags); + f.save(); + } + { + const FLAC::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + if (tags != properties) { + CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString()); + } + CPPUNIT_ASSERT(tags == properties); + } + } + + void testInvalid() + { + ScopedFileCopy copy("silence-44-s", ".flac"); + PropertyMap map; + map[L"H\x00c4\x00d6"] = String("bla"); + FLAC::File f(copy.fileName().c_str()); + PropertyMap invalid = f.setProperties(map); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, invalid.size()); + CPPUNIT_ASSERT_EQUAL((unsigned int)0, f.properties().size()); + } + + void testAudioProperties() + { + FLAC::File f(TEST_FILE_PATH_C("sinewave.flac")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(145, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(156556ULL, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL( + ByteVector("\xcf\xe3\xd9\xda\xba\xde\xab\x2c\xbf\x2c\xa2\x35\x27\x4b\x7f\x76"), + f.audioProperties()->signature()); + } + + void testZeroSizedPadding1() + { + ScopedFileCopy copy("zero-sized-padding", ".flac"); + + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + } + + void testZeroSizedPadding2() + { + ScopedFileCopy copy("silence-44-s", ".flac"); + + { + FLAC::File f(copy.fileName().c_str()); + f.xiphComment()->setTitle("ABC"); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + f.xiphComment()->setTitle(std::string(3067, 'X').c_str()); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + } + } + + void testShrinkPadding() + { + ScopedFileCopy copy("no-tags", ".flac"); + + { + FLAC::File f(copy.fileName().c_str()); + f.xiphComment()->setTitle(longText(128 * 1024)); + f.save(); + CPPUNIT_ASSERT(f.length() > 128 * 1024); + } + { + FLAC::File f(copy.fileName().c_str()); + f.xiphComment()->setTitle("0123456789"); + f.save(); + CPPUNIT_ASSERT(f.length() < 8 * 1024); + } + } + + void testSaveID3v1() + { + ScopedFileCopy copy("no-tags", ".flac"); + + ByteVector audioStream; + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + CPPUNIT_ASSERT_EQUAL((long)4692, f.length()); + + f.seek(0x0100); + audioStream = f.readBlock(4436); + + f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.save(); + CPPUNIT_ASSERT(f.hasID3v1Tag()); + CPPUNIT_ASSERT_EQUAL((long)4820, f.length()); + + f.seek(0x0100); + CPPUNIT_ASSERT_EQUAL(audioStream, f.readBlock(4436)); + } + } + + void testUpdateID3v2() + { + ScopedFileCopy copy("no-tags", ".flac"); + + { + FLAC::File f(copy.fileName().c_str()); + f.ID3v2Tag(true)->setTitle("0123456789"); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + f.ID3v2Tag()->setTitle("ABCDEFGHIJ"); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("ABCDEFGHIJ"), f.ID3v2Tag()->title()); + } + } + + void testEmptyID3v2() + { + ScopedFileCopy copy("no-tags", ".flac"); + + { + FLAC::File f(copy.fileName().c_str()); + f.ID3v2Tag(true); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + } + } + + void testStripTags() + { + ScopedFileCopy copy("silence-44-s", ".flac"); + + { + FLAC::File f(copy.fileName().c_str()); + f.xiphComment(true)->setTitle("XiphComment Title"); + f.ID3v1Tag(true)->setTitle("ID3v1 Title"); + f.ID3v2Tag(true)->setTitle("ID3v2 Title"); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasXiphComment()); + CPPUNIT_ASSERT(f.hasID3v1Tag()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String("XiphComment Title"), f.xiphComment()->title()); + CPPUNIT_ASSERT_EQUAL(String("ID3v1 Title"), f.ID3v1Tag()->title()); + CPPUNIT_ASSERT_EQUAL(String("ID3v2 Title"), f.ID3v2Tag()->title()); + f.strip(FLAC::File::ID3v2); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasXiphComment()); + CPPUNIT_ASSERT(f.hasID3v1Tag()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String("XiphComment Title"), f.xiphComment()->title()); + CPPUNIT_ASSERT_EQUAL(String("ID3v1 Title"), f.ID3v1Tag()->title()); + f.strip(FLAC::File::ID3v1); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasXiphComment()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String("XiphComment Title"), f.xiphComment()->title()); + f.strip(FLAC::File::XiphComment); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasXiphComment()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.xiphComment()->isEmpty()); + CPPUNIT_ASSERT_EQUAL(String("reference libFLAC 1.1.0 20030126"), f.xiphComment()->vendorID()); + } + } + + void testRemoveXiphField() + { + ScopedFileCopy copy("silence-44-s", ".flac"); + + { + FLAC::File f(copy.fileName().c_str()); + f.xiphComment(true)->setTitle("XiphComment Title"); + f.ID3v2Tag(true)->setTitle("ID3v2 Title"); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("XiphComment Title"), f.xiphComment()->title()); + f.xiphComment()->removeFields("TITLE"); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String(), f.xiphComment()->title()); + } + } + + void testEmptySeekTable() + { + ScopedFileCopy copy("empty-seektable", ".flac"); + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + f.xiphComment(true)->setTitle("XiphComment Title"); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + f.seek(42); + const ByteVector data = f.readBlock(4); + CPPUNIT_ASSERT_EQUAL(ByteVector("\x03\x00\x00\x00", 4), data); + } + } + + void testPictureStoredAfterComment() + { + // Blank.png from https://commons.wikimedia.org/wiki/File:Blank.png + const unsigned char blankPngData[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x9d, 0x74, 0x66, 0x1a, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3, 0x01, 0xc7, 0x6f, 0xa8, 0x64, 0x00, + 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0xc0, 0x01, + 0x18, 0x18, 0x00, 0x00, 0x1a, 0x00, 0x01, 0x82, 0x92, 0x4d, 0x60, 0x00, + 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 + }; + const ByteVector picData(reinterpret_cast<const char *>(blankPngData), + sizeof(blankPngData)); + + ScopedFileCopy copy("no-tags", ".flac"); + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT(!f.hasXiphComment()); + CPPUNIT_ASSERT(f.pictureList().isEmpty()); + + FLAC::Picture *pic = new FLAC::Picture; + pic->setData(picData); + pic->setType(FLAC::Picture::FrontCover); + pic->setMimeType("image/png"); + pic->setDescription("blank.png"); + pic->setWidth(3); + pic->setHeight(2); + pic->setColorDepth(32); + pic->setNumColors(0); + f.addPicture(pic); + f.xiphComment(true)->setTitle("Title"); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.hasXiphComment()); + const List<FLAC::Picture *> pictures = f.pictureList(); + CPPUNIT_ASSERT_EQUAL(1U, pictures.size()); + CPPUNIT_ASSERT_EQUAL(picData, pictures[0]->data()); + CPPUNIT_ASSERT_EQUAL(FLAC::Picture::FrontCover, pictures[0]->type()); + CPPUNIT_ASSERT_EQUAL(String("image/png"), pictures[0]->mimeType()); + CPPUNIT_ASSERT_EQUAL(String("blank.png"), pictures[0]->description()); + CPPUNIT_ASSERT_EQUAL(3, pictures[0]->width()); + CPPUNIT_ASSERT_EQUAL(2, pictures[0]->height()); + CPPUNIT_ASSERT_EQUAL(32, pictures[0]->colorDepth()); + CPPUNIT_ASSERT_EQUAL(0, pictures[0]->numColors()); + CPPUNIT_ASSERT_EQUAL(String("Title"), f.xiphComment(false)->title()); + } + + const unsigned char expectedHeadData[] = { + 'f', 'L', 'a', 'C', 0x00, 0x00, 0x00, 0x22, 0x12, 0x00, 0x12, 0x00, + 0x00, 0x00, 0x0e, 0x00, 0x00, 0x10, 0x0a, 0xc4, 0x42, 0xf0, 0x00, 0x02, + 0x7a, 0xc0, 0xa1, 0xb1, 0x41, 0xf7, 0x66, 0xe9, 0x84, 0x9a, 0xc3, 0xdb, + 0x10, 0x30, 0xa2, 0x0a, 0x3c, 0x77, 0x04, 0x00, 0x00, 0x17, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 'T', 'I', + 'T', 'L', 'E', '=', 'T', 'i', 't', 'l', 'e', 0x06, 0x00, 0x00, + 0xa9, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x09, 'i', 'm', 'a', + 'g', 'e', '/', 'p', 'n', 'g', 0x00, 0x00, 0x00, 0x09, 'b', 'l', + 'a', 'n', 'k', '.', 'p', 'n', 'g', 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x77 + }; + ByteVector expectedData(reinterpret_cast<const char *>(expectedHeadData), + sizeof(expectedHeadData)); + expectedData.append(picData); + const ByteVector fileData = PlainFile(copy.fileName().c_str()).readAll(); + CPPUNIT_ASSERT(fileData.startsWith(expectedData)); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestFLAC); diff --git a/Frameworks/TagLib/taglib/tests/test_flacpicture.cpp b/Frameworks/TagLib/taglib/tests/test_flacpicture.cpp new file mode 100644 index 000000000..6613c5981 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_flacpicture.cpp @@ -0,0 +1,77 @@ +/*************************************************************************** + copyright : (C) 2010 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <tstringlist.h> +#include <tbytevectorlist.h> +#include <flacfile.h> +#include <flacmetadatablock.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestFLACPicture : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestFLACPicture); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST(testPassThrough); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testParse() + { + const unsigned char data[] = { 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x09, 0x69, 0x6D, 0x61, 0x67, 0x65, 0x2F, 0x70, 0x6E, 0x67, 0x00, 0x00, 0x00, 0x08, 0x41, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6C, 0x2E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xDE, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0B, 0x13, 0x00, 0x00, 0x0B, 0x13, 0x01, 0x00, 0x9A, 0x9C, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4D, 0x45, 0x07, 0xD6, 0x0B, 0x1C, 0x0A, 0x36, 0x06, 0x08, 0x44, 0x3D, 0x32, 0x00, 0x00, 0x00, 0x1D, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6F, 0x6D, 0x6D, 0x65, 0x6E, 0x74, 0x00, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x54, 0x68, 0x65, 0x20, 0x47, 0x49, 0x4D, 0x50, 0xEF, 0x64, 0x25, 0x6E, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08, 0xD7, 0x63, 0xF8, 0xFF, 0xFF, 0x3F, 0x00, 0x05, 0xFE, 0x02, 0xFE, 0xDC, 0xCC, 0x59, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; + const char *pdata = reinterpret_cast<const char*>(data); + + FLAC::Picture pic(ByteVector(pdata, 199)); + + CPPUNIT_ASSERT_EQUAL(3, int(pic.type())); + CPPUNIT_ASSERT_EQUAL(1, pic.width()); + CPPUNIT_ASSERT_EQUAL(1, pic.height()); + CPPUNIT_ASSERT_EQUAL(24, pic.colorDepth()); + CPPUNIT_ASSERT_EQUAL(0, pic.numColors()); + CPPUNIT_ASSERT_EQUAL(String("image/png"), pic.mimeType()); + CPPUNIT_ASSERT_EQUAL(String("A pixel."), pic.description()); + CPPUNIT_ASSERT_EQUAL((unsigned int)150, pic.data().size()); + } + + void testPassThrough() + { + const unsigned char data[] = { 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x09, 0x69, 0x6D, 0x61, 0x67, 0x65, 0x2F, 0x70, 0x6E, 0x67, 0x00, 0x00, 0x00, 0x08, 0x41, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6C, 0x2E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xDE, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0B, 0x13, 0x00, 0x00, 0x0B, 0x13, 0x01, 0x00, 0x9A, 0x9C, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4D, 0x45, 0x07, 0xD6, 0x0B, 0x1C, 0x0A, 0x36, 0x06, 0x08, 0x44, 0x3D, 0x32, 0x00, 0x00, 0x00, 0x1D, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6F, 0x6D, 0x6D, 0x65, 0x6E, 0x74, 0x00, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x54, 0x68, 0x65, 0x20, 0x47, 0x49, 0x4D, 0x50, 0xEF, 0x64, 0x25, 0x6E, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08, 0xD7, 0x63, 0xF8, 0xFF, 0xFF, 0x3F, 0x00, 0x05, 0xFE, 0x02, 0xFE, 0xDC, 0xCC, 0x59, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; + const char *pdata = reinterpret_cast<const char*>(data); + + FLAC::Picture pic(ByteVector(pdata, 199)); + CPPUNIT_ASSERT_EQUAL(ByteVector(pdata, 199), pic.render()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestFLACPicture); + diff --git a/Frameworks/TagLib/taglib/tests/test_flacunknownmetadatablock.cpp b/Frameworks/TagLib/taglib/tests/test_flacunknownmetadatablock.cpp new file mode 100644 index 000000000..d08a9bae3 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_flacunknownmetadatablock.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + copyright : (C) 2012 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <tstringlist.h> +#include <tbytevectorlist.h> +#include <flacunknownmetadatablock.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestFLACUnknownMetadataBlock : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestFLACUnknownMetadataBlock); + CPPUNIT_TEST(testAccessors); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testAccessors() + { + ByteVector data("abc\x01", 4); + FLAC::UnknownMetadataBlock block(42, data); + CPPUNIT_ASSERT_EQUAL(42, block.code()); + CPPUNIT_ASSERT_EQUAL(data, block.data()); + CPPUNIT_ASSERT_EQUAL(data, block.render()); + ByteVector data2("xxx", 3); + block.setCode(13); + block.setData(data2); + CPPUNIT_ASSERT_EQUAL(13, block.code()); + CPPUNIT_ASSERT_EQUAL(data2, block.data()); + CPPUNIT_ASSERT_EQUAL(data2, block.render()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestFLACUnknownMetadataBlock); diff --git a/Frameworks/TagLib/taglib/tests/test_id3v1.cpp b/Frameworks/TagLib/taglib/tests/test_id3v1.cpp new file mode 100644 index 000000000..d3f037aa2 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_id3v1.cpp @@ -0,0 +1,88 @@ +/*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tstring.h> +#include <mpegfile.h> +#include <id3v1tag.h> +#include <id3v1genres.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestID3v1 : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestID3v1); + CPPUNIT_TEST(testStripWhiteSpace); + CPPUNIT_TEST(testGenres); + CPPUNIT_TEST(testRenamedGenres); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testStripWhiteSpace() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + { + MPEG::File f(newname.c_str()); + f.ID3v1Tag(true)->setArtist("Artist "); + f.save(); + } + + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT(f.ID3v1Tag(false)); + CPPUNIT_ASSERT_EQUAL(String("Artist"), f.ID3v1Tag(false)->artist()); + } + } + + void testGenres() + { + CPPUNIT_ASSERT_EQUAL(String("Darkwave"), ID3v1::genre(50)); + CPPUNIT_ASSERT_EQUAL(100, ID3v1::genreIndex("Humour")); + CPPUNIT_ASSERT(ID3v1::genreList().contains("Heavy Metal")); + CPPUNIT_ASSERT_EQUAL(79, ID3v1::genreMap()["Hard Rock"]); + } + + void testRenamedGenres() + { + CPPUNIT_ASSERT_EQUAL(String("Bebop"), ID3v1::genre(85)); + CPPUNIT_ASSERT_EQUAL(85, ID3v1::genreIndex("Bebop")); + CPPUNIT_ASSERT_EQUAL(85, ID3v1::genreIndex("Bebob")); + + ID3v1::Tag tag; + tag.setGenre("Hardcore"); + CPPUNIT_ASSERT_EQUAL(String("Hardcore Techno"), tag.genre()); + CPPUNIT_ASSERT_EQUAL(129U, tag.genreNumber()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v1); diff --git a/Frameworks/TagLib/taglib/tests/test_id3v2.cpp b/Frameworks/TagLib/taglib/tests/test_id3v2.cpp new file mode 100644 index 000000000..521f460a4 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_id3v2.cpp @@ -0,0 +1,1613 @@ + /*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <id3v2tag.h> +#include <mpegfile.h> +#include <id3v2frame.h> +#include <uniquefileidentifierframe.h> +#include <textidentificationframe.h> +#include <attachedpictureframe.h> +#include <unsynchronizedlyricsframe.h> +#include <synchronizedlyricsframe.h> +#include <eventtimingcodesframe.h> +#include <generalencapsulatedobjectframe.h> +#include <relativevolumeframe.h> +#include <popularimeterframe.h> +#include <urllinkframe.h> +#include <ownershipframe.h> +#include <unknownframe.h> +#include <chapterframe.h> +#include <tableofcontentsframe.h> +#include <commentsframe.h> +#include <podcastframe.h> +#include <privateframe.h> +#include <tdebug.h> +#include <tpropertymap.h> +#include <tzlib.h> +#include <cppunit/extensions/HelperMacros.h> +#include "plainfile.h" +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class PublicFrame : public ID3v2::Frame +{ + public: + PublicFrame() : ID3v2::Frame(ByteVector("XXXX\0\0\0\0\0\0", 10)) {} + String readStringField(const ByteVector &data, String::Type encoding, + int *position = 0) + { return ID3v2::Frame::readStringField(data, encoding, position); } + virtual String toString() const { return String(); } + virtual void parseFields(const ByteVector &) {} + virtual ByteVector renderFields() const { return ByteVector(); } +}; + +class TestID3v2 : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestID3v2); + CPPUNIT_TEST(testUnsynchDecode); + CPPUNIT_TEST(testDowngradeUTF8ForID3v23_1); + CPPUNIT_TEST(testDowngradeUTF8ForID3v23_2); + CPPUNIT_TEST(testUTF16BEDelimiter); + CPPUNIT_TEST(testUTF16Delimiter); + CPPUNIT_TEST(testReadStringField); + CPPUNIT_TEST(testParseAPIC); + CPPUNIT_TEST(testParseAPIC_UTF16_BOM); + CPPUNIT_TEST(testParseAPICv22); + CPPUNIT_TEST(testRenderAPIC); + CPPUNIT_TEST(testDontRender22); + CPPUNIT_TEST(testParseGEOB); + CPPUNIT_TEST(testRenderGEOB); + CPPUNIT_TEST(testPOPMtoString); + CPPUNIT_TEST(testParsePOPM); + CPPUNIT_TEST(testParsePOPMWithoutCounter); + CPPUNIT_TEST(testRenderPOPM); + CPPUNIT_TEST(testPOPMFromFile); + CPPUNIT_TEST(testParseRelativeVolumeFrame); + CPPUNIT_TEST(testRenderRelativeVolumeFrame); + CPPUNIT_TEST(testParseUniqueFileIdentifierFrame); + CPPUNIT_TEST(testParseEmptyUniqueFileIdentifierFrame); + CPPUNIT_TEST(testRenderUniqueFileIdentifierFrame); + CPPUNIT_TEST(testBrokenFrame1); + CPPUNIT_TEST(testItunes24FrameSize); + CPPUNIT_TEST(testParseUrlLinkFrame); + CPPUNIT_TEST(testRenderUrlLinkFrame); + CPPUNIT_TEST(testParseUserUrlLinkFrame); + CPPUNIT_TEST(testRenderUserUrlLinkFrame); + CPPUNIT_TEST(testParseOwnershipFrame); + CPPUNIT_TEST(testRenderOwnershipFrame); + CPPUNIT_TEST(testParseSynchronizedLyricsFrame); + CPPUNIT_TEST(testParseSynchronizedLyricsFrameWithEmptyDescritpion); + CPPUNIT_TEST(testRenderSynchronizedLyricsFrame); + CPPUNIT_TEST(testParseEventTimingCodesFrame); + CPPUNIT_TEST(testRenderEventTimingCodesFrame); + CPPUNIT_TEST(testParseCommentsFrame); + CPPUNIT_TEST(testRenderCommentsFrame); + CPPUNIT_TEST(testParsePodcastFrame); + CPPUNIT_TEST(testRenderPodcastFrame); + CPPUNIT_TEST(testParsePrivateFrame); + CPPUNIT_TEST(testRenderPrivateFrame); + CPPUNIT_TEST(testSaveUTF16Comment); + CPPUNIT_TEST(testUpdateGenre23_1); + CPPUNIT_TEST(testUpdateGenre23_2); + CPPUNIT_TEST(testUpdateGenre23_3); + CPPUNIT_TEST(testUpdateGenre24); + CPPUNIT_TEST(testUpdateDate22); + CPPUNIT_TEST(testDowngradeTo23); + // CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together + CPPUNIT_TEST(testCompressedFrameWithBrokenLength); + CPPUNIT_TEST(testW000); + CPPUNIT_TEST(testPropertyInterface); + CPPUNIT_TEST(testPropertyInterface2); + CPPUNIT_TEST(testPropertiesMovement); + CPPUNIT_TEST(testPropertyGrouping); + CPPUNIT_TEST(testDeleteFrame); + CPPUNIT_TEST(testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2); + CPPUNIT_TEST(testParseChapterFrame); + CPPUNIT_TEST(testRenderChapterFrame); + CPPUNIT_TEST(testParseTableOfContentsFrame); + CPPUNIT_TEST(testRenderTableOfContentsFrame); + CPPUNIT_TEST(testShrinkPadding); + CPPUNIT_TEST(testEmptyFrame); + CPPUNIT_TEST(testDuplicateTags); + CPPUNIT_TEST(testParseTOCFrameWithManyChildren); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testUnsynchDecode() + { + MPEG::File f(TEST_FILE_PATH_C("unsynch.id3"), false); + CPPUNIT_ASSERT(f.tag()); + CPPUNIT_ASSERT_EQUAL(String("My babe just cares for me"), f.tag()->title()); + } + + void testDowngradeUTF8ForID3v23_1() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + ID3v2::TextIdentificationFrame *f + = new ID3v2::TextIdentificationFrame(ByteVector("TPE1"), String::UTF8); + StringList sl; + sl.append("Foo"); + f->setText(sl); + + MPEG::File file(newname.c_str()); + file.ID3v2Tag(true)->addFrame(f); + file.save(MPEG::File::ID3v2, File::StripOthers, ID3v2::v3); + CPPUNIT_ASSERT_EQUAL(true, file.hasID3v2Tag()); + + ByteVector data = f->render(); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4+4+2+1+6+2), data.size()); + + ID3v2::TextIdentificationFrame f2(data); + CPPUNIT_ASSERT_EQUAL(sl, f2.fieldList()); + CPPUNIT_ASSERT_EQUAL(String::UTF16, f2.textEncoding()); + } + + void testDowngradeUTF8ForID3v23_2() + { + ScopedFileCopy copy("xing", ".mp3"); + + ID3v2::UnsynchronizedLyricsFrame *f + = new ID3v2::UnsynchronizedLyricsFrame(String::UTF8); + f->setText("Foo"); + + MPEG::File file(copy.fileName().c_str()); + file.ID3v2Tag(true)->addFrame(f); + file.save(MPEG::File::ID3v2, File::StripOthers, ID3v2::v3); + CPPUNIT_ASSERT(file.hasID3v2Tag()); + + ByteVector data = f->render(); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4+4+2+1+3+2+2+6+2), data.size()); + + ID3v2::UnsynchronizedLyricsFrame f2(data); + CPPUNIT_ASSERT_EQUAL(String("Foo"), f2.text()); + CPPUNIT_ASSERT_EQUAL(String::UTF16, f2.textEncoding()); + } + + void testUTF16BEDelimiter() + { + ID3v2::TextIdentificationFrame f(ByteVector("TPE1"), String::UTF16BE); + StringList sl; + sl.append("Foo"); + sl.append("Bar"); + f.setText(sl); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4+4+2+1+6+2+6), f.render().size()); + } + + void testUTF16Delimiter() + { + ID3v2::TextIdentificationFrame f(ByteVector("TPE1"), String::UTF16); + StringList sl; + sl.append("Foo"); + sl.append("Bar"); + f.setText(sl); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4+4+2+1+8+2+8), f.render().size()); + } + + void testBrokenFrame1() + { + MPEG::File f(TEST_FILE_PATH_C("broken-tenc.id3"), false); + CPPUNIT_ASSERT(f.tag()); + CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TENC")); + } + + void testReadStringField() + { + PublicFrame f; + ByteVector data("abc\0", 4); + String str = f.readStringField(data, String::Latin1); + CPPUNIT_ASSERT_EQUAL(String("abc"), str); + } + + // http://bugs.kde.org/show_bug.cgi?id=151078 + void testParseAPIC() + { + ID3v2::AttachedPictureFrame f(ByteVector("APIC" + "\x00\x00\x00\x07" + "\x00\x00" + "\x00" + "m\x00" + "\x01" + "d\x00" + "\x00", 17)); + CPPUNIT_ASSERT_EQUAL(String("m"), f.mimeType()); + CPPUNIT_ASSERT_EQUAL(ID3v2::AttachedPictureFrame::FileIcon, f.type()); + CPPUNIT_ASSERT_EQUAL(String("d"), f.description()); + } + + void testParseAPIC_UTF16_BOM() + { + ID3v2::AttachedPictureFrame f(ByteVector( + "\x41\x50\x49\x43\x00\x02\x0c\x59\x00\x00\x01\x69\x6d\x61\x67\x65" + "\x2f\x6a\x70\x65\x67\x00\x00\xfe\xff\x00\x63\x00\x6f\x00\x76\x00" + "\x65\x00\x72\x00\x2e\x00\x6a\x00\x70\x00\x67\x00\x00\xff\xd8\xff", + 16 * 3)); + CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), f.mimeType()); + CPPUNIT_ASSERT_EQUAL(ID3v2::AttachedPictureFrame::Other, f.type()); + CPPUNIT_ASSERT_EQUAL(String("cover.jpg"), f.description()); + CPPUNIT_ASSERT_EQUAL(ByteVector("\xff\xd8\xff", 3), f.picture()); + } + + void testParseAPICv22() + { + ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance(); + ByteVector data = ByteVector("PIC" + "\x00\x00\x08" + "\x00" + "JPG" + "\x01" + "d\x00" + "\x00", 14); + ID3v2::Header header; + header.setMajorVersion(2); + ID3v2::AttachedPictureFrame *frame = + dynamic_cast<TagLib::ID3v2::AttachedPictureFrame *>(factory->createFrame(data, &header)); + + CPPUNIT_ASSERT(frame); + CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), frame->mimeType()); + CPPUNIT_ASSERT_EQUAL(ID3v2::AttachedPictureFrame::FileIcon, frame->type()); + CPPUNIT_ASSERT_EQUAL(String("d"), frame->description()); + + delete frame; + } + + void testRenderAPIC() + { + ID3v2::AttachedPictureFrame f; + f.setTextEncoding(String::UTF8); + f.setMimeType("image/png"); + f.setType(ID3v2::AttachedPictureFrame::BackCover); + f.setDescription("Description"); + f.setPicture("PNG data"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("APIC" + "\x00\x00\x00\x20" + "\x00\x00" + "\x03" + "image/png\x00" + "\x04" + "Description\x00" + "PNG data", 42), + f.render()); + } + + void testDontRender22() + { + ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance(); + ByteVector data = ByteVector("FOO" + "\x00\x00\x08" + "\x00" + "JPG" + "\x01" + "d\x00" + "\x00", 14); + ID3v2::Header header; + header.setMajorVersion(2); + ID3v2::UnknownFrame *frame = + dynamic_cast<TagLib::ID3v2::UnknownFrame*>(factory->createFrame(data, &header)); + + CPPUNIT_ASSERT(frame); + + ID3v2::Tag tag; + tag.addFrame(frame); + CPPUNIT_ASSERT_EQUAL((unsigned int)1034, tag.render().size()); + } + + // http://bugs.kde.org/show_bug.cgi?id=151078 + void testParseGEOB() + { + ID3v2::GeneralEncapsulatedObjectFrame f(ByteVector("GEOB" + "\x00\x00\x00\x08" + "\x00\x00" + "\x00" + "m\x00" + "f\x00" + "d\x00" + "\x00", 18)); + CPPUNIT_ASSERT_EQUAL(String("m"), f.mimeType()); + CPPUNIT_ASSERT_EQUAL(String("f"), f.fileName()); + CPPUNIT_ASSERT_EQUAL(String("d"), f.description()); + } + + void testRenderGEOB() + { + ID3v2::GeneralEncapsulatedObjectFrame f; + f.setTextEncoding(String::Latin1); + f.setMimeType("application/octet-stream"); + f.setFileName("test.bin"); + f.setDescription("Description"); + f.setObject(ByteVector(3, '\x01')); + CPPUNIT_ASSERT_EQUAL( + ByteVector("GEOB" + "\x00\x00\x00\x32" + "\x00\x00" + "\x00" + "application/octet-stream\x00" + "test.bin\x00" + "Description\x00" + "\x01\x01\x01", 60), + f.render()); + } + + void testParsePOPM() + { + ID3v2::PopularimeterFrame f(ByteVector("POPM" + "\x00\x00\x00\x17" + "\x00\x00" + "email@example.com\x00" + "\x02" + "\x00\x00\x00\x03", 33)); + CPPUNIT_ASSERT_EQUAL(String("email@example.com"), f.email()); + CPPUNIT_ASSERT_EQUAL(2, f.rating()); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, f.counter()); + } + + void testParsePOPMWithoutCounter() + { + ID3v2::PopularimeterFrame f(ByteVector("POPM" + "\x00\x00\x00\x13" + "\x00\x00" + "email@example.com\x00" + "\x02", 29)); + CPPUNIT_ASSERT_EQUAL(String("email@example.com"), f.email()); + CPPUNIT_ASSERT_EQUAL(2, f.rating()); + CPPUNIT_ASSERT_EQUAL((unsigned int)0, f.counter()); + } + + void testRenderPOPM() + { + ID3v2::PopularimeterFrame f; + f.setEmail("email@example.com"); + f.setRating(2); + f.setCounter(3); + CPPUNIT_ASSERT_EQUAL( + ByteVector("POPM" + "\x00\x00\x00\x17" + "\x00\x00" + "email@example.com\x00" + "\x02" + "\x00\x00\x00\x03", 33), + f.render()); + } + + void testPOPMtoString() + { + ID3v2::PopularimeterFrame f; + f.setEmail("email@example.com"); + f.setRating(2); + f.setCounter(3); + CPPUNIT_ASSERT_EQUAL( + String("email@example.com rating=2 counter=3"), f.toString()); + } + + void testPOPMFromFile() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + ID3v2::PopularimeterFrame *f = new ID3v2::PopularimeterFrame(); + f->setEmail("email@example.com"); + f->setRating(200); + f->setCounter(3); + + { + MPEG::File foo(newname.c_str()); + foo.ID3v2Tag()->addFrame(f); + foo.save(); + } + { + MPEG::File bar(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("email@example.com"), dynamic_cast<ID3v2::PopularimeterFrame *>(bar.ID3v2Tag()->frameList("POPM").front())->email()); + CPPUNIT_ASSERT_EQUAL(200, dynamic_cast<ID3v2::PopularimeterFrame *>(bar.ID3v2Tag()->frameList("POPM").front())->rating()); + } + } + + // http://bugs.kde.org/show_bug.cgi?id=150481 + void testParseRelativeVolumeFrame() + { + ID3v2::RelativeVolumeFrame f( + ByteVector("RVA2" // Frame ID + "\x00\x00\x00\x0B" // Frame size + "\x00\x00" // Frame flags + "ident\x00" // Identification + "\x02" // Type of channel + "\x00\x0F" // Volume adjustment + "\x08" // Bits representing peak + "\x45", 21)); // Peak volume + CPPUNIT_ASSERT_EQUAL(String("ident"), f.identification()); + CPPUNIT_ASSERT_EQUAL(15.0f / 512.0f, + f.volumeAdjustment(ID3v2::RelativeVolumeFrame::FrontRight)); + CPPUNIT_ASSERT_EQUAL(static_cast<short>(15), + f.volumeAdjustmentIndex(ID3v2::RelativeVolumeFrame::FrontRight)); + CPPUNIT_ASSERT_EQUAL((unsigned char)8, + f.peakVolume(ID3v2::RelativeVolumeFrame::FrontRight).bitsRepresentingPeak); + CPPUNIT_ASSERT_EQUAL(ByteVector("\x45"), + f.peakVolume(ID3v2::RelativeVolumeFrame::FrontRight).peakVolume); + const List<ID3v2::RelativeVolumeFrame::ChannelType> channels = f.channels(); + CPPUNIT_ASSERT_EQUAL(1U, channels.size()); + CPPUNIT_ASSERT_EQUAL(ID3v2::RelativeVolumeFrame::FrontRight, channels[0]); + } + + void testRenderRelativeVolumeFrame() + { + ID3v2::RelativeVolumeFrame f; + f.setIdentification("ident"); + f.setVolumeAdjustment(15.0f / 512.0f, ID3v2::RelativeVolumeFrame::FrontRight); + ID3v2::RelativeVolumeFrame::PeakVolume peakVolume; + peakVolume.bitsRepresentingPeak = 8; + peakVolume.peakVolume.setData("\x45"); + f.setPeakVolume(peakVolume, ID3v2::RelativeVolumeFrame::FrontRight); + CPPUNIT_ASSERT_EQUAL( + ByteVector("RVA2" + "\x00\x00\x00\x0B" + "\x00\x00" + "ident\x00" + "\x02" + "\x00\x0F" + "\x08" + "\x45", 21), + f.render()); + } + + void testParseUniqueFileIdentifierFrame() + { + ID3v2::UniqueFileIdentifierFrame f( + ByteVector("UFID" // Frame ID + "\x00\x00\x00\x09" // Frame size + "\x00\x00" // Frame flags + "owner\x00" // Owner identifier + "\x00\x01\x02", 19)); // Identifier + CPPUNIT_ASSERT_EQUAL(String("owner"), + f.owner()); + CPPUNIT_ASSERT_EQUAL(ByteVector("\x00\x01\x02", 3), + f.identifier()); + } + + void testParseEmptyUniqueFileIdentifierFrame() + { + ID3v2::UniqueFileIdentifierFrame f( + ByteVector("UFID" // Frame ID + "\x00\x00\x00\x01" // Frame size + "\x00\x00" // Frame flags + "\x00" // Owner identifier + "", 11)); // Identifier + CPPUNIT_ASSERT_EQUAL(String(), + f.owner()); + CPPUNIT_ASSERT_EQUAL(ByteVector(), + f.identifier()); + } + + void testRenderUniqueFileIdentifierFrame() + { + ID3v2::UniqueFileIdentifierFrame f("owner", "\x01\x02\x03"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("UFID" + "\x00\x00\x00\x09" + "\x00\x00" + "owner\x00" + "\x01\x02\x03", 19), + f.render()); + } + + void testParseUrlLinkFrame() + { + ID3v2::UrlLinkFrame f( + ByteVector("WOAF" // Frame ID + "\x00\x00\x00\x12" // Frame size + "\x00\x00" // Frame flags + "http://example.com", 28)); // URL + CPPUNIT_ASSERT_EQUAL(String("http://example.com"), f.url()); + } + + void testRenderUrlLinkFrame() + { + ID3v2::UrlLinkFrame f("WOAF"); + f.setUrl("http://example.com"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("WOAF" // Frame ID + "\x00\x00\x00\x12" // Frame size + "\x00\x00" // Frame flags + "http://example.com", 28), // URL + f.render()); + } + + void testParseUserUrlLinkFrame() + { + ID3v2::UserUrlLinkFrame f( + ByteVector("WXXX" // Frame ID + "\x00\x00\x00\x17" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "foo\x00" // Description + "http://example.com", 33)); // URL + CPPUNIT_ASSERT_EQUAL(String("foo"), f.description()); + CPPUNIT_ASSERT_EQUAL(String("http://example.com"), f.url()); + } + + void testRenderUserUrlLinkFrame() + { + ID3v2::UserUrlLinkFrame f; + f.setDescription("foo"); + f.setUrl("http://example.com"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("WXXX" // Frame ID + "\x00\x00\x00\x17" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "foo\x00" // Description + "http://example.com", 33), // URL + f.render()); + } + + void testParseOwnershipFrame() + { + ID3v2::OwnershipFrame f( + ByteVector("OWNE" // Frame ID + "\x00\x00\x00\x19" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "GBP1.99\x00" // Price paid + "20120905" // Date of purchase + "Beatport", 35)); // Seller + CPPUNIT_ASSERT_EQUAL(String("GBP1.99"), f.pricePaid()); + CPPUNIT_ASSERT_EQUAL(String("20120905"), f.datePurchased()); + CPPUNIT_ASSERT_EQUAL(String("Beatport"), f.seller()); + } + + void testRenderOwnershipFrame() + { + ID3v2::OwnershipFrame f; + f.setPricePaid("GBP1.99"); + f.setDatePurchased("20120905"); + f.setSeller("Beatport"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("OWNE" // Frame ID + "\x00\x00\x00\x19" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "GBP1.99\x00" // Price paid + "20120905" // Date of purchase + "Beatport", 35), // URL + f.render()); + } + + void testParseSynchronizedLyricsFrame() + { + ID3v2::SynchronizedLyricsFrame f( + ByteVector("SYLT" // Frame ID + "\x00\x00\x00\x21" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "eng" // Language + "\x02" // Time stamp format + "\x01" // Content type + "foo\x00" // Content descriptor + "Example\x00" // 1st text + "\x00\x00\x04\xd2" // 1st time stamp + "Lyrics\x00" // 2nd text + "\x00\x00\x11\xd7", 43)); // 2nd time stamp + CPPUNIT_ASSERT_EQUAL(String::Latin1, f.textEncoding()); + CPPUNIT_ASSERT_EQUAL(ByteVector("eng", 3), f.language()); + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds, + f.timestampFormat()); + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchronizedLyricsFrame::Lyrics, f.type()); + CPPUNIT_ASSERT_EQUAL(String("foo"), f.description()); + ID3v2::SynchronizedLyricsFrame::SynchedTextList stl = f.synchedText(); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, stl.size()); + CPPUNIT_ASSERT_EQUAL(String("Example"), stl[0].text); + CPPUNIT_ASSERT_EQUAL((unsigned int)1234, stl[0].time); + CPPUNIT_ASSERT_EQUAL(String("Lyrics"), stl[1].text); + CPPUNIT_ASSERT_EQUAL((unsigned int)4567, stl[1].time); + } + + void testParseSynchronizedLyricsFrameWithEmptyDescritpion() + { + ID3v2::SynchronizedLyricsFrame f( + ByteVector("SYLT" // Frame ID + "\x00\x00\x00\x21" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "eng" // Language + "\x02" // Time stamp format + "\x01" // Content type + "\x00" // Content descriptor + "Example\x00" // 1st text + "\x00\x00\x04\xd2" // 1st time stamp + "Lyrics\x00" // 2nd text + "\x00\x00\x11\xd7", 40)); // 2nd time stamp + CPPUNIT_ASSERT_EQUAL(String::Latin1, f.textEncoding()); + CPPUNIT_ASSERT_EQUAL(ByteVector("eng", 3), f.language()); + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds, + f.timestampFormat()); + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchronizedLyricsFrame::Lyrics, f.type()); + CPPUNIT_ASSERT(f.description().isEmpty()); + ID3v2::SynchronizedLyricsFrame::SynchedTextList stl = f.synchedText(); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, stl.size()); + CPPUNIT_ASSERT_EQUAL(String("Example"), stl[0].text); + CPPUNIT_ASSERT_EQUAL((unsigned int)1234, stl[0].time); + CPPUNIT_ASSERT_EQUAL(String("Lyrics"), stl[1].text); + CPPUNIT_ASSERT_EQUAL((unsigned int)4567, stl[1].time); + } + + void testRenderSynchronizedLyricsFrame() + { + ID3v2::SynchronizedLyricsFrame f; + f.setTextEncoding(String::Latin1); + f.setLanguage(ByteVector("eng", 3)); + f.setTimestampFormat(ID3v2::SynchronizedLyricsFrame::AbsoluteMilliseconds); + f.setType(ID3v2::SynchronizedLyricsFrame::Lyrics); + f.setDescription("foo"); + ID3v2::SynchronizedLyricsFrame::SynchedTextList stl; + stl.append(ID3v2::SynchronizedLyricsFrame::SynchedText(1234, "Example")); + stl.append(ID3v2::SynchronizedLyricsFrame::SynchedText(4567, "Lyrics")); + f.setSynchedText(stl); + CPPUNIT_ASSERT_EQUAL( + ByteVector("SYLT" // Frame ID + "\x00\x00\x00\x21" // Frame size + "\x00\x00" // Frame flags + "\x00" // Text encoding + "eng" // Language + "\x02" // Time stamp format + "\x01" // Content type + "foo\x00" // Content descriptor + "Example\x00" // 1st text + "\x00\x00\x04\xd2" // 1st time stamp + "Lyrics\x00" // 2nd text + "\x00\x00\x11\xd7", 43), // 2nd time stamp + f.render()); + } + + void testParseEventTimingCodesFrame() + { + ID3v2::EventTimingCodesFrame f( + ByteVector("ETCO" // Frame ID + "\x00\x00\x00\x0b" // Frame size + "\x00\x00" // Frame flags + "\x02" // Time stamp format + "\x02" // 1st event + "\x00\x00\xf3\x5c" // 1st time stamp + "\xfe" // 2nd event + "\x00\x36\xee\x80", 21)); // 2nd time stamp + CPPUNIT_ASSERT_EQUAL(ID3v2::EventTimingCodesFrame::AbsoluteMilliseconds, + f.timestampFormat()); + ID3v2::EventTimingCodesFrame::SynchedEventList sel = f.synchedEvents(); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, sel.size()); + CPPUNIT_ASSERT_EQUAL(ID3v2::EventTimingCodesFrame::IntroStart, sel[0].type); + CPPUNIT_ASSERT_EQUAL((unsigned int)62300, sel[0].time); + CPPUNIT_ASSERT_EQUAL(ID3v2::EventTimingCodesFrame::AudioFileEnds, sel[1].type); + CPPUNIT_ASSERT_EQUAL((unsigned int)3600000, sel[1].time); + } + + void testRenderEventTimingCodesFrame() + { + ID3v2::EventTimingCodesFrame f; + f.setTimestampFormat(ID3v2::EventTimingCodesFrame::AbsoluteMilliseconds); + ID3v2::EventTimingCodesFrame::SynchedEventList sel; + sel.append(ID3v2::EventTimingCodesFrame::SynchedEvent(62300, ID3v2::EventTimingCodesFrame::IntroStart)); + sel.append(ID3v2::EventTimingCodesFrame::SynchedEvent(3600000, ID3v2::EventTimingCodesFrame::AudioFileEnds)); + f.setSynchedEvents(sel); + CPPUNIT_ASSERT_EQUAL( + ByteVector("ETCO" // Frame ID + "\x00\x00\x00\x0b" // Frame size + "\x00\x00" // Frame flags + "\x02" // Time stamp format + "\x02" // 1st event + "\x00\x00\xf3\x5c" // 1st time stamp + "\xfe" // 2nd event + "\x00\x36\xee\x80", 21), // 2nd time stamp + f.render()); + } + + void testParseCommentsFrame() + { + ID3v2::CommentsFrame f( + ByteVector("COMM" + "\x00\x00\x00\x14" + "\x00\x00" + "\x03" + "deu" + "Description\x00" + "Text", 30)); + CPPUNIT_ASSERT_EQUAL(String::UTF8, f.textEncoding()); + CPPUNIT_ASSERT_EQUAL(ByteVector("deu"), f.language()); + CPPUNIT_ASSERT_EQUAL(String("Description"), f.description()); + CPPUNIT_ASSERT_EQUAL(String("Text"), f.text()); + } + + void testRenderCommentsFrame() + { + ID3v2::CommentsFrame f; + f.setTextEncoding(String::UTF16); + f.setLanguage("eng"); + f.setDescription("Description"); + f.setText("Text"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("COMM" + "\x00\x00\x00\x28" + "\x00\x00" + "\x01" + "eng" + "\xff\xfe" "D\0e\0s\0c\0r\0i\0p\0t\0i\0o\0n\0" "\x00\x00" + "\xff\xfe" "T\0e\0x\0t\0", 50), + f.render()); + } + + void testParsePodcastFrame() + { + ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance(); + ByteVector data = ByteVector("PCST" + "\x00\x00\x00\x04" + "\x00\x00" + "\x00\x00\x00\x00", 14); + const ID3v2::Header header; + CPPUNIT_ASSERT(dynamic_cast<ID3v2::PodcastFrame *>( + factory->createFrame(data, &header))); + } + + void testRenderPodcastFrame() + { + ID3v2::PodcastFrame f; + CPPUNIT_ASSERT_EQUAL( + ByteVector("PCST" + "\x00\x00\x00\x04" + "\x00\x00" + "\x00\x00\x00\x00", 14), + f.render()); + } + + void testParsePrivateFrame() + { + ID3v2::PrivateFrame f( + ByteVector("PRIV" + "\x00\x00\x00\x0e" + "\x00\x00" + "WM/Provider\x00" + "TL", 24)); + CPPUNIT_ASSERT_EQUAL(String("WM/Provider"), f.owner()); + CPPUNIT_ASSERT_EQUAL(ByteVector("TL"), f.data()); + } + + void testRenderPrivateFrame() + { + ID3v2::PrivateFrame f; + f.setOwner("WM/Provider"); + f.setData("TL"); + CPPUNIT_ASSERT_EQUAL( + ByteVector("PRIV" + "\x00\x00\x00\x0e" + "\x00\x00" + "WM/Provider\x00" + "TL", 24), + f.render()); + } + + void testItunes24FrameSize() + { + MPEG::File f(TEST_FILE_PATH_C("005411.id3"), false); + CPPUNIT_ASSERT(f.tag()); + CPPUNIT_ASSERT(f.ID3v2Tag()->frameListMap().contains("TIT2")); + CPPUNIT_ASSERT_EQUAL(String("Sunshine Superman"), f.ID3v2Tag()->frameListMap()["TIT2"].front()->toString()); + } + + void testSaveUTF16Comment() + { + String::Type defaultEncoding = ID3v2::FrameFactory::instance()->defaultTextEncoding(); + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + ID3v2::FrameFactory::instance()->setDefaultTextEncoding(String::UTF16); + { + MPEG::File foo(newname.c_str()); + foo.strip(); + foo.tag()->setComment("Test comment!"); + foo.save(); + } + { + MPEG::File bar(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("Test comment!"), bar.tag()->comment()); + ID3v2::FrameFactory::instance()->setDefaultTextEncoding(defaultEncoding); + } + } + + void testUpdateGenre23_1() + { + // "Refinement" is the same as the ID3v1 genre - duplicate + ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance(); + ByteVector data = ByteVector("TCON" // Frame ID + "\x00\x00\x00\x10" // Frame size + "\x00\x00" // Frame flags + "\x00" // Encoding + "(22)Death Metal", 26); // Text + ID3v2::Header header; + header.setMajorVersion(3); + ID3v2::TextIdentificationFrame *frame = + dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(factory->createFrame(data, &header)); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, frame->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("Death Metal"), frame->fieldList()[0]); + + ID3v2::Tag tag; + tag.addFrame(frame); + CPPUNIT_ASSERT_EQUAL(String("Death Metal"), tag.genre()); + } + + void testUpdateGenre23_2() + { + // "Refinement" is different from the ID3v1 genre + ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance(); + ByteVector data = ByteVector("TCON" // Frame ID + "\x00\x00\x00\x0d" // Frame size + "\x00\x00" // Frame flags + "\x00" // Encoding + "(4)Eurodisco", 23); // Text + ID3v2::Header header; + header.setMajorVersion(3); + ID3v2::TextIdentificationFrame *frame = + dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(factory->createFrame(data, &header)); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, frame->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("4"), frame->fieldList()[0]); + CPPUNIT_ASSERT_EQUAL(String("Eurodisco"), frame->fieldList()[1]); + + ID3v2::Tag tag; + tag.addFrame(frame); + CPPUNIT_ASSERT_EQUAL(String("Disco Eurodisco"), tag.genre()); + } + + void testUpdateGenre23_3() + { + // Multiple references and a refinement + ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance(); + ByteVector data = ByteVector("TCON" // Frame ID + "\x00\x00\x00\x15" // Frame size + "\x00\x00" // Frame flags + "\x00" // Encoding + "(9)(138)Viking Metal", 31); // Text + ID3v2::Header header; + header.setMajorVersion(3); + ID3v2::TextIdentificationFrame *frame = + dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(factory->createFrame(data, &header)); + CPPUNIT_ASSERT_EQUAL(3U, frame->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("9"), frame->fieldList()[0]); + CPPUNIT_ASSERT_EQUAL(String("138"), frame->fieldList()[1]); + CPPUNIT_ASSERT_EQUAL(String("Viking Metal"), frame->fieldList()[2]); + + ID3v2::Tag tag; + tag.addFrame(frame); + CPPUNIT_ASSERT_EQUAL(String("Metal Black Metal Viking Metal"), tag.genre()); + } + + void testUpdateGenre24() + { + ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance(); + ByteVector data = ByteVector("TCON" // Frame ID + "\x00\x00\x00\x0D" // Frame size + "\x00\x00" // Frame flags + "\0" // Encoding + "14\0Eurodisco", 23); // Text + ID3v2::Header header; + ID3v2::TextIdentificationFrame *frame = + dynamic_cast<TagLib::ID3v2::TextIdentificationFrame*>(factory->createFrame(data, &header)); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, frame->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("14"), frame->fieldList()[0]); + CPPUNIT_ASSERT_EQUAL(String("Eurodisco"), frame->fieldList()[1]); + + ID3v2::Tag tag; + tag.addFrame(frame); + CPPUNIT_ASSERT_EQUAL(String("R&B Eurodisco"), tag.genre()); + } + + void testUpdateDate22() + { + MPEG::File f(TEST_FILE_PATH_C("id3v22-tda.mp3"), false); + CPPUNIT_ASSERT(f.tag()); + CPPUNIT_ASSERT_EQUAL((unsigned int)2010, f.tag()->year()); + } + + void testUpdateFullDate22() + { + MPEG::File f(TEST_FILE_PATH_C("id3v22-tda.mp3"), false); + CPPUNIT_ASSERT(f.tag()); + CPPUNIT_ASSERT_EQUAL(String("2010-04-03"), f.ID3v2Tag()->frameListMap()["TDRC"].front()->toString()); + } + + void testDowngradeTo23() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + ID3v2::TextIdentificationFrame *tf; + { + MPEG::File foo(newname.c_str()); + tf = new ID3v2::TextIdentificationFrame("TDOR", String::Latin1); + tf->setText("2011-03-16"); + foo.ID3v2Tag()->addFrame(tf); + tf = new ID3v2::TextIdentificationFrame("TDRC", String::Latin1); + tf->setText("2012-04-17T12:01"); + foo.ID3v2Tag()->addFrame(tf); + tf = new ID3v2::TextIdentificationFrame("TMCL", String::Latin1); + tf->setText(StringList().append("Guitar").append("Artist 1").append("Drums").append("Artist 2")); + foo.ID3v2Tag()->addFrame(tf); + tf = new ID3v2::TextIdentificationFrame("TIPL", String::Latin1); + tf->setText(StringList().append("Producer").append("Artist 3").append("Mastering").append("Artist 4")); + foo.ID3v2Tag()->addFrame(tf); + tf = new ID3v2::TextIdentificationFrame("TCON", String::Latin1); + tf->setText(StringList().append("51").append("Noise").append("Power Noise")); + foo.ID3v2Tag()->addFrame(tf); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDRL", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TDTG", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TMOO", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TPRO", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOA", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOT", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSST", String::Latin1)); + foo.ID3v2Tag()->addFrame(new ID3v2::TextIdentificationFrame("TSOP", String::Latin1)); + foo.save(MPEG::File::AllTags, File::StripOthers, ID3v2::v3); + } + { + MPEG::File bar(newname.c_str()); + tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TDOR").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("2011"), tf->fieldList().front()); + tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TDRC").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("2012-04-17T12:01"), tf->fieldList().front()); + tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TIPL").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL((unsigned int)8, tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("Guitar"), tf->fieldList()[0]); + CPPUNIT_ASSERT_EQUAL(String("Artist 1"), tf->fieldList()[1]); + CPPUNIT_ASSERT_EQUAL(String("Drums"), tf->fieldList()[2]); + CPPUNIT_ASSERT_EQUAL(String("Artist 2"), tf->fieldList()[3]); + CPPUNIT_ASSERT_EQUAL(String("Producer"), tf->fieldList()[4]); + CPPUNIT_ASSERT_EQUAL(String("Artist 3"), tf->fieldList()[5]); + CPPUNIT_ASSERT_EQUAL(String("Mastering"), tf->fieldList()[6]); + CPPUNIT_ASSERT_EQUAL(String("Artist 4"), tf->fieldList()[7]); + tf = dynamic_cast<ID3v2::TextIdentificationFrame *>(bar.ID3v2Tag()->frameList("TCON").front()); + CPPUNIT_ASSERT(tf); + CPPUNIT_ASSERT_EQUAL(3U, tf->fieldList().size()); + CPPUNIT_ASSERT_EQUAL(String("51"), tf->fieldList()[0]); + CPPUNIT_ASSERT_EQUAL(String("39"), tf->fieldList()[1]); + CPPUNIT_ASSERT_EQUAL(String("Power Noise"), tf->fieldList()[2]); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDRL")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TDTG")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TMOO")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TPRO")); +#ifdef NO_ITUNES_HACKS + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOA")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOT")); + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSOP")); +#endif + CPPUNIT_ASSERT(!bar.ID3v2Tag()->frameListMap().contains("TSST")); + } + { + const ByteVector expectedId3v23Data( + "ID3" "\x03\x00\x00\x00\x00\x09\x49" + "TSOA" "\x00\x00\x00\x01\x00\x00\x00" + "TSOT" "\x00\x00\x00\x01\x00\x00\x00" + "TSOP" "\x00\x00\x00\x01\x00\x00\x00" + "TORY" "\x00\x00\x00\x05\x00\x00\x00" "2011" + "TYER" "\x00\x00\x00\x05\x00\x00\x00" "2012" + "TDAT" "\x00\x00\x00\x05\x00\x00\x00" "1704" + "TIME" "\x00\x00\x00\x05\x00\x00\x00" "1201" + "IPLS" "\x00\x00\x00\x44\x00\x00\x00" "Guitar" "\x00" + "Artist 1" "\x00" "Drums" "\x00" "Artist 2" "\x00" "Producer" "\x00" + "Artist 3" "\x00" "Mastering" "\x00" "Artist 4" + "TCON" "\x00\x00\x00\x14\x00\x00\x00" "(51)(39)Power Noise", 211); + const ByteVector actualId3v23Data = + PlainFile(newname.c_str()).readBlock(expectedId3v23Data.size()); + CPPUNIT_ASSERT_EQUAL(expectedId3v23Data, actualId3v23Data); + } + + ScopedFileCopy rareFramesCopy("rare_frames", ".mp3"); + + { + MPEG::File f(rareFramesCopy.fileName().c_str()); + f.save(MPEG::File::AllTags, File::StripOthers, ID3v2::v3); + f.seek(f.find("TCON") + 11); + CPPUNIT_ASSERT_EQUAL(ByteVector("(13)"), f.readBlock(4)); + } + } + + void testCompressedFrameWithBrokenLength() + { + MPEG::File f(TEST_FILE_PATH_C("compressed_id3_frame.mp3"), false); + CPPUNIT_ASSERT(f.ID3v2Tag()->frameListMap().contains("APIC")); + + if(zlib::isAvailable()) { + ID3v2::AttachedPictureFrame *frame + = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(f.ID3v2Tag()->frameListMap()["APIC"].front()); + CPPUNIT_ASSERT(frame); + CPPUNIT_ASSERT_EQUAL(String("image/bmp"), frame->mimeType()); + CPPUNIT_ASSERT_EQUAL(ID3v2::AttachedPictureFrame::Other, frame->type()); + CPPUNIT_ASSERT_EQUAL(String(""), frame->description()); + CPPUNIT_ASSERT_EQUAL((unsigned int)86414, frame->picture().size()); + } + else { + // Skip the test if ZLIB is not installed. + // The message "Compressed frames are currently not supported." will be displayed. + + ID3v2::UnknownFrame *frame + = dynamic_cast<TagLib::ID3v2::UnknownFrame*>(f.ID3v2Tag()->frameListMap()["APIC"].front()); + CPPUNIT_ASSERT(frame); + } + } + + void testW000() + { + MPEG::File f(TEST_FILE_PATH_C("w000.mp3"), false); + CPPUNIT_ASSERT(f.ID3v2Tag()->frameListMap().contains("W000")); + ID3v2::UrlLinkFrame *frame = + dynamic_cast<TagLib::ID3v2::UrlLinkFrame*>(f.ID3v2Tag()->frameListMap()["W000"].front()); + CPPUNIT_ASSERT(frame); + CPPUNIT_ASSERT_EQUAL(String("lukas.lalinsky@example.com____"), frame->url()); + } + + void testPropertyInterface() + { + ScopedFileCopy copy("rare_frames", ".mp3"); + string newname = copy.fileName(); + MPEG::File f(newname.c_str()); + PropertyMap dict = f.ID3v2Tag(false)->properties(); + CPPUNIT_ASSERT_EQUAL((unsigned int)6, dict.size()); + + CPPUNIT_ASSERT(dict.contains("USERTEXTDESCRIPTION1")); + CPPUNIT_ASSERT(dict.contains("QuodLibet::USERTEXTDESCRIPTION2")); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, dict["USERTEXTDESCRIPTION1"].size()); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, dict["QuodLibet::USERTEXTDESCRIPTION2"].size()); + CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION1"][0]); + CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION1"][1]); + CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["QuodLibet::USERTEXTDESCRIPTION2"][0]); + CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["QuodLibet::USERTEXTDESCRIPTION2"][1]); + + CPPUNIT_ASSERT_EQUAL(String("Pop"), dict["GENRE"].front()); + + CPPUNIT_ASSERT_EQUAL(String("http://a.user.url"), dict["URL:USERURL"].front()); + + CPPUNIT_ASSERT_EQUAL(String("http://a.user.url/with/empty/description"), dict["URL"].front()); + CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"].front()); + + CPPUNIT_ASSERT_EQUAL(1u, dict.unsupportedData().size()); + CPPUNIT_ASSERT_EQUAL(String("UFID/supermihi@web.de"), dict.unsupportedData().front()); + } + + void testPropertyInterface2() + { + ID3v2::Tag tag; + ID3v2::UnsynchronizedLyricsFrame *frame1 = new ID3v2::UnsynchronizedLyricsFrame(); + frame1->setDescription("test"); + frame1->setText("la-la-la test"); + tag.addFrame(frame1); + + ID3v2::UnsynchronizedLyricsFrame *frame2 = new ID3v2::UnsynchronizedLyricsFrame(); + frame2->setDescription(""); + frame2->setText("la-la-la nodescription"); + tag.addFrame(frame2); + + ID3v2::AttachedPictureFrame *frame3 = new ID3v2::AttachedPictureFrame(); + frame3->setDescription("test picture"); + tag.addFrame(frame3); + + ID3v2::TextIdentificationFrame *frame4 = new ID3v2::TextIdentificationFrame("TIPL"); + frame4->setText("single value is invalid for TIPL"); + tag.addFrame(frame4); + + ID3v2::TextIdentificationFrame *frame5 = new ID3v2::TextIdentificationFrame("TMCL"); + StringList tmclData; + tmclData.append("VIOLIN"); + tmclData.append("a violinist"); + tmclData.append("PIANO"); + tmclData.append("a pianist"); + frame5->setText(tmclData); + tag.addFrame(frame5); + + ID3v2::UniqueFileIdentifierFrame *frame6 = new ID3v2::UniqueFileIdentifierFrame("http://musicbrainz.org", "152454b9-19ba-49f3-9fc9-8fc26545cf41"); + tag.addFrame(frame6); + + ID3v2::UniqueFileIdentifierFrame *frame7 = new ID3v2::UniqueFileIdentifierFrame("http://example.com", "123"); + tag.addFrame(frame7); + + ID3v2::UserTextIdentificationFrame *frame8 = new ID3v2::UserTextIdentificationFrame(); + frame8->setDescription("MusicBrainz Album Id"); + frame8->setText("95c454a5-d7e0-4d8f-9900-db04aca98ab3"); + tag.addFrame(frame8); + + PropertyMap properties = tag.properties(); + + CPPUNIT_ASSERT_EQUAL(3u, properties.unsupportedData().size()); + CPPUNIT_ASSERT(properties.unsupportedData().contains("TIPL")); + CPPUNIT_ASSERT(properties.unsupportedData().contains("APIC")); + CPPUNIT_ASSERT(properties.unsupportedData().contains("UFID/http://example.com")); + + CPPUNIT_ASSERT(properties.contains("PERFORMER:VIOLIN")); + CPPUNIT_ASSERT(properties.contains("PERFORMER:PIANO")); + CPPUNIT_ASSERT_EQUAL(String("a violinist"), properties["PERFORMER:VIOLIN"].front()); + CPPUNIT_ASSERT_EQUAL(String("a pianist"), properties["PERFORMER:PIANO"].front()); + + CPPUNIT_ASSERT(properties.contains("LYRICS")); + CPPUNIT_ASSERT(properties.contains("LYRICS:TEST")); + + CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_TRACKID")); + CPPUNIT_ASSERT_EQUAL(String("152454b9-19ba-49f3-9fc9-8fc26545cf41"), properties["MUSICBRAINZ_TRACKID"].front()); + + CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_ALBUMID")); + CPPUNIT_ASSERT_EQUAL(String("95c454a5-d7e0-4d8f-9900-db04aca98ab3"), properties["MUSICBRAINZ_ALBUMID"].front()); + + tag.removeUnsupportedProperties(properties.unsupportedData()); + CPPUNIT_ASSERT(tag.frameList("APIC").isEmpty()); + CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty()); + CPPUNIT_ASSERT_EQUAL((ID3v2::UniqueFileIdentifierFrame *)0, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://example.com")); + CPPUNIT_ASSERT_EQUAL(frame6, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://musicbrainz.org")); + } + + void testPropertiesMovement() + { + ID3v2::Tag tag; + ID3v2::TextIdentificationFrame *frameMvnm = new ID3v2::TextIdentificationFrame("MVNM"); + frameMvnm->setText("Movement Name"); + tag.addFrame(frameMvnm); + + ID3v2::TextIdentificationFrame *frameMvin = new ID3v2::TextIdentificationFrame("MVIN"); + frameMvin->setText("2/3"); + tag.addFrame(frameMvin); + + PropertyMap properties = tag.properties(); + CPPUNIT_ASSERT(properties.contains("MOVEMENTNAME")); + CPPUNIT_ASSERT(properties.contains("MOVEMENTNUMBER")); + CPPUNIT_ASSERT_EQUAL(String("Movement Name"), properties["MOVEMENTNAME"].front()); + CPPUNIT_ASSERT_EQUAL(String("2/3"), properties["MOVEMENTNUMBER"].front()); + + ByteVector frameDataMvnm("MVNM" + "\x00\x00\x00\x0e" + "\x00\x00" + "\x00" + "Movement Name", 24); + CPPUNIT_ASSERT_EQUAL(frameDataMvnm, frameMvnm->render()); + ByteVector frameDataMvin("MVIN" + "\x00\x00\x00\x04" + "\x00\x00" + "\x00" + "2/3", 14); + CPPUNIT_ASSERT_EQUAL(frameDataMvin, frameMvin->render()); + + ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance(); + ID3v2::Header header; + ID3v2::TextIdentificationFrame *parsedFrameMvnm = + dynamic_cast<ID3v2::TextIdentificationFrame *>( + factory->createFrame(frameDataMvnm, &header)); + ID3v2::TextIdentificationFrame *parsedFrameMvin = + dynamic_cast<ID3v2::TextIdentificationFrame *>( + factory->createFrame(frameDataMvin, &header)); + CPPUNIT_ASSERT(parsedFrameMvnm); + CPPUNIT_ASSERT(parsedFrameMvin); + CPPUNIT_ASSERT_EQUAL(String("Movement Name"), parsedFrameMvnm->toString()); + CPPUNIT_ASSERT_EQUAL(String("2/3"), parsedFrameMvin->toString()); + + tag.addFrame(parsedFrameMvnm); + tag.addFrame(parsedFrameMvin); + } + + void testPropertyGrouping() + { + ID3v2::Tag tag; + ID3v2::TextIdentificationFrame *frameGrp1 = new ID3v2::TextIdentificationFrame("GRP1"); + frameGrp1->setText("Grouping"); + tag.addFrame(frameGrp1); + + PropertyMap properties = tag.properties(); + CPPUNIT_ASSERT(properties.contains("GROUPING")); + CPPUNIT_ASSERT_EQUAL(String("Grouping"), properties["GROUPING"].front()); + + ByteVector frameDataGrp1("GRP1" + "\x00\x00\x00\x09" + "\x00\x00" + "\x00" + "Grouping", 19); + CPPUNIT_ASSERT_EQUAL(frameDataGrp1, frameGrp1->render()); + + ID3v2::FrameFactory *factory = ID3v2::FrameFactory::instance(); + ID3v2::Header header; + ID3v2::TextIdentificationFrame *parsedFrameGrp1 = + dynamic_cast<ID3v2::TextIdentificationFrame *>( + factory->createFrame(frameDataGrp1, &header)); + CPPUNIT_ASSERT(parsedFrameGrp1); + CPPUNIT_ASSERT_EQUAL(String("Grouping"), parsedFrameGrp1->toString()); + + tag.addFrame(parsedFrameGrp1); + } + + void testDeleteFrame() + { + ScopedFileCopy copy("rare_frames", ".mp3"); + string newname = copy.fileName(); + + { + MPEG::File f(newname.c_str()); + ID3v2::Tag *t = f.ID3v2Tag(); + ID3v2::Frame *frame = t->frameList("TCON")[0]; + CPPUNIT_ASSERT_EQUAL(1u, t->frameList("TCON").size()); + t->removeFrame(frame, true); + f.save(MPEG::File::ID3v2); + } + { + MPEG::File f2(newname.c_str()); + ID3v2::Tag *t = f2.ID3v2Tag(); + CPPUNIT_ASSERT(t->frameList("TCON").isEmpty()); + } + } + + void testSaveAndStripID3v1ShouldNotAddFrameFromID3v1ToId3v2() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + { + MPEG::File foo(newname.c_str()); + foo.tag()->setArtist("Artist"); + foo.save(MPEG::File::ID3v1 | MPEG::File::ID3v2); + } + + { + MPEG::File bar(newname.c_str()); + bar.ID3v2Tag()->removeFrames("TPE1"); + // Should strip ID3v1 here and not add old values to ID3v2 again + bar.save(MPEG::File::ID3v2, File::StripOthers); + } + + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT(!f.ID3v2Tag()->frameListMap().contains("TPE1")); + } + + void testParseChapterFrame() + { + ID3v2::Header header; + + ByteVector chapterData = + ByteVector("CHAP" // Frame ID + "\x00\x00\x00\x20" // Frame size + "\x00\x00" // Frame flags + "\x43\x00" // Element ID ("C") + "\x00\x00\x00\x03" // Start time + "\x00\x00\x00\x05" // End time + "\x00\x00\x00\x02" // Start offset + "\x00\x00\x00\x03", 28); // End offset + ByteVector embeddedFrameData = + ByteVector("TIT2" // Embedded frame ID + "\x00\x00\x00\x04" // Embedded frame size + "\x00\x00" // Embedded frame flags + "\x00" // TIT2 frame text encoding + "CH1", 14); // Chapter title + + ID3v2::ChapterFrame f1(&header, chapterData); + + CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f1.elementID()); + CPPUNIT_ASSERT((unsigned int)0x03 == f1.startTime()); + CPPUNIT_ASSERT((unsigned int)0x05 == f1.endTime()); + CPPUNIT_ASSERT((unsigned int)0x02 == f1.startOffset()); + CPPUNIT_ASSERT((unsigned int)0x03 == f1.endOffset()); + CPPUNIT_ASSERT((unsigned int)0x00 == f1.embeddedFrameList().size()); + + ID3v2::ChapterFrame f2(&header, chapterData + embeddedFrameData); + + CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f2.elementID()); + CPPUNIT_ASSERT((unsigned int)0x03 == f2.startTime()); + CPPUNIT_ASSERT((unsigned int)0x05 == f2.endTime()); + CPPUNIT_ASSERT((unsigned int)0x02 == f2.startOffset()); + CPPUNIT_ASSERT((unsigned int)0x03 == f2.endOffset()); + CPPUNIT_ASSERT((unsigned int)0x01 == f2.embeddedFrameList().size()); + CPPUNIT_ASSERT(f2.embeddedFrameList("TIT2").size() == 1); + CPPUNIT_ASSERT(f2.embeddedFrameList("TIT2")[0]->toString() == "CH1"); + } + + void testRenderChapterFrame() + { + ID3v2::Header header; + ID3v2::ChapterFrame f1(&header, "CHAP"); + f1.setElementID(ByteVector("\x43\x00", 2)); + f1.setStartTime(3); + f1.setEndTime(5); + f1.setStartOffset(2); + f1.setEndOffset(3); + ID3v2::TextIdentificationFrame *eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + f1.addEmbeddedFrame(eF); + + ByteVector expected = + ByteVector("CHAP" // Frame ID + "\x00\x00\x00\x20" // Frame size + "\x00\x00" // Frame flags + "\x43\x00" // Element ID + "\x00\x00\x00\x03" // Start time + "\x00\x00\x00\x05" // End time + "\x00\x00\x00\x02" // Start offset + "\x00\x00\x00\x03" // End offset + "TIT2" // Embedded frame ID + "\x00\x00\x00\x04" // Embedded frame size + "\x00\x00" // Embedded frame flags + "\x00" // TIT2 frame text encoding + "CH1", 42); // Chapter title + + CPPUNIT_ASSERT_EQUAL(expected, f1.render()); + + f1.setElementID("C"); + + CPPUNIT_ASSERT_EQUAL(expected, f1.render()); + + ID3v2::FrameList frames; + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + frames.append(eF); + + ID3v2::ChapterFrame f2(ByteVector("\x43\x00", 2), 3, 5, 2, 3, frames); + CPPUNIT_ASSERT_EQUAL(expected, f2.render()); + + frames.clear(); + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + frames.append(eF); + + ID3v2::ChapterFrame f3(ByteVector("C\x00", 2), 3, 5, 2, 3, frames); + CPPUNIT_ASSERT_EQUAL(expected, f3.render()); + + frames.clear(); + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + frames.append(eF); + + ID3v2::ChapterFrame f4("C", 3, 5, 2, 3, frames); + CPPUNIT_ASSERT_EQUAL(expected, f4.render()); + + CPPUNIT_ASSERT(!f4.toString().isEmpty()); + + ID3v2::ChapterFrame f5("C", 3, 5, 2, 3); + eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("CH1"); + f5.addEmbeddedFrame(eF); + CPPUNIT_ASSERT_EQUAL(expected, f5.render()); + } + + void testParseTableOfContentsFrame() + { + ID3v2::Header header; + ID3v2::TableOfContentsFrame f( + &header, + ByteVector("CTOC" // Frame ID + "\x00\x00\x00\x16" // Frame size + "\x00\x00" // Frame flags + "\x54\x00" // Element ID ("T") + "\x01" // CTOC flags + "\x02" // Entry count + "\x43\x00" // First entry ("C") + "\x44\x00" // Second entry ("D") + "TIT2" // Embedded frame ID + "\x00\x00\x00\x04" // Embedded frame size + "\x00\x00" // Embedded frame flags + "\x00" // TIT2 frame text encoding + "TC1", 32)); // Table of contents title + CPPUNIT_ASSERT_EQUAL(ByteVector("T"), f.elementID()); + CPPUNIT_ASSERT(!f.isTopLevel()); + CPPUNIT_ASSERT(f.isOrdered()); + CPPUNIT_ASSERT((unsigned int)0x02 == f.entryCount()); + CPPUNIT_ASSERT_EQUAL(ByteVector("C"), f.childElements()[0]); + CPPUNIT_ASSERT_EQUAL(ByteVector("D"), f.childElements()[1]); + CPPUNIT_ASSERT((unsigned int)0x01 == f.embeddedFrameList().size()); + CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").size() == 1); + CPPUNIT_ASSERT(f.embeddedFrameList("TIT2")[0]->toString() == "TC1"); + + f.removeChildElement("E"); // not existing + CPPUNIT_ASSERT_EQUAL(2U, f.entryCount()); + f.removeChildElement("C"); + CPPUNIT_ASSERT_EQUAL(1U, f.entryCount()); + CPPUNIT_ASSERT_EQUAL(ByteVector("D"), f.childElements()[0]); + + ID3v2::Frame *frame = f.embeddedFrameList("TIT2")[0]; + f.removeEmbeddedFrame(frame); + CPPUNIT_ASSERT(f.embeddedFrameList("TIT2").isEmpty()); + } + + void testRenderTableOfContentsFrame() + { + ID3v2::Header header; + ID3v2::TableOfContentsFrame f(&header, "CTOC"); + f.setElementID(ByteVector("\x54\x00", 2)); + f.setIsTopLevel(false); + f.setIsOrdered(true); + f.addChildElement(ByteVector("\x43\x00", 2)); + f.addChildElement(ByteVector("\x44\x00", 2)); + ID3v2::TextIdentificationFrame *eF = new ID3v2::TextIdentificationFrame("TIT2"); + eF->setText("TC1"); + f.addEmbeddedFrame(eF); + CPPUNIT_ASSERT_EQUAL( + ByteVector("CTOC" // Frame ID + "\x00\x00\x00\x16" // Frame size + "\x00\x00" // Frame flags + "\x54\x00" // Element ID + "\x01" // CTOC flags + "\x02" // Entry count + "\x43\x00" // First entry + "\x44\x00" // Second entry + "TIT2" // Embedded frame ID + "\x00\x00\x00\x04" // Embedded frame size + "\x00\x00" // Embedded frame flags + "\x00" // TIT2 frame text encoding + "TC1", 32), // Table of contents title + f.render()); + } + + void testShrinkPadding() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + { + MPEG::File f(newname.c_str()); + f.ID3v2Tag()->setTitle(longText(64 * 1024)); + f.save(MPEG::File::ID3v2, File::StripOthers); + } + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(74789L, f.length()); + f.ID3v2Tag()->setTitle("ABCDEFGHIJ"); + f.save(MPEG::File::ID3v2, File::StripOthers); + } + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(9263L, f.length()); + } + } + + void testEmptyFrame() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + { + MPEG::File f(newname.c_str()); + ID3v2::Tag *tag = f.ID3v2Tag(true); + + ID3v2::UrlLinkFrame *frame1 = new ID3v2::UrlLinkFrame( + ByteVector("WOAF\x00\x00\x00\x01\x00\x00\x00", 11)); + tag->addFrame(frame1); + + ID3v2::TextIdentificationFrame *frame2 = new ID3v2::TextIdentificationFrame("TIT2"); + frame2->setText("Title"); + tag->addFrame(frame2); + + f.save(); + } + + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + + ID3v2::Tag *tag = f.ID3v2Tag(); + CPPUNIT_ASSERT_EQUAL(String("Title"), tag->title()); + CPPUNIT_ASSERT_EQUAL(true, tag->frameListMap()["WOAF"].isEmpty()); + } + } + + void testDuplicateTags() + { + ScopedFileCopy copy("duplicate_id3v2", ".mp3"); + + ByteVector audioStream; + { + MPEG::File f(copy.fileName().c_str()); + f.seek(f.ID3v2Tag()->header()->completeTagSize()); + audioStream = f.readBlock(2089); + + // duplicate_id3v2.mp3 has duplicate ID3v2 tags. + // Sample rate will be 32000 if we can't skip the second tag. + + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL((unsigned int)8049, f.ID3v2Tag()->header()->completeTagSize()); + + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + + f.ID3v2Tag()->setArtist("Artist A"); + f.save(MPEG::File::ID3v2, File::StripOthers); + } + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL((long)3594, f.length()); + CPPUNIT_ASSERT_EQUAL((unsigned int)1505, f.ID3v2Tag()->header()->completeTagSize()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f.ID3v2Tag()->artist()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + + f.seek(f.ID3v2Tag()->header()->completeTagSize()); + CPPUNIT_ASSERT_EQUAL(f.readBlock(2089), audioStream); + + } + } + + void testParseTOCFrameWithManyChildren() + { + MPEG::File f(TEST_FILE_PATH_C("toc_many_children.mp3")); + CPPUNIT_ASSERT(f.isValid()); + + ID3v2::Tag *tag = f.ID3v2Tag(); + const ID3v2::FrameList &frames = tag->frameList(); + CPPUNIT_ASSERT_EQUAL(130U, frames.size()); + int i = 0; + for(ID3v2::FrameList::ConstIterator it = frames.begin(); it != frames.end(); + ++it, ++i) { + if(i > 0) { + CPPUNIT_ASSERT_EQUAL(ByteVector("CHAP"), (*it)->frameID()); + const ID3v2::ChapterFrame *chapFrame = + dynamic_cast<const ID3v2::ChapterFrame *>(*it); + CPPUNIT_ASSERT_EQUAL(ByteVector("chapter") + + ByteVector(String::number(i - 1).toCString()), + chapFrame->elementID()); + CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(100 * i), + chapFrame->startTime()); + CPPUNIT_ASSERT_EQUAL(static_cast<unsigned int>(100 * i), + chapFrame->endTime()); + const ID3v2::FrameList &embeddedFrames = chapFrame->embeddedFrameList(); + CPPUNIT_ASSERT_EQUAL(1U, embeddedFrames.size()); + const ID3v2::TextIdentificationFrame *tit2Frame = + dynamic_cast<const ID3v2::TextIdentificationFrame *>( + embeddedFrames.front()); + CPPUNIT_ASSERT(tit2Frame); + CPPUNIT_ASSERT_EQUAL(String("Marker ") + String::number(i), + tit2Frame->fieldList().front()); + } + else { + CPPUNIT_ASSERT_EQUAL(ByteVector("CTOC"), (*it)->frameID()); + const ID3v2::TableOfContentsFrame *ctocFrame = + dynamic_cast<const ID3v2::TableOfContentsFrame *>(*it); + CPPUNIT_ASSERT_EQUAL(ByteVector("toc"), ctocFrame->elementID()); + CPPUNIT_ASSERT(!ctocFrame->isTopLevel()); + CPPUNIT_ASSERT(!ctocFrame->isOrdered()); + CPPUNIT_ASSERT_EQUAL(129U, ctocFrame->entryCount()); + const ID3v2::FrameList &embeddedFrames = ctocFrame->embeddedFrameList(); + CPPUNIT_ASSERT_EQUAL(1U, embeddedFrames.size()); + const ID3v2::TextIdentificationFrame *tit2Frame = + dynamic_cast<const ID3v2::TextIdentificationFrame *>( + embeddedFrames.front()); + CPPUNIT_ASSERT(tit2Frame); + CPPUNIT_ASSERT_EQUAL(StringList("toplevel toc"), tit2Frame->fieldList()); + } + } + + CPPUNIT_ASSERT(!ID3v2::ChapterFrame::findByElementID(tag, "chap2")); + CPPUNIT_ASSERT(ID3v2::ChapterFrame::findByElementID(tag, "chapter2")); + + CPPUNIT_ASSERT(!ID3v2::TableOfContentsFrame::findTopLevel(tag)); + CPPUNIT_ASSERT(!ID3v2::TableOfContentsFrame::findByElementID(tag, "ctoc")); + CPPUNIT_ASSERT(ID3v2::TableOfContentsFrame::findByElementID(tag, "toc")); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2); + diff --git a/Frameworks/TagLib/taglib/tests/test_info.cpp b/Frameworks/TagLib/taglib/tests/test_info.cpp new file mode 100644 index 000000000..4302a249c --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_info.cpp @@ -0,0 +1,75 @@ +/*************************************************************************** + copyright : (C) 2012 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <infotag.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestInfoTag : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestInfoTag); + CPPUNIT_TEST(testTitle); + CPPUNIT_TEST(testNumericFields); + CPPUNIT_TEST_SUITE_END(); + +public: + void testTitle() + { + RIFF::Info::Tag tag; + + CPPUNIT_ASSERT_EQUAL(String(""), tag.title()); + tag.setTitle("Test title 1"); + tag.setFieldText("TEST", "Dummy Text"); + + CPPUNIT_ASSERT_EQUAL(String("Test title 1"), tag.title()); + + RIFF::Info::FieldListMap map = tag.fieldListMap(); + CPPUNIT_ASSERT_EQUAL(String("Test title 1"), map["INAM"]); + CPPUNIT_ASSERT_EQUAL(String("Dummy Text"), map["TEST"]); + } + + void testNumericFields() + { + RIFF::Info::Tag tag; + + CPPUNIT_ASSERT_EQUAL((unsigned int)0, tag.track()); + tag.setTrack(1234); + CPPUNIT_ASSERT_EQUAL((unsigned int)1234, tag.track()); + CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("IPRT")); + + CPPUNIT_ASSERT_EQUAL((unsigned int)0, tag.year()); + tag.setYear(1234); + CPPUNIT_ASSERT_EQUAL((unsigned int)1234, tag.year()); + CPPUNIT_ASSERT_EQUAL(String("1234"), tag.fieldText("ICRD")); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestInfoTag); + diff --git a/Frameworks/TagLib/taglib/tests/test_it.cpp b/Frameworks/TagLib/taglib/tests/test_it.cpp new file mode 100644 index 000000000..75afb54dc --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_it.cpp @@ -0,0 +1,139 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <itfile.h> +#include <tstringlist.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +static const String titleBefore("test song name"); +static const String titleAfter("changed title"); + +static const String commentBefore( + "This is a sample name.\n" + "In module file formats\n" + "sample names are abused\n" + "as multiline comments.\n" + " "); + +static const String newComment( + "This is a sample name!\n" + "In module file formats\n" + "sample names are abused\n" + "as multiline comments.\n" + "-----------------------------------\n" + "The previous line is truncated but starting with this line\n" + "the comment is not limeted in the line length but to 8000\n" + "additional characters (bytes).\n" + "\n" + "This is because it is saved in the 'message' proportion of\n" + "IT files."); + +static const String commentAfter( + "This is a sample name!\n" + "In module file formats\n" + "sample names are abused\n" + "as multiline comments.\n" + "-------------------------\n" + "The previous line is truncated but starting with this line\n" + "the comment is not limeted in the line length but to 8000\n" + "additional characters (bytes).\n" + "\n" + "This is because it is saved in the 'message' proportion of\n" + "IT files."); + +class TestIT : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestIT); + CPPUNIT_TEST(testReadTags); + CPPUNIT_TEST(testWriteTags); + CPPUNIT_TEST_SUITE_END(); + +public: + void testReadTags() + { + testRead(TEST_FILE_PATH_C("test.it"), titleBefore, commentBefore); + } + + void testWriteTags() + { + ScopedFileCopy copy("test", ".it"); + { + IT::File file(copy.fileName().c_str()); + CPPUNIT_ASSERT(file.tag() != 0); + file.tag()->setTitle(titleAfter); + file.tag()->setComment(newComment); + file.tag()->setTrackerName("won't be saved"); + CPPUNIT_ASSERT(file.save()); + } + testRead(copy.fileName().c_str(), titleAfter, commentAfter); + } + +private: + void testRead(FileName fileName, const String &title, const String &comment) + { + IT::File file(fileName); + + CPPUNIT_ASSERT(file.isValid()); + + IT::Properties *p = file.audioProperties(); + Mod::Tag *t = file.tag(); + + CPPUNIT_ASSERT(0 != p); + CPPUNIT_ASSERT(0 != t); + + CPPUNIT_ASSERT_EQUAL( 0, p->length()); + CPPUNIT_ASSERT_EQUAL( 0, p->bitrate()); + CPPUNIT_ASSERT_EQUAL( 0, p->sampleRate()); + CPPUNIT_ASSERT_EQUAL(64, p->channels()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 0, p->lengthInPatterns()); + CPPUNIT_ASSERT_EQUAL(true, p->stereo()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 0, p->instrumentCount()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 5, p->sampleCount()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 1, p->patternCount()); + CPPUNIT_ASSERT_EQUAL((unsigned short)535, p->version()); + CPPUNIT_ASSERT_EQUAL((unsigned short)532, p->compatibleVersion()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 9, p->flags()); + CPPUNIT_ASSERT_EQUAL((unsigned char)128, p->globalVolume()); + CPPUNIT_ASSERT_EQUAL((unsigned char) 48, p->mixVolume()); + CPPUNIT_ASSERT_EQUAL((unsigned char)125, p->tempo()); + CPPUNIT_ASSERT_EQUAL((unsigned char) 6, p->bpmSpeed()); + CPPUNIT_ASSERT_EQUAL((unsigned char)128, p->panningSeparation()); + CPPUNIT_ASSERT_EQUAL((unsigned char) 0, p->pitchWheelDepth()); + CPPUNIT_ASSERT_EQUAL(title, t->title()); + CPPUNIT_ASSERT_EQUAL(String(), t->artist()); + CPPUNIT_ASSERT_EQUAL(String(), t->album()); + CPPUNIT_ASSERT_EQUAL(comment, t->comment()); + CPPUNIT_ASSERT_EQUAL(String(), t->genre()); + CPPUNIT_ASSERT_EQUAL(0U, t->year()); + CPPUNIT_ASSERT_EQUAL(0U, t->track()); + CPPUNIT_ASSERT_EQUAL(String("Impulse Tracker"), t->trackerName()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestIT); diff --git a/Frameworks/TagLib/taglib/tests/test_list.cpp b/Frameworks/TagLib/taglib/tests/test_list.cpp new file mode 100644 index 000000000..1c6d8c4c6 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_list.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tlist.h> +#include <cppunit/extensions/HelperMacros.h> + +using namespace std; +using namespace TagLib; + +class TestList : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestList); + CPPUNIT_TEST(testAppend); + CPPUNIT_TEST(testDetach); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testAppend() + { + List<int> l1; + List<int> l2; + List<int> l3; + l1.append(2); + l2.append(3); + l2.append(4); + l1.append(l2); + l1.prepend(1); + l3.append(1); + l3.append(2); + l3.append(3); + l3.append(4); + CPPUNIT_ASSERT_EQUAL(4U, l1.size()); + CPPUNIT_ASSERT(l1 == l3); + } + + void testDetach() + { + List<int> l1; + l1.append(1); + l1.append(2); + l1.append(3); + l1.append(4); + + List<int> l2 = l1; + List<int>::Iterator it = l2.find(3); + *it = 33; + CPPUNIT_ASSERT_EQUAL(3, l1[2]); + CPPUNIT_ASSERT_EQUAL(33, l2[2]); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestList); diff --git a/Frameworks/TagLib/taglib/tests/test_map.cpp b/Frameworks/TagLib/taglib/tests/test_map.cpp new file mode 100644 index 000000000..b5e493b61 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_map.cpp @@ -0,0 +1,72 @@ +/*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tstring.h> +#include <tmap.h> +#include <cppunit/extensions/HelperMacros.h> + +using namespace std; +using namespace TagLib; + +class TestMap : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestMap); + CPPUNIT_TEST(testInsert); + CPPUNIT_TEST(testDetach); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testInsert() + { + Map<String, int> m1; + m1.insert("foo", 3); + m1.insert("bar", 5); + CPPUNIT_ASSERT_EQUAL(2U, m1.size()); + CPPUNIT_ASSERT_EQUAL(3, m1["foo"]); + CPPUNIT_ASSERT_EQUAL(5, m1["bar"]); + m1.insert("foo", 7); + CPPUNIT_ASSERT_EQUAL(2U, m1.size()); + CPPUNIT_ASSERT_EQUAL(7, m1["foo"]); + CPPUNIT_ASSERT_EQUAL(5, m1["bar"]); + } + + void testDetach() + { + Map<String, int> m1; + m1.insert("alice", 5); + m1.insert("bob", 9); + m1.insert("carol", 11); + + Map<String, int> m2 = m1; + Map<String, int>::Iterator it = m2.find("bob"); + (*it).second = 99; + CPPUNIT_ASSERT_EQUAL(9, m1["bob"]); + CPPUNIT_ASSERT_EQUAL(99, m2["bob"]); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMap); diff --git a/Frameworks/TagLib/taglib/tests/test_mod.cpp b/Frameworks/TagLib/taglib/tests/test_mod.cpp new file mode 100644 index 000000000..55fd74387 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_mod.cpp @@ -0,0 +1,133 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <modfile.h> +#include <tpropertymap.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +static const String titleBefore("title of song"); +static const String titleAfter("changed title"); + +static const String commentBefore( + "Instrument names\n" + "are abused as\n" + "comments in\n" + "module file formats.\n" + "-+-+-+-+-+-+-+-+-+-+-+\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); + +static const String newComment( + "This line will be truncated because it is too long for a mod instrument name.\n" + "This line is ok."); + +static const String commentAfter( + "This line will be trun\n" + "This line is ok.\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); + +class TestMod : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestMod); + CPPUNIT_TEST(testReadTags); + CPPUNIT_TEST(testWriteTags); + CPPUNIT_TEST(testPropertyInterface); + CPPUNIT_TEST_SUITE_END(); + +public: + void testReadTags() + { + testRead(TEST_FILE_PATH_C("test.mod"), titleBefore, commentBefore); + } + + void testWriteTags() + { + ScopedFileCopy copy("test", ".mod"); + { + Mod::File file(copy.fileName().c_str()); + CPPUNIT_ASSERT(file.tag() != 0); + file.tag()->setTitle(titleAfter); + file.tag()->setComment(newComment); + CPPUNIT_ASSERT(file.save()); + } + testRead(copy.fileName().c_str(), titleAfter, commentAfter); + CPPUNIT_ASSERT(fileEqual( + copy.fileName(), + TEST_FILE_PATH_C("changed.mod"))); + } + + void testPropertyInterface() + { + Mod::Tag t; + PropertyMap properties; + properties["BLA"] = String("bla"); + properties["ARTIST"] = String("artist1"); + properties["ARTIST"].append("artist2"); + properties["TITLE"] = String("title"); + + PropertyMap unsupported = t.setProperties(properties); + CPPUNIT_ASSERT(unsupported.contains("BLA")); + CPPUNIT_ASSERT(unsupported.contains("ARTIST")); + CPPUNIT_ASSERT_EQUAL(properties["ARTIST"], unsupported["ARTIST"]); + CPPUNIT_ASSERT(!unsupported.contains("TITLE")); + + properties = t.properties(); + CPPUNIT_ASSERT_EQUAL(StringList("title"), properties["TITLE"]); + } + +private: + void testRead(FileName fileName, const String &title, const String &comment) + { + Mod::File file(fileName); + + CPPUNIT_ASSERT(file.isValid()); + + Mod::Properties *p = file.audioProperties(); + Mod::Tag *t = file.tag(); + + CPPUNIT_ASSERT(0 != p); + CPPUNIT_ASSERT(0 != t); + + CPPUNIT_ASSERT_EQUAL(0, p->length()); + CPPUNIT_ASSERT_EQUAL(0, p->bitrate()); + CPPUNIT_ASSERT_EQUAL(0, p->sampleRate()); + CPPUNIT_ASSERT_EQUAL(8, p->channels()); + CPPUNIT_ASSERT_EQUAL(31U, p->instrumentCount()); + CPPUNIT_ASSERT_EQUAL((unsigned char)1, p->lengthInPatterns()); + CPPUNIT_ASSERT_EQUAL(title, t->title()); + CPPUNIT_ASSERT_EQUAL(String(), t->artist()); + CPPUNIT_ASSERT_EQUAL(String(), t->album()); + CPPUNIT_ASSERT_EQUAL(comment, t->comment()); + CPPUNIT_ASSERT_EQUAL(String(), t->genre()); + CPPUNIT_ASSERT_EQUAL(0U, t->year()); + CPPUNIT_ASSERT_EQUAL(0U, t->track()); + CPPUNIT_ASSERT_EQUAL(String("StarTrekker"), t->trackerName()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMod); diff --git a/Frameworks/TagLib/taglib/tests/test_mp4.cpp b/Frameworks/TagLib/taglib/tests/test_mp4.cpp new file mode 100644 index 000000000..5f96c9c01 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_mp4.cpp @@ -0,0 +1,659 @@ +/*************************************************************************** + copyright : (C) 2008 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <mp4tag.h> +#include <tbytevectorlist.h> +#include <tbytevectorstream.h> +#include <tpropertymap.h> +#include <mp4atom.h> +#include <mp4file.h> +#include <cppunit/extensions/HelperMacros.h> +#include "plainfile.h" +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestMP4 : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestMP4); + CPPUNIT_TEST(testPropertiesAAC); + CPPUNIT_TEST(testPropertiesAACWithoutBitrate); + CPPUNIT_TEST(testPropertiesALAC); + CPPUNIT_TEST(testPropertiesALACWithoutBitrate); + CPPUNIT_TEST(testPropertiesM4V); + CPPUNIT_TEST(testFreeForm); + CPPUNIT_TEST(testCheckValid); + CPPUNIT_TEST(testHasTag); + CPPUNIT_TEST(testIsEmpty); + CPPUNIT_TEST(testUpdateStco); + CPPUNIT_TEST(testSaveExisingWhenIlstIsLast); + CPPUNIT_TEST(test64BitAtom); + CPPUNIT_TEST(testGnre); + CPPUNIT_TEST(testCovrRead); + CPPUNIT_TEST(testCovrWrite); + CPPUNIT_TEST(testCovrRead2); + CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testPropertiesAllSupported); + CPPUNIT_TEST(testPropertiesMovement); + CPPUNIT_TEST(testFuzzedFile); + CPPUNIT_TEST(testRepeatedSave); + CPPUNIT_TEST(testWithZeroLengthAtom); + CPPUNIT_TEST(testEmptyValuesRemoveItems); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testPropertiesAAC() + { + MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3708, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec()); + } + + void testPropertiesAACWithoutBitrate() + { + ByteVector aacData = PlainFile(TEST_FILE_PATH_C("has-tags.m4a")).readAll(); + CPPUNIT_ASSERT_GREATER(1960U, aacData.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("mp4a"), aacData.mid(1890, 4)); + // Set the bitrate to zero + for (int offset = 1956; offset < 1960; ++offset) { + aacData[offset] = 0; + } + ByteVectorStream aacStream(aacData); + MP4::File f(&aacStream); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3708, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec()); + } + + void testPropertiesALAC() + { + MP4::File f(TEST_FILE_PATH_C("empty_alac.m4a")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, f.audioProperties()->codec()); + } + + void testPropertiesALACWithoutBitrate() + { + ByteVector alacData = PlainFile(TEST_FILE_PATH_C("empty_alac.m4a")).readAll(); + CPPUNIT_ASSERT_GREATER(474U, alacData.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("alac"), alacData.mid(446, 4)); + // Set the bitrate to zero + for (int offset = 470; offset < 474; ++offset) { + alacData[offset] = 0; + } + ByteVectorStream alacStream(alacData); + MP4::File f(&alacStream); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, f.audioProperties()->codec()); + } + + void testPropertiesM4V() + { + MP4::File f(TEST_FILE_PATH_C("blank_video.m4v")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(975, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(96, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec()); + } + + void testCheckValid() + { + MP4::File f(TEST_FILE_PATH_C("empty.aiff")); + CPPUNIT_ASSERT(!f.isValid()); + } + + void testHasTag() + { + { + MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.hasMP4Tag()); + } + + ScopedFileCopy copy("no-tags", ".m4a"); + + { + MP4::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.hasMP4Tag()); + f.tag()->setTitle("TITLE"); + f.save(); + } + { + MP4::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.hasMP4Tag()); + } + } + + void testIsEmpty() + { + MP4::Tag t1; + CPPUNIT_ASSERT(t1.isEmpty()); + t1.setArtist("Foo"); + CPPUNIT_ASSERT(!t1.isEmpty()); + + MP4::Tag t2; + t2.setItem("foo", "bar"); + CPPUNIT_ASSERT(!t2.isEmpty()); + } + + void testUpdateStco() + { + ScopedFileCopy copy("no-tags", ".3g2"); + string filename = copy.fileName(); + + ByteVectorList data1; + + { + MP4::File f(filename.c_str()); + f.tag()->setArtist(ByteVector(3000, 'x')); + + MP4::Atoms a(&f); + MP4::Atom *stco = a.find("moov")->findall("stco", true)[0]; + f.seek(stco->offset + 12); + ByteVector data = f.readBlock(stco->length - 12); + unsigned int count = data.mid(0, 4).toUInt(); + int pos = 4; + while (count--) { + unsigned int offset = data.mid(pos, 4).toUInt(); + f.seek(offset); + data1.append(f.readBlock(20)); + pos += 4; + } + + f.save(); + } + + { + MP4::File f(filename.c_str()); + + MP4::Atoms a(&f); + MP4::Atom *stco = a.find("moov")->findall("stco", true)[0]; + f.seek(stco->offset + 12); + ByteVector data = f.readBlock(stco->length - 12); + unsigned int count = data.mid(0, 4).toUInt(); + int pos = 4, i = 0; + while (count--) { + unsigned int offset = data.mid(pos, 4).toUInt(); + f.seek(offset); + CPPUNIT_ASSERT_EQUAL(data1[i], f.readBlock(20)); + pos += 4; + i++; + } + } + } + + void testFreeForm() + { + ScopedFileCopy copy("has-tags", ".m4a"); + string filename = copy.fileName(); + + { + MP4::File f(filename.c_str()); + CPPUNIT_ASSERT(f.tag()->contains("----:com.apple.iTunes:iTunNORM")); + f.tag()->setItem("----:org.kde.TagLib:Foo", StringList("Bar")); + f.save(); + } + { + MP4::File f(filename.c_str()); + CPPUNIT_ASSERT(f.tag()->contains("----:org.kde.TagLib:Foo")); + CPPUNIT_ASSERT_EQUAL(String("Bar"), + f.tag()->item("----:org.kde.TagLib:Foo").toStringList().front()); + f.save(); + } + } + + void testSaveExisingWhenIlstIsLast() + { + ScopedFileCopy copy("ilst-is-last", ".m4a"); + string filename = copy.fileName(); + + { + MP4::File f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL(String("82,164"), + f.tag()->item("----:com.apple.iTunes:replaygain_track_minmax").toStringList().front()); + CPPUNIT_ASSERT_EQUAL(String("Pearl Jam"), f.tag()->artist()); + f.tag()->setComment("foo"); + f.save(); + } + { + MP4::File f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL(String("82,164"), + f.tag()->item("----:com.apple.iTunes:replaygain_track_minmax").toStringList().front()); + CPPUNIT_ASSERT_EQUAL(String("Pearl Jam"), f.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(String("foo"), f.tag()->comment()); + } + } + + void test64BitAtom() + { + ScopedFileCopy copy("64bit", ".mp4"); + string filename = copy.fileName(); + + { + MP4::File f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL(true, f.tag()->itemMap()["cpil"].toBool()); + + MP4::Atoms atoms(&f); + MP4::Atom *moov = atoms.atoms[0]; + CPPUNIT_ASSERT_EQUAL(long(77), moov->length); + + f.tag()->setItem("pgap", true); + f.save(); + } + { + MP4::File f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL(true, f.tag()->item("cpil").toBool()); + CPPUNIT_ASSERT_EQUAL(true, f.tag()->item("pgap").toBool()); + + MP4::Atoms atoms(&f); + MP4::Atom *moov = atoms.atoms[0]; + // original size + 'pgap' size + padding + CPPUNIT_ASSERT_EQUAL(long(77 + 25 + 974), moov->length); + } + } + + void testGnre() + { + MP4::File f(TEST_FILE_PATH_C("gnre.m4a")); + CPPUNIT_ASSERT_EQUAL(TagLib::String("Ska"), f.tag()->genre()); + } + + void testCovrRead() + { + MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); + CPPUNIT_ASSERT(f.tag()->contains("covr")); + MP4::CoverArtList l = f.tag()->item("covr").toCoverArtList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, l.size()); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); + CPPUNIT_ASSERT_EQUAL((unsigned int)79, l[0].data().size()); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::JPEG, l[1].format()); + CPPUNIT_ASSERT_EQUAL((unsigned int)287, l[1].data().size()); + } + + void testCovrWrite() + { + ScopedFileCopy copy("has-tags", ".m4a"); + string filename = copy.fileName(); + + { + MP4::File f(filename.c_str()); + CPPUNIT_ASSERT(f.tag()->contains("covr")); + MP4::CoverArtList l = f.tag()->item("covr").toCoverArtList(); + l.append(MP4::CoverArt(MP4::CoverArt::PNG, "foo")); + f.tag()->setItem("covr", l); + f.save(); + } + { + MP4::File f(filename.c_str()); + CPPUNIT_ASSERT(f.tag()->contains("covr")); + MP4::CoverArtList l = f.tag()->item("covr").toCoverArtList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, l.size()); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); + CPPUNIT_ASSERT_EQUAL((unsigned int)79, l[0].data().size()); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::JPEG, l[1].format()); + CPPUNIT_ASSERT_EQUAL((unsigned int)287, l[1].data().size()); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[2].format()); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, l[2].data().size()); + } + } + + void testCovrRead2() + { + MP4::File f(TEST_FILE_PATH_C("covr-junk.m4a")); + CPPUNIT_ASSERT(f.tag()->contains("covr")); + MP4::CoverArtList l = f.tag()->item("covr").toCoverArtList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, l.size()); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); + CPPUNIT_ASSERT_EQUAL((unsigned int)79, l[0].data().size()); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::JPEG, l[1].format()); + CPPUNIT_ASSERT_EQUAL((unsigned int)287, l[1].data().size()); + } + + void testProperties() + { + MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); + + PropertyMap tags = f.properties(); + + CPPUNIT_ASSERT_EQUAL(StringList("Test Artist"), tags["ARTIST"]); + + tags["TRACKNUMBER"] = StringList("2/4"); + tags["DISCNUMBER"] = StringList("3/5"); + tags["BPM"] = StringList("123"); + tags["ARTIST"] = StringList("Foo Bar"); + tags["COMPILATION"] = StringList("1"); + f.setProperties(tags); + + tags = f.properties(); + + CPPUNIT_ASSERT(f.tag()->contains("trkn")); + CPPUNIT_ASSERT_EQUAL(2, f.tag()->item("trkn").toIntPair().first); + CPPUNIT_ASSERT_EQUAL(4, f.tag()->item("trkn").toIntPair().second); + CPPUNIT_ASSERT_EQUAL(StringList("2/4"), tags["TRACKNUMBER"]); + + CPPUNIT_ASSERT(f.tag()->contains("disk")); + CPPUNIT_ASSERT_EQUAL(3, f.tag()->item("disk").toIntPair().first); + CPPUNIT_ASSERT_EQUAL(5, f.tag()->item("disk").toIntPair().second); + CPPUNIT_ASSERT_EQUAL(StringList("3/5"), tags["DISCNUMBER"]); + + CPPUNIT_ASSERT(f.tag()->contains("tmpo")); + CPPUNIT_ASSERT_EQUAL(123, f.tag()->item("tmpo").toInt()); + CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]); + + CPPUNIT_ASSERT(f.tag()->contains("\251ART")); + CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), f.tag()->item("\251ART").toStringList()); + CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]); + + CPPUNIT_ASSERT(f.tag()->contains("cpil")); + CPPUNIT_ASSERT_EQUAL(true, f.tag()->item("cpil").toBool()); + CPPUNIT_ASSERT_EQUAL(StringList("1"), tags["COMPILATION"]); + + tags["COMPILATION"] = StringList("0"); + f.setProperties(tags); + + tags = f.properties(); + + CPPUNIT_ASSERT(f.tag()->contains("cpil")); + CPPUNIT_ASSERT_EQUAL(false, f.tag()->item("cpil").toBool()); + CPPUNIT_ASSERT_EQUAL(StringList("0"), tags["COMPILATION"]); + + // Empty properties do not result in access violations + // when converting integers + tags["TRACKNUMBER"] = StringList(); + tags["DISCNUMBER"] = StringList(); + tags["BPM"] = StringList(); + tags["COMPILATION"] = StringList(); + f.setProperties(tags); + } + + void testPropertiesAllSupported() + { + PropertyMap tags; + tags["ALBUM"] = StringList("Album"); + tags["ALBUMARTIST"] = StringList("Album Artist"); + tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + tags["ALBUMSORT"] = StringList("Album Sort"); + tags["ARTIST"] = StringList("Artist"); + tags["ARTISTS"] = StringList("Artists"); + tags["ARTISTSORT"] = StringList("Artist Sort"); + tags["ASIN"] = StringList("ASIN"); + tags["BARCODE"] = StringList("Barcode"); + tags["BPM"] = StringList("123"); + tags["CATALOGNUMBER"] = StringList("Catalog Number"); + tags["COMMENT"] = StringList("Comment"); + tags["COMPILATION"] = StringList("1"); + tags["COMPOSER"] = StringList("Composer"); + tags["COMPOSERSORT"] = StringList("Composer Sort"); + tags["CONDUCTOR"] = StringList("Conductor"); + tags["COPYRIGHT"] = StringList("2021 Copyright"); + tags["DATE"] = StringList("2021-01-03 12:29:23"); + tags["DISCNUMBER"] = StringList("3/5"); + tags["DISCSUBTITLE"] = StringList("Disc Subtitle"); + tags["DJMIXER"] = StringList("DJ Mixer"); + tags["ENCODEDBY"] = StringList("Encoded by"); + tags["ENGINEER"] = StringList("Engineer"); + tags["GAPLESSPLAYBACK"] = StringList("1"); + tags["GENRE"] = StringList("Genre"); + tags["GROUPING"] = StringList("Grouping"); + tags["ISRC"] = StringList("UKAAA0500001"); + tags["LABEL"] = StringList("Label"); + tags["LANGUAGE"] = StringList("eng"); + tags["LICENSE"] = StringList("License"); + tags["LYRICIST"] = StringList("Lyricist"); + tags["LYRICS"] = StringList("Lyrics"); + tags["MEDIA"] = StringList("Media"); + tags["MIXER"] = StringList("Mixer"); + tags["MOOD"] = StringList("Mood"); + tags["MOVEMENTCOUNT"] = StringList("3"); + tags["MOVEMENTNAME"] = StringList("Movement Name"); + tags["MOVEMENTNUMBER"] = StringList("2"); + tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); + tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + tags["MUSICBRAINZ_WORKID"] = StringList("MusicBrainz_WorkID"); + tags["ORIGINALDATE"] = StringList("2021-01-03 13:52:19"); + tags["PODCAST"] = StringList("1"); + tags["PODCASTCATEGORY"] = StringList("Podcast Category"); + tags["PODCASTDESC"] = StringList("Podcast Description"); + tags["PODCASTID"] = StringList("Podcast ID"); + tags["PODCASTURL"] = StringList("Podcast URL"); + tags["PRODUCER"] = StringList("Producer"); + tags["RELEASECOUNTRY"] = StringList("Release Country"); + tags["RELEASESTATUS"] = StringList("Release Status"); + tags["RELEASETYPE"] = StringList("Release Type"); + tags["REMIXER"] = StringList("Remixer"); + tags["SCRIPT"] = StringList("Script"); + tags["SHOWSORT"] = StringList("Show Sort"); + tags["SHOWWORKMOVEMENT"] = StringList("1"); + tags["SUBTITLE"] = StringList("Subtitle"); + tags["TITLE"] = StringList("Title"); + tags["TITLESORT"] = StringList("Title Sort"); + tags["TRACKNUMBER"] = StringList("2/4"); + tags["TVEPISODE"] = StringList("3"); + tags["TVEPISODEID"] = StringList("TV Episode ID"); + tags["TVNETWORK"] = StringList("TV Network"); + tags["TVSEASON"] = StringList("2"); + tags["TVSHOW"] = StringList("TV Show"); + tags["WORK"] = StringList("Work"); + + ScopedFileCopy copy("no-tags", ".m4a"); + { + MP4::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + CPPUNIT_ASSERT(properties.isEmpty()); + f.setProperties(tags); + f.save(); + } + { + const MP4::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + if (tags != properties) { + CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString()); + } + CPPUNIT_ASSERT(tags == properties); + } + } + + void testPropertiesMovement() + { + MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); + + PropertyMap tags = f.properties(); + + tags["WORK"] = StringList("Foo"); + tags["MOVEMENTNAME"] = StringList("Bar"); + tags["MOVEMENTNUMBER"] = StringList("2"); + tags["MOVEMENTCOUNT"] = StringList("3"); + tags["SHOWWORKMOVEMENT"] = StringList("1"); + f.setProperties(tags); + + tags = f.properties(); + + CPPUNIT_ASSERT(f.tag()->contains("\251wrk")); + CPPUNIT_ASSERT_EQUAL(StringList("Foo"), f.tag()->item("\251wrk").toStringList()); + CPPUNIT_ASSERT_EQUAL(StringList("Foo"), tags["WORK"]); + + CPPUNIT_ASSERT(f.tag()->contains("\251mvn")); + CPPUNIT_ASSERT_EQUAL(StringList("Bar"), f.tag()->item("\251mvn").toStringList()); + CPPUNIT_ASSERT_EQUAL(StringList("Bar"), tags["MOVEMENTNAME"]); + + CPPUNIT_ASSERT(f.tag()->contains("\251mvi")); + CPPUNIT_ASSERT_EQUAL(2, f.tag()->item("\251mvi").toInt()); + CPPUNIT_ASSERT_EQUAL(StringList("2"), tags["MOVEMENTNUMBER"]); + + CPPUNIT_ASSERT(f.tag()->contains("\251mvc")); + CPPUNIT_ASSERT_EQUAL(3, f.tag()->item("\251mvc").toInt()); + CPPUNIT_ASSERT_EQUAL(StringList("3"), tags["MOVEMENTCOUNT"]); + + CPPUNIT_ASSERT(f.tag()->contains("shwm")); + CPPUNIT_ASSERT_EQUAL(true, f.tag()->item("shwm").toBool()); + CPPUNIT_ASSERT_EQUAL(StringList("1"), tags["SHOWWORKMOVEMENT"]); + + tags["SHOWWORKMOVEMENT"] = StringList("0"); + f.setProperties(tags); + + tags = f.properties(); + + CPPUNIT_ASSERT(f.tag()->contains("shwm")); + CPPUNIT_ASSERT_EQUAL(false, f.tag()->item("shwm").toBool()); + CPPUNIT_ASSERT_EQUAL(StringList("0"), tags["SHOWWORKMOVEMENT"]); + + tags["WORK"] = StringList(); + tags["MOVEMENTNAME"] = StringList(); + tags["MOVEMENTNUMBER"] = StringList(); + tags["MOVEMENTCOUNT"] = StringList(); + tags["SHOWWORKMOVEMENT"] = StringList(); + f.setProperties(tags); + } + + void testFuzzedFile() + { + MP4::File f(TEST_FILE_PATH_C("infloop.m4a")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testRepeatedSave() + { + ScopedFileCopy copy("no-tags", ".m4a"); + + MP4::File f(copy.fileName().c_str()); + f.tag()->setTitle("0123456789"); + f.save(); + f.save(); + CPPUNIT_ASSERT_EQUAL(2862L, f.find("0123456789")); + CPPUNIT_ASSERT_EQUAL(-1L, f.find("0123456789", 2863)); + } + + void testWithZeroLengthAtom() + { + MP4::File f(TEST_FILE_PATH_C("zero-length-mdat.m4a")); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(1115, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(22050, f.audioProperties()->sampleRate()); + } + + void testEmptyValuesRemoveItems() + { + const MP4::File f(TEST_FILE_PATH_C("has-tags.m4a")); + MP4::Tag *tag = f.tag(); + const String testTitle("Title"); + const String testArtist("Artist"); + const String testAlbum("Album"); + const String testComment("Comment"); + const String testGenre("Genre"); + const String nullString; + const unsigned int testYear = 2020; + const unsigned int testTrack = 1; + const unsigned int zeroUInt = 0; + + tag->setTitle(testTitle); + CPPUNIT_ASSERT_EQUAL(testTitle, tag->title()); + CPPUNIT_ASSERT(tag->contains("\251nam")); + tag->setArtist(testArtist); + CPPUNIT_ASSERT_EQUAL(testArtist, tag->artist()); + CPPUNIT_ASSERT(tag->contains("\251ART")); + tag->setAlbum(testAlbum); + CPPUNIT_ASSERT_EQUAL(testAlbum, tag->album()); + CPPUNIT_ASSERT(tag->contains("\251alb")); + tag->setComment(testComment); + CPPUNIT_ASSERT_EQUAL(testComment, tag->comment()); + CPPUNIT_ASSERT(tag->contains("\251cmt")); + tag->setGenre(testGenre); + CPPUNIT_ASSERT_EQUAL(testGenre, tag->genre()); + CPPUNIT_ASSERT(tag->contains("\251gen")); + tag->setYear(testYear); + CPPUNIT_ASSERT_EQUAL(testYear, tag->year()); + CPPUNIT_ASSERT(tag->contains("\251day")); + tag->setTrack(testTrack); + CPPUNIT_ASSERT_EQUAL(testTrack, tag->track()); + CPPUNIT_ASSERT(tag->contains("trkn")); + + tag->setTitle(nullString); + CPPUNIT_ASSERT_EQUAL(nullString, tag->title()); + CPPUNIT_ASSERT(!tag->contains("\251nam")); + tag->setArtist(nullString); + CPPUNIT_ASSERT_EQUAL(nullString, tag->artist()); + CPPUNIT_ASSERT(!tag->contains("\251ART")); + tag->setAlbum(nullString); + CPPUNIT_ASSERT_EQUAL(nullString, tag->album()); + CPPUNIT_ASSERT(!tag->contains("\251alb")); + tag->setComment(nullString); + CPPUNIT_ASSERT_EQUAL(nullString, tag->comment()); + CPPUNIT_ASSERT(!tag->contains("\251cmt")); + tag->setGenre(nullString); + CPPUNIT_ASSERT_EQUAL(nullString, tag->genre()); + CPPUNIT_ASSERT(!tag->contains("\251gen")); + tag->setYear(zeroUInt); + CPPUNIT_ASSERT_EQUAL(zeroUInt, tag->year()); + CPPUNIT_ASSERT(!tag->contains("\251day")); + tag->setTrack(zeroUInt); + CPPUNIT_ASSERT_EQUAL(zeroUInt, tag->track()); + CPPUNIT_ASSERT(!tag->contains("trkn")); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4); diff --git a/Frameworks/TagLib/taglib/tests/test_mp4coverart.cpp b/Frameworks/TagLib/taglib/tests/test_mp4coverart.cpp new file mode 100644 index 000000000..49ef04707 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_mp4coverart.cpp @@ -0,0 +1,74 @@ +/*************************************************************************** + copyright : (C) 2009 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <mp4coverart.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestMP4CoverArt : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestMP4CoverArt); + CPPUNIT_TEST(testSimple); + CPPUNIT_TEST(testList); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testSimple() + { + MP4::CoverArt c(MP4::CoverArt::PNG, "foo"); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, c.format()); + CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), c.data()); + + MP4::CoverArt c2(c); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, c2.format()); + CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), c2.data()); + + MP4::CoverArt c3 = c; + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, c3.format()); + CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), c3.data()); + } + + void testList() + { + MP4::CoverArtList l; + l.append(MP4::CoverArt(MP4::CoverArt::PNG, "foo")); + l.append(MP4::CoverArt(MP4::CoverArt::JPEG, "bar")); + + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); + CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), l[0].data()); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::JPEG, l[1].format()); + CPPUNIT_ASSERT_EQUAL(ByteVector("bar"), l[1].data()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4CoverArt); diff --git a/Frameworks/TagLib/taglib/tests/test_mp4item.cpp b/Frameworks/TagLib/taglib/tests/test_mp4item.cpp new file mode 100644 index 000000000..a9a5c99ae --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_mp4item.cpp @@ -0,0 +1,62 @@ +/*************************************************************************** + copyright : (C) 2009 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <mp4coverart.h> +#include <mp4item.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestMP4Item : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestMP4Item); + CPPUNIT_TEST(testCoverArtList); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testCoverArtList() + { + MP4::CoverArtList l; + l.append(MP4::CoverArt(MP4::CoverArt::PNG, "foo")); + l.append(MP4::CoverArt(MP4::CoverArt::JPEG, "bar")); + + MP4::Item i(l); + MP4::CoverArtList l2 = i.toCoverArtList(); + + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); + CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), l[0].data()); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::JPEG, l[1].format()); + CPPUNIT_ASSERT_EQUAL(ByteVector("bar"), l[1].data()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4Item); diff --git a/Frameworks/TagLib/taglib/tests/test_mpc.cpp b/Frameworks/TagLib/taglib/tests/test_mpc.cpp new file mode 100644 index 000000000..25f759443 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_mpc.cpp @@ -0,0 +1,193 @@ +/*************************************************************************** + copyright : (C) 2012 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <apetag.h> +#include <id3v1tag.h> +#include <tstringlist.h> +#include <tbytevectorlist.h> +#include <tpropertymap.h> +#include <mpcfile.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestMPC : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestMPC); + CPPUNIT_TEST(testPropertiesSV8); + CPPUNIT_TEST(testPropertiesSV7); + CPPUNIT_TEST(testPropertiesSV5); + CPPUNIT_TEST(testPropertiesSV4); + CPPUNIT_TEST(testFuzzedFile1); + CPPUNIT_TEST(testFuzzedFile2); + CPPUNIT_TEST(testFuzzedFile3); + CPPUNIT_TEST(testFuzzedFile4); + CPPUNIT_TEST(testStripAndProperties); + CPPUNIT_TEST(testRepeatedSave); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testPropertiesSV8() + { + MPC::File f(TEST_FILE_PATH_C("sv8_header.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->mpcVersion()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(1497, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(66014U, f.audioProperties()->sampleFrames()); + } + + void testPropertiesSV7() + { + MPC::File f(TEST_FILE_PATH_C("click.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(7, f.audioProperties()->mpcVersion()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(40, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(318, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1760U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(14221, f.audioProperties()->trackGain()); + CPPUNIT_ASSERT_EQUAL(19848, f.audioProperties()->trackPeak()); + CPPUNIT_ASSERT_EQUAL(14221, f.audioProperties()->albumGain()); + CPPUNIT_ASSERT_EQUAL(19848, f.audioProperties()->albumPeak()); + } + + void testPropertiesSV5() + { + MPC::File f(TEST_FILE_PATH_C("sv5_header.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(5, f.audioProperties()->mpcVersion()); + CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(26371, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1162944U, f.audioProperties()->sampleFrames()); + } + + void testPropertiesSV4() + { + MPC::File f(TEST_FILE_PATH_C("sv4_header.mpc")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->mpcVersion()); + CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(26371, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(1162944U, f.audioProperties()->sampleFrames()); + } + + void testFuzzedFile1() + { + MPC::File f(TEST_FILE_PATH_C("zerodiv.mpc")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testFuzzedFile2() + { + MPC::File f(TEST_FILE_PATH_C("infloop.mpc")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testFuzzedFile3() + { + MPC::File f(TEST_FILE_PATH_C("segfault.mpc")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testFuzzedFile4() + { + MPC::File f(TEST_FILE_PATH_C("segfault2.mpc")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testStripAndProperties() + { + ScopedFileCopy copy("click", ".mpc"); + + { + MPC::File f(copy.fileName().c_str()); + f.APETag(true)->setTitle("APE"); + f.ID3v1Tag(true)->setTitle("ID3v1"); + f.save(); + } + { + MPC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("APE"), f.properties()["TITLE"].front()); + f.strip(MPC::File::APE); + CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front()); + f.strip(MPC::File::ID3v1); + CPPUNIT_ASSERT(f.properties().isEmpty()); + f.save(); + } + { + MPC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasAPETag()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + CPPUNIT_ASSERT(f.properties()["TITLE"].isEmpty()); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + + void testRepeatedSave() + { + ScopedFileCopy copy("click", ".mpc"); + + { + MPC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasAPETag()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + + f.APETag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.save(); + + f.APETag()->setTitle("0"); + f.save(); + + f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.APETag()->setTitle("01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789"); + f.save(); + } + { + MPC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasAPETag()); + CPPUNIT_ASSERT(f.hasID3v1Tag()); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMPC); diff --git a/Frameworks/TagLib/taglib/tests/test_mpeg.cpp b/Frameworks/TagLib/taglib/tests/test_mpeg.cpp new file mode 100644 index 000000000..1db1c9d4f --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_mpeg.cpp @@ -0,0 +1,539 @@ +/*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tstring.h> +#include <tpropertymap.h> +#include <mpegfile.h> +#include <id3v2tag.h> +#include <id3v1tag.h> +#include <apetag.h> +#include <mpegproperties.h> +#include <xingheader.h> +#include <mpegheader.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestMPEG : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestMPEG); + CPPUNIT_TEST(testAudioPropertiesXingHeaderCBR); + CPPUNIT_TEST(testAudioPropertiesXingHeaderVBR); + CPPUNIT_TEST(testAudioPropertiesVBRIHeader); + CPPUNIT_TEST(testAudioPropertiesNoVBRHeaders); + CPPUNIT_TEST(testSkipInvalidFrames1); + CPPUNIT_TEST(testSkipInvalidFrames2); + CPPUNIT_TEST(testSkipInvalidFrames3); + CPPUNIT_TEST(testVersion2DurationWithXingHeader); + CPPUNIT_TEST(testSaveID3v24); + CPPUNIT_TEST(testSaveID3v24WrongParam); + CPPUNIT_TEST(testSaveID3v23); + CPPUNIT_TEST(testDuplicateID3v2); + CPPUNIT_TEST(testFuzzedFile); + CPPUNIT_TEST(testFrameOffset); + CPPUNIT_TEST(testStripAndProperties); + CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testRepeatedSave1); + CPPUNIT_TEST(testRepeatedSave2); + CPPUNIT_TEST(testRepeatedSave3); + CPPUNIT_TEST(testEmptyID3v2); + CPPUNIT_TEST(testEmptyID3v1); + CPPUNIT_TEST(testEmptyAPE); + CPPUNIT_TEST(testIgnoreGarbage); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testAudioPropertiesXingHeaderCBR() + { + MPEG::File f(TEST_FILE_PATH_C("lame_cbr.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(1887164, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type()); + } + + void testAudioPropertiesXingHeaderVBR() + { + MPEG::File f(TEST_FILE_PATH_C("lame_vbr.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(1887164, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(70, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type()); + } + + void testAudioPropertiesVBRIHeader() + { + MPEG::File f(TEST_FILE_PATH_C("rare_frames.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(222, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(222198, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(233, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::VBRI, f.audioProperties()->xingHeader()->type()); + } + + void testAudioPropertiesNoVBRHeaders() + { + MPEG::File f(TEST_FILE_PATH_C("bladeenc.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3553, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT(!f.audioProperties()->xingHeader()); + + const long last = f.lastFrameOffset(); + const MPEG::Header lastHeader(&f, last, false); + + CPPUNIT_ASSERT_EQUAL(28213L, last); + CPPUNIT_ASSERT_EQUAL(209, lastHeader.frameLength()); + } + + void testSkipInvalidFrames1() + { + MPEG::File f(TEST_FILE_PATH_C("invalid-frames1.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(392, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(160, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT(!f.audioProperties()->xingHeader()); + } + + void testSkipInvalidFrames2() + { + MPEG::File f(TEST_FILE_PATH_C("invalid-frames2.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(314, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(192, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT(!f.audioProperties()->xingHeader()); + } + + void testSkipInvalidFrames3() + { + MPEG::File f(TEST_FILE_PATH_C("invalid-frames3.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(183, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(320, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT(!f.audioProperties()->xingHeader()); + } + + void testVersion2DurationWithXingHeader() + { + MPEG::File f(TEST_FILE_PATH_C("mpeg2.mp3")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(5387, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(5387285, f.audioProperties()->lengthInMilliseconds()); + } + + void testSaveID3v24() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + String xxx = ByteVector(254, 'X'); + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); + + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(MPEG::File::AllTags, File::StripOthers, ID3v2::v4); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + } + { + MPEG::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)4, f2.ID3v2Tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } + } + + void testSaveID3v24WrongParam() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + String xxx = ByteVector(254, 'X'); + { + MPEG::File f(newname.c_str()); + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(MPEG::File::AllTags, true, 8); + } + { + MPEG::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)4, f2.ID3v2Tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } + } + + void testSaveID3v23() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + String xxx = ByteVector(254, 'X'); + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); + + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(MPEG::File::AllTags, File::StripOthers, ID3v2::v3); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + } + { + MPEG::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, f2.ID3v2Tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } + } + + void testDuplicateID3v2() + { + MPEG::File f(TEST_FILE_PATH_C("duplicate_id3v2.mp3")); + + // duplicate_id3v2.mp3 has duplicate ID3v2 tags. + // Sample rate will be 32000 if can't skip the second tag. + + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + } + + void testFuzzedFile() + { + MPEG::File f(TEST_FILE_PATH_C("excessive_alloc.mp3")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testFrameOffset() + { + { + MPEG::File f(TEST_FILE_PATH_C("ape.mp3")); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL((long)0x0000, f.firstFrameOffset()); + CPPUNIT_ASSERT_EQUAL((long)0x1FD6, f.lastFrameOffset()); + } + { + MPEG::File f(TEST_FILE_PATH_C("ape-id3v1.mp3")); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL((long)0x0000, f.firstFrameOffset()); + CPPUNIT_ASSERT_EQUAL((long)0x1FD6, f.lastFrameOffset()); + } + { + MPEG::File f(TEST_FILE_PATH_C("ape-id3v2.mp3")); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL((long)0x041A, f.firstFrameOffset()); + CPPUNIT_ASSERT_EQUAL((long)0x23F0, f.lastFrameOffset()); + } + } + + void testStripAndProperties() + { + ScopedFileCopy copy("xing", ".mp3"); + + { + MPEG::File f(copy.fileName().c_str()); + f.ID3v2Tag(true)->setTitle("ID3v2"); + f.APETag(true)->setTitle("APE"); + f.ID3v1Tag(true)->setTitle("ID3v1"); + f.save(); + } + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("ID3v2"), f.properties()["TITLE"].front()); + f.strip(MPEG::File::ID3v2); + CPPUNIT_ASSERT_EQUAL(String("APE"), f.properties()["TITLE"].front()); + f.strip(MPEG::File::APE); + CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front()); + f.strip(MPEG::File::ID3v1); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + + void testProperties() + { + PropertyMap tags; + tags["ALBUM"] = StringList("Album"); + tags["ALBUMARTIST"] = StringList("Album Artist"); + tags["ALBUMARTISTSORT"] = StringList("Album Artist Sort"); + tags["ALBUMSORT"] = StringList("Album Sort"); + tags["ARRANGER"] = StringList("Arranger"); + tags["ARTIST"] = StringList("Artist"); + tags["ARTISTSORT"] = StringList("Artist Sort"); + tags["ARTISTWEBPAGE"] = StringList("Artist Web Page"); + tags["ASIN"] = StringList("ASIN"); + tags["AUDIOSOURCEWEBPAGE"] = StringList("Audio Source Web Page"); + tags["BARCODE"] = StringList("Barcode"); + tags["BPM"] = StringList("123"); + tags["CATALOGNUMBER"] = StringList("Catalog Number"); + tags["COMMENT"] = StringList("Comment"); + tags["COMMENT:CDESC"] = StringList("Comment with Description"); + tags["COMPOSER"] = StringList("Composer"); + tags["COMPOSERSORT"] = StringList("Composer Sort"); + tags["CONDUCTOR"] = StringList("Conductor"); + tags["CONTENTGROUP"] = StringList("Content Group"); + tags["COPYRIGHT"] = StringList("2021 Copyright"); + tags["COPYRIGHTURL"] = StringList("Copyright URL"); + tags["DATE"] = StringList("2021-01-03 12:29:23"); + tags["DISCNUMBER"] = StringList("3/5"); + tags["DJMIXER"] = StringList("DJ Mixer"); + tags["ENCODEDBY"] = StringList("Encoded by"); + tags["ENCODING"] = StringList("Encoding"); + tags["ENCODINGTIME"] = StringList("2021-01-03 13:48:44"); + tags["ENGINEER"] = StringList("Engineer"); + tags["FILETYPE"] = StringList("File Type"); + tags["FILEWEBPAGE"] = StringList("File Web Page"); + tags["GENRE"] = StringList("Genre"); + tags["GROUPING"] = StringList("Grouping"); + tags["INITIALKEY"] = StringList("Dbm"); + tags["ISRC"] = StringList("UKAAA0500001"); + tags["LABEL"] = StringList("Label"); + tags["LANGUAGE"] = StringList("eng"); + tags["LENGTH"] = StringList("1234"); + tags["LYRICIST"] = StringList("Lyricist"); + tags["LYRICS:LDESC"] = StringList("Lyrics"); + tags["MEDIA"] = StringList("Media"); + tags["MIXER"] = StringList("Mixer"); + tags["MOOD"] = StringList("Mood"); + tags["MOVEMENTNAME"] = StringList("Movement Name"); + tags["MOVEMENTNUMBER"] = StringList("2"); + tags["MUSICBRAINZ_ALBUMID"] = StringList("MusicBrainz_AlbumID"); + tags["MUSICBRAINZ_ALBUMARTISTID"] = StringList("MusicBrainz_AlbumartistID"); + tags["MUSICBRAINZ_ARTISTID"] = StringList("MusicBrainz_ArtistID"); + tags["MUSICBRAINZ_RELEASEGROUPID"] = StringList("MusicBrainz_ReleasegroupID"); + tags["MUSICBRAINZ_RELEASETRACKID"] = StringList("MusicBrainz_ReleasetrackID"); + tags["MUSICBRAINZ_TRACKID"] = StringList("MusicBrainz_TrackID"); + tags["MUSICBRAINZ_WORKID"] = StringList("MusicBrainz_WorkID"); + tags["ORIGINALALBUM"] = StringList("Original Album"); + tags["ORIGINALARTIST"] = StringList("Original Artist"); + tags["ORIGINALDATE"] = StringList("2021-01-03 13:52:19"); + tags["ORIGINALFILENAME"] = StringList("Original Filename"); + tags["ORIGINALLYRICIST"] = StringList("Original Lyricist"); + tags["OWNER"] = StringList("Owner"); + tags["PAYMENTWEBPAGE"] = StringList("Payment Web Page"); + tags["PERFORMER:DRUMS"] = StringList("Drummer"); + tags["PERFORMER:GUITAR"] = StringList("Guitarist"); + tags["PLAYLISTDELAY"] = StringList("10"); + tags["PODCAST"] = StringList(); + tags["PODCASTCATEGORY"] = StringList("Podcast Category"); + tags["PODCASTDESC"] = StringList("Podcast Description"); + tags["PODCASTID"] = StringList("Podcast ID"); + tags["PODCASTURL"] = StringList("Podcast URL"); + tags["PRODUCEDNOTICE"] = StringList("2021 Produced Notice"); + tags["PRODUCER"] = StringList("Producer"); + tags["PUBLISHERWEBPAGE"] = StringList("Publisher Web Page"); + tags["RADIOSTATION"] = StringList("Radio Station"); + tags["RADIOSTATIONOWNER"] = StringList("Radio Station Owner"); + tags["RELEASECOUNTRY"] = StringList("Release Country"); + tags["RELEASESTATUS"] = StringList("Release Status"); + tags["RELEASETYPE"] = StringList("Release Type"); + tags["REMIXER"] = StringList("Remixer"); + tags["SCRIPT"] = StringList("Script"); + tags["SUBTITLE"] = StringList("Subtitle"); + tags["TITLE"] = StringList("Title"); + tags["TITLESORT"] = StringList("Title Sort"); + tags["TRACKNUMBER"] = StringList("2/4"); + tags["URL:UDESC"] = StringList("URL"); + + ScopedFileCopy copy("xing", ".mp3"); + { + MPEG::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + CPPUNIT_ASSERT(properties.isEmpty()); + f.setProperties(tags); + f.save(); + } + { + const MPEG::File f(copy.fileName().c_str()); + PropertyMap properties = f.properties(); + if (tags != properties) { + CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString()); + } + CPPUNIT_ASSERT(tags == properties); + } + } + + void testRepeatedSave1() + { + ScopedFileCopy copy("xing", ".mp3"); + + { + MPEG::File f(copy.fileName().c_str()); + f.ID3v2Tag(true)->setTitle(std::string(4096, 'X').c_str()); + f.save(); + } + { + MPEG::File f(copy.fileName().c_str()); + f.ID3v2Tag(true)->setTitle(""); + f.save(); + f.ID3v2Tag(true)->setTitle(std::string(4096, 'X').c_str()); + f.save(); + CPPUNIT_ASSERT_EQUAL(5141L, f.firstFrameOffset()); + } + } + + void testRepeatedSave2() + { + ScopedFileCopy copy("xing", ".mp3"); + + MPEG::File f(copy.fileName().c_str()); + f.ID3v2Tag(true)->setTitle("0123456789"); + f.save(); + f.save(); + CPPUNIT_ASSERT_EQUAL(-1L, f.find("ID3", 3)); + } + + void testRepeatedSave3() + { + ScopedFileCopy copy("xing", ".mp3"); + + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasAPETag()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + + f.APETag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.save(); + f.APETag()->setTitle("0"); + f.save(); + f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.APETag()->setTitle("01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789"); + f.save(); + } + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasAPETag()); + CPPUNIT_ASSERT(f.hasID3v1Tag()); + } + } + + void testEmptyID3v2() + { + ScopedFileCopy copy("xing", ".mp3"); + + { + MPEG::File f(copy.fileName().c_str()); + f.ID3v2Tag(true)->setTitle("0123456789"); + f.save(MPEG::File::ID3v2); + } + { + MPEG::File f(copy.fileName().c_str()); + f.ID3v2Tag(true)->setTitle(""); + f.save(MPEG::File::ID3v2, File::StripNone); + } + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + } + } + + void testEmptyID3v1() + { + ScopedFileCopy copy("xing", ".mp3"); + + { + MPEG::File f(copy.fileName().c_str()); + f.ID3v1Tag(true)->setTitle("0123456789"); + f.save(MPEG::File::ID3v1); + } + { + MPEG::File f(copy.fileName().c_str()); + f.ID3v1Tag(true)->setTitle(""); + f.save(MPEG::File::ID3v1, File::StripNone); + } + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + } + } + + void testEmptyAPE() + { + ScopedFileCopy copy("xing", ".mp3"); + + { + MPEG::File f(copy.fileName().c_str()); + f.APETag(true)->setTitle("0123456789"); + f.save(MPEG::File::APE); + } + { + MPEG::File f(copy.fileName().c_str()); + f.APETag(true)->setTitle(""); + f.save(MPEG::File::APE, File::StripNone); + } + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasAPETag()); + } + } + + void testIgnoreGarbage() + { + const ScopedFileCopy copy("garbage", ".mp3"); + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(2255L, f.firstFrameOffset()); + CPPUNIT_ASSERT_EQUAL(6015L, f.lastFrameOffset()); + CPPUNIT_ASSERT_EQUAL(String("Title A"), f.ID3v2Tag()->title()); + f.ID3v2Tag()->setTitle("Title B"); + f.save(); + } + { + MPEG::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String("Title B"), f.ID3v2Tag()->title()); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMPEG); diff --git a/Frameworks/TagLib/taglib/tests/test_ogg.cpp b/Frameworks/TagLib/taglib/tests/test_ogg.cpp new file mode 100644 index 000000000..6564a2474 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_ogg.cpp @@ -0,0 +1,227 @@ +/*************************************************************************** + copyright : (C) 2009 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <tstringlist.h> +#include <tbytevectorlist.h> +#include <tpropertymap.h> +#include <oggfile.h> +#include <vorbisfile.h> +#include <oggpageheader.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestOGG : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestOGG); + CPPUNIT_TEST(testSimple); + CPPUNIT_TEST(testSplitPackets1); + CPPUNIT_TEST(testSplitPackets2); + CPPUNIT_TEST(testDictInterface1); + CPPUNIT_TEST(testDictInterface2); + CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testPageChecksum); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testSimple() + { + ScopedFileCopy copy("empty", ".ogg"); + string newname = copy.fileName(); + + { + Vorbis::File f(newname.c_str()); + f.tag()->setArtist("The Artist"); + f.save(); + } + { + Vorbis::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("The Artist"), f.tag()->artist()); + } + } + + void testSplitPackets1() + { + ScopedFileCopy copy("empty", ".ogg"); + string newname = copy.fileName(); + + const String text = longText(128 * 1024, true); + + { + Vorbis::File f(newname.c_str()); + f.tag()->setTitle(text); + f.save(); + } + { + Vorbis::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(136383L, f.length()); + CPPUNIT_ASSERT_EQUAL(19, f.lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(30U, f.packet(0).size()); + CPPUNIT_ASSERT_EQUAL(131127U, f.packet(1).size()); + CPPUNIT_ASSERT_EQUAL(3832U, f.packet(2).size()); + CPPUNIT_ASSERT_EQUAL(text, f.tag()->title()); + + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + + f.tag()->setTitle("ABCDE"); + f.save(); + } + { + Vorbis::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(4370L, f.length()); + CPPUNIT_ASSERT_EQUAL(3, f.lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(30U, f.packet(0).size()); + CPPUNIT_ASSERT_EQUAL(60U, f.packet(1).size()); + CPPUNIT_ASSERT_EQUAL(3832U, f.packet(2).size()); + CPPUNIT_ASSERT_EQUAL(String("ABCDE"), f.tag()->title()); + + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + } + } + + void testSplitPackets2() + { + ScopedFileCopy copy("empty", ".ogg"); + string newname = copy.fileName(); + + const String text = longText(60890, true); + + { + Vorbis::File f(newname.c_str()); + f.tag()->setTitle(text); + f.save(); + } + { + Vorbis::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(text, f.tag()->title()); + + f.tag()->setTitle("ABCDE"); + f.save(); + } + { + Vorbis::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(String("ABCDE"), f.tag()->title()); + } + } + + void testDictInterface1() + { + ScopedFileCopy copy("empty", ".ogg"); + string newname = copy.fileName(); + + Vorbis::File f(newname.c_str()); + + CPPUNIT_ASSERT_EQUAL((unsigned int)0, f.tag()->properties().size()); + + PropertyMap newTags; + StringList values("value 1"); + values.append("value 2"); + newTags["ARTIST"] = values; + f.tag()->setProperties(newTags); + + PropertyMap map = f.tag()->properties(); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, map.size()); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, map["ARTIST"].size()); + CPPUNIT_ASSERT_EQUAL(String("value 1"), map["ARTIST"][0]); + } + + void testDictInterface2() + { + ScopedFileCopy copy("test", ".ogg"); + string newname = copy.fileName(); + + Vorbis::File f(newname.c_str()); + PropertyMap tags = f.tag()->properties(); + + CPPUNIT_ASSERT_EQUAL((unsigned int)2, tags["UNUSUALTAG"].size()); + CPPUNIT_ASSERT_EQUAL(String("usual value"), tags["UNUSUALTAG"][0]); + CPPUNIT_ASSERT_EQUAL(String("another value"), tags["UNUSUALTAG"][1]); + CPPUNIT_ASSERT_EQUAL( + String("\xC3\xB6\xC3\xA4\xC3\xBC\x6F\xCE\xA3\xC3\xB8", String::UTF8), + tags["UNICODETAG"][0]); + + tags["UNICODETAG"][0] = String( + "\xCE\xBD\xCE\xB5\xCF\x89\x20\xCE\xBD\xCE\xB1\xCE\xBB\xCF\x85\xCE\xB5", String::UTF8); + tags.erase("UNUSUALTAG"); + f.tag()->setProperties(tags); + CPPUNIT_ASSERT_EQUAL( + String("\xCE\xBD\xCE\xB5\xCF\x89\x20\xCE\xBD\xCE\xB1\xCE\xBB\xCF\x85\xCE\xB5", String::UTF8), + f.tag()->properties()["UNICODETAG"][0]); + CPPUNIT_ASSERT_EQUAL(false, f.tag()->properties().contains("UNUSUALTAG")); + } + + void testAudioProperties() + { + Ogg::Vorbis::File f(TEST_FILE_PATH_C("empty.ogg")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->vorbisVersion()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMaximum()); + CPPUNIT_ASSERT_EQUAL(112000, f.audioProperties()->bitrateNominal()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMinimum()); + } + + void testPageChecksum() + { + ScopedFileCopy copy("empty", ".ogg"); + + { + Vorbis::File f(copy.fileName().c_str()); + f.tag()->setArtist("The Artist"); + f.save(); + + f.seek(0x50); + CPPUNIT_ASSERT_EQUAL((unsigned int)0x3d3bd92d, f.readBlock(4).toUInt(0, true)); + } + { + Vorbis::File f(copy.fileName().c_str()); + f.tag()->setArtist("The Artist 2"); + f.save(); + + f.seek(0x50); + CPPUNIT_ASSERT_EQUAL((unsigned int)0xd985291c, f.readBlock(4).toUInt(0, true)); + } + + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestOGG); diff --git a/Frameworks/TagLib/taglib/tests/test_oggflac.cpp b/Frameworks/TagLib/taglib/tests/test_oggflac.cpp new file mode 100644 index 000000000..1d00d1231 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_oggflac.cpp @@ -0,0 +1,122 @@ +/*************************************************************************** + copyright : (C) 2009 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <tstringlist.h> +#include <tbytevectorlist.h> +#include <oggfile.h> +#include <oggflacfile.h> +#include <oggpageheader.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestOggFLAC : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestOggFLAC); + CPPUNIT_TEST(testFramingBit); + CPPUNIT_TEST(testFuzzedFile); + CPPUNIT_TEST(testSplitPackets); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testFramingBit() + { + ScopedFileCopy copy("empty_flac", ".oga"); + string newname = copy.fileName(); + + { + Ogg::FLAC::File f(newname.c_str()); + f.tag()->setArtist("The Artist"); + f.save(); + } + { + Ogg::FLAC::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("The Artist"), f.tag()->artist()); + + f.seek(0, File::End); + CPPUNIT_ASSERT_EQUAL(9134L, f.tell()); + } + } + + void testFuzzedFile() + { + Ogg::FLAC::File f(TEST_FILE_PATH_C("segfault.oga")); + CPPUNIT_ASSERT(!f.isValid()); + } + + void testSplitPackets() + { + ScopedFileCopy copy("empty_flac", ".oga"); + string newname = copy.fileName(); + + const String text = longText(128 * 1024, true); + + { + Ogg::FLAC::File f(newname.c_str()); + f.tag()->setTitle(text); + f.save(); + } + { + Ogg::FLAC::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(141141L, f.length()); + CPPUNIT_ASSERT_EQUAL(21, f.lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(51U, f.packet(0).size()); + CPPUNIT_ASSERT_EQUAL(131126U, f.packet(1).size()); + CPPUNIT_ASSERT_EQUAL(22U, f.packet(2).size()); + CPPUNIT_ASSERT_EQUAL(8196U, f.packet(3).size()); + CPPUNIT_ASSERT_EQUAL(text, f.tag()->title()); + + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); + + f.tag()->setTitle("ABCDE"); + f.save(); + } + { + Ogg::FLAC::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(9128L, f.length()); + CPPUNIT_ASSERT_EQUAL(5, f.lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(51U, f.packet(0).size()); + CPPUNIT_ASSERT_EQUAL(59U, f.packet(1).size()); + CPPUNIT_ASSERT_EQUAL(22U, f.packet(2).size()); + CPPUNIT_ASSERT_EQUAL(8196U, f.packet(3).size()); + CPPUNIT_ASSERT_EQUAL(String("ABCDE"), f.tag()->title()); + + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestOggFLAC); diff --git a/Frameworks/TagLib/taglib/tests/test_opus.cpp b/Frameworks/TagLib/taglib/tests/test_opus.cpp new file mode 100644 index 000000000..9a49d239a --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_opus.cpp @@ -0,0 +1,137 @@ +/*************************************************************************** + copyright : (C) 2012 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <tbytevectorlist.h> +#include <opusfile.h> +#include <oggpageheader.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestOpus : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestOpus); + CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testReadComments); + CPPUNIT_TEST(testWriteComments); + CPPUNIT_TEST(testSplitPackets); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testAudioProperties() + { + Ogg::Opus::File f(TEST_FILE_PATH_C("correctness_gain_silent_output.opus")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(7, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(7737, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(36, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->inputSampleRate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->opusVersion()); + } + + void testReadComments() + { + Ogg::Opus::File f(TEST_FILE_PATH_C("correctness_gain_silent_output.opus")); + CPPUNIT_ASSERT_EQUAL(StringList("Xiph.Org Opus testvectormaker"), f.tag()->fieldListMap()["ENCODER"]); + CPPUNIT_ASSERT(f.tag()->fieldListMap().contains("TESTDESCRIPTION")); + CPPUNIT_ASSERT(!f.tag()->fieldListMap().contains("ARTIST")); + CPPUNIT_ASSERT_EQUAL(String("libopus 0.9.11-66-g64c2dd7"), f.tag()->vendorID()); + } + + void testWriteComments() + { + ScopedFileCopy copy("correctness_gain_silent_output", ".opus"); + string filename = copy.fileName(); + + { + Ogg::Opus::File f(filename.c_str()); + f.tag()->setArtist("Your Tester"); + f.save(); + } + { + Ogg::Opus::File f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL(StringList("Xiph.Org Opus testvectormaker"), f.tag()->fieldListMap()["ENCODER"]); + CPPUNIT_ASSERT(f.tag()->fieldListMap().contains("TESTDESCRIPTION")); + CPPUNIT_ASSERT_EQUAL(StringList("Your Tester"), f.tag()->fieldListMap()["ARTIST"]); + CPPUNIT_ASSERT_EQUAL(String("libopus 0.9.11-66-g64c2dd7"), f.tag()->vendorID()); + } + } + + void testSplitPackets() + { + ScopedFileCopy copy("correctness_gain_silent_output", ".opus"); + string newname = copy.fileName(); + + const String text = longText(128 * 1024, true); + + { + Ogg::Opus::File f(newname.c_str()); + f.tag()->setTitle(text); + f.save(); + } + { + Ogg::Opus::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(167534L, f.length()); + CPPUNIT_ASSERT_EQUAL(27, f.lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(19U, f.packet(0).size()); + CPPUNIT_ASSERT_EQUAL(131380U, f.packet(1).size()); + CPPUNIT_ASSERT_EQUAL(5U, f.packet(2).size()); + CPPUNIT_ASSERT_EQUAL(5U, f.packet(3).size()); + CPPUNIT_ASSERT_EQUAL(text, f.tag()->title()); + + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(7737, f.audioProperties()->lengthInMilliseconds()); + + f.tag()->setTitle("ABCDE"); + f.save(); + } + { + Ogg::Opus::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(35521L, f.length()); + CPPUNIT_ASSERT_EQUAL(11, f.lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(19U, f.packet(0).size()); + CPPUNIT_ASSERT_EQUAL(313U, f.packet(1).size()); + CPPUNIT_ASSERT_EQUAL(5U, f.packet(2).size()); + CPPUNIT_ASSERT_EQUAL(5U, f.packet(3).size()); + CPPUNIT_ASSERT_EQUAL(String("ABCDE"), f.tag()->title()); + + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(7737, f.audioProperties()->lengthInMilliseconds()); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestOpus); diff --git a/Frameworks/TagLib/taglib/tests/test_propertymap.cpp b/Frameworks/TagLib/taglib/tests/test_propertymap.cpp new file mode 100644 index 000000000..2125fb57d --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_propertymap.cpp @@ -0,0 +1,114 @@ +/*************************************************************************** + copyright : (C) 2012 by Michael Helmling + email : helmling@mathematik.uni-kl.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tpropertymap.h> +#include <tag.h> +#include <id3v1tag.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace TagLib; + +class TestPropertyMap : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestPropertyMap); + CPPUNIT_TEST(testInvalidKeys); + CPPUNIT_TEST(testGetSet); + CPPUNIT_TEST_SUITE_END(); + +public: + void testInvalidKeys() + { + PropertyMap map1; + CPPUNIT_ASSERT(map1.isEmpty()); + map1[L"\x00c4\x00d6\x00dc"].append("test"); + CPPUNIT_ASSERT_EQUAL(map1.size(), 1u); + + PropertyMap map2; + map2[L"\x00c4\x00d6\x00dc"].append("test"); + CPPUNIT_ASSERT(map1 == map2); + CPPUNIT_ASSERT(map1.contains(map2)); + + map2["ARTIST"] = String("Test Artist"); + CPPUNIT_ASSERT(map1 != map2); + CPPUNIT_ASSERT(map2.contains(map1)); + + map2[L"\x00c4\x00d6\x00dc"].append("test 2"); + CPPUNIT_ASSERT(!map2.contains(map1)); + + } + + void testGetSet() + { + ID3v1::Tag tag; + + tag.setTitle("Test Title"); + tag.setArtist("Test Artist"); + tag.setAlbum("Test Album"); + tag.setYear(2015); + tag.setTrack(10); + + { + PropertyMap prop = tag.properties(); + CPPUNIT_ASSERT_EQUAL(String("Test Title"), prop["TITLE" ].front()); + CPPUNIT_ASSERT_EQUAL(String("Test Artist"), prop["ARTIST" ].front()); + CPPUNIT_ASSERT_EQUAL(String("Test Album"), prop["ALBUM" ].front()); + CPPUNIT_ASSERT_EQUAL(String("2015"), prop["DATE" ].front()); + CPPUNIT_ASSERT_EQUAL(String("10"), prop["TRACKNUMBER"].front()); + + prop["TITLE" ].front() = "Test Title 2"; + prop["ARTIST" ].front() = "Test Artist 2"; + prop["TRACKNUMBER"].front() = "5"; + + tag.setProperties(prop); + } + + CPPUNIT_ASSERT_EQUAL(String("Test Title 2"), tag.title()); + CPPUNIT_ASSERT_EQUAL(String("Test Artist 2"), tag.artist()); + CPPUNIT_ASSERT_EQUAL(5U, tag.track()); + + PropertyMap props = tag.properties(); + CPPUNIT_ASSERT_EQUAL(StringList("Test Artist 2"), props.find("ARTIST")->second); + CPPUNIT_ASSERT(props.find("COMMENT") == props.end()); + props.replace("ARTIST", StringList("Test Artist 3")); + CPPUNIT_ASSERT_EQUAL(StringList("Test Artist 3"), props["ARTIST"]); + + PropertyMap eraseMap; + eraseMap.insert("ARTIST", StringList()); + eraseMap.insert("ALBUM", StringList()); + eraseMap.insert("TITLE", StringList()); + props.erase(eraseMap); + CPPUNIT_ASSERT_EQUAL(String("DATE=2015\nTRACKNUMBER=5\n"), props.toString()); + + tag.setProperties(PropertyMap()); + + CPPUNIT_ASSERT_EQUAL(String(""), tag.title()); + CPPUNIT_ASSERT_EQUAL(String(""), tag.artist()); + CPPUNIT_ASSERT_EQUAL(0U, tag.track()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestPropertyMap); diff --git a/Frameworks/TagLib/taglib/tests/test_riff.cpp b/Frameworks/TagLib/taglib/tests/test_riff.cpp new file mode 100644 index 000000000..f9a20dfb1 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_riff.cpp @@ -0,0 +1,298 @@ +/*************************************************************************** + copyright : (C) 2009 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <tag.h> +#include <tbytevectorlist.h> +#include <rifffile.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class PublicRIFF : public RIFF::File +{ +public: + PublicRIFF(FileName file) : RIFF::File(file, BigEndian) {}; + unsigned int riffSize() { return RIFF::File::riffSize(); }; + unsigned int chunkCount() { return RIFF::File::chunkCount(); }; + unsigned int chunkOffset(unsigned int i) { return RIFF::File::chunkOffset(i); }; + unsigned int chunkPadding(unsigned int i) { return RIFF::File::chunkPadding(i); }; + unsigned int chunkDataSize(unsigned int i) { return RIFF::File::chunkDataSize(i); }; + ByteVector chunkName(unsigned int i) { return RIFF::File::chunkName(i); }; + ByteVector chunkData(unsigned int i) { return RIFF::File::chunkData(i); }; + void setChunkData(unsigned int i, const ByteVector &data) { + RIFF::File::setChunkData(i, data); + } + void setChunkData(const ByteVector &name, const ByteVector &data) { + RIFF::File::setChunkData(name, data); + }; + virtual TagLib::Tag* tag() const { return 0; }; + virtual TagLib::AudioProperties* audioProperties() const { return 0;}; + virtual bool save() { return false; }; + void removeChunk(unsigned int i) { RIFF::File::removeChunk(i); } + void removeChunk(const ByteVector &name) { RIFF::File::removeChunk(name); } +}; + +class TestRIFF : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestRIFF); + CPPUNIT_TEST(testPadding); + CPPUNIT_TEST(testLastChunkAtEvenPosition); + CPPUNIT_TEST(testLastChunkAtEvenPosition2); + CPPUNIT_TEST(testLastChunkAtEvenPosition3); + CPPUNIT_TEST(testChunkOffset); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testPadding() + { + ScopedFileCopy copy("empty", ".aiff"); + string filename = copy.fileName(); + + { + PublicRIFF f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x1728 + 8), f.chunkOffset(2)); + + f.setChunkData("TEST", "foo"); + } + { + PublicRIFF f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), f.chunkData(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(3), f.chunkDataSize(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x1728 + 8), f.chunkOffset(2)); + + f.setChunkData("SSND", "abcd"); + + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(1)); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcd"), f.chunkData(1)); + + f.seek(f.chunkOffset(1)); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcd"), f.readBlock(4)); + + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), f.chunkData(2)); + + f.seek(f.chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), f.readBlock(3)); + } + { + PublicRIFF f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(1)); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcd"), f.chunkData(1)); + + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), f.chunkData(2)); + } + } + + void testLastChunkAtEvenPosition() + { + ScopedFileCopy copy("noise", ".aif"); + string filename = copy.fileName(); + + { + PublicRIFF f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0xff0 + 8), f.chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(311), f.chunkDataSize(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(1), f.chunkPadding(2)); + CPPUNIT_ASSERT_EQUAL(long(4400), f.length()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4399 - 8), f.riffSize()); + f.setChunkData("TEST", "abcd"); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4088), f.chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(311), f.chunkDataSize(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(1), f.chunkPadding(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4408), f.chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4), f.chunkDataSize(3)); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.chunkName(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0), f.chunkPadding(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4412 - 8), f.riffSize()); + } + { + PublicRIFF f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4088), f.chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(311), f.chunkDataSize(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(1), f.chunkPadding(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4408), f.chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4), f.chunkDataSize(3)); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.chunkName(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0), f.chunkPadding(3)); + CPPUNIT_ASSERT_EQUAL(long(4412), f.length()); + } + } + + void testLastChunkAtEvenPosition2() + { + ScopedFileCopy copy("noise_odd", ".aif"); + string filename = copy.fileName(); + + { + PublicRIFF f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0xff0 + 8), f.chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(311), f.chunkDataSize(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0), f.chunkPadding(2)); + CPPUNIT_ASSERT_EQUAL(long(4399), f.length()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4399 - 8), f.riffSize()); + f.setChunkData("TEST", "abcd"); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4088), f.chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(311), f.chunkDataSize(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(1), f.chunkPadding(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4408), f.chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4), f.chunkDataSize(3)); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.chunkName(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0), f.chunkPadding(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4412 - 8), f.riffSize()); + } + { + PublicRIFF f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4088), f.chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(311), f.chunkDataSize(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(1), f.chunkPadding(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4408), f.chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4), f.chunkDataSize(3)); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.chunkName(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0), f.chunkPadding(3)); + CPPUNIT_ASSERT_EQUAL(long(4412), f.length()); + } + } + + void testLastChunkAtEvenPosition3() + { + ScopedFileCopy copy("noise_odd", ".aif"); + string filename = copy.fileName(); + + { + PublicRIFF f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0xff0 + 8), f.chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(311), f.chunkDataSize(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0), f.chunkPadding(2)); + CPPUNIT_ASSERT_EQUAL(long(4399), f.length()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4399 - 8), f.riffSize()); + f.setChunkData("TEST", "abc"); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4088), f.chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(311), f.chunkDataSize(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(1), f.chunkPadding(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4408), f.chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(3), f.chunkDataSize(3)); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.chunkName(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(1), f.chunkPadding(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4412 - 8), f.riffSize()); + } + { + PublicRIFF f(filename.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4088), f.chunkOffset(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(311), f.chunkDataSize(2)); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(1), f.chunkPadding(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(4408), f.chunkOffset(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(3), f.chunkDataSize(3)); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.chunkName(3)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(1), f.chunkPadding(3)); + CPPUNIT_ASSERT_EQUAL(long(4412), f.length()); + } + } + + void testChunkOffset() + { + ScopedFileCopy copy("empty", ".aiff"); + string filename = copy.fileName(); + + PublicRIFF f(filename.c_str()); + + CPPUNIT_ASSERT_EQUAL(5928U, f.riffSize()); + CPPUNIT_ASSERT_EQUAL(5936L, f.length()); + CPPUNIT_ASSERT_EQUAL(ByteVector("COMM"), f.chunkName(0)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x000C + 8), f.chunkOffset(0)); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.chunkName(1)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x0026 + 8), f.chunkOffset(1)); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.chunkName(2)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x1728 + 8), f.chunkOffset(2)); + + const ByteVector data(0x400, ' '); + f.setChunkData("SSND", data); + CPPUNIT_ASSERT_EQUAL(1070U, f.riffSize()); + CPPUNIT_ASSERT_EQUAL(1078L, f.length()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x000C + 8), f.chunkOffset(0)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x0026 + 8), f.chunkOffset(1)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x042E + 8), f.chunkOffset(2)); + + f.seek(f.chunkOffset(0) - 8); + CPPUNIT_ASSERT_EQUAL(ByteVector("COMM"), f.readBlock(4)); + f.seek(f.chunkOffset(1) - 8); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.readBlock(4)); + f.seek(f.chunkOffset(2) - 8); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.readBlock(4)); + + f.setChunkData(0, data); + CPPUNIT_ASSERT_EQUAL(2076U, f.riffSize()); + CPPUNIT_ASSERT_EQUAL(2084L, f.length()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x000C + 8), f.chunkOffset(0)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x0414 + 8), f.chunkOffset(1)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x081C + 8), f.chunkOffset(2)); + + f.seek(f.chunkOffset(0) - 8); + CPPUNIT_ASSERT_EQUAL(ByteVector("COMM"), f.readBlock(4)); + f.seek(f.chunkOffset(1) - 8); + CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f.readBlock(4)); + f.seek(f.chunkOffset(2) - 8); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.readBlock(4)); + + f.removeChunk("SSND"); + CPPUNIT_ASSERT_EQUAL(1044U, f.riffSize()); + CPPUNIT_ASSERT_EQUAL(1052L, f.length()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x000C + 8), f.chunkOffset(0)); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x0414 + 8), f.chunkOffset(1)); + + f.seek(f.chunkOffset(0) - 8); + CPPUNIT_ASSERT_EQUAL(ByteVector("COMM"), f.readBlock(4)); + f.seek(f.chunkOffset(1) - 8); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.readBlock(4)); + + f.removeChunk(0); + CPPUNIT_ASSERT_EQUAL(12U, f.riffSize()); + CPPUNIT_ASSERT_EQUAL(20L, f.length()); + CPPUNIT_ASSERT_EQUAL((unsigned int)(0x000C + 8), f.chunkOffset(0)); + + f.seek(f.chunkOffset(0) - 8); + CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f.readBlock(4)); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestRIFF); + diff --git a/Frameworks/TagLib/taglib/tests/test_s3m.cpp b/Frameworks/TagLib/taglib/tests/test_s3m.cpp new file mode 100644 index 000000000..fd211d220 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_s3m.cpp @@ -0,0 +1,127 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <s3mfile.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +static const String titleBefore("test song name"); +static const String titleAfter("changed title"); + +static const String commentBefore( + "This is an instrument name.\n" + "Module file formats\n" + "abuse instrument names\n" + "as multiline comments.\n" + " "); + +static const String newComment( + "This is an instrument name!\n" + "Module file formats\n" + "abuse instrument names\n" + "as multiline comments.\n" + "-----------------------------------\n" + "This line will be dropped and the previous is truncated."); + +static const String commentAfter( + "This is an instrument name!\n" + "Module file formats\n" + "abuse instrument names\n" + "as multiline comments.\n" + "---------------------------"); + +class TestS3M : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestS3M); + CPPUNIT_TEST(testReadTags); + CPPUNIT_TEST(testWriteTags); + CPPUNIT_TEST_SUITE_END(); + +public: + void testReadTags() + { + testRead(TEST_FILE_PATH_C("test.s3m"), titleBefore, commentBefore); + } + + void testWriteTags() + { + ScopedFileCopy copy("test", ".s3m"); + { + S3M::File file(copy.fileName().c_str()); + CPPUNIT_ASSERT(file.tag() != 0); + file.tag()->setTitle(titleAfter); + file.tag()->setComment(newComment); + file.tag()->setTrackerName("won't be saved"); + CPPUNIT_ASSERT(file.save()); + } + testRead(copy.fileName().c_str(), titleAfter, commentAfter); + CPPUNIT_ASSERT(fileEqual( + copy.fileName(), + TEST_FILE_PATH_C("changed.s3m"))); + } + +private: + void testRead(FileName fileName, const String &title, const String &comment) + { + S3M::File file(fileName); + + CPPUNIT_ASSERT(file.isValid()); + + S3M::Properties *p = file.audioProperties(); + Mod::Tag *t = file.tag(); + + CPPUNIT_ASSERT(0 != p); + CPPUNIT_ASSERT(0 != t); + + CPPUNIT_ASSERT_EQUAL( 0, p->length()); + CPPUNIT_ASSERT_EQUAL( 0, p->bitrate()); + CPPUNIT_ASSERT_EQUAL( 0, p->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, p->channels()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 0, p->lengthInPatterns()); + CPPUNIT_ASSERT_EQUAL(false, p->stereo()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 5, p->sampleCount()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 1, p->patternCount()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 0, p->flags()); + CPPUNIT_ASSERT_EQUAL((unsigned short)4896, p->trackerVersion()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 2, p->fileFormatVersion()); + CPPUNIT_ASSERT_EQUAL((unsigned char) 64, p->globalVolume()); + CPPUNIT_ASSERT_EQUAL((unsigned char) 48, p->masterVolume()); + CPPUNIT_ASSERT_EQUAL((unsigned char)125, p->tempo()); + CPPUNIT_ASSERT_EQUAL((unsigned char) 6, p->bpmSpeed()); + CPPUNIT_ASSERT_EQUAL(title, t->title()); + CPPUNIT_ASSERT_EQUAL(String(), t->artist()); + CPPUNIT_ASSERT_EQUAL(String(), t->album()); + CPPUNIT_ASSERT_EQUAL(comment, t->comment()); + CPPUNIT_ASSERT_EQUAL(String(), t->genre()); + CPPUNIT_ASSERT_EQUAL(0U, t->year()); + CPPUNIT_ASSERT_EQUAL(0U, t->track()); + CPPUNIT_ASSERT_EQUAL(String("ScreamTracker III"), t->trackerName()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestS3M); diff --git a/Frameworks/TagLib/taglib/tests/test_speex.cpp b/Frameworks/TagLib/taglib/tests/test_speex.cpp new file mode 100644 index 000000000..1f5bb754a --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_speex.cpp @@ -0,0 +1,102 @@ +/*************************************************************************** + copyright : (C) 2015 by Tsuda Kageyu + email : tsuda.kageyu@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <speexfile.h> +#include <oggpageheader.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestSpeex : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestSpeex); + CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testSplitPackets); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testAudioProperties() + { + Ogg::Speex::File f(TEST_FILE_PATH_C("empty.spx")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(53, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(-1, f.audioProperties()->bitrateNominal()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + } + + void testSplitPackets() + { + ScopedFileCopy copy("empty", ".spx"); + string newname = copy.fileName(); + + const String text = longText(128 * 1024, true); + + { + Ogg::Speex::File f(newname.c_str()); + f.tag()->setTitle(text); + f.save(); + } + { + Ogg::Speex::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(156330L, f.length()); + CPPUNIT_ASSERT_EQUAL(23, f.lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(80U, f.packet(0).size()); + CPPUNIT_ASSERT_EQUAL(131116U, f.packet(1).size()); + CPPUNIT_ASSERT_EQUAL(93U, f.packet(2).size()); + CPPUNIT_ASSERT_EQUAL(93U, f.packet(3).size()); + CPPUNIT_ASSERT_EQUAL(text, f.tag()->title()); + + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + + f.tag()->setTitle("ABCDE"); + f.save(); + } + { + Ogg::Speex::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(24317L, f.length()); + CPPUNIT_ASSERT_EQUAL(7, f.lastPageHeader()->pageSequenceNumber()); + CPPUNIT_ASSERT_EQUAL(80U, f.packet(0).size()); + CPPUNIT_ASSERT_EQUAL(49U, f.packet(1).size()); + CPPUNIT_ASSERT_EQUAL(93U, f.packet(2).size()); + CPPUNIT_ASSERT_EQUAL(93U, f.packet(3).size()); + CPPUNIT_ASSERT_EQUAL(String("ABCDE"), f.tag()->title()); + + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestSpeex); diff --git a/Frameworks/TagLib/taglib/tests/test_string.cpp b/Frameworks/TagLib/taglib/tests/test_string.cpp new file mode 100644 index 000000000..7b60b8147 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_string.cpp @@ -0,0 +1,371 @@ +/*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <tstring.h> +#include <string.h> +#include <cppunit/extensions/HelperMacros.h> + +using namespace std; +using namespace TagLib; + +class TestString : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestString); + CPPUNIT_TEST(testString); + CPPUNIT_TEST(testRfind); + CPPUNIT_TEST(testUTF16Encode); + CPPUNIT_TEST(testUTF16Decode); + CPPUNIT_TEST(testUTF16DecodeInvalidBOM); + CPPUNIT_TEST(testUTF16DecodeEmptyWithBOM); + CPPUNIT_TEST(testSurrogatePair); + CPPUNIT_TEST(testAppendCharDetach); + CPPUNIT_TEST(testAppendStringDetach); + CPPUNIT_TEST(testToInt); + CPPUNIT_TEST(testFromInt); + CPPUNIT_TEST(testSubstr); + CPPUNIT_TEST(testNewline); + CPPUNIT_TEST(testUpper); + CPPUNIT_TEST(testEncodeNonLatin1); + CPPUNIT_TEST(testEncodeEmpty); + CPPUNIT_TEST(testEncodeNonBMP); + CPPUNIT_TEST(testIterator); + CPPUNIT_TEST(testInvalidUTF8); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testString() + { + String s = "taglib string"; + ByteVector v = "taglib string"; + CPPUNIT_ASSERT(v == s.data(String::Latin1)); + + char str[] = "taglib string"; + CPPUNIT_ASSERT(strcmp(s.toCString(), str) == 0); + CPPUNIT_ASSERT(s == "taglib string"); + CPPUNIT_ASSERT(s != "taglib STRING"); + CPPUNIT_ASSERT(s != "taglib"); + CPPUNIT_ASSERT(s != "taglib string taglib"); + CPPUNIT_ASSERT(s == L"taglib string"); + CPPUNIT_ASSERT(s != L"taglib STRING"); + CPPUNIT_ASSERT(s != L"taglib"); + CPPUNIT_ASSERT(s != L"taglib string taglib"); + + s.clear(); + CPPUNIT_ASSERT(s.isEmpty()); + + String unicode("José Carlos", String::UTF8); + CPPUNIT_ASSERT(strcmp(unicode.toCString(), "Jos\xe9 Carlos") == 0); + + String latin = "Jos\xe9 Carlos"; + CPPUNIT_ASSERT(strcmp(latin.toCString(true), "José Carlos") == 0); + + String c; + c = "1"; + CPPUNIT_ASSERT(c == L"1"); + + c = L'\u4E00'; + CPPUNIT_ASSERT(c == L"\u4E00"); + + String unicode2(unicode.to8Bit(true), String::UTF8); + CPPUNIT_ASSERT(unicode == unicode2); + + String unicode3(L"\u65E5\u672C\u8A9E"); + CPPUNIT_ASSERT(*(unicode3.toCWString() + 1) == L'\u672C'); + + String unicode4(L"\u65e5\u672c\u8a9e", String::UTF16BE); + CPPUNIT_ASSERT(unicode4[1] == L'\u672c'); + + String unicode5(L"\u65e5\u672c\u8a9e", String::UTF16LE); + CPPUNIT_ASSERT(unicode5[1] == L'\u2c67'); + + std::wstring stduni = L"\u65e5\u672c\u8a9e"; + + String unicode6(stduni, String::UTF16BE); + CPPUNIT_ASSERT(unicode6[1] == L'\u672c'); + + String unicode7(stduni, String::UTF16LE); + CPPUNIT_ASSERT(unicode7[1] == L'\u2c67'); + + CPPUNIT_ASSERT(String(" foo ").stripWhiteSpace() == String("foo")); + CPPUNIT_ASSERT(String("foo ").stripWhiteSpace() == String("foo")); + CPPUNIT_ASSERT(String(" foo").stripWhiteSpace() == String("foo")); + CPPUNIT_ASSERT(String("foo").stripWhiteSpace() == String("foo")); + CPPUNIT_ASSERT(String("f o o").stripWhiteSpace() == String("f o o")); + CPPUNIT_ASSERT(String(" f o o ").stripWhiteSpace() == String("f o o")); + + CPPUNIT_ASSERT(memcmp(String("foo").data(String::Latin1).data(), "foo", 3) == 0); + CPPUNIT_ASSERT(memcmp(String("f").data(String::Latin1).data(), "f", 1) == 0); + } + + void testUTF16Encode() + { + String a("foo"); + ByteVector b("\0f\0o\0o", 6); + ByteVector c("f\0o\0o\0", 6); + ByteVector d("\377\376f\0o\0o\0", 8); + CPPUNIT_ASSERT(a.data(String::UTF16BE) != a.data(String::UTF16LE)); + CPPUNIT_ASSERT(b == a.data(String::UTF16BE)); + CPPUNIT_ASSERT(c == a.data(String::UTF16LE)); + CPPUNIT_ASSERT_EQUAL(d, a.data(String::UTF16)); + } + + void testUTF16Decode() + { + String a("foo"); + ByteVector b("\0f\0o\0o", 6); + ByteVector c("f\0o\0o\0", 6); + ByteVector d("\377\376f\0o\0o\0", 8); + CPPUNIT_ASSERT_EQUAL(a, String(b, String::UTF16BE)); + CPPUNIT_ASSERT_EQUAL(a, String(c, String::UTF16LE)); + CPPUNIT_ASSERT_EQUAL(a, String(d, String::UTF16)); + } + + // this test is expected to print "TagLib: String::prepare() - + // Invalid UTF16 string." on the console 3 times + void testUTF16DecodeInvalidBOM() + { + ByteVector b(" ", 1); + ByteVector c(" ", 2); + ByteVector d(" \0f\0o\0o", 8); + CPPUNIT_ASSERT_EQUAL(String(), String(b, String::UTF16)); + CPPUNIT_ASSERT_EQUAL(String(), String(c, String::UTF16)); + CPPUNIT_ASSERT_EQUAL(String(), String(d, String::UTF16)); + } + + void testUTF16DecodeEmptyWithBOM() + { + ByteVector a("\377\376", 2); + ByteVector b("\376\377", 2); + CPPUNIT_ASSERT_EQUAL(String(), String(a, String::UTF16)); + CPPUNIT_ASSERT_EQUAL(String(), String(b, String::UTF16)); + } + + void testSurrogatePair() + { + // Make sure that a surrogate pair is converted into single UTF-8 char + // and vice versa. + + const ByteVector v1("\xff\xfe\x42\xd8\xb7\xdf\xce\x91\x4b\x5c"); + const ByteVector v2("\xf0\xa0\xae\xb7\xe9\x87\x8e\xe5\xb1\x8b"); + + const String s1(v1, String::UTF16); + CPPUNIT_ASSERT_EQUAL(s1.data(String::UTF8), v2); + + const String s2(v2, String::UTF8); + CPPUNIT_ASSERT_EQUAL(s2.data(String::UTF16), v1); + + const ByteVector v3("\xfe\xff\xd8\x01\x30\x42"); + CPPUNIT_ASSERT(String(v3, String::UTF16).data(String::UTF8).isEmpty()); + + const ByteVector v4("\xfe\xff\x30\x42\xdc\x01"); + CPPUNIT_ASSERT(String(v4, String::UTF16).data(String::UTF8).isEmpty()); + + const ByteVector v5("\xfe\xff\xdc\x01\xd8\x01"); + CPPUNIT_ASSERT(String(v5, String::UTF16).data(String::UTF8).isEmpty()); + } + + void testAppendStringDetach() + { + String a("a"); + String b = a; + a += "b"; + CPPUNIT_ASSERT_EQUAL(String("ab"), a); + CPPUNIT_ASSERT_EQUAL(String("a"), b); + } + + void testAppendCharDetach() + { + String a("a"); + String b = a; + a += 'b'; + CPPUNIT_ASSERT_EQUAL(String("ab"), a); + CPPUNIT_ASSERT_EQUAL(String("a"), b); + } + + void testRfind() + { + CPPUNIT_ASSERT_EQUAL(-1, String("foo.bar").rfind(".", 0)); + CPPUNIT_ASSERT_EQUAL(-1, String("foo.bar").rfind(".", 1)); + CPPUNIT_ASSERT_EQUAL(-1, String("foo.bar").rfind(".", 2)); + CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind(".", 3)); + CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind(".", 4)); + CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind(".", 5)); + CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind(".", 6)); + CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind(".", 7)); + CPPUNIT_ASSERT_EQUAL(3, String("foo.bar").rfind(".")); + } + + void testToInt() + { + bool ok; + CPPUNIT_ASSERT_EQUAL(String("123").toInt(&ok), 123); + CPPUNIT_ASSERT_EQUAL(ok, true); + + CPPUNIT_ASSERT_EQUAL(String("-123").toInt(&ok), -123); + CPPUNIT_ASSERT_EQUAL(ok, true); + + CPPUNIT_ASSERT_EQUAL(String("abc").toInt(&ok), 0); + CPPUNIT_ASSERT_EQUAL(ok, false); + + CPPUNIT_ASSERT_EQUAL(String("1x").toInt(&ok), 1); + CPPUNIT_ASSERT_EQUAL(ok, false); + + CPPUNIT_ASSERT_EQUAL(String("").toInt(&ok), 0); + CPPUNIT_ASSERT_EQUAL(ok, false); + + CPPUNIT_ASSERT_EQUAL(String("-").toInt(&ok), 0); + CPPUNIT_ASSERT_EQUAL(ok, false); + + CPPUNIT_ASSERT_EQUAL(String("123").toInt(), 123); + CPPUNIT_ASSERT_EQUAL(String("-123").toInt(), -123); + CPPUNIT_ASSERT_EQUAL(String("123aa").toInt(), 123); + CPPUNIT_ASSERT_EQUAL(String("-123aa").toInt(), -123); + + CPPUNIT_ASSERT_EQUAL(String("0000").toInt(), 0); + CPPUNIT_ASSERT_EQUAL(String("0001").toInt(), 1); + + String("2147483648").toInt(&ok); + CPPUNIT_ASSERT_EQUAL(ok, false); + + String("-2147483649").toInt(&ok); + CPPUNIT_ASSERT_EQUAL(ok, false); + } + + void testFromInt() + { + CPPUNIT_ASSERT_EQUAL(String::number(0), String("0")); + CPPUNIT_ASSERT_EQUAL(String::number(12345678), String("12345678")); + CPPUNIT_ASSERT_EQUAL(String::number(-12345678), String("-12345678")); + } + + void testSubstr() + { + CPPUNIT_ASSERT_EQUAL(String("01"), String("0123456").substr(0, 2)); + CPPUNIT_ASSERT_EQUAL(String("12"), String("0123456").substr(1, 2)); + CPPUNIT_ASSERT_EQUAL(String("123456"), String("0123456").substr(1, 200)); + CPPUNIT_ASSERT_EQUAL(String("0123456"), String("0123456").substr(0, 7)); + CPPUNIT_ASSERT_EQUAL(String("0123456"), String("0123456").substr(0, 200)); + } + + void testNewline() + { + ByteVector cr("abc\x0dxyz", 7); + ByteVector lf("abc\x0axyz", 7); + ByteVector crlf("abc\x0d\x0axyz", 8); + + CPPUNIT_ASSERT_EQUAL((unsigned int)7, String(cr).size()); + CPPUNIT_ASSERT_EQUAL((unsigned int)7, String(lf).size()); + CPPUNIT_ASSERT_EQUAL((unsigned int)8, String(crlf).size()); + + CPPUNIT_ASSERT_EQUAL(L'\x0d', String(cr)[3]); + CPPUNIT_ASSERT_EQUAL(L'\x0a', String(lf)[3]); + CPPUNIT_ASSERT_EQUAL(L'\x0d', String(crlf)[3]); + CPPUNIT_ASSERT_EQUAL(L'\x0a', String(crlf)[4]); + } + + void testUpper() + { + String s1 = "tagLIB 012 strING"; + String s2 = s1.upper(); + CPPUNIT_ASSERT_EQUAL(String("tagLIB 012 strING"), s1); + CPPUNIT_ASSERT_EQUAL(String("TAGLIB 012 STRING"), s2); + } + + void testEncodeNonLatin1() + { + const String jpn(L"\u65E5\u672C\u8A9E"); + CPPUNIT_ASSERT_EQUAL(ByteVector("\xE5\x2C\x9E"), jpn.data(String::Latin1)); + CPPUNIT_ASSERT_EQUAL(ByteVector("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E"), jpn.data(String::UTF8)); + CPPUNIT_ASSERT_EQUAL(ByteVector("\xFF\xFE\xE5\x65\x2C\x67\x9E\x8A"), jpn.data(String::UTF16)); + CPPUNIT_ASSERT_EQUAL(ByteVector("\xE5\x65\x2C\x67\x9E\x8A"), jpn.data(String::UTF16LE)); + CPPUNIT_ASSERT_EQUAL(ByteVector("\x65\xE5\x67\x2C\x8A\x9E"), jpn.data(String::UTF16BE)); + CPPUNIT_ASSERT_EQUAL(std::string("\xE5\x2C\x9E"), jpn.to8Bit(false)); + CPPUNIT_ASSERT_EQUAL(std::string("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E"), jpn.to8Bit(true)); + } + + void testEncodeEmpty() + { + const String empty; + CPPUNIT_ASSERT(empty.data(String::Latin1).isEmpty()); + CPPUNIT_ASSERT(empty.data(String::UTF8).isEmpty()); + CPPUNIT_ASSERT_EQUAL(ByteVector("\xFF\xFE"), empty.data(String::UTF16)); + CPPUNIT_ASSERT(empty.data(String::UTF16LE).isEmpty()); + CPPUNIT_ASSERT(empty.data(String::UTF16BE).isEmpty()); + CPPUNIT_ASSERT(empty.to8Bit(false).empty()); + CPPUNIT_ASSERT(empty.to8Bit(true).empty()); + } + + void testEncodeNonBMP() + { + const ByteVector a("\xFF\xFE\x3C\xD8\x50\xDD\x40\xD8\xF5\xDC\x3C\xD8\x00\xDE", 14); + const ByteVector b("\xF0\x9F\x85\x90\xF0\xA0\x83\xB5\xF0\x9F\x88\x80"); + CPPUNIT_ASSERT_EQUAL(b, String(a, String::UTF16).data(String::UTF8)); + } + + void testIterator() + { + String s1 = "taglib string"; + String s2 = s1; + + String::Iterator it1 = s1.begin(); + String::Iterator it2 = s2.begin(); + + CPPUNIT_ASSERT_EQUAL(L't', *it1); + CPPUNIT_ASSERT_EQUAL(L't', *it2); + + std::advance(it1, 4); + std::advance(it2, 4); + *it2 = L'I'; + CPPUNIT_ASSERT_EQUAL(L'i', *it1); + CPPUNIT_ASSERT_EQUAL(L'I', *it2); + } + + void testInvalidUTF8() + { + CPPUNIT_ASSERT_EQUAL(String("/"), String(ByteVector("\x2F"), String::UTF8)); + CPPUNIT_ASSERT(String(ByteVector("\xC0\xAF"), String::UTF8).isEmpty()); + CPPUNIT_ASSERT(String(ByteVector("\xE0\x80\xAF"), String::UTF8).isEmpty()); + CPPUNIT_ASSERT(String(ByteVector("\xF0\x80\x80\xAF"), String::UTF8).isEmpty()); + + CPPUNIT_ASSERT(String(ByteVector("\xF8\x80\x80\x80\x80"), String::UTF8).isEmpty()); + CPPUNIT_ASSERT(String(ByteVector("\xFC\x80\x80\x80\x80\x80"), String::UTF8).isEmpty()); + + CPPUNIT_ASSERT(String(ByteVector("\xC2"), String::UTF8).isEmpty()); + CPPUNIT_ASSERT(String(ByteVector("\xE0\x80"), String::UTF8).isEmpty()); + CPPUNIT_ASSERT(String(ByteVector("\xF0\x80\x80"), String::UTF8).isEmpty()); + CPPUNIT_ASSERT(String(ByteVector("\xF8\x80\x80\x80"), String::UTF8).isEmpty()); + CPPUNIT_ASSERT(String(ByteVector("\xFC\x80\x80\x80\x80"), String::UTF8).isEmpty()); + + CPPUNIT_ASSERT(String('\x80', String::UTF8).isEmpty()); + + CPPUNIT_ASSERT(String(ByteVector("\xED\xA0\x80\xED\xB0\x80"), String::UTF8).isEmpty()); + CPPUNIT_ASSERT(String(ByteVector("\xED\xB0\x80\xED\xA0\x80"), String::UTF8).isEmpty()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestString); + diff --git a/Frameworks/TagLib/taglib/tests/test_synchdata.cpp b/Frameworks/TagLib/taglib/tests/test_synchdata.cpp new file mode 100644 index 000000000..08d650794 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_synchdata.cpp @@ -0,0 +1,127 @@ +/*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include <id3v2synchdata.h> +#include <cppunit/extensions/HelperMacros.h> + +using namespace std; +using namespace TagLib; + +class TestID3v2SynchData : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestID3v2SynchData); + CPPUNIT_TEST(test1); + CPPUNIT_TEST(test2); + CPPUNIT_TEST(test3); + CPPUNIT_TEST(testToUIntBroken); + CPPUNIT_TEST(testToUIntBrokenAndTooLarge); + CPPUNIT_TEST(testDecode1); + CPPUNIT_TEST(testDecode2); + CPPUNIT_TEST(testDecode3); + CPPUNIT_TEST(testDecode4); + CPPUNIT_TEST_SUITE_END(); + +public: + + void test1() + { + char data[] = { 0, 0, 0, 127 }; + ByteVector v(data, 4); + + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchData::toUInt(v), (unsigned int)127); + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchData::fromUInt(127), v); + } + + void test2() + { + char data[] = { 0, 0, 1, 0 }; + ByteVector v(data, 4); + + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchData::toUInt(v), (unsigned int)128); + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchData::fromUInt(128), v); + } + + void test3() + { + char data[] = { 0, 0, 1, 1 }; + ByteVector v(data, 4); + + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchData::toUInt(v), (unsigned int)129); + CPPUNIT_ASSERT_EQUAL(ID3v2::SynchData::fromUInt(129), v); + } + + void testToUIntBroken() + { + char data[] = { 0, 0, 0, (char)-1 }; + char data2[] = { 0, 0, (char)-1, (char)-1 }; + + CPPUNIT_ASSERT_EQUAL((unsigned int)255, ID3v2::SynchData::toUInt(ByteVector(data, 4))); + CPPUNIT_ASSERT_EQUAL((unsigned int)65535, ID3v2::SynchData::toUInt(ByteVector(data2, 4))); + } + + void testToUIntBrokenAndTooLarge() + { + char data[] = { 0, 0, 0, (char)-1, 0 }; + ByteVector v(data, 5); + + CPPUNIT_ASSERT_EQUAL((unsigned int)255, ID3v2::SynchData::toUInt(v)); + } + + void testDecode1() + { + ByteVector a("\xff\x00\x00", 3); + a = ID3v2::SynchData::decode(a); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, a.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("\xff\x00", 2), a); + } + + void testDecode2() + { + ByteVector a("\xff\x44", 2); + a = ID3v2::SynchData::decode(a); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, a.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("\xff\x44", 2), a); + } + + void testDecode3() + { + ByteVector a("\xff\xff\x00", 3); + a = ID3v2::SynchData::decode(a); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, a.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("\xff\xff", 2), a); + } + + void testDecode4() + { + ByteVector a("\xff\xff\xff", 3); + a = ID3v2::SynchData::decode(a); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, a.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("\xff\xff\xff", 3), a); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2SynchData); diff --git a/Frameworks/TagLib/taglib/tests/test_trueaudio.cpp b/Frameworks/TagLib/taglib/tests/test_trueaudio.cpp new file mode 100644 index 000000000..d8e6fbca3 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_trueaudio.cpp @@ -0,0 +1,135 @@ +/*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <id3v1tag.h> +#include <id3v2tag.h> +#include <tpropertymap.h> +#include <trueaudiofile.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestTrueAudio : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestTrueAudio); + CPPUNIT_TEST(testReadPropertiesWithoutID3v2); + CPPUNIT_TEST(testReadPropertiesWithTags); + CPPUNIT_TEST(testStripAndProperties); + CPPUNIT_TEST(testRepeatedSave); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testReadPropertiesWithoutID3v2() + { + TrueAudio::File f(TEST_FILE_PATH_C("empty.tta")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(173, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(162496U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->ttaVersion()); + } + + void testReadPropertiesWithTags() + { + TrueAudio::File f(TEST_FILE_PATH_C("tagged.tta")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3685, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(173, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(162496U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->ttaVersion()); + } + + void testStripAndProperties() + { + ScopedFileCopy copy("empty", ".tta"); + + { + TrueAudio::File f(copy.fileName().c_str()); + f.ID3v2Tag(true)->setTitle("ID3v2"); + f.ID3v1Tag(true)->setTitle("ID3v1"); + f.save(); + } + { + TrueAudio::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasID3v1Tag()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String("ID3v2"), f.properties()["TITLE"].front()); + f.strip(TrueAudio::File::ID3v2); + CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front()); + f.strip(TrueAudio::File::ID3v1); + CPPUNIT_ASSERT(f.properties().isEmpty()); + f.save(); + } + { + TrueAudio::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.properties()["TITLE"].isEmpty()); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + + void testRepeatedSave() + { + ScopedFileCopy copy("empty", ".tta"); + + { + TrueAudio::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + + f.ID3v2Tag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.save(); + + f.ID3v2Tag()->setTitle("0"); + f.save(); + + f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.ID3v2Tag()->setTitle("01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789"); + f.save(); + } + { + TrueAudio::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.hasID3v1Tag()); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestTrueAudio); diff --git a/Frameworks/TagLib/taglib/tests/test_wav.cpp b/Frameworks/TagLib/taglib/tests/test_wav.cpp new file mode 100644 index 000000000..61081393c --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_wav.cpp @@ -0,0 +1,389 @@ +/*************************************************************************** + copyright : (C) 2010 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <id3v2tag.h> +#include <infotag.h> +#include <tbytevectorlist.h> +#include <tbytevectorstream.h> +#include <tfilestream.h> +#include <tpropertymap.h> +#include <wavfile.h> +#include <cppunit/extensions/HelperMacros.h> +#include "plainfile.h" +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestWAV : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestWAV); + CPPUNIT_TEST(testPCMProperties); + CPPUNIT_TEST(testALAWProperties); + CPPUNIT_TEST(testFloatProperties); + CPPUNIT_TEST(testFloatWithoutFactChunkProperties); + CPPUNIT_TEST(testZeroSizeDataChunk); + CPPUNIT_TEST(testID3v2Tag); + CPPUNIT_TEST(testSaveID3v23); + CPPUNIT_TEST(testInfoTag); + CPPUNIT_TEST(testStripTags); + CPPUNIT_TEST(testDuplicateTags); + CPPUNIT_TEST(testFuzzedFile1); + CPPUNIT_TEST(testFuzzedFile2); + CPPUNIT_TEST(testFileWithGarbageAppended); + CPPUNIT_TEST(testStripAndProperties); + CPPUNIT_TEST(testPCMWithFactChunk); + CPPUNIT_TEST(testWaveFormatExtensible); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testPCMProperties() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("empty.wav")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3675, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(32, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(1000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(3675U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->format()); + } + + void testALAWProperties() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("alaw.wav")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(128, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(8000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(28400U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(6, f.audioProperties()->format()); + } + + void testFloatProperties() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("float64.wav")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(97, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(5645, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(4281U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->format()); + } + + void testFloatWithoutFactChunkProperties() + { + ByteVector wavData = PlainFile(TEST_FILE_PATH_C("float64.wav")).readAll(); + CPPUNIT_ASSERT_EQUAL(ByteVector("fact"), wavData.mid(36, 4)); + // Remove the fact chunk by renaming it to fakt + wavData[38] = 'k'; + ByteVectorStream wavStream(wavData); + RIFF::WAV::File f(&wavStream); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(97, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(5645, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(4281U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->format()); + } + + void testZeroSizeDataChunk() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("zero-size-chunk.wav")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testID3v2Tag() + { + ScopedFileCopy copy("empty", ".wav"); + string filename = copy.fileName(); + + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + + f.ID3v2Tag()->setTitle(L"Title"); + f.ID3v2Tag()->setArtist(L"Artist"); + f.save(); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + } + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String(L"Title"), f.ID3v2Tag()->title()); + CPPUNIT_ASSERT_EQUAL(String(L"Artist"), f.ID3v2Tag()->artist()); + + f.ID3v2Tag()->setTitle(L""); + f.ID3v2Tag()->setArtist(L""); + f.save(); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + } + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String(L""), f.ID3v2Tag()->title()); + CPPUNIT_ASSERT_EQUAL(String(L""), f.ID3v2Tag()->artist()); + } + } + + void testSaveID3v23() + { + ScopedFileCopy copy("empty", ".wav"); + string newname = copy.fileName(); + + String xxx = ByteVector(254, 'X'); + { + RIFF::WAV::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(false, f.hasID3v2Tag()); + + f.tag()->setTitle(xxx); + f.tag()->setArtist("Artist A"); + f.save(RIFF::WAV::File::AllTags, File::StripOthers, ID3v2::v3); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + } + { + RIFF::WAV::File f2(newname.c_str()); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, f2.ID3v2Tag()->header()->majorVersion()); + CPPUNIT_ASSERT_EQUAL(String("Artist A"), f2.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(xxx, f2.tag()->title()); + } + } + + void testInfoTag() + { + ScopedFileCopy copy("empty", ".wav"); + string filename = copy.fileName(); + + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.hasInfoTag()); + + f.InfoTag()->setTitle(L"Title"); + f.InfoTag()->setArtist(L"Artist"); + f.save(); + CPPUNIT_ASSERT(f.hasInfoTag()); + } + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.hasInfoTag()); + CPPUNIT_ASSERT_EQUAL(String(L"Title"), f.InfoTag()->title()); + CPPUNIT_ASSERT_EQUAL(String(L"Artist"), f.InfoTag()->artist()); + + f.InfoTag()->setTitle(L""); + f.InfoTag()->setArtist(L""); + f.save(); + CPPUNIT_ASSERT(!f.hasInfoTag()); + } + + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(!f.hasInfoTag()); + CPPUNIT_ASSERT_EQUAL(String(L""), f.InfoTag()->title()); + CPPUNIT_ASSERT_EQUAL(String(L""), f.InfoTag()->artist()); + } + } + + void testStripTags() + { + ScopedFileCopy copy("empty", ".wav"); + string filename = copy.fileName(); + + { + RIFF::WAV::File f(filename.c_str()); + f.ID3v2Tag()->setTitle("test title"); + f.InfoTag()->setTitle("test title"); + f.save(); + } + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.hasInfoTag()); + f.save(RIFF::WAV::File::ID3v2, File::StripOthers); + } + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT(!f.hasInfoTag()); + f.ID3v2Tag()->setTitle("test title"); + f.InfoTag()->setTitle("test title"); + f.save(); + } + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.hasInfoTag()); + f.save(RIFF::WAV::File::Info, File::StripOthers); + } + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + CPPUNIT_ASSERT(f.hasInfoTag()); + } + } + + void testDuplicateTags() + { + ScopedFileCopy copy("duplicate_tags", ".wav"); + + RIFF::WAV::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(17052L, f.length()); + + // duplicate_tags.wav has duplicate ID3v2/INFO tags. + // title() returns "Title2" if can't skip the second tag. + + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String("Title1"), f.ID3v2Tag()->title()); + + CPPUNIT_ASSERT(f.hasInfoTag()); + CPPUNIT_ASSERT_EQUAL(String("Title1"), f.InfoTag()->title()); + + f.save(); + CPPUNIT_ASSERT_EQUAL(15898L, f.length()); + CPPUNIT_ASSERT_EQUAL(-1L, f.find("Title2")); + } + + void testFuzzedFile1() + { + RIFF::WAV::File f1(TEST_FILE_PATH_C("infloop.wav")); + CPPUNIT_ASSERT(f1.isValid()); + // The file has problems: + // Chunk 'ISTt' has invalid size (larger than the file size). + // Its properties can nevertheless be read. + RIFF::WAV::Properties* properties = f1.audioProperties(); + CPPUNIT_ASSERT_EQUAL(1, properties->channels()); + CPPUNIT_ASSERT_EQUAL(88, properties->bitrate()); + CPPUNIT_ASSERT_EQUAL(8, properties->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(11025, properties->sampleRate()); + CPPUNIT_ASSERT(!f1.hasInfoTag()); + CPPUNIT_ASSERT(!f1.hasID3v2Tag()); + } + + void testFuzzedFile2() + { + RIFF::WAV::File f2(TEST_FILE_PATH_C("segfault.wav")); + CPPUNIT_ASSERT(f2.isValid()); + } + + void testFileWithGarbageAppended() + { + ScopedFileCopy copy("empty", ".wav"); + ByteVector contentsBeforeModification; + { + FileStream stream(copy.fileName().c_str()); + stream.seek(0, IOStream::End); + const char garbage[] = "12345678"; + stream.writeBlock(ByteVector(garbage, sizeof(garbage) - 1)); + stream.seek(0); + contentsBeforeModification = stream.readBlock(stream.length()); + } + { + RIFF::WAV::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.isValid()); + f.ID3v2Tag()->setTitle("ID3v2 Title"); + f.InfoTag()->setTitle("INFO Title"); + CPPUNIT_ASSERT(f.save()); + } + { + RIFF::WAV::File f(copy.fileName().c_str()); + f.strip(); + } + { + FileStream stream(copy.fileName().c_str()); + ByteVector contentsAfterModification = stream.readBlock(stream.length()); + CPPUNIT_ASSERT_EQUAL(contentsBeforeModification, contentsAfterModification); + } + } + + void testStripAndProperties() + { + ScopedFileCopy copy("empty", ".wav"); + + { + RIFF::WAV::File f(copy.fileName().c_str()); + f.ID3v2Tag()->setTitle("ID3v2"); + f.InfoTag()->setTitle("INFO"); + f.save(); + } + { + RIFF::WAV::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("ID3v2"), f.properties()["TITLE"].front()); + f.strip(RIFF::WAV::File::ID3v2); + CPPUNIT_ASSERT_EQUAL(String("INFO"), f.properties()["TITLE"].front()); + f.strip(RIFF::WAV::File::Info); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + + void testPCMWithFactChunk() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("pcm_with_fact_chunk.wav")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3675, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(32, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(1000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(3675U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->format()); + } + + void testWaveFormatExtensible() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("uint8we.wav")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(2937, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(128, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(8000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(23493U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->format()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestWAV); diff --git a/Frameworks/TagLib/taglib/tests/test_wavpack.cpp b/Frameworks/TagLib/taglib/tests/test_wavpack.cpp new file mode 100644 index 000000000..591529fb7 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_wavpack.cpp @@ -0,0 +1,183 @@ +/*************************************************************************** + copyright : (C) 2010 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <apetag.h> +#include <id3v1tag.h> +#include <tbytevectorlist.h> +#include <tpropertymap.h> +#include <wavpackfile.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestWavPack : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestWavPack); + CPPUNIT_TEST(testNoLengthProperties); + CPPUNIT_TEST(testMultiChannelProperties); + CPPUNIT_TEST(testDsdStereoProperties); + CPPUNIT_TEST(testNonStandardRateProperties); + CPPUNIT_TEST(testTaggedProperties); + CPPUNIT_TEST(testFuzzedFile); + CPPUNIT_TEST(testStripAndProperties); + CPPUNIT_TEST(testRepeatedSave); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testNoLengthProperties() + { + WavPack::File f(TEST_FILE_PATH_C("no_length.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(163392U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); + } + + void testMultiChannelProperties() + { + WavPack::File f(TEST_FILE_PATH_C("four_channels.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3833, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(112, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(169031U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); + } + + void testDsdStereoProperties() + { + WavPack::File f(TEST_FILE_PATH_C("dsd_stereo.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(200, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(2096, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(352800, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(70560U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1040, f.audioProperties()->version()); + } + + void testNonStandardRateProperties() + { + WavPack::File f(TEST_FILE_PATH_C("non_standard_rate.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3675, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(1000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(3675U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1040, f.audioProperties()->version()); + } + + void testTaggedProperties() + { + WavPack::File f(TEST_FILE_PATH_C("tagged.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(172, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(156556U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); + } + + void testFuzzedFile() + { + WavPack::File f(TEST_FILE_PATH_C("infloop.wv")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testStripAndProperties() + { + ScopedFileCopy copy("click", ".wv"); + + { + WavPack::File f(copy.fileName().c_str()); + f.APETag(true)->setTitle("APE"); + f.ID3v1Tag(true)->setTitle("ID3v1"); + f.save(); + } + { + WavPack::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("APE"), f.properties()["TITLE"].front()); + f.strip(WavPack::File::APE); + CPPUNIT_ASSERT_EQUAL(String("ID3v1"), f.properties()["TITLE"].front()); + f.strip(WavPack::File::ID3v1); + CPPUNIT_ASSERT(f.properties().isEmpty()); + } + } + + void testRepeatedSave() + { + ScopedFileCopy copy("click", ".wv"); + + { + WavPack::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(!f.hasAPETag()); + CPPUNIT_ASSERT(!f.hasID3v1Tag()); + + f.APETag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.save(); + + f.APETag()->setTitle("0"); + f.save(); + + f.ID3v1Tag(true)->setTitle("01234 56789 ABCDE FGHIJ"); + f.APETag()->setTitle("01234 56789 ABCDE FGHIJ 01234 56789 ABCDE FGHIJ 01234 56789"); + f.save(); + } + { + WavPack::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.hasAPETag()); + CPPUNIT_ASSERT(f.hasID3v1Tag()); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestWavPack); diff --git a/Frameworks/TagLib/taglib/tests/test_xiphcomment.cpp b/Frameworks/TagLib/taglib/tests/test_xiphcomment.cpp new file mode 100644 index 000000000..386a3e67e --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_xiphcomment.cpp @@ -0,0 +1,215 @@ +/*************************************************************************** + copyright : (C) 2009 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <string> +#include <stdio.h> +#include <xiphcomment.h> +#include <vorbisfile.h> +#include <tpropertymap.h> +#include <tdebug.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestXiphComment : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestXiphComment); + CPPUNIT_TEST(testYear); + CPPUNIT_TEST(testSetYear); + CPPUNIT_TEST(testTrack); + CPPUNIT_TEST(testSetTrack); + CPPUNIT_TEST(testInvalidKeys1); + CPPUNIT_TEST(testInvalidKeys2); + CPPUNIT_TEST(testClearComment); + CPPUNIT_TEST(testRemoveFields); + CPPUNIT_TEST(testPicture); + CPPUNIT_TEST(testLowercaseFields); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testYear() + { + Ogg::XiphComment cmt; + CPPUNIT_ASSERT_EQUAL((unsigned int)0, cmt.year()); + cmt.addField("YEAR", "2009"); + CPPUNIT_ASSERT_EQUAL((unsigned int)2009, cmt.year()); + cmt.addField("DATE", "2008"); + CPPUNIT_ASSERT_EQUAL((unsigned int)2008, cmt.year()); + } + + void testSetYear() + { + Ogg::XiphComment cmt; + cmt.addField("YEAR", "2009"); + cmt.addField("DATE", "2008"); + cmt.setYear(1995); + CPPUNIT_ASSERT(cmt.fieldListMap()["YEAR"].isEmpty()); + CPPUNIT_ASSERT_EQUAL(String("1995"), cmt.fieldListMap()["DATE"].front()); + } + + void testTrack() + { + Ogg::XiphComment cmt; + CPPUNIT_ASSERT_EQUAL((unsigned int)0, cmt.track()); + cmt.addField("TRACKNUM", "7"); + CPPUNIT_ASSERT_EQUAL((unsigned int)7, cmt.track()); + cmt.addField("TRACKNUMBER", "8"); + CPPUNIT_ASSERT_EQUAL((unsigned int)8, cmt.track()); + } + + void testSetTrack() + { + Ogg::XiphComment cmt; + cmt.addField("TRACKNUM", "7"); + cmt.addField("TRACKNUMBER", "8"); + cmt.setTrack(3); + CPPUNIT_ASSERT(cmt.fieldListMap()["TRACKNUM"].isEmpty()); + CPPUNIT_ASSERT_EQUAL(String("3"), cmt.fieldListMap()["TRACKNUMBER"].front()); + } + + void testInvalidKeys1() + { + PropertyMap map; + map[""] = String("invalid key: empty string"); + map["A=B"] = String("invalid key: contains '='"); + map["A~B"] = String("invalid key: contains '~'"); + map["A\x7F" "B"] = String("invalid key: contains '\x7F'"); + map[L"A\x3456" "B"] = String("invalid key: Unicode"); + + Ogg::XiphComment cmt; + PropertyMap unsuccessful = cmt.setProperties(map); + CPPUNIT_ASSERT_EQUAL((unsigned int)5, unsuccessful.size()); + CPPUNIT_ASSERT(cmt.properties().isEmpty()); + } + + void testInvalidKeys2() + { + Ogg::XiphComment cmt; + cmt.addField("", "invalid key: empty string"); + cmt.addField("A=B", "invalid key: contains '='"); + cmt.addField("A~B", "invalid key: contains '~'"); + cmt.addField("A\x7F" "B", "invalid key: contains '\x7F'"); + cmt.addField(L"A\x3456" "B", "invalid key: Unicode"); + CPPUNIT_ASSERT_EQUAL(0U, cmt.fieldCount()); + } + + void testClearComment() + { + ScopedFileCopy copy("empty", ".ogg"); + + { + Ogg::Vorbis::File f(copy.fileName().c_str()); + f.tag()->addField("COMMENT", "Comment1"); + f.save(); + } + { + Ogg::Vorbis::File f(copy.fileName().c_str()); + f.tag()->setComment(""); + CPPUNIT_ASSERT_EQUAL(String(""), f.tag()->comment()); + } + } + + void testRemoveFields() + { + Ogg::Vorbis::File f(TEST_FILE_PATH_C("empty.ogg")); + f.tag()->addField("title", "Title1"); + f.tag()->addField("Title", "Title1", false); + f.tag()->addField("titlE", "Title2", false); + f.tag()->addField("TITLE", "Title3", false); + f.tag()->addField("artist", "Artist1"); + f.tag()->addField("ARTIST", "Artist2", false); + CPPUNIT_ASSERT_EQUAL(String("Title1 Title1 Title2 Title3"), f.tag()->title()); + CPPUNIT_ASSERT_EQUAL(String("Artist1 Artist2"), f.tag()->artist()); + + f.tag()->removeFields("title", "Title1"); + CPPUNIT_ASSERT_EQUAL(String("Title2 Title3"), f.tag()->title()); + CPPUNIT_ASSERT_EQUAL(String("Artist1 Artist2"), f.tag()->artist()); + + f.tag()->removeFields("Artist"); + CPPUNIT_ASSERT_EQUAL(String("Title2 Title3"), f.tag()->title()); + CPPUNIT_ASSERT(f.tag()->artist().isEmpty()); + + f.tag()->removeAllFields(); + CPPUNIT_ASSERT(f.tag()->title().isEmpty()); + CPPUNIT_ASSERT(f.tag()->artist().isEmpty()); + CPPUNIT_ASSERT_EQUAL(String("Xiph.Org libVorbis I 20050304"), f.tag()->vendorID()); + } + + void testPicture() + { + ScopedFileCopy copy("empty", ".ogg"); + string newname = copy.fileName(); + + { + Vorbis::File f(newname.c_str()); + FLAC::Picture *newpic = new FLAC::Picture(); + newpic->setType(FLAC::Picture::BackCover); + newpic->setWidth(5); + newpic->setHeight(6); + newpic->setColorDepth(16); + newpic->setNumColors(7); + newpic->setMimeType("image/jpeg"); + newpic->setDescription("new image"); + newpic->setData("JPEG data"); + f.tag()->addPicture(newpic); + f.save(); + } + { + Vorbis::File f(newname.c_str()); + List<FLAC::Picture *> lst = f.tag()->pictureList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size()); + CPPUNIT_ASSERT_EQUAL((int)5, lst[0]->width()); + CPPUNIT_ASSERT_EQUAL((int)6, lst[0]->height()); + CPPUNIT_ASSERT_EQUAL((int)16, lst[0]->colorDepth()); + CPPUNIT_ASSERT_EQUAL((int)7, lst[0]->numColors()); + CPPUNIT_ASSERT_EQUAL(String("image/jpeg"), lst[0]->mimeType()); + CPPUNIT_ASSERT_EQUAL(String("new image"), lst[0]->description()); + CPPUNIT_ASSERT_EQUAL(ByteVector("JPEG data"), lst[0]->data()); + } + } + + void testLowercaseFields() + { + const ScopedFileCopy copy("lowercase-fields", ".ogg"); + { + Vorbis::File f(copy.fileName().c_str()); + List<FLAC::Picture *> lst = f.tag()->pictureList(); + CPPUNIT_ASSERT_EQUAL(String("TEST TITLE"), f.tag()->title()); + CPPUNIT_ASSERT_EQUAL(String("TEST ARTIST"), f.tag()->artist()); + CPPUNIT_ASSERT_EQUAL((unsigned int)1, lst.size()); + f.save(); + } + { + Vorbis::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.find("METADATA_BLOCK_PICTURE") > 0); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestXiphComment); diff --git a/Frameworks/TagLib/taglib/tests/test_xm.cpp b/Frameworks/TagLib/taglib/tests/test_xm.cpp new file mode 100644 index 000000000..fcda4f568 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/test_xm.cpp @@ -0,0 +1,220 @@ +/*************************************************************************** + copyright : (C) 2011 by Mathias Panzenböck + email : grosser.meister.morti@gmx.net + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include <xmfile.h> +#include <cppunit/extensions/HelperMacros.h> +#include "utils.h" + +using namespace std; +using namespace TagLib; + +static const String titleBefore("title of song"); +static const String titleAfter("changed title"); + +static const String trackerNameBefore("MilkyTracker "); +static const String trackerNameAfter("TagLib"); + +static const String commentBefore( + "Instrument names\n" + "are abused as\n" + "comments in\n" + "module file formats.\n" + "-+-+-+-+-+-+-+-+-+-+-+\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n" + "Sample\n" + "names\n" + "are sometimes\n" + "also abused as\n" + "comments."); + +static const String newCommentShort( + "Instrument names\n" + "are abused as\n" + "comments in\n" + "module file formats.\n" + "======================\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n" + "Sample names\n" + "are sometimes\n" + "also abused as\n" + "comments."); + +static const String newCommentLong( + "Instrument names\n" + "are abused as\n" + "comments in\n" + "module file formats.\n" + "======================\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n" + "Sample names\n" + "are sometimes\n" + "also abused as\n" + "comments.\n" + "\n\n\n\n\n\n\n" + "TEST"); + +static const String commentAfter( + "Instrument names\n" + "are abused as\n" + "comments in\n" + "module file formats.\n" + "======================\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + "\n\n\n" + "Sample names\n" + "are sometimes\n" + "also abused as\n" + "comments.\n"); + +class TestXM : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestXM); + CPPUNIT_TEST(testReadTags); + CPPUNIT_TEST(testReadStrippedTags); + CPPUNIT_TEST(testWriteTagsShort); + CPPUNIT_TEST(testWriteTagsLong); + CPPUNIT_TEST_SUITE_END(); + +public: + void testReadTags() + { + testRead(TEST_FILE_PATH_C("test.xm"), titleBefore, + commentBefore, trackerNameBefore); + } + + void testReadStrippedTags() + { + XM::File file(TEST_FILE_PATH_C("stripped.xm")); + CPPUNIT_ASSERT(file.isValid()); + + XM::Properties *p = file.audioProperties(); + Mod::Tag *t = file.tag(); + + CPPUNIT_ASSERT(0 != p); + CPPUNIT_ASSERT(0 != t); + + CPPUNIT_ASSERT_EQUAL(0, p->length()); + CPPUNIT_ASSERT_EQUAL(0, p->bitrate()); + CPPUNIT_ASSERT_EQUAL(0, p->sampleRate()); + CPPUNIT_ASSERT_EQUAL(8, p->channels()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 1, p->lengthInPatterns()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 0, p->version()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 0 , p->restartPosition()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 1, p->patternCount()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 0, p->instrumentCount()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 1, p->flags()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 6, p->tempo()); + CPPUNIT_ASSERT_EQUAL((unsigned short)125, p->bpmSpeed()); + CPPUNIT_ASSERT_EQUAL(titleBefore, t->title()); + CPPUNIT_ASSERT_EQUAL(String(), t->artist()); + CPPUNIT_ASSERT_EQUAL(String(), t->album()); + CPPUNIT_ASSERT_EQUAL(String(), t->comment()); + CPPUNIT_ASSERT_EQUAL(String(), t->genre()); + CPPUNIT_ASSERT_EQUAL(0U, t->year()); + CPPUNIT_ASSERT_EQUAL(0U, t->track()); + CPPUNIT_ASSERT_EQUAL(String(), t->trackerName()); + } + + void testWriteTagsShort() + { + testWriteTags(newCommentShort); + } + + void testWriteTagsLong() + { + testWriteTags(newCommentLong); + } + +private: + void testRead(FileName fileName, const String &title, + const String &comment, const String &trackerName) + { + XM::File file(fileName); + + CPPUNIT_ASSERT(file.isValid()); + + XM::Properties *p = file.audioProperties(); + Mod::Tag *t = file.tag(); + + CPPUNIT_ASSERT(0 != p); + CPPUNIT_ASSERT(0 != t); + + CPPUNIT_ASSERT_EQUAL(0, p->length()); + CPPUNIT_ASSERT_EQUAL(0, p->bitrate()); + CPPUNIT_ASSERT_EQUAL(0, p->sampleRate()); + CPPUNIT_ASSERT_EQUAL(8, p->channels()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 1, p->lengthInPatterns()); + CPPUNIT_ASSERT_EQUAL((unsigned short)260, p->version()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 0, p->restartPosition()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 1, p->patternCount()); + CPPUNIT_ASSERT_EQUAL((unsigned short)128, p->instrumentCount()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 1, p->flags()); + CPPUNIT_ASSERT_EQUAL((unsigned short) 6, p->tempo()); + CPPUNIT_ASSERT_EQUAL((unsigned short)125, p->bpmSpeed()); + CPPUNIT_ASSERT_EQUAL(title, t->title()); + CPPUNIT_ASSERT_EQUAL(String(), t->artist()); + CPPUNIT_ASSERT_EQUAL(String(), t->album()); + CPPUNIT_ASSERT_EQUAL(comment, t->comment()); + CPPUNIT_ASSERT_EQUAL(String(), t->genre()); + CPPUNIT_ASSERT_EQUAL(0U, t->year()); + CPPUNIT_ASSERT_EQUAL(0U, t->track()); + CPPUNIT_ASSERT_EQUAL(trackerName, t->trackerName()); + } + + void testWriteTags(const String &comment) + { + ScopedFileCopy copy("test", ".xm"); + { + XM::File file(copy.fileName().c_str()); + CPPUNIT_ASSERT(file.tag() != 0); + file.tag()->setTitle(titleAfter); + file.tag()->setComment(comment); + file.tag()->setTrackerName(trackerNameAfter); + CPPUNIT_ASSERT(file.save()); + } + testRead(copy.fileName().c_str(), titleAfter, + commentAfter, trackerNameAfter); + CPPUNIT_ASSERT(fileEqual( + copy.fileName(), + TEST_FILE_PATH_C("changed.xm"))); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestXM); diff --git a/Frameworks/TagLib/taglib/tests/utils.h b/Frameworks/TagLib/taglib/tests/utils.h new file mode 100644 index 000000000..51d8862b5 --- /dev/null +++ b/Frameworks/TagLib/taglib/tests/utils.h @@ -0,0 +1,150 @@ +/*************************************************************************** + copyright : (C) 2007 by Lukas Lalinsky + email : lukas@oxygene.sk + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef _WIN32 +#include <windows.h> +#else +#include <unistd.h> +#include <fcntl.h> +#include <sys/fcntl.h> +#include <sys/stat.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <string> +#include <fstream> + +using namespace std; + +inline string testFilePath(const string &filename) +{ + return string(TESTS_DIR "data/") + filename; +} + +#define TEST_FILE_PATH_C(f) testFilePath(f).c_str() + +inline string copyFile(const string &filename, const string &ext) +{ + char testFileName[1024]; + +#ifdef _WIN32 + char tempDir[MAX_PATH + 1]; + GetTempPathA(sizeof(tempDir), tempDir); + wsprintfA(testFileName, "%s\\taglib-test%s", tempDir, ext.c_str()); +#else + snprintf(testFileName, sizeof(testFileName), "/%s/taglib-test%s", P_tmpdir, ext.c_str()); +#endif + + string sourceFileName = testFilePath(filename) + ext; + ifstream source(sourceFileName.c_str(), std::ios::binary); + ofstream destination(testFileName, std::ios::binary); + destination << source.rdbuf(); + return string(testFileName); +} + +inline void deleteFile(const string &filename) +{ + remove(filename.c_str()); +} + +inline bool fileEqual(const string &filename1, const string &filename2) +{ + char buf1[BUFSIZ]; + char buf2[BUFSIZ]; + + ifstream stream1(filename1.c_str(), ios_base::in | ios_base::binary); + ifstream stream2(filename2.c_str(), ios_base::in | ios_base::binary); + + if(!stream1 && !stream2) return true; + if(!stream1 || !stream2) return false; + + for(;;) + { + stream1.read(buf1, BUFSIZ); + stream2.read(buf2, BUFSIZ); + + streamsize n1 = stream1.gcount(); + streamsize n2 = stream2.gcount(); + + if(n1 != n2) return false; + + if(n1 == 0) break; + + if(memcmp(buf1, buf2, static_cast<size_t>(n1)) != 0) return false; + } + + return stream1.good() == stream2.good(); +} + +#ifdef TAGLIB_STRING_H + +namespace TagLib { + + inline String longText(size_t length, bool random = false) + { + const wchar_t chars[] = L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"; + + std::wstring text(length, L'X'); + + if(random) { + for(size_t i = 0; i < length; ++i) + text[i] = chars[rand() % 53]; + } + + return String(text); + } +} + +#endif + +class ScopedFileCopy +{ +public: + ScopedFileCopy(const string &filename, const string &ext, bool deleteFile=true) : + m_deleteFile(deleteFile), + m_filename(copyFile(filename, ext)) + { + } + + ~ScopedFileCopy() + { + if(m_deleteFile) + deleteFile(m_filename); + } + + string fileName() const + { + return m_filename; + } + +private: + const bool m_deleteFile; + const string m_filename; +};