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 000000000..2791cc887 Binary files /dev/null and b/Frameworks/TagLib/taglib/doc/taglib.png differ 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 000000000..ab2e0997a Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/005411.id3 differ diff --git a/Frameworks/TagLib/taglib/tests/data/64bit.mp4 b/Frameworks/TagLib/taglib/tests/data/64bit.mp4 new file mode 100644 index 000000000..0bd7f9f33 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/64bit.mp4 differ diff --git a/Frameworks/TagLib/taglib/tests/data/alaw.aifc b/Frameworks/TagLib/taglib/tests/data/alaw.aifc new file mode 100644 index 000000000..33b4ea2a5 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/alaw.aifc differ diff --git a/Frameworks/TagLib/taglib/tests/data/alaw.wav b/Frameworks/TagLib/taglib/tests/data/alaw.wav new file mode 100644 index 000000000..cf548effc Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/alaw.wav differ 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 000000000..a761d6ca6 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/ape-id3v1.mp3 differ 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 000000000..72c1291d1 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/ape-id3v2.mp3 differ diff --git a/Frameworks/TagLib/taglib/tests/data/ape.mp3 b/Frameworks/TagLib/taglib/tests/data/ape.mp3 new file mode 100644 index 000000000..a17e26993 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/ape.mp3 differ diff --git a/Frameworks/TagLib/taglib/tests/data/bladeenc.mp3 b/Frameworks/TagLib/taglib/tests/data/bladeenc.mp3 new file mode 100644 index 000000000..e3d1a4b51 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/bladeenc.mp3 differ 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 000000000..4bb15ded3 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/blank_video.m4v differ 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 000000000..809040506 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/broken-tenc.id3 differ diff --git a/Frameworks/TagLib/taglib/tests/data/changed.mod b/Frameworks/TagLib/taglib/tests/data/changed.mod new file mode 100644 index 000000000..13dcea8bc Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/changed.mod differ diff --git a/Frameworks/TagLib/taglib/tests/data/changed.s3m b/Frameworks/TagLib/taglib/tests/data/changed.s3m new file mode 100644 index 000000000..37bd49cdd Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/changed.s3m differ diff --git a/Frameworks/TagLib/taglib/tests/data/changed.xm b/Frameworks/TagLib/taglib/tests/data/changed.xm new file mode 100644 index 000000000..bb5db3ddd Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/changed.xm differ diff --git a/Frameworks/TagLib/taglib/tests/data/click.mpc b/Frameworks/TagLib/taglib/tests/data/click.mpc new file mode 100644 index 000000000..a41f14e9e Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/click.mpc differ diff --git a/Frameworks/TagLib/taglib/tests/data/click.wv b/Frameworks/TagLib/taglib/tests/data/click.wv new file mode 100644 index 000000000..f8bd1a851 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/click.wv differ 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 000000000..824d036fa Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/compressed_id3_frame.mp3 differ 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 000000000..00972c42f Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/correctness_gain_silent_output.opus differ 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 000000000..ac80cb29d Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/covr-junk.m4a differ 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 000000000..80619270f Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/dsd_stereo.wv differ 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 000000000..6703583f2 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/duplicate_id3v2.aiff differ 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 000000000..34f4f158c Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/duplicate_id3v2.mp3 differ 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 000000000..b9865bbd5 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/duplicate_tags.wav differ 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 000000000..20dd90d91 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/empty-seektable.flac differ diff --git a/Frameworks/TagLib/taglib/tests/data/empty.aiff b/Frameworks/TagLib/taglib/tests/data/empty.aiff new file mode 100644 index 000000000..849b762da Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/empty.aiff differ diff --git a/Frameworks/TagLib/taglib/tests/data/empty.ogg b/Frameworks/TagLib/taglib/tests/data/empty.ogg new file mode 100644 index 000000000..aa533104d Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/empty.ogg differ diff --git a/Frameworks/TagLib/taglib/tests/data/empty.spx b/Frameworks/TagLib/taglib/tests/data/empty.spx new file mode 100644 index 000000000..70572b458 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/empty.spx differ diff --git a/Frameworks/TagLib/taglib/tests/data/empty.tta b/Frameworks/TagLib/taglib/tests/data/empty.tta new file mode 100644 index 000000000..9cc00ba81 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/empty.tta differ diff --git a/Frameworks/TagLib/taglib/tests/data/empty.wav b/Frameworks/TagLib/taglib/tests/data/empty.wav new file mode 100644 index 000000000..74b5a6de7 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/empty.wav differ 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 000000000..8c6783218 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/empty_alac.m4a differ 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 000000000..444587fd0 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/empty_flac.oga differ 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 000000000..aa533104d Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/empty_vorbis.oga differ 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 000000000..9cb3a6e10 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/excessive_alloc.aif differ 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 000000000..cd8aa2aba Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/excessive_alloc.mp3 differ diff --git a/Frameworks/TagLib/taglib/tests/data/float64.wav b/Frameworks/TagLib/taglib/tests/data/float64.wav new file mode 100644 index 000000000..d34f692bf Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/float64.wav differ 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 000000000..de682f242 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/four_channels.wv differ diff --git a/Frameworks/TagLib/taglib/tests/data/garbage.mp3 b/Frameworks/TagLib/taglib/tests/data/garbage.mp3 new file mode 100644 index 000000000..730b74e7c Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/garbage.mp3 differ diff --git a/Frameworks/TagLib/taglib/tests/data/gnre.m4a b/Frameworks/TagLib/taglib/tests/data/gnre.m4a new file mode 100644 index 000000000..f925ea9eb Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/gnre.m4a differ 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 000000000..f48a28b52 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/has-tags.m4a differ 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 000000000..b0545ea6f Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/id3v22-tda.mp3 differ 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 000000000..c56c80498 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/ilst-is-last.m4a differ diff --git a/Frameworks/TagLib/taglib/tests/data/infloop.m4a b/Frameworks/TagLib/taglib/tests/data/infloop.m4a new file mode 100644 index 000000000..bbf76db8f Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/infloop.m4a differ diff --git a/Frameworks/TagLib/taglib/tests/data/infloop.mpc b/Frameworks/TagLib/taglib/tests/data/infloop.mpc new file mode 100644 index 000000000..46861ab37 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/infloop.mpc differ diff --git a/Frameworks/TagLib/taglib/tests/data/infloop.wav b/Frameworks/TagLib/taglib/tests/data/infloop.wav new file mode 100644 index 000000000..c220baa8f Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/infloop.wav differ diff --git a/Frameworks/TagLib/taglib/tests/data/infloop.wv b/Frameworks/TagLib/taglib/tests/data/infloop.wv new file mode 100644 index 000000000..d8c720cfe Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/infloop.wv differ 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 000000000..c076712c0 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/invalid-frames1.mp3 differ 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 000000000..01976fc54 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/invalid-frames2.mp3 differ 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 000000000..6bbd2d397 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/invalid-frames3.mp3 differ 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 000000000..b7badeb05 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/lame_cbr.mp3 differ 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 000000000..643056ef8 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/lame_vbr.mp3 differ diff --git a/Frameworks/TagLib/taglib/tests/data/longloop.ape b/Frameworks/TagLib/taglib/tests/data/longloop.ape new file mode 100644 index 000000000..3800387ac Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/longloop.ape differ diff --git a/Frameworks/TagLib/taglib/tests/data/lossless.wma b/Frameworks/TagLib/taglib/tests/data/lossless.wma new file mode 100644 index 000000000..e29befcc8 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/lossless.wma differ 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 000000000..0ddd49357 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/lowercase-fields.ogg differ 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 000000000..c703e2e2f Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/mac-390-hdr.ape differ 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 000000000..fa7ae4149 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/mac-396.ape differ 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 000000000..2ea97fc45 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/mac-399-id3v2.ape differ 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 000000000..3f5a656eb Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/mac-399-tagged.ape differ 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 000000000..3b0661ee1 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/mac-399.ape differ diff --git a/Frameworks/TagLib/taglib/tests/data/mpeg2.mp3 b/Frameworks/TagLib/taglib/tests/data/mpeg2.mp3 new file mode 100644 index 000000000..13e8d53df Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/mpeg2.mp3 differ 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 000000000..93d9a8a14 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/multiple-vc.flac differ diff --git a/Frameworks/TagLib/taglib/tests/data/no-extension b/Frameworks/TagLib/taglib/tests/data/no-extension new file mode 100644 index 000000000..65f57c2ee Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/no-extension differ 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 000000000..d31a6ce96 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/no-tags.3g2 differ 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 000000000..417144167 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/no-tags.flac differ 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 000000000..ba4e92bae Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/no-tags.m4a differ 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 000000000..c06d1071d Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/no_length.wv differ diff --git a/Frameworks/TagLib/taglib/tests/data/noise.aif b/Frameworks/TagLib/taglib/tests/data/noise.aif new file mode 100644 index 000000000..310b995e3 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/noise.aif differ 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 000000000..bccfd7283 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/noise_odd.aif differ 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 000000000..ccc90277e Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/non_standard_rate.wv differ 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 000000000..a6dc1d6c5 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/pcm_with_fact_chunk.wav differ 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 000000000..e485337f9 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/rare_frames.mp3 differ diff --git a/Frameworks/TagLib/taglib/tests/data/segfault.aif b/Frameworks/TagLib/taglib/tests/data/segfault.aif new file mode 100644 index 000000000..5dce192b0 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/segfault.aif differ diff --git a/Frameworks/TagLib/taglib/tests/data/segfault.mpc b/Frameworks/TagLib/taglib/tests/data/segfault.mpc new file mode 100644 index 000000000..2c7e29fb7 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/segfault.mpc differ diff --git a/Frameworks/TagLib/taglib/tests/data/segfault.oga b/Frameworks/TagLib/taglib/tests/data/segfault.oga new file mode 100644 index 000000000..e23c21706 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/segfault.oga differ diff --git a/Frameworks/TagLib/taglib/tests/data/segfault.wav b/Frameworks/TagLib/taglib/tests/data/segfault.wav new file mode 100644 index 000000000..0385e99be Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/segfault.wav differ 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 000000000..e06f91766 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/silence-1.wma differ 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 000000000..24e15deb8 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/silence-44-s.flac differ diff --git a/Frameworks/TagLib/taglib/tests/data/sinewave.flac b/Frameworks/TagLib/taglib/tests/data/sinewave.flac new file mode 100644 index 000000000..25d31b2d7 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/sinewave.flac differ diff --git a/Frameworks/TagLib/taglib/tests/data/stripped.xm b/Frameworks/TagLib/taglib/tests/data/stripped.xm new file mode 100644 index 000000000..57055f5f1 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/stripped.xm differ 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 000000000..214f7ac4e Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/sv4_header.mpc differ 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 000000000..6d17e65f0 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/sv5_header.mpc differ 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 000000000..3405545a2 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/sv8_header.mpc differ diff --git a/Frameworks/TagLib/taglib/tests/data/tagged.tta b/Frameworks/TagLib/taglib/tests/data/tagged.tta new file mode 100644 index 000000000..1677a7edf Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/tagged.tta differ diff --git a/Frameworks/TagLib/taglib/tests/data/tagged.wv b/Frameworks/TagLib/taglib/tests/data/tagged.wv new file mode 100644 index 000000000..333f86871 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/tagged.wv differ diff --git a/Frameworks/TagLib/taglib/tests/data/test.it b/Frameworks/TagLib/taglib/tests/data/test.it new file mode 100644 index 000000000..379444b91 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/test.it differ diff --git a/Frameworks/TagLib/taglib/tests/data/test.mod b/Frameworks/TagLib/taglib/tests/data/test.mod new file mode 100644 index 000000000..136b61191 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/test.mod differ diff --git a/Frameworks/TagLib/taglib/tests/data/test.ogg b/Frameworks/TagLib/taglib/tests/data/test.ogg new file mode 100644 index 000000000..220f76f0c Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/test.ogg differ diff --git a/Frameworks/TagLib/taglib/tests/data/test.s3m b/Frameworks/TagLib/taglib/tests/data/test.s3m new file mode 100644 index 000000000..668250bb7 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/test.s3m differ diff --git a/Frameworks/TagLib/taglib/tests/data/test.xm b/Frameworks/TagLib/taglib/tests/data/test.xm new file mode 100644 index 000000000..b09d91324 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/test.xm differ 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 000000000..168c47981 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/toc_many_children.mp3 differ diff --git a/Frameworks/TagLib/taglib/tests/data/uint8we.wav b/Frameworks/TagLib/taglib/tests/data/uint8we.wav new file mode 100644 index 000000000..9623db229 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/uint8we.wav differ 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 000000000..65f57c2ee Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/unsupported-extension.xx differ diff --git a/Frameworks/TagLib/taglib/tests/data/unsynch.id3 b/Frameworks/TagLib/taglib/tests/data/unsynch.id3 new file mode 100644 index 000000000..cfe6ee1a6 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/unsynch.id3 differ diff --git a/Frameworks/TagLib/taglib/tests/data/w000.mp3 b/Frameworks/TagLib/taglib/tests/data/w000.mp3 new file mode 100644 index 000000000..f9c226176 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/w000.mp3 differ diff --git a/Frameworks/TagLib/taglib/tests/data/xing.mp3 b/Frameworks/TagLib/taglib/tests/data/xing.mp3 new file mode 100644 index 000000000..0c880151b Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/xing.mp3 differ 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 000000000..578d2ef7a Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/zero-length-mdat.m4a differ 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 000000000..8517e797d Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/zero-size-chunk.wav differ 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 000000000..86ab8bf7b Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/zero-sized-padding.flac differ diff --git a/Frameworks/TagLib/taglib/tests/data/zerodiv.ape b/Frameworks/TagLib/taglib/tests/data/zerodiv.ape new file mode 100644 index 000000000..683bc2ddb Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/zerodiv.ape differ diff --git a/Frameworks/TagLib/taglib/tests/data/zerodiv.mpc b/Frameworks/TagLib/taglib/tests/data/zerodiv.mpc new file mode 100644 index 000000000..d3ea57c75 Binary files /dev/null and b/Frameworks/TagLib/taglib/tests/data/zerodiv.mpc differ 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; +};