From 252f3ca727839b262468c686fad4639ec628585b Mon Sep 17 00:00:00 2001 From: Peter Deltchev Date: Thu, 3 Sep 2015 18:59:46 -0700 Subject: [PATCH] Upgraded getID3 to the newest stable version. --- app/library/getid3/.gitattributes | 22 + app/library/getid3/.gitignore | 167 + app/library/getid3/README.md | 607 ++++ app/library/getid3/changelog.txt | 2807 ++++++++++++++++ app/library/getid3/composer.json | 15 + .../getid3/demos/demo.audioinfo.class.php | 316 ++ app/library/getid3/demos/demo.basic.php | 52 + app/library/getid3/demos/demo.browse.php | 611 ++++ app/library/getid3/demos/demo.cache.dbm.php | 31 + app/library/getid3/demos/demo.cache.mysql.php | 31 + app/library/getid3/demos/demo.joinmp3.php | 103 + app/library/getid3/demos/demo.mimeonly.php | 74 + app/library/getid3/demos/demo.mp3header.php | 2888 +++++++++++++++++ app/library/getid3/demos/demo.mysql.php | 2196 +++++++++++++ app/library/getid3/demos/demo.simple.php | 55 + .../getid3/demos/demo.simple.write.php | 60 + app/library/getid3/demos/demo.write.php | 271 ++ app/library/getid3/demos/demo.zip.php | 101 + app/library/getid3/demos/getid3.css | 171 + .../getid3/demos/getid3.demo.dirscan.php | 258 ++ app/library/getid3/demos/index.php | 19 + app/library/getid3/dependencies.txt | 25 + .../{ => getid3}/extension.cache.dbm.php | 18 +- .../{ => getid3}/extension.cache.mysql.php | 65 +- .../getid3/getid3/extension.cache.sqlite3.php | 265 ++ .../getid3/{ => getid3}/getid3.lib.php | 380 ++- app/library/getid3/{ => getid3}/getid3.php | 511 +-- .../{ => getid3}/module.archive.gzip.php | 23 +- .../{ => getid3}/module.archive.rar.php | 8 +- .../{ => getid3}/module.archive.szip.php | 21 +- .../{ => getid3}/module.archive.tar.php | 23 +- .../{ => getid3}/module.archive.zip.php | 209 +- .../{ => getid3}/module.audio-video.asf.php | 194 +- .../{ => getid3}/module.audio-video.bink.php | 15 +- .../{ => getid3}/module.audio-video.flv.php | 304 +- .../module.audio-video.matroska.php | 527 +-- .../getid3/getid3/module.audio-video.mpeg.php | 606 ++++ .../{ => getid3}/module.audio-video.nsv.php | 26 +- .../module.audio-video.quicktime.php | 762 +++-- .../{ => getid3}/module.audio-video.real.php | 30 +- .../{ => getid3}/module.audio-video.riff.php | 1405 ++++---- .../{ => getid3}/module.audio-video.swf.php | 12 +- .../getid3/getid3/module.audio-video.ts.php | 79 + .../getid3/{ => getid3}/module.audio.aa.php | 26 +- .../getid3/{ => getid3}/module.audio.aac.php | 30 +- .../getid3/{ => getid3}/module.audio.ac3.php | 239 +- .../getid3/getid3/module.audio.amr.php | 97 + .../getid3/{ => getid3}/module.audio.au.php | 18 +- .../getid3/{ => getid3}/module.audio.avr.php | 10 +- .../getid3/{ => getid3}/module.audio.bonk.php | 46 +- .../getid3/getid3/module.audio.dss.php | 93 + .../getid3/{ => getid3}/module.audio.dts.php | 187 +- .../getid3/getid3/module.audio.flac.php | 453 +++ .../getid3/{ => getid3}/module.audio.la.php | 17 +- .../getid3/{ => getid3}/module.audio.lpac.php | 10 +- .../getid3/{ => getid3}/module.audio.midi.php | 26 +- .../getid3/{ => getid3}/module.audio.mod.php | 34 +- .../{ => getid3}/module.audio.monkey.php | 15 +- .../getid3/{ => getid3}/module.audio.mp3.php | 211 +- .../getid3/{ => getid3}/module.audio.mpc.php | 46 +- .../getid3/{ => getid3}/module.audio.ogg.php | 386 ++- .../{ => getid3}/module.audio.optimfrog.php | 64 +- .../getid3/{ => getid3}/module.audio.rkau.php | 11 +- .../{ => getid3}/module.audio.shorten.php | 21 +- .../getid3/{ => getid3}/module.audio.tta.php | 12 +- .../getid3/{ => getid3}/module.audio.voc.php | 54 +- .../getid3/{ => getid3}/module.audio.vqf.php | 26 +- .../{ => getid3}/module.audio.wavpack.php | 34 +- .../{ => getid3}/module.graphic.bmp.php | 30 +- .../{ => getid3}/module.graphic.efax.php | 10 +- .../{ => getid3}/module.graphic.gif.php | 32 +- .../{ => getid3}/module.graphic.jpg.php | 54 +- .../{ => getid3}/module.graphic.pcd.php | 29 +- .../{ => getid3}/module.graphic.png.php | 30 +- .../{ => getid3}/module.graphic.svg.php | 10 +- .../{ => getid3}/module.graphic.tiff.php | 40 +- .../getid3/{ => getid3}/module.misc.cue.php | 28 +- .../getid3/{ => getid3}/module.misc.exe.php | 10 +- .../getid3/{ => getid3}/module.misc.iso.php | 37 +- .../{ => getid3}/module.misc.msoffice.php | 12 +- .../getid3/{ => getid3}/module.misc.par2.php | 6 +- .../getid3/{ => getid3}/module.misc.pdf.php | 6 +- .../getid3/{ => getid3}/module.tag.apetag.php | 134 +- .../getid3/{ => getid3}/module.tag.id3v1.php | 32 +- .../getid3/{ => getid3}/module.tag.id3v2.php | 490 ++- .../{ => getid3}/module.tag.lyrics3.php | 39 +- .../getid3/{ => getid3}/module.tag.xmp.php | 34 +- .../getid3/{ => getid3}/write.apetag.php | 41 +- .../getid3/{ => getid3}/write.id3v1.php | 23 +- .../getid3/{ => getid3}/write.id3v2.php | 96 +- .../getid3/{ => getid3}/write.lyrics3.php | 23 +- .../getid3/{ => getid3}/write.metaflac.php | 21 +- app/library/getid3/{ => getid3}/write.php | 59 +- .../getid3/{ => getid3}/write.real.php | 37 +- .../{ => getid3}/write.vorbiscomment.php | 21 +- app/library/getid3/helperapps/head.exe | Bin 0 -> 24064 bytes app/library/getid3/helperapps/md5sum.exe | Bin 0 -> 28160 bytes app/library/getid3/helperapps/metaflac.exe | Bin 0 -> 118784 bytes .../getid3/helperapps/readme.helperapps.txt | 56 + app/library/getid3/helperapps/shorten.exe | Bin 0 -> 60928 bytes app/library/getid3/helperapps/tail.exe | Bin 0 -> 35328 bytes .../getid3/helperapps/vorbiscomment.exe | Bin 0 -> 192512 bytes app/library/getid3/license.txt | 29 + .../getid3/licenses/licence.gpl-10.txt | 251 ++ .../getid3/licenses/licence.gpl-20.txt | 339 ++ .../getid3/licenses/licence.gpl-30.txt | 674 ++++ .../getid3/licenses/licence.lgpl-30.txt | 165 + .../getid3/licenses/licence.mpl-20.txt | 373 +++ .../getid3/licenses/license.commercial.txt | 27 + .../getid3/module.audio-video.mpeg.php | 299 -- app/library/getid3/module.audio.dss.php | 75 - app/library/getid3/module.audio.flac.php | 480 --- app/library/getid3/readme.txt | 606 ++++ app/library/getid3/structure.txt | 2252 +++++++++++++ app/library/helperapps/metaflac.exe | Bin 151552 -> 0 bytes app/library/helperapps/vorbiscomment.exe | Bin 299810 -> 0 bytes app/models/Entities/Track.php | 6 +- 117 files changed, 21481 insertions(+), 4064 deletions(-) create mode 100755 app/library/getid3/.gitattributes create mode 100755 app/library/getid3/.gitignore create mode 100755 app/library/getid3/README.md create mode 100755 app/library/getid3/changelog.txt create mode 100755 app/library/getid3/composer.json create mode 100755 app/library/getid3/demos/demo.audioinfo.class.php create mode 100755 app/library/getid3/demos/demo.basic.php create mode 100755 app/library/getid3/demos/demo.browse.php create mode 100755 app/library/getid3/demos/demo.cache.dbm.php create mode 100755 app/library/getid3/demos/demo.cache.mysql.php create mode 100755 app/library/getid3/demos/demo.joinmp3.php create mode 100755 app/library/getid3/demos/demo.mimeonly.php create mode 100755 app/library/getid3/demos/demo.mp3header.php create mode 100755 app/library/getid3/demos/demo.mysql.php create mode 100755 app/library/getid3/demos/demo.simple.php create mode 100755 app/library/getid3/demos/demo.simple.write.php create mode 100755 app/library/getid3/demos/demo.write.php create mode 100755 app/library/getid3/demos/demo.zip.php create mode 100755 app/library/getid3/demos/getid3.css create mode 100755 app/library/getid3/demos/getid3.demo.dirscan.php create mode 100755 app/library/getid3/demos/index.php create mode 100755 app/library/getid3/dependencies.txt rename app/library/getid3/{ => getid3}/extension.cache.dbm.php (93%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/extension.cache.mysql.php (60%) mode change 100644 => 100755 create mode 100755 app/library/getid3/getid3/extension.cache.sqlite3.php rename app/library/getid3/{ => getid3}/getid3.lib.php (72%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/getid3.php (84%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.archive.gzip.php (93%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.archive.rar.php (92%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.archive.szip.php (87%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.archive.tar.php (93%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.archive.zip.php (64%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio-video.asf.php (94%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio-video.bink.php (88%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio-video.flv.php (67%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio-video.matroska.php (83%) mode change 100644 => 100755 create mode 100755 app/library/getid3/getid3/module.audio-video.mpeg.php rename app/library/getid3/{ => getid3}/module.audio-video.nsv.php (92%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio-video.quicktime.php (78%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio-video.real.php (97%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio-video.riff.php (71%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio-video.swf.php (94%) mode change 100644 => 100755 create mode 100755 app/library/getid3/getid3/module.audio-video.ts.php rename app/library/getid3/{ => getid3}/module.audio.aa.php (70%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.aac.php (95%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.ac3.php (62%) mode change 100644 => 100755 create mode 100755 app/library/getid3/getid3/module.audio.amr.php rename app/library/getid3/{ => getid3}/module.audio.au.php (92%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.avr.php (97%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.bonk.php (87%) mode change 100644 => 100755 create mode 100755 app/library/getid3/getid3/module.audio.dss.php rename app/library/getid3/{ => getid3}/module.audio.dts.php (53%) mode change 100644 => 100755 create mode 100755 app/library/getid3/getid3/module.audio.flac.php rename app/library/getid3/{ => getid3}/module.audio.la.php (95%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.lpac.php (97%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.midi.php (95%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.mod.php (80%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.monkey.php (96%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.mp3.php (91%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.mpc.php (95%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.ogg.php (63%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.optimfrog.php (89%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.rkau.php (94%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.shorten.php (92%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.tta.php (95%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.voc.php (88%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.vqf.php (90%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.audio.wavpack.php (94%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.graphic.bmp.php (97%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.graphic.efax.php (93%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.graphic.gif.php (89%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.graphic.jpg.php (84%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.graphic.pcd.php (85%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.graphic.png.php (96%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.graphic.svg.php (95%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.graphic.tiff.php (85%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.misc.cue.php (92%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.misc.exe.php (95%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.misc.iso.php (96%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.misc.msoffice.php (85%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.misc.par2.php (91%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.misc.pdf.php (92%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.tag.apetag.php (64%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.tag.id3v1.php (92%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.tag.id3v2.php (85%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.tag.lyrics3.php (88%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/module.tag.xmp.php (96%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/write.apetag.php (84%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/write.id3v1.php (92%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/write.id3v2.php (96%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/write.lyrics3.php (79%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/write.metaflac.php (91%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/write.php (91%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/write.real.php (91%) mode change 100644 => 100755 rename app/library/getid3/{ => getid3}/write.vorbiscomment.php (89%) mode change 100644 => 100755 create mode 100755 app/library/getid3/helperapps/head.exe create mode 100755 app/library/getid3/helperapps/md5sum.exe create mode 100755 app/library/getid3/helperapps/metaflac.exe create mode 100755 app/library/getid3/helperapps/readme.helperapps.txt create mode 100755 app/library/getid3/helperapps/shorten.exe create mode 100755 app/library/getid3/helperapps/tail.exe create mode 100755 app/library/getid3/helperapps/vorbiscomment.exe create mode 100755 app/library/getid3/license.txt create mode 100755 app/library/getid3/licenses/licence.gpl-10.txt create mode 100755 app/library/getid3/licenses/licence.gpl-20.txt create mode 100755 app/library/getid3/licenses/licence.gpl-30.txt create mode 100755 app/library/getid3/licenses/licence.lgpl-30.txt create mode 100755 app/library/getid3/licenses/licence.mpl-20.txt create mode 100755 app/library/getid3/licenses/license.commercial.txt delete mode 100644 app/library/getid3/module.audio-video.mpeg.php delete mode 100644 app/library/getid3/module.audio.dss.php delete mode 100644 app/library/getid3/module.audio.flac.php create mode 100755 app/library/getid3/readme.txt create mode 100755 app/library/getid3/structure.txt delete mode 100644 app/library/helperapps/metaflac.exe delete mode 100644 app/library/helperapps/vorbiscomment.exe diff --git a/app/library/getid3/.gitattributes b/app/library/getid3/.gitattributes new file mode 100755 index 00000000..412eeda7 --- /dev/null +++ b/app/library/getid3/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/app/library/getid3/.gitignore b/app/library/getid3/.gitignore new file mode 100755 index 00000000..ecc47c4f --- /dev/null +++ b/app/library/getid3/.gitignore @@ -0,0 +1,167 @@ +helperapps/sha1sum.exe +helperapps/*.dll + + +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.vspscc +.builds +*.dotCover + +## TODO: If you have NuGet Package Restore enabled, uncomment this +#packages/ + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp + +# ReSharper is a .NET coding add-in +_ReSharper* + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish + +# Others +[Bb]in +[Oo]bj +sql +TestResults +*.Cache +ClientBin +stylecop.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML + + + +############ +## Windows +############ + +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg + +# Mac crap +.DS_Store diff --git a/app/library/getid3/README.md b/app/library/getid3/README.md new file mode 100755 index 00000000..c7e7522a --- /dev/null +++ b/app/library/getid3/README.md @@ -0,0 +1,607 @@ +getID3() by James Heinrich () +=== +**Available at or ** + +getID3() is released under multiple licenses. You may choose from the following licenses, and use getID3 according to the terms of the license most suitable to your project. + +**GNU GPL:** + +* [v3](https://gnu.org/licenses/gpl.html) + +* [v2](https://gnu.org/licenses/old-licenses/gpl-2.0.html) + +* [v1](https://gnu.org/licenses/old-licenses/gpl-1.0.html) + +**GNU LGPL:** + +* [v3](https://gnu.org/licenses/lgpl.html) + +**Mozilla MPL:** + +* [v2](http://www.mozilla.org/MPL/2.0/) + +**getID3 Commercial License:** + +* [gCL](http://getid3.org/#gCL) (payment required) + +* * * +Copies of each of the above licenses are included in the `licenses/` +directory of the getID3 distribution. + +If you want to donate, there is a link on for PayPal donations. + + + +Quick Start +=== + +**Q:** How can I check that getID3() works on my server/files? + +**A:** Unzip getID3() to a directory, then access `/demos/demo.browse.php` + + + +Support +=== + +**Q:** I have a question, or I found a bug. What do I do? + +**A:** The preferred method of support requests and/or bug reports is the forum at + + + +Sourceforge Notification +=== + +It's highly recommended that you sign up for notification from +Sourceforge for when new versions are released. Please visit: + +and click the little "monitor package" icon/link. If you're +previously signed up for the mailing list, be aware that it has +been discontinued, only the automated Sourceforge notification +will be used from now on. + + + +What does getID3() do? +=== + +Reads & parses (to varying degrees): + ++ tags: + * APE (v1 and v2) + * ID3v1 (& ID3v1.1) + * ID3v2 (v2.4, v2.3, v2.2) + * Lyrics3 (v1 & v2) + ++ audio-lossy: + * MP3/MP2/MP1 + * MPC / Musepack + * Ogg (Vorbis, OggFLAC, Speex, Opus) + * AAC / MP4 + * AC3 + * DTS + * RealAudio + * Speex + * DSS + * VQF + ++ audio-lossless: + * AIFF + * AU + * Bonk + * CD-audio (*.cda) + * FLAC + * LA (Lossless Audio) + * LiteWave + * LPAC + * MIDI + * Monkey's Audio + * OptimFROG + * RKAU + * Shorten + * TTA + * VOC + * WAV (RIFF) + * WavPack + ++ audio-video: + * ASF: ASF, Windows Media Audio (WMA), Windows Media Video (WMV) + * AVI (RIFF) + * Flash + * Matroska (MKV) + * MPEG-1 / MPEG-2 + * NSV (Nullsoft Streaming Video) + * Quicktime (including MP4) + * RealVideo + ++ still image: + * BMP + * GIF + * JPEG + * PNG + * TIFF + * SWF (Flash) + * PhotoCD + ++ data: + * ISO-9660 CD-ROM image (directory structure) + * SZIP (limited support) + * ZIP (directory structure) + * TAR + * CUE + + ++ Writes: + * ID3v1 (& ID3v1.1) + * ID3v2 (v2.3 & v2.4) + * VorbisComment on OggVorbis + * VorbisComment on FLAC (not OggFLAC) + * APE v2 + * Lyrics3 (delete only) + + + +Requirements +=== + +* PHP 4.2.0 up to 5.2.x for getID3() 1.7.x (and earlier) +* PHP 5.0.5 (or higher) for getID3() 1.8.x (and up) +* PHP 5.0.5 (or higher) for getID3() 2.0.x (and up) +* at least 4MB memory for PHP. 8MB or more is highly recommended. + 12MB is required with all modules loaded. + + + +Usage +=== + +See /demos/demo.basic.php for a very basic use of getID3() with no +fancy output, just scanning one file. + +See structure.txt for the returned data structure. + +**For an example of a complete directory-browsing, file-scanning implementation of getID3(), please run /demos/demo.browse.php** + +See /demos/demo.mysql.php for a sample recursive scanning code that +scans every file in a given directory, and all sub-directories, stores +the results in a database and allows various analysis / maintenance +operations + +To analyze remote files over HTTP or FTP you need to copy the file +locally first before running getID3(). Your code would look something +like this: + +``` php +analyze($filename); + // Delete temporary file + unlink($localtempfilename); + } + fclose($fp_remote); +} + +``` + + +**See /demos/demo.write.php for how to write tags.** + +What does the returned data structure look like? +=== + +See structure.txt + +It is recommended that you look at the output of +/demos/demo.browse.php scanning the file(s) you're interested in to +confirm what data is actually returned for any particular filetype in +general, and your files in particular, as the actual data returned +may vary considerably depending on what information is available in +the file itself. + + + +Notes +=== + +getID3() 1.x: +--- +If the format parser encounters a critical problem, it will return +something in `$fileinfo['error']`, describing the encountered error. If +a less critical error or notice is generated it will appear in +`$fileinfo['warning']`. Both keys may contain more than one warning or +error. If something is returned in ['error'] then the file was not +correctly parsed and returned data may or may not be correct and/or +complete. If something is returned in `['warning']` (and not `['error']`) +then the data that is returned is OK - usually getID3() is reporting +errors in the file that have been worked around due to known bugs in +other programs. Some warnings may indicate that the data that is +returned is OK but that some data could not be extracted due to +errors in the file. + +getID3() 2.x: +--- +See above except errors are thrown (so you will only get one error). + +Disclaimer +=== + +getID3() has been tested on many systems, on many types of files, +under many operating systems, and is generally believe to be stable +and safe. That being said, there is still the chance there is an +undiscovered and/or unfixed bug that may potentially corrupt your +file, especially within the writing functions. By using getID3() you +agree that it's not my fault if any of your files are corrupted. +In fact, I'm not liable for anything :) + +License +=== + +GNU General Public License - see license.txt + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to: +Free Software Foundation, Inc. +59 Temple Place - Suite 330 +Boston, MA 02111-1307, USA. + +FAQ: +--- +**Q:** Can I use getID3() in my program? Do I need a commercial license? + +**A:** You're generally free to use getID3 however you see fit. The only + case in which you would require a commercial license is if you're + selling your closed-source program that integrates getID3. If you + sell your program including a copy of getID3, that's fine as long + as you include a copy of the sourcecode when you sell it. Or you + can distribute your code without getID3 and say "download it from + getid3.sourceforge.net" + + + +Why is it called "getID3()" if it does so much more than just that? +=== + +v0.1 did in fact just do that. I don't have a copy of code that old, but I +could essentially write it today with a one-line function: + +``` php +function getID3($filename) { return unpack('a3TAG/a30title/a30artist/a30album/a4year/a28comment/c1track/c1genreid', substr(file_get_contents($filename), -128)); } + +``` + + +Future Plans +=== + + +* Better support for MP4 container format +* Scan for appended ID3v2 tag at end of file per ID3v2.4 specs (Section 5.0) +* Support for JPEG-2000 (http://www.morgan-multimedia.com/jpeg2000_overview.htm) +* Support for MOD (mod/stm/s3m/it/xm/mtm/ult/669) +* Support for ACE (thanks Vince) +* Support for Ogg other than Vorbis, Speex and OggFlac (ie. Ogg+Xvid) +* Ability to create Xing/LAME VBR header for VBR MP3s that are missing VBR header +* Ability to "clean" ID3v2 padding (replace invalid padding with valid padding) +* Warn if MP3s change version mid-stream (in full-scan mode) +* check for corrupt/broken mid-file MP3 streams in histogram scan +* Support for lossless-compression formats + (http://www.firstpr.com.au/audiocomp/lossless/#Links) + (http://compression.ca/act-sound.html) + (http://web.inter.nl.net/users/hvdh/lossless/lossless.htm) +* Support for RIFF-INFO chunks + * http://lotto.st-andrews.ac.uk/~njh/tag_interchange.html + (thanks Nick Humfrey ) + * http://abcavi.narod.ru/sof/abcavi/infotags.htm + (thanks Kibi) +* Better support for Bink video +* http://www.hr/josip/DSP/AudioFile2.html +* http://www.pcisys.net/~melanson/codecs/ +* Detect mp3PRO +* Support for PSD +* Support for JPC +* Support for JP2 +* Support for JPX +* Support for JB2 +* Support for IFF +* Support for ICO +* Support for ANI +* Support for EXE (comments, author, etc) (thanks p*quaedackersØplanet*nl) +* Support for DVD-IFO (region, subtitles, aspect ratio, etc) + (thanks p*quaedackersØplanet*nl) +* More complete support for SWF - parsing encapsulated MP3 and/or JPEG content + (thanks n8n8Øyahoo*com) +* Support for a2b +* Optional scan-through-frames for AVI verification + (thanks rockcohenØmassive-interactive*nl) +* Support for TTF (thanks infoØbutterflyx*com) +* Support for DSS (http://www.getid3.org/phpBB3/viewtopic.php?t=171) +* Support for SMAF (http://smaf-yamaha.com/what/demo.html) + http://www.getid3.org/phpBB3/viewtopic.php?t=182 +* Support for AMR (http://www.getid3.org/phpBB3/viewtopic.php?t=195) +* Support for 3gpp (http://www.getid3.org/phpBB3/viewtopic.php?t=195) +* Support for ID4 (http://www.wackysoft.cjb.net grizlyY2KØhotmail*com) +* Parse XML data returned in Ogg comments +* Parse XML data from Quicktime SMIL metafiles (klausrathØmac*com) +* ID3v2 genre string creator function +* More complete parsing of JPG +* Support for all old-style ASF packets +* ASF/WMA/WMV tag writing +* Parse declared T??? ID3v2 text information frames, where appropriate + (thanks Christian Fritz for the idea) +* Recognize encoder: + http://www.guerillasoft.com/EncSpot2/index.html + http://ff123.net/identify.html + http://www.hydrogenaudio.org/?act=ST&f=16&t=9414 + http://www.hydrogenaudio.org/?showtopic=11785 +* Support for other OS/2 bitmap structures: Bitmap Array('BA'), + Color Icon('CI'), Color Pointer('CP'), Icon('IC'), Pointer ('PT') + http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm +* Support for WavPack RAW mode +* ASF/WMA/WMV data packet parsing +* ID3v2FrameFlagsLookupTagAlter() +* ID3v2FrameFlagsLookupFileAlter() +* obey ID3v2 tag alter/preserve/discard rules +* http://www.geocities.com/SiliconValley/Sector/9654/Softdoc/Illyrium/Aolyr.htm +* proper checking for LINK/LNK frame validity in ID3v2 writing +* proper checking for ASPI-TLEN frame validity in ID3v2 writing +* proper checking for COMR frame validity in ID3v2 writing +* http://www.geocities.co.jp/SiliconValley-Oakland/3664/index.html +* decode GEOB ID3v2 structure as encoded by RealJukebox, + decode NCON ID3v2 structure as encoded by MusicMatch + (probably won't happen - the formats are proprietary) + + + +Known Bugs/Issues in getID3() that may be fixed eventually +=== + + +* Cannot determine bitrate for MPEG video with VBR video data + (need documentation) +* Interlace/progressive cannot be determined for MPEG video + (need documentation) +* MIDI playtime is sometimes inaccurate +* AAC-RAW mode files cannot be identified +* WavPack-RAW mode files cannot be identified +* mp4 files report lots of "Unknown QuickTime atom type" + (need documentation) +* Encrypted ASF/WMA/WMV files warn about "unhandled GUID + ASF_Content_Encryption_Object" +* Bitrate split between audio and video cannot be calculated for + NSV, only the total bitrate. (need documentation) +* All Ogg formats (Vorbis, OggFLAC, Speex) are affected by the + problem of large VorbisComments spanning multiple Ogg pages, but + but only OggVorbis files can be processed with vorbiscomment. +* The version of "head" supplied with Mac OS 10.2.8 (maybe other + versions too) does only understands a single option (-n) and + therefore fails. getID3 ignores this and returns wrong md5_data. + + + +Known Bugs/Issues in getID3() that cannot be fixed +--- + + +* 32-bit PHP installations only: + Files larger than 2GB cannot always be parsed fully by getID3() + due to limitations in the 32-bit PHP filesystem functions. + NOTE: Since v1.7.8b3 there is partial support for larger-than- + 2GB files, most of which will parse OK, as long as no critical + data is located beyond the 2GB offset. + Known will-work: + * all file formats on 64-bit PHP + * ZIP (format doesn't support files >2GB) + * FLAC (current encoders don't support files >2GB) + Known will-not-work: + * ID3v1 tags (always located at end-of-file) + * Lyrics3 tags (always located at end-of-file) + * APE tags (always located at end-of-file) + Maybe-will-work: + * Quicktime (will work if needed metadata is before 2GB offset, + that is if the file has been hinted/optimized for streaming) + * RIFF.WAV (should work fine, but gives warnings about not being + able to parse all chunks) + * RIFF.AVI (playtime will probably be wrong, is only based on + "movi" chunk that fits in the first 2GB, should issue error + to show that playtime is incorrect. Other data should be mostly + correct, assuming that data is constant throughout the file) + + + +Known Bugs/Issues in other programs +--- + + +* Windows Media Player (up to v11) and iTunes (up to v10+) do + not correctly handle ID3v2.3 tags with UTF-16BE+BOM + encoding (they assume the data is UTF-16LE+BOM and either + crash (WMP) or output Asian character set (iTunes) +* Winamp (up to v2.80 at least) does not support ID3v2.4 tags, + only ID3v2.3 + see: http://forums.winamp.com/showthread.php?postid=387524 +* Some versions of Helium2 (www.helium2.com) do not write + ID3v2.4-compliant Frame Sizes, even though the tag is marked + as ID3v2.4) (detected by getID3()) +* MP3ext V3.3.17 places a non-compliant padding string at the end + of the ID3v2 header. This is supposedly fixed in v3.4b21 but + only if you manually add a registry key. This fix is not yet + confirmed. (detected by getID3()) +* CDex v1.40 (fixed by v1.50b7) writes non-compliant Ogg comment + strings, supposed to be in the format "NAME=value" but actually + written just "value" (detected by getID3()) +* Oggenc 0.9-rc3 flags the encoded file as ABR whether it's + actually ABR or VBR. +* iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably + other versions are too) writes ID3v2.3 comment tags using a + frame name 'COM ' which is not valid for ID3v2.3+ (it's an + ID3v2.2-style frame name) (detected by getID3()) +* MP2enc does not encode mono CBR MP2 files properly (half speed + sound and double playtime) +* MP2enc does not encode mono VBR MP2 files properly (actually + encoded as stereo) +* tooLAME does not encode mono VBR MP2 files properly (actually + encoded as stereo) +* AACenc encodes files in VBR mode (actually ABR) even if CBR is + specified +* AAC/ADIF - bitrate_mode = cbr for vbr files +* LAME 3.90-3.92 prepends one frame of null data (space for the + LAME/VBR header, but it never gets written) when encoding in CBR + mode with the DLL +* Ahead Nero encodes TwinVQF with a DSIZ value (which is supposed + to be the filesize in bytes) of "0" for TwinVQF v1.0 and "1" for + TwinVQF v2.0 (detected by getID3()) +* Ahead Nero encodes TwinVQF files 1 second shorter than they + should be +* AAC-ADTS files are always actually encoded VBR, even if CBR mode + is specified (the CBR-mode switches on the encoder enable ABR + mode, not CBR as such, but it's not possible to tell the + difference between such ABR files and true VBR) +* STREAMINFO.audio_signature in OggFLAC is always null. "The reason + it's like that is because there is no seeking support in + libOggFLAC yet, so it has no way to go back and write the + computed sum after encoding. Seeking support in Ogg FLAC is the + #1 item for the next release." - Josh Coalson (FLAC developer) + NOTE: getID3() will calculate md5_data in a method similar to + other file formats, but that value cannot be compared to the + md5_data value from FLAC data in a FLAC file format. +* STREAMINFO.audio_signature is not calculated in FLAC v0.3.0 & + v0.4.0 - getID3() will calculate md5_data in a method similar to + other file formats, but that value cannot be compared to the + md5_data value from FLAC v0.5.0+ +* RioPort (various versions including 2.0 and 3.11) tags ID3v2 with + a WCOM frame that has no data portion +* Earlier versions of Coolplayer adds illegal ID3 tags to Ogg Vorbis + files, thus making them corrupt. +* Meracl ID3 Tag Writer v1.3.4 (and older) incorrectly truncates the + last byte of data from an MP3 file when appending a new ID3v1 tag. + (detected by getID3()) +* Lossless-Audio files encoded with and without the -noseek switch + do actually differ internally and therefore cannot match md5_data +* iTunes has been known to append a new ID3v1 tag on the end of an + existing ID3v1 tag when ID3v2 tag is also present + (detected by getID3()) +* MediaMonkey may write a blank RGAD ID3v2 frame but put actual + replay gain adjustments in a series of user-defined TXXX frames + (detected and handled by getID3() since v1.9.2) + + + + +Reference material: +=== + +[www.id3.org](http://www.id3.org) material now mirrored at + +* http://www.id3.org/id3v2.4.0-structure.txt +* http://www.id3.org/id3v2.4.0-frames.txt +* http://www.id3.org/id3v2.4.0-changes.txt +* http://www.id3.org/id3v2.3.0.txt +* http://www.id3.org/id3v2-00.txt +* http://www.id3.org/mp3frame.html +* http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html +* http://www.dv.co.yu/mpgscript/mpeghdr.htm +* http://www.mp3-tech.org/programmer/frame_header.html +* http://users.belgacom.net/gc247244/extra/tag.html +* http://gabriel.mp3-tech.org/mp3infotag.html +* http://www.id3.org/iso4217.html +* http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-1.TXT +* http://www.xiph.org/ogg/vorbis/doc/framing.html +* http://www.xiph.org/ogg/vorbis/doc/v-comment.html +* http://leknor.com/code/php/class.ogg.php.txt +* http://www.id3.org/iso639-2.html +* http://www.id3.org/lyrics3.html +* http://www.id3.org/lyrics3200.html +* http://www.psc.edu/general/software/packages/ieee/ieee.html +* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html +* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html +* http://www.jmcgowan.com/avi.html +* http://www.wotsit.org/ +* http://www.herdsoft.com/ti/davincie/davp3xo2.htm +* http://www.mathdogs.com/vorbis-illuminated/bitstream-appendix.html +* "Standard MIDI File Format" by Dustin Caldwell (from www.wotsit.org) +* http://midistudio.com/Help/GMSpecs_Patches.htm +* http://www.xiph.org/archives/vorbis/200109/0459.html +* http://www.replaygain.org/ +* http://www.lossless-audio.com/ +* http://download.microsoft.com/download/winmediatech40/Doc/1.0/WIN98MeXP/EN-US/ASF_Specification_v.1.0.exe +* http://mediaxw.sourceforge.net/files/doc/Active%20Streaming%20Format%20(ASF)%201.0%20Specification.pdf +* http://www.uni-jena.de/~pfk/mpp/sv8/ (archived at http://www.hydrogenaudio.org/musepack/klemm/www.personal.uni-jena.de/~pfk/mpp/sv8/) +* http://jfaul.de/atl/ +* http://www.uni-jena.de/~pfk/mpp/ (archived at http://www.hydrogenaudio.org/musepack/klemm/www.personal.uni-jena.de/~pfk/mpp/) +* http://www.libpng.org/pub/png/spec/png-1.2-pdg.html +* http://www.real.com/devzone/library/creating/rmsdk/doc/rmff.htm +* http://www.fastgraph.com/help/bmp_os2_header_format.html +* http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm +* http://flac.sourceforge.net/format.html +* http://www.research.att.com/projects/mpegaudio/mpeg2.html +* http://www.audiocoding.com/wiki/index.php?page=AAC +* http://libmpeg.org/mpeg4/doc/w2203tfs.pdf +* http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt +* http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/frameset.htm +* http://www.nullsoft.com/nsv/ +* http://www.wotsit.org/download.asp?f=iso9660 +* http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html +* http://www.cdroller.com/htm/readdata.html +* http://www.speex.org/manual/node10.html +* http://www.harmony-central.com/Computer/Programming/aiff-file-format.doc +* http://www.faqs.org/rfcs/rfc2361.html +* http://ghido.shelter.ro/ +* http://www.ebu.ch/tech_t3285.pdf +* http://www.sr.se/utveckling/tu/bwf +* http://ftp.aessc.org/pub/aes46-2002.pdf +* http://cartchunk.org:8080/ +* http://www.broadcastpapers.com/radio/cartchunk01.htm +* http://www.hr/josip/DSP/AudioFile2.html +* http://home.attbi.com/~chris.bagwell/AudioFormats-11.html +* http://www.pure-mac.com/extkey.html +* http://cesnet.dl.sourceforge.net/sourceforge/bonkenc/bonk-binary-format-0.9.txt +* http://www.headbands.com/gspot/ +* http://www.openswf.org/spec/SWFfileformat.html +* http://j-faul.virtualave.net/ +* http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html +* http://cui.unige.ch/OSG/info/AudioFormats/ap11.html +* http://sswf.sourceforge.net/SWFalexref.html +* http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt +* http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm +* http://developer.apple.com/quicktime/icefloe/dispatch012.html +* http://www.csdn.net/Dev/Format/graphics/PCD.htm +* http://tta.iszf.irk.ru/ +* http://www.atsc.org/standards/a_52a.pdf +* http://www.alanwood.net/unicode/ +* http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html +* http://www.its.msstate.edu/net/real/reports/config/tags.stats +* http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt +* http://brennan.young.net/Comp/LiveStage/things.html +* http://www.multiweb.cz/twoinches/MP3inside.htm +* http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended +* http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ +* http://www.unicode.org/unicode/faq/utf_bom.html +* http://tta.corecodec.org/?menu=format +* http://www.scvi.net/nsvformat.htm +* http://pda.etsi.org/pda/queryform.asp +* http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm +* http://trac.musepack.net/trac/wiki/SV8Specification +* http://wyday.com/cuesharp/specification.php +* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html diff --git a/app/library/getid3/changelog.txt b/app/library/getid3/changelog.txt new file mode 100755 index 00000000..1bd712dd --- /dev/null +++ b/app/library/getid3/changelog.txt @@ -0,0 +1,2807 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// +// // +// changelog.txt - part of getID3() // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + + » denotes a major feature addition/change + ¤ denotes a change in the returned structure + ! denotes a cry for help from developers +* Bugfix: denotes a fixed bug + +Version History +=============== + +1.9.9: [2014-12-18] James Heinrich + » Added basic support for OggOpus + » Add ID3v2 CHAP + CTOC support + * Add composer autoloader + * bugfix: removed non-printable ASCII in comment + * bugfix: possible memory leak in OggFLAC + * bugfix: sys_get_temp_dir undefined before PHP 5.2.1 + * bugfix: improved fix for XXE security issue (CVE-2014-2053) + (thanks nacinØwordpress*org) + * bugfix: G:25 ID3v2 LINK utf8_encode not defined + * bugfix: G:22 ID3v2 TXXX description encoding + * bugfix: #1855 - copy image height/width/etc to comments + * bugfix: #1855 - PHP errors in badly written APE/ID3v2 tags + * bugfix: #1845 - Quicktime parsing with no PHP memory_limit + * bugfix: #1828 - ID3v2 writing unknown frame names + +1.9.8: [2014-05-11] James Heinrich + » Add support for AMR (Adaptive Multi-Rate audio codec) + new file: module.audio.amr.php + » Added composer.json, registered on packagist.org + * Added workaround for PHP Bug #39923 (undefined constant IMG_JPG) + * Bugfix: (#1813) avoid running out of memory when parsing large + Quicktime files + * Bugfix: (#1812) potential unwanted high-ASCII characters in errors + * Bugfix: close potential XXE security issue (CVE-2014-2053) + * Bugfix: (G:10) Avoid warnings from realpath() if SAFE MODE is enabled + * Bugfix: (G:12) If [tags] data contains an array of strings then html + encoding did not take place. + * Bugfix: (G:12) IPTC character set not specified + * Bugfix: possible divide by zero error in FLV module + * Bugfix: possible undefined key in ID3v2 + * Bugfix: possible undefined key in MPEG video files + * Bugfix: demo.browse to use character set consistently + +1.9.7: [2013-07-05] James Heinrich + * Bugfix: [module.audio-video.quicktime.php] track languages set + with 15-bit-encoded ISO639-2 language codes not parsed correctly + * Bugfix: (#1717) QuickTime atom hierarchy broken + * Bugfix: (#1716) truncate MIDI file could cause infinite loop + * Bugfix: all source files converted to UTF-8 + +1.9.6: [2013-06-03] James Heinrich + » getID3() is now licensed under GPL / LGPL / MozillaPL / gCL + See license.txt for more details. + * Bugfix: (#1550) Quicktime video track sample description parsed + incorrectly + * Bugfix: (#1550) Quicktime matrix U/V/W values calculated incorrectly + * [demo.browse] disable edit-tag and delete-file links by default + * Bugfix: option_max_2gb_check should issue warning not error on >2GB + +1.9.5: [2013-02-20] James Heinrich, Dmitry Arkhipov + » DTS-in-WAV now properly supported + ¤ DSS files return additional data in new keys, and some existing + keys have been renamed + * Bugfix: open_basedir not parsed correctly under Windows + (thanks yannick*jamontØgmail*com) + * Bugfix: [demo/demo.browse] might not display file or directory name + on PHP >=5.4.0 if filename not UTF-8 friendly + * Bugfix: [demo/demo.zip] could read more uncompressed data than + required; fail to read file if local data descriptor not set; + some wrong include files were listed; improved error message display + * Bugfix: [module.audio-video.riff] INFO comment chunks with null name + chunk not parsed correctly + * Bugfix: [module.archive.gz] gzip files with filename stored may have + filename reduplicated in [gzip][files] output + * Bugfix: [module.archive.zip] data_descriptor not parsed correctly + * Bugfix: [module.archive.zip] some newer compression methods unknown + * Bugfix: [module.archive.zip] not all flags parsed + * Bugfix: [module.archive.zip] local file header not parsed correctly + if file has zero values for compressed_size in Local File Header + * Bugfix: (#1493) better support for >2GB filesize on 32-bit Linux + * Bugfix: (#1474) unneccesary call to GetDataImageSize in JPEG module + * Bugfix: (#1470) GIF files falsely detected as TS format + * Bugfix: (#1431) Matroska did not parse PixelCrop* / DisplayUnit + (thanks jgerberØwikimedia*org) + * Bugfix: (#1430) split ID3v2 text values on null separator + * Bugfix: (#1426) MS Office 2007 file format now recognized as zip.msoffice + * Bugfix: (#1423) optimized CreateDeepArray function + * Bugfix: (#1415) add support for DS2 variant of DSS + +1.9.4b1: [2012-10-05] James Heinrich, Dmitry Arkhipov, Karl G. Holz + » New module: extension.cache.sqlite3.php (by Karl G. Holz) + » New demo: demos/getid3.demo.dirscan.php (by Karl G. Holz) + » PHP5 standards improvements (thanks phansysØgmail*com) + » more reliable >4GB file size parsing using COM (if available) + Scripting.FileSystemObject rather than parsing `dir` output + * added support for FLAC inside Matroska (audio bitrate cannot + be determined in this case) + * XMP module now returns all tags, not just whitelisted ones + * (#1297) Added detection of MPEG Transport Stream files. + Stub module.audio-video.ts.php incomplete + * (#1383) removed unneeded ?> tags (thanks daveØholyfield*info) + * Bugfix: XMP returns attributes array not just value strings + * Bugfix: (#1369) ID3v2 IPLS contents not parsed + * Bugfix: (#1357) demo.mysql.php mysql_table_exists() failed + * Bugfix: (#1355) copy Foobar2000 QuickTime tags to [comments] + * Bugfix: (#1351) QuickTime files with zero-sized atom boxes + could cause infinite loop + * Bugfix: (#1343) FLAC attached pictures Ogg not handled + * Bugfix: (#1343) ID3v2 inside WAV "id3 " chunk not handled + * Bugfix: (#1315) BMP detection was broken + * Bugfix: (#1309) ID3v2.2 content_group_description (TT2) did + not copy to same place as ID3v2.3/ID3v2.4 (TIT2) + * Bugfix: (#1308) [playtime_string] could show hh:mm:60 + * Bugfix: (#1306) extension.cache.mysql.php keyword TYPE->ENGINE + * Bugfix: (#1295) missing video information if QuickTime file has + disabled tracks + * Bugfix: (#1275) MD5/SHA1 data hashes not working properly + under Windows + + +1.9.3: [2011-12-13] Dmitry Arkhipov, James Heinrich + * Matroska module improved: + 1. Added support for A_MS/ACM audio codec + 2. Fixed issues in tags, cues, chapters and clusters parsing + 3. Fixed almost all errors with track_data_offset, errors + still may occur with Xiph data lacing + 4. Optimized audio/video streams population with usage of the + official default values for missing elements + 5. Audio/video keys are now populated with data from the + default stream, not from the first one as before + 6. Full WebM support + * Bugfix: demo.browse would not pop up warnings when clicked + if warning contains apostrophe/single-quote character + * Bugfix: (#1269) ID3v1 genre typo "Trash"->"Thrash" Metal + + +1.9.2: [2011-12-08] James Heinrich, Dmitry Arkhipov + » significant rewrite to module.audio-video.matroska.php + ¤ (#1256) ID3 tags in AIFF 'ID3 ' chunks now parsed + ¤ (#1039) iXML data in WAV files now returned and parsed into + [riff][WAVE][iXML][0][data] and [riff][WAVE][iXML][0][parsed] + ¤ [playtime_string] now returns M:SS if less than 1 hour, and + H:MM:SS if 1 hour or longer + * Bugfix: (#1266) variable tablename: extension.cache.mysql.php + * Bugfix: (#1265) unescaped # in regex in write.id3v2.php + * Bugfix: (#1252) MediaMonkey writes blank ID3v2 RGAD frames + and puts replay-gain values in TXXX frames + * Bugfix: (#1251) FLV playtime could be inaccurate for longer + files where meta frame is present but meta-playtime is zero + * Bugfix: (#1216) show hex values of unknown atom names + * Bugfix: (#1215) undefined variable in PrintHexBytes() + * Bugfix: FLV audio bitrate was returning kbps not bps + * Bugfix: missing ) in write.real.php::RemoveReal() + * Bugfix: replace $this::VERSION with getID3::VERSION in + extension.cache.*.php + + +1.9.1: [2011-08-10] James Heinrich + ¤ ASF Extended Header Object data now (partially) parsed + * Default getID3 encoding now set to UTF-8 not ISO-8859-1 + * Bugfix: (#1212) truncated Matroska files may result in + infinite loop and memory exhaustion + * Bugfix: (#1203) parse RIFF JUNK chunks for version strings + * Bugfix: (#1201) multi-byte characters strings incorrectly + displayed by table_var_dump() in demo.browse.php + * Bugfix: (#1199) prevent PHP warning on malformed ID3v2 APIC + * Bugfix: (#1196) typo in module.audio-video.quicktime.php + * Bugfix: (#1195) QuicktimeStoreFrontCodeLookup() broken + * Bugfix: (#1194) mp4 embedded images not handled correctly + * Bugfix: (#1193) [image_mime] key not set fo WM/picture data + * Bugfix: (#1193) ASF Extended Header Object Metadata Library + now parsed for embedded images and handled per usual style + * Bugfix: (#1190) demo.mimeonly.php was broken since v1.9.0 + * Bugfix: ID3v2 comment is now called 'comment' not 'comments' + * Bugfix: AVI unknown codec fourcc would be reported as blank + * Bugfix: AVI zero-size JUNK chunk would give warning + + +1.9.0: [2011-06-20] James Heinrich + » changed all module classes to have proper constructors + with the actual analysis code moved to function Analyze() + * removed unnecessary ob_* calls, replaced with appropriate + checks and judicious use of @ error suppression + ¤ GETID3_VERSION constant replaced with $getID3->version() + ¤ picture data is now returned only in the original source + location and [comments][picture], it is no longer replicated + in [comments_html], [tags] or [tags_html] + ¤ Matroska tags are now returned in [comments] as per normal + ¤ Matroska tags are better supported, including pictures + ¤ GPS data in MP4 files (e.g. iPhone) is now parsed (#1157) + ¤ Matroska audio/video tracks with a default flag, the default + stream flag is now copied to [audio|video][streams] (#1147) + ¤ Nikon-specific data (NCDT atom) in Quicktime videos now parsed + ¤ QuickTime atoms 'meta' and 'data' now (mostly) parsed + * Bugfix: remove false warning of junk data on WAV+ID3v1 + * Bugfix: DolbyDigitalWAV files returned wrong audio bitrate + * Bugfix: large attachment data in Matroska tags were not + returned completely. + * Bugfix: wrong image_mime used for images in demo.browse.php + * Bugfix: broken preg_match in module.audio.dss.php + * Bugfix: Lyrics3 end offset was off by 1 + * Bugfix: audio channelmode could be wrong for 2 channels + (e.g. joint stereo reported as stereo) + * Bugfix: MultiByteCharString2HTML() would return empty string + if passed float or int value, now casts to string first + * Bugfix: FLAC.picture was not returning under [data] + + [image_mime] per standardized format + * Bugfix: BigEndian2Int() could incorrectly return negative + signed synchsafe integer instead of casting to float + * Bugfix: (#1177) ID3v2.4 extended headers were broken + * Bugfix: (#1173) some MIDI files not completely parsed + * Bugfix: (#1171) change helperapps error to nonblocking warning + * Bugfix: (#1170) possible infinite loop in FLV module + * Bugfix: (#1169) $this reference in static function (ID3v2) + * Bugfix: (#1156) demo.mysql.php not working + * Bugfix: (#1153) badly-tagged files could produce invalid + argument errors in module.tag.xmp.php + * Bugfix: (#1152) add error-suppression to iconv() calls + * Bugfix: (#1151) AAC-ADTS files could sometimes not find sync + * Bugfix: (#1136) last character of unicode tags (e.g. ASF) + was being truncated + * Bugfix: (#1133) write.id3v2.php IsValidURL() was broken + * Bugfix: (#1126) ID3v2.POPM field was being clobbered + * Bugfix: (#999, #1154) ID3v2 UFID data was missing + + +1.8.5: [2011-02-18] James Heinrich + » support >2GB files on 64-bit PHP + - note current unofficial 64-bit PHP builds for Windows + do not actually support 64-bit integers so are still + subject to normal 32-bit limits (2GB) for file functions + » PHP v5.0.5 now minimum required version. + Removed obsolte functions from getid3.lib.php: + md5_file, sha1_file, image_type_to_mime_type + » IDivX tags now parsed on AVI files + ¤ embedded image data is returned inside [comments][picture] + in a 2-element array (data, image_mime) for all formats + * $this->overwrite_tags=false is now known to be buggy and + has been disabled for this version until a full review + of tag writing can be completed. Certainly affects ID3v2, + the other writable tag formats may or may not be broken + * getID3 constructor no longer checks for (or sets) timezone + * demo.browse.php now shows cover art as inline images + rather than dumped to separate files + * [audio][streams][x][language] now set when known + * Bugfix: RIFF-AVI "JUNK" chunks are now parsed properly, + including zero-sized ones (no more false errors) + * Bugfix: msoffice documents now return correct error message + * Bugfix: demo.browse.php now encodes data according to + current page encoding (default=UTF-8) + * Bugfix: (#1120) sometimes incorrect ID3v2 genre parsing + * Bugfix: (#1116) possibly incorrect warnings (or lack of) + for RIFFs > 2GB. + * Bugfix: (#1115) wrong RIFFtype in RIFF files + * Bugfix: (#1114) wrong MIME type may be set for Matroska + * Bugfix: (#1113) support DSS v3 files + * Bugfix: (#1111) cover art in APE tags now supported + * Bugfix: (#1091) RemoveID3v1() unitialized variables + * Bugfix: (# 504) do not set Quicktime resolution if + 'tkhd' atom is disabled + * Bugfix: (# 95) return [quicktime][controller] if known + + +1.8.4: [2011-02-03] James Heinrich + * change default encoding in ID3v2 writing to UTF16-LE+BOM + (or ISO-8859-1 where possible) for better compatability + with broken versions of Windows Media Player and iTunes + * Bugfix: [FLV] incorrect overall bitrate in some files + * Bugfix: (#1102) missing parentheses in write[.id3v2].php + * Bugfix: (#510) undefined IsValidDottedIP() in write.id3v2.php + + +1.8.3: [2011-01-18] James Heinrich + » magic_quotes_gpc must now be disabled to use getID3 + » replace all error-suppressing @$variable calls with + isset() or empty() as appropriate for some configurations + where @ does not act to suppress warnings of undefined + variables (e.g. support forum thread #798) + * remove SafeStripSlashes() and FixTextFields functions + * [quicktime] use fourcc if codec name zero-length + * [quicktime] support "iods" atom + * Bugfix: (#1099) sometimes incorrect detection of safe_mode + * Bugfix: (#1095) more robust setting of temp dir + * Bugfix: (#1093) add support for ClusterSimpleBlock to + prevent "Undefined index: track_data_offsets" errors + in Matroska files + * Bugfix: [riff] prevent errors when RIFF.WAVE.BEXT chunk + contains null date/time (thanks moysevichØgmail*com) + * Bugfix: [quicktime] prevent divide-by-zero errors if + time_to_sample_table has zero-sample entry + (thanks moysevichØgmail*com) + + +1.8.2: [2010-12-06] James Heinrich + * include startup warning for PHP < v5 + * magic_quotes_runtime must now be disabled to use getID3 + ¤ MusicBrainz / AmpliFIND data more accessible in returned data + from Quicktime-style files (e.g. MP4/AAC) + * Bugfix: (#1079) wrong encoding might be used for ID3v2 + text data, and/or garbage data prepended before text + data; DataLengthIndicator value was being ignored + * Bugfix: (#1055) clearer warnings on non-EXIF contents in + JPEG [APP1] + * Bugfix: (#999) ID3v2 UFID data was missing + + +1.8.1: [2010-11-25] James Heinrich + * replaced calls to deprecated mysql_escape_string() with + mysql_real_escape_string() + * Bugfix: (#1072) memory limit not handled correctly if + in gigabytes in php.ini (e.g. "2G") + * Bugfix: (#1068) wrong encoding for Quicktime tags + * Bugfix: (#1040) possible infinite loop in genre parsing + * Bugfix: (#1036) helperapps directory not resolving 8.3 + path names correctly + * Bugfix: (#1023) dbm cache extension not correctly handling + types other than "db3" + * Bugfix: (#1023) mysql cache extension now base64_encodes + data to make binary-safe. Existing cached data must be + purged from your database cache + * Bugfix: (#1007) ClosestStandardMP3Bitrate() not selecting + most appropriate value + * Bugfix: (#996) inefficient and buggy ID3v1 and ID3v2 + genre parsing + * Bugfix: (#974) track number handled incorrectly in + demo.write.php + * Bugfix: (#969) tempnam() calls failing with open_basedir + * Bugfix: (#955) UTF-16LE text files could be falsely + identified as corrupt mp3 files + * Bugfix: (#877) detect if mbstring.func_overload is set in php.ini + * Bugfix: (#858) PHP safe_mode setting in php.ini incorrectly + handled if set to "Off" + * Bugfix: (#838) prevent warnings with assorted unhandled + Quicktime atoms + + +1.8.0: [2010-11-23] James Heinrich + » Changes required for PHP v5.3+ compatability, including: + - change ereg* functions to preg_* equivalents + - declare functions static as needed + note: users of PHP v4.x may need to stay with getID3 v1.7.x + » Added CUE (cuesheet) support + new file: module.misc.cue.php + (thanks Nigel Barnes ngbarnesØhotmail*com) + » Added XMP (Adobe Extensible Metadata Platform) support + currently used with module.graphic.jpg.php + new file: module.tag.xmp.php + (thanks Nigel Barnes ngbarnesØhotmail*com) + ¤ [jpg][exif][GPS][computed] now exists when possible with + calculated values (decimal latitude, longitude, altitude, time) + ¤ Prevent clobbering WMA artist with albumartist value; added WMA + partofset tag; added WMA tag picture data to WMA comments + (thanks ngbarnesØhotmail*com) + ¤ RIFF.WAVE.SNDM (SoundMiner) metadata now parsed + (thanks emerrittØwbgu*bgsu*edu) + ¤ FLAC embedded pictures now return [data_length] key + (thanks darrenburnhillØhotmail*com) + * added support for a number of new comment atom types added in + iTunes v4.0-v7.0 (thanks ngbarnesØhotmail*com) + * demo.browse.php now shows video resolution and framerate (if no + artist or title info present) + * additional FLV details parsed, may be faster as well + (thanks ngbarnesØhotmail*com) + * Bugfix: DSS files longer than 60 seconds had wrong playtime + * Bugfix: possible empty array encountered in APE tags + (thanks csnaitsirchØweb*de) + * Bugfix: prevent fatal error when calling BigEndian2Int() on + zero-length string (thanks taylor*fausakØgmail*com) + * Bugfix: prevent errors when parsing invalid Vorbis comments + (thanks dr*dieselØgmail*com) + * Bugfix: files could not be analyzed from Windows shares + (e.g. \\SERVER\Directory\Filename.mp3) + * Bugfix: RAR file opening should use 'filenamepath' + (thanks adrien*gibratØgmail*com) + * Bugfix: [asf][codec_list_object][codec_entries][x][description] + not containing expected comma-seperated values no longer aborts + (thanks larry_globusØyahoo*com) + * Bugfix: [id3v2] UFID was not returning data + (thanks joostØdecock*org) + +1.7.9: [2009-03-08] James Heinrich + » Added DSS (Digital Speech Standard) support + new file: module.audio.dss.php + (thanks luke*wilkinsØdtsam*com) + » Added MPC (Musepack) SV8 support + (thanks WaldoMonster) + ¤ some MPC [header] keys renamed to be the same between SV7/SV8 + ¤ start aligning demos CSS styling with v2.x styles + new file: demos/getid3.css + ¤ JPEG now returns parsed IPTC tags in [iptc] + ¤ getid3_lib::GetDataImageSize now requires $imageinfo parameter + ¤ better support for Matroska files with AC3/DTS/MP3/OGG audio + (support still lacking for AAC) + ¤ standardize ID3v2 TCMP key to 'part_of_a_set' between reading + and writing (thanks aaron_stormØyahoo*com) + ¤ added ID3v2 keys 'TCMP','TCP' to for writing iTunes-style tags + (thanks aaron_stormØyahoo*com) + ¤ back-ported PICTURE tag handling in FLAC tags + (thanks WaldoMonster) + ¤ added alternate method to get [video][frame_rate] from QuickTime + * added partial support for "TCMP"/"TCP" ID3v2 frames (iTunes + non-standard part-of-a-compilation tag) + (thanks aaron_stormØyahoo*com) + * slightly improved scanning through FLV files speed + (thanks franki) + * faster Matroska scanning by stopping at cluster chunks once + needed header chunks are found (much faster for large files) + * added workaround for broken tagging programs that miss terminating + null byte for numeric ID3v2.4 genres + (thanks yam655Øgmail*com) + * Bugfix: MultiByteCharString2HTML() did not escape common HTML + special characters like & and ? + * Bugfix: cleaned up some malformed HTML errors in demo.browse.php + * Bugfix: under Windows files >2GB might not be processed due to + "dir" command not finding file with double directory slashes + * Bugfix: "MODule (assorted sub-formats)" was falsely matching + some random files (e.g. JPEGs) (thanks qwertywin) + * Bugfix: suppress PHP_notice on failed SWF-compressed + decompression failure (thanks mkron) + + +1.7.8b3: [2008-07-13] James Heinrich + » Experimental partial support for files > 2GB (gets filesize + from shell call to "dir" or "ls", parse files with PHP only + up to 2GB limit). See readme.txt for details on what formats + work properly and other limitations + » Initial support for Matroska. Has only been tested with a + limited number of sample files, please report any bugs + » Experimental support for PHP-RAR reading. Known buggy, disabled + by default, enable with care + ¤ getid3_lib::CastAsInt() now returns ints up to 2^31 (not 2^30) + ¤ Quicktime: [video] now returns [frame_rate] and [fourcc] for MP4 + video files + * MP3: headerless VBR files now only have up to 10 blocks of 5000 + frames each scanned by default and bitrate extrapolated from that + distribution for speed (thanks glau*stuffØridiculousprods*com) + * Quicktime: support "co64" atom + * SWF: lower memory use when compressed SWF files processed + (thanks doughammondØblueyonder*co*uk) + * Bugfix: FLV height and width was calculated incorrectly + (thanks moysevichØgmail*com) + * Bugfix: FLV GETID3_FLV_TAG_META parsed incorrectly + (thanks moysevichØgmail*com) + * Bugfix: Quicktime: 'tkhd' matrix_v and matrix_d were switched + (thanks rjjmoroØhotmail*com) + * Bugfix: Quicktime: frame_rate was often incorrect for MP4 video + * Bugfix: getid3_lib::CastAsInt returned -2147483648 when passed + 2147483648 (0x80000000) + + +1.7.8b2: [2007-10-15] James Heinrich, Allan Hansen + * Video bitrate now calculated even if not explicitly stated in + file metadata, but if overall and audio bitrates are known + * Bugfix: 'comments_html' missing last letter in id3v2 tags. + * Bugfix: module objects (e.g. getid3_riff) that are instantiated + in other modules are explicitly disposed once no longer needed. + * Bugfix: some AVI files were not returning audio information + because "strh" chunk was not being read in + * Bugfix: asf [audio][][dataformat] should be set + to "wma" but wasn't + * Bugfix: [mpeg][audio][bitrate_mode] should always be one of + ("cbr", "vbr", "abr") but wasn't for some values in + LAMEvbrMethodLookup() + * Bugfix: MP3 audio in AVI files could show "cbr" instead of + correct audio bitrate_mode, and audio bitrate could be slightly + incorrect if multiple files were scanned in a loop (scanning + single files produced correct values). + * Bugfix: remove [audio/video][bitrate] key if falsely set to zero + * Bugfix: PlaytimeString returned non-matching value for negative + playtimes (which shouldn't happen either, but now they're at + least shown correctly, if they happen due to other bugs) + * Bugfix: Several ASF header values are invalid if the broadcast + flag is set, getID3() now calculates these values in other + ways if the broadcast flag is set (thanks fletchØpobox*com) + * Bugfix: lyrics3-flags-lyrics field was always false, and there + never was a lyrics3-flags-timestamp field present even though + the lyrics3-raw-IND field consisted of "10" (lyrics present, + timestamp not present). (thanks i*f*schulzØweb*de) + * Bugfix: TAR.GZ files produce PHP errors when + option_gzip_parse_contents == true in module.archive.gzip.php + (thanks alan*harderØsun*com) + + +1.7.8b1: [2007-01-08] Allan Hansen + » Major update to readme.txt + » PHP 4.2.0 required + » Tagwriter requires metaflac 1.1.1+ in order to write FLAC tags. + » Removed broken and non-fixable tagwriting module for real format. + ! Developers please help fix the above module: + http://www.getid3.org/phpBB3/viewtopic.php?t=677 + » Avoided security issues with demo.browse.php, demo.write.php and + demo.mysql.php. These demos are now disabled by default and has + to be enabled in the source. + * Bugfix: id3v2 genre broken since 1.7.7. + » Added DTS module (module.audio.dts.php) + ¤ ASF/WMV files now return largest video stream dimensions in + [video][resolution_x] and [video][resolution_y] + * Bugfix: Minor issues with midi module (avoid PHP_NOTICE). + * Bugfix: Minor issues with lyrics3 (avoid PHP_NOTICE). + * Bugfix: PHP_NOTICE issues in MultiByteCharString2HTML() + * Bugfix: PHP_NOTICE issue in BigEndian2Float() + * Bugfix: fread() zero bytes issue in real module. + * Bugfix: ASF module returned mime type video/x-ms-wma instead of + video/x-ms-wmv for certain FourCCs. + * Bugfix: PHP_NOTICE issues with broken ID3v2 tag/garbage. + * Bugfix: PNG module broken in regards to gIFg and gIFx chunks. + » Removed detection of short filenames 8dot3 under windows, as + it only worked for English versions of windows and has other + problems. + * Bugfix: Some CBR MP3 files detected as VBR with plenty of warnings. + * Bugfix: PHP_NOTICE issues in MP3 module. + * Bugfix: Quicktime returned incorrect frame rate. + * Bugfix: DivByZero on zero length FLV files. + * Bugfix: PHP_NOTICE one some FLV files. + * Bugfix: ID3v2 UTF-8/16 encoded frames terminated by \x00 + * Bugfix: ID3v2 LINK frames iconv error. + * Bugfix: ID3v2 padding length calculated incorrectly. + * Bugfix: ID3v2.3 extended headers non-conformance + » SVG file detection. + » Added SVG user module (user_modules/module.graphic.svg.php). + Thanks to Roan Horning. + » PAR2 file detection (no parsing) + * Bugfix: Wave files being detected as MP3. + * Bugfix: ASF padding offset bug. + * Bugfix: Shorten module not working for wav files with fmt + chunks <> 16 bytes. + ¤ RIFF: Zero sized chunk invokes warning instead of error. + ¤ FLAC: Removed some ['raw'] keys. + ¤ MPC: Mime type returned: audio/x-musepack + +1.7.7: [2006-06-25] Allan Hansen + * Bugfix: AAC static bitrate cache wrong result when parsing + several files. + * Bugfix: Do not return NULL video bitrate for ASF v3. + * Bugfix: getid3_lib::GetImageSize() broken => JPG module broken. + * Bugfix: Encoder options should now be returned with correct + "--alt-preset n" / "--alt-preset cbr n" when scanning more files. + * Bugfix: Shorten module not escapeshellarg() filenames (UNIX only). + * Bugfix: Filenames not escapeshellarg() for md5_data and + sha1_data (UNIX only). + * Bugfix: UNIX: head and tail called with -cNNN instead of "-c NNN". + » Added detection support for PDF and MS Office documents + (*.doc, *.xls, *.pps, etc) (thanks zeromassmediaØgmail*com) + ¤ Bugfix: ID3v2 "TDRC" frame now used as "year" in comments if TYER + unavailable (TYER is deprecated in ID3v2.4) + (thanks matthiasØpanczyk*org) + ¤ Removed GETID3_OS_DIRSLASH, replaced with DIRECTORY_SEPARATOR + * Bugfix: added LAME preset guessing for presets 410,420,440,490 + (thanks adminØlogbud*com) + * Bugfix: Added escapeshellarg() call in getid3_lib::hash_data + (thanks towbØgmx*net) + » TAR module no longer reads entire file into memory + » FLV module no longer reads entire file into memory + * Bugfix: added LAME preset guessing for presets 410,420,440,490 + (thanks adminØlogbud*com) + * Bugfix: Added escapeshellarg() call in getid3_lib::hash_data + (thanks towbØgmx*net) + * Bugfix: Error message when padding in FLAC files were used up. + * Bugfix: Shorten module not working under windows. + ¤ Bugfix: gmmktime() instead of mktime(). + ¤ Using gmmktime() instead of mktime() in ISO, ZIP, PNG and RIFF + modules to avoid E_STRICT notices with PHP5.1+. + * Bugfix: ['comments_html'] and ['comments'] contains different + value when having multiple tags (one of them ID3v1) and the + long field names. + +1.7.6: [2006-03-12] James Heinrich + * Rewrote getid3_lib::GetDataImageSize() to use GetImageSize() + instead of using code by filØrezox*com + * Bugfix: incorrect dimensions from disabled Quicktime tracks + (thanks m-1Øgmx*net) + * Bugfix: ['codec'] key warning in module.audio-video.asf.php + (thanks niel*archerØblueyonder*co*uk) + * Bugfix: undefined array in write.php + (thanks drewishØkatherinehouse*com) + * Bugfix: DeleteAPEtag() incorrectly failing when no tag present + (thanks drewishØkatherinehouse*com) + * Bugfix: ID3v2 writing frames with URL fields failing when URL + is not in ISO-8859-1 (thanks drewishØkatherinehouse*com) + * Bugfix: PHP notices on bad ID3v2 frames + (thanks cw264701Øohiou*edu) + * Bugfix: audio & video bitrates sometimes wrong in ASF files + (thanks kris_kauperØexcite*com) + +1.7.5: [2005-12-29] James Heinrich + » Added FLV (FLash Video) support -- new file: + module.audio-video.flv.php + (thanks Seth Kaufman for code) + » Real tags can now be written (previous Real tag writing + code was not supposed to be in public releases, as it + was not complete) + » GETID3_HELPERAPPSDIR now autodetected under Windows + ¤ ASF lyrics now returned under [comments][lyrics] + * Bugfix: removed "--lowpass xxxxx" info from guessed + LAME presets when source frequency <= 32kHz + * Bugfix: ID3v2 extended header errors + * Bugfix: missing ob_end_clean() in write.id3v2.php + (thanks rasherØgmail*com) + +1.7.4: [2005-05-04] James Heinrich + ¤ Added ['quicktime']['hinting'] key (boolean) + (thanks jonØwebignition*net) + * Bugfix: major UTF-8 to UTF-16/ISO-8859-1 conversion + bug (empty string returned) when using iconv_fallback + (thanks chrisØfmgp*com) + * Bugfix: Missing 'lossless' key in RIFF-WAV + (thanks bobbfwedØcomcast*net) + +1.7.3: [2005-04-22] James Heinrich + » Added TAR support -- new file: module.archive.tar.php + (thanks Mike Mozolin for code!) + » Added GZIP support -- new file: module.archive.gzip.php + (thanks Mike Mozolin for code!) + * Bugfix: demo.browse.php now displays embedded images + internally instead of passing local filename as IMG + SRC (should allow demo.browse.php to correctly show + embedded images over a network) + (thanks patpowermanØhotmail*com) + * Bugfix: minor UTF-8 display issues in demo.browse.php + * Bugfix: demo.browse.php now works even if the evil + setting magic_quotes_gpc is turned on + (thanks patpowermanØhotmail*com) + * Bugfix: incorrect MIDI playtime for some files + (thanks joelØoneporpoise*com) + * Bugfix: 'url_source' typo in module.tag.id3v2.php + (thanks richardlynchØusers*sourceforge*net) + * Bugfix: Quicktime 'mvhd' matrix values were wrong + (thanks webØbobbymac*net) + ¤ ID3v2 now returns xx/yy for ['track'] (if + available), with xx in ['tracknum'] and yy in + ['totaltracks']. Previously ['tracknum'] was not + available and ['track'] had only xx. + Bugfixes and improvements to /demo/demo.mysql.php: + - remix/version parsed from tags and stored in + database, can be used when renaming files + - track number can be used for renaming files + + +1.7.2: [2004-10-18] Allan Hansen + » Added support for WavPack v4.0+ + (thanks ahØartemis*dk) + » Removed code for parsing EXE files + (thanks ahØartemis*dk) + Removed file: module.misc.exe.php + * Bugfix: Large ID3v2 tags inside ASF not parsed + properly under PHP5. + * Bugfix: Certain Wavpack3 files failed under PHP5 due + to new undocumented tmpfile() limit (same problem as + above). + * Bugfix: New iTunes crashes PHP - temp fix - no tags + on those files. + * Bugfix: ['nsv']['NSVs']['framerate_index'] might be + wrong (thanks ahØartemis*dk) + * Bugfix: transparent color was wrong from truecolor + PNG (thanks ahØartemis*dk) + * Bugfix: Changed MPC SV7 header size from 30 to 28, + this will change hash values for MPC files + (thanks ahØartemis*dk) + * Bugfix: Changed MPC SV4-6 header size from 28 to 8, + this will change hash values for MPC files + (thanks ahØartemis*dk) + ¤ Trim/unset wavpack encoder_options to match 2.0.0b2 + output. + ¤ Commented-out unknown/unused values in NSV and ISO + modules (thanks ahØartemis*dk) + + +1.7.1b1: [July-26-2004] James Heinrich + » Added support for Apple Lossless Audio Codec + » Added support for RealAudio Lossless + » Added support for TTA v3 + » Added support for TIFF + New file: /getid3/module.graphic.tiff.php + » Modified iconv_fallback to work with UTF-8, UTF-16, UTF-16LE, + UTF-16BE and ISO-8859-1 even if iconv() and/or XML support is + not available. This means that iconv() is no longer required + for most users of getID3() + (thanks Jeremia, khleeØbitpass*com) + » Added support for Monkey's Audio v3.98+ (thanks ahØartemis*dk) + » Included new demo showing most-basic getID3() usage + New file: /demos/demo.basic.php + * Bugfix: LAME3.94+ presets cached incorrectly if multiple files + are scanned in one batch and first file is LAME3.93 or earlier + (thanks enoyandØyahoo*com) + * Bugfix: Added warning if compressed ID3v2 frame decompression + fails. (thanks Mike Billings) + * Bugfix: Assorted small fixes to ensure compatability with PHP5 + * Bugfix: ID3v1 genre "Blues" could not be written + (thanks Jeremia) + * Bugfix: ['bitrate_mode'] typo in module.audio-video.real.php + (thanks asukakenjiØusers*sourceforge*net) + * Bugfix: ['zip']['files'] is now populated with filenames even + if End Of Central Directory couldn't be parsed + * Bugfix: ['audio']['lossless'] was incorrect for FLAC + (thanks WaldoMonster) + * Bugfix: MD5 File was incorrect in directory browse mode for + /demo/getid3.browse.php + * Bugfix: PHP v5 compatability changes (float array keys, fread() + calls with zero data length) + (thanks getid3Øjsc*pp*ru) + * Bugfix: was dying if on compressed ID3v2 frames if + gzuncompress() function was unavailable + * Bugfix: ['vqf']['COMM'] was always empty + * Bugfix: MIDI playtime was missing for single-track MIDI files + * Bugfix: removed � characters from ['comments_html'] + (thanks p*quaedackersØplanet*nl) + * Bugfix: improved MIDI playtime accuracy + (thanks joelØoneporpoise*com) + * Bugfix: BMP subtypes 4 and 5 were not being identified + * Bugfix: frame_rate in AVI was incorrectly truncated to integer + * Bugfix: FLAC cuesheet track index was incorrect + (thanks tetsuo*yokozukaØoperamail*com) + ¤ ['quicktime']['display_scale'] now contains the playback scale + multiplier for QuickTime movies - a movie set to playback at + double-size will have "2" here. Other values are "1" and "0.5" + ¤ Added LAME preset guessing for --preset medium with v3.90.3 + (thanks phwipØfish*co*uk) + ¤ Added $encoding_id3v1 to allow for ID3v1 encodings other than + the standard ISO-8859-1 + ¤ Default AVI video bitrate_mode is now 'vbr' + (thanks eltoderØpisem*net) + Force getID3() to abort if Shorten files have ID3 or APE tags + (thanks ahØartemis*dk) + Editable textbox for parent directory in demo.browse.php + (thanks eltoderØpisem*net) + + +1.7.0-hotfix [2004-03-17] Allan Hansen + (hotfix version released by Allan Hansen) + * Bugfix: PHP 4.1.x compatiblity - fgets($fp) => fgets($fp, 1024) + * Bugfix: Added default charset to TextEncodingNameLookup() in + module.tag.id3v2.php + Ø Removed option_no_iconv + iconv() support is only a requirement for WMA/WMW/ASF, and for + destination encodings other than ISO-8859-1 and UTF-8, iconv is + not needed otherwise. New 'iconv_req' in GetFileFormatArray() + only set for WMA/WMV/ASF. analyze() now refuses to analyse + WMA/ASF file if iconv is not present. + iconv_fallback() only dies on internal errors not missing iconv() + + +1.7.0: [January-19-2004] James Heinrich + » Added support for RIFF/CDXA files (MPEG video in RIFF container + format (thanks chrisØdigitekdesign*com) + » Added support for TTA v2 (thanks ahØartemis*dk) + ¤ ID3v2 unsynchronisation scheme disabled by default because most + tag-reading programs cannot read unsynchronised tags. Can be + overridden by setting id3v2_use_unsynchronisation to true. + (thanks mikeØdelusion*org) + ¤ extention.*.php renamed to extension.*.php + (thanks tp62Øcornell*edu) + ¤ /demo/demo.check.php renamed to /demo/demo.browse.php + ¤ Added id3v2_paddedlength configuration parameter to WriteTags() + and renamed tag_language to id3v2_tag_language + ¤ MPEG audio layers are now represented as 1, 2 or 3 instead of + 'I', 'II', or 'III' + ¤ Added [audio][wformattag] and [video][fourcc] for WAV and AVI + ¤ Added [audio][streams] which contains one entry for each audio + stream present in the file (usually only one). The data is a + copy of what is usually found in [audio]. If there are multiple + audio streams then [audio] will contain a sum of the bitrates + of all audio streams, and the data format of the first stream + (if streams are of different data types) + ¤ Added BruteForce mode to mp3 scanning. Disabled by default as + it is extremely slow and only files that are broken enough to + not really play will gain any benefit from this. + ¤ Suppress '--resample xxxxx' appended to encoder options for mp3 + with low-quality presets for default sampling frequencies + ¤ Enhanced LAME preset guessing for pre-3.93 with a better lookup + table, --resample/--lowpass guessing (thanks phwipØfish*co*uk) + ¤ RIFF files with non-MP3 contents no longer have + [audio][encoder_options] set + ¤ Added [audio][encoder_options] to audio formats where possible + (including LiteWave, LPAC, OptimFROG, TTA) + ¤ Moved [quantization] and [max_prediction_order] from + [lpac][flags] to just [lpac] + ¤ WavPack flags are now parsed into [wavpack][flags] + * Bugfix: APEtags with ReplayGain information stored with comma- + seperated decimal values (ie "0,95" instead of "0.95") were + giving wrong peak and gain values + * Bugfix: Filesize > 2GB not always detected correctly + * Bugfix: Some ID3v2 frames had data key unset incorrectly + (thanks chrisØdigitekdesign*com) + * Bugfix: Warnings on empty-strings-only comments + * Bugfix: ID3v2 tag writing may have had incorrect padding length + if padded length less than current ID3v2 tag length and + merge_existing_data is false (thanks mikeØdelusion*org) + * Bugfix: hash_data() for SHA1 was broken under Windows + * Bugfix: BigEndian2Float()/LittleEndian2Float() were broken + * Bugfix: LAME header calculated track peaks were incorrect for + LAME3.94a15 and earlier + * Bugfix: AVIs with VBR MP3 audio data reported incorrect bitrate + and bitrate_mode + * Bugfix: AVIs sometimes had incorrect or missing video and total + bitrates + * Bugifx: AVIs sometimes had incorrect ['avdataend'] and + therefore also incorrect data hashes (md5_data, sha1_data) + * Bugfix: ID3v1 genreid no longer returned for Unknown genre + * Bugfix: ID3v1 SCMPX genres were broken + Modified LAME header parsing to correctly process peak track + value for LAME3.94a16+ (thanks Gabriel) + md5_file() and sha1_file() now work under Windows in PHP < 4.2.0 + and 4.3.0 respectively with helper apps + Default md5_data() tempfile location is now system temp directory + instead of same directory as file (thanks towbØtiscali*de) + Improved list of RIFF ['INFO'] comment key translations + More helpful error message when GETID3_HELPERAPPSDIR has spaces + /demo/demo.browse.php now autogets both MD5 and SHA1 hashes for + files < 50MB + Replaced PHP_OS comparisons with GETID3_OS_ISWINDOWS define + (thanks necroticØusers*sourceforge*net) + + +1.7.0b5: [December-29-2003] James Heinrich + » Windows only: Various binary files are now required for some + file formats, especially for tag writing, as well as md5sum + (and other) calculations. These binaries are now stored in the + directory defined as GETID3_HELPERAPPSDIR in getid3.php + (default is /helperapps/ parallel to /getid3/). + Note: This directory must not have any spaces in the pathname. + All neccesary files are available as a seperate download. + See /helperapps/readme.txt for more information + New file: /helperapps/readme.txt + » Unified tag-writing interface for all tag formats + New file: /getid3/write.php + /getid3/write.apetag.php + /getid3/write.id3v1.php + /getid3/write.id3v2.php + /getid3/write.lyrics3.php + /getid3/write.metaflac.php + /getid3/write.vorbiscomment.php + » Added support for Shorten - requires shorten binary (head.exe + is also required under Windows). + New file: /getid3/module.audio.shorten.php + » Added support for RKAU + New file: /getid3/module.audio.rkau.php + » Added (minimal) support for SZIP + New file: /getid3/module.archive.szip.php + » Added MySQL caching extention (thanks ahØartemis*dk) + New file: /getid3/extention.cache.mysql.php + » Added DBM caching extention (thanks ahØartemis*dk) + New file: /getid3/extention.cache.dbm.php + » Added sha1_data hash option (thanks ahØartemis*dk) + » Added option to allow getID3() to skip ID3v2 without parsing it + for faster scanning when ID3v2 data is not required. If you + want to enable this feature delete /getid3/module.tag.id3v2.php + (thanks ahØartemis*dk) + ¤ 8-bit WAV data now calculates MD5 checksums as normal, not + converting to signed data as before, so stored md5_data_source + in FLAC files will no longer match md5_data for the equivalent + decoded 8-bit WAV. A warning will be generated for 8-bit FLAC + files + ¤ Added option_no_iconv option to allow getID3() to work + partially without iconv() support enabled in PHP + (thanks ahØartemis*dk) + ¤ All '*_ascii' keys removed for ASF/WMA/WMV files + ¤ All 'ascii*' keys removed for ID3v2 tags + ¤ Ogg filetypes now return MIME of "application/ogg" instead of + the previous "application/x-ogg" + (thanks blakewattersØusers*sourceforge*net) + ¤ Force contents of ['id3v2']['comments'] to UTF-8 format from + whatever encoding each frame may have (text encoding can vary + from frame to frame in ID3v2) + ¤ MP3Gain information from APE tags suppressed from ['tags'] and + parsed into ['replay_gain'] + ¤ ReplayGain information (all formats) changed from "Radio" and + "Audiophile" to "Track" and "Album" respectively + ¤ ['volume'] and ['max_noclip_gain'] are now available in both + ['replay_gain']['track'] and ['replay_gain']['album'] for all + formats that calculate ReplayGain. + ¤ ['video']['total_frames'] is available for AVIs + ¤ All parsed ID3v2 frame data is now in ['id3v2'][XXXX][#] + (previously some frame types would have numeric array keys if + multiple instances of that frame type were allowed and other + frame types would not) + ¤ ASF/WMA "WM/Picture" images are now parsed in the same manner + as ID3v2 with the image (ex JPEG) data returned in [data] + rather than [value] + * Bugfix: Optional tag processing options were being ignored (ie + ID3v1 still processed even if option_tag_id3v1 == false) + (thanks ahØartemis*dk) + * Bugfix: fixed MultiByteCharString2HTML() for UTF-8 + * Bugfix: Quicktime files not always reporting video frame_rate + * Bugfix: False ID3v1 synch patterns in APE or Lyrics3 tags are + now detected and incorrect ID3v1 data not returned + (thanks sebastian_maresØusers*sourceforge*net for the idea) + * Bugfix: WMA9 Lossless now reported as lossless + * Bugfix: two typos in ID3v1 genre list + * Bugfix: MPEG-2/2.5 ABR/VBR MP3 files had doubled playtime + * Bugfix: MPEG-2/2.5 LayerII (ie MP2: 24/22.05/16kHz) files were + not detected due to incorrect frame length calculation + * Bugfix: MPEG LayerI files were not detected due to incorrect + frame length calculation (must be multiple of slot length) + Added alternative md5_data via system call - twice as fast. Needs + "getID3()-WindowsSupport" to work under Windows. + (thanks ahØartemis*dk) + ID3v2.4 compressed frames are now supported + php_uname() calls changed to use PHP_OS constant + Added SCMPX extensions to ID3v1 genres (0xF0-0xFE) + Obfuscated contributor email address in changelog and sourcecode + Added memory-saving EmbeddedLookup() function for lookup tables + in RIFF and ID3v2 modules (thanks ahØartemis*dk) + Major memory patches to many modules by using + $var = &$INFO_ARRAY_AT_SOME_INDEX + in place of large multi-dimensional array declarations. + Memory saved: RIFF: ~200kB; ID3v2: ~475kB; ASF: ~50kB etc. + (thanks ahØartemis*dk) + + +1.7.0b4: [November-19-2003] James Heinrich + » Support added for MPC files with old SV4-SV6 structure + » RealVideo now properly supported with resolution, framerate, etc + (thanks jcsston) + » RealAudio files with old-style file format (v2-v4) are now + fully supported + » Support added for DolbyDigital WAV files (thanks ahØartemis*dk) + ¤ ['RIFF'] is now ['riff'] to conform to make all root key names + lowercase + ¤ ['OFR'] is now ['ofr'] to conform to make all root key names + lowercase + ¤ ['tags_html'] is now available as a copy of ['tags'] but + with all text replaced with an HTML version of all characters + above chr(127), translated according to whatever the encoding + of the source tag is, in the HTML form Ӓ + ¤ CopyTagsToComments() is now available in getid3_lib + ¤ QuicktimeVR files now return a ['video']['dataformat'] of + 'quicktimevr' instead of 'quicktime' (thanks gtsØtsu*biz) + ¤ Quicktime video files with DivX, Xvid, 3ivx or MPEG4 video + streams now return those names as ['video']['dataformat'] + ¤ MPEG video files are now identified with ['video']['codec'] set + to either 'MPEG-1' or 'MPEG-2' (rather than just 'MPEG'). If you + see a file wrongly identified, please report it! + (thanks fccHandler) + ¤ All bitrate values in ['mpeg']['audio'] is now reported in bps + rather than kbps (ie 128000 instead of 128) for consistancy + ¤ AVIs with MP2 audio now report ['audio']['dataformat'] as 'mp2' + rather than 'wav' (thanks metalbrainØnetian*com) + ¤ Added ['md5_data_source'] for OptimFROG + ¤ AC3 in RIFF-WAV now identified with ['audio']['dataformat'] + returning 'ac3' + ¤ WavPack ['extra_bc'] now returned as integer + ¤ WavPack ['extras'] now returned as 3-element array of integers + ¤ MP3 ['audio']['encoder options'] now returns 'VBR' or 'CBR' only + (no bitrate) if no LAME preset is used, or 'VBR q??' where ?? is + a number 0-100 for Fraunhofer-encoded VBR MP3s + * Bugfix: VBR MP3s could have incorrect bitrate reported + * Bugfix: Quicktime files with MP4 audio were not returning + ['video']['dataformat'] (thanks robØmassive-interactive*nl) + * Bugfix: strpad vs str_pad typo in module.riff.php + (thanks nicojunØusers*sourceforge*net) + * Bugfix: ReplayGain information was often wrong for MPC files + * Bugfix: MD5 and other post-TAIL chunks were not being processed + in module.audio.optimfrog.php + * Bugfix: Undefined variable in table_var_dump() in demo/check.php + * Bugfix: QuickTime files now only return information in [audio] + or [video] if those exist in the file + * Bugfix: WavPack no longer tries to read entire compressed data + chunk + * Bugfix: Properly handle VBR MP3s with "Info" (rather than + "Xing") header frame. foobar2000 adds this to MP3 files when + "Fix MP3 Header" function is used (thanks ahØartemis*dk) + * Bugfix: Fraunhofer VBRI headers for MP3s were assuming 2-byte + entries for TOC rather than using stride, and were ignoring the + scaling value. (thanks sebastianØmaresweb*net) + Several QuickTime atoms have been added to an exclusion list + because they have been observed, but I have no idea what they + are supposed to do so I can't add real support for them, but + they should not generate warnings (robØmassive-interactive*nl) + Old MPC encoder (before v1.06) was return as v0.00, now returned + as 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05' + (thanks ahØartemis*dk) + Added check for magic_quotes_runtime and code to disable it if + neccesary (thanks stefan*kischkelØt-online*de) + Added 3ivx fourCCs to module.audio-video.quicktime.php + MP3 and AC3 streams are now parsed when contained inside RIFF-WAV + or RIFF-AVI container formats + Better detection of named presets in LAME 3.93/3.94 + + +1.7.0b3: [October-17-2003] James Heinrich + » AC-3 (aka Dolby Digital) is now supported. + New file: /getid3/module.audio.ac3.php + * Bugfix: ID3v2-writing function Unsynchronise() was broken, which + made ID3v2 tag containing binary data (typically pictures) get + corrupted. (thanks t*coombesØbendigo*vic*gov*au, + i*kuehlbornØndh*net, mikeØdelusion*org, mikeØftl*com) + * Bugfix: Zip comments now returned as array instead of string, + as they're supposed to be. + * Bugfix: Quicktime/MP4 files may have reported extremely low + bitrates (thanks spunkØdasspunk*com) + Improved double-ID3v1 check to prevent false detection when string + "TAG" is present in APE or Lyrics3 + Fixed /demo/simple.php + Fixed /demo/joinmp3.php + Fixed /demo/mimeonly.php + Fixed /demo/write.php + + +1.7.0b2: [October-15-2003] James Heinrich + » TTA Lossless Audio Compressor format now supported. + (http://tta.iszf.irk.ru) + New file: /getid3/module.graphic.tta.php + » PhotoCD (PCD) format now supported. Image data for the three + lowest resolutions (192x128, 384x256, 768x512) can be optionally + extracted. + New file: /getid3/module.graphic.pcd.php + ¤ RIFF-MP3 files now should return the same ['md5_data'] as the + identical MP3 file outside the RIFF container + ¤ Name of LAME preset used (if available, needs LAME v3.90+) + returned in ['mpeg']['audio']['LAME']['preset_used'] and also as + part of ['audio']['encoder_options'] + ¤ VQF module now sets ['audio']['encoder_options'] to i.e. CBR96 + ¤ MP3 module now sets ['audio']['encoder_options'] on CBR files + and all LAME-encoded files + ¤ MPC module now sets ['audio']['encoder_options'] + ¤ Monkey module now sets ['audio']['encoder_options'] + ¤ AAC module now sets ['audio']['encoder_options'] to profile name + ¤ ASF module now sets ['audio']['encoder_options'] + ¤ Ogg module adds ['audio']['encoder_options'] -b 128 on + Ogg Vorbis 1.0+ ABR files + ¤ Ogg module adds ['audio']['encoder_options'] -q N on + Ogg Vorbis 1.0+ VBR files 44k/48k sample rate/stereo files only. + ¤ Ogg module ['audio']['encoder_options'] "Nominal birate: 80k" to + other Ogg Vorbis files. + ¤ ID3v2 track number now returned as string (with leading zeros, + if present in data) rather than integer (thanks Plamen) + ¤ ASF module returns ['asf']['comments']['encoding_time_unix'] if + available (from WM/EncodingTime) + ¤ Fixed /demo/mysql.php and added some new features: + - encoder options + - ID3v2 "Encoded By" + - non-empty comments + - total entries in database summary (totals & averages) + - database version update + * Bugfix: 'UNICODE' iconv() charset changed to 'UTF-16LE' or + 'UTF-16BE' as appropriate + * Bugfix: iconv_fallback() function created in case iconv() fails + * Bugfix: fixed MD5 calls in demo/check.php + * Bugfix: reenabled detection of APE + Lyrics3 tags in same file + * Bugfix: ASF module now returns ID3v1 genre as string instead of + number - patch from Eugene Toder. + * Bugfix: ASF module now reads non-standard field names, + i.e. "date" as well as WM/Year - patch from Eugene Toder. + * Bugfix: ASF module now returns genre as-is if it is not a + standard ID3v1 genre (thanks wonderboy) + * Bugfix: Eliminated false-synch problem in MP3 module + * Bugfix: Fixed missing root ['bitrate'] for most formats + * Bugfix: ['audio']['compression_ration'] missing for MPC + (thanks WaldoMonster) + * Bugfix: NSV module died in 1.7.0b1 + * Bugfix: ASF module died in 1.7.0b1 when WM/Picture preset + * Bugfix: ASF tracknumber incorrect when specified by WM/Track + rather than WM/TrackNumber (thanks jgriffiniiiØhotmail*com) + * Bugfix: MPEG audio+video playtime should now be pretty accurate + (ie within 0.1% variation at most) + (thanks mgrimmØhealthtvchannel*org) + * Bugfix: ID3v2 not being copied to ['tags'] in some cases + * Bugfix: LAME CBR files with Info tag were being incorrectly + flagged as VBR (thanks Jojo) + * Bugfix: LAME tag not being detected for LAME 3.90 (original) + Changed regex pattern match for MP3 to include 3rd byte for more + reliable/accurate pattern matching + Added duplicate-ID3v1 tag checking (two ID3v1 tags, one after the + other) that has been known to occur with iTunes + (thanks towbØtiscali*de) + Added instructions for enabling iconv() support under Windows + Removed some unneccesary debugging code + Suppressed duplicate PHP warnings for missing include files + Included some missing dependencies in various files + /demo/audioinfo.class.php now copies ['audio']['encoder_options'] + + +1.7.0b1: [2003-09-28] Allan Hansen + This beta version was not made by James Heinrich. It was made by + Allan Hansen - please send bug reports on this + beta directly to me. + + James Heinrich will release 1.7.0 final, but it may take some time + to work out the bugs from the major rewrite. + + This version could be called getID3lite. It makes a lot of checks + optional and makes it easy to remove support for undesired formats + + It also is more library-like. Older versions of getID3() declared + an incredible amount of global scope functions and defined several + constants. 1.7.0beta1 still declares constants, but they are all + prepended by GETID3_. It declares no global scope functions - they + are all wrapped into classes. + + » Made getID3() depend on iconv library: compile PHP --with-iconv + » Created new directory structure + Moved all demos to demos/ + Moved all getID3() files to getid3/ + Renamed most files to module.something + Changed header in all module.something to explain what they do + Simply remove all modules you don't need + Wrapped all modules into classes + * Bugfix: Implemented misc patches from Eugene Toder + * Bugfix: Implemented misc patches from "six" + ¤ Added root key 'encoding' + ¤ Added prefix GETID3_ to all defined constants. + ¤ Wrapped getid3.php into getid3 class + ¤ Wrapped getid3.functions.php into getid3_lib class + Removed unused functions + Moved several functions away from getid3.functions.php and + into the files where they are actually used. + Renamed getid3.functions.php to getid3.lib.php + Moved getid3.rgad.php functions into getid3_lib + Moved getid3.getimagesize.php funcitons ingo getid3_lib + ¤ Moved getid3.ogginfo.php into ogg module + ¤ Combined GetTagOnly() and GetAllFileInfo() in method analyze + ¤ Removed redundant and unuseful root keys + 'file_modified_time' == filemtime($filename) + 'md5_file' == md5_file($filename) + 'exist' == file_exists($filename) + ¤ Changed root key ['tags'] from array of string to array of array + of comments. + Simplified code for detecting base path. + Removed ob_ from InitializeFilepointerArray(). That was really a + ugly HACK to get output from fopen. If user want the reason, + he should open the file himself! + Checking for APE tags before lyrics3 - makes Lyrics3 not depend + on APE tag. It seems to work on my test file. + Changed ['error'] and ['warning'] in multiple files to append to + array instead of appending to string. That simplified code in + getid3.php too. + Simplified clean-up procedure: simply remove all empty root keys + Setting tags in individual modules instead of main getid3.php + Made Bonk and ASF modules non-dependent on id3 modules - id3 + optional. + Rewrote HandleAllTags() - simplified and convert comments to + desired encoding. + Replaced all calls to RoughTranslateUnicodeToASCII() in ASF module + with a TrimConvert() method. This uses iconv() for conversion. + It also converts from UNICODE instead of UTF-16BE, as the spec + says it should. + Replaced all calls to RoughTranslateUnicodeToASCII() in id3v2 + module with iconv(). id3v2 module also reads + $ThisFileInfo['encoding'] and converts all comments to this + format. All other formats just add their comments in their + native charset, but every comment field in id3v2 can have a + different encoding, so this is needed. + Did same thing as above with ISO module. However - it does not + work. I need to find out how to specify big-endian unicode != + UNICODING encoding name given to iconv(). + Built-in assume mp3 format in getid3.php + Temporarily nuked root key ['comments'] and CopyCommentsToRoot() + Updated demo/audioinfo.class.php + Updated demo/check.php - some thing don't work! + Other demos are out of order! + + +1.6.5: [October-06-2003] James Heinrich + » Added support for LiteWave (thanks supportØclearjump*com) + Ø Split out speedup info from ['OFR']['OFR']['compression'] into + ['OFR']['OFR']['speedup'] + Ø If EXIF functions for JPEG not available, now warning not error + Ø ID3v2 track number now returned as string (with leading zeros, + if present in data) rather than integer (thanks Plamen) + * Bugfix: now correctly parses cbSize element of WAVEFORMATEX + structure (thanks supportØclearjump*com) + * Bugfix: ASF module now reads non-standard field names, + i.e. "date" as well as WM/Year - patch from Eugene Toder. + * Bugfix: ASF module now returns genre as-is if it is not a + standard ID3v1 genre (thanks wonderboy) + * Bugfix: ['audio']['compression_ration'] missing for MPC + (thanks WaldoMonster) + Prevent infinite loop in MP3 histogram if framelength == 0 + Added wFormatTag values 0x00FF and 0x2001 - 0x2005 + (thanks steveØheadbands*com) + Added "twos" and "sowt" FourCCs for Mac AIFC + + +1.6.4: [June-30-2003] James Heinrich + » Added support for free-format MP3s + (thanks Sebastian Mares for the idea) + » Compressed (Flash 6+) SWF files are now handled properly + (thanks alan*cheungØalumni*ust*hk) + » Added DeleteLyrics3() to getid3.lyrics3.php + » Added FixID3v1Padding() to getid3.putid3.php + » Added new simple MP3-splicing sample file + (thanks tommybobØmailandnews*com for the idea) + New file: getid3.demo.joinmp3.php + » Moved all contents of getid3.putid3.php into either + getid3.id3v1.php or getid3.id3v2.php or getid3.functions.php as + appropriate + Removed file: getid3.putid3.php + ¤ ['error'] and ['warning'] keys now return as arrays, not strings + ¤ New root key for all files: ['file_modified_time'] (UNIX time) + ¤ getid3.demo.scandir.php renamed to getid3.demo.mysql.php + ¤ New demo file returns the MIME type only for a single file + (thanks adminØe-tones*co*uk for the idea) + New file: getid3.demo.mimeonly.php + ¤ Added check for valid ID3v1 padding (strings should be padded + with null characters but some taggers incorrectly use spaces). + A warning will be generated if padding is invalid. New boolean + key ['id3v1']['padding_valid'] indicates padding validity. + ¤ CleanUpGetAllMP3info() removes more useless root keys for + unknown-format files + ¤ Extended LAME information in ['mpeg']['audio']['LAME'] is now + only returned for LAME v3.90+ + ¤ LAME-encoded MP3s now return + ['mpeg']['audio']['LAME']['long_version'] as well as + ['mpeg']['audio']['LAME']['short_version'] - these are identical + in LAME v3.90+ but older versions will report longer more + detailed version information if available + ¤ New Lyrics3 values: ['lyrics3']['raw']['offset_start'] and + ['lyrics3']['raw']['offset_end'] + ¤ New optional parameter on getAPEtagFilepointer() to scan from a + defined offset rather than end-of-file to allow scanning of APE + tags before Lyrics3 tags + ¤ ['tag_offset_start'] and ['tag_offset_end'] are now present in + ['ape'], ['lyrics3'], ['id3v1'] and ['id3v2'] + ¤ Numerous changes to the returned structure and content for La + files, including parsing the seektable (if applicable) and + parsing RIFF data occuring after the end of the compressed audio + data (notably RIFF comments) + (thanks mikeØbevin*de) + ¤ getSWFHeaderFilepointer() now has optional 3rd parameter + $ReturnAllTagData (default == false) which if set to true will + return data on all tags in ['swf']['tags'] + ¤ ['swf']['bgcolor'] now returns the 6-character string + representing the background color in HTML hex color format + (thanks ubergeekØubergeek*tv) + ¤ ['swf']['header']['frame_delay'] is no longer returned + ¤ getQuicktimeHeaderFilepointer() now has two additional optional + parameters: $ReturnAtomData (default == true) and + $ParseAllPossibleAtoms (default == false). Setting + $ReturnAtomData to false will reduce the size of the returned + data array by unsetting ['quicktime']['moov'] before returning. + Leaving $ParseAllPossibleAtoms as false now suppresses parsing + of several atom types that contain very large tables of data + that are not typically useful. Atom type suppressed are: + stts, stss, stsc, stsz, and stco + (thanks ubergeekØubergeek*tv) + ¤ ['fileformat'] no longer set to 'id3' if ID3v1 or ID3v2 tag + detected but no other data format recognized + * Bugfix: La files now return the correct values for + ['avdataoffset'] and ['avdataend'] and therefore the correct + values for ['md5_data'] - note that ['md5_data'] values will not + match values from previous versions of getID3() - the previous + versions were incorrect + (thanks mikeØbevin*de) + * Bugfix: A temporary file was being created in the web server's + root directory (not DocumentRoot) each time ['md5_data'] was + calculated, and not removed due to lack of permissions. Temp + file is now created (as it was supposed to be) in the directory + of the file being examined, or the system temp directory, and + properly removed when done. + * Bugfix: Several incorrect values were being returned inside + ['mpeg']['audio']['LAME'] (thanks bouvigneØmp3-tech*org) + * Bugfix: SWF frame rates values were usually incorrect. + (thanks alan.cheungØalumni*ust*hk and ubergeekØubergeek*tv) + * Bugfix: ID3v2.2 files always flagged 4 bytes of invalid padding + (thanks marcaØmac*com) + * Bugfix: Lyrics3 without ID3v1 was not working properly + * Bugfix: Lyrics3, APE & ID3v1 can all now exist in the same file. + A warning is issued if APE comes after Lyrics3 (because Lyrics3- + aware taggers probably are not APE-aware and therefore won't be + able to find the Lyrics3 tag) (thanks mp3gainØhotmail*com) + * Bugfix: WriteAPEtag() now writes the APE tag before any Lyrics3 + tags (if present) and removes any incorrect ones that are after + existing Lyrics3 tags (thanks mp3gainØhotmail*com) + * Bugfix: RIFF-WAVE file with incorrect NumberOfSamples values in + the 'fact' chunk no longer cause incorrect playtime calculation + (thanks stprasadØindusnetworks*com) + * Bugfix: getid3.demo.simple.php had undefined variables if the + file needed to be deep-scanned with assumeFormat + * Bugfix: fixed previously-incorrect ['avdataend'] values for APE + and Lyrics3 tags in some cases, which in some cases means that + ['md5_data'] is different than previously (now correct) + Much-improved detection of AAC-ADTS, which also means MP3 + format detection should now be nearly twice as fast + Truncated AVIs and WAVs are now reported + Number of new features and bugfixes in getid3.demo.mysql.php + Quicktime 'meta' atoms now parsed, so Quicktime MP4 files can now + return artist, title, album, etc (thanks spunkØdasspunk*com) + Consolidated all comments processing functions (processing the + ['comments'] and ['tags'] keys) into HandleAllTags() which now + also checks to ensure that APE tags are really better than ID3v2 + before using them in ['comments'] + Known issue with Meracl ID3 Tag Writer v1.3.4 truncating last byte + of MP3 file when appending new ID3v1 tag now specifically noted + (rather than generic Probably Truncated File message) + getid3.demo.mysql.php now stores last-modified time for each file + getid3.demo.mysql.php is now case-sensitive for filenames + getid3.demo.mysql.php can generate M3U playlists of any of the + groups of files it can select (duplicate filenames, tag types, + etc.) + getid3.demo.mysql.php can now find mismatched tag contents and + filenames + getid3.demo.check.php now shows total number of errors & warnings + GetFileFormatArray() now matches actual patterns for MP3 files + based on the first two bytes of the file, rather than just the + first one + Simplified DeleteAPEtag() and made it work properly with Lyrics3 + + +1.6.3: [May-17-2003] James Heinrich + » Added support for Bonk (thanks ahØartemis*dk) + New file: getid3.bonk.php + » Added support for AVR (thanks ahØartemis*dk) + New file: getid3.avr.php + ¤ Contents of getid3.id3.php moved to getid3.id3v1.php + Removed file: getid3.id3.php + ¤ Contents of getid3.frames.php moved to getid3.id3v2.php + Removed file: getid3.frames.php + ¤ Returned data structure documentation improved and updated and + now stored in getid3.structure.txt rather than getid3.readme.txt + New file: getid3.structure.txt + ¤ Now including the GNU General Public License in the distribution + as getid3.license.txt + New file: getid3.license.txt + ¤ Added new, optional, parameter to WriteAPEtag() (and also + GenerateAPEtag()) which must be set to TRUE if the values you + are passing are already UTF8-encoded, otherwise all data is + encoded to UTF8 by default. For all ASCII/ANSI data this value + should be left at the defaul value of FALSE. + ¤ Added third, optional, parameter to getID3v2Filepointer() - + $StartingOffset (default == 0) which can parse an ID3v2 tag + in a file at a position other than the start-of-file. + ¤ ['video']['pixel_aspect_ratio'] now returned when known + ¤ AVI files with WMA audio now return ['audio']['dataformat'] + of 'wma' rather than 'wav' + ¤ ASF-WMA files now return the artist value from WM/AlbumArtist + in ['comments']['artist'] (thanks msibbaldØsaebauld*com) + ¤ ASF-WMA files now return the 'author' value from + ['asf']['content_description'] in ['comments']['artist'] + instead of ['comments']['author'] + ¤ ASF-WMA files now return the 'description' value from + ['asf']['content_description'] in ['comments']['comment'] + instead of ['comments']['description'] + * Bugfix: APE tag writing with multiple values for a tag (more + than one ARTIST for example) was not being correctly written + (thanks ahØartemis*dk) + * Bugfix: CreateDeepArray() was returning an empty-string key as + the top-level returned value - ['iso']['files'] now directly + contains the file listing without an empty array in between. + * Bugfix: ID3v2 genreid was not being returned in some cases. + * Bugfix: APEv1 tags would generate error messages + * Bugfix: APE tags would sometimes show phantom second entry for + each item (title, artist, etc) with no data + * Bugfix: APE tag writing was not UTF8-encoding the data - + non-ASCII characters (above chr(127)) were being incorrectly + stored (thanks ahØartemis*dk) + * Bugfix: getid3.demo.scandir.php had undefined function error + * Bugfix: getid3.demo.scandir.php would not display list of files + with no tags + Added link to getid3.demo.check.php from list of specific-tags + files in getid3.demo.scandir.php + + +1.6.2: [May-04-2003] James Heinrich + » New official mirror site for getID3() - http://www.getid3.org + » Added basic support for SWF (Flash) (thanks n8n8Øyahoo*com) + New file: getid3.swf.php + » Added experimental support for parsing the audio portion of + MPEG-video files. I don't have any actual documentation for + this, so this part is experimental and not guaranteed accurate, + but it seems to be working OK as far as I have been able to test + it. Bug reports (or even better - documentation!) are welcome at + info@getid3.org + » Added new simple directory-scanning sample file + New file: getid3.demo.simple.php + » getid3.demo.write.php now writes APE tags as well. + ¤ Renamed getid3.write.php to getid3.demo.write.php + ¤ Renamed audioinfo.class.php to getid3.demo.audioinfo.class.php + ¤ getid3.php now automatically includes the getid3.functions.php + function library file, no need to include it seperately. + ¤ getLyrics3Filepointer() has been changed to be consistant with + all the other similar function structures - the parameters have + changed. The old function has been renamed to getLyrics3Data() + ¤ Added DeleteAPEtag() function to getid3.ape.php + ¤ HandleID3v1Tag() now only handles ID3v1. Lyrics3 processing is + now done by HandleLyrics3Tag() + ¤ If BitrateHistogram is enabled in getOnlyMPEGaudioInfo() it now + also returns ['mpeg']['audio']['version_distribution'] showing + the number of frames of each MPEG version (1, 2 or 2.5) - all + frames *should* be of the same MPEG version + ¤ getID3v1Filepointer() always returns TRUE now, even if it didn't + find a valid ID3v1 tag + ¤ getOnlyMPEGaudioInfo() now looks for MPEG sync in the first 128k + bytes rather than the first 64k bytes + ¤ Added dummy function GetAllMP3info() to generate warning not to + use that deprecated function. + ¤ ['video']['codec'] is now 'MPEG' for all MPEG video files (this + will change to 'MPEG-1' or 'MPEG-2' as soon as I figure out how + to determine that) (thanks jigalØspill*nl) + ¤ ['mpeg']['audio']['LAME']['mp3_gain'] renamed to + ['mpeg']['audio']['LAME']['mp3_gain_db'] (gain in dB) + ¤ Added ['mpeg']['audio']['LAME']['mp3_gain_factor'] (gain as a + multiplication factor) + ¤ Added support for Preset and Surround Info bytes from LAME VBR + tag (http://gabriel.mp3-tech.org/mp3infotag.html) + * Bugfix: APE tag writing would put the string 'Array' for all + values rather than the actual data (thanks ahØartemis*dk) + * Bugfix: Warning now generated for VBR MPEG-video files because + getID3() cannot determine average bitrate. If you know of + documentation that would tell me how to do this, please email + info@getid3.org + * Bugfix: Replay Gain values from Vorbis comments are now + returned in ['replay_gain'] (and not in ['comments']) + (thanks ahØartemis*dk) + * Bugfix: Replay Gain values from APE comments are now correctly + returned in ['replay_gain'] (thanks ahØartemis*dk) + * Bugfix: getid3.demo.check.php is now case-insensitive when + assuming a format for a corrupted file if standard detection + does not identify the file type. + * Bugfix: RIFF comments were overwriting/suppressing ID3 comments + for RIFF-MP3 files (thanks wmØwofuer*com) + * Bugfix: RIFF-MP3 files with 'RMP3' chunks instead of 'WAVE' were + not being correctly identified. + * Bugfix: ID3v2 padding shorter than the length of an ID3v2 frame + header was not correctly detected + * Bugfix: getid3.demo.check.php now does in-depth scanning for MP2 + and MP1 files the same as for MP3 files based on file extension + if a MPEG-audio structure isn't found immediately at the start + of the file + * Bugfix: removed condition where RIFF-WAV was being scanned for + MPEG-audio signature when it shouldn't be present (non-MP3 WAV) + * Bugfix: ASF files were not always showing correct audio datatype + * Bugfix: array_merge_clobber() and array_merge_noclobber() were + not being conditionally defined in getid3.functions.php + (thanks rich.martinØreden-anders*com) + * Bugfix: stream_numbers was not being correctly returned in + bitrate_mutual_exclusion_object chunks of ASF files + * Bugfix: Added support for 24kHz and 12kHz audio in ASF files + * Bugfix: Removed possible undefined offset error in MP3s where + cannot find synch before end of file + * Bugfix: Removed potential out-of-memory crash situation when + parsing Real files with chunks larger than the available memory + (thanks jigalØspill*nl) + * Bugfix: ID3v1 was incorrectly taking precedence over ID3v2 in + the ['comments'] array (thanks lionelflØwanadoo*fr) + * Bugfix: No longer calculates overall bitrate and playtime for + VBR MPEG video files based on the audio bitrate. + * Bugfix: AssumeFormat was not working properly + Added summary footer line to getid3.demo.check.php + Added '.mpeg' to the list of assume-format-from-filenames list in + getid3.demo.check.php + MPEG-video files now more reliably detected + A number of additional features have been added to + getid3.demo.scandir.php + Added many RIFF-AVI audio types and fourcc video types to the + lookup functions in getid3.riff.php + Now identifes files with Lyrics3 v1 tags that are of incorrect + length (v1 Lyrics3 is supposed to be 5100 bytes long, but + [unknown program] writes variable-length tags (which is illegal + for Lyrics3 v1)). getID3() now correctly parses these tags and + issues a warning. + Split GetFileFormat() to GetFileFormat() and GetFileFormatArray() + HTML colors in getid3.demo.check.php are now defined as constant + variables at the top of the file (if you want to change them) + Added support for OptimFROG v4.50x (non-alpha) (new header fields) + (thanks floringhidoØyahoo*com) + Added support for Lossless Audio v0.4 (thanks mikeØbevin*de) + + +1.6.1: [March-03-2003] James Heinrich + » Added support for writing APE v2. + WriteAPEtag() in getid3.ape.php + NOTE: APE v1 writing support will *not* be added to future + versions of getID3() + (thanks ahØartemis*dk and adamØphysco*com for the idea) + » Added support for AIFF (Audio Interchange File Format) including + AIFF, AIFC and 8SVX (thanks ahØartemis*dk for the idea) + Removed file: getid3.aiff.php + » Added support for OptimFROG (v4.50a and v4.2x) + (thanks ahØartemis*dk for the idea) + New file: getid3.optimfrog.php + » Added support for WavPack (thanks ahØartemis*dk for the idea) + » Added support for LPAC (thanks ahØartemis*dk for the idea) + » Added support for NeXT/Sun .au format + New file: getid3.au.php + » Added support for Creative SoundBlaster VOC format + New file: getid3.voc.php + » Added support for the BWF (Broadcast Wave File) RIFF chunks + "bext" and "MEXT" (thanks Ryan and njhØsurgeradio*co*uk) + » Added support for the CART (Broadcast Wave File) RIFF chunks + (thanks Ryan) + » Added getid3.demo.scandir.php - a sample recursive scanning demo + that scans every file in a given directory, and all sub- + directories, and stores the resulting data in MySQL database, + and then displays a list of duplicate files based on md5_data + ¤ ['md5_data_source'] now contains the MD5 value for the original + uncompressed data for formats that store that information + (currently only FLAC v0.5+). ['md5_data'] (if chosen to be + calculated) will contain the calculated MD5 value for the + compressed file. To check if 2 files are identical in every way, + including all comments: compare ['md5_file']. To check if two + files were compressed from the same source file: compare + ['md5_data_source']. To check if the compressed audio/video data + of two files is identical, even if comments or even the + container file format is different (MP3 in RIFF container, + FLAC in Ogg container, etc): compare ['md5_data']. + ¤ ['md5_data'] for 8-bit WAV files is now calculated based on a + converted version of the data from unsigned to signed (MSB + inverted) to match the MD5 value calculated by FLAC + ¤ New optional parameter added to GetAllFileInfo() - + $MD5dataIfMD5SourceKnown (default: false). If false the md5_data + value will NOT be calculated for files (such as FLAC) that have + ['md5_data_source'] set, even if $MD5data == true. + (thanks ahØartemis*dk) + ¤ getid3.check.php renamed to getid3.demo.check.php + ¤ Added GetTagOnly() function to getid3.php - similar to + GetAllFileInfo() except only takes a filename as a parameter and + only returns ID3v2, APE, Lyrics3 and ID3v1 tag information - no + attempt is made to parse the data contents of the file at all. + (thanks Phil for the idea) + ¤ Added ['audio']['lossless'] and ['video']['lossless'] for all + formats (when known). Both are boolean values - true means the + data is lossless-compressed, false means the data is lossy- + compressed. + ¤ Added ['audio']['compression_ratio'] and/or + ['video']['compression_ratio'] for all formats. Returns a number + (usually) less than 1, where 1 represents no compression and 0.5 + represents a compressed file half the size of the original file + ¤ Added ['video']['bits_per_sample'] to all video formats (when + known) + ¤ Added ['video']['frame_rate'] to all video formats (when known) + ¤ ['fileformat'] set to 'mp1' or 'mp2' instead of 'mp3' when + ['audio']['dataformat'] is one of those (thanks ahØartemis*dk) + ¤ Added 4th parameter to md5_data(), $invertsign, which will invert + the MSB of each byte before MD5'ing. This is needed for 8-bit + WAV files because FLAC calculates the stored MD5 value on + signed data rather than the original byte values. ['md5_data'] + of an 8-bit WAV will now match the ['md5_data_source'] value + (thanks lichvarmØphoenix*inf*upol*cz) + ¤ ['ape']['items']['data'] and ['ape']['items']['data_ascii'] now + contains an array of values, if the tag contains UTF-8 text (as + opposed to binary data) + ¤ ['mpeg']['audio']['bitratemode'] renamed to + ['mpeg']['audio']['bitrate_mode'] + * Bugfix: Removed potential bug that could replace all MP3 file + contents with only the new ID3v2 tag in getid3.putid3.php + * Bugfix: md5_data values calculated for RIFF (WAV, AVI) files + were incorrect (thanks ahØartemis*dk) + * Bugfix: MP3 data in an MP4 wrapper fileformat could not identify + bitrate (thanks ahØartemis*dk) + * Bugfix: ['audio'] and/or ['video'] keys would sometimes get + removed even if not empty + * Bugfix: Prevented creation of null entries in + ['RIFF']['WAVE']['INFO'] if a comment entry was not present + * Bugfix: Potential infinite-loop condition in getid3.ogg.php + (thanks afshin.behniaØsbcglobal*net) + * Bugfix: Ogg files with illegal ID3v1 (and/or APE or Lyrics3) + tags were not finding the last Ogg page + (thanks afshin.behniaØsbcglobal*net) + * Bugfix: replay-gain values not properly set from LAME tag + * Bugfix: RIFF-MP3 had incorrect md5_data + * Bugfix: the LAME DLL CBR problem of not re-writing the LAME + frame at the beginning of the data is now detected for MP3s + with ID3v2 tags as well + * Bugfix: APE tags with multiple values (ie multiple entries in + the "artist" tag) are now shown properly in ['ape']['items'] + * Bugfix: fixed condition where APE tag with no ID3v1 tag could be + mistaken for APE tag with ID3v1 (and incorrectly parsed) + * Bugfix: added warning if ID3v2 frame has zero-length data + (thanks cmassetØclubinternet*fr) + * Bugfix: getid3.frames.php looking for non-existant key in USER + frames + Improved detection of RIFF-MP3 data. [unknown program] encodes + RIFF-WAV data with a chunk name of 'RMP3' instead of the + standard 'RIFF' + Encoder now returned in both ['comments'] and ['audio']['encoder'] + for RIFF-WAV files with an INFO.ISFT chunk + Generate a warning for FLAC files encoded with v0.3 or v0.4 + because audio_signature is not calculated during encoding + (thanks ahØartemis*dk) + Modified getid3.check.php to display md5_data_source as well as + md5_file and md5_data if display-MD5 mode is selected + Modified getid3.check.php to assume-format based on file extension + in browse mode if fileformat is found to be 'id3' (formerly only + if the fileformat was null) + Changed scaling of BitrateColor() from representing 1-256kbps to + representing 1-768kbps for better display of high-bitrate files, + specifically lossless-compressed CD-audio (FLAC, LA, etc) + + +1.6.0: [January-30-2003] James Heinrich + » Added support for OggFLAC (FLAC data stored in an Ogg container) + (thanks ahØartemis*dk for the idea) + » Added support for Speex (the data stored in an Ogg container) + » Comments are now available in the root 2-dimensional array + ['comments'] - each entry in this array will contain one or more + strings. For example, if there are two artists then + ['comments']['artist'][0] will contain the first one and + ['comments']['artist'][1] the other. All keys are forced + lowercase. Comments will be stored in the ['comments'] array in + this order of precedence: + 1) Native format tags (ASF, VQF, NSV, RIFF, Quicktime, Vorbis) + 2) APE tags + 3) ID3v2 + 4) Lyrics3 + 5) ID3v1 + Lower-priority tags will not overwrite or append existing values + of higher-priority tags (for example, 'artist' in ID3v1 will be + ignored if already specified in APE), but missing values will be + filled in (for example, if 'album' is specified in ID3v2 but not + in APE, it will be included in the ['comments'] array). + Note: Root keys (['title'], ['artist'], etc) are NOT available + in this or future versions of getID3(). + (thanks ahØartemis*dk) + » MD5 hashes are now available for all formats for both the entire + file (['md5_file']) and the portion of the file containing only + the audio/video data, stripped of all prepended/appended tags + like ID3v2, ID3v1, APE, etc - ['md5_data'] + (thanks ahØartemis*dk for alternate md5_file() function that + runs on UNIX system running PHP < 4.2.0) + NOTE: Ogg files require the use of vorbiscomment to obtain the + md5_data value. vorbiscomment must be downloaded from + http://www.vorbis.com/download.psp and placed in the getID3() + directory. All Ogg formats (Vorbis, OggFLAC, Speex) are affected + by this problem, but only OggVorbis files can be processed with + vorbiscomment. OggFLAC and Speex files will be processed by + getID3(), but this may result in an incorrect value for md5_data + in the event that VorbisComments are larger than 1 page (4-8kB). + NOTE: md5_data for Ogg will not work if PHP is running in Safe + Mode + » There is now a wrapper class available, written by Allan Hansen, + which should simplify extracting most common basic information + (such as format, bitrate, comments). + New file: audioinfo.class.php + » OggWrite() in getid3.ogginfo.php has been replaced with a new + version that uses vorbiscomment to write the comments, because + of a reported bug that can corrupt OggVorbis files such they + cannot be played. + NOTE: Ogg comment writing now requires the use of vorbiscomment + which must be downloaded from http://www.vorbis.com/download.psp + and placed in the getID3() directory. + NOTE: Ogg comment writing will not work if PHP is running in + Safe Mode + ¤ New root key ['tags'] is now always returned for all formats. + It is an array that may contain any of: + * Native format tags: 'vqf', 'riff', 'vorbiscomment', 'asf', + 'nsv', 'real', 'midi', 'zip', 'quicktime' + * Appended data tags: 'ape', 'lyrics3', 'id3v2', 'id3v1' + ¤ New root key ['audio'] is an array containing any or all of: + codec, channels, channelmode, bitrate, bits_per_sample, + dataformat, bitrate_mode, sample_rate, encoder + Note: This replaces several root keys, including: + bitrate_audio, bits_per_sample, frequency, channels + ¤ New root key ['video'] is an array containing any or all of: + bitrate_mode, bitrate, codec, resolution_x, resolution_y, + resolution_y, frame_rate, encoder + Note: This replaces several root keys, including: + bitrate_video, resolution_x, resolution_y, frame_rate + ¤ ['id3']['id3v1'] has moved to ['id3v1'] + ¤ ['id3']['id3v2'] has moved to ['id3v2'] + ¤ ['audiodataoffset'] and ['audiodataend'] have been renamed to + ['avdataoffset'] and ['avdataend'] respectively + ¤ GetAllMP3info() has been changed to GetAllFileInfo() with a + different parameter list ($allowedFormats is no longer a + parameter). Check your code where you're calling + GetAllMP3Info() - you will need to change both the function + name and the parameter list if you pass more than 2 parameters + ¤ All formats now return ['audio']['dataformat'] and/or + ['video']['dataformat'] where appropriate - this goes along with + ['fileformat'] - ['fileformat'] will return the actual structure + of the file, whereas ['dataformat'] will return the format of + the data inside that structure. For example, an Ogg file can + contain Vobis data (normal), or it can contain FLAC data in the + Ogg container format. In that case, ['fileformat'] would be + 'ogg', but ['dataformat'] would be 'flac'. + Note: this means that WAV and AVI files now return a + ['fileformat'] of 'riff' rather than 'wav' or 'avi'. + ¤ ['filesize'] is no longer returned for files larger than 2GB + because PHP does not support large file access. Attempting to + parse a file larger than 2GB will result in a message stored in + ['error'] and ['filesize'] not set. + ¤ APEtag, ID3v1, and ID3v2 are now supported on ALL multimedia + files - even if illegal by format. Ogg will return warning if + ID3/APE tags are present. (thanks ahØartemis*dk) + ¤ All files: non-critical errors are now returned in the root key + ['warning'] rather than ['error'] (only critical errors that + prevent getID3() from correctly parsing the file are returned in + ['error'] (thanks ahØartemis*dk) + ¤ Renamed all references to $MP3fileInfo to $ThisFileInfo + ¤ Joliet now supported for ISO-9660. + ['iso']['supplementary_volume_descriptor'] is now returned, if + available, and ['iso']['files'] will contain ASCII equivalents + of the Unicode directory structure & filenames stored. + ¤ Moved Monkey's Audio code from getid3.ape.php to seperate file. + New file: getid3.monkey.php + ¤ Added new keys for ISO-9660: ['name_ascii'] for directories, + ['file_identifier_ascii'] for files + ¤ Added root key ['track'] for CD-audio files + ¤ Ogg/Vorbis-comment files now have comments returned inside + ['ogg']['comments_common'] as an array of strings, rather than + simple strings in ['ogg'] + ¤ Quicktime files now have comments returned inside + ['quicktime']['comments'] as an array of strings, rather than + simple strings in ['quicktime'] + ¤ ['mime_type'] is a new root key returned for all supported + formats (thanks ahØartemis*dk) + ¤ ['fileformat'] now returns 'mp1' instead of 'mp3' for MPEG-1 + layer-I audio files (thanks ahØartemis*dk) + ¤ ['mpeg']['audio']['bitratemode'] now returns lowercase + ¤ MPEG-4 audio files which consist of MP3 data wrapped in a + Quicktime fileformat will now return the usual data in + ['mpeg']['audio'] + ¤ Type-1 DV AVIs are now supported + ¤ DV AVIs will return 1 or 2 in ['RIFF']['video'][x]['dv_type'] + ¤ Changed ['fileformat'] from 'mpg' to 'mpeg' for MPEG video files + ¤ ASF comments are now stored in ['asf']['comments'] instead of + ['asf'] + ¤ RealMedia chunk data is now returned inside ['real']['chunks'] + instead of ['real'] + ¤ ['replay_gain'] now properly populated from APE tags + ¤ Added support for ASF_Old_ASF_Index_Object in ASF files + (thanks ahØartemis*dk) + ¤ AAC-ADTS files now return ['aac']['bitrate_distribution'] + ¤ ParseVorbisComments() has been replaced with + ParseVorbisCommentsFilepointer() (with different parameters) + ¤ All references to any key ['frequency'] are now ['sample_rate'] + ¤ Moved ID3v2 comments from ['id3v2'] into common root + ['comments'] structure, and now returns more values than before + * Bugfix: ['iso']['files'] and ['zip']['files'] could potentially + contain duplicate entries (in a numeric-indexed array) for files + if the directory structure specifies files multiple times. + Entries are now guaranteed unique, with the last entry for the + file overwriting any former ones. + * Bugfix: RIFF parsing had numerous issues, including: + - large AVIs would take a very very long time to parse + - chunks with odd (not even) sizes would cause the parser fail + - video and/or audio codecs not always identified + The ParseRIFF() function has been completely rewritten and fixes + all known issues with RIFF parsing. Users are, however, + encouraged to double-check output of any parsed (AVI/WAV/CDDA) + files. + * Bugfix: Modified getid3.riff.php to return correct total + bitrates for AVIs with multiple audio streams + * Bugfix: GetFileFormat() was not creating array structure + correctly (thanks ahØartemis*dk) + * Bugfix: LAME tag for MP3s can only specify up to 255kbps, so any + files with actual CBR bitrate of >=256 were reported incorrectly + * Bugfix: Lyrics3 synched lyrics were not being correctly returned + * Bugfix: CreateDeepArray() was broken for non-nested cases, which + meant ZIP and ISO ['files'] structures were broken + * Bugfix: Incorrect pattern matching for ZIP files meant no zip + files were being detected as such + * Bugfix: AAC-ADIF was returning an incorrect number of channels + (too few) in some cases (thanks ahØartemis*dk) + * Bugfix: Vorbis comments were returning an incorrect value for + ['dataoffset'] in some cases + * Bugfix: MPEG video ['marker_bit'] and ['vbv_buffer_size'] were + incorrect + * Bugfix: ['playtime_string'] could potentially have a value of + x minutes and 60 seconds (ie 3:60 instead of 4:00) + Added support for FLAC cuesheets (FLAC 1.1.0+) + (thanks ahØartemis*dk) + Improved parsing speed in MP3, MP2 and AAC (thanks ahØartemis*dk) + Extra error-checking added to try and identify corrupt files for + most audio formats (thanks ahØartemis*dk) + More accurate playtime calculation for RealMedia + (thanks ahØartemis*dk) + Changed all relevant files to use ['audiodataoffset'] and + ['audiodataend'] rather than ['filesize'] where appropriate + (thanks ahØartemis*dk) + Added text encoding type 255 as a duplicate of UTF-16BE but with + Big-Endian rather than Little-Endian byte order + Added many RIFF-AVI audio types and fourcc video types to the + lookup functions in getid3.riff.php + Added numerous new known GUIDs to getid3.asf.php + Added PoweredBygetID3() function to easily get a "powered by" + string with the current getID3() version. + Added "Morgan Multimedia Motion JPEG2000" (MJ2C), "DivX v5" (DX50) + and "XviD" (XVID) codecs to list of known codecs in + getid3.riff.php + Changed GETID3_INCLUDEPATH path seperators to forced / + (from \ for Windows) + Modified getid3.check.php to only change \ directory seperators to + / on Windows operating systems + Modified getid3.check.php to handle larger-than-2GB files (which + now do not return a filesize) + Modified getid3.check.php to handle ['dataformat_audio'] and + ['dataformat_video'] + Modified getid3.check.php to show a list of present tags in one + column rather than one column for each of ID3v1, ID3v2, etc + Modified getid3.check.php to show MD5 values. Initially disabled + but can be enabled for a directory with a click. md5_file is + always calculated when displaying detailed info about a single + file; md5_data is calculated if the file is < 50MB + Modified getid3.check.php to show errors and warnings. Details are + visible with a mouseover or a click. + Changed getid3.check.php to use SafeStripSlashes instead of a + manual conditional directory name replacement for special + characters + Added sample recursive scanning sample code to getid3.readme.txt + (thanks lipisinØmail*ru for the idea) + + +1.5.7: [January-10-2003] James Heinrich + » Added support for ISO 9660 (CD-ROM image) format. Most-useful + data is directory structure returned under ['iso']['files'] + Note: Only ISO-9660 supported, not (yet) Joliet extension + (thanks nebula_djØsofthome*net for the idea) + New file: getid3.iso.php + ¤ ZIP files are now parsed by getID3() itself without relying on + built-in PHP functions and/or ZZipLib support. + (thanks Vince for the idea) + ¤ ZIP files now return a simple directory listing with filename + and filesize info only under ['zip']['files']. + Note: empty subdirectories will note appear in here, only files + and non-empty subdirectories. Information for all entries, + including empty subdirectories, is available under + ['zip']['central_directory'] (or under ['zip']['entries'] if the + Central Directory cannot be located (usually due to a trucated + file). + ¤ RIFF-WAV files with MP3 data (or MP3s with RIFF headers, if you + want to think of it that way) now have the MPEG audio portion + scanned and the usual data returned in ['mpeg']['audio'] if the + RIFF audio codec has wFormatTag of "85" (identified by getID3() + as "MPEG Layer 3") + (thanks ahØartemis*dk for the idea) + ¤ EXIF data (if present) is returned for JPEG files under + ['jpg']['exif'] (thanks nebula_djØsofthome*net) + ¤ ['filepath'] now returned for all files with the directory part + of the full filename. + ¤ ['filenamepath'] is now returned for all files (equivalent to + ['filepath'].'/'.['filename']) + * Bugfix: ['id3']['id3v2'][]['dataoffset'] was wrong + * Bugfix: MP3s tagged with iTunes have an invalid comment field + frame name ('COM ' - should be 'COMM') but the data is valid + otherwise; the frame is now renamed to 'COMM' and parsed + normally (with the error noted in ['error']) + (thanks kheller2Ømac*com for the sample file) + * Bugfix: Some ASF/WMA audio files were not being identified as + any format (thanks ahØartemis*dk) + * Bugfix: Warning now generated and ASCII format assumed for + invalid text encoding values in ID3v2 + * Bugfix: Changed ZIP detection pattern from 'PK' to 'PK\x04\x03' + * Bugfix: Ogg/FLAC files with large Vorbis comments were dying in + an infinite loop with lots of error messages due to missing $fd + parameter on ParseVorbisComments() (thanks ahØartemis*dk) + * Bugfix: ['data'] and ['image_mime'] were being returned for all + Ogg comments even if they were not images for versions of PHP + that have image_type_to_mime_type() built in (ie PHP 4.3.0+) + + +1.5.6: [December-31-2002] James Heinrich + » Added support for NSV (Nullsoft Streaming Video) + (www.nullsoft.com/nsv/) + (thanks demonØsoundplanet*com for the idea) + New file: getid3.nsv.php + » Added support for CD-audio track files (track01.cda etc) + ¤ Added standard ['frame_rate'] root value when known (AVI, NSV, + MPEG-video) + ¤ ASF files now report ['fileformat'] of: + 'wmv' when Windows Media Video codec v7/v8/v9 is used; + 'wma' when any 'Windows Media Audio' named audio codec is used + and no video stream is present; + 'asf' in all other cases (audio-only, video-only, or both) + ¤ Removed support for ZIP functions (will be rewritten to not + require ZZIPlib support in future versions) + ¤ Added function SafeStripSlashes() as a drop-in replacement for + stripslashes(), but that only strips slashes if magic_quotes_gpc + is set + ¤ Removed support for remote file scanning (HTTP / FTP) + ¤ Added ['aac']['frames'] (number of AAC frames in file) + ¤ Added ['mpeg']['audio']['frame_count'] when a bitrate histogram + is created + ¤ Average bitrate for VBR MP3/MP2 is calculated from actual counts + of frames of various bitrates (rather than relying on the header + values or filesize) when a bitrate histogram is created + ¤ RecursiveFrameScanning() split out into seperate function + (getid3.mp3.php) + ¤ Removed old function getMP3header() from getid3.mp3.php + ¤ Changed default MPEG_VALID_CHECK_FRAMES (number of mp3 frames + scanned to ensure a valid audio sequence has been located) from + 10 to 25. This means scanning will be slightly slower, but more + reliable/accurate + * Bugfix: ID3v2.2 - valid frame names not correctly detected + (thanks maeckerØweb*de for the sample file) + * Bugfix: ID3v2.2 - valid padding not correctly detected + (thanks maeckerØweb*de for the sample file) + * Bugfix: MIDI files with flat key signatures were not being + correctly reported (thanks alexleeisØshaw*ca for sample file) + * Bugfix: now returns message in ['error'] if file does not exist + * Bugfix: ['RIFF']['video'][x]['codec'] wasn't always being + correctly populated + * Bugfix: ['bitrate'] was incorrect for multi-stream RealMedia + * Bugfix: ['playtime_seconds'] was sometimes null or incorrect + for multi-stream RealMedia + * Bugfix: ChannelTypeID was incorrect in RVA2 ID3v2.4 frames + * Bugfix: Fixed potential divide-by-zero error for corrupt FLAC + files (thanks ahØartemis*dk) + * Bugfix: AAC-ADTS was not returning ['bitrate_mode'] unless + $ReturnExtendedInfo was TRUE (thanks ahØartemis*dk) + * Bugfix: LAME-encoded CBR MP3s now properly identified as CBR + with correct bitrate (thanks ahØartemis*dk) + * Bugfix: VBR MP2 (or headerless MP3) is now identified as VBR + rather than CBR. Note: to obtain VBR bitrate for headerless + files, the entire file is scanned and a histogram distribution + of bitrates is created, and the average bitrate calculated from + that. (thanks ahØartemis*dk for sample file) + Added support for DSIZ chunks in VQF, and checks to make sure size + of audio data matches DSIZ value, if present + (thanks ahØartemis*dk for sample file) + Rewrote GetAllMP3info() - removed some unneccesary code, changed + format-detection routine from ParseAsThisFormat() to + GetFileFormat() to allow for more flexible format parsing + (needed for ISO CD-ROM images, helpful for Quicktime and others) + Changed references in all files from string-cast indexes: ["$i"] + to non-cast indexes: [$i] where appropriate + Put a sans-serif 9pt style on all text in getid3.check.php + getAACADTSheaderFilepointer() now return TRUE if synch is lost + after the first frame has been successfully parsed (previously + it would return FALSE if synch was lost at any time, meaning the + file is most likely MP3, which was incorrect) + (thanks ahØartemis*dk for sample file) + Speed improvement code changes to getid3.mp3.php (up to 24% faster + in some cases) (thanks ahØartemis*dk for the code) + Changed all include_once() to require_once() + + +1.5.5: [November-25-2002] James Heinrich + » Added support for La (Lossless Audio - www.lossless-audio.com) + (thanks ahØartemis*dk for the idea) + New file: getid3.la.php + ¤ Moved lookup functions from getid3.lookup.php to the files where + they are used. + New file: getid3.id3.php + New file: getid3.rgad.php + Removed file: getid3.lookup.php + ¤ getID3v1Filepointer() returns FALSE if ID3v1 tag not found + ¤ Added new paramter "ReturnExtendedInfo" to the function + getAACADTSheaderFilepointer() in getid3.aac.php which now + defaults to FALSE - if TRUE then the data for every frame is + returned (containing aac_frame_length, adts_buffer_fullness and + num_raw_data_blocks, which aren't usually very useful). Speed + improvement with FALSE is about 35%. + ¤ Now returns fopen() errors in ['error'], for example if a remote + file is not accessible. + ¤ Changed default number of MP3 audio frames to scan to determine + if a valid stream has been found from 5 to 10, now also defined + as a constant at the top of getid3.mp3.php This will result in + slightly slower MP3 parsing, but greater reliability in + detecting false/invalid/corrupted VBR headers. + ¤ fopen() errors now displayed in getid3.putid3.php + (thanks miguel.dieckmannØhamburg*de) + ¤ Added 4th parameter to decodeMPEGaudioHeader() $ScanAsCBR which + will force an MP3 audio frame sequence to be force-scanned in + CBR mode. You should never need to call this directly, it's only + used internally to scan for MP3 files that have an illegal VBR + header with CBR data. (thanks fletchØpobox*com) + * Bugfix: ASF_Marker_Object in getid3.asf.php was always returning + an error in non-existant "reserved_1" and failing + * Bugfix: VBR bitrate calculations in getid3.mp3.php only occur if + ['mpeg']['audio']['VBR_frames'] is defined. + (thanks fletchØpobox*com) + * Bugfix: getid3.putid3.php no longer deletes original MP3 if + ID3v2 tag writing fails (thanks miguel*dieckmannØhamburg*de) + * Bugfix: incorrect order of if-statement error messages in + getid3.putid3.php (thanks miguel*dieckmannØhamburg*de) + getid3.asf.php now notes the error and continues parsing rather + than failing when it encounters an error parsing a chunk + Now actually scan 1000 frames for AAC ADTS as reported in the + v1.5.4 changelog, rather than 100. (thanks ahØartemis*dk) + Improved scanning speed in getAACADTSheaderFilepointer() by ~30% + (thanks ahØartemis*dk for the fix) + Added FileSizeNiceDisplay() function to getid3.functions.php for + formatting filesize output in kB, MB, GB, etc. + + +1.5.4: [October-07-2002] James Heinrich + » Added support for Quicktime. + New file: getid3.quicktime.php + » Added support for AAC files, both ADTS and ADIF header formats. + ADIF format is a pain because it's very similar to standard MP3 + header format, and it's hard to distinguish between the two. I + have tried to make the detection accurate, but I have a limited + number of AAC test files to play with so if you have an AAC file + that gets detected as MP3/MP2 (or vice-versa), please send me + the details via email at infoØgetid3Øorg + ADTS format is very slow to parse because to get the bitrate of + VBR files the whole file has to be stepped through frame by + frame (getID3() scans up to the first 1000 frames and assumes + that to be close enough). + Note: I would suggest commenting out support for AAC (see top of + GetAllMP3info() function in getid3.php) unless you need it. + (thanks jfaulØgmx*de for the idea and sample Delphi source code) + New file: getid3.aac.php + » Added bitrate distribution analysis option for MP3 VBR files. A + new boolean parameter for getOnlyMPEGaudioInfo() enabled this + feature which steps through the MP3 file frame by frame and + counts how many frames of each bitrate exist. This information + is returned in ['mpeg']['audio']['bitrate_distribution'] + Caution: this feature is very inefficient for large files and + takes a very long time and does lots of disk I/O. Use with care. + ¤ Changed layout of allowedFormats in GetAllMP3info() function in + getid3.php to allow easy removal of support for any of the + supported format. As stated above, I recommend commenting out + AAC unless needed. + ¤ Added ['flac']['compressed_audio_bytes'], + ['flac']['uncompressed_audio_bytes'], and + ['flac']['compression_ratio'] + ¤ Replaced FXPT2DOT30toFloat() function with FixedPoint2_30() + * Bugfix: getid3.mpc.php was slightly miscalculating the number of + samples, therefore also bitrate and playtime + (thanks ahØartemis*dk for the fix) + * Bugfix: MonkeyCompressionLevelNameLookup() didn't know about + 'insane' compression (thanks ahØartemis*dk for the fix) + * Bugfix: MonkeySamplesPerFrame() was incorrect for MAC v3.95+ + (thanks ahØartemis*dk for the fix) + * Bugfix: getid3.check.php wasn't processing the assumeFormat + directive when (register_globals == off) + * Bugfix: detecting of synch pattern for MP3 files with invalid + data at the beginning wasn't always correct, also meant possible + incorrect bitrate/duration/etc info for such corrupt files. + getid3.functions.php now includes a replacement utf8_decode() + function for those PHP installations that are not configured + with the --with-xml option. (thanks stephaneØtekartists*com) + + +1.5.3: [September-30-2002] James Heinrich + » Added support for VQF. (thanks mtØmansonthomas*com for the idea) + New file: getid3.vqf.php + » Added support for FLAC. Comments, if present, are returned under + ['ogg'] because they follow the Ogg Vorbis structure standard. + New file: getid3.flac.php + ¤ OS/2-format bitmaps are now correctly interpreted. The format of + the bitmap is now returned in ['bmp']['type_os'] and + ['bmp']['type_version']. OS/2 bitmaps can be v1 or v2, Windows + can be v1, v4 or v5 + + +1.5.2: [September-25-2002] James Heinrich + » Support for RealMedia (audio & video) added + Note: only tested on G2 and v5 audio and video files - if anyone + has older and/or newer sample files, please test it and/or send + me the sample files. + (thanks stephaneØtekartists*com for idea) + New file: getid3.real.php + » Support for BMP added. Palette and pixel data can optionally be + extracted as well - this is slow and generally unneccesary, but + the option is there if you need it. Also includes PlotBMP() + which will take the extracted pixel data and output it as a true + color PNG. This function requires GD v2.0+ + Note: Untested on 16-bit and 32-bit BMPs because I couldn't find + any sample files - if you know of a program that can create such + files, please email infoØgetid3Øorg + Note: Support for RGB (uncompressed), RLE8 and RLE4 is included + and tested. BITFIELDS support is also included for 16- & 32-bit + formats, but it's untested, so if anybody has any test files + please send them to infoØgetid3Øorg + Note: Support currently only for Windows-format BMPs, and trying + to parse an OS/2-format bitmap leads to unpredictable/invalid + results. + New file: getid3.bmp.php + » PNG now fully parsed, including all information chunks + New file: getid3.png.php + ¤ Support for GIF/JPG/PNG moved to seperate files and expanded, + including standard ['resolution_x'] and ['resolution_y'] as well + as more thorough parsing of header information + New file: getid3.gif.php + New file: getid3.jpg.php + table_var_dump() simplified and now outputs {-style character + entities for characters outside the normal alphanumeric range + CleanOggCommentName() changed to a regular expression + (thanks chris-getid3Øbolt*cx for rewriting the function) + + +1.5.1: [September-20-2002] James Heinrich + » Added support for MPEGplus/Musepack SV7. ['fileformat'] is 'SV7' + for version 7 files (versions 4, 5 ,6 and 8 are not supported + yet, but will be of ['fileformat'] SV4, SV5, SV6 and SV8) when + they are supported (thanks Christian Fritz for the idea) + New file: getid3.mpc.php + ¤ ['bitrate_audio'], ['bitrate_video'], ['bitrate_mode'], + ['channels'], ['resolution_x'], and ['resolution_y'] keys added + for all appropriate formats + ¤ Ogg files with a COVERART comment now save and display the + attached image the same way as is done with ID3v2 APICs + ¤ ['ogg']['comments'][n]['data'] and + ['ogg']['comments'][n]['dataoffset'] is now returned for all + comments. ['ogg']['comments'][n]['data'] is only useful if + the field is supposed to contain binary data. It is a + base64_decode()'d version of ['value']. + ['ogg']['comments'][n]['dataoffset'] is the byte offset in the + file at which the 'COMMENTNAME=value string' starts, not the + start of just 'value' + ¤ ['ogg']['comments'][n]['image_mime'] is now returned if + ['ogg']['comments'][n]['data'] contains valid image data. + ¤ More than 3 Ogg pages may now be read in, if the comment data + is longer than 1 page (usually about 4kB) + ¤ ['fileformat'] is now 'mp2' rather than 'mp3' if it's MPEG-1, + Layer-II audio + ¤ ASF bitrates now calculated even if stream_bitrate_properties + object not present + ¤ ['asf']['stream_properties_object'] is now a numeric-key array + with one entry for each stream - the key being the stream number + ¤ ['replay_gain'] is returned for all audio formats that support + it (MP3-LAME, ID3v2, Ogg) (thanks Christian Fritz for the idea) + ¤ ['mpeg']['audio']['LAME']['RGAD']['radio_replay_gain'] is now + ['mpeg']['audio']['LAME']['RGAD']['radio'] (same for audiophile) + ¤ ASF/WMA files now use WM/Track to get track number from if + WM/TrackNumber is not available (thanks stephaneØtekartists*com) + ¤ ASF/WMV files now returns ['year'] and ['asf']['year'] + ¤ ASV/WMV files now use ['content_description']['description'] for + the ['comment'] field (thanks stephaneØtekartists*com) + ¤ ['track'] is now always returned as an integer + * Bugfix: Ogg comments that are larger than one data page (usually + about 4kB) are now correctly parsed (thanks Christian Fritz) + * Bugfix: Ogg comment data is now UTF8-decoded + * Bugfix: Ogg comment writing now UTF8-encodes the data + * Bugfix: playtime for ASF files was off by (usually + between 3 and 12 seconds) + * Bugfix: ['asf']['stream_properties_objects']['flags'] data was + possibly incorrect + * Bugfix: ASF Padding Object was overwriting + Stream Bitrate Properties Object data (now returned correctly in + ['asf']['padding_object'] + * Bugfix: ASF Marker Object Reserved_2 field was incorrect + * Bugfix: ASF Bitrate Mutual Exclusion Object had incorrect stream + numbers + Warning displayed if incorrectly-formatted Ogg comment is present + (known to be an issue with CDex v1.40, but fixed by v1.50b7) + (thanks Christian Fritz) + Ogg comment writing now checks for valid comment names + Added bitrate column in getid3.check.php, and added some formatting + (font, colour) + Performance tweaks using bitwise math instead of binary string + operations + + +1.5.0: [September-18-2002] James Heinrich + » Ogg comment writing support added. getid3.write.php has been + updated to allow for writing comment tags to both MP3 and Ogg. + Big thanks to Chris Bolt for writing the + OggWrite() function and offering it for inclusion in getID3() + New file: getid3.ogginfo.php + » Support for Monkey's Audio and APE tag added. + (thanks Christian Fritz for the idea) + New file: getid3.ape.php + ['fileformat'] now returns 'mac' for Monkey's Audio files, or + 'ape' for files with an APE tag (Monkey's Audio or other format) + » getid3.thumbnail.php has been removed from the distribution and + the table_var_dump() function now outputs APICs as seperate + files in the same directory as the analyzed file. This should + make the image-displaying more reliable as well as reduce + complexity. The naming convention for the images is + filename.ext.[byte offset of APIC data].[jpg|gif|png] + If anybody still has any problems with corrupted images please + let me know at infoØgetid3Øorg + » Support for extended Xing/LAME tag + (see http://users.belgacom.net/gc247244/extra/tag.html) + Data is returned in ['mpeg']['audio']['LAME'] + ¤ ['ogg']['tracknumber'] has been renamed to ['ogg']['track'] and + ['track'] is now returned in the root of the array + ¤ ['ogg']['pageheader'][n]['flag'] has been renamed to + ['ogg']['pageheader'][n]['flags'] and the unprocessed flag byte + is available in ['ogg']['pageheader'][n]['flags_raw'] + ¤ ['frequency'] is now returned for WAVE files in the root of the + array (thanks danielØelectroteque*org) + ¤ ASF files now return codec, bitrate, resolution, etc information + under ['asf']['video_media'] or ['asf']['audio_media'] + * Bugfix: RVA2 and EQU2 writing in getid3.putid3.php were + incorrectly writing Volume Adjustment field + * Bugfix: EQU2 in getid3.frames.php was reading Volume Adjustment + as unsigned integer instead of signed integer + * Bugfix: handling of remote files over HTTP & FTP was broken + (thanks Vince) + * Bugfix: incorrect handling of some ASF packets + ASF/Windows Media format now more fully parsed, including Index + Objects + Added several new fourCC video codecs + + +1.4.3: [September-15-2002] James Heinrich + » Now parses ASF / WMV / WMA files + ¤ New file: getid3.asf.php + * Bugfix: RoughTranslateUnicodeToASCII() would return nothing + if didn't find a terminator it was expecting + Added FILETIMEtoUNIXtime() function (for converting 64-bit + Microsoft FILETIME timestamps, used in ASF files and elsewhere, + to UNIX Epoch timestamps) + Added GUIDtoBytestring() and BytestringToGUID() functions + + +1.4.2: [September-12-2002] James Heinrich + » getID3() now requires PHP v4.1.0 or higher because it now is + designed to work with register_globals = off and the new auto- + globals ($_GET, $_SERVER, etc). + * Bugfix: VBR MP3 files with Fraunhofer-style VBR header were not + being correctly detected in most cases + (thanks dkushnerØoddcast*com and mikeØftl*com for sample files) + * Bugfix: IsValidTextEncoding() was broken + * Bugfix: Add stripslashes($EditorFilename) to getid3.write.php + (writing was broken for files with ' or " in the filename) + (thanks mikeØftl*com and kthejoker) + * Bugfix: If there is garbage data between a valid VBR header + frame and a sequence of valid MPEG-audio frames the VBR data is + no longer discarded. (thanks to mikeØftl*com for sample + Fraunhofer-style VBR file produced with MusicMatch v7.2) + ¤ Changed variable system to work with (register_globals = off) + ¤ Moved relevant code into seperate PlaytimeString() function + ¤ Added nl2br() to table_var_dump() for cleaner output + ¤ Now returns the following keys from Fraunhofer-VBR files: + ['VBR_seek_offsets'], ['VBR_seek_offsets_stride'], + ['VBR_offsets_relative'] and ['VBR_offsets_absolute'] + ¤ Added ID3v1matchesID3v2() function and implemented in + getid3.check.php (thanks to "Guest" in the forums for the idea) + Changed amount of data read in getid3.getimagesize.php from 10kB + to entire file. (thanks mikeØftl*com) + Wrapped function_exists() checks around function definitions in + getid3.functions.php + Fixed a lot of E_WARNING and E_NOTICE situations, especially in + ID3-writing code (getid3.putid3.php, etc) + Added checks to make sure all needed data is available for writing + ID3v2 tags + + +1.4.1b5: [May-30-2002] James Heinrich + * Bugfix: Unsynchronise() was broken, now fixed + (thanks mikeØftl*com) + * Bugfix: GenerateID3v2Tag() now correctly uses non-synchsafe + integers for frame size descriptors in ID3v2.3 and ID3v2.2 + (thanks mikeØftl*com) + ¤ Added ['artist'], ['title'], etc keys to root of returned + array to provide a common place to access any returned info + from any file type. Currently gets info from ID3v1, ID3v2, + Ogg, and RIFF/WAVE. Possible returned keys are: + title, artist, album, year, genre, comment, track + ¤ Modified LookupGenre() function to search for either genre based + on numeric ID, or now reverse lookup as well + ¤ Added ['artist'], ['title'], etc keys to ['RIFF'] information + if info tags are present + Added functionality to attach a picture to the ID3v2 tag in + getid3.write.php + Sorted genres into alphabetical order (special 3 at end of list) + in getid3.write.php + Changed the comment-edit field in getid3.write.php to a multi-line + '; + + echo 'Picture
(ID3v2 only)
'; + echo ''; + echo ' '; + echo ''; + + } else { + + echo 'Error'.htmlentities($Filename).' does not exist'; + + } + echo ''; + echo ''; + +} + +echo ''; \ No newline at end of file diff --git a/app/library/getid3/demos/demo.zip.php b/app/library/getid3/demos/demo.zip.php new file mode 100755 index 00000000..1aaeef7f --- /dev/null +++ b/app/library/getid3/demos/demo.zip.php @@ -0,0 +1,101 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// +// // +// /demo/demo.zip.php - part of getID3() // +// Sample script how to use getID3() to decompress zip files // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + + +function UnzipFileContents($filename, &$errors) { + $errors = array(); + $DecompressedFileContents = array(); + if (!class_exists('getID3')) { + $errors[] = 'class getID3 not defined, please include getid3.php'; + } elseif (include_once('module.archive.zip.php')) { + $getid3 = new getID3(); + $getid3->info['filesize'] = filesize($filename); + ob_start(); + if ($getid3->fp = fopen($filename, 'rb')) { + ob_end_clean(); + $getid3_zip = new getid3_zip($getid3); + $getid3_zip->analyze($filename); + if (($getid3->info['fileformat'] == 'zip') && !empty($getid3->info['zip']['files'])) { + if (!empty($getid3->info['zip']['central_directory'])) { + $ZipDirectoryToWalk = $getid3->info['zip']['central_directory']; + } elseif (!empty($getid3->info['zip']['entries'])) { + $ZipDirectoryToWalk = $getid3->info['zip']['entries']; + } else { + $errors[] = 'failed to parse ZIP attachment "'.$piece_filename.'" (no central directory)
'; + fclose($getid3->fp); + return false; + } + foreach ($ZipDirectoryToWalk as $key => $valuearray) { + fseek($getid3->fp, $valuearray['entry_offset'], SEEK_SET); + $LocalFileHeader = $getid3_zip->ZIPparseLocalFileHeader($getid3->fp); + if ($LocalFileHeader['flags']['encrypted']) { + // password-protected + $DecompressedFileContents[$valuearray['filename']] = ''; + } else { + fseek($getid3->fp, $LocalFileHeader['data_offset'], SEEK_SET); + $compressedFileData = ''; + while ((strlen($compressedFileData) < $LocalFileHeader['compressed_size']) && !feof($getid3->fp)) { + $compressedFileData .= fread($getid3->fp, 32768); + } + switch ($LocalFileHeader['raw']['compression_method']) { + case 0: // store - great, just copy data unchanged + $uncompressedFileData = $compressedFileData; + break; + + case 8: // deflate + ob_start(); + $uncompressedFileData = gzinflate($compressedFileData); + $gzinflate_errors = trim(strip_tags(ob_get_contents())); + ob_end_clean(); + if ($gzinflate_errors) { + $errors[] = 'gzinflate() failed for file ['.$LocalFileHeader['filename'].']: "'.$gzinflate_errors.'"'; + continue 2; + } + break; + + case 1: // shrink + case 2: // reduce-1 + case 3: // reduce-2 + case 4: // reduce-3 + case 5: // reduce-4 + case 6: // implode + case 7: // tokenize + case 9: // deflate64 + case 10: // PKWARE Date Compression Library Imploding + $DecompressedFileContents[$valuearray['filename']] = ''; + $errors[] = 'unsupported ZIP compression method ('.$LocalFileHeader['raw']['compression_method'].' = '.$getid3_zip->ZIPcompressionMethodLookup($LocalFileHeader['raw']['compression_method']).')'; + continue 2; + + default: + $DecompressedFileContents[$valuearray['filename']] = ''; + $errors[] = 'unknown ZIP compression method ('.$LocalFileHeader['raw']['compression_method'].')'; + continue 2; + } + $DecompressedFileContents[$valuearray['filename']] = $uncompressedFileData; + unset($compressedFileData); + } + } + } else { + $errors[] = $filename.' does not appear to be a zip file'; + } + } else { + $error_message = ob_get_contents(); + ob_end_clean(); + $errors[] = 'failed to fopen('.$filename.', rb): '.$error_message; + } + } else { + $errors[] = 'failed to include_once(module.archive.zip.php)'; + } + return $DecompressedFileContents; +} diff --git a/app/library/getid3/demos/getid3.css b/app/library/getid3/demos/getid3.css new file mode 100755 index 00000000..0f5b730a --- /dev/null +++ b/app/library/getid3/demos/getid3.css @@ -0,0 +1,171 @@ +/** +* Common elements +*/ + +body { + font: 12px Verdana, sans-serif; + background-color: white; + color: black; + margin-top: 6px; + margin-bottom: 30px; + margin-left: 12px; + margin-right: 12px; +} + +h1 { + font: bold 18px Verdana, sans-serif; + line-height: 26px; + margin-top: 12px; + margin-bottom: 15px; + margin-left: 0px; + margin-right: 7px; + background-color: #e6eaf6; + padding-left: 10px; + padding-top: 2px; + padding-bottom: 4px; +} + +h3 { + font: bold 13px Verdana, sans-serif; + line-height: 26px; + margin-top: 12px; + margin-bottom: 0px; + margin-left: 0px; + margin-right: 7px; + padding-left: 4px; +} + +ul { + margin-top: 0px; +} + +p, li { + font: 9pt/135% sans-serif; + margin-top: 1x; + margin-bottom: 0x; +} + +a, a:link, a:visited { + color: #0000cc; +} + +hr { + height: 0; + border: solid gray 0; + border-top-width: thin; + width: 700px; +} + +table.table td { + font: 9pt sans-serif; + padding-top: 1px; + padding-bottom: 1px; + padding-left: 5px; + padding-right: 5px; +} + +table.table td.header { + background-color: #cccccc; + padding-top: 2px; + padding-bottom: 2px; + font-weight: bold; +} + +table.table tr.even_files { + background-color: #fefefe; +} + +table.table tr.odd_files { + background-color: #e9e9e9; +} + +table.dump { + border-top: solid 1px #cccccc; + border-left: solid 1px #cccccc; + margin: 2px; +} + +table.dump td { + font: 9pt sans-serif; + padding-top: 1px; + padding-bottom: 1px; + padding-left: 5px; + padding-right: 5px; + border-right: solid 1px #cccccc; + border-bottom: solid 1px #cccccc; +} + +td.dump_string { + font-weight: bold; + color: #006600; + font-family: Zawgyi-One,sans-serif; +} + +td.dump_integer { + color: #CC0000; + font-weight: bold; +} + +td.dump_double { + color: #FF9900; + font-weight: bold; +} + +td.dump_boolean { + color: #0000FF; + font-weight: bold; +} + +.error { + color: red +} + + +/** +* Tool Tips +*/ + +.tooltip { + font: 9pt sans-serif; + background: #ffffe1; + color: black; + border: black 1px solid; + margin: 2px; + padding: 7px; + position: absolute; + top: 10px; + left: 10px; + z-index: 10000; + visibility: hidden; +} + +.tooltip p { + margin-top: -2px; + margin-bottom: 4px; +} + + +/** +* Forms +*/ + +table.form td { + font: 9pt/135% sans-serif; + padding: 2px; +} + +select, input { + font: 9pt/135% sans-serif; +} + +.select, .field { + width: 260px; +} + +#sel_field { + width: 85px; +} + +.button { + margin-top: 10px; +} diff --git a/app/library/getid3/demos/getid3.demo.dirscan.php b/app/library/getid3/demos/getid3.demo.dirscan.php new file mode 100755 index 00000000..bfb7c9db --- /dev/null +++ b/app/library/getid3/demos/getid3.demo.dirscan.php @@ -0,0 +1,258 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////////////////////// +/// // +// getid3.dirscan.php - tool for batch media file processing with getID3() // +// /// +///////////////////////////////////////////////////////////////////////////////// +/// // +// Directory Scanning and Caching CLI tool by Karl G. Holz // +// /// +///////////////////////////////////////////////////////////////////////////////// +/** +* This is a directory scanning and caching cli tool for getID3(). +* +* use like so for the default sqlite3 database, which is hidden: +* +* cd +* php /getid3.dirscan.php +* +* or +* +* php /getid3.dirscan.php +* +* Supported Cache Types (this extension) +* +* SQL Databases: +* +* cache_type +* ------------------------------------------------------------------- +* mysql + +$cache='mysql'; +$database['host']=''; +$database['database']=''; +$database['username']=''; +$database['password']=''; +$database['table']=''; + +* sqlite3 + +$cache='sqlite3'; +$database['table']='getid3_cache'; +$database['hide']=true; + +*/ +$dir = $_SERVER['PWD']; +$media = array('mp4', 'm4v', 'mov', 'mp3', 'm4a', 'jpg', 'png', 'gif'); +$database = array(); +/** +* configure the database bellow +*/ +// sqlite3 +$cache = 'sqlite3'; +$database['table'] = 'getid3_cache'; +$database['hide'] = true; +/** + * mysql +$cache = 'mysql'; +$database['host'] = ''; +$database['database'] = ''; +$database['username'] = ''; +$database['password'] = ''; +$database['table'] = ''; +*/ + +/** +* id3 tags class file +*/ +require_once(dirname(__FILE__).'/getid3.php'); +/** +* dirscan scans all directories for files that match your selected filetypes into the cache database +* this is useful for a lot of media files +* +* +* @package dirscan +* @author Karl Holz +* +*/ + +class dirscan { + /** + * type_brace() * Might not work on Solaris and other non GNU systems * + * + * Configures a filetype list for use with glob searches, + * will match uppercase or lowercase extensions only, no mixing + * @param string $dir directory to use + * @param mixed cvs list of extentions or an array + * @return string or null if checks fail + */ + private function type_brace($dir, $search=array()) { + $dir = str_replace(array('///', '//'), array('/', '/'), $dir); + if (!is_dir($dir)) { + return null; + } + if (!is_array($search)) { + $e = explode(',', $search); + } elseif (count($search) < 1) { + return null; + } else { + $e = $search; + } + $ext = array(); + foreach ($e as $new) { + $ext[] = strtolower(trim($new)); + $ext[] = strtoupper(trim($new)); + } + $b = $dir.'/*.{'.implode(',', $ext).'}'; + return $b; + } + + /** + * this function will search 4 levels deep for directories + * will return null on failure + * @param string $root + * @return array return an array of dirs under root + * @todo figure out how to block tabo directories with ease + */ + private function getDirs($root) { + switch ($root) { // return null on tabo directories, add as needed -> case {dir to block }: this is not perfect yet + case '/': + case '/var': + case '/etc': + case '/home': + case '/usr': + case '/root': + case '/private/etc': + case '/private/var': + case '/etc/apache2': + case '/home': + case '/tmp': + case '/var/log': + return null; + break; + default: // scan 4 directories deep + if (!is_dir($root)) { + return null; + } + $dirs = array_merge(glob($root.'/*', GLOB_ONLYDIR), glob($root.'/*/*', GLOB_ONLYDIR), glob($root.'/*/*/*', GLOB_ONLYDIR), glob($root.'/*/*/*/*', GLOB_ONLYDIR), glob($root.'/*/*/*/*/*', GLOB_ONLYDIR), glob($root.'/*/*/*/*/*/*', GLOB_ONLYDIR), glob($root.'/*/*/*/*/*/*/*', GLOB_ONLYDIR)); + break; + } + if (count($dirs) < 1) { + $dirs = array($root); + } + return $dirs; + } + + /** + * file_check() check the number of file that are found that match the brace search + * + * @param string $search + * @return mixed + */ + private function file_check($search) { + $t = array(); + $s = glob($search, GLOB_BRACE); + foreach ($s as $file) { + $t[] = str_replace(array('///', '//'), array('/', '/'), $file); + } + if (count($t) > 0) { + return $t; + } + return null; + } + + function getTime() { + return microtime(true); + // old method for PHP < 5 + //$a = explode(' ', microtime()); + //return (double) $a[0] + $a[1]; + } + + + /** + * + * @param type $dir + * @param type $match search type name extentions, can be an array or csv list + * @param type $cache caching extention, select one of sqlite3, mysql, dbm + * @param array $opt database options, + */ + function scan_files($dir, $match, $cache='sqlite3', $opt=array('table'=>'getid3_cache', 'hide'=>true)) { + $Start = self::getTime(); + switch ($cache) { // load the caching module + case 'sqlite3': + if (!class_exists('getID3_cached_sqlite3')) { + require_once(dirname(__FILE__)).'/extension.cache.sqlite3.php'; + } + $id3 = new getID3_cached_sqlite3($opt['table'], $opt['hide']); + break; + case 'mysql': + if (!class_exists('getID3_cached_mysql')) { + require_once(dirname(__FILE__)).'/extension.cache.mysql.php'; + } + $id3 = new getID3_cached_mysql($opt['host'], $opt['database'], $opt['username'], $opt['password'], $opt['table']); + break; + // I'll leave this for some one else + //case 'dbm': + // if (!class_exists('getID3_cached_dbm')) { + // require_once(dirname(__FILE__)).'/extension.cache.dbm.php'; + // } + // die(' This has not be implemented, sorry for the inconvenience'); + // break; + default: + die(' You have selected an Invalid cache type, only "sqlite3" and "mysql" are valid'."\n"); + break; + } + $count = array('dir'=>0, 'file'=>0); + $dirs = self::getDirs($dir); + if ($dirs !== null) { + foreach ($dirs as $d) { + echo ' Scanning: '.$d."\n"; + $search = self::type_brace($d, $match); + if ($search !== null) { + $files = self::file_check($search); + if ($files !== null) { + foreach ($files as $f) { + echo ' * Analyzing '.$f.' '."\n"; + $id3->analyze($f); + $count['file']++; + } + $count['dir']++; + } else { + echo 'Failed to get files '."\n"; + } + } else { + echo 'Failed to create match string '."\n"; + } + } + echo '**************************************'."\n"; + echo '* Finished Scanning your directories '."\n*\n"; + echo '* Directories '.$count['dir']."\n"; + echo '* Files '.$count['file']."\n"; + $End = self::getTime(); + $t = number_format(($End - $Start) / 60, 2); + echo '* Time taken to scan '.$dir.' '.$t.' min '."\n"; + echo '**************************************'."\n"; + } else { + echo ' failed to get directories '."\n"; + } + } +} + +if (PHP_SAPI === 'cli') { + if (count($argv) == 2) { + if (is_dir($argv[1])) { + $dir = $argv[1]; + } + if (count(explode(',', $argv[2])) > 0) { + $media = $arg[2]; + } + } + echo ' * Starting to scan directory: '.$dir."\n"; + echo ' * Using default media types: '.implode(',', $media)."\n"; + dirscan::scan_files($dir, $media, $cache, $database); +} diff --git a/app/library/getid3/demos/index.php b/app/library/getid3/demos/index.php new file mode 100755 index 00000000..89120759 --- /dev/null +++ b/app/library/getid3/demos/index.php @@ -0,0 +1,19 @@ + +getID3 demos + +In this directory are a number of examples of how to use getID3().
+If you don't know what to run, take a look at demo.browse.php +
+Other demos: + + \ No newline at end of file diff --git a/app/library/getid3/dependencies.txt b/app/library/getid3/dependencies.txt new file mode 100755 index 00000000..18f576eb --- /dev/null +++ b/app/library/getid3/dependencies.txt @@ -0,0 +1,25 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// +// // +// dependencies.txt - part of getID3() // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +lyrics3 depends on apetag (optional) +ogg depends on flac +id3v2 depends on id3v1 +apetag depends on id3v1 (optional, writing only) +bonk depends on id3v2 (optional) +riff depends on mp3 +mpeg depends on mp3 +quicktime depends on mp3 +flac depends on ogg +optimfrog depends on riff +la depends on riff +lpac depends on riff +asf depends on riff, id3v1 (optional) diff --git a/app/library/getid3/extension.cache.dbm.php b/app/library/getid3/getid3/extension.cache.dbm.php old mode 100644 new mode 100755 similarity index 93% rename from app/library/getid3/extension.cache.dbm.php rename to app/library/getid3/getid3/extension.cache.dbm.php index 9a88c22b..ada8d5bc --- a/app/library/getid3/extension.cache.dbm.php +++ b/app/library/getid3/getid3/extension.cache.dbm.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // // // extension.cache.dbm.php - part of getID3() // @@ -10,7 +11,7 @@ // /// ///////////////////////////////////////////////////////////////// // // -// This extension written by Allan Hansen // +// This extension written by Allan Hansen // // /// ///////////////////////////////////////////////////////////////// @@ -73,7 +74,7 @@ class getID3_cached_dbm extends getID3 { // public: constructor - see top of this file for cache type and cache_options - function getID3_cached_dbm($cache_type, $dbm_filename, $lock_filename) { + public function getID3_cached_dbm($cache_type, $dbm_filename, $lock_filename) { // Check for dba extension if (!extension_loaded('dba')) { @@ -135,13 +136,13 @@ class getID3_cached_dbm extends getID3 $this->clear_cache(); } - parent::getID3(); + parent::__construct(); } - // public: destuctor - function __destruct() { + // public: destructor + public function __destruct() { // Close dbm file dba_close($this->dba); @@ -156,7 +157,7 @@ class getID3_cached_dbm extends getID3 // public: clear cache - function clear_cache() { + public function clear_cache() { // Close dbm file dba_close($this->dba); @@ -178,7 +179,7 @@ class getID3_cached_dbm extends getID3 // public: analyze file - function analyze($filename) { + public function analyze($filename) { if (file_exists($filename)) { @@ -206,6 +207,3 @@ class getID3_cached_dbm extends getID3 } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/extension.cache.mysql.php b/app/library/getid3/getid3/extension.cache.mysql.php old mode 100644 new mode 100755 similarity index 60% rename from app/library/getid3/extension.cache.mysql.php rename to app/library/getid3/getid3/extension.cache.mysql.php index 1e1f91fa..2647584f --- a/app/library/getid3/extension.cache.mysql.php +++ b/app/library/getid3/getid3/extension.cache.mysql.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // // // extension.cache.mysql.php - part of getID3() // @@ -10,8 +11,8 @@ // /// ///////////////////////////////////////////////////////////////// // // -// This extension written by Allan Hansen // -// Table name mod by Carlo Capocasa // +// This extension written by Allan Hansen // +// Table name mod by Carlo Capocasa // // /// ///////////////////////////////////////////////////////////////// @@ -74,12 +75,12 @@ class getID3_cached_mysql extends getID3 { // private vars - var $cursor; - var $connection; + private $cursor; + private $connection; // public: constructor - see top of this file for cache type and cache_options - function getID3_cached_mysql($host, $database, $username, $password, $table='getid3_cache') { + public function getID3_cached_mysql($host, $database, $username, $password, $table='getid3_cache') { // Check for mysql support if (!function_exists('mysql_pconnect')) { @@ -105,38 +106,49 @@ class getID3_cached_mysql extends getID3 // Check version number and clear cache if changed $version = ''; - if ($this->cursor = mysql_query("SELECT `value` FROM `".mysql_real_escape_string($this->table)."` WHERE (`filename` = '".mysql_real_escape_string(getID3::VERSION)."') AND (`filesize` = '-1') AND (`filetime` = '-1') AND (`analyzetime` = '-1')", $this->connection)) { + $SQLquery = 'SELECT `value`'; + $SQLquery .= ' FROM `'.mysql_real_escape_string($this->table).'`'; + $SQLquery .= ' WHERE (`filename` = \''.mysql_real_escape_string(getID3::VERSION).'\')'; + $SQLquery .= ' AND (`filesize` = -1)'; + $SQLquery .= ' AND (`filetime` = -1)'; + $SQLquery .= ' AND (`analyzetime` = -1)'; + if ($this->cursor = mysql_query($SQLquery, $this->connection)) { list($version) = mysql_fetch_array($this->cursor); } if ($version != getID3::VERSION) { $this->clear_cache(); } - parent::getID3(); + parent::__construct(); } // public: clear cache - function clear_cache() { + public function clear_cache() { - $this->cursor = mysql_query("DELETE FROM `".mysql_real_escape_string($this->table)."`", $this->connection); - $this->cursor = mysql_query("INSERT INTO `".mysql_real_escape_string($this->table)."` VALUES ('".getID3::VERSION."', -1, -1, -1, '".getID3::VERSION."')", $this->connection); + $this->cursor = mysql_query('DELETE FROM `'.mysql_real_escape_string($this->table).'`', $this->connection); + $this->cursor = mysql_query('INSERT INTO `'.mysql_real_escape_string($this->table).'` VALUES (\''.getID3::VERSION.'\', -1, -1, -1, \''.getID3::VERSION.'\')', $this->connection); } // public: analyze file - function analyze($filename) { + public function analyze($filename) { if (file_exists($filename)) { // Short-hands $filetime = filemtime($filename); - $filesize = filesize($filename); + $filesize = filesize($filename); // Lookup file - $this->cursor = mysql_query("SELECT `value` FROM `".mysql_real_escape_string($this->table)."` WHERE (`filename` = '".mysql_real_escape_string($filename)."') AND (`filesize` = '".mysql_real_escape_string($filesize)."') AND (`filetime` = '".mysql_real_escape_string($filetime)."')", $this->connection); + $SQLquery = 'SELECT `value`'; + $SQLquery .= ' FROM `'.mysql_real_escape_string($this->table).'`'; + $SQLquery .= ' WHERE (`filename` = \''.mysql_real_escape_string($filename).'\')'; + $SQLquery .= ' AND (`filesize` = \''.mysql_real_escape_string($filesize).'\')'; + $SQLquery .= ' AND (`filetime` = \''.mysql_real_escape_string($filetime).'\')'; + $this->cursor = mysql_query($SQLquery, $this->connection); if (mysql_num_rows($this->cursor) > 0) { // Hit list($result) = mysql_fetch_array($this->cursor); @@ -149,7 +161,13 @@ class getID3_cached_mysql extends getID3 // Save result if (file_exists($filename)) { - $this->cursor = mysql_query("INSERT INTO `".mysql_real_escape_string($this->table)."` (`filename`, `filesize`, `filetime`, `analyzetime`, `value`) VALUES ('".mysql_real_escape_string($filename)."', '".mysql_real_escape_string($filesize)."', '".mysql_real_escape_string($filetime)."', '".mysql_real_escape_string(time())."', '".mysql_real_escape_string(base64_encode(serialize($analysis)))."')", $this->connection); + $SQLquery = 'INSERT INTO `'.mysql_real_escape_string($this->table).'` (`filename`, `filesize`, `filetime`, `analyzetime`, `value`) VALUES ('; + $SQLquery .= '\''.mysql_real_escape_string($filename).'\''; + $SQLquery .= ', \''.mysql_real_escape_string($filesize).'\''; + $SQLquery .= ', \''.mysql_real_escape_string($filetime).'\''; + $SQLquery .= ', \''.mysql_real_escape_string(time() ).'\''; + $SQLquery .= ', \''.mysql_real_escape_string(base64_encode(serialize($analysis))).'\')'; + $this->cursor = mysql_query($SQLquery, $this->connection); } return $analysis; } @@ -157,17 +175,16 @@ class getID3_cached_mysql extends getID3 // private: (re)create sql table - function create_table($drop=false) { + private function create_table($drop=false) { - $this->cursor = mysql_query("CREATE TABLE IF NOT EXISTS `".mysql_real_escape_string($this->table)."` ( - `filename` VARCHAR(255) NOT NULL DEFAULT '', - `filesize` INT(11) NOT NULL DEFAULT '0', - `filetime` INT(11) NOT NULL DEFAULT '0', - `analyzetime` INT(11) NOT NULL DEFAULT '0', - `value` TEXT NOT NULL, - PRIMARY KEY (`filename`,`filesize`,`filetime`)) TYPE=MyISAM", $this->connection); + $SQLquery = 'CREATE TABLE IF NOT EXISTS `'.mysql_real_escape_string($this->table).'` ('; + $SQLquery .= '`filename` VARCHAR(255) NOT NULL DEFAULT \'\''; + $SQLquery .= ', `filesize` INT(11) NOT NULL DEFAULT \'0\''; + $SQLquery .= ', `filetime` INT(11) NOT NULL DEFAULT \'0\''; + $SQLquery .= ', `analyzetime` INT(11) NOT NULL DEFAULT \'0\''; + $SQLquery .= ', `value` TEXT NOT NULL'; + $SQLquery .= ', PRIMARY KEY (`filename`, `filesize`, `filetime`)) ENGINE=MyISAM'; + $this->cursor = mysql_query($SQLquery, $this->connection); echo mysql_error($this->connection); } } - -?> \ No newline at end of file diff --git a/app/library/getid3/getid3/extension.cache.sqlite3.php b/app/library/getid3/getid3/extension.cache.sqlite3.php new file mode 100755 index 00000000..dd232390 --- /dev/null +++ b/app/library/getid3/getid3/extension.cache.sqlite3.php @@ -0,0 +1,265 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////////////////////// +/// // +// extension.cache.sqlite3.php - part of getID3() // +// Please see readme.txt for more information // +// /// +///////////////////////////////////////////////////////////////////////////////// +/// // +// MySQL extension written by Allan Hansen // +// Table name mod by Carlo Capocasa // +// MySQL extension was reworked for SQLite3 by Karl G. Holz // +// /// +///////////////////////////////////////////////////////////////////////////////// +/** +* This is a caching extension for getID3(). It works the exact same +* way as the getID3 class, but return cached information much faster +* +* Normal getID3 usage (example): +* +* require_once 'getid3/getid3.php'; +* $getID3 = new getID3; +* $getID3->encoding = 'UTF-8'; +* $info1 = $getID3->analyze('file1.flac'); +* $info2 = $getID3->analyze('file2.wv'); +* +* getID3_cached usage: +* +* require_once 'getid3/getid3.php'; +* require_once 'getid3/extension.cache.sqlite3.php'; +* // all parameters are optional, defaults are: +* $getID3 = new getID3_cached_sqlite3($table='getid3_cache', $hide=FALSE); +* $getID3->encoding = 'UTF-8'; +* $info1 = $getID3->analyze('file1.flac'); +* $info2 = $getID3->analyze('file2.wv'); +* +* +* Supported Cache Types (this extension) +* +* SQL Databases: +* +* cache_type cache_options +* ------------------------------------------------------------------- +* mysql host, database, username, password +* +* sqlite3 table='getid3_cache', hide=false (PHP5) +* + +*** database file will be stored in the same directory as this script, +*** webserver must have write access to that directory! +*** set $hide to TRUE to prefix db file with .ht to pervent access from web client +*** this is a default setting in the Apache configuration: + +# The following lines prevent .htaccess and .htpasswd files from being viewed by Web clients. + + + Order allow,deny + Deny from all + Satisfy all + + +******************************************************************************** +* +* ------------------------------------------------------------------- +* DBM-Style Databases: (use extension.cache.dbm) +* +* cache_type cache_options +* ------------------------------------------------------------------- +* gdbm dbm_filename, lock_filename +* ndbm dbm_filename, lock_filename +* db2 dbm_filename, lock_filename +* db3 dbm_filename, lock_filename +* db4 dbm_filename, lock_filename (PHP5 required) +* +* PHP must have write access to both dbm_filename and lock_filename. +* +* Recommended Cache Types +* +* Infrequent updates, many reads any DBM +* Frequent updates mysql +******************************************************************************** +* +* IMHO this is still a bit slow, I'm using this with MP4/MOV/ M4v files +* there is a plan to add directory scanning and analyzing to make things work much faster +* +* +*/ +class getID3_cached_sqlite3 extends getID3 { + + /** + * __construct() + * @param string $table holds name of sqlite table + * @return type + */ + public function __construct($table='getid3_cache', $hide=false) { + $this->table = $table; // Set table + $file = dirname(__FILE__).'/'.basename(__FILE__, 'php').'sqlite'; + if ($hide) { + $file = dirname(__FILE__).'/.ht.'.basename(__FILE__, 'php').'sqlite'; + } + $this->db = new SQLite3($file); + $db = $this->db; + $this->create_table(); // Create cache table if not exists + $version = ''; + $sql = $this->version_check; + $stmt = $db->prepare($sql); + $stmt->bindValue(':filename', getID3::VERSION, SQLITE3_TEXT); + $result = $stmt->execute(); + list($version) = $result->fetchArray(); + if ($version != getID3::VERSION) { // Check version number and clear cache if changed + $this->clear_cache(); + } + return parent::__construct(); + } + + /** + * close the database connection + */ + public function __destruct() { + $db=$this->db; + $db->close(); + } + + /** + * hold the sqlite db + * @var SQLite Resource + */ + private $db; + + /** + * table to use for caching + * @var string $table + */ + private $table; + + /** + * clear the cache + * @access private + * @return type + */ + private function clear_cache() { + $db = $this->db; + $sql = $this->delete_cache; + $db->exec($sql); + $sql = $this->set_version; + $stmt = $db->prepare($sql); + $stmt->bindValue(':filename', getID3::VERSION, SQLITE3_TEXT); + $stmt->bindValue(':dirname', getID3::VERSION, SQLITE3_TEXT); + $stmt->bindValue(':val', getID3::VERSION, SQLITE3_TEXT); + return $stmt->execute(); + } + + /** + * analyze file and cache them, if cached pull from the db + * @param type $filename + * @return boolean + */ + public function analyze($filename) { + if (!file_exists($filename)) { + return false; + } + // items to track for caching + $filetime = filemtime($filename); + $filesize = filesize($filename); + // this will be saved for a quick directory lookup of analized files + // ... why do 50 seperate sql quries when you can do 1 for the same result + $dirname = dirname($filename); + // Lookup file + $db = $this->db; + $sql = $this->get_id3_data; + $stmt = $db->prepare($sql); + $stmt->bindValue(':filename', $filename, SQLITE3_TEXT); + $stmt->bindValue(':filesize', $filesize, SQLITE3_INTEGER); + $stmt->bindValue(':filetime', $filetime, SQLITE3_INTEGER); + $res = $stmt->execute(); + list($result) = $res->fetchArray(); + if (count($result) > 0 ) { + return unserialize(base64_decode($result)); + } + // if it hasn't been analyzed before, then do it now + $analysis = parent::analyze($filename); + // Save result + $sql = $this->cache_file; + $stmt = $db->prepare($sql); + $stmt->bindValue(':filename', $filename, SQLITE3_TEXT); + $stmt->bindValue(':dirname', $dirname, SQLITE3_TEXT); + $stmt->bindValue(':filesize', $filesize, SQLITE3_INTEGER); + $stmt->bindValue(':filetime', $filetime, SQLITE3_INTEGER); + $stmt->bindValue(':atime', time(), SQLITE3_INTEGER); + $stmt->bindValue(':val', base64_encode(serialize($analysis)), SQLITE3_TEXT); + $res = $stmt->execute(); + return $analysis; + } + + /** + * create data base table + * this is almost the same as MySQL, with the exception of the dirname being added + * @return type + */ + private function create_table() { + $db = $this->db; + $sql = $this->make_table; + return $db->exec($sql); + } + + /** + * get cached directory + * + * This function is not in the MySQL extention, it's ment to speed up requesting multiple files + * which is ideal for podcasting, playlists, etc. + * + * @access public + * @param string $dir directory to search the cache database for + * @return array return an array of matching id3 data + */ + public function get_cached_dir($dir) { + $db = $this->db; + $rows = array(); + $sql = $this->get_cached_dir; + $stmt = $db->prepare($sql); + $stmt->bindValue(':dirname', $dir, SQLITE3_TEXT); + $res = $stmt->execute(); + while ($row=$res->fetchArray()) { + $rows[] = unserialize(base64_decode($row)); + } + return $rows; + } + + /** + * use the magical __get() for sql queries + * + * access as easy as $this->{case name}, returns NULL if query is not found + */ + public function __get($name) { + switch($name) { + case 'version_check': + return "SELECT val FROM $this->table WHERE filename = :filename AND filesize = '-1' AND filetime = '-1' AND analyzetime = '-1'"; + break; + case 'delete_cache': + return "DELETE FROM $this->table"; + break; + case 'set_version': + return "INSERT INTO $this->table (filename, dirname, filesize, filetime, analyzetime, val) VALUES (:filename, :dirname, -1, -1, -1, :val)"; + break; + case 'get_id3_data': + return "SELECT val FROM $this->table WHERE filename = :filename AND filesize = :filesize AND filetime = :filetime"; + break; + case 'cache_file': + return "INSERT INTO $this->table (filename, dirname, filesize, filetime, analyzetime, val) VALUES (:filename, :dirname, :filesize, :filetime, :atime, :val)"; + break; + case 'make_table': + return "CREATE TABLE IF NOT EXISTS $this->table (filename VARCHAR(255) NOT NULL DEFAULT '', dirname VARCHAR(255) NOT NULL DEFAULT '', filesize INT(11) NOT NULL DEFAULT '0', filetime INT(11) NOT NULL DEFAULT '0', analyzetime INT(11) NOT NULL DEFAULT '0', val text not null, PRIMARY KEY (filename, filesize, filetime))"; + break; + case 'get_cached_dir': + return "SELECT val FROM $this->table WHERE dirname = :dirname"; + break; + } + return null; + } + +} diff --git a/app/library/getid3/getid3.lib.php b/app/library/getid3/getid3/getid3.lib.php old mode 100644 new mode 100755 similarity index 72% rename from app/library/getid3/getid3.lib.php rename to app/library/getid3/getid3/getid3.lib.php index e736688e..efb3ba5e --- a/app/library/getid3/getid3.lib.php +++ b/app/library/getid3/getid3/getid3.lib.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // // // getid3.lib.php - part of getID3() // @@ -14,13 +15,13 @@ class getid3_lib { - static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') { + public static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') { $returnstring = ''; for ($i = 0; $i < strlen($string); $i++) { if ($hex) { $returnstring .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT); } else { - $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string{$i}) ? $string{$i} : '�'); + $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string{$i}) ? $string{$i} : '¤'); } if ($spaces) { $returnstring .= ' '; @@ -35,7 +36,7 @@ class getid3_lib return $returnstring; } - static function trunc($floatnumber) { + public static function trunc($floatnumber) { // truncates a floating-point number at the decimal point // returns int (if possible, otherwise float) if ($floatnumber >= 1) { @@ -45,14 +46,14 @@ class getid3_lib } else { $truncatednumber = 0; } - if (getid3_lib::intValueSupported($truncatednumber)) { + if (self::intValueSupported($truncatednumber)) { $truncatednumber = (int) $truncatednumber; } return $truncatednumber; } - static function safe_inc(&$variable, $increment=1) { + public static function safe_inc(&$variable, $increment=1) { if (isset($variable)) { $variable += $increment; } else { @@ -61,14 +62,14 @@ class getid3_lib return true; } - static function CastAsInt($floatnum) { + public static function CastAsInt($floatnum) { // convert to float if not already $floatnum = (float) $floatnum; // convert a float to type int, only if possible - if (getid3_lib::trunc($floatnum) == $floatnum) { + if (self::trunc($floatnum) == $floatnum) { // it's not floating point - if (getid3_lib::intValueSupported($floatnum)) { + if (self::intValueSupported($floatnum)) { // it's within int range $floatnum = (int) $floatnum; } @@ -92,20 +93,20 @@ class getid3_lib return false; } - static function DecimalizeFraction($fraction) { + public static function DecimalizeFraction($fraction) { list($numerator, $denominator) = explode('/', $fraction); return $numerator / ($denominator ? $denominator : 1); } - static function DecimalBinary2Float($binarynumerator) { - $numerator = getid3_lib::Bin2Dec($binarynumerator); - $denominator = getid3_lib::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator))); + public static function DecimalBinary2Float($binarynumerator) { + $numerator = self::Bin2Dec($binarynumerator); + $denominator = self::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator))); return ($numerator / $denominator); } - static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { + public static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html if (strpos($binarypointnumber, '.') === false) { $binarypointnumber = '0.'.$binarypointnumber; @@ -129,23 +130,23 @@ class getid3_lib } - static function Float2BinaryDecimal($floatvalue) { + public static function Float2BinaryDecimal($floatvalue) { // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html $maxbits = 128; // to how many bits of precision should the calculations be taken? - $intpart = getid3_lib::trunc($floatvalue); + $intpart = self::trunc($floatvalue); $floatpart = abs($floatvalue - $intpart); $pointbitstring = ''; while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) { $floatpart *= 2; - $pointbitstring .= (string) getid3_lib::trunc($floatpart); - $floatpart -= getid3_lib::trunc($floatpart); + $pointbitstring .= (string) self::trunc($floatpart); + $floatpart -= self::trunc($floatpart); } $binarypointnumber = decbin($intpart).'.'.$pointbitstring; return $binarypointnumber; } - static function Float2String($floatvalue, $bits) { + public static function Float2String($floatvalue, $bits) { // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html switch ($bits) { case 32: @@ -167,26 +168,26 @@ class getid3_lib } else { $signbit = '1'; } - $normalizedbinary = getid3_lib::NormalizeBinaryPoint(getid3_lib::Float2BinaryDecimal($floatvalue), $fractionbits); + $normalizedbinary = self::NormalizeBinaryPoint(self::Float2BinaryDecimal($floatvalue), $fractionbits); $biasedexponent = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent $exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT); $fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT); - return getid3_lib::BigEndian2String(getid3_lib::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false); + return self::BigEndian2String(self::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false); } - static function LittleEndian2Float($byteword) { - return getid3_lib::BigEndian2Float(strrev($byteword)); + public static function LittleEndian2Float($byteword) { + return self::BigEndian2Float(strrev($byteword)); } - static function BigEndian2Float($byteword) { + public static function BigEndian2Float($byteword) { // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic // http://www.psc.edu/general/software/packages/ieee/ieee.html // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html - $bitword = getid3_lib::BigEndian2Bin($byteword); + $bitword = self::BigEndian2Bin($byteword); if (!$bitword) { return 0; } @@ -209,8 +210,8 @@ class getid3_lib $exponentstring = substr($bitword, 1, 15); $isnormalized = intval($bitword{16}); $fractionstring = substr($bitword, 17, 63); - $exponent = pow(2, getid3_lib::Bin2Dec($exponentstring) - 16383); - $fraction = $isnormalized + getid3_lib::DecimalBinary2Float($fractionstring); + $exponent = pow(2, self::Bin2Dec($exponentstring) - 16383); + $fraction = $isnormalized + self::DecimalBinary2Float($fractionstring); $floatvalue = $exponent * $fraction; if ($signbit == '1') { $floatvalue *= -1; @@ -224,8 +225,8 @@ class getid3_lib } $exponentstring = substr($bitword, 1, $exponentbits); $fractionstring = substr($bitword, $exponentbits + 1, $fractionbits); - $exponent = getid3_lib::Bin2Dec($exponentstring); - $fraction = getid3_lib::Bin2Dec($fractionstring); + $exponent = self::Bin2Dec($exponentstring); + $fraction = self::Bin2Dec($fractionstring); if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) { // Not a Number @@ -245,12 +246,12 @@ class getid3_lib $floatvalue = ($signbit ? 0 : -0); } elseif (($exponent == 0) && ($fraction != 0)) { // These are 'unnormalized' values - $floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * getid3_lib::DecimalBinary2Float($fractionstring); + $floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * self::DecimalBinary2Float($fractionstring); if ($signbit == '1') { $floatvalue *= -1; } } elseif ($exponent != 0) { - $floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + getid3_lib::DecimalBinary2Float($fractionstring)); + $floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + self::DecimalBinary2Float($fractionstring)); if ($signbit == '1') { $floatvalue *= -1; } @@ -259,7 +260,7 @@ class getid3_lib } - static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { + public static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { $intvalue = 0; $bytewordlen = strlen($byteword); if ($bytewordlen == 0) { @@ -281,19 +282,19 @@ class getid3_lib $intvalue = 0 - ($intvalue & ($signMaskBit - 1)); } } else { - throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits ('.strlen($byteword).') in getid3_lib::BigEndian2Int()'); + throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits ('.strlen($byteword).') in self::BigEndian2Int()'); } } - return getid3_lib::CastAsInt($intvalue); + return self::CastAsInt($intvalue); } - static function LittleEndian2Int($byteword, $signed=false) { - return getid3_lib::BigEndian2Int(strrev($byteword), false, $signed); + public static function LittleEndian2Int($byteword, $signed=false) { + return self::BigEndian2Int(strrev($byteword), false, $signed); } - static function BigEndian2Bin($byteword) { + public static function BigEndian2Bin($byteword) { $binvalue = ''; $bytewordlen = strlen($byteword); for ($i = 0; $i < $bytewordlen; $i++) { @@ -303,15 +304,15 @@ class getid3_lib } - static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { + public static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { if ($number < 0) { - throw new Exception('ERROR: getid3_lib::BigEndian2String() does not support negative numbers'); + throw new Exception('ERROR: self::BigEndian2String() does not support negative numbers'); } $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF); $intstring = ''; if ($signed) { if ($minbytes > PHP_INT_SIZE) { - throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits in getid3_lib::BigEndian2String()'); + throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits in self::BigEndian2String()'); } $number = $number & (0x80 << (8 * ($minbytes - 1))); } @@ -324,7 +325,7 @@ class getid3_lib } - static function Dec2Bin($number) { + public static function Dec2Bin($number) { while ($number >= 256) { $bytes[] = (($number / 256) - (floor($number / 256))) * 256; $number = floor($number / 256); @@ -338,7 +339,7 @@ class getid3_lib } - static function Bin2Dec($binstring, $signed=false) { + public static function Bin2Dec($binstring, $signed=false) { $signmult = 1; if ($signed) { if ($binstring{0} == '1') { @@ -350,22 +351,22 @@ class getid3_lib for ($i = 0; $i < strlen($binstring); $i++) { $decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i); } - return getid3_lib::CastAsInt($decvalue * $signmult); + return self::CastAsInt($decvalue * $signmult); } - static function Bin2String($binstring) { + public static function Bin2String($binstring) { // return 'hi' for input of '0110100001101001' $string = ''; $binstringreversed = strrev($binstring); for ($i = 0; $i < strlen($binstringreversed); $i += 8) { - $string = chr(getid3_lib::Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string; + $string = chr(self::Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string; } return $string; } - static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { + public static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { $intstring = ''; while ($number > 0) { if ($synchsafe) { @@ -380,8 +381,8 @@ class getid3_lib } - static function array_merge_clobber($array1, $array2) { - // written by kc�hireability*com + public static function array_merge_clobber($array1, $array2) { + // written by kcØhireability*com // taken from http://www.php.net/manual/en/function.array-merge-recursive.php if (!is_array($array1) || !is_array($array2)) { return false; @@ -389,7 +390,7 @@ class getid3_lib $newarray = $array1; foreach ($array2 as $key => $val) { if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { - $newarray[$key] = getid3_lib::array_merge_clobber($newarray[$key], $val); + $newarray[$key] = self::array_merge_clobber($newarray[$key], $val); } else { $newarray[$key] = $val; } @@ -398,14 +399,14 @@ class getid3_lib } - static function array_merge_noclobber($array1, $array2) { + public static function array_merge_noclobber($array1, $array2) { if (!is_array($array1) || !is_array($array2)) { return false; } $newarray = $array1; foreach ($array2 as $key => $val) { if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { - $newarray[$key] = getid3_lib::array_merge_noclobber($newarray[$key], $val); + $newarray[$key] = self::array_merge_noclobber($newarray[$key], $val); } elseif (!isset($newarray[$key])) { $newarray[$key] = $val; } @@ -414,7 +415,7 @@ class getid3_lib } - static function ksort_recursive(&$theArray) { + public static function ksort_recursive(&$theArray) { ksort($theArray); foreach ($theArray as $key => $value) { if (is_array($value)) { @@ -424,7 +425,7 @@ class getid3_lib return true; } - static function fileextension($filename, $numextensions=1) { + public static function fileextension($filename, $numextensions=1) { if (strstr($filename, '.')) { $reversedfilename = strrev($filename); $offset = 0; @@ -440,58 +441,56 @@ class getid3_lib } - static function PlaytimeString($seconds) { + public static function PlaytimeString($seconds) { $sign = (($seconds < 0) ? '-' : ''); - $seconds = abs($seconds); - $H = floor( $seconds / 3600); - $M = floor(($seconds - (3600 * $H) ) / 60); - $S = round( $seconds - (3600 * $H) - (60 * $M) ); + $seconds = round(abs($seconds)); + $H = (int) floor( $seconds / 3600); + $M = (int) floor(($seconds - (3600 * $H) ) / 60); + $S = (int) round( $seconds - (3600 * $H) - (60 * $M) ); return $sign.($H ? $H.':' : '').($H ? str_pad($M, 2, '0', STR_PAD_LEFT) : intval($M)).':'.str_pad($S, 2, 0, STR_PAD_LEFT); } - static function DateMac2Unix($macdate) { + public static function DateMac2Unix($macdate) { // Macintosh timestamp: seconds since 00:00h January 1, 1904 // UNIX timestamp: seconds since 00:00h January 1, 1970 - return getid3_lib::CastAsInt($macdate - 2082844800); + return self::CastAsInt($macdate - 2082844800); } - static function FixedPoint8_8($rawdata) { - return getid3_lib::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8)); + public static function FixedPoint8_8($rawdata) { + return self::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (self::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8)); } - static function FixedPoint16_16($rawdata) { - return getid3_lib::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16)); + public static function FixedPoint16_16($rawdata) { + return self::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (self::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16)); } - static function FixedPoint2_30($rawdata) { - $binarystring = getid3_lib::BigEndian2Bin($rawdata); - return getid3_lib::Bin2Dec(substr($binarystring, 0, 2)) + (float) (getid3_lib::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30)); + public static function FixedPoint2_30($rawdata) { + $binarystring = self::BigEndian2Bin($rawdata); + return self::Bin2Dec(substr($binarystring, 0, 2)) + (float) (self::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30)); } - static function CreateDeepArray($ArrayPath, $Separator, $Value) { + public static function CreateDeepArray($ArrayPath, $Separator, $Value) { // assigns $Value to a nested array path: - // $foo = getid3_lib::CreateDeepArray('/path/to/my', '/', 'file.txt') + // $foo = self::CreateDeepArray('/path/to/my', '/', 'file.txt') // is the same as: // $foo = array('path'=>array('to'=>'array('my'=>array('file.txt')))); // or // $foo['path']['to']['my'] = 'file.txt'; - while ($ArrayPath && ($ArrayPath{0} == $Separator)) { - $ArrayPath = substr($ArrayPath, 1); - } + $ArrayPath = ltrim($ArrayPath, $Separator); if (($pos = strpos($ArrayPath, $Separator)) !== false) { - $ReturnedArray[substr($ArrayPath, 0, $pos)] = getid3_lib::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value); + $ReturnedArray[substr($ArrayPath, 0, $pos)] = self::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value); } else { $ReturnedArray[$ArrayPath] = $Value; } return $ReturnedArray; } - static function array_max($arraydata, $returnkey=false) { + public static function array_max($arraydata, $returnkey=false) { $maxvalue = false; $maxkey = false; foreach ($arraydata as $key => $value) { @@ -505,7 +504,7 @@ class getid3_lib return ($returnkey ? $maxkey : $maxvalue); } - static function array_min($arraydata, $returnkey=false) { + public static function array_min($arraydata, $returnkey=false) { $minvalue = false; $minkey = false; foreach ($arraydata as $key => $value) { @@ -519,17 +518,20 @@ class getid3_lib return ($returnkey ? $minkey : $minvalue); } - static function XML2array($XMLstring) { - if (function_exists('simplexml_load_string')) { - if (function_exists('get_object_vars')) { - $XMLobject = simplexml_load_string($XMLstring); - return self::SimpleXMLelement2array($XMLobject); - } - } + public static function XML2array($XMLstring) { + if (function_exists('simplexml_load_string') && function_exists('libxml_disable_entity_loader')) { + // http://websec.io/2012/08/27/Preventing-XEE-in-PHP.html + // https://core.trac.wordpress.org/changeset/29378 + $loader = libxml_disable_entity_loader(true); + $XMLobject = simplexml_load_string($XMLstring, 'SimpleXMLElement', LIBXML_NOENT); + $return = self::SimpleXMLelement2array($XMLobject); + libxml_disable_entity_loader($loader); + return $return; + } return false; } - static function SimpleXMLelement2array($XMLobject) { + public static function SimpleXMLelement2array($XMLobject) { if (!is_object($XMLobject) && !is_array($XMLobject)) { return $XMLobject; } @@ -541,11 +543,11 @@ class getid3_lib } - // Allan Hansen - // getid3_lib::md5_data() - returns md5sum for a file from startuing position to absolute end position - static function hash_data($file, $offset, $end, $algorithm) { + // Allan Hansen + // self::md5_data() - returns md5sum for a file from startuing position to absolute end position + public static function hash_data($file, $offset, $end, $algorithm) { static $tempdir = ''; - if (!getid3_lib::intValueSupported($end)) { + if (!self::intValueSupported($end)) { return false; } switch ($algorithm) { @@ -564,7 +566,7 @@ class getid3_lib break; default: - throw new Exception('Invalid algorithm ('.$algorithm.') in getid3_lib::hash_data()'); + throw new Exception('Invalid algorithm ('.$algorithm.') in self::hash_data()'); break; } $size = $end - $offset; @@ -581,10 +583,10 @@ class getid3_lib foreach ($RequiredFiles as $required_file) { if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { // helper apps not available - fall back to old method - break; + break 2; } } - $commandline = GETID3_HELPERAPPSDIR.'head.exe -c '.$end.' "'.escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $file)).'" | '; + $commandline = GETID3_HELPERAPPSDIR.'head.exe -c '.$end.' '.escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $file)).' | '; $commandline .= GETID3_HELPERAPPSDIR.'tail.exe -c '.$size.' | '; $commandline .= GETID3_HELPERAPPSDIR.$windows_call; @@ -604,7 +606,7 @@ class getid3_lib if (empty($tempdir)) { // yes this is ugly, feel free to suggest a better way - require_once(dirname(__FILE__).'/getid3.php'); + require_once(dirname(__FILE__) . '/getid3.php'); $getid3_temp = new getID3(); $tempdir = $getid3_temp->tempdir; unset($getid3_temp); @@ -620,22 +622,22 @@ class getid3_lib // copy parts of file try { - getid3_lib::CopyFileParts($file, $data_filename, $offset, $end - $offset); + self::CopyFileParts($file, $data_filename, $offset, $end - $offset); $result = $hash_function($data_filename); } catch (Exception $e) { - throw new Exception('getid3_lib::CopyFileParts() failed in getid_lib::hash_data(): '.$e->getMessage()); + throw new Exception('self::CopyFileParts() failed in getid_lib::hash_data(): '.$e->getMessage()); } unlink($data_filename); return $result; } - static function CopyFileParts($filename_source, $filename_dest, $offset, $length) { - if (!getid3_lib::intValueSupported($offset + $length)) { + public static function CopyFileParts($filename_source, $filename_dest, $offset, $length) { + if (!self::intValueSupported($offset + $length)) { throw new Exception('cannot copy file portion, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit'); } if (is_readable($filename_source) && is_file($filename_source) && ($fp_src = fopen($filename_source, 'rb'))) { if (($fp_dest = fopen($filename_dest, 'wb'))) { - if (fseek($fp_src, $offset, SEEK_SET) == 0) { + if (fseek($fp_src, $offset) == 0) { $byteslefttowrite = $length; while (($byteslefttowrite > 0) && ($buffer = fread($fp_src, min($byteslefttowrite, getID3::FREAD_BUFFER_SIZE)))) { $byteswritten = fwrite($fp_dest, $buffer, $byteslefttowrite); @@ -656,7 +658,7 @@ class getid3_lib return false; } - static function iconv_fallback_int_utf8($charval) { + public static function iconv_fallback_int_utf8($charval) { if ($charval < 128) { // 0bbbbbbb $newcharstring = chr($charval); @@ -680,7 +682,7 @@ class getid3_lib } // ISO-8859-1 => UTF-8 - static function iconv_fallback_iso88591_utf8($string, $bom=false) { + public static function iconv_fallback_iso88591_utf8($string, $bom=false) { if (function_exists('utf8_encode')) { return utf8_encode($string); } @@ -691,13 +693,13 @@ class getid3_lib } for ($i = 0; $i < strlen($string); $i++) { $charval = ord($string{$i}); - $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval); + $newcharstring .= self::iconv_fallback_int_utf8($charval); } return $newcharstring; } // ISO-8859-1 => UTF-16BE - static function iconv_fallback_iso88591_utf16be($string, $bom=false) { + public static function iconv_fallback_iso88591_utf16be($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFE\xFF"; @@ -709,7 +711,7 @@ class getid3_lib } // ISO-8859-1 => UTF-16LE - static function iconv_fallback_iso88591_utf16le($string, $bom=false) { + public static function iconv_fallback_iso88591_utf16le($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFF\xFE"; @@ -721,12 +723,12 @@ class getid3_lib } // ISO-8859-1 => UTF-16LE (BOM) - static function iconv_fallback_iso88591_utf16($string) { - return getid3_lib::iconv_fallback_iso88591_utf16le($string, true); + public static function iconv_fallback_iso88591_utf16($string) { + return self::iconv_fallback_iso88591_utf16le($string, true); } // UTF-8 => ISO-8859-1 - static function iconv_fallback_utf8_iso88591($string) { + public static function iconv_fallback_utf8_iso88591($string) { if (function_exists('utf8_decode')) { return utf8_decode($string); } @@ -770,7 +772,7 @@ class getid3_lib } // UTF-8 => UTF-16BE - static function iconv_fallback_utf8_utf16be($string, $bom=false) { + public static function iconv_fallback_utf8_utf16be($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFE\xFF"; @@ -806,14 +808,14 @@ class getid3_lib $offset += 1; } if ($charval !== false) { - $newcharstring .= (($charval < 65536) ? getid3_lib::BigEndian2String($charval, 2) : "\x00".'?'); + $newcharstring .= (($charval < 65536) ? self::BigEndian2String($charval, 2) : "\x00".'?'); } } return $newcharstring; } // UTF-8 => UTF-16LE - static function iconv_fallback_utf8_utf16le($string, $bom=false) { + public static function iconv_fallback_utf8_utf16le($string, $bom=false) { $newcharstring = ''; if ($bom) { $newcharstring .= "\xFF\xFE"; @@ -849,96 +851,96 @@ class getid3_lib $offset += 1; } if ($charval !== false) { - $newcharstring .= (($charval < 65536) ? getid3_lib::LittleEndian2String($charval, 2) : '?'."\x00"); + $newcharstring .= (($charval < 65536) ? self::LittleEndian2String($charval, 2) : '?'."\x00"); } } return $newcharstring; } // UTF-8 => UTF-16LE (BOM) - static function iconv_fallback_utf8_utf16($string) { - return getid3_lib::iconv_fallback_utf8_utf16le($string, true); + public static function iconv_fallback_utf8_utf16($string) { + return self::iconv_fallback_utf8_utf16le($string, true); } // UTF-16BE => UTF-8 - static function iconv_fallback_utf16be_utf8($string) { + public static function iconv_fallback_utf16be_utf8($string) { if (substr($string, 0, 2) == "\xFE\xFF") { // strip BOM $string = substr($string, 2); } $newcharstring = ''; for ($i = 0; $i < strlen($string); $i += 2) { - $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2)); - $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval); + $charval = self::BigEndian2Int(substr($string, $i, 2)); + $newcharstring .= self::iconv_fallback_int_utf8($charval); } return $newcharstring; } // UTF-16LE => UTF-8 - static function iconv_fallback_utf16le_utf8($string) { + public static function iconv_fallback_utf16le_utf8($string) { if (substr($string, 0, 2) == "\xFF\xFE") { // strip BOM $string = substr($string, 2); } $newcharstring = ''; for ($i = 0; $i < strlen($string); $i += 2) { - $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2)); - $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval); + $charval = self::LittleEndian2Int(substr($string, $i, 2)); + $newcharstring .= self::iconv_fallback_int_utf8($charval); } return $newcharstring; } // UTF-16BE => ISO-8859-1 - static function iconv_fallback_utf16be_iso88591($string) { + public static function iconv_fallback_utf16be_iso88591($string) { if (substr($string, 0, 2) == "\xFE\xFF") { // strip BOM $string = substr($string, 2); } $newcharstring = ''; for ($i = 0; $i < strlen($string); $i += 2) { - $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2)); + $charval = self::BigEndian2Int(substr($string, $i, 2)); $newcharstring .= (($charval < 256) ? chr($charval) : '?'); } return $newcharstring; } // UTF-16LE => ISO-8859-1 - static function iconv_fallback_utf16le_iso88591($string) { + public static function iconv_fallback_utf16le_iso88591($string) { if (substr($string, 0, 2) == "\xFF\xFE") { // strip BOM $string = substr($string, 2); } $newcharstring = ''; for ($i = 0; $i < strlen($string); $i += 2) { - $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2)); + $charval = self::LittleEndian2Int(substr($string, $i, 2)); $newcharstring .= (($charval < 256) ? chr($charval) : '?'); } return $newcharstring; } // UTF-16 (BOM) => ISO-8859-1 - static function iconv_fallback_utf16_iso88591($string) { + public static function iconv_fallback_utf16_iso88591($string) { $bom = substr($string, 0, 2); if ($bom == "\xFE\xFF") { - return getid3_lib::iconv_fallback_utf16be_iso88591(substr($string, 2)); + return self::iconv_fallback_utf16be_iso88591(substr($string, 2)); } elseif ($bom == "\xFF\xFE") { - return getid3_lib::iconv_fallback_utf16le_iso88591(substr($string, 2)); + return self::iconv_fallback_utf16le_iso88591(substr($string, 2)); } return $string; } // UTF-16 (BOM) => UTF-8 - static function iconv_fallback_utf16_utf8($string) { + public static function iconv_fallback_utf16_utf8($string) { $bom = substr($string, 0, 2); if ($bom == "\xFE\xFF") { - return getid3_lib::iconv_fallback_utf16be_utf8(substr($string, 2)); + return self::iconv_fallback_utf16be_utf8(substr($string, 2)); } elseif ($bom == "\xFF\xFE") { - return getid3_lib::iconv_fallback_utf16le_utf8(substr($string, 2)); + return self::iconv_fallback_utf16le_utf8(substr($string, 2)); } return $string; } - static function iconv_fallback($in_charset, $out_charset, $string) { + public static function iconv_fallback($in_charset, $out_charset, $string) { if ($in_charset == $out_charset) { return $string; @@ -981,13 +983,26 @@ class getid3_lib } if (isset($ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)])) { $ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)]; - return getid3_lib::$ConversionFunction($string); + return self::$ConversionFunction($string); } throw new Exception('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset); } + public static function recursiveMultiByteCharString2HTML($data, $charset='ISO-8859-1') { + if (is_string($data)) { + return self::MultiByteCharString2HTML($data, $charset); + } elseif (is_array($data)) { + $return_data = array(); + foreach ($data as $key => $value) { + $return_data[$key] = self::recursiveMultiByteCharString2HTML($value, $charset); + } + return $return_data; + } + // integer, float, objects, resources, etc + return $data; + } - static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') { + public static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') { $string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string $HTMLstring = ''; @@ -1052,7 +1067,7 @@ class getid3_lib case 'UTF-16LE': for ($i = 0; $i < strlen($string); $i += 2) { - $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2)); + $charval = self::LittleEndian2Int(substr($string, $i, 2)); if (($charval >= 32) && ($charval <= 127)) { $HTMLstring .= chr($charval); } else { @@ -1063,7 +1078,7 @@ class getid3_lib case 'UTF-16BE': for ($i = 0; $i < strlen($string); $i += 2) { - $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2)); + $charval = self::BigEndian2Int(substr($string, $i, 2)); if (($charval >= 32) && ($charval <= 127)) { $HTMLstring .= chr($charval); } else { @@ -1081,7 +1096,7 @@ class getid3_lib - static function RGADnameLookup($namecode) { + public static function RGADnameLookup($namecode) { static $RGADname = array(); if (empty($RGADname)) { $RGADname[0] = 'not set'; @@ -1093,7 +1108,7 @@ class getid3_lib } - static function RGADoriginatorLookup($originatorcode) { + public static function RGADoriginatorLookup($originatorcode) { static $RGADoriginator = array(); if (empty($RGADoriginator)) { $RGADoriginator[0] = 'unspecified'; @@ -1106,7 +1121,7 @@ class getid3_lib } - static function RGADadjustmentLookup($rawadjustment, $signbit) { + public static function RGADadjustmentLookup($rawadjustment, $signbit) { $adjustment = $rawadjustment / 10; if ($signbit == 1) { $adjustment *= -1; @@ -1115,7 +1130,7 @@ class getid3_lib } - static function RGADgainString($namecode, $originatorcode, $replaygain) { + public static function RGADgainString($namecode, $originatorcode, $replaygain) { if ($replaygain < 0) { $signbit = '1'; } else { @@ -1130,16 +1145,16 @@ class getid3_lib return $gainstring; } - static function RGADamplitude2dB($amplitude) { + public static function RGADamplitude2dB($amplitude) { return 20 * log10($amplitude); } - static function GetDataImageSize($imgData, &$imageinfo) { + public static function GetDataImageSize($imgData, &$imageinfo=array()) { static $tempdir = ''; if (empty($tempdir)) { // yes this is ugly, feel free to suggest a better way - require_once(dirname(__FILE__).'/getid3.php'); + require_once(dirname(__FILE__) . '/getid3.php'); $getid3_temp = new getID3(); $tempdir = $getid3_temp->tempdir; unset($getid3_temp); @@ -1149,14 +1164,21 @@ class getid3_lib if (is_writable($tempfilename) && is_file($tempfilename) && ($tmp = fopen($tempfilename, 'wb'))) { fwrite($tmp, $imgData); fclose($tmp); - $GetDataImageSize = @GetImageSize($tempfilename, $imageinfo); + $GetDataImageSize = @getimagesize($tempfilename, $imageinfo); + $GetDataImageSize['height'] = $GetDataImageSize[0]; + $GetDataImageSize['width'] = $GetDataImageSize[1]; } unlink($tempfilename); } return $GetDataImageSize; } - static function ImageTypesLookup($imagetypeid) { + public static function ImageExtFromMime($mime_type) { + // temporary way, works OK for now, but should be reworked in the future + return str_replace(array('image/', 'x-', 'jpeg'), array('', '', 'jpg'), $mime_type); + } + + public static function ImageTypesLookup($imagetypeid) { static $ImageTypesLookup = array(); if (empty($ImageTypesLookup)) { $ImageTypesLookup[1] = 'gif'; @@ -1177,7 +1199,7 @@ class getid3_lib return (isset($ImageTypesLookup[$imagetypeid]) ? $ImageTypesLookup[$imagetypeid] : ''); } - static function CopyTagsToComments(&$ThisFileInfo) { + public static function CopyTagsToComments(&$ThisFileInfo) { // Copy all entries from ['tags'] into common ['comments'] if (!empty($ThisFileInfo['tags'])) { @@ -1205,16 +1227,21 @@ class getid3_lib $newvaluelength = strlen(trim($value)); foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { $oldvaluelength = strlen(trim($existingvalue)); - if (($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) { + if ((strlen($existingvalue) > 10) && ($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) { $ThisFileInfo['comments'][$tagname][$existingkey] = trim($value); - break 2; + //break 2; + break; } } } if (is_array($value) || empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) { $value = (is_string($value) ? trim($value) : $value); - $ThisFileInfo['comments'][$tagname][] = $value; + if (!is_numeric($key)) { + $ThisFileInfo['comments'][$tagname][$key] = $value; + } else { + $ThisFileInfo['comments'][$tagname][] = $value; + } } } } @@ -1222,26 +1249,29 @@ class getid3_lib } // Copy to ['comments_html'] - foreach ($ThisFileInfo['comments'] as $field => $values) { - if ($field == 'picture') { - // pictures can take up a lot of space, and we don't need multiple copies of them - // let there be a single copy in [comments][picture], and not elsewhere - continue; - } - foreach ($values as $index => $value) { - if (is_array($value)) { - $ThisFileInfo['comments_html'][$field][$index] = $value; - } else { - $ThisFileInfo['comments_html'][$field][$index] = str_replace('�', '', getid3_lib::MultiByteCharString2HTML($value, $ThisFileInfo['encoding'])); + if (!empty($ThisFileInfo['comments'])) { + foreach ($ThisFileInfo['comments'] as $field => $values) { + if ($field == 'picture') { + // pictures can take up a lot of space, and we don't need multiple copies of them + // let there be a single copy in [comments][picture], and not elsewhere + continue; + } + foreach ($values as $index => $value) { + if (is_array($value)) { + $ThisFileInfo['comments_html'][$field][$index] = $value; + } else { + $ThisFileInfo['comments_html'][$field][$index] = str_replace('�', '', self::MultiByteCharString2HTML($value, $ThisFileInfo['encoding'])); + } } } } + } return true; } - static function EmbeddedLookup($key, $begin, $end, $file, $name) { + public static function EmbeddedLookup($key, $begin, $end, $file, $name) { // Cached static $cache; @@ -1287,7 +1317,7 @@ class getid3_lib return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); } - static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { + public static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { global $GETID3_ERRORARRAY; if (file_exists($filename)) { @@ -1311,6 +1341,40 @@ class getid3_lib return trim($string, "\x00"); } -} + public static function getFileSizeSyscall($path) { + $filesize = false; -?> \ No newline at end of file + if (GETID3_OS_ISWINDOWS) { + if (class_exists('COM')) { // From PHP 5.3.15 and 5.4.5, COM and DOTNET is no longer built into the php core.you have to add COM support in php.ini: + $filesystem = new COM('Scripting.FileSystemObject'); + $file = $filesystem->GetFile($path); + $filesize = $file->Size(); + unset($filesystem, $file); + } else { + $commandline = 'for %I in ('.escapeshellarg($path).') do @echo %~zI'; + } + } else { + $commandline = 'ls -l '.escapeshellarg($path).' | awk \'{print $5}\''; + } + if (isset($commandline)) { + $output = trim(`$commandline`); + if (ctype_digit($output)) { + $filesize = (float) $output; + } + } + return $filesize; + } + + + /** + * Workaround for Bug #37268 (https://bugs.php.net/bug.php?id=37268) + * @param string $path A path. + * @param string $suffix If the name component ends in suffix this will also be cut off. + * @return string + */ + public static function mb_basename($path, $suffix = null) { + $splited = preg_split('#/#', rtrim($path, '/ ')); + return substr(basename('X'.$splited[count($splited) - 1], $suffix), 1); + } + +} diff --git a/app/library/getid3/getid3.php b/app/library/getid3/getid3/getid3.php old mode 100644 new mode 100755 similarity index 84% rename from app/library/getid3/getid3.php rename to app/library/getid3/getid3/getid3.php index e8a3f7e2..25a52fdd --- a/app/library/getid3/getid3.php +++ b/app/library/getid3/getid3/getid3.php @@ -3,23 +3,36 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // // // Please see readme.txt for more information // // /// ///////////////////////////////////////////////////////////////// +// define a constant rather than looking up every time it is needed +if (!defined('GETID3_OS_ISWINDOWS')) { + define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0)); +} +// Get base path of getID3() - ONCE +if (!defined('GETID3_INCLUDEPATH')) { + define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); +} +// Workaround Bug #39923 (https://bugs.php.net/bug.php?id=39923) +if (!defined('IMG_JPG') && defined('IMAGETYPE_JPEG')) { + define('IMG_JPG', IMAGETYPE_JPEG); +} + // attempt to define temp dir as something flexible but reliable $temp_dir = ini_get('upload_tmp_dir'); if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) { $temp_dir = ''; } -if (!$temp_dir && function_exists('sys_get_temp_dir')) { - // PHP v5.2.1+ +if (!$temp_dir && function_exists('sys_get_temp_dir')) { // sys_get_temp_dir added in PHP v5.2.1 // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts $temp_dir = sys_get_temp_dir(); } -$temp_dir = realpath($temp_dir); +$temp_dir = @realpath($temp_dir); // see https://github.com/JamesHeinrich/getID3/pull/10 $open_basedir = ini_get('open_basedir'); if ($open_basedir) { // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/" @@ -29,7 +42,7 @@ if ($open_basedir) { $temp_dir .= DIRECTORY_SEPARATOR; } $found_valid_tempdir = false; - $open_basedirs = explode(':', $open_basedir); + $open_basedirs = explode(PATH_SEPARATOR, $open_basedir); foreach ($open_basedirs as $basedir) { if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) { $basedir .= DIRECTORY_SEPARATOR; @@ -48,29 +61,11 @@ if (!$temp_dir) { $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir } // $temp_dir = '/something/else/'; // feel free to override temp dir here if it works better for your system -define('GETID3_TEMP_DIR', $temp_dir); +if (!defined('GETID3_TEMP_DIR')) { + define('GETID3_TEMP_DIR', $temp_dir); +} unset($open_basedir, $temp_dir); - -// define a constant rather than looking up every time it is needed -if (!defined('GETID3_OS_ISWINDOWS')) { - if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { - define('GETID3_OS_ISWINDOWS', true); - } else { - define('GETID3_OS_ISWINDOWS', false); - } -} - -// Get base path of getID3() - ONCE -if (!defined('GETID3_INCLUDEPATH')) { - foreach (get_included_files() as $key => $val) { - if (basename($val) == 'getid3.php') { - define('GETID3_INCLUDEPATH', dirname($val).DIRECTORY_SEPARATOR); - break; - } - } -} - // End: Defines @@ -107,15 +102,15 @@ class getID3 public $filename; // Filename of file being analysed. public $fp; // Filepointer to file being analysed. public $info; // Result array. + public $tempdir = GETID3_TEMP_DIR; + public $memory_limit = 0; // Protected variables protected $startup_error = ''; protected $startup_warning = ''; - protected $memory_limit = 0; - const VERSION = '1.9.3-20111213'; + const VERSION = '1.9.9-20141121'; const FREAD_BUFFER_SIZE = 32768; - var $tempdir = GETID3_TEMP_DIR; const ATTACHMENTS_NONE = false; const ATTACHMENTS_INLINE = true; @@ -124,7 +119,7 @@ class getID3 public function __construct() { // Check for PHP version - $required_php_version = '5.0.5'; + $required_php_version = '5.3.0'; if (version_compare(PHP_VERSION, $required_php_version, '<')) { $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION; return false; @@ -171,7 +166,7 @@ class getID3 } // Load support library - if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { + if (!include_once(GETID3_INCLUDEPATH . 'getid3.lib.php')) { $this->startup_error .= 'getid3.lib.php is missing or corrupt'; } @@ -234,7 +229,7 @@ class getID3 // public: setOption - function setOption($optArray) { + public function setOption($optArray) { if (!is_array($optArray) || empty($optArray)) { return false; } @@ -261,7 +256,7 @@ class getID3 $this->filename = $filename; $this->info = array(); $this->info['GETID3_VERSION'] = $this->version(); - $this->info['php_memory_limit'] = $this->memory_limit; + $this->info['php_memory_limit'] = (($this->memory_limit > 0) ? $this->memory_limit : false); // remote files not supported if (preg_match('/^(ht|f)tp:\/\//', $filename)) { @@ -272,16 +267,32 @@ class getID3 $filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#U', '\1'.DIRECTORY_SEPARATOR, $filename); // open local file - if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { + //if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see http://www.getid3.org/phpBB3/viewtopic.php?t=1720 + if ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // great } else { - throw new getid3_exception('Could not open "'.$filename.'" (does not exist, or is not a file)'); + $errormessagelist = array(); + if (!is_readable($filename)) { + $errormessagelist[] = '!is_readable'; + } + if (!is_file($filename)) { + $errormessagelist[] = '!is_file'; + } + if (!file_exists($filename)) { + $errormessagelist[] = '!file_exists'; + } + if (empty($errormessagelist)) { + $errormessagelist[] = 'fopen failed'; + } + throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')'); } $this->info['filesize'] = filesize($filename); // set redundant parameters - might be needed in some include file - $this->info['filename'] = basename($filename); + // filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion + $filename = str_replace('\\', '/', $filename); $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); + $this->info['filename'] = getid3_lib::mb_basename($filename); $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; @@ -294,20 +305,8 @@ class getID3 if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) || ($this->info['filesize'] < 0) || (ftell($this->fp) < 0)) { - $real_filesize = false; - if (GETID3_OS_ISWINDOWS) { - $commandline = 'dir /-C "'.str_replace('/', DIRECTORY_SEPARATOR, $filename).'"'; - $dir_output = `$commandline`; - if (preg_match('#1 File\(s\)[ ]+([0-9]+) bytes#i', $dir_output, $matches)) { - $real_filesize = (float) $matches[1]; - } - } else { - $commandline = 'ls -o -g -G --time-style=long-iso '.escapeshellarg($filename); - $dir_output = `$commandline`; - if (preg_match('#([0-9]+) ([0-9]{4}-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}) '.str_replace('#', '\\#', preg_quote($filename)).'$#', $dir_output, $matches)) { - $real_filesize = (float) $matches[1]; - } - } + $real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']); + if ($real_filesize === false) { unset($this->info['filesize']); fclose($this->fp); @@ -318,7 +317,7 @@ class getID3 throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize, 3).'GB, please report to info@getid3.org'); } $this->info['filesize'] = $real_filesize; - $this->error('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.'); + $this->warning('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.'); } } @@ -343,7 +342,7 @@ class getID3 } // public: analyze file - function analyze($filename) { + public function analyze($filename) { try { if (!$this->openfile($filename)) { return $this->info; @@ -375,7 +374,7 @@ class getID3 // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier if (!$this->option_tag_id3v2) { - fseek($this->fp, 0, SEEK_SET); + fseek($this->fp, 0); $header = fread($this->fp, 10); if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { $this->info['id3v2']['header'] = true; @@ -386,7 +385,7 @@ class getID3 } // read 32 kb file data - fseek($this->fp, $this->info['avdataoffset'], SEEK_SET); + fseek($this->fp, $this->info['avdataoffset']); $formattest = fread($this->fp, 32774); // determine format @@ -447,18 +446,8 @@ class getID3 if (!class_exists($class_name)) { return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); } - //if (isset($determined_format['option'])) { - // //$class = new $class_name($this->fp, $this->info, $determined_format['option']); - //} else { - //$class = new $class_name($this->fp, $this->info); - $class = new $class_name($this); - //} - - if (!empty($determined_format['set_inline_attachments'])) { - $class->inline_attachments = $this->option_save_attachments; - } + $class = new $class_name($this); $class->Analyze(); - unset($class); // close file @@ -480,7 +469,7 @@ class getID3 // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags if ($this->option_md5_data) { - // do not cald md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too + // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { $this->getHashdata('md5'); } @@ -504,7 +493,7 @@ class getID3 // private: error handling - function error($message) { + public function error($message) { $this->CleanUp(); if (!isset($this->info['error'])) { $this->info['error'] = array(); @@ -515,14 +504,14 @@ class getID3 // private: warning handling - function warning($message) { + public function warning($message) { $this->info['warning'][] = $message; return true; } // private: CleanUp - function CleanUp() { + private function CleanUp() { // remove possible empty keys $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate'); @@ -570,7 +559,7 @@ class getID3 // return array containing information about all supported formats - function GetFileFormatArray() { + public function GetFileFormatArray() { static $format_info = array(); if (empty($format_info)) { $format_info = array( @@ -594,15 +583,15 @@ class getID3 'fail_ape' => 'WARNING', ), - +/* // AA - audio - Audible Audiobook - 'adts' => array( + 'aa' => array( 'pattern' => '^.{4}\x57\x90\x75\x36', 'group' => 'audio', 'module' => 'aa', - 'mime_type' => 'audio/audible ', + 'mime_type' => 'audio/audible', ), - +*/ // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) 'adts' => array( 'pattern' => '^\xFF[\xF0-\xF1\xF8-\xF9]', @@ -621,6 +610,14 @@ class getID3 'mime_type' => 'audio/basic', ), + // AMR - audio - Adaptive Multi Rate + 'amr' => array( + 'pattern' => '^\x23\x21AMR\x0A', // #!AMR[0A] + 'group' => 'audio', + 'module' => 'amr', + 'mime_type' => 'audio/amr', + ), + // AVR - audio - Audio Visual Research 'avr' => array( 'pattern' => '^2BIT', @@ -639,7 +636,7 @@ class getID3 // DSS - audio - Digital Speech Standard 'dss' => array( - 'pattern' => '^[\x02-\x03]dss', + 'pattern' => '^[\x02-\x03]ds[s2]', 'group' => 'audio', 'module' => 'dss', 'mime_type' => 'application/octet-stream', @@ -659,7 +656,6 @@ class getID3 'group' => 'audio', 'module' => 'flac', 'mime_type' => 'audio/x-flac', - 'set_inline_attachments' => true, ), // LA - audio - Lossless Audio (LA) @@ -839,7 +835,6 @@ class getID3 'group' => 'audio-video', 'module' => 'matroska', 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska - 'set_inline_attachments' => true, ), // MPEG - audio/video - MPEG (Moving Pictures Experts Group) @@ -866,7 +861,6 @@ class getID3 'mime_type' => 'application/ogg', 'fail_id3' => 'WARNING', 'fail_ape' => 'WARNING', - 'set_inline_attachments' => true, ), // QT - audio/video - Quicktime @@ -902,6 +896,14 @@ class getID3 'mime_type' => 'application/x-shockwave-flash', ), + // TS - audio/video - MPEG-2 Transport Stream + 'ts' => array( + 'pattern' => '^(\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G". Check for at least 10 packets matching this pattern + 'group' => 'audio-video', + 'module' => 'ts', + 'mime_type' => 'video/MP2T', + ), + // Still-Image formats @@ -980,7 +982,7 @@ class getID3 // EFAX - still image - eFax (TIFF derivative) - 'bmp' => array( + 'efax' => array( 'pattern' => '^\xDC\xFE', 'group' => 'graphic', 'module' => 'efax', @@ -1102,7 +1104,7 @@ class getID3 - function GetFileFormat(&$filedata, $filename='') { + public function GetFileFormat(&$filedata, $filename='') { // this function will determine the format of a file based on usually // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG, // and in the case of ISO CD image, 6 bytes offset 32kb from the start @@ -1141,7 +1143,7 @@ class getID3 // converts array to $encoding charset from $this->encoding - function CharConvert(&$array, $encoding) { + public function CharConvert(&$array, $encoding) { // identical encoding - end here if ($encoding == $this->encoding) { @@ -1164,7 +1166,7 @@ class getID3 } - function HandleAllTags() { + public function HandleAllTags() { // key name => array (tag name, character encoding) static $tags; @@ -1187,6 +1189,9 @@ class getID3 'ape' => array('ape' , 'UTF-8'), 'cue' => array('cue' , 'ISO-8859-1'), 'matroska' => array('matroska' , 'UTF-8'), + 'flac' => array('vorbiscomment' , 'UTF-8'), + 'divxtag' => array('divx' , 'ISO-8859-1'), + 'iptc' => array('iptc' , 'ISO-8859-1'), ); } @@ -1201,16 +1206,22 @@ class getID3 // copy comments if key name set if (!empty($this->info[$comment_name]['comments'])) { - foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { foreach ($valuearray as $key => $value) { if (is_string($value)) { $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! } if ($value) { - $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; + if (!is_numeric($key)) { + $this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value; + } else { + $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; + } } } + if ($tag_key == 'picture') { + unset($this->info[$comment_name]['comments'][$tag_key]); + } } if (!isset($this->info['tags'][$tag_name])) { @@ -1220,14 +1231,7 @@ class getID3 if ($this->option_tags_html) { foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { - foreach ($valuearray as $key => $value) { - if (is_string($value)) { - //$this->info['tags_html'][$tag_name][$tag_key][$key] = getid3_lib::MultiByteCharString2HTML($value, $encoding); - $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('�', '', trim(getid3_lib::MultiByteCharString2HTML($value, $encoding))); - } else { - $this->info['tags_html'][$tag_name][$tag_key][$key] = $value; - } - } + $this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $encoding); } } @@ -1283,8 +1287,7 @@ class getID3 return true; } - - function getHashdata($algorithm) { + public function getHashdata($algorithm) { switch ($algorithm) { case 'md5': case 'sha1': @@ -1408,7 +1411,7 @@ class getID3 } - function ChannelsBitratePlaytimeCalculations() { + public function ChannelsBitratePlaytimeCalculations() { // set channelmode on audio if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) { @@ -1473,7 +1476,7 @@ class getID3 } - function CalculateCompressionRatioVideo() { + public function CalculateCompressionRatioVideo() { if (empty($this->info['video'])) { return false; } @@ -1521,8 +1524,8 @@ class getID3 } - function CalculateCompressionRatioAudio() { - if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate'])) { + public function CalculateCompressionRatioAudio() { + if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) { return false; } $this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16)); @@ -1538,7 +1541,7 @@ class getID3 } - function CalculateReplayGain() { + public function CalculateReplayGain() { if (isset($this->info['replay_gain'])) { if (!isset($this->info['replay_gain']['reference_volume'])) { $this->info['replay_gain']['reference_volume'] = (double) 89.0; @@ -1560,7 +1563,7 @@ class getID3 return true; } - function ProcessAudioStreams() { + public function ProcessAudioStreams() { if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) { if (!isset($this->info['audio']['streams'])) { foreach ($this->info['audio'] as $key => $value) { @@ -1573,72 +1576,10 @@ class getID3 return true; } - function getid3_tempnam() { + public function getid3_tempnam() { return tempnam($this->tempdir, 'gI3'); } - - public function saveAttachment(&$ThisFileInfoIndex, $filename, $offset, $length) { - try { - if (!getid3_lib::intValueSupported($offset + $length)) { - throw new Exception('cannot extract attachment, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit'); - } - - // do not extract at all - if ($this->option_save_attachments === getID3::ATTACHMENTS_NONE) { - - unset($ThisFileInfoIndex); // do not set any - - // extract to return array - } elseif ($this->option_save_attachments === getID3::ATTACHMENTS_INLINE) { - - // get whole data in one pass, till it is anyway stored in memory - $ThisFileInfoIndex = file_get_contents($this->info['filenamepath'], false, null, $offset, $length); - if (($ThisFileInfoIndex === false) || (strlen($ThisFileInfoIndex) != $length)) { // verify - throw new Exception('failed to read attachment data'); - } - - // assume directory path is given - } else { - - $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->option_save_attachments), DIRECTORY_SEPARATOR); - // check supplied directory - if (!is_dir($dir) || !is_writable($dir)) { - throw new Exception('getID3::saveAttachment() -- supplied path ('.$dir.') does not exist, or is not writable'); - } - - // set up destination path - $dest = $dir.DIRECTORY_SEPARATOR.$filename; - - // optimize speed if read buffer size is configured to be large enough - // here stream_copy_to_stream() may also be used. need to do speed-compare tests - if ($length <= $this->fread_buffer_size()) { - $data = file_get_contents($this->info['filenamepath'], false, null, $offset, $length); - if (($data === false) || (strlen($data) != $length)) { // verify - throw new Exception('failed to read attachment data'); - } - if (!file_put_contents($dest, $data)) { - throw new Exception('failed to create file '.$dest); - } - } else { - // optimization not available - copy data in loop - // here stream_copy_to_stream() shouldn't be used because it's internal read buffer may be larger than ours! - getid3_lib::CopyFileParts($this->info['filenamepath'], $dest, $offset, $length); - } - $ThisFileInfoIndex = $dest; - } - - } catch (Exception $e) { - - unset($ThisFileInfoIndex); // do not set any is case of error - $this->warning('Failed to extract attachment '.$filename.': '.$e->getMessage()); - return false; - - } - return true; - } - - public function include_module($name) { //if (!file_exists($this->include_path.'module.'.$name.'.php')) { if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) { @@ -1651,94 +1592,212 @@ class getID3 } -abstract class getid3_handler -{ - protected $getid3; // pointer +abstract class getid3_handler { - protected $data_string_flag = false; // analyzing filepointer or string - protected $data_string; // string to analyze - protected $data_string_position = 0; // seek position in string + /** + * @var getID3 + */ + protected $getid3; // pointer + + protected $data_string_flag = false; // analyzing filepointer or string + protected $data_string = ''; // string to analyze + protected $data_string_position = 0; // seek position in string + protected $data_string_length = 0; // string length + + private $dependency_to = null; - public function __construct(getID3 $getid3) { - $this->getid3 = $getid3; - } + public function __construct(getID3 $getid3, $call_module=null) { + $this->getid3 = $getid3; + + if ($call_module) { + $this->dependency_to = str_replace('getid3_', '', $call_module); + } + } - // Analyze from file pointer - abstract public function Analyze(); + // Analyze from file pointer + abstract public function Analyze(); - // Analyze from string instead - public function AnalyzeString(&$string) { - // Enter string mode - $this->data_string_flag = true; - $this->data_string = $string; + // Analyze from string instead + public function AnalyzeString($string) { + // Enter string mode + $this->setStringMode($string); - // Save info - $saved_avdataoffset = $this->getid3->info['avdataoffset']; - $saved_avdataend = $this->getid3->info['avdataend']; - $saved_filesize = $this->getid3->info['filesize']; + // Save info + $saved_avdataoffset = $this->getid3->info['avdataoffset']; + $saved_avdataend = $this->getid3->info['avdataend']; + $saved_filesize = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call - // Reset some info - $this->getid3->info['avdataoffset'] = 0; - $this->getid3->info['avdataend'] = $this->getid3->info['filesize'] = strlen($string); + // Reset some info + $this->getid3->info['avdataoffset'] = 0; + $this->getid3->info['avdataend'] = $this->getid3->info['filesize'] = $this->data_string_length; - // Analyze - $this->Analyze(); + // Analyze + $this->Analyze(); - // Restore some info - $this->getid3->info['avdataoffset'] = $saved_avdataoffset; - $this->getid3->info['avdataend'] = $saved_avdataend; - $this->getid3->info['filesize'] = $saved_filesize; + // Restore some info + $this->getid3->info['avdataoffset'] = $saved_avdataoffset; + $this->getid3->info['avdataend'] = $saved_avdataend; + $this->getid3->info['filesize'] = $saved_filesize; - // Exit string mode - $this->data_string_flag = false; - } + // Exit string mode + $this->data_string_flag = false; + } + public function setStringMode($string) { + $this->data_string_flag = true; + $this->data_string = $string; + $this->data_string_length = strlen($string); + } - protected function ftell() { - if ($this->data_string_flag) { - return $this->data_string_position; - } - return ftell($this->getid3->fp); - } + protected function ftell() { + if ($this->data_string_flag) { + return $this->data_string_position; + } + return ftell($this->getid3->fp); + } + protected function fread($bytes) { + if ($this->data_string_flag) { + $this->data_string_position += $bytes; + return substr($this->data_string, $this->data_string_position - $bytes, $bytes); + } + $pos = $this->ftell() + $bytes; + if (!getid3_lib::intValueSupported($pos)) { + throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10); + } + return fread($this->getid3->fp, $bytes); + } - protected function fread($bytes) { - if ($this->data_string_flag) { - $this->data_string_position += $bytes; - return substr($this->data_string, $this->data_string_position - $bytes, $bytes); - } - return fread($this->getid3->fp, $bytes); - } + protected function fseek($bytes, $whence=SEEK_SET) { + if ($this->data_string_flag) { + switch ($whence) { + case SEEK_SET: + $this->data_string_position = $bytes; + break; + case SEEK_CUR: + $this->data_string_position += $bytes; + break; - protected function fseek($bytes, $whence = SEEK_SET) { - if ($this->data_string_flag) { - switch ($whence) { - case SEEK_SET: - $this->data_string_position = $bytes; - return; + case SEEK_END: + $this->data_string_position = $this->data_string_length + $bytes; + break; + } + return 0; + } else { + $pos = $bytes; + if ($whence == SEEK_CUR) { + $pos = $this->ftell() + $bytes; + } elseif ($whence == SEEK_END) { + $pos = $this->getid3->info['filesize'] + $bytes; + } + if (!getid3_lib::intValueSupported($pos)) { + throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10); + } + } + return fseek($this->getid3->fp, $bytes, $whence); + } - case SEEK_CUR: - $this->data_string_position += $bytes; - return; + protected function feof() { + if ($this->data_string_flag) { + return $this->data_string_position >= $this->data_string_length; + } + return feof($this->getid3->fp); + } - case SEEK_END: - $this->data_string_position = strlen($this->data_string) + $bytes; - return; - } - } - return fseek($this->getid3->fp, $bytes, $whence); - } + final protected function isDependencyFor($module) { + return $this->dependency_to == $module; + } + + protected function error($text) { + $this->getid3->info['error'][] = $text; + + return false; + } + + protected function warning($text) { + return $this->getid3->warning($text); + } + + protected function notice($text) { + // does nothing for now + } + + public function saveAttachment($name, $offset, $length, $image_mime=null) { + try { + + // do not extract at all + if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) { + + $attachment = null; // do not set any + + // extract to return array + } elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { + + $this->fseek($offset); + $attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory + if ($attachment === false || strlen($attachment) != $length) { + throw new Exception('failed to read attachment data'); + } + + // assume directory path is given + } else { + + // set up destination path + $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($dir) || !is_writable($dir)) { // check supplied directory + throw new Exception('supplied path ('.$dir.') does not exist, or is not writable'); + } + $dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : ''); + + // create dest file + if (($fp_dest = fopen($dest, 'wb')) == false) { + throw new Exception('failed to create file '.$dest); + } + + // copy data + $this->fseek($offset); + $buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size()); + $bytesleft = $length; + while ($bytesleft > 0) { + if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) { + throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space'); + } + $bytesleft -= $byteswritten; + } + + fclose($fp_dest); + $attachment = $dest; + + } + + } catch (Exception $e) { + + // close and remove dest file if created + if (isset($fp_dest) && is_resource($fp_dest)) { + fclose($fp_dest); + unlink($dest); + } + + // do not set any is case of error + $attachment = null; + $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage()); + + } + + // seek to the end of attachment + $this->fseek($offset + $length); + + return $attachment; + } } class getid3_exception extends Exception { - public $message; + public $message; } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.archive.gzip.php b/app/library/getid3/getid3/module.archive.gzip.php old mode 100644 new mode 100755 similarity index 93% rename from app/library/getid3/module.archive.gzip.php rename to app/library/getid3/getid3/module.archive.gzip.php index c30052ed..fc84988c --- a/app/library/getid3/module.archive.gzip.php +++ b/app/library/getid3/getid3/module.archive.gzip.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -14,7 +15,7 @@ ///////////////////////////////////////////////////////////////// // // // Module originally written by // -// Mike Mozolin // +// Mike Mozolin // // // ///////////////////////////////////////////////////////////////// @@ -22,9 +23,9 @@ class getid3_gzip extends getid3_handler { // public: Optional file list - disable for speed. - var $option_gzip_parse_contents = false; // decode gzipped files, if possible, and parse recursively (.tar.gz for example) + public $option_gzip_parse_contents = false; // decode gzipped files, if possible, and parse recursively (.tar.gz for example) - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'gzip'; @@ -35,12 +36,12 @@ class getid3_gzip extends getid3_handler { //|ID1|ID2|CM |FLG| MTIME |XFL|OS | //+---+---+---+---+---+---+---+---+---+---+ - if ($info['filesize'] > $info['php_memory_limit']) { + if ($info['php_memory_limit'] && ($info['filesize'] > $info['php_memory_limit'])) { $info['error'][] = 'File is too large ('.number_format($info['filesize']).' bytes) to read into memory (limit: '.number_format($info['php_memory_limit'] / 1048576).'MB)'; return false; } - fseek($this->getid3->fp, 0); - $buffer = fread($this->getid3->fp, $info['filesize']); + $this->fseek(0); + $buffer = $this->fread($info['filesize']); $arr_members = explode("\x1F\x8B\x08", $buffer); while (true) { @@ -55,7 +56,7 @@ class getid3_gzip extends getid3_handler { $attr = unpack($unpack_header, substr($buf, 0, $start_length)); if (!$this->get_os_type(ord($attr['os']))) { // Merge member with previous if wrong OS type - $arr_members[$i - 1] .= $buf; + $arr_members[($i - 1)] .= $buf; $arr_members[$i] = ''; $is_wrong_members = true; continue; @@ -140,8 +141,9 @@ class getid3_gzip extends getid3_handler { //|...original file name, zero-terminated...| //+=========================================+ // GZIP files may have only one file, with no filename, so assume original filename is current filename without .gz - $thisInfo['filename'] = preg_replace('#\.gz$#i', '', $info['filename']); + $thisInfo['filename'] = preg_replace('#\\.gz$#i', '', $info['filename']); if ($thisInfo['flags']['filename']) { + $thisInfo['filename'] = ''; while (true) { if (ord($buff[$fpointer]) == 0) { $fpointer++; @@ -245,7 +247,7 @@ class getid3_gzip extends getid3_handler { } // Converts the OS type - function get_os_type($key) { + public function get_os_type($key) { static $os_type = array( '0' => 'FAT filesystem (MS-DOS, OS/2, NT/Win32)', '1' => 'Amiga', @@ -267,7 +269,7 @@ class getid3_gzip extends getid3_handler { } // Converts the eXtra FLags - function get_xflag_type($key) { + public function get_xflag_type($key) { static $xflag_type = array( '0' => 'unknown', '2' => 'maximum compression', @@ -277,4 +279,3 @@ class getid3_gzip extends getid3_handler { } } -?> diff --git a/app/library/getid3/module.archive.rar.php b/app/library/getid3/getid3/module.archive.rar.php old mode 100644 new mode 100755 similarity index 92% rename from app/library/getid3/module.archive.rar.php rename to app/library/getid3/getid3/module.archive.rar.php index 4f5d46f8..665cf1cd --- a/app/library/getid3/module.archive.rar.php +++ b/app/library/getid3/getid3/module.archive.rar.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,9 +18,9 @@ class getid3_rar extends getid3_handler { - var $option_use_rar_extension = false; + public $option_use_rar_extension = false; - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'rar'; @@ -48,6 +49,3 @@ class getid3_rar extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.archive.szip.php b/app/library/getid3/getid3/module.archive.szip.php old mode 100644 new mode 100755 similarity index 87% rename from app/library/getid3/module.archive.szip.php rename to app/library/getid3/getid3/module.archive.szip.php index 3be62532..be152a39 --- a/app/library/getid3/module.archive.szip.php +++ b/app/library/getid3/getid3/module.archive.szip.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,11 +18,11 @@ class getid3_szip extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $SZIPHeader = fread($this->getid3->fp, 6); + $this->fseek($info['avdataoffset']); + $SZIPHeader = $this->fread(6); if (substr($SZIPHeader, 0, 4) != "SZ\x0A\x04") { $info['error'][] = 'Expecting "53 5A 0A 04" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($SZIPHeader, 0, 4)).'"'; return false; @@ -29,20 +30,22 @@ class getid3_szip extends getid3_handler $info['fileformat'] = 'szip'; $info['szip']['major_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 4, 1)); $info['szip']['minor_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 5, 1)); +$info['error'][] = 'SZIP parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; +return false; - while (!feof($this->getid3->fp)) { - $NextBlockID = fread($this->getid3->fp, 2); + while (!$this->feof()) { + $NextBlockID = $this->fread(2); switch ($NextBlockID) { case 'SZ': // Note that szip files can be concatenated, this has the same effect as // concatenating the files. this also means that global header blocks // might be present between directory/data blocks. - fseek($this->getid3->fp, 4, SEEK_CUR); + $this->fseek(4, SEEK_CUR); break; case 'BH': - $BHheaderbytes = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 3)); - $BHheaderdata = fread($this->getid3->fp, $BHheaderbytes); + $BHheaderbytes = getid3_lib::BigEndian2Int($this->fread(3)); + $BHheaderdata = $this->fread($BHheaderbytes); $BHheaderoffset = 0; while (strpos($BHheaderdata, "\x00", $BHheaderoffset) > 0) { //filename as \0 terminated string (empty string indicates end) @@ -92,5 +95,3 @@ class getid3_szip extends getid3_handler } } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.archive.tar.php b/app/library/getid3/getid3/module.archive.tar.php old mode 100644 new mode 100755 similarity index 93% rename from app/library/getid3/module.archive.tar.php rename to app/library/getid3/getid3/module.archive.tar.php index 94d32039..9e8d72a2 --- a/app/library/getid3/module.archive.tar.php +++ b/app/library/getid3/getid3/module.archive.tar.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -14,7 +15,7 @@ ///////////////////////////////////////////////////////////////// // // // Module originally written by // -// Mike Mozolin // +// Mike Mozolin // // // ///////////////////////////////////////////////////////////////// @@ -22,7 +23,7 @@ class getid3_tar extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'tar'; @@ -31,9 +32,9 @@ class getid3_tar extends getid3_handler $unpack_header = 'a100fname/a8mode/a8uid/a8gid/a12size/a12mtime/a8chksum/a1typflag/a100lnkname/a6magic/a2ver/a32uname/a32gname/a8devmaj/a8devmin/a155prefix'; $null_512k = str_repeat("\x00", 512); // end-of-file marker - fseek($this->getid3->fp, 0); + $this->fseek(0); while (!feof($this->getid3->fp)) { - $buffer = fread($this->getid3->fp, 512); + $buffer = $this->fread(512); if (strlen($buffer) < 512) { break; } @@ -82,12 +83,12 @@ class getid3_tar extends getid3_handler } // Read to the next chunk - fseek($this->getid3->fp, $size, SEEK_CUR); + $this->fseek($size, SEEK_CUR); $diff = $size % 512; if ($diff != 0) { // Padding, throw away - fseek($this->getid3->fp, (512 - $diff), SEEK_CUR); + $this->fseek((512 - $diff), SEEK_CUR); } // Protect against tar-files with garbage at the end if ($name == '') { @@ -96,13 +97,13 @@ class getid3_tar extends getid3_handler $info['tar']['file_details'][$name] = array ( 'name' => $name, 'mode_raw' => $mode, - 'mode' => getid3_tar::display_perms($mode), + 'mode' => self::display_perms($mode), 'uid' => $uid, 'gid' => $gid, 'size' => $size, 'mtime' => $mtime, 'chksum' => $chksum, - 'typeflag' => getid3_tar::get_flag_type($typflag), + 'typeflag' => self::get_flag_type($typflag), 'linkname' => $lnkname, 'magic' => $magic, 'version' => $ver, @@ -117,7 +118,7 @@ class getid3_tar extends getid3_handler } // Parses the file mode to file permissions - function display_perms($mode) { + public function display_perms($mode) { // Determine Type if ($mode & 0x1000) $type='p'; // FIFO pipe elseif ($mode & 0x2000) $type='c'; // Character special @@ -152,7 +153,7 @@ class getid3_tar extends getid3_handler } // Converts the file type - function get_flag_type($typflag) { + public function get_flag_type($typflag) { static $flag_types = array( '0' => 'LF_NORMAL', '1' => 'LF_LINK', @@ -174,5 +175,3 @@ class getid3_tar extends getid3_handler } } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.archive.zip.php b/app/library/getid3/getid3/module.archive.zip.php old mode 100644 new mode 100755 similarity index 64% rename from app/library/getid3/module.archive.zip.php rename to app/library/getid3/getid3/module.archive.zip.php index 7db8fd23..3716dcdd --- a/app/library/getid3/module.archive.zip.php +++ b/app/library/getid3/getid3/module.archive.zip.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,7 +18,7 @@ class getid3_zip extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'zip'; @@ -36,16 +37,16 @@ class getid3_zip extends getid3_handler $EOCDsearchCounter = 0; while ($EOCDsearchCounter++ < 512) { - fseek($this->getid3->fp, -128 * $EOCDsearchCounter, SEEK_END); - $EOCDsearchData = fread($this->getid3->fp, 128).$EOCDsearchData; + $this->fseek(-128 * $EOCDsearchCounter, SEEK_END); + $EOCDsearchData = $this->fread(128).$EOCDsearchData; if (strstr($EOCDsearchData, 'PK'."\x05\x06")) { $EOCDposition = strpos($EOCDsearchData, 'PK'."\x05\x06"); - fseek($this->getid3->fp, (-128 * $EOCDsearchCounter) + $EOCDposition, SEEK_END); + $this->fseek((-128 * $EOCDsearchCounter) + $EOCDposition, SEEK_END); $info['zip']['end_central_directory'] = $this->ZIPparseEndOfCentralDirectory(); - fseek($this->getid3->fp, $info['zip']['end_central_directory']['directory_offset'], SEEK_SET); + $this->fseek($info['zip']['end_central_directory']['directory_offset']); $info['zip']['entries_count'] = 0; while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($this->getid3->fp)) { $info['zip']['central_directory'][] = $centraldirectoryentry; @@ -53,7 +54,8 @@ class getid3_zip extends getid3_handler $info['zip']['compressed_size'] += $centraldirectoryentry['compressed_size']; $info['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size']; - if ($centraldirectoryentry['uncompressed_size'] > 0) { + //if ($centraldirectoryentry['uncompressed_size'] > 0) { zero-byte files are valid + if (!empty($centraldirectoryentry['filename'])) { $info['zip']['files'] = getid3_lib::array_merge_clobber($info['zip']['files'], getid3_lib::CreateDeepArray($centraldirectoryentry['filename'], '/', $centraldirectoryentry['uncompressed_size'])); } } @@ -77,37 +79,58 @@ class getid3_zip extends getid3_handler $info['zip']['compression_speed'] = 'store'; } - return true; + // secondary check - we (should) already have all the info we NEED from the Central Directory above, but scanning each + // Local File Header entry will + foreach ($info['zip']['central_directory'] as $central_directory_entry) { + $this->fseek($central_directory_entry['entry_offset']); + if ($fileentry = $this->ZIPparseLocalFileHeader()) { + $info['zip']['entries'][] = $fileentry; + } else { + $info['warning'][] = 'Error parsing Local File Header at offset '.$central_directory_entry['entry_offset']; + } + } + if (!empty($info['zip']['files']['[Content_Types].xml']) && + !empty($info['zip']['files']['_rels']['.rels']) && + !empty($info['zip']['files']['docProps']['app.xml']) && + !empty($info['zip']['files']['docProps']['core.xml'])) { + // http://technet.microsoft.com/en-us/library/cc179224.aspx + $info['fileformat'] = 'zip.msoffice'; + if (!empty($ThisFileInfo['zip']['files']['ppt'])) { + $info['mime_type'] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; + } elseif (!empty($ThisFileInfo['zip']['files']['xl'])) { + $info['mime_type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; + } elseif (!empty($ThisFileInfo['zip']['files']['word'])) { + $info['mime_type'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; + } + } + + return true; } } } - if ($this->getZIPentriesFilepointer()) { - - // central directory couldn't be found and/or parsed - // scan through actual file data entries, recover as much as possible from probable trucated file - if ($info['zip']['compressed_size'] > ($info['filesize'] - 46 - 22)) { - $info['error'][] = 'Warning: Truncated file! - Total compressed file sizes ('.$info['zip']['compressed_size'].' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures ('.($info['filesize'] - 46 - 22).' bytes)'; - } - $info['error'][] = 'Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete'; - foreach ($info['zip']['entries'] as $key => $valuearray) { - $info['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size']; - } - return true; - - } else { - + if (!$this->getZIPentriesFilepointer()) { unset($info['zip']); $info['fileformat'] = ''; $info['error'][] = 'Cannot find End Of Central Directory (truncated file?)'; return false; - } + + // central directory couldn't be found and/or parsed + // scan through actual file data entries, recover as much as possible from probable trucated file + if ($info['zip']['compressed_size'] > ($info['filesize'] - 46 - 22)) { + $info['error'][] = 'Warning: Truncated file! - Total compressed file sizes ('.$info['zip']['compressed_size'].' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures ('.($info['filesize'] - 46 - 22).' bytes)'; + } + $info['error'][] = 'Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete'; + foreach ($info['zip']['entries'] as $key => $valuearray) { + $info['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size']; + } + return true; } - function getZIPHeaderFilepointerTopDown() { + public function getZIPHeaderFilepointerTopDown() { $info = &$this->getid3->info; $info['fileformat'] = 'zip'; @@ -153,7 +176,7 @@ class getid3_zip extends getid3_handler } - function getZIPentriesFilepointer() { + public function getZIPentriesFilepointer() { $info = &$this->getid3->info; $info['zip']['compressed_size'] = 0; @@ -176,15 +199,15 @@ class getid3_zip extends getid3_handler } - function ZIPparseLocalFileHeader() { - $LocalFileHeader['offset'] = ftell($this->getid3->fp); + public function ZIPparseLocalFileHeader() { + $LocalFileHeader['offset'] = $this->ftell(); - $ZIPlocalFileHeader = fread($this->getid3->fp, 30); + $ZIPlocalFileHeader = $this->fread(30); $LocalFileHeader['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 0, 4)); - if ($LocalFileHeader['raw']['signature'] != 0x04034B50) { + if ($LocalFileHeader['raw']['signature'] != 0x04034B50) { // "PK\x03\x04" // invalid Local File Header Signature - fseek($this->getid3->fp, $LocalFileHeader['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + $this->fseek($LocalFileHeader['offset']); // seek back to where filepointer originally was so it can be handled properly return false; } $LocalFileHeader['raw']['extract_version'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 4, 2)); @@ -208,7 +231,7 @@ class getid3_zip extends getid3_handler $FilenameExtrafieldLength = $LocalFileHeader['raw']['filename_length'] + $LocalFileHeader['raw']['extra_field_length']; if ($FilenameExtrafieldLength > 0) { - $ZIPlocalFileHeader .= fread($this->getid3->fp, $FilenameExtrafieldLength); + $ZIPlocalFileHeader .= $this->fread($FilenameExtrafieldLength); if ($LocalFileHeader['raw']['filename_length'] > 0) { $LocalFileHeader['filename'] = substr($ZIPlocalFileHeader, 30, $LocalFileHeader['raw']['filename_length']); @@ -218,30 +241,69 @@ class getid3_zip extends getid3_handler } } - $LocalFileHeader['data_offset'] = ftell($this->getid3->fp); - //$LocalFileHeader['compressed_data'] = fread($this->getid3->fp, $LocalFileHeader['raw']['compressed_size']); - fseek($this->getid3->fp, $LocalFileHeader['raw']['compressed_size'], SEEK_CUR); + if ($LocalFileHeader['compressed_size'] == 0) { + // *Could* be a zero-byte file + // But could also be a file written on the fly that didn't know compressed filesize beforehand. + // Correct compressed filesize should be in the data_descriptor located after this file data, and also in Central Directory (at end of zip file) + if (!empty($this->getid3->info['zip']['central_directory'])) { + foreach ($this->getid3->info['zip']['central_directory'] as $central_directory_entry) { + if ($central_directory_entry['entry_offset'] == $LocalFileHeader['offset']) { + if ($central_directory_entry['compressed_size'] > 0) { + // overwrite local zero value (but not ['raw']'compressed_size']) so that seeking for data_descriptor (and next file entry) works correctly + $LocalFileHeader['compressed_size'] = $central_directory_entry['compressed_size']; + } + break; + } + } + } + + } + $LocalFileHeader['data_offset'] = $this->ftell(); + $this->fseek($LocalFileHeader['compressed_size'], SEEK_CUR); // this should (but may not) match value in $LocalFileHeader['raw']['compressed_size'] -- $LocalFileHeader['compressed_size'] could have been overwritten above with value from Central Directory if ($LocalFileHeader['flags']['data_descriptor_used']) { - $DataDescriptor = fread($this->getid3->fp, 12); - $LocalFileHeader['data_descriptor']['crc_32'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 0, 4)); - $LocalFileHeader['data_descriptor']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 4, 4)); - $LocalFileHeader['data_descriptor']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 8, 4)); - } + $DataDescriptor = $this->fread(16); + $LocalFileHeader['data_descriptor']['signature'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 0, 4)); + if ($LocalFileHeader['data_descriptor']['signature'] != 0x08074B50) { // "PK\x07\x08" + $this->getid3->warning[] = 'invalid Local File Header Data Descriptor Signature at offset '.($this->ftell() - 16).' - expecting 08 07 4B 50, found '.getid3_lib::PrintHexBytes($LocalFileHeader['data_descriptor']['signature']); + $this->fseek($LocalFileHeader['offset']); // seek back to where filepointer originally was so it can be handled properly + return false; + } + $LocalFileHeader['data_descriptor']['crc_32'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 4, 4)); + $LocalFileHeader['data_descriptor']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 8, 4)); + $LocalFileHeader['data_descriptor']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 12, 4)); + if (!$LocalFileHeader['raw']['compressed_size'] && $LocalFileHeader['data_descriptor']['compressed_size']) { + foreach ($this->getid3->info['zip']['central_directory'] as $central_directory_entry) { + if ($central_directory_entry['entry_offset'] == $LocalFileHeader['offset']) { + if ($LocalFileHeader['data_descriptor']['compressed_size'] == $central_directory_entry['compressed_size']) { + // $LocalFileHeader['compressed_size'] already set from Central Directory + } else { + $this->getid3->info['warning'][] = 'conflicting compressed_size from data_descriptor ('.$LocalFileHeader['data_descriptor']['compressed_size'].') vs Central Directory ('.$central_directory_entry['compressed_size'].') for file at offset '.$LocalFileHeader['offset']; + } + if ($LocalFileHeader['data_descriptor']['uncompressed_size'] == $central_directory_entry['uncompressed_size']) { + $LocalFileHeader['uncompressed_size'] = $LocalFileHeader['data_descriptor']['uncompressed_size']; + } else { + $this->getid3->info['warning'][] = 'conflicting uncompressed_size from data_descriptor ('.$LocalFileHeader['data_descriptor']['uncompressed_size'].') vs Central Directory ('.$central_directory_entry['uncompressed_size'].') for file at offset '.$LocalFileHeader['offset']; + } + break; + } + } + } + } return $LocalFileHeader; } - function ZIPparseCentralDirectory() { - $CentralDirectory['offset'] = ftell($this->getid3->fp); + public function ZIPparseCentralDirectory() { + $CentralDirectory['offset'] = $this->ftell(); - $ZIPcentralDirectory = fread($this->getid3->fp, 46); + $ZIPcentralDirectory = $this->fread(46); $CentralDirectory['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 0, 4)); if ($CentralDirectory['raw']['signature'] != 0x02014B50) { // invalid Central Directory Signature - fseek($this->getid3->fp, $CentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + $this->fseek($CentralDirectory['offset']); // seek back to where filepointer originally was so it can be handled properly return false; } $CentralDirectory['raw']['create_version'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 4, 2)); @@ -273,7 +335,7 @@ class getid3_zip extends getid3_handler $FilenameExtrafieldCommentLength = $CentralDirectory['raw']['filename_length'] + $CentralDirectory['raw']['extra_field_length'] + $CentralDirectory['raw']['file_comment_length']; if ($FilenameExtrafieldCommentLength > 0) { - $FilenameExtrafieldComment = fread($this->getid3->fp, $FilenameExtrafieldCommentLength); + $FilenameExtrafieldComment = $this->fread($FilenameExtrafieldCommentLength); if ($CentralDirectory['raw']['filename_length'] > 0) { $CentralDirectory['filename'] = substr($FilenameExtrafieldComment, 0, $CentralDirectory['raw']['filename_length']); @@ -289,15 +351,15 @@ class getid3_zip extends getid3_handler return $CentralDirectory; } - function ZIPparseEndOfCentralDirectory() { - $EndOfCentralDirectory['offset'] = ftell($this->getid3->fp); + public function ZIPparseEndOfCentralDirectory() { + $EndOfCentralDirectory['offset'] = $this->ftell(); - $ZIPendOfCentralDirectory = fread($this->getid3->fp, 22); + $ZIPendOfCentralDirectory = $this->fread(22); $EndOfCentralDirectory['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 0, 4)); if ($EndOfCentralDirectory['signature'] != 0x06054B50) { // invalid End Of Central Directory Signature - fseek($this->getid3->fp, $EndOfCentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + $this->fseek($EndOfCentralDirectory['offset']); // seek back to where filepointer originally was so it can be handled properly return false; } $EndOfCentralDirectory['disk_number_current'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 4, 2)); @@ -309,15 +371,31 @@ class getid3_zip extends getid3_handler $EndOfCentralDirectory['comment_length'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 20, 2)); if ($EndOfCentralDirectory['comment_length'] > 0) { - $EndOfCentralDirectory['comment'] = fread($this->getid3->fp, $EndOfCentralDirectory['comment_length']); + $EndOfCentralDirectory['comment'] = $this->fread($EndOfCentralDirectory['comment_length']); } return $EndOfCentralDirectory; } - static function ZIPparseGeneralPurposeFlags($flagbytes, $compressionmethod) { - $ParsedFlags['encrypted'] = (bool) ($flagbytes & 0x0001); + public static function ZIPparseGeneralPurposeFlags($flagbytes, $compressionmethod) { + // https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip-printable.html + $ParsedFlags['encrypted'] = (bool) ($flagbytes & 0x0001); + // 0x0002 -- see below + // 0x0004 -- see below + $ParsedFlags['data_descriptor_used'] = (bool) ($flagbytes & 0x0008); + $ParsedFlags['enhanced_deflation'] = (bool) ($flagbytes & 0x0010); + $ParsedFlags['compressed_patched_data'] = (bool) ($flagbytes & 0x0020); + $ParsedFlags['strong_encryption'] = (bool) ($flagbytes & 0x0040); + // 0x0080 - unused + // 0x0100 - unused + // 0x0200 - unused + // 0x0400 - unused + $ParsedFlags['language_encoding'] = (bool) ($flagbytes & 0x0800); + // 0x1000 - reserved + $ParsedFlags['mask_header_values'] = (bool) ($flagbytes & 0x2000); + // 0x4000 - reserved + // 0x8000 - reserved switch ($compressionmethod) { case 6: @@ -343,13 +421,12 @@ class getid3_zip extends getid3_handler } break; } - $ParsedFlags['data_descriptor_used'] = (bool) ($flagbytes & 0x0008); return $ParsedFlags; } - static function ZIPversionOSLookup($index) { + public static function ZIPversionOSLookup($index) { static $ZIPversionOSLookup = array( 0 => 'MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)', 1 => 'Amiga', @@ -368,13 +445,16 @@ class getid3_zip extends getid3_handler 14 => 'VFAT', 15 => 'Alternate MVS', 16 => 'BeOS', - 17 => 'Tandem' + 17 => 'Tandem', + 18 => 'OS/400', + 19 => 'OS/X (Darwin)', ); return (isset($ZIPversionOSLookup[$index]) ? $ZIPversionOSLookup[$index] : '[unknown]'); } - static function ZIPcompressionMethodLookup($index) { + public static function ZIPcompressionMethodLookup($index) { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/ZIP.html static $ZIPcompressionMethodLookup = array( 0 => 'store', 1 => 'shrink', @@ -386,13 +466,25 @@ class getid3_zip extends getid3_handler 7 => 'tokenize', 8 => 'deflate', 9 => 'deflate64', - 10 => 'PKWARE Date Compression Library Imploding' + 10 => 'Imploded (old IBM TERSE)', + 11 => 'RESERVED[11]', + 12 => 'BZIP2', + 13 => 'RESERVED[13]', + 14 => 'LZMA (EFS)', + 15 => 'RESERVED[15]', + 16 => 'RESERVED[16]', + 17 => 'RESERVED[17]', + 18 => 'IBM TERSE (new)', + 19 => 'IBM LZ77 z Architecture (PFS)', + 96 => 'JPEG recompressed', + 97 => 'WavPack compressed', + 98 => 'PPMd version I, Rev 1', ); return (isset($ZIPcompressionMethodLookup[$index]) ? $ZIPcompressionMethodLookup[$index] : '[unknown]'); } - static function DOStime2UNIXtime($DOSdate, $DOStime) { + public static function DOStime2UNIXtime($DOSdate, $DOStime) { // wFatDate // Specifies the MS-DOS date. The date is a packed 16-bit value with the following format: // Bits Contents @@ -419,6 +511,3 @@ class getid3_zip extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio-video.asf.php b/app/library/getid3/getid3/module.audio-video.asf.php old mode 100644 new mode 100755 similarity index 94% rename from app/library/getid3/module.audio-video.asf.php rename to app/library/getid3/getid3/module.audio-video.asf.php index 0bb095f5..ee61d7dc --- a/app/library/getid3/module.audio-video.asf.php +++ b/app/library/getid3/getid3/module.audio-video.asf.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -15,10 +16,9 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); -class getid3_asf extends getid3_handler -{ +class getid3_asf extends getid3_handler { - function __construct(getID3 $getid3) { + public function __construct(getID3 $getid3) { parent::__construct($getid3); // extends getid3_handler::__construct() // initialize all GUID constants @@ -30,7 +30,7 @@ class getid3_asf extends getid3_handler } } - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; // Shortcuts @@ -66,25 +66,22 @@ class getid3_asf extends getid3_handler $info['fileformat'] = 'asf'; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $HeaderObjectData = fread($this->getid3->fp, 30); + $this->fseek($info['avdataoffset']); + $HeaderObjectData = $this->fread(30); $thisfile_asf_headerobject['objectid'] = substr($HeaderObjectData, 0, 16); $thisfile_asf_headerobject['objectid_guid'] = $this->BytestringToGUID($thisfile_asf_headerobject['objectid']); if ($thisfile_asf_headerobject['objectid'] != GETID3_ASF_Header_Object) { - $info['warning'][] = 'ASF header GUID {'.$this->BytestringToGUID($thisfile_asf_headerobject['objectid']).'} does not match expected "GETID3_ASF_Header_Object" GUID {'.$this->BytestringToGUID(GETID3_ASF_Header_Object).'}'; - unset($info['fileformat']); - unset($info['asf']); - return false; - break; + unset($info['fileformat'], $info['asf']); + return $this->error('ASF header GUID {'.$this->BytestringToGUID($thisfile_asf_headerobject['objectid']).'} does not match expected "GETID3_ASF_Header_Object" GUID {'.$this->BytestringToGUID(GETID3_ASF_Header_Object).'}'); } $thisfile_asf_headerobject['objectsize'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 16, 8)); $thisfile_asf_headerobject['headerobjects'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 24, 4)); $thisfile_asf_headerobject['reserved1'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 28, 1)); $thisfile_asf_headerobject['reserved2'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 29, 1)); - $NextObjectOffset = ftell($this->getid3->fp); - $ASFHeaderData = fread($this->getid3->fp, $thisfile_asf_headerobject['objectsize'] - 30); + $NextObjectOffset = $this->ftell(); + $ASFHeaderData = $this->fread($thisfile_asf_headerobject['objectsize'] - 30); $offset = 0; for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) { @@ -226,7 +223,7 @@ class getid3_asf extends getid3_handler $thisfile_audio['dataformat'] = (!empty($thisfile_audio['dataformat']) ? $thisfile_audio['dataformat'] : 'asf'); $thisfile_audio['bitrate_mode'] = (!empty($thisfile_audio['bitrate_mode']) ? $thisfile_audio['bitrate_mode'] : 'cbr'); - $audiodata = getid3_riff::RIFFparseWAVEFORMATex(substr($StreamPropertiesObjectData['type_specific_data'], 0, 16)); + $audiodata = getid3_riff::parseWAVEFORMATex(substr($StreamPropertiesObjectData['type_specific_data'], 0, 16)); unset($audiodata['raw']); $thisfile_audio = getid3_lib::array_merge_noclobber($audiodata, $thisfile_audio); break; @@ -284,7 +281,7 @@ class getid3_asf extends getid3_handler $offset += 4; $thisfile_asf_headerextensionobject['extension_data'] = substr($ASFHeaderData, $offset, $thisfile_asf_headerextensionobject['extension_data_size']); $unhandled_sections = 0; - $thisfile_asf_headerextensionobject['extension_data_parsed'] = $this->ASF_HeaderExtensionObjectDataParse($thisfile_asf_headerextensionobject['extension_data'], $unhandled_sections); + $thisfile_asf_headerextensionobject['extension_data_parsed'] = $this->HeaderExtensionObjectDataParse($thisfile_asf_headerextensionobject['extension_data'], $unhandled_sections); if ($unhandled_sections === 0) { unset($thisfile_asf_headerextensionobject['extension_data']); } @@ -332,7 +329,7 @@ class getid3_asf extends getid3_handler $thisfile_asf_codeclistobject_codecentries_current['type_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); $offset += 2; - $thisfile_asf_codeclistobject_codecentries_current['type'] = $this->ASFCodecListObjectTypeLookup($thisfile_asf_codeclistobject_codecentries_current['type_raw']); + $thisfile_asf_codeclistobject_codecentries_current['type'] = self::codecListObjectTypeLookup($thisfile_asf_codeclistobject_codecentries_current['type_raw']); $CodecNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character $offset += 2; @@ -826,22 +823,14 @@ class getid3_asf extends getid3_handler break; case 'id3': - // id3v2 module might not be loaded - if (class_exists('getid3_id3v2')) { - $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); - $tempfilehandle = fopen($tempfile, 'wb'); - $tempThisfileInfo = array('encoding'=>$info['encoding']); - fwrite($tempfilehandle, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); - fclose($tempfilehandle); + $this->getid3->include_module('tag.id3v2'); - $getid3_temp = new getID3(); - $getid3_temp->openfile($tempfile); - $getid3_id3v2 = new getid3_id3v2($getid3_temp); - $getid3_id3v2->Analyze(); - $info['id3v2'] = $getid3_temp->info['id3v2']; - unset($getid3_temp, $getid3_id3v2); + $getid3_id3v2 = new getid3_id3v2($this->getid3); + $getid3_id3v2->AnalyzeString($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); + unset($getid3_id3v2); - unlink($tempfile); + if ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length'] > 1024) { + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = ''; } break; @@ -860,7 +849,7 @@ class getid3_asf extends getid3_handler $wm_picture_offset = 0; $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 1)); $wm_picture_offset += 1; - $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type'] = $this->WMpictureTypeLookup($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id']); + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type'] = self::WMpictureTypeLookup($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id']); $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_size'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 4)); $wm_picture_offset += 4; @@ -1033,7 +1022,7 @@ class getid3_asf extends getid3_handler $audiomediaoffset = 0; - $thisfile_asf_audiomedia_currentstream = getid3_riff::RIFFparseWAVEFORMATex(substr($streamdata['type_specific_data'], $audiomediaoffset, 16)); + $thisfile_asf_audiomedia_currentstream = getid3_riff::parseWAVEFORMATex(substr($streamdata['type_specific_data'], $audiomediaoffset, 16)); $audiomediaoffset += 16; $thisfile_audio['lossless'] = false; @@ -1141,7 +1130,7 @@ class getid3_asf extends getid3_handler } } - $thisfile_asf_videomedia_currentstream['format_data']['codec'] = getid3_riff::RIFFfourccLookup($thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']); + $thisfile_asf_videomedia_currentstream['format_data']['codec'] = getid3_riff::fourccLookup($thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']); $thisfile_video['streams'][$streamnumber]['fourcc'] = $thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']; $thisfile_video['streams'][$streamnumber]['codec'] = $thisfile_asf_videomedia_currentstream['format_data']['codec']; @@ -1156,8 +1145,8 @@ class getid3_asf extends getid3_handler } } - while (ftell($this->getid3->fp) < $info['avdataend']) { - $NextObjectDataHeader = fread($this->getid3->fp, 24); + while ($this->ftell() < $info['avdataend']) { + $NextObjectDataHeader = $this->fread(24); $offset = 0; $NextObjectGUID = substr($NextObjectDataHeader, 0, 16); $offset += 16; @@ -1179,7 +1168,7 @@ class getid3_asf extends getid3_handler $thisfile_asf['data_object'] = array(); $thisfile_asf_dataobject = &$thisfile_asf['data_object']; - $DataObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 50 - 24); + $DataObjectData = $NextObjectDataHeader.$this->fread(50 - 24); $offset = 24; $thisfile_asf_dataobject['objectid'] = $NextObjectGUID; @@ -1207,9 +1196,9 @@ class getid3_asf extends getid3_handler // * * Error Correction Present bits 1 // If set, use Opaque Data Packet structure, else use Payload structure // * Error Correction Data - $info['avdataoffset'] = ftell($this->getid3->fp); - fseek($this->getid3->fp, ($thisfile_asf_dataobject['objectsize'] - 50), SEEK_CUR); // skip actual audio/video data - $info['avdataend'] = ftell($this->getid3->fp); + $info['avdataoffset'] = $this->ftell(); + $this->fseek(($thisfile_asf_dataobject['objectsize'] - 50), SEEK_CUR); // skip actual audio/video data + $info['avdataend'] = $this->ftell(); break; case GETID3_ASF_Simple_Index_Object: @@ -1229,7 +1218,7 @@ class getid3_asf extends getid3_handler $thisfile_asf['simple_index_object'] = array(); $thisfile_asf_simpleindexobject = &$thisfile_asf['simple_index_object']; - $SimpleIndexObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 56 - 24); + $SimpleIndexObjectData = $NextObjectDataHeader.$this->fread(56 - 24); $offset = 24; $thisfile_asf_simpleindexobject['objectid'] = $NextObjectGUID; @@ -1246,7 +1235,7 @@ class getid3_asf extends getid3_handler $thisfile_asf_simpleindexobject['index_entries_count'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4)); $offset += 4; - $IndexEntriesData = $SimpleIndexObjectData.fread($this->getid3->fp, 6 * $thisfile_asf_simpleindexobject['index_entries_count']); + $IndexEntriesData = $SimpleIndexObjectData.$this->fread(6 * $thisfile_asf_simpleindexobject['index_entries_count']); for ($IndexEntriesCounter = 0; $IndexEntriesCounter < $thisfile_asf_simpleindexobject['index_entries_count']; $IndexEntriesCounter++) { $thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_number'] = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4)); $offset += 4; @@ -1283,7 +1272,7 @@ class getid3_asf extends getid3_handler $thisfile_asf['asf_index_object'] = array(); $thisfile_asf_asfindexobject = &$thisfile_asf['asf_index_object']; - $ASFIndexObjectData = $NextObjectDataHeader.fread($this->getid3->fp, 34 - 24); + $ASFIndexObjectData = $NextObjectDataHeader.$this->fread(34 - 24); $offset = 24; $thisfile_asf_asfindexobject['objectid'] = $NextObjectGUID; @@ -1297,7 +1286,7 @@ class getid3_asf extends getid3_handler $thisfile_asf_asfindexobject['index_blocks_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); $offset += 4; - $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count']); + $ASFIndexObjectData .= $this->fread(4 * $thisfile_asf_asfindexobject['index_specifiers_count']); for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $IndexSpecifierStreamNumber = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); $offset += 2; @@ -1307,17 +1296,17 @@ class getid3_asf extends getid3_handler $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type_text'] = $this->ASFIndexObjectIndexTypeLookup($thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type']); } - $ASFIndexObjectData .= fread($this->getid3->fp, 4); + $ASFIndexObjectData .= $this->fread(4); $thisfile_asf_asfindexobject['index_entry_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); $offset += 4; - $ASFIndexObjectData .= fread($this->getid3->fp, 8 * $thisfile_asf_asfindexobject['index_specifiers_count']); + $ASFIndexObjectData .= $this->fread(8 * $thisfile_asf_asfindexobject['index_specifiers_count']); for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $thisfile_asf_asfindexobject['block_positions'][$IndexSpecifiersCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 8)); $offset += 8; } - $ASFIndexObjectData .= fread($this->getid3->fp, 4 * $thisfile_asf_asfindexobject['index_specifiers_count'] * $thisfile_asf_asfindexobject['index_entry_count']); + $ASFIndexObjectData .= $this->fread(4 * $thisfile_asf_asfindexobject['index_specifiers_count'] * $thisfile_asf_asfindexobject['index_entry_count']); for ($IndexEntryCounter = 0; $IndexEntryCounter < $thisfile_asf_asfindexobject['index_entry_count']; $IndexEntryCounter++) { for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { $thisfile_asf_asfindexobject['offsets'][$IndexSpecifiersCounter][$IndexEntryCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); @@ -1332,9 +1321,9 @@ class getid3_asf extends getid3_handler if ($this->GUIDname($NextObjectGUIDtext)) { $info['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF body at offset '.($offset - 16 - 8); } else { - $info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.(ftell($this->getid3->fp) - 16 - 8); + $info['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.($this->ftell() - 16 - 8); } - fseek($this->getid3->fp, ($NextObjectSize - 16 - 8), SEEK_CUR); + $this->fseek(($NextObjectSize - 16 - 8), SEEK_CUR); break; } } @@ -1433,10 +1422,10 @@ class getid3_asf extends getid3_handler $thisfile_video['dataformat'] = (!empty($thisfile_video['dataformat']) ? $thisfile_video['dataformat'] : 'asf'); } if (!empty($thisfile_video['streams'])) { - $thisfile_video['streams']['resolution_x'] = 0; - $thisfile_video['streams']['resolution_y'] = 0; + $thisfile_video['resolution_x'] = 0; + $thisfile_video['resolution_y'] = 0; foreach ($thisfile_video['streams'] as $key => $valuearray) { - if (($valuearray['resolution_x'] > $thisfile_video['streams']['resolution_x']) || ($valuearray['resolution_y'] > $thisfile_video['streams']['resolution_y'])) { + if (($valuearray['resolution_x'] > $thisfile_video['resolution_x']) || ($valuearray['resolution_y'] > $thisfile_video['resolution_y'])) { $thisfile_video['resolution_x'] = $valuearray['resolution_x']; $thisfile_video['resolution_y'] = $valuearray['resolution_y']; } @@ -1451,18 +1440,17 @@ class getid3_asf extends getid3_handler return true; } - static function ASFCodecListObjectTypeLookup($CodecListType) { - static $ASFCodecListObjectTypeLookup = array(); - if (empty($ASFCodecListObjectTypeLookup)) { - $ASFCodecListObjectTypeLookup[0x0001] = 'Video Codec'; - $ASFCodecListObjectTypeLookup[0x0002] = 'Audio Codec'; - $ASFCodecListObjectTypeLookup[0xFFFF] = 'Unknown Codec'; - } + public static function codecListObjectTypeLookup($CodecListType) { + static $lookup = array( + 0x0001 => 'Video Codec', + 0x0002 => 'Audio Codec', + 0xFFFF => 'Unknown Codec' + ); - return (isset($ASFCodecListObjectTypeLookup[$CodecListType]) ? $ASFCodecListObjectTypeLookup[$CodecListType] : 'Invalid Codec Type'); + return (isset($lookup[$CodecListType]) ? $lookup[$CodecListType] : 'Invalid Codec Type'); } - static function KnownGUIDs() { + public static function KnownGUIDs() { static $GUIDarray = array( 'GETID3_ASF_Extended_Stream_Properties_Object' => '14E6A5CB-C672-4332-8399-A96952065B5A', 'GETID3_ASF_Padding_Object' => '1806D474-CADF-4509-A4BA-9AABCB96AAE8', @@ -1576,15 +1564,15 @@ class getid3_asf extends getid3_handler return $GUIDarray; } - static function GUIDname($GUIDstring) { + public static function GUIDname($GUIDstring) { static $GUIDarray = array(); if (empty($GUIDarray)) { - $GUIDarray = getid3_asf::KnownGUIDs(); + $GUIDarray = self::KnownGUIDs(); } return array_search($GUIDstring, $GUIDarray); } - static function ASFIndexObjectIndexTypeLookup($id) { + public static function ASFIndexObjectIndexTypeLookup($id) { static $ASFIndexObjectIndexTypeLookup = array(); if (empty($ASFIndexObjectIndexTypeLookup)) { $ASFIndexObjectIndexTypeLookup[1] = 'Nearest Past Data Packet'; @@ -1594,7 +1582,7 @@ class getid3_asf extends getid3_handler return (isset($ASFIndexObjectIndexTypeLookup[$id]) ? $ASFIndexObjectIndexTypeLookup[$id] : 'invalid'); } - static function GUIDtoBytestring($GUIDstring) { + public static function GUIDtoBytestring($GUIDstring) { // Microsoft defines these 16-byte (128-bit) GUIDs in the strangest way: // first 4 bytes are in little-endian order // next 2 bytes are appended in little-endian order @@ -1629,7 +1617,7 @@ class getid3_asf extends getid3_handler return $hexbytecharstring; } - static function BytestringToGUID($Bytestring) { + public static function BytestringToGUID($Bytestring) { $GUIDstring = str_pad(dechex(ord($Bytestring{3})), 2, '0', STR_PAD_LEFT); $GUIDstring .= str_pad(dechex(ord($Bytestring{2})), 2, '0', STR_PAD_LEFT); $GUIDstring .= str_pad(dechex(ord($Bytestring{1})), 2, '0', STR_PAD_LEFT); @@ -1654,7 +1642,7 @@ class getid3_asf extends getid3_handler return strtoupper($GUIDstring); } - static function FILETIMEtoUNIXtime($FILETIME, $round=true) { + public static function FILETIMEtoUNIXtime($FILETIME, $round=true) { // FILETIME is a 64-bit unsigned integer representing // the number of 100-nanosecond intervals since January 1, 1601 // UNIX timestamp is number of seconds since January 1, 1970 @@ -1665,32 +1653,38 @@ class getid3_asf extends getid3_handler return ($FILETIME - 116444736000000000) / 10000000; } - static function WMpictureTypeLookup($WMpictureType) { - static $WMpictureTypeLookup = array(); - if (empty($WMpictureTypeLookup)) { - $WMpictureTypeLookup[0x03] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Front Cover'); - $WMpictureTypeLookup[0x04] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Back Cover'); - $WMpictureTypeLookup[0x00] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'User Defined'); - $WMpictureTypeLookup[0x05] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Leaflet Page'); - $WMpictureTypeLookup[0x06] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Media Label'); - $WMpictureTypeLookup[0x07] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Lead Artist'); - $WMpictureTypeLookup[0x08] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Artist'); - $WMpictureTypeLookup[0x09] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Conductor'); - $WMpictureTypeLookup[0x0A] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Band'); - $WMpictureTypeLookup[0x0B] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Composer'); - $WMpictureTypeLookup[0x0C] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Lyricist'); - $WMpictureTypeLookup[0x0D] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Recording Location'); - $WMpictureTypeLookup[0x0E] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'During Recording'); - $WMpictureTypeLookup[0x0F] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'During Performance'); - $WMpictureTypeLookup[0x10] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Video Screen Capture'); - $WMpictureTypeLookup[0x12] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Illustration'); - $WMpictureTypeLookup[0x13] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Band Logotype'); - $WMpictureTypeLookup[0x14] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Publisher Logotype'); + public static function WMpictureTypeLookup($WMpictureType) { + static $lookup = null; + if ($lookup === null) { + $lookup = array( + 0x03 => 'Front Cover', + 0x04 => 'Back Cover', + 0x00 => 'User Defined', + 0x05 => 'Leaflet Page', + 0x06 => 'Media Label', + 0x07 => 'Lead Artist', + 0x08 => 'Artist', + 0x09 => 'Conductor', + 0x0A => 'Band', + 0x0B => 'Composer', + 0x0C => 'Lyricist', + 0x0D => 'Recording Location', + 0x0E => 'During Recording', + 0x0F => 'During Performance', + 0x10 => 'Video Screen Capture', + 0x12 => 'Illustration', + 0x13 => 'Band Logotype', + 0x14 => 'Publisher Logotype' + ); + $lookup = array_map(function($str) { + return getid3_lib::iconv_fallback('UTF-8', 'UTF-16LE', $str); + }, $lookup); } - return (isset($WMpictureTypeLookup[$WMpictureType]) ? $WMpictureTypeLookup[$WMpictureType] : ''); + + return (isset($lookup[$WMpictureType]) ? $lookup[$WMpictureType] : ''); } - function ASF_HeaderExtensionObjectDataParse(&$asf_header_extension_object_data, &$unhandled_sections) { + public function HeaderExtensionObjectDataParse(&$asf_header_extension_object_data, &$unhandled_sections) { // http://msdn.microsoft.com/en-us/library/bb643323.aspx $offset = 0; @@ -1825,7 +1819,7 @@ class getid3_asf extends getid3_handler $descriptionRecord['data_type'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); $offset += 2; - $descriptionRecord['data_type_text'] = $this->ASFmetadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']); + $descriptionRecord['data_type_text'] = self::metadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']); $descriptionRecord['data_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); $offset += 4; @@ -1897,7 +1891,7 @@ class getid3_asf extends getid3_handler $descriptionRecord['data_type'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 2)); $offset += 2; - $descriptionRecord['data_type_text'] = $this->ASFmetadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']); + $descriptionRecord['data_type_text'] = self::metadataLibraryObjectDataTypeLookup($descriptionRecord['data_type']); $descriptionRecord['data_length'] = getid3_lib::LittleEndian2Int(substr($asf_header_extension_object_data, $offset, 4)); $offset += 4; @@ -1937,8 +1931,8 @@ class getid3_asf extends getid3_handler } - static function ASFmetadataLibraryObjectDataTypeLookup($id) { - static $ASFmetadataLibraryObjectDataTypeLookup = array( + public static function metadataLibraryObjectDataTypeLookup($id) { + static $lookup = array( 0x0000 => 'Unicode string', // The data consists of a sequence of Unicode characters 0x0001 => 'BYTE array', // The type of the data is implementation-specific 0x0002 => 'BOOL', // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer. Only 0x0000 or 0x0001 are permitted values @@ -1947,10 +1941,10 @@ class getid3_asf extends getid3_handler 0x0005 => 'WORD', // The data is 2 bytes long and should be interpreted as a 16-bit unsigned integer 0x0006 => 'GUID', // The data is 16 bytes long and should be interpreted as a 128-bit GUID ); - return (isset($ASFmetadataLibraryObjectDataTypeLookup[$id]) ? $ASFmetadataLibraryObjectDataTypeLookup[$id] : 'invalid'); + return (isset($lookup[$id]) ? $lookup[$id] : 'invalid'); } - function ASF_WMpicture(&$data) { + public function ASF_WMpicture(&$data) { //typedef struct _WMPicture{ // LPWSTR pwszMIMEType; // BYTE bPictureType; @@ -1964,7 +1958,7 @@ class getid3_asf extends getid3_handler $offset = 0; $WMpicture['image_type_id'] = getid3_lib::LittleEndian2Int(substr($data, $offset, 1)); $offset += 1; - $WMpicture['image_type'] = $this->WMpictureTypeLookup($WMpicture['image_type_id']); + $WMpicture['image_type'] = self::WMpictureTypeLookup($WMpicture['image_type_id']); $WMpicture['image_size'] = getid3_lib::LittleEndian2Int(substr($data, $offset, 4)); $offset += 4; @@ -2002,13 +1996,13 @@ class getid3_asf extends getid3_handler // Remove terminator 00 00 and convert UTF-16LE to Latin-1 - static function TrimConvert($string) { - return trim(getid3_lib::iconv_fallback('UTF-16LE', 'ISO-8859-1', getid3_asf::TrimTerm($string)), ' '); + public static function TrimConvert($string) { + return trim(getid3_lib::iconv_fallback('UTF-16LE', 'ISO-8859-1', self::TrimTerm($string)), ' '); } // Remove terminator 00 00 - static function TrimTerm($string) { + public static function TrimTerm($string) { // remove terminator, only if present (it should be, but...) if (substr($string, -2) === "\x00\x00") { $string = substr($string, 0, -2); @@ -2017,5 +2011,3 @@ class getid3_asf extends getid3_handler } } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio-video.bink.php b/app/library/getid3/getid3/module.audio-video.bink.php old mode 100644 new mode 100755 similarity index 88% rename from app/library/getid3/module.audio-video.bink.php rename to app/library/getid3/getid3/module.audio-video.bink.php index 0a321396..4fd5b90b --- a/app/library/getid3/module.audio-video.bink.php +++ b/app/library/getid3/getid3/module.audio-video.bink.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,13 +18,13 @@ class getid3_bink extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['error'][] = 'Bink / Smacker files not properly processed by this version of getID3() ['.$this->getid3->version().']'; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $fileTypeID = fread($this->getid3->fp, 3); + $this->fseek($info['avdataoffset']); + $fileTypeID = $this->fread(3); switch ($fileTypeID) { case 'BIK': return $this->ParseBink(); @@ -43,12 +44,12 @@ $info['error'][] = 'Bink / Smacker files not properly processed by this version } - function ParseBink() { + public function ParseBink() { $info = &$this->getid3->info; $info['fileformat'] = 'bink'; $info['video']['dataformat'] = 'bink'; - $fileData = 'BIK'.fread($this->getid3->fp, 13); + $fileData = 'BIK'.$this->fread(13); $info['bink']['data_size'] = getid3_lib::LittleEndian2Int(substr($fileData, 4, 4)); $info['bink']['frame_count'] = getid3_lib::LittleEndian2Int(substr($fileData, 8, 2)); @@ -60,7 +61,7 @@ $info['error'][] = 'Bink / Smacker files not properly processed by this version return true; } - function ParseSmacker() { + public function ParseSmacker() { $info = &$this->getid3->info; $info['fileformat'] = 'smacker'; $info['video']['dataformat'] = 'smacker'; @@ -69,5 +70,3 @@ $info['error'][] = 'Bink / Smacker files not properly processed by this version } } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio-video.flv.php b/app/library/getid3/getid3/module.audio-video.flv.php old mode 100644 new mode 100755 similarity index 67% rename from app/library/getid3/module.audio-video.flv.php rename to app/library/getid3/getid3/module.audio-video.flv.php index ba3cd908..c5fbd4b0 --- a/app/library/getid3/module.audio-video.flv.php +++ b/app/library/getid3/getid3/module.audio-video.flv.php @@ -3,8 +3,9 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // // // -// FLV module by Seth Kaufman // +// FLV module by Seth Kaufman // // // // * version 0.1 (26 June 2005) // // // @@ -14,7 +15,7 @@ // // // * version 0.2 (22 February 2006) // // Support for On2 VP6 codec and meta information // -// by Steve Webster // +// by Steve Webster // // // // * version 0.3 (15 June 2006) // // Modified to not read entire file into memory // @@ -23,21 +24,26 @@ // * version 0.4 (07 December 2007) // // Bugfixes for incorrectly parsed FLV dimensions // // and incorrect parsing of onMetaTag // -// by Evgeny Moysevich // +// by Evgeny Moysevich // // // // * version 0.5 (21 May 2009) // // Fixed parsing of audio tags and added additional codec // // details. The duration is now read from onMetaTag (if // // exists), rather than parsing whole file // -// by Nigel Barnes // +// by Nigel Barnes // // // // * version 0.6 (24 May 2009) // // Better parsing of files with h264 video // -// by Evgeny Moysevich // +// by Evgeny Moysevich // // // // * version 0.6.1 (30 May 2011) // // prevent infinite loops in expGolombUe() // // // +// * version 0.7.0 (16 Jul 2013) // +// handle GETID3_FLV_VIDEO_VP6FLV_ALPHA // +// improved AVCSequenceParameterSetReader::readData() // +// by Xander Schouwerwou // +// // ///////////////////////////////////////////////////////////////// // // // module.audio-video.flv.php // @@ -67,38 +73,38 @@ define('H264_PROFILE_HIGH422', 122); define('H264_PROFILE_HIGH444', 144); define('H264_PROFILE_HIGH444_PREDICTIVE', 244); -class getid3_flv extends getid3_handler -{ - var $max_frames = 100000; // break out of the loop if too many frames have been scanned; only scan this many if meta frame does not contain useful duration +class getid3_flv extends getid3_handler { - function Analyze() { + const magic = 'FLV'; + + public $max_frames = 100000; // break out of the loop if too many frames have been scanned; only scan this many if meta frame does not contain useful duration + + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); $FLVdataLength = $info['avdataend'] - $info['avdataoffset']; - $FLVheader = fread($this->getid3->fp, 5); + $FLVheader = $this->fread(5); $info['fileformat'] = 'flv'; $info['flv']['header']['signature'] = substr($FLVheader, 0, 3); $info['flv']['header']['version'] = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1)); $TypeFlags = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1)); - $magic = 'FLV'; - if ($info['flv']['header']['signature'] != $magic) { - $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"'; - unset($info['flv']); - unset($info['fileformat']); + if ($info['flv']['header']['signature'] != self::magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(self::magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"'; + unset($info['flv'], $info['fileformat']); return false; } $info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04); $info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01); - $FrameSizeDataLength = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 4)); + $FrameSizeDataLength = getid3_lib::BigEndian2Int($this->fread(4)); $FLVheaderFrameLength = 9; if ($FrameSizeDataLength > $FLVheaderFrameLength) { - fseek($this->getid3->fp, $FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR); + $this->fseek($FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR); } $Duration = 0; $found_video = false; @@ -108,15 +114,15 @@ class getid3_flv extends getid3_handler $tagParseCount = 0; $info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0); $flv_framecount = &$info['flv']['framecount']; - while (((ftell($this->getid3->fp) + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime)) { - $ThisTagHeader = fread($this->getid3->fp, 16); + while ((($this->ftell() + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime)) { + $ThisTagHeader = $this->fread(16); $PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 0, 4)); $TagType = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 4, 1)); $DataLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 5, 3)); $Timestamp = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 8, 3)); $LastHeaderByte = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1)); - $NextOffset = ftell($this->getid3->fp) - 1 + $DataLength; + $NextOffset = $this->ftell() - 1 + $DataLength; if ($Timestamp > $Duration) { $Duration = $Timestamp; } @@ -140,10 +146,10 @@ class getid3_flv extends getid3_handler $found_video = true; $info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07; - $FLVvideoHeader = fread($this->getid3->fp, 11); + $FLVvideoHeader = $this->fread(11); if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) { - // this code block contributed by: moysevichgmail*com + // this code block contributed by: moysevichØgmail*com $AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1)); if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) { @@ -160,7 +166,7 @@ class getid3_flv extends getid3_handler //$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2)); $spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2)); // read the first SequenceParameterSet - $sps = fread($this->getid3->fp, $spsSize); + $sps = $this->fread($spsSize); if (strlen($sps) == $spsSize) { // make sure that whole SequenceParameterSet was red $spsReader = new AVCSequenceParameterSetReader($sps); $spsReader->readData(); @@ -169,7 +175,7 @@ class getid3_flv extends getid3_handler } } } - // end: moysevichgmail*com + // end: moysevichØgmail*com } elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) { @@ -185,19 +191,15 @@ class getid3_flv extends getid3_handler //$PictureSizeEnc <<= 1; //$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8; - $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2)); - $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)); - $PictureSizeEnc['x'] >>= 7; - $PictureSizeEnc['y'] >>= 7; + $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2)) >> 7; + $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)) >> 7; $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF; $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF; break; case 1: - $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3)); - $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3)); - $PictureSizeEnc['x'] >>= 7; - $PictureSizeEnc['y'] >>= 7; + $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3)) >> 7; + $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3)) >> 7; $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF; $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF; break; @@ -233,8 +235,22 @@ class getid3_flv extends getid3_handler break; } + + } elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_VP6FLV_ALPHA) { + + /* contributed by schouwerwouØgmail*com */ + if (!isset($info['video']['resolution_x'])) { // only when meta data isn't set + $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2)); + $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 2)); + $info['video']['resolution_x'] = ($PictureSizeEnc['x'] & 0xFF) << 3; + $info['video']['resolution_y'] = ($PictureSizeEnc['y'] & 0xFF) << 3; + } + /* end schouwerwouØgmail*com */ + + } + if (!empty($info['video']['resolution_x']) && !empty($info['video']['resolution_y'])) { + $info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y']; } - $info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y']; } break; @@ -242,8 +258,8 @@ class getid3_flv extends getid3_handler case GETID3_FLV_TAG_META: if (!$found_meta) { $found_meta = true; - fseek($this->getid3->fp, -1, SEEK_CUR); - $datachunk = fread($this->getid3->fp, $DataLength); + $this->fseek(-1, SEEK_CUR); + $datachunk = $this->fread($DataLength); $AMFstream = new AMFStream($datachunk); $reader = new AMFReader($AMFstream); $eventName = $reader->readData(); @@ -279,7 +295,7 @@ class getid3_flv extends getid3_handler // noop break; } - fseek($this->getid3->fp, $NextOffset, SEEK_SET); + $this->fseek($NextOffset); } $info['playtime_seconds'] = $Duration / 1000; @@ -288,16 +304,16 @@ class getid3_flv extends getid3_handler } if ($info['flv']['header']['hasAudio']) { - $info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['audio']['audioFormat']); - $info['audio']['sample_rate'] = $this->FLVaudioRate($info['flv']['audio']['audioRate']); - $info['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($info['flv']['audio']['audioSampleSize']); + $info['audio']['codec'] = self::audioFormatLookup($info['flv']['audio']['audioFormat']); + $info['audio']['sample_rate'] = self::audioRateLookup($info['flv']['audio']['audioRate']); + $info['audio']['bits_per_sample'] = self::audioBitDepthLookup($info['flv']['audio']['audioSampleSize']); $info['audio']['channels'] = $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo $info['audio']['lossless'] = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed $info['audio']['dataformat'] = 'flv'; } if (!empty($info['flv']['header']['hasVideo'])) { - $info['video']['codec'] = $this->FLVvideoCodec($info['flv']['video']['videoCodec']); + $info['video']['codec'] = self::videoCodecLookup($info['flv']['video']['videoCodec']); $info['video']['dataformat'] = 'flv'; $info['video']['lossless'] = false; } @@ -308,17 +324,17 @@ class getid3_flv extends getid3_handler $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; } if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) { - $info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['meta']['onMetaData']['audiocodecid']); + $info['audio']['codec'] = self::audioFormatLookup($info['flv']['meta']['onMetaData']['audiocodecid']); } if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) { - $info['video']['codec'] = $this->FLVvideoCodec($info['flv']['meta']['onMetaData']['videocodecid']); + $info['video']['codec'] = self::videoCodecLookup($info['flv']['meta']['onMetaData']['videocodecid']); } return true; } - function FLVaudioFormat($id) { - $FLVaudioFormat = array( + public static function audioFormatLookup($id) { + static $lookup = array( 0 => 'Linear PCM, platform endian', 1 => 'ADPCM', 2 => 'mp3', @@ -330,35 +346,35 @@ class getid3_flv extends getid3_handler 8 => 'G.711 mu-law logarithmic PCM', 9 => 'reserved', 10 => 'AAC', - 11 => false, // unknown? + 11 => 'Speex', 12 => false, // unknown? 13 => false, // unknown? 14 => 'mp3 8kHz', 15 => 'Device-specific sound', ); - return (isset($FLVaudioFormat[$id]) ? $FLVaudioFormat[$id] : false); + return (isset($lookup[$id]) ? $lookup[$id] : false); } - function FLVaudioRate($id) { - $FLVaudioRate = array( + public static function audioRateLookup($id) { + static $lookup = array( 0 => 5500, 1 => 11025, 2 => 22050, 3 => 44100, ); - return (isset($FLVaudioRate[$id]) ? $FLVaudioRate[$id] : false); + return (isset($lookup[$id]) ? $lookup[$id] : false); } - function FLVaudioBitDepth($id) { - $FLVaudioBitDepth = array( + public static function audioBitDepthLookup($id) { + static $lookup = array( 0 => 8, 1 => 16, ); - return (isset($FLVaudioBitDepth[$id]) ? $FLVaudioBitDepth[$id] : false); + return (isset($lookup[$id]) ? $lookup[$id] : false); } - function FLVvideoCodec($id) { - $FLVvideoCodec = array( + public static function videoCodecLookup($id) { + static $lookup = array( GETID3_FLV_VIDEO_H263 => 'Sorenson H.263', GETID3_FLV_VIDEO_SCREEN => 'Screen video', GETID3_FLV_VIDEO_VP6FLV => 'On2 VP6', @@ -366,87 +382,87 @@ class getid3_flv extends getid3_handler GETID3_FLV_VIDEO_SCREENV2 => 'Screen video v2', GETID3_FLV_VIDEO_H264 => 'Sorenson H.264', ); - return (isset($FLVvideoCodec[$id]) ? $FLVvideoCodec[$id] : false); + return (isset($lookup[$id]) ? $lookup[$id] : false); } } class AMFStream { - var $bytes; - var $pos; + public $bytes; + public $pos; - function AMFStream(&$bytes) { + public function __construct(&$bytes) { $this->bytes =& $bytes; $this->pos = 0; } - function readByte() { + public function readByte() { return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1)); } - function readInt() { + public function readInt() { return ($this->readByte() << 8) + $this->readByte(); } - function readLong() { + public function readLong() { return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte(); } - function readDouble() { + public function readDouble() { return getid3_lib::BigEndian2Float($this->read(8)); } - function readUTF() { + public function readUTF() { $length = $this->readInt(); return $this->read($length); } - function readLongUTF() { + public function readLongUTF() { $length = $this->readLong(); return $this->read($length); } - function read($length) { + public function read($length) { $val = substr($this->bytes, $this->pos, $length); $this->pos += $length; return $val; } - function peekByte() { + public function peekByte() { $pos = $this->pos; $val = $this->readByte(); $this->pos = $pos; return $val; } - function peekInt() { + public function peekInt() { $pos = $this->pos; $val = $this->readInt(); $this->pos = $pos; return $val; } - function peekLong() { + public function peekLong() { $pos = $this->pos; $val = $this->readLong(); $this->pos = $pos; return $val; } - function peekDouble() { + public function peekDouble() { $pos = $this->pos; $val = $this->readDouble(); $this->pos = $pos; return $val; } - function peekUTF() { + public function peekUTF() { $pos = $this->pos; $val = $this->readUTF(); $this->pos = $pos; return $val; } - function peekLongUTF() { + public function peekLongUTF() { $pos = $this->pos; $val = $this->readLongUTF(); $this->pos = $pos; @@ -455,13 +471,13 @@ class AMFStream { } class AMFReader { - var $stream; + public $stream; - function AMFReader(&$stream) { + public function __construct(&$stream) { $this->stream =& $stream; } - function readData() { + public function readData() { $value = null; $type = $this->stream->readByte(); @@ -531,19 +547,19 @@ class AMFReader { return $value; } - function readDouble() { + public function readDouble() { return $this->stream->readDouble(); } - function readBoolean() { + public function readBoolean() { return $this->stream->readByte() == 1; } - function readString() { + public function readString() { return $this->stream->readUTF(); } - function readObject() { + public function readObject() { // Get highest numerical index - ignored // $highestIndex = $this->stream->readLong(); @@ -560,7 +576,7 @@ class AMFReader { return $data; } - function readMixedArray() { + public function readMixedArray() { // Get highest numerical index - ignored $highestIndex = $this->stream->readLong(); @@ -581,7 +597,7 @@ class AMFReader { return $data; } - function readArray() { + public function readArray() { $length = $this->stream->readLong(); $data = array(); @@ -591,103 +607,103 @@ class AMFReader { return $data; } - function readDate() { + public function readDate() { $timestamp = $this->stream->readDouble(); $timezone = $this->stream->readInt(); return $timestamp; } - function readLongString() { + public function readLongString() { return $this->stream->readLongUTF(); } - function readXML() { + public function readXML() { return $this->stream->readLongUTF(); } - function readTypedObject() { + public function readTypedObject() { $className = $this->stream->readUTF(); return $this->readObject(); } } class AVCSequenceParameterSetReader { - var $sps; - var $start = 0; - var $currentBytes = 0; - var $currentBits = 0; - var $width; - var $height; + public $sps; + public $start = 0; + public $currentBytes = 0; + public $currentBits = 0; + public $width; + public $height; - function AVCSequenceParameterSetReader($sps) { + public function __construct($sps) { $this->sps = $sps; } - function readData() { + public function readData() { $this->skipBits(8); $this->skipBits(8); - $profile = $this->getBits(8); // read profile - $this->skipBits(16); - $this->expGolombUe(); // read sps id - if (in_array($profile, array(H264_PROFILE_HIGH, H264_PROFILE_HIGH10, H264_PROFILE_HIGH422, H264_PROFILE_HIGH444, H264_PROFILE_HIGH444_PREDICTIVE))) { - if ($this->expGolombUe() == 3) { - $this->skipBits(1); - } - $this->expGolombUe(); - $this->expGolombUe(); - $this->skipBits(1); - if ($this->getBit()) { - for ($i = 0; $i < 8; $i++) { - if ($this->getBit()) { - $size = $i < 6 ? 16 : 64; - $lastScale = 8; - $nextScale = 8; - for ($j = 0; $j < $size; $j++) { - if ($nextScale != 0) { - $deltaScale = $this->expGolombUe(); - $nextScale = ($lastScale + $deltaScale + 256) % 256; - } - if ($nextScale != 0) { - $lastScale = $nextScale; - } - } - } + $profile = $this->getBits(8); // read profile + if ($profile > 0) { + $this->skipBits(8); + $level_idc = $this->getBits(8); // level_idc + $this->expGolombUe(); // seq_parameter_set_id // sps + $this->expGolombUe(); // log2_max_frame_num_minus4 + $picOrderType = $this->expGolombUe(); // pic_order_cnt_type + if ($picOrderType == 0) { + $this->expGolombUe(); // log2_max_pic_order_cnt_lsb_minus4 + } elseif ($picOrderType == 1) { + $this->skipBits(1); // delta_pic_order_always_zero_flag + $this->expGolombSe(); // offset_for_non_ref_pic + $this->expGolombSe(); // offset_for_top_to_bottom_field + $num_ref_frames_in_pic_order_cnt_cycle = $this->expGolombUe(); // num_ref_frames_in_pic_order_cnt_cycle + for ($i = 0; $i < $num_ref_frames_in_pic_order_cnt_cycle; $i++) { + $this->expGolombSe(); // offset_for_ref_frame[ i ] } } - } - $this->expGolombUe(); - $pocType = $this->expGolombUe(); - if ($pocType == 0) { - $this->expGolombUe(); - } elseif ($pocType == 1) { - $this->skipBits(1); - $this->expGolombSe(); - $this->expGolombSe(); - $pocCycleLength = $this->expGolombUe(); - for ($i = 0; $i < $pocCycleLength; $i++) { - $this->expGolombSe(); + $this->expGolombUe(); // num_ref_frames + $this->skipBits(1); // gaps_in_frame_num_value_allowed_flag + $pic_width_in_mbs_minus1 = $this->expGolombUe(); // pic_width_in_mbs_minus1 + $pic_height_in_map_units_minus1 = $this->expGolombUe(); // pic_height_in_map_units_minus1 + + $frame_mbs_only_flag = $this->getBits(1); // frame_mbs_only_flag + if ($frame_mbs_only_flag == 0) { + $this->skipBits(1); // mb_adaptive_frame_field_flag } + $this->skipBits(1); // direct_8x8_inference_flag + $frame_cropping_flag = $this->getBits(1); // frame_cropping_flag + + $frame_crop_left_offset = 0; + $frame_crop_right_offset = 0; + $frame_crop_top_offset = 0; + $frame_crop_bottom_offset = 0; + + if ($frame_cropping_flag) { + $frame_crop_left_offset = $this->expGolombUe(); // frame_crop_left_offset + $frame_crop_right_offset = $this->expGolombUe(); // frame_crop_right_offset + $frame_crop_top_offset = $this->expGolombUe(); // frame_crop_top_offset + $frame_crop_bottom_offset = $this->expGolombUe(); // frame_crop_bottom_offset + } + $this->skipBits(1); // vui_parameters_present_flag + // etc + + $this->width = (($pic_width_in_mbs_minus1 + 1) * 16) - ($frame_crop_left_offset * 2) - ($frame_crop_right_offset * 2); + $this->height = ((2 - $frame_mbs_only_flag) * ($pic_height_in_map_units_minus1 + 1) * 16) - ($frame_crop_top_offset * 2) - ($frame_crop_bottom_offset * 2); } - $this->expGolombUe(); - $this->skipBits(1); - $this->width = ($this->expGolombUe() + 1) * 16; - $heightMap = $this->expGolombUe() + 1; - $this->height = (2 - $this->getBit()) * $heightMap * 16; } - function skipBits($bits) { + public function skipBits($bits) { $newBits = $this->currentBits + $bits; $this->currentBytes += (int)floor($newBits / 8); $this->currentBits = $newBits % 8; } - function getBit() { + public function getBit() { $result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01; $this->skipBits(1); return $result; } - function getBits($bits) { + public function getBits($bits) { $result = 0; for ($i = 0; $i < $bits; $i++) { $result = ($result << 1) + $this->getBit(); @@ -695,7 +711,7 @@ class AVCSequenceParameterSetReader { return $result; } - function expGolombUe() { + public function expGolombUe() { $significantBits = 0; $bit = $this->getBit(); while ($bit == 0) { @@ -710,7 +726,7 @@ class AVCSequenceParameterSetReader { return (1 << $significantBits) + $this->getBits($significantBits) - 1; } - function expGolombSe() { + public function expGolombSe() { $result = $this->expGolombUe(); if (($result & 0x01) == 0) { return -($result >> 1); @@ -719,13 +735,11 @@ class AVCSequenceParameterSetReader { } } - function getWidth() { + public function getWidth() { return $this->width; } - function getHeight() { + public function getHeight() { return $this->height; } } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio-video.matroska.php b/app/library/getid3/getid3/module.audio-video.matroska.php old mode 100644 new mode 100755 similarity index 83% rename from app/library/getid3/module.audio-video.matroska.php rename to app/library/getid3/getid3/module.audio-video.matroska.php index b7ab6679..f2cc5ac0 --- a/app/library/getid3/module.audio-video.matroska.php +++ b/app/library/getid3/getid3/module.audio-video.matroska.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -14,7 +15,6 @@ ///////////////////////////////////////////////////////////////// -// from: http://www.matroska.org/technical/specs/index.html define('EBML_ID_CHAPTERS', 0x0043A770); // [10][43][A7][70] -- A system to define basic menus and partition data. For more detailed information, look at the Chapters Explanation. define('EBML_ID_SEEKHEAD', 0x014D9B74); // [11][4D][9B][74] -- Contains the position of other level 1 elements. define('EBML_ID_TAGS', 0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found . @@ -90,7 +90,8 @@ define('EBML_ID_CUEBLOCKNUMBER', 0x1378); // [53][78] -- define('EBML_ID_TRACKOFFSET', 0x137F); // [53][7F] -- A value to add to the Block's Timecode. This can be used to adjust the playback offset of a track. define('EBML_ID_SEEKID', 0x13AB); // [53][AB] -- The binary ID corresponding to the element name. define('EBML_ID_SEEKPOSITION', 0x13AC); // [53][AC] -- The position of the element in the segment in octets (0 = first level 1 element). -define('EBML_ID_STEREOMODE', 0x13B8); // [53][B8] -- Stereo-3D video mode on 2 bits (0: mono, 1: right eye, 2: left eye, 3: both eyes). +define('EBML_ID_STEREOMODE', 0x13B8); // [53][B8] -- Stereo-3D video mode. +define('EBML_ID_OLDSTEREOMODE', 0x13B9); // [53][B9] -- Bogus StereoMode value used in old versions of libmatroska. DO NOT USE. (0: mono, 1: right eye, 2: left eye, 3: both eyes). define('EBML_ID_PIXELCROPBOTTOM', 0x14AA); // [54][AA] -- The number of video pixels to remove at the bottom of the image (for HDTV content). define('EBML_ID_DISPLAYWIDTH', 0x14B0); // [54][B0] -- Width of the video frames to display. define('EBML_ID_DISPLAYUNIT', 0x14B2); // [54][B2] -- Type of the unit for DisplayWidth/Height (0: pixels, 1: centimeters, 2: inches). @@ -206,19 +207,25 @@ define('EBML_ID_CLUSTERREFERENCEBLOCK', 0x7B); // [FB] -- define('EBML_ID_CLUSTERREFERENCEVIRTUAL', 0x7D); // [FD] -- Relative position of the data that should be in position of the virtual block. +/** +* @tutorial http://www.matroska.org/technical/specs/index.html +* +* @todo Rewrite EBML parser to reduce it's size and honor default element values +* @todo After rewrite implement stream size calculation, that will provide additional useful info and enable AAC/FLAC audio bitrate detection +*/ class getid3_matroska extends getid3_handler { // public options - public static $hide_clusters = true; // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful [default: TRUE] - public static $parse_whole_file = false; // true to parse the whole file, not only header [default: FALSE] + public static $hide_clusters = true; // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful [default: TRUE] + public static $parse_whole_file = false; // true to parse the whole file, not only header [default: FALSE] + + // private parser settings/placeholders + private $EBMLbuffer = ''; + private $EBMLbuffer_offset = 0; + private $EBMLbuffer_length = 0; + private $current_offset = 0; + private $unuseful_elements = array(EBML_ID_CRC32, EBML_ID_VOID); - // private parser settings/placeholders - private $EBMLbuffer = ''; - private $EBMLbuffer_offset = 0; - private $EBMLbuffer_length = 0; - private $current_offset = 0; - private $unuseful_elements = array(EBML_ID_CRC32, EBML_ID_VOID); - public function Analyze() { $info = &$this->getid3->info; @@ -226,8 +233,7 @@ class getid3_matroska extends getid3_handler // parse container try { $this->parseEBML($info); - } - catch (Exception $e) { + } catch (Exception $e) { $info['error'][] = 'EBML parser: '.$e->getMessage(); } @@ -252,44 +258,71 @@ class getid3_matroska extends getid3_handler // process tracks if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { - + $track_info = array(); - $track_info['dataformat'] = self::MatroskaCodecIDtoCommonName($trackarray['CodecID']); + $track_info['dataformat'] = self::CodecIDtoCommonName($trackarray['CodecID']); $track_info['default'] = (isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true); if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; } - + switch ($trackarray['TrackType']) { - + case 1: // Video $track_info['resolution_x'] = $trackarray['PixelWidth']; $track_info['resolution_y'] = $trackarray['PixelHeight']; - if (isset($trackarray['DisplayWidth'])) { $track_info['display_x'] = $trackarray['DisplayWidth']; } - if (isset($trackarray['DisplayHeight'])) { $track_info['display_y'] = $trackarray['DisplayHeight']; } - if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } - //if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } - + $track_info['display_unit'] = self::displayUnit(isset($trackarray['DisplayUnit']) ? $trackarray['DisplayUnit'] : 0); + $track_info['display_x'] = (isset($trackarray['DisplayWidth']) ? $trackarray['DisplayWidth'] : $trackarray['PixelWidth']); + $track_info['display_y'] = (isset($trackarray['DisplayHeight']) ? $trackarray['DisplayHeight'] : $trackarray['PixelHeight']); + + if (isset($trackarray['PixelCropBottom'])) { $track_info['crop_bottom'] = $trackarray['PixelCropBottom']; } + if (isset($trackarray['PixelCropTop'])) { $track_info['crop_top'] = $trackarray['PixelCropTop']; } + if (isset($trackarray['PixelCropLeft'])) { $track_info['crop_left'] = $trackarray['PixelCropLeft']; } + if (isset($trackarray['PixelCropRight'])) { $track_info['crop_right'] = $trackarray['PixelCropRight']; } + if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } + if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } + switch ($trackarray['CodecID']) { case 'V_MS/VFW/FOURCC': - if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { - $this->getid3->warning('Unable to parse codec private data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'); - break; - } + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + $parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']); - $track_info['codec'] = getid3_riff::RIFFfourccLookup($parsed['fourcc']); + $track_info['codec'] = getid3_riff::fourccLookup($parsed['fourcc']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; break; + + /*case 'V_MPEG4/ISO/AVC': + $h264['profile'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 1, 1)); + $h264['level'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 3, 1)); + $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 4, 1)); + $h264['NALUlength'] = ($rn & 3) + 1; + $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 5, 1)); + $nsps = ($rn & 31); + $offset = 6; + for ($i = 0; $i < $nsps; $i ++) { + $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); + $h264['SPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); + $offset += 2 + $length; + } + $npps = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 1)); + $offset += 1; + for ($i = 0; $i < $npps; $i ++) { + $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); + $h264['PPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); + $offset += 2 + $length; + } + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $h264; + break;*/ } - + $info['video']['streams'][] = $track_info; break; - + case 2: // Audio $track_info['sample_rate'] = (isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0); $track_info['channels'] = (isset($trackarray['Channels']) ? $trackarray['Channels'] : 1); $track_info['language'] = (isset($trackarray['Language']) ? $trackarray['Language'] : 'eng'); if (isset($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } - //if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } - + if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } + switch ($trackarray['CodecID']) { case 'A_PCM/INT/LIT': case 'A_PCM/INT/BIG': @@ -299,32 +332,29 @@ class getid3_matroska extends getid3_handler case 'A_AC3': case 'A_DTS': case 'A_MPEG/L3': - //case 'A_FLAC': - if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.'.$track_info['dataformat'].'.php', __FILE__, false)) { - $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.'.$track_info['dataformat'].'.php"'); - break; - } + case 'A_MPEG/L2': + case 'A_FLAC': + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.'.($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']).'.php', __FILE__, true); if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { - $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $info[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set'); + $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $info[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set'); break; } // create temp instance $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); + if ($track_info['dataformat'] != 'flac') { + $getid3_temp->openfile($this->getid3->filename); + } $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; - if ($track_info['dataformat'] == 'mp3' || $track_info['dataformat'] == 'flac') { + if ($track_info['dataformat'][0] == 'm' || $track_info['dataformat'] == 'flac') { $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; } // analyze - $class = 'getid3_'.$track_info['dataformat']; - $header_data_key = $track_info['dataformat'] == 'mp3' ? 'mpeg' : $track_info['dataformat']; - $getid3_audio = new $class($getid3_temp); - if ($track_info['dataformat'] == 'mp3') { - $getid3_audio->allow_bruteforce = true; - } + $class = 'getid3_'.($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']); + $header_data_key = $track_info['dataformat'][0] == 'm' ? 'mpeg' : $track_info['dataformat']; + $getid3_audio = new $class($getid3_temp, __CLASS__); if ($track_info['dataformat'] == 'flac') { $getid3_audio->AnalyzeString($trackarray['CodecPrivate']); } @@ -332,7 +362,6 @@ class getid3_matroska extends getid3_handler $getid3_audio->Analyze(); } if (!empty($getid3_temp->info[$header_data_key])) { - unset($getid3_temp->info[$header_data_key]['GETID3_VERSION']); $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key]; if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { foreach ($getid3_temp->info['audio'] as $key => $value) { @@ -341,22 +370,18 @@ class getid3_matroska extends getid3_handler } } else { - $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because '.$class.'::Analyze() failed at offset '.$getid3_temp->info['avdataoffset']); + $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because '.$class.'::Analyze() failed at offset '.$getid3_temp->info['avdataoffset']); } // copy errors and warnings if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { - $this->getid3->warning($class.'() says: ['.$newerror.']'); + $this->warning($class.'() says: ['.$newerror.']'); } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { - if ($track_info['dataformat'] == 'mp3' && preg_match('/^Probable truncated file: expecting \d+ bytes of audio data, only found \d+ \(short by \d+ bytes\)$/', $newerror)) { - // LAME/Xing header is probably set, but audio data is chunked into Matroska file and near-impossible to verify if audio stream is complete, so ignore useless warning - continue; - } - $this->getid3->warning($class.'() says: ['.$newerror.']'); + $this->warning($class.'() says: ['.$newerror.']'); } } unset($getid3_temp, $getid3_audio); @@ -364,30 +389,28 @@ class getid3_matroska extends getid3_handler case 'A_AAC': case 'A_AAC/MPEG2/LC': + case 'A_AAC/MPEG2/LC/SBR': case 'A_AAC/MPEG4/LC': case 'A_AAC/MPEG4/LC/SBR': - $this->getid3->warning($trackarray['CodecID'].' audio data contains no header, audio/video bitrates can\'t be calculated'); + $this->warning($trackarray['CodecID'].' audio data contains no header, audio/video bitrates can\'t be calculated'); break; case 'A_VORBIS': if (!isset($trackarray['CodecPrivate'])) { - $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data not set'); + $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data not set'); break; } $vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1); if ($vorbis_offset === false) { - $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data does not contain "vorbis" keyword'); + $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data does not contain "vorbis" keyword'); break; } $vorbis_offset -= 1; - if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, false)) { - $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.ogg.php"'); - } + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); // create temp instance $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); // analyze $getid3_ogg = new getid3_ogg($getid3_temp); @@ -401,19 +424,19 @@ class getid3_matroska extends getid3_handler } } } - + // copy errors and warnings if (!empty($getid3_temp->info['error'])) { foreach ($getid3_temp->info['error'] as $newerror) { - $this->getid3->warning('getid3_ogg() says: ['.$newerror.']'); + $this->warning('getid3_ogg() says: ['.$newerror.']'); } } if (!empty($getid3_temp->info['warning'])) { foreach ($getid3_temp->info['warning'] as $newerror) { - $this->getid3->warning('getid3_ogg() says: ['.$newerror.']'); + $this->warning('getid3_ogg() says: ['.$newerror.']'); } } - + if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) { $track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal']; } @@ -421,12 +444,9 @@ class getid3_matroska extends getid3_handler break; case 'A_MS/ACM': - if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { - $this->getid3->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'); - break; - } - - $parsed = getid3_riff::RIFFparseWAVEFORMATex($trackarray['CodecPrivate']); + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + + $parsed = getid3_riff::parseWAVEFORMATex($trackarray['CodecPrivate']); foreach ($parsed as $key => $value) { if ($key != 'raw') { $track_info[$key] = $value; @@ -434,16 +454,16 @@ class getid3_matroska extends getid3_handler } $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; break; - + default: - $this->getid3->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"'); + $this->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"'); } $info['audio']['streams'][] = $track_info; break; } } - + if (!empty($info['video']['streams'])) { $info['video'] = self::getDefaultStreamInfo($info['video']['streams']); } @@ -452,7 +472,16 @@ class getid3_matroska extends getid3_handler } } - // determine mime type + // process attachments + if (isset($info['matroska']['attachments']) && $this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE) { + foreach ($info['matroska']['attachments'] as $i => $entry) { + if (strpos($entry['FileMimeType'], 'image/') === 0 && !empty($entry['FileData'])) { + $info['matroska']['comments']['picture'][] = array('data' => $entry['FileData'], 'image_mime' => $entry['FileMimeType'], 'filename' => $entry['FileName']); + } + } + } + + // determine mime type if (!empty($info['video']['streams'])) { $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska'); } elseif (!empty($info['audio']['streams'])) { @@ -464,11 +493,7 @@ class getid3_matroska extends getid3_handler return true; } - -/////////////////////////////////////// - - private function parseEBML(&$info) - { + private function parseEBML(&$info) { // http://www.matroska.org/technical/specs/index.html#EBMLBasics $this->current_offset = $info['avdataoffset']; @@ -476,7 +501,6 @@ class getid3_matroska extends getid3_handler switch ($top_element['id']) { case EBML_ID_EBML: - $info['fileformat'] = 'matroska'; $info['matroska']['header']['offset'] = $top_element['offset']; $info['matroska']['header']['length'] = $top_element['length']; @@ -495,20 +519,15 @@ class getid3_matroska extends getid3_handler case EBML_ID_DOCTYPE: $element_data['data'] = getid3_lib::trimNullByte($element_data['data']); $info['matroska']['doctype'] = $element_data['data']; - break; - - case EBML_ID_CRC32: // not useful, ignore - $this->current_offset = $element_data['end']; - unset($element_data); + $info['fileformat'] = $element_data['data']; break; default: $this->unhandledElement('header', __LINE__, $element_data); } - if (!empty($element_data)) { - unset($element_data['offset'], $element_data['end']); - $info['matroska']['header']['elements'][] = $element_data; - } + + unset($element_data['offset'], $element_data['end']); + $info['matroska']['header']['elements'][] = $element_data; } break; @@ -564,7 +583,7 @@ class getid3_matroska extends getid3_handler case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. - while ($this->getEBMLelement($subelement, $track_entry['end'], array(EBML_ID_VIDEO, EBML_ID_AUDIO, EBML_ID_CONTENTENCODINGS))) { + while ($this->getEBMLelement($subelement, $track_entry['end'], array(EBML_ID_VIDEO, EBML_ID_AUDIO, EBML_ID_CONTENTENCODINGS, EBML_ID_CODECPRIVATE))) { switch ($subelement['id']) { case EBML_ID_TRACKNUMBER: @@ -587,9 +606,9 @@ class getid3_matroska extends getid3_handler case EBML_ID_CODECNAME: $track_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); break; - + case EBML_ID_CODECPRIVATE: - $track_entry[$subelement['id_name']] = $subelement['data']; + $track_entry[$subelement['id_name']] = $this->readEBMLelementData($subelement['length'], true); break; case EBML_ID_FLAGENABLED: @@ -607,7 +626,6 @@ class getid3_matroska extends getid3_handler case EBML_ID_PIXELWIDTH: case EBML_ID_PIXELHEIGHT: - case EBML_ID_STEREOMODE: case EBML_ID_PIXELCROPBOTTOM: case EBML_ID_PIXELCROPTOP: case EBML_ID_PIXELCROPLEFT: @@ -616,6 +634,8 @@ class getid3_matroska extends getid3_handler case EBML_ID_DISPLAYHEIGHT: case EBML_ID_DISPLAYUNIT: case EBML_ID_ASPECTRATIOTYPE: + case EBML_ID_STEREOMODE: + case EBML_ID_OLDSTEREOMODE: $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); break; @@ -752,8 +772,6 @@ class getid3_matroska extends getid3_handler while ($this->getEBMLelement($subelement, $element_data['end'], true)) { switch ($subelement['id']) { - case EBML_ID_CHAPTERTRANSLATEEDITIONUID: - case EBML_ID_CHAPTERTRANSLATECODEC: case EBML_ID_TIMECODESCALE: $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); break; @@ -770,11 +788,13 @@ class getid3_matroska extends getid3_handler case EBML_ID_SEGMENTUID: case EBML_ID_PREVUID: case EBML_ID_NEXTUID: - case EBML_ID_SEGMENTFAMILY: - case EBML_ID_CHAPTERTRANSLATEID: $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); break; + case EBML_ID_SEGMENTFAMILY: + $info_entry[$subelement['id_name']][] = getid3_lib::trimNullByte($subelement['data']); + break; + case EBML_ID_SEGMENTFILENAME: case EBML_ID_PREVFILENAME: case EBML_ID_NEXTFILENAME: @@ -785,6 +805,31 @@ class getid3_matroska extends getid3_handler $info['matroska']['comments'][strtolower($subelement['id_name'])][] = $info_entry[$subelement['id_name']]; break; + case EBML_ID_CHAPTERTRANSLATE: + $chaptertranslate_entry = array(); + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { + switch ($sub_subelement['id']) { + + case EBML_ID_CHAPTERTRANSLATEEDITIONUID: + $chaptertranslate_entry[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_CHAPTERTRANSLATECODEC: + $chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_CHAPTERTRANSLATEID: + $chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); + break; + + default: + $this->unhandledElement('info.chaptertranslate', __LINE__, $sub_subelement); + } + } + $info_entry[$subelement['id_name']] = $chaptertranslate_entry; + break; + default: $this->unhandledElement('info', __LINE__, $subelement); } @@ -809,8 +854,8 @@ class getid3_matroska extends getid3_handler switch ($sub_subelement['id']) { case EBML_ID_CUETRACKPOSITIONS: - $cuetrackpositions_entry = array(); - + $cuetrackpositions_entry = array(); + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { switch ($sub_sub_subelement['id']) { @@ -847,14 +892,14 @@ class getid3_matroska extends getid3_handler break; case EBML_ID_TAGS: // Element containing elements specific to Tracks/Chapters. - $tags_entry = array(); - + $tags_entry = array(); + while ($this->getEBMLelement($subelement, $element_data['end'], false)) { switch ($subelement['id']) { case EBML_ID_TAG: $tag_entry = array(); - + while ($this->getEBMLelement($sub_subelement, $subelement['end'], false)) { switch ($sub_subelement['id']) { @@ -866,14 +911,14 @@ class getid3_matroska extends getid3_handler case EBML_ID_TARGETTYPEVALUE: $targets_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); - $targets_entry[strtolower($sub_sub_subelement['id_name']).'_long'] = self::MatroskaTargetTypeValue($targets_entry[$sub_sub_subelement['id_name']]); + $targets_entry[strtolower($sub_sub_subelement['id_name']).'_long'] = self::TargetTypeValue($targets_entry[$sub_sub_subelement['id_name']]); break; case EBML_ID_TARGETTYPE: $targets_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; break; - - case EBML_ID_TAGTRACKUID: + + case EBML_ID_TAGTRACKUID: case EBML_ID_TAGEDITIONUID: case EBML_ID_TAGCHAPTERUID: case EBML_ID_TAGATTACHMENTUID: @@ -926,17 +971,11 @@ class getid3_matroska extends getid3_handler $attachedfile_entry['data_offset'] = $this->current_offset; $attachedfile_entry['data_length'] = $sub_subelement['length']; - $this->getid3->saveAttachment( - $attachedfile_entry[$sub_subelement['id_name']], + $attachedfile_entry[$sub_subelement['id_name']] = $this->saveAttachment( $attachedfile_entry['FileName'], $attachedfile_entry['data_offset'], $attachedfile_entry['data_length']); - if (@$attachedfile_entry[$sub_subelement['id_name']] && is_file($attachedfile_entry[$sub_subelement['id_name']])) { - $attachedfile_entry[$sub_subelement['id_name'].'_filename'] = $attachedfile_entry[$sub_subelement['id_name']]; - unset($attachedfile_entry[$sub_subelement['id_name']]); - } - $this->current_offset = $sub_subelement['end']; break; @@ -948,19 +987,7 @@ class getid3_matroska extends getid3_handler $this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement); } } - if (!empty($attachedfile_entry['FileData']) && !empty($attachedfile_entry['FileMimeType']) && preg_match('#^image/#i', $attachedfile_entry['FileMimeType'])) { - if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { - $attachedfile_entry['data'] = $attachedfile_entry['FileData']; - $attachedfile_entry['image_mime'] = $attachedfile_entry['FileMimeType']; - $info['matroska']['comments']['picture'][] = array('data' => $attachedfile_entry['data'], 'image_mime' => $attachedfile_entry['image_mime'], 'filename' => (!empty($attachedfile_entry['FileName']) ? $attachedfile_entry['FileName'] : '')); - unset($attachedfile_entry['FileData'], $attachedfile_entry['FileMimeType']); - } - } - if (!empty($attachedfile_entry['image_mime']) && preg_match('#^image/#i', $attachedfile_entry['image_mime'])) { - // don't add a second copy of attached images, which are grouped under the standard location [comments][picture] - } else { - $info['matroska']['attachments'][] = $attachedfile_entry; - } + $info['matroska']['attachments'][] = $attachedfile_entry; break; default: @@ -1115,7 +1142,7 @@ class getid3_matroska extends getid3_handler case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int $cluster_block_group[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data'], false, true); break; - + case EBML_ID_CLUSTERCODECSTATE: $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); break; @@ -1144,7 +1171,9 @@ class getid3_matroska extends getid3_handler if (!self::$parse_whole_file) { if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { - return; + if (count($info['matroska']['track_data_offsets']) == count($info['matroska']['tracks']['tracks'])) { + return; + } } } } @@ -1160,38 +1189,35 @@ class getid3_matroska extends getid3_handler $this->unhandledElement('root', __LINE__, $top_element); } } - } + } - private function EnsureBufferHasEnoughData($min_data = 1024) - { + private function EnsureBufferHasEnoughData($min_data=1024) { if (($this->current_offset - $this->EBMLbuffer_offset) >= ($this->EBMLbuffer_length - $min_data)) { + $read_bytes = max($min_data, $this->getid3->fread_buffer_size()); - if (!getid3_lib::intValueSupported($this->current_offset + $this->getid3->fread_buffer_size())) { - $this->getid3->info['error'][] = 'EBML parser: cannot read past '.$this->current_offset; + try { + $this->fseek($this->current_offset); + $this->EBMLbuffer_offset = $this->current_offset; + $this->EBMLbuffer = $this->fread($read_bytes); + $this->EBMLbuffer_length = strlen($this->EBMLbuffer); + } catch (getid3_exception $e) { + $this->warning('EBML parser: '.$e->getMessage()); return false; } - fseek($this->getid3->fp, $this->current_offset, SEEK_SET); - $this->EBMLbuffer_offset = $this->current_offset; - $this->EBMLbuffer = fread($this->getid3->fp, max($min_data, $this->getid3->fread_buffer_size())); - $this->EBMLbuffer_length = strlen($this->EBMLbuffer); - - if ($this->EBMLbuffer_length == 0 && feof($this->getid3->fp)) { - $this->getid3->info['error'][] = 'EBML parser: ran out of file at offset '.$this->current_offset; - return false; + if ($this->EBMLbuffer_length == 0 && $this->feof()) { + return $this->error('EBML parser: ran out of file at offset '.$this->current_offset); } } - return true; } - private function readEBMLint() - { + private function readEBMLint() { $actual_offset = $this->current_offset - $this->EBMLbuffer_offset; // get length of integer $first_byte_int = ord($this->EBMLbuffer[$actual_offset]); - if (0x80 & $first_byte_int) { + if (0x80 & $first_byte_int) { $length = 1; } elseif (0x40 & $first_byte_int) { $length = 2; @@ -1218,16 +1244,16 @@ class getid3_matroska extends getid3_handler return $int_value; } - private function readEBMLelementData($length) - { + private function readEBMLelementData($length, $check_buffer=false) { + if ($check_buffer && !$this->EnsureBufferHasEnoughData($length)) { + return false; + } $data = substr($this->EBMLbuffer, $this->current_offset - $this->EBMLbuffer_offset, $length); $this->current_offset += $length; - return $data; } - private function getEBMLelement(&$element, $parent_end, $get_data = false) - { + private function getEBMLelement(&$element, $parent_end, $get_data=false) { if ($this->current_offset >= $parent_end) { return false; } @@ -1263,11 +1289,10 @@ class getid3_matroska extends getid3_handler return true; } - private function unhandledElement($type, $line, $element) - { + private function unhandledElement($type, $line, $element) { // warn only about unknown and missed elements, not about unuseful if (!in_array($element['id'], $this->unuseful_elements)) { - $this->getid3->warning('Unhandled '.$type.' element ['.basename(__FILE__).':'.$line.'] ('.$element['id'].'::'.$element['id_name'].' ['.$element['length'].' bytes]) at '.$element['offset']); + $this->warning('Unhandled '.$type.' element ['.basename(__FILE__).':'.$line.'] ('.$element['id'].'::'.$element['id_name'].' ['.$element['length'].' bytes]) at '.$element['offset']); } // increase offset for unparsed elements @@ -1276,8 +1301,7 @@ class getid3_matroska extends getid3_handler } } - private function ExtractCommentsSimpleTag($SimpleTagArray) - { + private function ExtractCommentsSimpleTag($SimpleTagArray) { if (!empty($SimpleTagArray['SimpleTag'])) { foreach ($SimpleTagArray['SimpleTag'] as $SimpleTagKey => $SimpleTagData) { if (!empty($SimpleTagData['TagName']) && !empty($SimpleTagData['TagString'])) { @@ -1292,8 +1316,7 @@ class getid3_matroska extends getid3_handler return true; } - private function HandleEMBLSimpleTag($parent_end) - { + private function HandleEMBLSimpleTag($parent_end) { $simpletag_entry = array(); while ($this->getEBMLelement($element, $parent_end, array(EBML_ID_SIMPLETAG))) { @@ -1322,58 +1345,67 @@ class getid3_matroska extends getid3_handler return $simpletag_entry; } - private function HandleEMBLClusterBlock($element, $block_type, &$info) - { + private function HandleEMBLClusterBlock($element, $block_type, &$info) { // http://www.matroska.org/technical/specs/index.html#block_structure // http://www.matroska.org/technical/specs/index.html#simpleblock_structure - $cluster_block_data = array(); - $cluster_block_data['tracknumber'] = $this->readEBMLint(); - $cluster_block_data['timecode'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(2)); - $cluster_block_data['flags_raw'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); + $block_data = array(); + $block_data['tracknumber'] = $this->readEBMLint(); + $block_data['timecode'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(2), false, true); + $block_data['flags_raw'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { - $cluster_block_data['flags']['keyframe'] = (($cluster_block_data['flags_raw'] & 0x80) >> 7); - //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0x70) >> 4); + $block_data['flags']['keyframe'] = (($block_data['flags_raw'] & 0x80) >> 7); + //$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0x70) >> 4); } else { - //$cluster_block_data['flags']['reserved1'] = (($cluster_block_data['flags_raw'] & 0xF0) >> 4); + //$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0xF0) >> 4); } - $cluster_block_data['flags']['invisible'] = (bool)(($cluster_block_data['flags_raw'] & 0x08) >> 3); - $cluster_block_data['flags']['lacing'] = (($cluster_block_data['flags_raw'] & 0x06) >> 1); // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing + $block_data['flags']['invisible'] = (bool)(($block_data['flags_raw'] & 0x08) >> 3); + $block_data['flags']['lacing'] = (($block_data['flags_raw'] & 0x06) >> 1); // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { - $cluster_block_data['flags']['discardable'] = (($cluster_block_data['flags_raw'] & 0x01)); + $block_data['flags']['discardable'] = (($block_data['flags_raw'] & 0x01)); } else { - //$cluster_block_data['flags']['reserved2'] = (($cluster_block_data['flags_raw'] & 0x01) >> 0); + //$block_data['flags']['reserved2'] = (($block_data['flags_raw'] & 0x01) >> 0); } - $cluster_block_data['flags']['lacing_type'] = self::MatroskaBlockLacingType($cluster_block_data['flags']['lacing']); + $block_data['flags']['lacing_type'] = self::BlockLacingType($block_data['flags']['lacing']); - // Lace (when lacing bit is set) - if ($cluster_block_data['flags']['lacing'] > 0) { - $cluster_block_data['lace_frames'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)) + 1; // Number of frames in the lace-1 (uint8) - if ($cluster_block_data['flags']['lacing'] != 0x02) { // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). - for ($i = 1; $i < $cluster_block_data['lace_frames']; $i ++) { - if ($cluster_block_data['flags']['lacing'] == 0x03) { // EBML lacing - // TODO: read size correctly, calc size for the last frame. For now offsets are deteminded OK with readEBMLint() and that's the most important thing. - $cluster_block_data['lace_frames_size'][$i] = $this->readEBMLint(); + // Lace (when lacing bit is set) + if ($block_data['flags']['lacing'] > 0) { + $block_data['lace_frames'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)) + 1; // Number of frames in the lace-1 (uint8) + if ($block_data['flags']['lacing'] != 0x02) { + for ($i = 1; $i < $block_data['lace_frames']; $i ++) { // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). + if ($block_data['flags']['lacing'] == 0x03) { // EBML lacing + $block_data['lace_frames_size'][$i] = $this->readEBMLint(); // TODO: read size correctly, calc size for the last frame. For now offsets are deteminded OK with readEBMLint() and that's the most important thing. } else { // Xiph lacing - $cluster_block_data['lace_frames_size'][$i] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); + $block_data['lace_frames_size'][$i] = 0; + do { + $size = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); + $block_data['lace_frames_size'][$i] += $size; + } + while ($size == 255); } } + if ($block_data['flags']['lacing'] == 0x01) { // calc size of the last frame only for Xiph lacing, till EBML sizes are now anyway determined incorrectly + $block_data['lace_frames_size'][] = $element['end'] - $this->current_offset - array_sum($block_data['lace_frames_size']); + } } } - if (!isset($info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']])) { - $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['offset'] = $this->current_offset; - $info['matroska']['track_data_offsets'][$cluster_block_data['tracknumber']]['length'] = $element['end'] - $this->current_offset; + if (!isset($info['matroska']['track_data_offsets'][$block_data['tracknumber']])) { + $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['offset'] = $this->current_offset; + $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length'] = $element['end'] - $this->current_offset; + //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] = 0; } + //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] += $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length']; + //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['duration'] = $block_data['timecode'] * ((isset($info['matroska']['info'][0]['TimecodeScale']) ? $info['matroska']['info'][0]['TimecodeScale'] : 1000000) / 1000000000); // set offset manually $this->current_offset = $element['end']; - return $cluster_block_data; + return $block_data; } private static function EBML2Int($EBMLstring) { @@ -1424,66 +1456,66 @@ class getid3_matroska extends getid3_handler return round(($EBMLdatestamp / 1000000000) + 978307200); } - public static function MatroskaTargetTypeValue($target_type) { + public static function TargetTypeValue($target_type) { // http://www.matroska.org/technical/specs/tagging/index.html - static $MatroskaTargetTypeValue = array(); - if (empty($MatroskaTargetTypeValue)) { - $MatroskaTargetTypeValue[10] = 'A: ~ V:shot'; // the lowest hierarchy found in music or movies - $MatroskaTargetTypeValue[20] = 'A:subtrack/part/movement ~ V:scene'; // corresponds to parts of a track for audio (like a movement) - $MatroskaTargetTypeValue[30] = 'A:track/song ~ V:chapter'; // the common parts of an album or a movie - $MatroskaTargetTypeValue[40] = 'A:part/session ~ V:part/session'; // when an album or episode has different logical parts - $MatroskaTargetTypeValue[50] = 'A:album/opera/concert ~ V:movie/episode/concert'; // the most common grouping level of music and video (equals to an episode for TV series) - $MatroskaTargetTypeValue[60] = 'A:edition/issue/volume/opus ~ V:season/sequel/volume'; // a list of lower levels grouped together - $MatroskaTargetTypeValue[70] = 'A:collection ~ V:collection'; // the high hierarchy consisting of many different lower items + static $TargetTypeValue = array(); + if (empty($TargetTypeValue)) { + $TargetTypeValue[10] = 'A: ~ V:shot'; // the lowest hierarchy found in music or movies + $TargetTypeValue[20] = 'A:subtrack/part/movement ~ V:scene'; // corresponds to parts of a track for audio (like a movement) + $TargetTypeValue[30] = 'A:track/song ~ V:chapter'; // the common parts of an album or a movie + $TargetTypeValue[40] = 'A:part/session ~ V:part/session'; // when an album or episode has different logical parts + $TargetTypeValue[50] = 'A:album/opera/concert ~ V:movie/episode/concert'; // the most common grouping level of music and video (equals to an episode for TV series) + $TargetTypeValue[60] = 'A:edition/issue/volume/opus ~ V:season/sequel/volume'; // a list of lower levels grouped together + $TargetTypeValue[70] = 'A:collection ~ V:collection'; // the high hierarchy consisting of many different lower items } - return (isset($MatroskaTargetTypeValue[$target_type]) ? $MatroskaTargetTypeValue[$target_type] : $target_type); + return (isset($TargetTypeValue[$target_type]) ? $TargetTypeValue[$target_type] : $target_type); } - public static function MatroskaBlockLacingType($lacingtype) { + public static function BlockLacingType($lacingtype) { // http://matroska.org/technical/specs/index.html#block_structure - static $MatroskaBlockLacingType = array(); - if (empty($MatroskaBlockLacingType)) { - $MatroskaBlockLacingType[0x00] = 'no lacing'; - $MatroskaBlockLacingType[0x01] = 'Xiph lacing'; - $MatroskaBlockLacingType[0x02] = 'fixed-size lacing'; - $MatroskaBlockLacingType[0x03] = 'EBML lacing'; + static $BlockLacingType = array(); + if (empty($BlockLacingType)) { + $BlockLacingType[0x00] = 'no lacing'; + $BlockLacingType[0x01] = 'Xiph lacing'; + $BlockLacingType[0x02] = 'fixed-size lacing'; + $BlockLacingType[0x03] = 'EBML lacing'; } - return (isset($MatroskaBlockLacingType[$lacingtype]) ? $MatroskaBlockLacingType[$lacingtype] : $lacingtype); + return (isset($BlockLacingType[$lacingtype]) ? $BlockLacingType[$lacingtype] : $lacingtype); } - public static function MatroskaCodecIDtoCommonName($codecid) { + public static function CodecIDtoCommonName($codecid) { // http://www.matroska.org/technical/specs/codecid/index.html - static $MatroskaCodecIDlist = array(); - if (empty($MatroskaCodecIDlist)) { - $MatroskaCodecIDlist['A_AAC'] = 'aac'; - $MatroskaCodecIDlist['A_AAC/MPEG2/LC'] = 'aac'; - $MatroskaCodecIDlist['A_AC3'] = 'ac3'; - $MatroskaCodecIDlist['A_DTS'] = 'dts'; - $MatroskaCodecIDlist['A_FLAC'] = 'flac'; - $MatroskaCodecIDlist['A_MPEG/L1'] = 'mp1'; - $MatroskaCodecIDlist['A_MPEG/L2'] = 'mp2'; - $MatroskaCodecIDlist['A_MPEG/L3'] = 'mp3'; - $MatroskaCodecIDlist['A_PCM/INT/LIT'] = 'pcm'; // PCM Integer Little Endian - $MatroskaCodecIDlist['A_PCM/INT/BIG'] = 'pcm'; // PCM Integer Big Endian - $MatroskaCodecIDlist['A_QUICKTIME/QDMC'] = 'quicktime'; // Quicktime: QDesign Music - $MatroskaCodecIDlist['A_QUICKTIME/QDM2'] = 'quicktime'; // Quicktime: QDesign Music v2 - $MatroskaCodecIDlist['A_VORBIS'] = 'vorbis'; - $MatroskaCodecIDlist['V_MPEG1'] = 'mpeg'; - $MatroskaCodecIDlist['V_THEORA'] = 'theora'; - $MatroskaCodecIDlist['V_REAL/RV40'] = 'real'; - $MatroskaCodecIDlist['V_REAL/RV10'] = 'real'; - $MatroskaCodecIDlist['V_REAL/RV20'] = 'real'; - $MatroskaCodecIDlist['V_REAL/RV30'] = 'real'; - $MatroskaCodecIDlist['V_QUICKTIME'] = 'quicktime'; // Quicktime - $MatroskaCodecIDlist['V_MPEG4/ISO/AP'] = 'mpeg4'; - $MatroskaCodecIDlist['V_MPEG4/ISO/ASP'] = 'mpeg4'; - $MatroskaCodecIDlist['V_MPEG4/ISO/AVC'] = 'h264'; - $MatroskaCodecIDlist['V_MPEG4/ISO/SP'] = 'mpeg4'; - $MatroskaCodecIDlist['V_VP8'] = 'vp8'; - $MatroskaCodecIDlist['V_MS/VFW/FOURCC'] = 'riff'; - $MatroskaCodecIDlist['A_MS/ACM'] = 'riff'; + static $CodecIDlist = array(); + if (empty($CodecIDlist)) { + $CodecIDlist['A_AAC'] = 'aac'; + $CodecIDlist['A_AAC/MPEG2/LC'] = 'aac'; + $CodecIDlist['A_AC3'] = 'ac3'; + $CodecIDlist['A_DTS'] = 'dts'; + $CodecIDlist['A_FLAC'] = 'flac'; + $CodecIDlist['A_MPEG/L1'] = 'mp1'; + $CodecIDlist['A_MPEG/L2'] = 'mp2'; + $CodecIDlist['A_MPEG/L3'] = 'mp3'; + $CodecIDlist['A_PCM/INT/LIT'] = 'pcm'; // PCM Integer Little Endian + $CodecIDlist['A_PCM/INT/BIG'] = 'pcm'; // PCM Integer Big Endian + $CodecIDlist['A_QUICKTIME/QDMC'] = 'quicktime'; // Quicktime: QDesign Music + $CodecIDlist['A_QUICKTIME/QDM2'] = 'quicktime'; // Quicktime: QDesign Music v2 + $CodecIDlist['A_VORBIS'] = 'vorbis'; + $CodecIDlist['V_MPEG1'] = 'mpeg'; + $CodecIDlist['V_THEORA'] = 'theora'; + $CodecIDlist['V_REAL/RV40'] = 'real'; + $CodecIDlist['V_REAL/RV10'] = 'real'; + $CodecIDlist['V_REAL/RV20'] = 'real'; + $CodecIDlist['V_REAL/RV30'] = 'real'; + $CodecIDlist['V_QUICKTIME'] = 'quicktime'; // Quicktime + $CodecIDlist['V_MPEG4/ISO/AP'] = 'mpeg4'; + $CodecIDlist['V_MPEG4/ISO/ASP'] = 'mpeg4'; + $CodecIDlist['V_MPEG4/ISO/AVC'] = 'h264'; + $CodecIDlist['V_MPEG4/ISO/SP'] = 'mpeg4'; + $CodecIDlist['V_VP8'] = 'vp8'; + $CodecIDlist['V_MS/VFW/FOURCC'] = 'vcm'; // Microsoft (TM) Video Codec Manager (VCM) + $CodecIDlist['A_MS/ACM'] = 'acm'; // Microsoft (TM) Audio Codec Manager (ACM) } - return (isset($MatroskaCodecIDlist[$codecid]) ? $MatroskaCodecIDlist[$codecid] : $codecid); + return (isset($CodecIDlist[$codecid]) ? $CodecIDlist[$codecid] : $codecid); } private static function EBMLidName($value) { @@ -1647,6 +1679,7 @@ class getid3_matroska extends getid3_handler $EBMLidList[EBML_ID_SIMPLETAG] = 'SimpleTag'; $EBMLidList[EBML_ID_CLUSTERSLICES] = 'ClusterSlices'; $EBMLidList[EBML_ID_STEREOMODE] = 'StereoMode'; + $EBMLidList[EBML_ID_OLDSTEREOMODE] = 'OldStereoMode'; $EBMLidList[EBML_ID_TAG] = 'Tag'; $EBMLidList[EBML_ID_TAGATTACHMENTUID] = 'TagAttachmentUID'; $EBMLidList[EBML_ID_TAGBINARY] = 'TagBinary'; @@ -1682,7 +1715,18 @@ class getid3_matroska extends getid3_handler return (isset($EBMLidList[$value]) ? $EBMLidList[$value] : dechex($value)); } - + + public static function displayUnit($value) { + // http://www.matroska.org/technical/specs/index.html#DisplayUnit + static $units = array( + 0 => 'pixels', + 1 => 'centimeters', + 2 => 'inches', + 3 => 'Display Aspect Ratio'); + + return (isset($units[$value]) ? $units[$value] : 'unknown'); + } + private static function getDefaultStreamInfo($streams) { foreach (array_reverse($streams) as $stream) { @@ -1690,17 +1734,18 @@ class getid3_matroska extends getid3_handler break; } } - unset($stream['default']); - if (isset($stream['name'])) { - unset($stream['name']); + + $unset = array('default', 'name'); + foreach ($unset as $u) { + if (isset($stream[$u])) { + unset($stream[$u]); + } } - + $info = $stream; $info['streams'] = $streams; - + return $info; } } - -?> \ No newline at end of file diff --git a/app/library/getid3/getid3/module.audio-video.mpeg.php b/app/library/getid3/getid3/module.audio-video.mpeg.php new file mode 100755 index 00000000..ea7a9a9c --- /dev/null +++ b/app/library/getid3/getid3/module.audio-video.mpeg.php @@ -0,0 +1,606 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.mpeg.php // +// module for analyzing MPEG files // +// dependencies: module.audio.mp3.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); + +class getid3_mpeg extends getid3_handler { + + const START_CODE_BASE = "\x00\x00\x01"; + const VIDEO_PICTURE_START = "\x00\x00\x01\x00"; + const VIDEO_USER_DATA_START = "\x00\x00\x01\xB2"; + const VIDEO_SEQUENCE_HEADER = "\x00\x00\x01\xB3"; + const VIDEO_SEQUENCE_ERROR = "\x00\x00\x01\xB4"; + const VIDEO_EXTENSION_START = "\x00\x00\x01\xB5"; + const VIDEO_SEQUENCE_END = "\x00\x00\x01\xB7"; + const VIDEO_GROUP_START = "\x00\x00\x01\xB8"; + const AUDIO_START = "\x00\x00\x01\xC0"; + + + public function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'mpeg'; + $this->fseek($info['avdataoffset']); + + $MPEGstreamData = $this->fread($this->getid3->option_fread_buffer_size); + $MPEGstreamBaseOffset = 0; // how far are we from the beginning of the file data ($info['avdataoffset']) + $MPEGstreamDataOffset = 0; // how far are we from the beginning of the buffer data (~32kB) + + $StartCodeValue = false; + $prevStartCodeValue = false; + + $GOPcounter = -1; + $FramesByGOP = array(); + $ParsedAVchannels = array(); + + do { +//echo $MPEGstreamDataOffset.' vs '.(strlen($MPEGstreamData) - 1024).'
'; + if ($MPEGstreamDataOffset > (strlen($MPEGstreamData) - 16384)) { + // buffer running low, get more data +//echo 'reading more data
'; + $MPEGstreamData .= $this->fread($this->getid3->option_fread_buffer_size); + if (strlen($MPEGstreamData) > $this->getid3->option_fread_buffer_size) { + $MPEGstreamData = substr($MPEGstreamData, $MPEGstreamDataOffset); + $MPEGstreamBaseOffset += $MPEGstreamDataOffset; + $MPEGstreamDataOffset = 0; + } + } + if (($StartCodeOffset = strpos($MPEGstreamData, self::START_CODE_BASE, $MPEGstreamDataOffset)) === false) { +//echo 'no more start codes found.
'; + break; + } else { + $MPEGstreamDataOffset = $StartCodeOffset; + $prevStartCodeValue = $StartCodeValue; + $StartCodeValue = ord(substr($MPEGstreamData, $StartCodeOffset + 3, 1)); +//echo 'Found "'.strtoupper(dechex($StartCodeValue)).'" at offset '.($MPEGstreamBaseOffset + $StartCodeOffset).' ($MPEGstreamDataOffset = '.$MPEGstreamDataOffset.')
'; + } + $MPEGstreamDataOffset += 4; + switch ($StartCodeValue) { + + case 0x00: // picture_start_code + if (!empty($info['mpeg']['video']['bitrate_mode']) && ($info['mpeg']['video']['bitrate_mode'] == 'vbr')) { + $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 4)); + $bitstreamoffset = 0; + + $PictureHeader = array(); + + $PictureHeader['temporal_reference'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 10); // 10-bit unsigned integer associated with each input picture. It is incremented by one, modulo 1024, for each input frame. When a frame is coded as two fields the temporal reference in the picture header of both fields is the same. Following a group start header the temporal reference of the earliest picture (in display order) shall be reset to zero. + $PictureHeader['picture_coding_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_coding_type + $PictureHeader['vbv_delay'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 16); // 16 bits for vbv_delay + //... etc + + $FramesByGOP[$GOPcounter][] = $PictureHeader; + } + break; + + case 0xB3: // sequence_header_code + /* + Note: purposely doing the less-pretty (and probably a bit slower) method of using string of bits rather than bitwise operations. + Mostly because PHP 32-bit doesn't handle unsigned integers well for bitwise operation. + Also the MPEG stream is designed as a bitstream and often doesn't align nicely with byte boundaries. + */ + $info['video']['codec'] = 'MPEG-1'; // will be updated if extension_start_code found + + $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 8)); + $bitstreamoffset = 0; + + $info['mpeg']['video']['raw']['horizontal_size_value'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for horizontal frame size. Note: horizontal_size_extension, if present, will add 2 most-significant bits to this value + $info['mpeg']['video']['raw']['vertical_size_value'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for vertical frame size. Note: vertical_size_extension, if present, will add 2 most-significant bits to this value + $info['mpeg']['video']['raw']['aspect_ratio_information'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for aspect_ratio_information + $info['mpeg']['video']['raw']['frame_rate_code'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for Frame Rate id code + $info['mpeg']['video']['raw']['bitrate'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 18); // 18 bits for bit_rate_value (18 set bits = VBR, otherwise bitrate = this value * 400) + $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. + $info['mpeg']['video']['raw']['vbv_buffer_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 10); // 10 bits vbv_buffer_size_value + $info['mpeg']['video']['raw']['constrained_param_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: constrained_param_flag + $info['mpeg']['video']['raw']['load_intra_quantiser_matrix'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: load_intra_quantiser_matrix + + if ($info['mpeg']['video']['raw']['load_intra_quantiser_matrix']) { + $bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 12, 64)); + for ($i = 0; $i < 64; $i++) { + $info['mpeg']['video']['raw']['intra_quantiser_matrix'][$i] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); + } + } + $info['mpeg']['video']['raw']['load_non_intra_quantiser_matrix'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); + + if ($info['mpeg']['video']['raw']['load_non_intra_quantiser_matrix']) { + $bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 12 + ($info['mpeg']['video']['raw']['load_intra_quantiser_matrix'] ? 64 : 0), 64)); + for ($i = 0; $i < 64; $i++) { + $info['mpeg']['video']['raw']['non_intra_quantiser_matrix'][$i] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); + } + } + + $info['mpeg']['video']['pixel_aspect_ratio'] = self::videoAspectRatioLookup($info['mpeg']['video']['raw']['aspect_ratio_information']); + $info['mpeg']['video']['pixel_aspect_ratio_text'] = self::videoAspectRatioTextLookup($info['mpeg']['video']['raw']['aspect_ratio_information']); + $info['mpeg']['video']['frame_rate'] = self::videoFramerateLookup($info['mpeg']['video']['raw']['frame_rate_code']); + if ($info['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits = VBR + //$this->warning('This version of getID3() ['.$this->getid3->version().'] cannot determine average bitrate of VBR MPEG video files'); + $info['mpeg']['video']['bitrate_mode'] = 'vbr'; + } else { + $info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400; + $info['mpeg']['video']['bitrate_mode'] = 'cbr'; + $info['video']['bitrate'] = $info['mpeg']['video']['bitrate']; + } + $info['video']['resolution_x'] = $info['mpeg']['video']['raw']['horizontal_size_value']; + $info['video']['resolution_y'] = $info['mpeg']['video']['raw']['vertical_size_value']; + $info['video']['frame_rate'] = $info['mpeg']['video']['frame_rate']; + $info['video']['bitrate_mode'] = $info['mpeg']['video']['bitrate_mode']; + $info['video']['pixel_aspect_ratio'] = $info['mpeg']['video']['pixel_aspect_ratio']; + $info['video']['lossless'] = false; + $info['video']['bits_per_sample'] = 24; + break; + + case 0xB5: // extension_start_code + $info['video']['codec'] = 'MPEG-2'; + + $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 8)); // 48 bits for Sequence Extension ID; 61 bits for Sequence Display Extension ID; 59 bits for Sequence Scalable Extension ID + $bitstreamoffset = 0; + + $info['mpeg']['video']['raw']['extension_start_code_identifier'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for extension_start_code_identifier +//echo $info['mpeg']['video']['raw']['extension_start_code_identifier'].'
'; + switch ($info['mpeg']['video']['raw']['extension_start_code_identifier']) { + case 1: // 0001 Sequence Extension ID + $info['mpeg']['video']['raw']['profile_and_level_indication'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for profile_and_level_indication + $info['mpeg']['video']['raw']['progressive_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: progressive_sequence + $info['mpeg']['video']['raw']['chroma_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for chroma_format + $info['mpeg']['video']['raw']['horizontal_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for horizontal_size_extension + $info['mpeg']['video']['raw']['vertical_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for vertical_size_extension + $info['mpeg']['video']['raw']['bit_rate_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for bit_rate_extension + $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. + $info['mpeg']['video']['raw']['vbv_buffer_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for vbv_buffer_size_extension + $info['mpeg']['video']['raw']['low_delay'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: low_delay + $info['mpeg']['video']['raw']['frame_rate_extension_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for frame_rate_extension_n + $info['mpeg']['video']['raw']['frame_rate_extension_d'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for frame_rate_extension_d + + $info['video']['resolution_x'] = ($info['mpeg']['video']['raw']['horizontal_size_extension'] << 12) | $info['mpeg']['video']['raw']['horizontal_size_value']; + $info['video']['resolution_y'] = ($info['mpeg']['video']['raw']['vertical_size_extension'] << 12) | $info['mpeg']['video']['raw']['vertical_size_value']; + $info['video']['interlaced'] = !$info['mpeg']['video']['raw']['progressive_sequence']; + $info['mpeg']['video']['interlaced'] = !$info['mpeg']['video']['raw']['progressive_sequence']; + $info['mpeg']['video']['chroma_format'] = self::chromaFormatTextLookup($info['mpeg']['video']['raw']['chroma_format']); + break; + + case 2: // 0010 Sequence Display Extension ID + $info['mpeg']['video']['raw']['video_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for video_format + $info['mpeg']['video']['raw']['colour_description'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: colour_description + if ($info['mpeg']['video']['raw']['colour_description']) { + $info['mpeg']['video']['raw']['colour_primaries'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for colour_primaries + $info['mpeg']['video']['raw']['transfer_characteristics'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for transfer_characteristics + $info['mpeg']['video']['raw']['matrix_coefficients'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for matrix_coefficients + } + $info['mpeg']['video']['raw']['display_horizontal_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for display_horizontal_size + $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. + $info['mpeg']['video']['raw']['display_vertical_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for display_vertical_size + + $info['mpeg']['video']['video_format'] = self::videoFormatTextLookup($info['mpeg']['video']['raw']['video_format']); + break; + + case 3: // 0011 Quant Matrix Extension ID + break; + + case 5: // 0101 Sequence Scalable Extension ID + $info['mpeg']['video']['raw']['scalable_mode'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for scalable_mode + $info['mpeg']['video']['raw']['layer_id'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for layer_id + if ($info['mpeg']['video']['raw']['scalable_mode'] == 1) { // "spatial scalability" + $info['mpeg']['video']['raw']['lower_layer_prediction_horizontal_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for lower_layer_prediction_horizontal_size + $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. + $info['mpeg']['video']['raw']['lower_layer_prediction_vertical_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for lower_layer_prediction_vertical_size + $info['mpeg']['video']['raw']['horizontal_subsampling_factor_m'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for horizontal_subsampling_factor_m + $info['mpeg']['video']['raw']['horizontal_subsampling_factor_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for horizontal_subsampling_factor_n + $info['mpeg']['video']['raw']['vertical_subsampling_factor_m'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for vertical_subsampling_factor_m + $info['mpeg']['video']['raw']['vertical_subsampling_factor_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for vertical_subsampling_factor_n + } elseif ($info['mpeg']['video']['raw']['scalable_mode'] == 3) { // "temporal scalability" + $info['mpeg']['video']['raw']['picture_mux_enable'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: picture_mux_enable + if ($info['mpeg']['video']['raw']['picture_mux_enable']) { + $info['mpeg']['video']['raw']['mux_to_progressive_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: mux_to_progressive_sequence + } + $info['mpeg']['video']['raw']['picture_mux_order'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_mux_order + $info['mpeg']['video']['raw']['picture_mux_factor'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_mux_factor + } + + $info['mpeg']['video']['scalable_mode'] = self::scalableModeTextLookup($info['mpeg']['video']['raw']['scalable_mode']); + break; + + case 7: // 0111 Picture Display Extension ID + break; + + case 8: // 1000 Picture Coding Extension ID + $info['mpeg']['video']['raw']['f_code_00'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[0][0] (forward horizontal) + $info['mpeg']['video']['raw']['f_code_01'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[0][1] (forward vertical) + $info['mpeg']['video']['raw']['f_code_10'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[1][0] (backward horizontal) + $info['mpeg']['video']['raw']['f_code_11'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[1][1] (backward vertical) + $info['mpeg']['video']['raw']['intra_dc_precision'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for intra_dc_precision + $info['mpeg']['video']['raw']['picture_structure'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for picture_structure + $info['mpeg']['video']['raw']['top_field_first'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: top_field_first + $info['mpeg']['video']['raw']['frame_pred_frame_dct'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: frame_pred_frame_dct + $info['mpeg']['video']['raw']['concealment_motion_vectors'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: concealment_motion_vectors + $info['mpeg']['video']['raw']['q_scale_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: q_scale_type + $info['mpeg']['video']['raw']['intra_vlc_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: intra_vlc_format + $info['mpeg']['video']['raw']['alternate_scan'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: alternate_scan + $info['mpeg']['video']['raw']['repeat_first_field'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: repeat_first_field + $info['mpeg']['video']['raw']['chroma_420_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: chroma_420_type + $info['mpeg']['video']['raw']['progressive_frame'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: progressive_frame + $info['mpeg']['video']['raw']['composite_display_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: composite_display_flag + if ($info['mpeg']['video']['raw']['composite_display_flag']) { + $info['mpeg']['video']['raw']['v_axis'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: v_axis + $info['mpeg']['video']['raw']['field_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for field_sequence + $info['mpeg']['video']['raw']['sub_carrier'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: sub_carrier + $info['mpeg']['video']['raw']['burst_amplitude'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 7); // 7 bits for burst_amplitude + $info['mpeg']['video']['raw']['sub_carrier_phase'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for sub_carrier_phase + } + + $info['mpeg']['video']['intra_dc_precision_bits'] = $info['mpeg']['video']['raw']['intra_dc_precision'] + 8; + $info['mpeg']['video']['picture_structure'] = self::pictureStructureTextLookup($info['mpeg']['video']['raw']['picture_structure']); + break; + + case 9: // 1001 Picture Spatial Scalable Extension ID + break; + case 10: // 1010 Picture Temporal Scalable Extension ID + break; + + default: + $this->warning('Unexpected $info[mpeg][video][raw][extension_start_code_identifier] value of '.$info['mpeg']['video']['raw']['extension_start_code_identifier']); + break; + } + break; + + + case 0xB8: // group_of_pictures_header + $GOPcounter++; + if ($info['mpeg']['video']['bitrate_mode'] == 'vbr') { + $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 4)); // 27 bits needed for group_of_pictures_header + $bitstreamoffset = 0; + + $GOPheader = array(); + + $GOPheader['byte_offset'] = $MPEGstreamBaseOffset + $StartCodeOffset; + $GOPheader['drop_frame_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: drop_frame_flag + $GOPheader['time_code_hours'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for time_code_hours + $GOPheader['time_code_minutes'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_minutes + $marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation. + $GOPheader['time_code_seconds'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_seconds + $GOPheader['time_code_pictures'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_pictures + $GOPheader['closed_gop'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: closed_gop + $GOPheader['broken_link'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: broken_link + + $time_code_separator = ($GOPheader['drop_frame_flag'] ? ';' : ':'); // While non-drop time code is displayed with colons separating the digit pairs"HH:MM:SS:FF"drop frame is usually represented with a semi-colon (;) or period (.) as the divider between all the digit pairs"HH;MM;SS;FF", "HH.MM.SS.FF" + $GOPheader['time_code'] = sprintf('%02d'.$time_code_separator.'%02d'.$time_code_separator.'%02d'.$time_code_separator.'%02d', $GOPheader['time_code_hours'], $GOPheader['time_code_minutes'], $GOPheader['time_code_seconds'], $GOPheader['time_code_pictures']); + + $info['mpeg']['group_of_pictures'][] = $GOPheader; + } + break; + + case 0xC0: // audio stream + case 0xC1: // audio stream + case 0xC2: // audio stream + case 0xC3: // audio stream + case 0xC4: // audio stream + case 0xC5: // audio stream + case 0xC6: // audio stream + case 0xC7: // audio stream + case 0xC8: // audio stream + case 0xC9: // audio stream + case 0xCA: // audio stream + case 0xCB: // audio stream + case 0xCC: // audio stream + case 0xCD: // audio stream + case 0xCE: // audio stream + case 0xCF: // audio stream + case 0xD0: // audio stream + case 0xD1: // audio stream + case 0xD2: // audio stream + case 0xD3: // audio stream + case 0xD4: // audio stream + case 0xD5: // audio stream + case 0xD6: // audio stream + case 0xD7: // audio stream + case 0xD8: // audio stream + case 0xD9: // audio stream + case 0xDA: // audio stream + case 0xDB: // audio stream + case 0xDC: // audio stream + case 0xDD: // audio stream + case 0xDE: // audio stream + case 0xDF: // audio stream + //case 0xE0: // video stream + //case 0xE1: // video stream + //case 0xE2: // video stream + //case 0xE3: // video stream + //case 0xE4: // video stream + //case 0xE5: // video stream + //case 0xE6: // video stream + //case 0xE7: // video stream + //case 0xE8: // video stream + //case 0xE9: // video stream + //case 0xEA: // video stream + //case 0xEB: // video stream + //case 0xEC: // video stream + //case 0xED: // video stream + //case 0xEE: // video stream + //case 0xEF: // video stream + if (isset($ParsedAVchannels[$StartCodeValue])) { + break; + } + $ParsedAVchannels[$StartCodeValue] = $StartCodeValue; + // http://en.wikipedia.org/wiki/Packetized_elementary_stream + // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html +/* + $PackedElementaryStream = array(); + if ($StartCodeValue >= 0xE0) { + $PackedElementaryStream['stream_type'] = 'video'; + $PackedElementaryStream['stream_id'] = $StartCodeValue - 0xE0; + } else { + $PackedElementaryStream['stream_type'] = 'audio'; + $PackedElementaryStream['stream_id'] = $StartCodeValue - 0xC0; + } + $PackedElementaryStream['packet_length'] = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $StartCodeOffset + 4, 2)); + + $bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 6, 3)); // more may be needed below + $bitstreamoffset = 0; + + $PackedElementaryStream['marker_bits'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for marker_bits -- should be "10" = 2 +echo 'marker_bits = '.$PackedElementaryStream['marker_bits'].'
'; + $PackedElementaryStream['scrambling_control'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for scrambling_control -- 00 implies not scrambled + $PackedElementaryStream['priority'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: priority + $PackedElementaryStream['data_alignment_indicator'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: data_alignment_indicator -- 1 indicates that the PES packet header is immediately followed by the video start code or audio syncword + $PackedElementaryStream['copyright'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: copyright -- 1 implies copyrighted + $PackedElementaryStream['original_or_copy'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: original_or_copy -- 1 implies original + $PackedElementaryStream['pts_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: pts_flag -- Presentation Time Stamp + $PackedElementaryStream['dts_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: dts_flag -- Decode Time Stamp + $PackedElementaryStream['escr_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: escr_flag -- Elementary Stream Clock Reference + $PackedElementaryStream['es_rate_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: es_rate_flag -- Elementary Stream [data] Rate + $PackedElementaryStream['dsm_trick_mode_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: dsm_trick_mode_flag -- DSM trick mode - not used by DVD + $PackedElementaryStream['additional_copy_info_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: additional_copy_info_flag + $PackedElementaryStream['crc_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: crc_flag + $PackedElementaryStream['extension_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: extension_flag + $PackedElementaryStream['pes_remain_header_length'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 1 bit flag: priority + + $additional_header_bytes = 0; + $additional_header_bytes += ($PackedElementaryStream['pts_flag'] ? 5 : 0); + $additional_header_bytes += ($PackedElementaryStream['dts_flag'] ? 5 : 0); + $additional_header_bytes += ($PackedElementaryStream['escr_flag'] ? 6 : 0); + $additional_header_bytes += ($PackedElementaryStream['es_rate_flag'] ? 3 : 0); + $additional_header_bytes += ($PackedElementaryStream['additional_copy_info_flag'] ? 1 : 0); + $additional_header_bytes += ($PackedElementaryStream['crc_flag'] ? 2 : 0); + $additional_header_bytes += ($PackedElementaryStream['extension_flag'] ? 1 : 0); +$PackedElementaryStream['additional_header_bytes'] = $additional_header_bytes; + $bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 9, $additional_header_bytes)); + + $info['mpeg']['packed_elementary_streams'][$PackedElementaryStream['stream_type']][$PackedElementaryStream['stream_id']][] = $PackedElementaryStream; +*/ + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info = $info; + $getid3_mp3 = new getid3_mp3($getid3_temp); + for ($i = 0; $i <= 7; $i++) { + // some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after + // I have no idea why or what the difference is, so this is a stupid hack. + // If anybody has any better idea of what's going on, please let me know - info@getid3.org + $getid3_temp->info = $info; // only overwrite real data if valid header found +//echo 'audio at? '.($MPEGstreamBaseOffset + $StartCodeOffset + 4 + 8 + $i).'
'; + if ($getid3_mp3->decodeMPEGaudioHeader($MPEGstreamBaseOffset + $StartCodeOffset + 4 + 8 + $i, $getid3_temp->info, false)) { +//echo 'yes!
'; + $info = $getid3_temp->info; + $info['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['lossless'] = false; + break; + } + } + unset($getid3_temp, $getid3_mp3); + break; + + case 0xBC: // Program Stream Map + case 0xBD: // Private stream 1 (non MPEG audio, subpictures) + case 0xBE: // Padding stream + case 0xBF: // Private stream 2 (navigation data) + case 0xF0: // ECM stream + case 0xF1: // EMM stream + case 0xF2: // DSM-CC stream + case 0xF3: // ISO/IEC_13522_stream + case 0xF4: // ITU-I Rec. H.222.1 type A + case 0xF5: // ITU-I Rec. H.222.1 type B + case 0xF6: // ITU-I Rec. H.222.1 type C + case 0xF7: // ITU-I Rec. H.222.1 type D + case 0xF8: // ITU-I Rec. H.222.1 type E + case 0xF9: // ancilliary stream + case 0xFA: // ISO/IEC 14496-1 SL-packtized stream + case 0xFB: // ISO/IEC 14496-1 FlexMux stream + case 0xFC: // metadata stream + case 0xFD: // extended stream ID + case 0xFE: // reserved data stream + case 0xFF: // program stream directory + // ignore + break; + + default: + // ignore + break; + } + } while (true); + + + +// // Temporary hack to account for interleaving overhead: +// if (!empty($info['video']['bitrate']) && !empty($info['audio']['bitrate'])) { +// $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['video']['bitrate'] + $info['audio']['bitrate']); +// +// // Interleaved MPEG audio/video files have a certain amount of overhead that varies +// // by both video and audio bitrates, and not in any sensible, linear/logarithmic pattern +// // Use interpolated lookup tables to approximately guess how much is overhead, because +// // playtime is calculated as filesize / total-bitrate +// $info['playtime_seconds'] *= self::systemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']); +// +// //switch ($info['video']['bitrate']) { +// // case('5000000'): +// // $multiplier = 0.93292642112380355828048824319889; +// // break; +// // case('5500000'): +// // $multiplier = 0.93582895375200989965359777343219; +// // break; +// // case('6000000'): +// // $multiplier = 0.93796247714820932532911373859139; +// // break; +// // case('7000000'): +// // $multiplier = 0.9413264083635103463010117778776; +// // break; +// // default: +// // $multiplier = 1; +// // break; +// //} +// //$info['playtime_seconds'] *= $multiplier; +// //$info['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.'; +// if ($info['video']['bitrate'] < 50000) { +// $this->warning('Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.'); +// } +// } +// +/* +$time_prev = 0; +$byte_prev = 0; +$vbr_bitrates = array(); +foreach ($info['mpeg']['group_of_pictures'] as $gopkey => $gopdata) { + $time_this = ($gopdata['time_code_hours'] * 3600) + ($gopdata['time_code_minutes'] * 60) + $gopdata['time_code_seconds'] + ($gopdata['time_code_seconds'] / 30); + $byte_this = $gopdata['byte_offset']; + if ($gopkey > 0) { + if ($time_this > $time_prev) { + $bytedelta = $byte_this - $byte_prev; + $timedelta = $time_this - $time_prev; + $this_bitrate = ($bytedelta * 8) / $timedelta; +echo $gopkey.': ('.number_format($time_prev, 2).'-'.number_format($time_this, 2).') '.number_format($bytedelta).' bytes over '.number_format($timedelta, 3).' seconds = '.number_format($this_bitrate / 1000, 2).'kbps
'; + $time_prev = $time_this; + $byte_prev = $byte_this; + $vbr_bitrates[] = $this_bitrate; + } + } +} +echo 'average_File_bitrate = '.number_format(array_sum($vbr_bitrates) / count($vbr_bitrates), 1).'
'; +*/ +//echo '
'.print_r($FramesByGOP, true).'
'; + if (!empty($info['mpeg']['video']['bitrate_mode']) && ($info['mpeg']['video']['bitrate_mode'] == 'vbr')) { + $last_GOP_id = max(array_keys($FramesByGOP)); + $frames_in_last_GOP = count($FramesByGOP[$last_GOP_id]); + $gopdata = &$info['mpeg']['group_of_pictures'][$last_GOP_id]; + $info['playtime_seconds'] = ($gopdata['time_code_hours'] * 3600) + ($gopdata['time_code_minutes'] * 60) + $gopdata['time_code_seconds'] + (($gopdata['time_code_pictures'] + $frames_in_last_GOP + 1) / $info['mpeg']['video']['frame_rate']); + if (!isset($info['video']['bitrate'])) { + $overall_bitrate = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; + $info['video']['bitrate'] = $overall_bitrate - (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0); + } + unset($info['mpeg']['group_of_pictures']); + } + + return true; + } + + private function readBitsFromStream(&$bitstream, &$bitstreamoffset, $bits_to_read, $return_singlebit_as_boolean=true) { + $return = bindec(substr($bitstream, $bitstreamoffset, $bits_to_read)); + $bitstreamoffset += $bits_to_read; + if (($bits_to_read == 1) && $return_singlebit_as_boolean) { + $return = (bool) $return; + } + return $return; + } + + + public static function systemNonOverheadPercentage($VideoBitrate, $AudioBitrate) { + $OverheadPercentage = 0; + + $AudioBitrate = max(min($AudioBitrate / 1000, 384), 32); // limit to range of 32kbps - 384kbps (should be only legal bitrates, but maybe VBR?) + $VideoBitrate = max(min($VideoBitrate / 1000, 10000), 10); // limit to range of 10kbps - 10Mbps (beyond that curves flatten anyways, no big loss) + + + //OMBB[audiobitrate] = array(video-10kbps, video-100kbps, video-1000kbps, video-10000kbps) + $OverheadMultiplierByBitrate[32] = array(0, 0.9676287944368530, 0.9802276264360310, 0.9844916183244460, 0.9852821845179940); + $OverheadMultiplierByBitrate[48] = array(0, 0.9779100089209830, 0.9787770035359320, 0.9846738664076130, 0.9852683013799960); + $OverheadMultiplierByBitrate[56] = array(0, 0.9731249855367600, 0.9776624308938040, 0.9832606361852130, 0.9843922606633340); + $OverheadMultiplierByBitrate[64] = array(0, 0.9755642683275760, 0.9795256705493390, 0.9836573009193170, 0.9851122539404470); + $OverheadMultiplierByBitrate[96] = array(0, 0.9788025247497290, 0.9798553314148700, 0.9822956869792560, 0.9834815119124690); + $OverheadMultiplierByBitrate[128] = array(0, 0.9816940050925480, 0.9821675936072120, 0.9829756927470870, 0.9839763420152050); + $OverheadMultiplierByBitrate[160] = array(0, 0.9825894094561180, 0.9820913399073960, 0.9823907143253970, 0.9832821783651570); + $OverheadMultiplierByBitrate[192] = array(0, 0.9832038474336260, 0.9825731694317960, 0.9821028622712400, 0.9828262076447620); + $OverheadMultiplierByBitrate[224] = array(0, 0.9836516298538770, 0.9824718601823890, 0.9818302180625380, 0.9823735101626480); + $OverheadMultiplierByBitrate[256] = array(0, 0.9845863022094920, 0.9837229411967540, 0.9824521662210830, 0.9828645172100790); + $OverheadMultiplierByBitrate[320] = array(0, 0.9849565280263180, 0.9837683142805110, 0.9822885275960400, 0.9824424382727190); + $OverheadMultiplierByBitrate[384] = array(0, 0.9856094774357600, 0.9844573394432720, 0.9825970399837330, 0.9824673808303890); + + $BitrateToUseMin = 32; + $BitrateToUseMax = 32; + $previousBitrate = 32; + foreach ($OverheadMultiplierByBitrate as $key => $value) { + if ($AudioBitrate >= $previousBitrate) { + $BitrateToUseMin = $previousBitrate; + } + if ($AudioBitrate < $key) { + $BitrateToUseMax = $key; + break; + } + $previousBitrate = $key; + } + $FactorA = ($BitrateToUseMax - $AudioBitrate) / ($BitrateToUseMax - $BitrateToUseMin); + + $VideoBitrateLog10 = log10($VideoBitrate); + $VideoFactorMin1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][floor($VideoBitrateLog10)]; + $VideoFactorMin2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][floor($VideoBitrateLog10)]; + $VideoFactorMax1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][ceil($VideoBitrateLog10)]; + $VideoFactorMax2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][ceil($VideoBitrateLog10)]; + $FactorV = $VideoBitrateLog10 - floor($VideoBitrateLog10); + + $OverheadPercentage = $VideoFactorMin1 * $FactorA * $FactorV; + $OverheadPercentage += $VideoFactorMin2 * (1 - $FactorA) * $FactorV; + $OverheadPercentage += $VideoFactorMax1 * $FactorA * (1 - $FactorV); + $OverheadPercentage += $VideoFactorMax2 * (1 - $FactorA) * (1 - $FactorV); + + return $OverheadPercentage; + } + + + public static function videoFramerateLookup($rawframerate) { + $lookup = array(0, 23.976, 24, 25, 29.97, 30, 50, 59.94, 60); + return (isset($lookup[$rawframerate]) ? (float) $lookup[$rawframerate] : (float) 0); + } + + public static function videoAspectRatioLookup($rawaspectratio) { + $lookup = array(0, 1, 0.6735, 0.7031, 0.7615, 0.8055, 0.8437, 0.8935, 0.9157, 0.9815, 1.0255, 1.0695, 1.0950, 1.1575, 1.2015, 0); + return (isset($lookup[$rawaspectratio]) ? (float) $lookup[$rawaspectratio] : (float) 0); + } + + public static function videoAspectRatioTextLookup($rawaspectratio) { + $lookup = array('forbidden', 'square pixels', '0.6735', '16:9, 625 line, PAL', '0.7615', '0.8055', '16:9, 525 line, NTSC', '0.8935', '4:3, 625 line, PAL, CCIR601', '0.9815', '1.0255', '1.0695', '4:3, 525 line, NTSC, CCIR601', '1.1575', '1.2015', 'reserved'); + return (isset($lookup[$rawaspectratio]) ? $lookup[$rawaspectratio] : ''); + } + + public static function videoFormatTextLookup($video_format) { + // ISO/IEC 13818-2, section 6.3.6, Table 6-6. Meaning of video_format + $lookup = array('component', 'PAL', 'NTSC', 'SECAM', 'MAC', 'Unspecified video format', 'reserved(6)', 'reserved(7)'); + return (isset($lookup[$video_format]) ? $lookup[$video_format] : ''); + } + + public static function scalableModeTextLookup($scalable_mode) { + // ISO/IEC 13818-2, section 6.3.8, Table 6-10. Definition of scalable_mode + $lookup = array('data partitioning', 'spatial scalability', 'SNR scalability', 'temporal scalability'); + return (isset($lookup[$scalable_mode]) ? $lookup[$scalable_mode] : ''); + } + + public static function pictureStructureTextLookup($picture_structure) { + // ISO/IEC 13818-2, section 6.3.11, Table 6-14 Meaning of picture_structure + $lookup = array('reserved', 'Top Field', 'Bottom Field', 'Frame picture'); + return (isset($lookup[$picture_structure]) ? $lookup[$picture_structure] : ''); + } + + public static function chromaFormatTextLookup($chroma_format) { + // ISO/IEC 13818-2, section 6.3.11, Table 6-14 Meaning of picture_structure + $lookup = array('reserved', '4:2:0', '4:2:2', '4:4:4'); + return (isset($lookup[$chroma_format]) ? $lookup[$chroma_format] : ''); + } + +} \ No newline at end of file diff --git a/app/library/getid3/module.audio-video.nsv.php b/app/library/getid3/getid3/module.audio-video.nsv.php old mode 100644 new mode 100755 similarity index 92% rename from app/library/getid3/module.audio-video.nsv.php rename to app/library/getid3/getid3/module.audio-video.nsv.php index 5a587e67..697d5956 --- a/app/library/getid3/module.audio-video.nsv.php +++ b/app/library/getid3/getid3/module.audio-video.nsv.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,11 +18,11 @@ class getid3_nsv extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $NSVheader = fread($this->getid3->fp, 4); + $this->fseek($info['avdataoffset']); + $NSVheader = $this->fread(4); switch ($NSVheader) { case 'NSVs': @@ -58,10 +59,10 @@ class getid3_nsv extends getid3_handler return true; } - function getNSVsHeaderFilepointer($fileoffset) { + public function getNSVsHeaderFilepointer($fileoffset) { $info = &$this->getid3->info; - fseek($this->getid3->fp, $fileoffset, SEEK_SET); - $NSVsheader = fread($this->getid3->fp, 28); + $this->fseek($fileoffset); + $NSVsheader = $this->fread(28); $offset = 0; $info['nsv']['NSVs']['identifier'] = substr($NSVsheader, $offset, 4); @@ -131,10 +132,10 @@ class getid3_nsv extends getid3_handler return true; } - function getNSVfHeaderFilepointer($fileoffset, $getTOCoffsets=false) { + public function getNSVfHeaderFilepointer($fileoffset, $getTOCoffsets=false) { $info = &$this->getid3->info; - fseek($this->getid3->fp, $fileoffset, SEEK_SET); - $NSVfheader = fread($this->getid3->fp, 28); + $this->fseek($fileoffset); + $NSVfheader = $this->fread(28); $offset = 0; $info['nsv']['NSVf']['identifier'] = substr($NSVfheader, $offset, 4); @@ -171,7 +172,7 @@ class getid3_nsv extends getid3_handler return false; } - $NSVfheader .= fread($this->getid3->fp, $info['nsv']['NSVf']['meta_size'] + (4 * $info['nsv']['NSVf']['TOC_entries_1']) + (4 * $info['nsv']['NSVf']['TOC_entries_2'])); + $NSVfheader .= $this->fread($info['nsv']['NSVf']['meta_size'] + (4 * $info['nsv']['NSVf']['TOC_entries_1']) + (4 * $info['nsv']['NSVf']['TOC_entries_2'])); $NSVfheaderlength = strlen($NSVfheader); $info['nsv']['NSVf']['metadata'] = substr($NSVfheader, $offset, $info['nsv']['NSVf']['meta_size']); $offset += $info['nsv']['NSVf']['meta_size']; @@ -205,7 +206,7 @@ class getid3_nsv extends getid3_handler } - static function NSVframerateLookup($framerateindex) { + public static function NSVframerateLookup($framerateindex) { if ($framerateindex <= 127) { return (float) $framerateindex; } @@ -221,6 +222,3 @@ class getid3_nsv extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio-video.quicktime.php b/app/library/getid3/getid3/module.audio-video.quicktime.php old mode 100644 new mode 100755 similarity index 78% rename from app/library/getid3/module.audio-video.quicktime.php rename to app/library/getid3/getid3/module.audio-video.quicktime.php index 3e96ea1a..482c0912 --- a/app/library/getid3/module.audio-video.quicktime.php +++ b/app/library/getid3/getid3/module.audio-video.quicktime.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -10,43 +11,45 @@ // module.audio-video.quicktime.php // // module for analyzing Quicktime and MP3-in-MP4 files // // dependencies: module.audio.mp3.php // +// dependencies: module.tag.id3v2.php // // /// ///////////////////////////////////////////////////////////////// getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); // needed for ISO 639-2 language code lookup class getid3_quicktime extends getid3_handler { - var $ReturnAtomData = true; - var $ParseAllPossibleAtoms = false; + public $ReturnAtomData = true; + public $ParseAllPossibleAtoms = false; - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'quicktime'; $info['quicktime']['hinting'] = false; $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); $offset = 0; $atomcounter = 0; - + $atom_data_read_buffer_size = ($info['php_memory_limit'] ? round($info['php_memory_limit'] / 2) : $this->getid3->option_fread_buffer_size * 1024); // allow [default: 32MB] if PHP configured with no memory_limit while ($offset < $info['avdataend']) { if (!getid3_lib::intValueSupported($offset)) { $info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; break; } - fseek($this->getid3->fp, $offset, SEEK_SET); - $AtomHeader = fread($this->getid3->fp, 8); + $this->fseek($offset); + $AtomHeader = $this->fread(8); $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4)); $atomname = substr($AtomHeader, 4, 4); - // 64-bit MOV patch by jlegatektnc*com + // 64-bit MOV patch by jlegateØktnc*com if ($atomsize == 1) { - $atomsize = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 8)); + $atomsize = getid3_lib::BigEndian2Int($this->fread(8)); } $info['quicktime'][$atomname]['name'] = $atomname; @@ -64,58 +67,8 @@ class getid3_quicktime extends getid3_handler // to read user data atoms, you should allow for the terminating 0. break; } - switch ($atomname) { - case 'mdat': // Media DATa atom - // 'mdat' contains the actual data for the audio/video - if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) { - - $info['avdataoffset'] = $info['quicktime'][$atomname]['offset'] + 8; - $OldAVDataEnd = $info['avdataend']; - $info['avdataend'] = $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; - - $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); - $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; - $getid3_temp->info['avdataend'] = $info['avdataend']; - $getid3_mp3 = new getid3_mp3($getid3_temp); - if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode(fread($this->getid3->fp, 4)))) { - $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); - if (!empty($getid3_temp->info['warning'])) { - foreach ($getid3_temp->info['warning'] as $value) { - $info['warning'][] = $value; - } - } - if (!empty($getid3_temp->info['mpeg'])) { - $info['mpeg'] = $getid3_temp->info['mpeg']; - if (isset($info['mpeg']['audio'])) { - $info['audio']['dataformat'] = 'mp3'; - $info['audio']['codec'] = (!empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' :'mp3'))); - $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; - $info['audio']['channels'] = $info['mpeg']['audio']['channels']; - $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; - $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); - $info['bitrate'] = $info['audio']['bitrate']; - } - } - } - unset($getid3_mp3, $getid3_temp); - $info['avdataend'] = $OldAVDataEnd; - unset($OldAVDataEnd); - - } - break; - - case 'free': // FREE space atom - case 'skip': // SKIP atom - case 'wide': // 64-bit expansion placeholder atom - // 'free', 'skip' and 'wide' are just padding, contains no useful data at all - break; - - default: - $atomHierarchy = array(); - $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($this->getid3->fp, $atomsize), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); - break; - } + $atomHierarchy = array(); + $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, $this->fread(min($atomsize, $atom_data_read_buffer_size)), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); $offset += $atomsize; $atomcounter++; @@ -165,19 +118,17 @@ class getid3_quicktime extends getid3_handler return true; } - function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { + public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm $info = &$this->getid3->info; - $atom_parent = array_pop($atomHierarchy); + $atom_parent = end($atomHierarchy); // not array_pop($atomHierarchy); see http://www.getid3.org/phpBB3/viewtopic.php?t=1717 array_push($atomHierarchy, $atomname); $atom_structure['hierarchy'] = implode(' ', $atomHierarchy); $atom_structure['name'] = $atomname; $atom_structure['size'] = $atomsize; $atom_structure['offset'] = $baseoffset; -//echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8)).'
'; -//echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8), false).'

'; switch ($atomname) { case 'moov': // MOVie container atom case 'trak': // TRAcK container atom @@ -197,27 +148,27 @@ class getid3_quicktime extends getid3_handler break; case 'ilst': // Item LiST container atom - $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); - - // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted - $allnumericnames = true; - foreach ($atom_structure['subatoms'] as $subatomarray) { - if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) { - $allnumericnames = false; - break; - } - } - if ($allnumericnames) { - $newData = array(); + if ($atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms)) { + // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted + $allnumericnames = true; foreach ($atom_structure['subatoms'] as $subatomarray) { - foreach ($subatomarray['subatoms'] as $newData_subatomarray) { - unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); - $newData[$subatomarray['name']] = $newData_subatomarray; + if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) { + $allnumericnames = false; break; } } - $atom_structure['data'] = $newData; - unset($atom_structure['subatoms']); + if ($allnumericnames) { + $newData = array(); + foreach ($atom_structure['subatoms'] as $subatomarray) { + foreach ($subatomarray['subatoms'] as $newData_subatomarray) { + unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); + $newData[$subatomarray['name']] = $newData_subatomarray; + break; + } + } + $atom_structure['data'] = $newData; + unset($atom_structure['subatoms']); + } } break; @@ -305,46 +256,46 @@ class getid3_quicktime extends getid3_handler case 'geID': case 'plID': case 'sfID': // iTunes store country - case 'alb': // ALBum - case 'art': // ARTist - case 'ART': - case 'aut': - case 'cmt': // CoMmenT - case 'com': // COMposer - case 'cpy': - case 'day': // content created year - case 'dir': - case 'ed1': - case 'ed2': - case 'ed3': - case 'ed4': - case 'ed5': - case 'ed6': - case 'ed7': - case 'ed8': - case 'ed9': - case 'enc': - case 'fmt': - case 'gen': // GENre - case 'grp': // GRouPing - case 'hst': - case 'inf': - case 'lyr': // LYRics - case 'mak': - case 'mod': - case 'nam': // full NAMe - case 'ope': - case 'PRD': - case 'prd': - case 'prf': - case 'req': - case 'src': - case 'swr': - case 'too': // encoder - case 'trk': // TRacK - case 'url': - case 'wrn': - case 'wrt': // WRiTer + case "\xA9".'alb': // ALBum + case "\xA9".'art': // ARTist + case "\xA9".'ART': + case "\xA9".'aut': + case "\xA9".'cmt': // CoMmenT + case "\xA9".'com': // COMposer + case "\xA9".'cpy': + case "\xA9".'day': // content created year + case "\xA9".'dir': + case "\xA9".'ed1': + case "\xA9".'ed2': + case "\xA9".'ed3': + case "\xA9".'ed4': + case "\xA9".'ed5': + case "\xA9".'ed6': + case "\xA9".'ed7': + case "\xA9".'ed8': + case "\xA9".'ed9': + case "\xA9".'enc': + case "\xA9".'fmt': + case "\xA9".'gen': // GENre + case "\xA9".'grp': // GRouPing + case "\xA9".'hst': + case "\xA9".'inf': + case "\xA9".'lyr': // LYRics + case "\xA9".'mak': + case "\xA9".'mod': + case "\xA9".'nam': // full NAMe + case "\xA9".'ope': + case "\xA9".'PRD': + case "\xA9".'prd': + case "\xA9".'prf': + case "\xA9".'req': + case "\xA9".'src': + case "\xA9".'swr': + case "\xA9".'too': // encoder + case "\xA9".'trk': // TRacK + case "\xA9".'url': + case "\xA9".'wrn': + case "\xA9".'wrt': // WRiTer case '----': // itunes specific if ($atom_parent == 'udta') { // User data atom handler @@ -366,12 +317,18 @@ class getid3_quicktime extends getid3_handler $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); + if ($boxsmallsize <= 1) { + $info['warning'][] = 'Invalid QuickTime atom smallbox size "'.$boxsmallsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset); + $atom_structure['data'] = null; + $atomoffset = strlen($atom_data); + break; + } switch ($boxsmalltype) { case "\x10\xB5": $atom_structure['data'] = $boxsmalldata; break; default: - $info['warning'][] = 'Unknown QuickTime smallbox type: "'.getid3_lib::PrintHexBytes($boxsmalltype).'" at offset '.$baseoffset; + $info['warning'][] = 'Unknown QuickTime smallbox type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxsmalltype).'" ('.trim(getid3_lib::PrintHexBytes($boxsmalltype)).') at offset '.$baseoffset; $atom_structure['data'] = $atom_data; break; } @@ -382,6 +339,13 @@ class getid3_quicktime extends getid3_handler $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); $boxtype = substr($atom_data, $atomoffset + 4, 4); $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); + if ($boxsize <= 1) { + $info['warning'][] = 'Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" at offset: '.($atom_structure['offset'] + $atomoffset); + $atom_structure['data'] = null; + $atomoffset = strlen($atom_data); + break; + } + $atomoffset += $boxsize; switch ($boxtype) { case 'mean': @@ -393,7 +357,7 @@ class getid3_quicktime extends getid3_handler $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); switch ($atom_structure['flags_raw']) { - case 0: // data flag + case 0: // data flag case 21: // tmpo/cpil flag switch ($atomname) { case 'cpil': @@ -444,21 +408,30 @@ class getid3_quicktime extends getid3_handler } break; - case 1: // text flag + case 1: // text flag case 13: // image flag default: $atom_structure['data'] = substr($boxdata, 8); + if ($atomname == 'covr') { + // not a foolproof check, but better than nothing + if (preg_match('#^\xFF\xD8\xFF#', $atom_structure['data'])) { + $atom_structure['image_mime'] = 'image/jpeg'; + } elseif (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $atom_structure['data'])) { + $atom_structure['image_mime'] = 'image/png'; + } elseif (preg_match('#^GIF#', $atom_structure['data'])) { + $atom_structure['image_mime'] = 'image/gif'; + } + } break; } break; default: - $info['warning'][] = 'Unknown QuickTime box type: "'.getid3_lib::PrintHexBytes($boxtype).'" at offset '.$baseoffset; + $info['warning'][] = 'Unknown QuickTime box type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $boxtype).'" ('.trim(getid3_lib::PrintHexBytes($boxtype)).') at offset '.$baseoffset; $atom_structure['data'] = $atom_data; } - $atomoffset += $boxsize; } } } @@ -495,7 +468,7 @@ class getid3_quicktime extends getid3_handler case 'cmvd': // Compressed MooV Data atom - // Code by ubergeekubergeek*tv based on information from + // Code by ubergeekØubergeek*tv based on information from // http://developer.apple.com/quicktime/icefloe/dispatch012.html $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); @@ -651,19 +624,65 @@ class getid3_quicktime extends getid3_handler switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { case "\x00\x00\x00\x00": - // audio atom + // audio tracks $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); + // video tracks + // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap3/qtff3.html + $atom_structure['sample_description_table'][$i]['temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); + $atom_structure['sample_description_table'][$i]['spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); + $atom_structure['sample_description_table'][$i]['width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); + $atom_structure['sample_description_table'][$i]['height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); + $atom_structure['sample_description_table'][$i]['resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); + $atom_structure['sample_description_table'][$i]['resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); + $atom_structure['sample_description_table'][$i]['data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 4)); + $atom_structure['sample_description_table'][$i]['frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 36, 2)); + $atom_structure['sample_description_table'][$i]['compressor_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 38, 4); + $atom_structure['sample_description_table'][$i]['pixel_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 42, 2)); + $atom_structure['sample_description_table'][$i]['color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 44, 2)); + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case '2vuY': case 'avc1': + case 'cvid': + case 'dvc ': + case 'dvcp': + case 'gif ': + case 'h263': + case 'jpeg': + case 'kpcd': + case 'mjpa': + case 'mjpb': case 'mp4v': + case 'png ': + case 'raw ': + case 'rle ': + case 'rpza': + case 'smc ': + case 'SVQ1': + case 'SVQ3': + case 'tiff': + case 'v210': + case 'v216': + case 'v308': + case 'v408': + case 'v410': + case 'yuv2': $info['fileformat'] = 'mp4'; $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; - //$info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] does not fully support MPEG-4 audio/video streams'; // 2011-02-18: why am I warning about this again? What's not supported? +// http://www.getid3.org/phpBB3/viewtopic.php?t=1550 +//if ((!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['width'])) && (empty($info['video']['resolution_x']) || empty($info['video']['resolution_y']) || (number_format($info['video']['resolution_x'], 6) != number_format(round($info['video']['resolution_x']), 6)) || (number_format($info['video']['resolution_y'], 6) != number_format(round($info['video']['resolution_y']), 6)))) { // ugly check for floating point numbers +if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['height'])) { + // assume that values stored here are more important than values stored in [tkhd] atom + $info['video']['resolution_x'] = $atom_structure['sample_description_table'][$i]['width']; + $info['video']['resolution_y'] = $atom_structure['sample_description_table'][$i]['height']; + $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; + $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; +} break; case 'qtvr': @@ -779,7 +798,12 @@ class getid3_quicktime extends getid3_handler $sttsEntriesDataOffset = 8; //$FrameRateCalculatorArray = array(); $frames_count = 0; - for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + + $max_stts_entries_to_scan = ($info['php_memory_limit'] ? min(floor($this->getid3->memory_limit / 10000), $atom_structure['number_entries']) : $atom_structure['number_entries']); + if ($max_stts_entries_to_scan < $atom_structure['number_entries']) { + $info['warning'][] = 'QuickTime atom "stts" has '.$atom_structure['number_entries'].' but only scanning the first '.$max_stts_entries_to_scan.' entries due to limited PHP memory available ('.floor($atom_structure['number_entries'] / 1048576).'MB).'; + } + for ($i = 0; $i < $max_stts_entries_to_scan; $i++) { $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); $sttsEntriesDataOffset += 4; $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); @@ -1025,8 +1049,8 @@ class getid3_quicktime extends getid3_handler case 'sync': // SYNChronization atom case 'scpt': // tranSCriPT atom case 'ssrc': // non-primary SouRCe atom - for ($i = 0; $i < (strlen($atom_data) % 4); $i++) { - $atom_structure['track_id'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $i * 4, 4)); + for ($i = 0; $i < strlen($atom_data); $i += 4) { + @$atom_structure['track_id'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); } break; @@ -1115,18 +1139,19 @@ class getid3_quicktime extends getid3_handler $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); +// http://developer.apple.com/library/mac/#documentation/QuickTime/RM/MovieBasics/MTEditing/K-Chapter/11MatrixFunctions.html +// http://developer.apple.com/library/mac/#documentation/QuickTime/qtff/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737 $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); - $atom_structure['matrix_u'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); + $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 48, 4)); $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); - $atom_structure['matrix_v'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); - $atom_structure['matrix_x'] = getid3_lib::FixedPoint2_30(substr($atom_data, 64, 4)); - $atom_structure['matrix_y'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); + $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 60, 4)); + $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); + $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 68, 4)); $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); - $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001); $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002); $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004); @@ -1144,9 +1169,10 @@ class getid3_quicktime extends getid3_handler $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; } else { - if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } - if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } - if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } + // see: http://www.getid3.org/phpBB3/viewtopic.php?t=1295 + //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } + //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } + //if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } } break; @@ -1197,10 +1223,76 @@ class getid3_quicktime extends getid3_handler break; case 'mdat': // Media DATa atom + // 'mdat' contains the actual data for the audio/video, possibly also subtitles + +/* due to lack of known documentation, this is a kludge implementation. If you know of documentation on how mdat is properly structed, please send it to info@getid3.org */ + + // first, skip any 'wide' padding, and second 'mdat' header (with specified size of zero?) + $mdat_offset = 0; + while (true) { + if (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x08".'wide') { + $mdat_offset += 8; + } elseif (substr($atom_data, $mdat_offset, 8) == "\x00\x00\x00\x00".'mdat') { + $mdat_offset += 8; + } else { + break; + } + } + + // check to see if it looks like chapter titles, in the form of unterminated strings with a leading 16-bit size field + while (($chapter_string_length = getid3_lib::BigEndian2Int(substr($atom_data, $mdat_offset, 2))) + && ($chapter_string_length < 1000) + && ($chapter_string_length <= (strlen($atom_data) - $mdat_offset - 2)) + && preg_match('#^[\x20-\xFF]+$#', substr($atom_data, $mdat_offset + 2, $chapter_string_length), $chapter_matches)) { + $mdat_offset += (2 + $chapter_string_length); + @$info['quicktime']['comments']['chapters'][] = $chapter_matches[0]; + } + + + + if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) { + + $info['avdataoffset'] = $atom_structure['offset'] + 8; // $info['quicktime'][$atomname]['offset'] + 8; + $OldAVDataEnd = $info['avdataend']; + $info['avdataend'] = $atom_structure['offset'] + $atom_structure['size']; // $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; + + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + $getid3_mp3 = new getid3_mp3($getid3_temp); + if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode($this->fread(4)))) { + $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $value) { + $info['warning'][] = $value; + } + } + if (!empty($getid3_temp->info['mpeg'])) { + $info['mpeg'] = $getid3_temp->info['mpeg']; + if (isset($info['mpeg']['audio'])) { + $info['audio']['dataformat'] = 'mp3'; + $info['audio']['codec'] = (!empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' :'mp3'))); + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + $info['bitrate'] = $info['audio']['bitrate']; + } + } + } + unset($getid3_mp3, $getid3_temp); + $info['avdataend'] = $OldAVDataEnd; + unset($OldAVDataEnd); + + } + + unset($mdat_offset, $chapter_string_length, $chapter_matches); + break; + case 'free': // FREE space atom case 'skip': // SKIP atom case 'wide': // 64-bit expansion placeholder atom - // 'mdat' data is too big to deal with, contains no useful metadata // 'free', 'skip' and 'wide' are just padding, contains no useful data at all // When writing QuickTime files, it is sometimes necessary to update an atom's size. @@ -1266,7 +1358,7 @@ class getid3_quicktime extends getid3_handler //$atom_structure['data'] = $atom_data; break; - case 'xyz': // GPS latitude+longitude+altitude + case "\xA9".'xyz': // GPS latitude+longitude+altitude $atom_structure['data'] = $atom_data; if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { @list($all, $latitude, $longitude, $altitude) = $matches; @@ -1276,7 +1368,7 @@ class getid3_quicktime extends getid3_handler $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); } } else { - $info['warning'][] = 'QuickTime atom "xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'; + $info['warning'][] = 'QuickTime atom "©xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'; } break; @@ -1295,23 +1387,19 @@ class getid3_quicktime extends getid3_handler $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']); } break; - case 'NCHD': // MakerNoteVersion - // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html - $atom_structure['data'] = $atom_data; - break; - case 'NCTG': // NikonTags - // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG + case 'NCTG': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); break; - case 'NCDB': // NikonTags - // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + case 'NCHD': // Nikon:MakerNoteVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + case 'NCDB': // Nikon - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + case 'CNCV': // Canon:CompressorVersion - http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html $atom_structure['data'] = $atom_data; break; case "\x00\x00\x00\x00": case 'meta': // METAdata atom // some kind of metacontainer, may contain a big data dump such as: - // mdta keys  mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst   data DEApple 0  (data DE2011-05-11T17:54:04+0200 2  *data DE+52.4936+013.3897+040.247/   data DE4.3.1  data DEiPhone 4 + // mdta keys \005 mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst \01D \001 \015data \001DE\010Apple 0 \002 (data \001DE\0102011-05-11T17:54:04+0200 2 \003 *data \001DE\010+52.4936+013.3897+040.247/ \01D \004 \015data \001DE\0104.3.1 \005 \018data \001DE\010iPhone 4 // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); @@ -1328,7 +1416,7 @@ class getid3_quicktime extends getid3_handler break; default: - $info['warning'][] = 'Unknown QuickTime atom type: "'.getid3_lib::PrintHexBytes($atomname).'" at offset '.$baseoffset; + $info['warning'][] = 'Unknown QuickTime atom type: "'.preg_replace('#[^a-zA-Z0-9 _\\-]#', '?', $atomname).'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset; $atom_structure['data'] = $atom_data; break; } @@ -1336,7 +1424,7 @@ class getid3_quicktime extends getid3_handler return $atom_structure; } - function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { + public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { //echo 'QuicktimeParseContainerAtom('.substr($atom_data, 4, 4).') @ '.$baseoffset.'

'; $atom_structure = false; $subatomoffset = 0; @@ -1364,7 +1452,7 @@ class getid3_quicktime extends getid3_handler } - function quicktime_read_mp4_descr_length($data, &$offset) { + public function quicktime_read_mp4_descr_length($data, &$offset) { // http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html $num_bytes = 0; $length = 0; @@ -1376,124 +1464,144 @@ class getid3_quicktime extends getid3_handler } - function QuicktimeLanguageLookup($languageid) { + public function QuicktimeLanguageLookup($languageid) { + // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-34353 static $QuicktimeLanguageLookup = array(); if (empty($QuicktimeLanguageLookup)) { - $QuicktimeLanguageLookup[0] = 'English'; - $QuicktimeLanguageLookup[1] = 'French'; - $QuicktimeLanguageLookup[2] = 'German'; - $QuicktimeLanguageLookup[3] = 'Italian'; - $QuicktimeLanguageLookup[4] = 'Dutch'; - $QuicktimeLanguageLookup[5] = 'Swedish'; - $QuicktimeLanguageLookup[6] = 'Spanish'; - $QuicktimeLanguageLookup[7] = 'Danish'; - $QuicktimeLanguageLookup[8] = 'Portuguese'; - $QuicktimeLanguageLookup[9] = 'Norwegian'; - $QuicktimeLanguageLookup[10] = 'Hebrew'; - $QuicktimeLanguageLookup[11] = 'Japanese'; - $QuicktimeLanguageLookup[12] = 'Arabic'; - $QuicktimeLanguageLookup[13] = 'Finnish'; - $QuicktimeLanguageLookup[14] = 'Greek'; - $QuicktimeLanguageLookup[15] = 'Icelandic'; - $QuicktimeLanguageLookup[16] = 'Maltese'; - $QuicktimeLanguageLookup[17] = 'Turkish'; - $QuicktimeLanguageLookup[18] = 'Croatian'; - $QuicktimeLanguageLookup[19] = 'Chinese (Traditional)'; - $QuicktimeLanguageLookup[20] = 'Urdu'; - $QuicktimeLanguageLookup[21] = 'Hindi'; - $QuicktimeLanguageLookup[22] = 'Thai'; - $QuicktimeLanguageLookup[23] = 'Korean'; - $QuicktimeLanguageLookup[24] = 'Lithuanian'; - $QuicktimeLanguageLookup[25] = 'Polish'; - $QuicktimeLanguageLookup[26] = 'Hungarian'; - $QuicktimeLanguageLookup[27] = 'Estonian'; - $QuicktimeLanguageLookup[28] = 'Lettish'; - $QuicktimeLanguageLookup[28] = 'Latvian'; - $QuicktimeLanguageLookup[29] = 'Saamisk'; - $QuicktimeLanguageLookup[29] = 'Lappish'; - $QuicktimeLanguageLookup[30] = 'Faeroese'; - $QuicktimeLanguageLookup[31] = 'Farsi'; - $QuicktimeLanguageLookup[31] = 'Persian'; - $QuicktimeLanguageLookup[32] = 'Russian'; - $QuicktimeLanguageLookup[33] = 'Chinese (Simplified)'; - $QuicktimeLanguageLookup[34] = 'Flemish'; - $QuicktimeLanguageLookup[35] = 'Irish'; - $QuicktimeLanguageLookup[36] = 'Albanian'; - $QuicktimeLanguageLookup[37] = 'Romanian'; - $QuicktimeLanguageLookup[38] = 'Czech'; - $QuicktimeLanguageLookup[39] = 'Slovak'; - $QuicktimeLanguageLookup[40] = 'Slovenian'; - $QuicktimeLanguageLookup[41] = 'Yiddish'; - $QuicktimeLanguageLookup[42] = 'Serbian'; - $QuicktimeLanguageLookup[43] = 'Macedonian'; - $QuicktimeLanguageLookup[44] = 'Bulgarian'; - $QuicktimeLanguageLookup[45] = 'Ukrainian'; - $QuicktimeLanguageLookup[46] = 'Byelorussian'; - $QuicktimeLanguageLookup[47] = 'Uzbek'; - $QuicktimeLanguageLookup[48] = 'Kazakh'; - $QuicktimeLanguageLookup[49] = 'Azerbaijani'; - $QuicktimeLanguageLookup[50] = 'AzerbaijanAr'; - $QuicktimeLanguageLookup[51] = 'Armenian'; - $QuicktimeLanguageLookup[52] = 'Georgian'; - $QuicktimeLanguageLookup[53] = 'Moldavian'; - $QuicktimeLanguageLookup[54] = 'Kirghiz'; - $QuicktimeLanguageLookup[55] = 'Tajiki'; - $QuicktimeLanguageLookup[56] = 'Turkmen'; - $QuicktimeLanguageLookup[57] = 'Mongolian'; - $QuicktimeLanguageLookup[58] = 'MongolianCyr'; - $QuicktimeLanguageLookup[59] = 'Pashto'; - $QuicktimeLanguageLookup[60] = 'Kurdish'; - $QuicktimeLanguageLookup[61] = 'Kashmiri'; - $QuicktimeLanguageLookup[62] = 'Sindhi'; - $QuicktimeLanguageLookup[63] = 'Tibetan'; - $QuicktimeLanguageLookup[64] = 'Nepali'; - $QuicktimeLanguageLookup[65] = 'Sanskrit'; - $QuicktimeLanguageLookup[66] = 'Marathi'; - $QuicktimeLanguageLookup[67] = 'Bengali'; - $QuicktimeLanguageLookup[68] = 'Assamese'; - $QuicktimeLanguageLookup[69] = 'Gujarati'; - $QuicktimeLanguageLookup[70] = 'Punjabi'; - $QuicktimeLanguageLookup[71] = 'Oriya'; - $QuicktimeLanguageLookup[72] = 'Malayalam'; - $QuicktimeLanguageLookup[73] = 'Kannada'; - $QuicktimeLanguageLookup[74] = 'Tamil'; - $QuicktimeLanguageLookup[75] = 'Telugu'; - $QuicktimeLanguageLookup[76] = 'Sinhalese'; - $QuicktimeLanguageLookup[77] = 'Burmese'; - $QuicktimeLanguageLookup[78] = 'Khmer'; - $QuicktimeLanguageLookup[79] = 'Lao'; - $QuicktimeLanguageLookup[80] = 'Vietnamese'; - $QuicktimeLanguageLookup[81] = 'Indonesian'; - $QuicktimeLanguageLookup[82] = 'Tagalog'; - $QuicktimeLanguageLookup[83] = 'MalayRoman'; - $QuicktimeLanguageLookup[84] = 'MalayArabic'; - $QuicktimeLanguageLookup[85] = 'Amharic'; - $QuicktimeLanguageLookup[86] = 'Tigrinya'; - $QuicktimeLanguageLookup[87] = 'Galla'; - $QuicktimeLanguageLookup[87] = 'Oromo'; - $QuicktimeLanguageLookup[88] = 'Somali'; - $QuicktimeLanguageLookup[89] = 'Swahili'; - $QuicktimeLanguageLookup[90] = 'Ruanda'; - $QuicktimeLanguageLookup[91] = 'Rundi'; - $QuicktimeLanguageLookup[92] = 'Chewa'; - $QuicktimeLanguageLookup[93] = 'Malagasy'; - $QuicktimeLanguageLookup[94] = 'Esperanto'; - $QuicktimeLanguageLookup[128] = 'Welsh'; - $QuicktimeLanguageLookup[129] = 'Basque'; - $QuicktimeLanguageLookup[130] = 'Catalan'; - $QuicktimeLanguageLookup[131] = 'Latin'; - $QuicktimeLanguageLookup[132] = 'Quechua'; - $QuicktimeLanguageLookup[133] = 'Guarani'; - $QuicktimeLanguageLookup[134] = 'Aymara'; - $QuicktimeLanguageLookup[135] = 'Tatar'; - $QuicktimeLanguageLookup[136] = 'Uighur'; - $QuicktimeLanguageLookup[137] = 'Dzongkha'; - $QuicktimeLanguageLookup[138] = 'JavaneseRom'; + $QuicktimeLanguageLookup[0] = 'English'; + $QuicktimeLanguageLookup[1] = 'French'; + $QuicktimeLanguageLookup[2] = 'German'; + $QuicktimeLanguageLookup[3] = 'Italian'; + $QuicktimeLanguageLookup[4] = 'Dutch'; + $QuicktimeLanguageLookup[5] = 'Swedish'; + $QuicktimeLanguageLookup[6] = 'Spanish'; + $QuicktimeLanguageLookup[7] = 'Danish'; + $QuicktimeLanguageLookup[8] = 'Portuguese'; + $QuicktimeLanguageLookup[9] = 'Norwegian'; + $QuicktimeLanguageLookup[10] = 'Hebrew'; + $QuicktimeLanguageLookup[11] = 'Japanese'; + $QuicktimeLanguageLookup[12] = 'Arabic'; + $QuicktimeLanguageLookup[13] = 'Finnish'; + $QuicktimeLanguageLookup[14] = 'Greek'; + $QuicktimeLanguageLookup[15] = 'Icelandic'; + $QuicktimeLanguageLookup[16] = 'Maltese'; + $QuicktimeLanguageLookup[17] = 'Turkish'; + $QuicktimeLanguageLookup[18] = 'Croatian'; + $QuicktimeLanguageLookup[19] = 'Chinese (Traditional)'; + $QuicktimeLanguageLookup[20] = 'Urdu'; + $QuicktimeLanguageLookup[21] = 'Hindi'; + $QuicktimeLanguageLookup[22] = 'Thai'; + $QuicktimeLanguageLookup[23] = 'Korean'; + $QuicktimeLanguageLookup[24] = 'Lithuanian'; + $QuicktimeLanguageLookup[25] = 'Polish'; + $QuicktimeLanguageLookup[26] = 'Hungarian'; + $QuicktimeLanguageLookup[27] = 'Estonian'; + $QuicktimeLanguageLookup[28] = 'Lettish'; + $QuicktimeLanguageLookup[28] = 'Latvian'; + $QuicktimeLanguageLookup[29] = 'Saamisk'; + $QuicktimeLanguageLookup[29] = 'Lappish'; + $QuicktimeLanguageLookup[30] = 'Faeroese'; + $QuicktimeLanguageLookup[31] = 'Farsi'; + $QuicktimeLanguageLookup[31] = 'Persian'; + $QuicktimeLanguageLookup[32] = 'Russian'; + $QuicktimeLanguageLookup[33] = 'Chinese (Simplified)'; + $QuicktimeLanguageLookup[34] = 'Flemish'; + $QuicktimeLanguageLookup[35] = 'Irish'; + $QuicktimeLanguageLookup[36] = 'Albanian'; + $QuicktimeLanguageLookup[37] = 'Romanian'; + $QuicktimeLanguageLookup[38] = 'Czech'; + $QuicktimeLanguageLookup[39] = 'Slovak'; + $QuicktimeLanguageLookup[40] = 'Slovenian'; + $QuicktimeLanguageLookup[41] = 'Yiddish'; + $QuicktimeLanguageLookup[42] = 'Serbian'; + $QuicktimeLanguageLookup[43] = 'Macedonian'; + $QuicktimeLanguageLookup[44] = 'Bulgarian'; + $QuicktimeLanguageLookup[45] = 'Ukrainian'; + $QuicktimeLanguageLookup[46] = 'Byelorussian'; + $QuicktimeLanguageLookup[47] = 'Uzbek'; + $QuicktimeLanguageLookup[48] = 'Kazakh'; + $QuicktimeLanguageLookup[49] = 'Azerbaijani'; + $QuicktimeLanguageLookup[50] = 'AzerbaijanAr'; + $QuicktimeLanguageLookup[51] = 'Armenian'; + $QuicktimeLanguageLookup[52] = 'Georgian'; + $QuicktimeLanguageLookup[53] = 'Moldavian'; + $QuicktimeLanguageLookup[54] = 'Kirghiz'; + $QuicktimeLanguageLookup[55] = 'Tajiki'; + $QuicktimeLanguageLookup[56] = 'Turkmen'; + $QuicktimeLanguageLookup[57] = 'Mongolian'; + $QuicktimeLanguageLookup[58] = 'MongolianCyr'; + $QuicktimeLanguageLookup[59] = 'Pashto'; + $QuicktimeLanguageLookup[60] = 'Kurdish'; + $QuicktimeLanguageLookup[61] = 'Kashmiri'; + $QuicktimeLanguageLookup[62] = 'Sindhi'; + $QuicktimeLanguageLookup[63] = 'Tibetan'; + $QuicktimeLanguageLookup[64] = 'Nepali'; + $QuicktimeLanguageLookup[65] = 'Sanskrit'; + $QuicktimeLanguageLookup[66] = 'Marathi'; + $QuicktimeLanguageLookup[67] = 'Bengali'; + $QuicktimeLanguageLookup[68] = 'Assamese'; + $QuicktimeLanguageLookup[69] = 'Gujarati'; + $QuicktimeLanguageLookup[70] = 'Punjabi'; + $QuicktimeLanguageLookup[71] = 'Oriya'; + $QuicktimeLanguageLookup[72] = 'Malayalam'; + $QuicktimeLanguageLookup[73] = 'Kannada'; + $QuicktimeLanguageLookup[74] = 'Tamil'; + $QuicktimeLanguageLookup[75] = 'Telugu'; + $QuicktimeLanguageLookup[76] = 'Sinhalese'; + $QuicktimeLanguageLookup[77] = 'Burmese'; + $QuicktimeLanguageLookup[78] = 'Khmer'; + $QuicktimeLanguageLookup[79] = 'Lao'; + $QuicktimeLanguageLookup[80] = 'Vietnamese'; + $QuicktimeLanguageLookup[81] = 'Indonesian'; + $QuicktimeLanguageLookup[82] = 'Tagalog'; + $QuicktimeLanguageLookup[83] = 'MalayRoman'; + $QuicktimeLanguageLookup[84] = 'MalayArabic'; + $QuicktimeLanguageLookup[85] = 'Amharic'; + $QuicktimeLanguageLookup[86] = 'Tigrinya'; + $QuicktimeLanguageLookup[87] = 'Galla'; + $QuicktimeLanguageLookup[87] = 'Oromo'; + $QuicktimeLanguageLookup[88] = 'Somali'; + $QuicktimeLanguageLookup[89] = 'Swahili'; + $QuicktimeLanguageLookup[90] = 'Ruanda'; + $QuicktimeLanguageLookup[91] = 'Rundi'; + $QuicktimeLanguageLookup[92] = 'Chewa'; + $QuicktimeLanguageLookup[93] = 'Malagasy'; + $QuicktimeLanguageLookup[94] = 'Esperanto'; + $QuicktimeLanguageLookup[128] = 'Welsh'; + $QuicktimeLanguageLookup[129] = 'Basque'; + $QuicktimeLanguageLookup[130] = 'Catalan'; + $QuicktimeLanguageLookup[131] = 'Latin'; + $QuicktimeLanguageLookup[132] = 'Quechua'; + $QuicktimeLanguageLookup[133] = 'Guarani'; + $QuicktimeLanguageLookup[134] = 'Aymara'; + $QuicktimeLanguageLookup[135] = 'Tatar'; + $QuicktimeLanguageLookup[136] = 'Uighur'; + $QuicktimeLanguageLookup[137] = 'Dzongkha'; + $QuicktimeLanguageLookup[138] = 'JavaneseRom'; + $QuicktimeLanguageLookup[32767] = 'Unspecified'; + } + if (($languageid > 138) && ($languageid < 32767)) { + /* + ISO Language Codes - http://www.loc.gov/standards/iso639-2/php/code_list.php + Because the language codes specified by ISO 639-2/T are three characters long, they must be packed to fit into a 16-bit field. + The packing algorithm must map each of the three characters, which are always lowercase, into a 5-bit integer and then concatenate + these integers into the least significant 15 bits of a 16-bit integer, leaving the 16-bit integer's most significant bit set to zero. + + One algorithm for performing this packing is to treat each ISO character as a 16-bit integer. Subtract 0x60 from the first character + and multiply by 2^10 (0x400), subtract 0x60 from the second character and multiply by 2^5 (0x20), subtract 0x60 from the third character, + and add the three 16-bit values. This will result in a single 16-bit value with the three codes correctly packed into the 15 least + significant bits and the most significant bit set to zero. + */ + $iso_language_id = ''; + $iso_language_id .= chr((($languageid & 0x7C00) >> 10) + 0x60); + $iso_language_id .= chr((($languageid & 0x03E0) >> 5) + 0x60); + $iso_language_id .= chr((($languageid & 0x001F) >> 0) + 0x60); + $QuicktimeLanguageLookup[$languageid] = getid3_id3v2::LanguageLookup($iso_language_id); } return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid'); } - function QuicktimeVideoCodecLookup($codecid) { + public function QuicktimeVideoCodecLookup($codecid) { static $QuicktimeVideoCodecLookup = array(); if (empty($QuicktimeVideoCodecLookup)) { $QuicktimeVideoCodecLookup['.SGI'] = 'SGI'; @@ -1552,7 +1660,7 @@ class getid3_quicktime extends getid3_handler return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : ''); } - function QuicktimeAudioCodecLookup($codecid) { + public function QuicktimeAudioCodecLookup($codecid) { static $QuicktimeAudioCodecLookup = array(); if (empty($QuicktimeAudioCodecLookup)) { $QuicktimeAudioCodecLookup['.mp3'] = 'Fraunhofer MPEG Layer-III alias'; @@ -1597,7 +1705,7 @@ class getid3_quicktime extends getid3_handler return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : ''); } - function QuicktimeDCOMLookup($compressionid) { + public function QuicktimeDCOMLookup($compressionid) { static $QuicktimeDCOMLookup = array(); if (empty($QuicktimeDCOMLookup)) { $QuicktimeDCOMLookup['zlib'] = 'ZLib Deflate'; @@ -1606,7 +1714,7 @@ class getid3_quicktime extends getid3_handler return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : ''); } - function QuicktimeColorNameLookup($colordepthid) { + public function QuicktimeColorNameLookup($colordepthid) { static $QuicktimeColorNameLookup = array(); if (empty($QuicktimeColorNameLookup)) { $QuicktimeColorNameLookup[1] = '2-color (monochrome)'; @@ -1624,7 +1732,7 @@ class getid3_quicktime extends getid3_handler return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid'); } - function QuicktimeSTIKLookup($stik) { + public function QuicktimeSTIKLookup($stik) { static $QuicktimeSTIKLookup = array(); if (empty($QuicktimeSTIKLookup)) { $QuicktimeSTIKLookup[0] = 'Movie'; @@ -1641,7 +1749,7 @@ class getid3_quicktime extends getid3_handler return (isset($QuicktimeSTIKLookup[$stik]) ? $QuicktimeSTIKLookup[$stik] : 'invalid'); } - function QuicktimeIODSaudioProfileName($audio_profile_id) { + public function QuicktimeIODSaudioProfileName($audio_profile_id) { static $QuicktimeIODSaudioProfileNameLookup = array(); if (empty($QuicktimeIODSaudioProfileNameLookup)) { $QuicktimeIODSaudioProfileNameLookup = array( @@ -1701,7 +1809,7 @@ class getid3_quicktime extends getid3_handler } - function QuicktimeIODSvideoProfileName($video_profile_id) { + public function QuicktimeIODSvideoProfileName($video_profile_id) { static $QuicktimeIODSvideoProfileNameLookup = array(); if (empty($QuicktimeIODSvideoProfileNameLookup)) { $QuicktimeIODSvideoProfileNameLookup = array( @@ -1773,7 +1881,7 @@ class getid3_quicktime extends getid3_handler } - function QuicktimeContentRatingLookup($rtng) { + public function QuicktimeContentRatingLookup($rtng) { static $QuicktimeContentRatingLookup = array(); if (empty($QuicktimeContentRatingLookup)) { $QuicktimeContentRatingLookup[0] = 'None'; @@ -1783,7 +1891,7 @@ class getid3_quicktime extends getid3_handler return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid'); } - function QuicktimeStoreAccountTypeLookup($akid) { + public function QuicktimeStoreAccountTypeLookup($akid) { static $QuicktimeStoreAccountTypeLookup = array(); if (empty($QuicktimeStoreAccountTypeLookup)) { $QuicktimeStoreAccountTypeLookup[0] = 'iTunes'; @@ -1792,7 +1900,7 @@ class getid3_quicktime extends getid3_handler return (isset($QuicktimeStoreAccountTypeLookup[$akid]) ? $QuicktimeStoreAccountTypeLookup[$akid] : 'invalid'); } - function QuicktimeStoreFrontCodeLookup($sfid) { + public function QuicktimeStoreFrontCodeLookup($sfid) { static $QuicktimeStoreFrontCodeLookup = array(); if (empty($QuicktimeStoreFrontCodeLookup)) { $QuicktimeStoreFrontCodeLookup[143460] = 'Australia'; @@ -1821,7 +1929,7 @@ class getid3_quicktime extends getid3_handler return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid'); } - function QuicktimeParseNikonNCTG($atom_data) { + public function QuicktimeParseNikonNCTG($atom_data) { // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 // Data is stored as records of: @@ -2000,61 +2108,61 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
getid3->info; $comment_key = ''; - if ($boxname && ($boxname != $keyname) && isset($handyatomtranslatorarray[$boxname])) { - $comment_key = $handyatomtranslatorarray[$boxname]; + if ($boxname && ($boxname != $keyname)) { + $comment_key = (isset($handyatomtranslatorarray[$boxname]) ? $handyatomtranslatorarray[$boxname] : $boxname); } elseif (isset($handyatomtranslatorarray[$keyname])) { $comment_key = $handyatomtranslatorarray[$keyname]; } @@ -2116,7 +2230,7 @@ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
\ No newline at end of file diff --git a/app/library/getid3/module.audio-video.real.php b/app/library/getid3/getid3/module.audio-video.real.php old mode 100644 new mode 100755 similarity index 97% rename from app/library/getid3/module.audio-video.real.php rename to app/library/getid3/getid3/module.audio-video.real.php index d716e7da..1cc3731b --- a/app/library/getid3/module.audio-video.real.php +++ b/app/library/getid3/getid3/module.audio-video.real.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -18,22 +19,22 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', class getid3_real extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'real'; $info['bitrate'] = 0; $info['playtime_seconds'] = 0; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); $ChunkCounter = 0; - while (ftell($this->getid3->fp) < $info['avdataend']) { - $ChunkData = fread($this->getid3->fp, 8); + while ($this->ftell() < $info['avdataend']) { + $ChunkData = $this->fread(8); $ChunkName = substr($ChunkData, 0, 4); $ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, 4, 4)); if ($ChunkName == '.ra'."\xFD") { - $ChunkData .= fread($this->getid3->fp, $ChunkSize - 8); + $ChunkData .= $this->fread($ChunkSize - 8); if ($this->ParseOldRAheader(substr($ChunkData, 0, 128), $info['real']['old_ra_header'])) { $info['audio']['dataformat'] = 'real'; $info['audio']['lossless'] = false; @@ -63,7 +64,7 @@ class getid3_real extends getid3_handler $thisfile_real_chunks_currentchunk = &$info['real']['chunks'][$ChunkCounter]; $thisfile_real_chunks_currentchunk['name'] = $ChunkName; - $thisfile_real_chunks_currentchunk['offset'] = ftell($this->getid3->fp) - 8; + $thisfile_real_chunks_currentchunk['offset'] = $this->ftell() - 8; $thisfile_real_chunks_currentchunk['length'] = $ChunkSize; if (($thisfile_real_chunks_currentchunk['offset'] + $thisfile_real_chunks_currentchunk['length']) > $info['avdataend']) { $info['warning'][] = 'Chunk "'.$thisfile_real_chunks_currentchunk['name'].'" at offset '.$thisfile_real_chunks_currentchunk['offset'].' claims to be '.$thisfile_real_chunks_currentchunk['length'].' bytes long, which is beyond end of file'; @@ -72,12 +73,12 @@ class getid3_real extends getid3_handler if ($ChunkSize > ($this->getid3->fread_buffer_size() + 8)) { - $ChunkData .= fread($this->getid3->fp, $this->getid3->fread_buffer_size() - 8); - fseek($this->getid3->fp, $thisfile_real_chunks_currentchunk['offset'] + $ChunkSize, SEEK_SET); + $ChunkData .= $this->fread($this->getid3->fread_buffer_size() - 8); + $this->fseek($thisfile_real_chunks_currentchunk['offset'] + $ChunkSize); } elseif(($ChunkSize - 8) > 0) { - $ChunkData .= fread($this->getid3->fp, $ChunkSize - 8); + $ChunkData .= $this->fread($ChunkSize - 8); } $offset = 8; @@ -202,7 +203,7 @@ class getid3_real extends getid3_handler //$thisfile_real_chunks_currentchunk_videoinfo['unknown8'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 34, 2)); //$thisfile_real_chunks_currentchunk_videoinfo['unknown9'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 36, 2)); - $thisfile_real_chunks_currentchunk_videoinfo['codec'] = getid3_riff::RIFFfourccLookup($thisfile_real_chunks_currentchunk_videoinfo['fourcc2']); + $thisfile_real_chunks_currentchunk_videoinfo['codec'] = getid3_riff::fourccLookup($thisfile_real_chunks_currentchunk_videoinfo['fourcc2']); $info['video']['resolution_x'] = $thisfile_real_chunks_currentchunk_videoinfo['width']; $info['video']['resolution_y'] = $thisfile_real_chunks_currentchunk_videoinfo['height']; @@ -347,7 +348,7 @@ class getid3_real extends getid3_handler break 2; } else { // non-last index chunk, seek to next index chunk (skipping actual index data) - fseek($this->getid3->fp, $thisfile_real_chunks_currentchunk['next_index_header'], SEEK_SET); + $this->fseek($thisfile_real_chunks_currentchunk['next_index_header']); } } break; @@ -370,7 +371,7 @@ class getid3_real extends getid3_handler } - function ParseOldRAheader($OldRAheaderData, &$ParsedArray) { + public function ParseOldRAheader($OldRAheaderData, &$ParsedArray) { // http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html $ParsedArray = array(); @@ -484,7 +485,7 @@ class getid3_real extends getid3_handler return true; } - function RealAudioCodecFourCClookup($fourcc, $bitrate) { + public function RealAudioCodecFourCClookup($fourcc, $bitrate) { static $RealAudioCodecFourCClookup = array(); if (empty($RealAudioCodecFourCClookup)) { // http://www.its.msstate.edu/net/real/reports/config/tags.stats @@ -525,6 +526,3 @@ class getid3_real extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio-video.riff.php b/app/library/getid3/getid3/module.audio-video.riff.php old mode 100644 new mode 100755 similarity index 71% rename from app/library/getid3/module.audio-video.riff.php rename to app/library/getid3/getid3/module.audio-video.riff.php index 8e8f53a4..e8ba9445 --- a/app/library/getid3/module.audio-video.riff.php +++ b/app/library/getid3/getid3/module.audio-video.riff.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -12,17 +13,25 @@ // multiple formats supported by this module: // // Wave, AVI, AIFF/AIFC, (MP3,AC3)/RIFF, Wavpack v3, 8SVX // // dependencies: module.audio.mp3.php // -// module.audio.ac3.php (optional) // -// module.audio.dts.php (optional) // +// module.audio.ac3.php // +// module.audio.dts.php // // /// ///////////////////////////////////////////////////////////////// +/** +* @todo Parse AC-3/DTS audio inside WAVE correctly +* @todo Rewrite RIFF parser totally +*/ + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true); +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, true); -class getid3_riff extends getid3_handler -{ +class getid3_riff extends getid3_handler { - function Analyze() { + protected $container = 'riff'; // default + + public function Analyze() { $info = &$this->getid3->info; // initialize these values to an empty array, otherwise they default to NULL @@ -38,30 +47,40 @@ class getid3_riff extends getid3_handler $thisfile_riff_audio = &$thisfile_riff['audio']; $thisfile_riff_video = &$thisfile_riff['video']; - $Original['avdataoffset'] = $info['avdataoffset']; $Original['avdataend'] = $info['avdataend']; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $RIFFheader = fread($this->getid3->fp, 12); + $this->fseek($info['avdataoffset']); + $RIFFheader = $this->fread(12); + $offset = $this->ftell(); + $RIFFtype = substr($RIFFheader, 0, 4); + $RIFFsize = substr($RIFFheader, 4, 4); $RIFFsubtype = substr($RIFFheader, 8, 4); - switch (substr($RIFFheader, 0, 4)) { - case 'FORM': - $info['fileformat'] = 'aiff'; - $thisfile_riff['header_size'] = $this->EitherEndian2Int(substr($RIFFheader, 4, 4)); - $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($info['avdataoffset'] + 12, $info['avdataoffset'] + $thisfile_riff['header_size']); + + switch ($RIFFtype) { + + case 'FORM': // AIFF, AIFC + //$info['fileformat'] = 'aiff'; + $this->container = 'aiff'; + $thisfile_riff['header_size'] = $this->EitherEndian2Int($RIFFsize); + $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4)); break; case 'RIFF': // AVI, WAV, etc case 'SDSS': // SDSS is identical to RIFF, just renamed. Used by SmartSound QuickTracks (www.smartsound.com) case 'RMP3': // RMP3 is identical to RIFF, just renamed. Used by [unknown program] when creating RIFF-MP3s - $info['fileformat'] = 'riff'; - $thisfile_riff['header_size'] = $this->EitherEndian2Int(substr($RIFFheader, 4, 4)); + //$info['fileformat'] = 'riff'; + $this->container = 'riff'; + $thisfile_riff['header_size'] = $this->EitherEndian2Int($RIFFsize); if ($RIFFsubtype == 'RMP3') { // RMP3 is identical to WAVE, just renamed. Used by [unknown program] when creating RIFF-MP3s $RIFFsubtype = 'WAVE'; } - $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($info['avdataoffset'] + 12, $info['avdataoffset'] + $thisfile_riff['header_size']); + if ($RIFFsubtype != 'AMV ') { + // AMV files are RIFF-AVI files with parts of the spec deliberately broken, such as chunk size fields hardcoded to zero (because players known in hardware that these fields are always a certain size + // Handled separately in ParseRIFFAMV() + $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4)); + } if (($info['avdataend'] - $info['filesize']) == 1) { // LiteWave appears to incorrectly *not* pad actual output file // to nearest WORD boundary so may appear to be short by one @@ -71,63 +90,71 @@ class getid3_riff extends getid3_handler $nextRIFFoffset = $Original['avdataoffset'] + 8 + $thisfile_riff['header_size']; // 8 = "RIFF" + 32-bit offset while ($nextRIFFoffset < min($info['filesize'], $info['avdataend'])) { - if (!getid3_lib::intValueSupported($nextRIFFoffset + 1024)) { - $info['error'][] = 'AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; - $info['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; - break; - } else { - fseek($this->getid3->fp, $nextRIFFoffset, SEEK_SET); - $nextRIFFheader = fread($this->getid3->fp, 12); - if ($nextRIFFoffset == ($info['avdataend'] - 1)) { - if (substr($nextRIFFheader, 0, 1) == "\x00") { - // RIFF padded to WORD boundary, we're actually already at the end - break; - } - } - $nextRIFFheaderID = substr($nextRIFFheader, 0, 4); - $nextRIFFsize = $this->EitherEndian2Int(substr($nextRIFFheader, 4, 4)); - $nextRIFFtype = substr($nextRIFFheader, 8, 4); - $chunkdata = array(); - $chunkdata['offset'] = $nextRIFFoffset + 8; - $chunkdata['size'] = $nextRIFFsize; - $nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size']; - switch ($nextRIFFheaderID) { - case 'RIFF': - $info['avdataend'] = $nextRIFFoffset; - if (!getid3_lib::intValueSupported($info['avdataend'])) { - $info['error'][] = 'AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime is probably wrong'; - $info['warning'][] = '[avdataend] value may be incorrect, multiple AVIX chunks may be present'; - } - $chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $chunkdata['offset'] + $chunkdata['size']); - - if (!isset($thisfile_riff[$nextRIFFtype])) { - $thisfile_riff[$nextRIFFtype] = array(); - } - $thisfile_riff[$nextRIFFtype][] = $chunkdata; - break; - case 'JUNK': - // ignore - $thisfile_riff[$nextRIFFheaderID][] = $chunkdata; - break; - default: - if ($info['filesize'] == ($chunkdata['offset'] - 8 + 128)) { - $DIVXTAG = $nextRIFFheader.fread($this->getid3->fp, 128 - 12); - if (substr($DIVXTAG, -7) == 'DIVXTAG') { - // DIVXTAG is supposed to be inside an IDVX chunk in a LIST chunk, but some bad encoders just slap it on the end of a file - $info['warning'][] = 'Found wrongly-structured DIVXTAG at offset '.(ftell($this->getid3->fp) - 128 + 12).', parsing anyway'; - $thisfile_riff['DIVXTAG'] = $this->ParseDIVXTAG($DIVXTAG); - foreach ($thisfile_riff['DIVXTAG'] as $key => $value) { - if ($value && !preg_match('#_id$#', $key)) { - $thisfile_riff['comments'][$key][] = $value; - } - } - break 2; - } - } - $info['warning'][] = 'expecting "RIFF" or "JUNK" at '.$nextRIFFoffset.', found '.getid3_lib::PrintHexBytes(substr($nextRIFFheader, 0, 4)).' - skipping rest of file'; - break 2; + try { + $this->fseek($nextRIFFoffset); + } catch (getid3_exception $e) { + if ($e->getCode() == 10) { + //$this->warning('RIFF parser: '.$e->getMessage()); + $this->error('AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime may be wrong'); + $this->warning('[avdataend] value may be incorrect, multiple AVIX chunks may be present'); + break; + } else { + throw $e; } } + $nextRIFFheader = $this->fread(12); + if ($nextRIFFoffset == ($info['avdataend'] - 1)) { + if (substr($nextRIFFheader, 0, 1) == "\x00") { + // RIFF padded to WORD boundary, we're actually already at the end + break; + } + } + $nextRIFFheaderID = substr($nextRIFFheader, 0, 4); + $nextRIFFsize = $this->EitherEndian2Int(substr($nextRIFFheader, 4, 4)); + $nextRIFFtype = substr($nextRIFFheader, 8, 4); + $chunkdata = array(); + $chunkdata['offset'] = $nextRIFFoffset + 8; + $chunkdata['size'] = $nextRIFFsize; + $nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size']; + + switch ($nextRIFFheaderID) { + case 'RIFF': + $chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $nextRIFFoffset); + if (!isset($thisfile_riff[$nextRIFFtype])) { + $thisfile_riff[$nextRIFFtype] = array(); + } + $thisfile_riff[$nextRIFFtype][] = $chunkdata; + break; + + case 'AMV ': + unset($info['riff']); + $info['amv'] = $this->ParseRIFFAMV($chunkdata['offset'] + 4, $nextRIFFoffset); + break; + + case 'JUNK': + // ignore + $thisfile_riff[$nextRIFFheaderID][] = $chunkdata; + break; + + case 'IDVX': + $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunkdata['size'])); + break; + + default: + if ($info['filesize'] == ($chunkdata['offset'] - 8 + 128)) { + $DIVXTAG = $nextRIFFheader.$this->fread(128 - 12); + if (substr($DIVXTAG, -7) == 'DIVXTAG') { + // DIVXTAG is supposed to be inside an IDVX chunk in a LIST chunk, but some bad encoders just slap it on the end of a file + $this->warning('Found wrongly-structured DIVXTAG at offset '.($this->ftell() - 128).', parsing anyway'); + $info['divxtag']['comments'] = self::ParseDIVXTAG($DIVXTAG); + break 2; + } + } + $this->warning('Expecting "RIFF|JUNK|IDVX" at '.$nextRIFFoffset.', found "'.$nextRIFFheaderID.'" ('.getid3_lib::PrintHexBytes($nextRIFFheaderID).') - skipping rest of file'); + break 2; + + } + } if ($RIFFsubtype == 'WAVE') { $thisfile_riff_WAVE = &$thisfile_riff['WAVE']; @@ -135,15 +162,18 @@ class getid3_riff extends getid3_handler break; default: - $info['error'][] = 'Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead'; - unset($info['fileformat']); + $this->error('Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead'); + //unset($info['fileformat']); return false; - break; } $streamindex = 0; switch ($RIFFsubtype) { + + // http://en.wikipedia.org/wiki/Wav case 'WAVE': + $info['fileformat'] = 'wav'; + if (empty($thisfile_audio['bitrate_mode'])) { $thisfile_audio['bitrate_mode'] = 'cbr'; } @@ -157,7 +187,7 @@ class getid3_riff extends getid3_handler } if (isset($thisfile_riff_WAVE['fmt '][0]['data'])) { - $thisfile_riff_audio[$streamindex] = getid3_riff::RIFFparseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']); + $thisfile_riff_audio[$streamindex] = self::parseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']); $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; if (!isset($thisfile_riff_audio[$streamindex]['bitrate']) || ($thisfile_riff_audio[$streamindex]['bitrate'] == 0)) { $info['error'][] = 'Corrupt RIFF file: bitrate_audio == zero'; @@ -173,7 +203,9 @@ class getid3_riff extends getid3_handler } $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; - $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); + if (empty($info['playtime_seconds'])) { // may already be set (e.g. DTS-WAV) + $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); + } $thisfile_audio['lossless'] = false; if (isset($thisfile_riff_WAVE['data'][0]['offset']) && isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { @@ -303,22 +335,22 @@ class getid3_riff extends getid3_handler // shortcut $thisfile_riff_WAVE_cart_0 = &$thisfile_riff_WAVE['cart'][0]; - $thisfile_riff_WAVE_cart_0['version'] = substr($thisfile_riff_WAVE_cart_0['data'], 0, 4); - $thisfile_riff_WAVE_cart_0['title'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 4, 64)); - $thisfile_riff_WAVE_cart_0['artist'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 68, 64)); - $thisfile_riff_WAVE_cart_0['cut_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 132, 64)); - $thisfile_riff_WAVE_cart_0['client_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 196, 64)); - $thisfile_riff_WAVE_cart_0['category'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 260, 64)); - $thisfile_riff_WAVE_cart_0['classification'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 324, 64)); - $thisfile_riff_WAVE_cart_0['out_cue'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 388, 64)); - $thisfile_riff_WAVE_cart_0['start_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 452, 10)); - $thisfile_riff_WAVE_cart_0['start_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 462, 8)); - $thisfile_riff_WAVE_cart_0['end_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 470, 10)); - $thisfile_riff_WAVE_cart_0['end_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 480, 8)); - $thisfile_riff_WAVE_cart_0['producer_app_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 488, 64)); - $thisfile_riff_WAVE_cart_0['producer_app_version'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 552, 64)); - $thisfile_riff_WAVE_cart_0['user_defined_text'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 616, 64)); - $thisfile_riff_WAVE_cart_0['zero_db_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 680, 4), true); + $thisfile_riff_WAVE_cart_0['version'] = substr($thisfile_riff_WAVE_cart_0['data'], 0, 4); + $thisfile_riff_WAVE_cart_0['title'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 4, 64)); + $thisfile_riff_WAVE_cart_0['artist'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 68, 64)); + $thisfile_riff_WAVE_cart_0['cut_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 132, 64)); + $thisfile_riff_WAVE_cart_0['client_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 196, 64)); + $thisfile_riff_WAVE_cart_0['category'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 260, 64)); + $thisfile_riff_WAVE_cart_0['classification'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 324, 64)); + $thisfile_riff_WAVE_cart_0['out_cue'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 388, 64)); + $thisfile_riff_WAVE_cart_0['start_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 452, 10)); + $thisfile_riff_WAVE_cart_0['start_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 462, 8)); + $thisfile_riff_WAVE_cart_0['end_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 470, 10)); + $thisfile_riff_WAVE_cart_0['end_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 480, 8)); + $thisfile_riff_WAVE_cart_0['producer_app_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 488, 64)); + $thisfile_riff_WAVE_cart_0['producer_app_version'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 552, 64)); + $thisfile_riff_WAVE_cart_0['user_defined_text'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 616, 64)); + $thisfile_riff_WAVE_cart_0['zero_db_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 680, 4), true); for ($i = 0; $i < 8; $i++) { $thisfile_riff_WAVE_cart_0['post_time'][$i]['usage_fourcc'] = substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8), 4); $thisfile_riff_WAVE_cart_0['post_time'][$i]['timer_value'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8) + 4, 4)); @@ -362,7 +394,7 @@ class getid3_riff extends getid3_handler $SNDM_startoffset += $SNDM_thisTagSize; $thisfile_riff_WAVE_SNDM_0['parsed_raw'][$SNDM_thisTagKey] = $SNDM_thisTagDataText; - if ($parsedkey = $this->RIFFwaveSNDMtagLookup($SNDM_thisTagKey)) { + if ($parsedkey = self::waveSNDMtagLookup($SNDM_thisTagKey)) { $thisfile_riff_WAVE_SNDM_0['parsed'][$parsedkey] = $SNDM_thisTagDataText; } else { $info['warning'][] = 'RIFF.WAVE.SNDM contains unknown tag "'.$SNDM_thisTagKey.'" at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; @@ -424,15 +456,15 @@ class getid3_riff extends getid3_handler $info['avdataend'] = $Original['avdataend']; $thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; - fseek($this->getid3->fp, $info['avdataoffset'] - 44, SEEK_SET); - $RIFFdata = fread($this->getid3->fp, 44); + $this->fseek($info['avdataoffset'] - 44); + $RIFFdata = $this->fread(44); $OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8; $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { $info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); - fseek($this->getid3->fp, $info['avdataend'], SEEK_SET); - $RIFFdata .= fread($this->getid3->fp, $OrignalRIFFheaderSize - $OrignalRIFFdataSize); + $this->fseek($info['avdataend']); + $RIFFdata .= $this->fread($OrignalRIFFheaderSize - $OrignalRIFFdataSize); } // move the data chunk after all other chunks (if any) @@ -450,11 +482,19 @@ class getid3_riff extends getid3_handler if (!empty($info['ac3'])) { // Dolby Digital WAV files masquerade as PCM-WAV, but they're not $thisfile_audio['wformattag'] = 0x2000; - $thisfile_audio['codec'] = $this->RIFFwFormatTagLookup($thisfile_audio['wformattag']); + $thisfile_audio['codec'] = self::wFormatTagLookup($thisfile_audio['wformattag']); $thisfile_audio['lossless'] = false; $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; $thisfile_audio['sample_rate'] = $info['ac3']['sample_rate']; } + if (!empty($info['dts'])) { + // Dolby DTS files masquerade as PCM-WAV, but they're not + $thisfile_audio['wformattag'] = 0x2001; + $thisfile_audio['codec'] = self::wFormatTagLookup($thisfile_audio['wformattag']); + $thisfile_audio['lossless'] = false; + $thisfile_audio['bitrate'] = $info['dts']['bitrate']; + $thisfile_audio['sample_rate'] = $info['dts']['sample_rate']; + } break; case 0x08AE: // ClearJump LiteWave $thisfile_audio['bitrate_mode'] = 'vbr'; @@ -478,29 +518,36 @@ class getid3_riff extends getid3_handler // shortcut $thisfile_riff['litewave']['raw'] = array(); - $thisfile_riff_litewave = &$thisfile_riff['litewave']; - $thisfile_riff_litewave_raw = &$thisfile_riff_litewave['raw']; + $riff_litewave = &$thisfile_riff['litewave']; + $riff_litewave_raw = &$riff_litewave['raw']; - $thisfile_riff_litewave_raw['compression_method'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 18, 1)); - $thisfile_riff_litewave_raw['compression_flags'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 19, 1)); - $thisfile_riff_litewave_raw['m_dwScale'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 20, 4)); - $thisfile_riff_litewave_raw['m_dwBlockSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 24, 4)); - $thisfile_riff_litewave_raw['m_wQuality'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 28, 2)); - $thisfile_riff_litewave_raw['m_wMarkDistance'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 30, 2)); - $thisfile_riff_litewave_raw['m_wReserved'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 32, 2)); - $thisfile_riff_litewave_raw['m_dwOrgSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 34, 4)); - $thisfile_riff_litewave_raw['m_bFactExists'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 38, 2)); - $thisfile_riff_litewave_raw['m_dwRiffChunkSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 40, 4)); + $flags = array( + 'compression_method' => 1, + 'compression_flags' => 1, + 'm_dwScale' => 4, + 'm_dwBlockSize' => 4, + 'm_wQuality' => 2, + 'm_wMarkDistance' => 2, + 'm_wReserved' => 2, + 'm_dwOrgSize' => 4, + 'm_bFactExists' => 2, + 'm_dwRiffChunkSize' => 4, + ); + $litewave_offset = 18; + foreach ($flags as $flag => $length) { + $riff_litewave_raw[$flag] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], $litewave_offset, $length)); + $litewave_offset += $length; + } - //$thisfile_riff_litewave['quality_factor'] = intval(round((2000 - $thisfile_riff_litewave_raw['m_dwScale']) / 20)); - $thisfile_riff_litewave['quality_factor'] = $thisfile_riff_litewave_raw['m_wQuality']; + //$riff_litewave['quality_factor'] = intval(round((2000 - $riff_litewave_raw['m_dwScale']) / 20)); + $riff_litewave['quality_factor'] = $riff_litewave_raw['m_wQuality']; - $thisfile_riff_litewave['flags']['raw_source'] = ($thisfile_riff_litewave_raw['compression_flags'] & 0x01) ? false : true; - $thisfile_riff_litewave['flags']['vbr_blocksize'] = ($thisfile_riff_litewave_raw['compression_flags'] & 0x02) ? false : true; - $thisfile_riff_litewave['flags']['seekpoints'] = (bool) ($thisfile_riff_litewave_raw['compression_flags'] & 0x04); + $riff_litewave['flags']['raw_source'] = ($riff_litewave_raw['compression_flags'] & 0x01) ? false : true; + $riff_litewave['flags']['vbr_blocksize'] = ($riff_litewave_raw['compression_flags'] & 0x02) ? false : true; + $riff_litewave['flags']['seekpoints'] = (bool) ($riff_litewave_raw['compression_flags'] & 0x04); - $thisfile_audio['lossless'] = (($thisfile_riff_litewave_raw['m_wQuality'] == 100) ? true : false); - $thisfile_audio['encoder_options'] = '-q'.$thisfile_riff_litewave['quality_factor']; + $thisfile_audio['lossless'] = (($riff_litewave_raw['m_wQuality'] == 100) ? true : false); + $thisfile_audio['encoder_options'] = '-q'.$riff_litewave['quality_factor']; break; default: @@ -556,10 +603,13 @@ class getid3_riff extends getid3_handler } break; + // http://en.wikipedia.org/wiki/Audio_Video_Interleave case 'AVI ': + $info['fileformat'] = 'avi'; + $info['mime_type'] = 'video/avi'; + $thisfile_video['bitrate_mode'] = 'vbr'; // maybe not, but probably $thisfile_video['dataformat'] = 'avi'; - $info['mime_type'] = 'video/avi'; if (isset($thisfile_riff[$RIFFsubtype]['movi']['offset'])) { $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8; @@ -586,19 +636,19 @@ class getid3_riff extends getid3_handler // ), //); foreach ($thisfile_riff['AVI ']['hdrl']['strl']['indx'] as $streamnumber => $steamdataarray) { - $thisfile_riff_avi_hdrl_strl_indx_stream_data = &$thisfile_riff['AVI ']['hdrl']['strl']['indx'][$streamnumber]['data']; + $ahsisd = &$thisfile_riff['AVI ']['hdrl']['strl']['indx'][$streamnumber]['data']; - $thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 0, 2)); - $thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 2, 1)); - $thisfile_riff_raw['indx'][$streamnumber]['bIndexType'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 3, 1)); - $thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 4, 4)); - $thisfile_riff_raw['indx'][$streamnumber]['dwChunkId'] = substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 8, 4); - $thisfile_riff_raw['indx'][$streamnumber]['dwReserved'] = $this->EitherEndian2Int(substr($thisfile_riff_avi_hdrl_strl_indx_stream_data, 12, 4)); + $thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = $this->EitherEndian2Int(substr($ahsisd, 0, 2)); + $thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType'] = $this->EitherEndian2Int(substr($ahsisd, 2, 1)); + $thisfile_riff_raw['indx'][$streamnumber]['bIndexType'] = $this->EitherEndian2Int(substr($ahsisd, 3, 1)); + $thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse'] = $this->EitherEndian2Int(substr($ahsisd, 4, 4)); + $thisfile_riff_raw['indx'][$streamnumber]['dwChunkId'] = substr($ahsisd, 8, 4); + $thisfile_riff_raw['indx'][$streamnumber]['dwReserved'] = $this->EitherEndian2Int(substr($ahsisd, 12, 4)); //$thisfile_riff_raw['indx'][$streamnumber]['bIndexType_name'] = $bIndexType[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']]; //$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType_name'] = $bIndexSubtype[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']][$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']]; - unset($thisfile_riff_avi_hdrl_strl_indx_stream_data); + unset($ahsisd); } } if (isset($thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data'])) { @@ -613,26 +663,39 @@ class getid3_riff extends getid3_handler $info['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero'; return false; } - $thisfile_riff_raw_avih['dwMaxBytesPerSec'] = $this->EitherEndian2Int(substr($avihData, 4, 4)); // max. transfer rate - $thisfile_riff_raw_avih['dwPaddingGranularity'] = $this->EitherEndian2Int(substr($avihData, 8, 4)); // pad to multiples of this size; normally 2K. - $thisfile_riff_raw_avih['dwFlags'] = $this->EitherEndian2Int(substr($avihData, 12, 4)); // the ever-present flags - $thisfile_riff_raw_avih['dwTotalFrames'] = $this->EitherEndian2Int(substr($avihData, 16, 4)); // # frames in file - $thisfile_riff_raw_avih['dwInitialFrames'] = $this->EitherEndian2Int(substr($avihData, 20, 4)); - $thisfile_riff_raw_avih['dwStreams'] = $this->EitherEndian2Int(substr($avihData, 24, 4)); - $thisfile_riff_raw_avih['dwSuggestedBufferSize'] = $this->EitherEndian2Int(substr($avihData, 28, 4)); - $thisfile_riff_raw_avih['dwWidth'] = $this->EitherEndian2Int(substr($avihData, 32, 4)); - $thisfile_riff_raw_avih['dwHeight'] = $this->EitherEndian2Int(substr($avihData, 36, 4)); - $thisfile_riff_raw_avih['dwScale'] = $this->EitherEndian2Int(substr($avihData, 40, 4)); - $thisfile_riff_raw_avih['dwRate'] = $this->EitherEndian2Int(substr($avihData, 44, 4)); - $thisfile_riff_raw_avih['dwStart'] = $this->EitherEndian2Int(substr($avihData, 48, 4)); - $thisfile_riff_raw_avih['dwLength'] = $this->EitherEndian2Int(substr($avihData, 52, 4)); - $thisfile_riff_raw_avih['flags']['hasindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000010); - $thisfile_riff_raw_avih['flags']['mustuseindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000020); - $thisfile_riff_raw_avih['flags']['interleaved'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000100); - $thisfile_riff_raw_avih['flags']['trustcktype'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000800); - $thisfile_riff_raw_avih['flags']['capturedfile'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00010000); - $thisfile_riff_raw_avih['flags']['copyrighted'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00020010); + $flags = array( + 'dwMaxBytesPerSec', // max. transfer rate + 'dwPaddingGranularity', // pad to multiples of this size; normally 2K. + 'dwFlags', // the ever-present flags + 'dwTotalFrames', // # frames in file + 'dwInitialFrames', // + 'dwStreams', // + 'dwSuggestedBufferSize', // + 'dwWidth', // + 'dwHeight', // + 'dwScale', // + 'dwRate', // + 'dwStart', // + 'dwLength', // + ); + $avih_offset = 4; + foreach ($flags as $flag) { + $thisfile_riff_raw_avih[$flag] = $this->EitherEndian2Int(substr($avihData, $avih_offset, 4)); + $avih_offset += 4; + } + + $flags = array( + 'hasindex' => 0x00000010, + 'mustuseindex' => 0x00000020, + 'interleaved' => 0x00000100, + 'trustcktype' => 0x00000800, + 'capturedfile' => 0x00010000, + 'copyrighted' => 0x00020010, + ); + foreach ($flags as $flag => $value) { + $thisfile_riff_raw_avih['flags'][$flag] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & $value); + } // shortcut $thisfile_riff_video[$streamindex] = array(); @@ -675,7 +738,7 @@ class getid3_riff extends getid3_handler $streamindex = count($thisfile_riff_audio); } - $thisfile_riff_audio[$streamindex] = getid3_riff::RIFFparseWAVEFORMATex($strfData); + $thisfile_riff_audio[$streamindex] = self::parseWAVEFORMATex($strfData); $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; // shortcut @@ -758,10 +821,10 @@ class getid3_riff extends getid3_handler $thisfile_riff_raw_strh_current['dwSampleSize'] = $this->EitherEndian2Int(substr($strhData, 44, 4)); $thisfile_riff_raw_strh_current['rcFrame'] = $this->EitherEndian2Int(substr($strhData, 48, 4)); - $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strh_current['fccHandler']); + $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_riff_raw_strh_current['fccHandler']); $thisfile_video['fourcc'] = $thisfile_riff_raw_strh_current['fccHandler']; - if (!$thisfile_riff_video_current['codec'] && isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { - $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']); + if (!$thisfile_riff_video_current['codec'] && isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && self::fourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { + $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']); $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; } $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; @@ -780,8 +843,7 @@ class getid3_riff extends getid3_handler switch ($strhfccType) { case 'vids': - $thisfile_riff_raw_strf_strhfccType_streamindex = getid3_riff::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($info['fileformat'] == 'riff')); -//echo '
'.print_r($thisfile_riff_raw_strf_strhfccType_streamindex, true).'
'; + $thisfile_riff_raw_strf_strhfccType_streamindex = self::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($this->container == 'riff')); $thisfile_video['bits_per_sample'] = $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount']; if ($thisfile_riff_video_current['codec'] == 'DV') { @@ -806,8 +868,8 @@ class getid3_riff extends getid3_handler if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; - if (getid3_riff::RIFFfourccLookup($thisfile_video['fourcc'])) { - $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_video['fourcc']); + if (self::fourccLookup($thisfile_video['fourcc'])) { + $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_video['fourcc']); $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; } @@ -831,11 +893,28 @@ class getid3_riff extends getid3_handler } break; + + case 'AMV ': + $info['fileformat'] = 'amv'; + $info['mime_type'] = 'video/amv'; + + $thisfile_video['bitrate_mode'] = 'vbr'; // it's MJPEG, presumably contant-quality encoding, thereby VBR + $thisfile_video['dataformat'] = 'mjpeg'; + $thisfile_video['codec'] = 'mjpeg'; + $thisfile_video['lossless'] = false; + $thisfile_video['bits_per_sample'] = 24; + + $thisfile_audio['dataformat'] = 'adpcm'; + $thisfile_audio['lossless'] = false; + break; + + + // http://en.wikipedia.org/wiki/CD-DA case 'CDDA': - $thisfile_audio['bitrate_mode'] = 'cbr'; + $info['fileformat'] = 'cda'; + unset($info['mime_type']); + $thisfile_audio_dataformat = 'cda'; - $thisfile_audio['lossless'] = true; - unset($info['mime_type']); $info['avdataoffset'] = 44; @@ -857,6 +936,7 @@ class getid3_riff extends getid3_handler $info['playtime_seconds'] = $thisfile_riff_CDDA_fmt_0['playtime_seconds']; // hardcoded data for CD-audio + $thisfile_audio['lossless'] = true; $thisfile_audio['sample_rate'] = 44100; $thisfile_audio['channels'] = 2; $thisfile_audio['bits_per_sample'] = 16; @@ -865,13 +945,15 @@ class getid3_riff extends getid3_handler } break; - + // http://en.wikipedia.org/wiki/AIFF case 'AIFF': case 'AIFC': + $info['fileformat'] = 'aiff'; + $info['mime_type'] = 'audio/x-aiff'; + $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio_dataformat = 'aiff'; $thisfile_audio['lossless'] = true; - $info['mime_type'] = 'audio/x-aiff'; if (isset($thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'])) { $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8; @@ -969,7 +1051,7 @@ class getid3_riff extends getid3_handler $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; } } - +/* if (isset($thisfile_riff[$RIFFsubtype]['ID3 '])) { getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); $getid3_temp = new getID3(); @@ -981,14 +1063,18 @@ class getid3_riff extends getid3_handler } unset($getid3_temp, $getid3_id3v2); } +*/ break; + // http://en.wikipedia.org/wiki/8SVX case '8SVX': + $info['fileformat'] = '8svx'; + $info['mime_type'] = 'audio/8svx'; + $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio_dataformat = '8svx'; $thisfile_audio['bits_per_sample'] = 8; $thisfile_audio['channels'] = 1; // overridden below, if need be - $info['mime_type'] = 'audio/x-aiff'; if (isset($thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'])) { $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8; @@ -1063,55 +1149,69 @@ class getid3_riff extends getid3_handler } break; - case 'CDXA': - $info['mime_type'] = 'video/mpeg'; + $info['fileformat'] = 'vcd'; // Asume Video CD + $info['mime_type'] = 'video/mpeg'; + if (!empty($thisfile_riff['CDXA']['data'][0]['size'])) { - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, false)) { - $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); - $getid3_mpeg = new getid3_mpeg($getid3_temp); - $getid3_mpeg->Analyze(); - if (empty($getid3_temp->info['error'])) { - $info['audio'] = $getid3_temp->info['audio']; - $info['video'] = $getid3_temp->info['video']; - $info['mpeg'] = $getid3_temp->info['mpeg']; - $info['warning'] = $getid3_temp->info['warning']; - } - unset($getid3_temp, $getid3_mpeg); + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, true); + + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_mpeg = new getid3_mpeg($getid3_temp); + $getid3_mpeg->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['video'] = $getid3_temp->info['video']; + $info['mpeg'] = $getid3_temp->info['mpeg']; + $info['warning'] = $getid3_temp->info['warning']; } + unset($getid3_temp, $getid3_mpeg); } break; default: $info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead'; - unset($info['fileformat']); + //unset($info['fileformat']); + } + + switch ($RIFFsubtype) { + case 'WAVE': + case 'AIFF': + case 'AIFC': + $ID3v2_key_good = 'id3 '; + $ID3v2_keys_bad = array('ID3 ', 'tag '); + foreach ($ID3v2_keys_bad as $ID3v2_key_bad) { + if (isset($thisfile_riff[$RIFFsubtype][$ID3v2_key_bad]) && !array_key_exists($ID3v2_key_good, $thisfile_riff[$RIFFsubtype])) { + $thisfile_riff[$RIFFsubtype][$ID3v2_key_good] = $thisfile_riff[$RIFFsubtype][$ID3v2_key_bad]; + $info['warning'][] = 'mapping "'.$ID3v2_key_bad.'" chunk to "'.$ID3v2_key_good.'"'; + } + } + + if (isset($thisfile_riff[$RIFFsubtype]['id3 '])) { + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); + + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_id3v2 = new getid3_id3v2($getid3_temp); + $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['id3 '][0]['offset'] + 8; + if ($thisfile_riff[$RIFFsubtype]['id3 '][0]['valid'] = $getid3_id3v2->Analyze()) { + $info['id3v2'] = $getid3_temp->info['id3v2']; + } + unset($getid3_temp, $getid3_id3v2); + } break; } - if (isset($thisfile_riff_raw['fmt ']['wFormatTag']) && ($thisfile_riff_raw['fmt ']['wFormatTag'] == 1)) { - // http://www.mega-nerd.com/erikd/Blog/Windiots/dts.html - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $FirstFourBytes = fread($this->getid3->fp, 4); - if (preg_match('/^\xFF\x1F\x00\xE8/s', $FirstFourBytes)) { - // DTSWAV - $thisfile_audio_dataformat = 'dts'; - } elseif (preg_match('/^\x7F\xFF\x80\x01/s', $FirstFourBytes)) { - // DTS, but this probably shouldn't happen - $thisfile_audio_dataformat = 'dts'; - } - } - - if (isset($thisfile_riff_WAVE['DISP']) && is_array($thisfile_riff_WAVE['DISP'])) { $thisfile_riff['comments']['title'][] = trim(substr($thisfile_riff_WAVE['DISP'][count($thisfile_riff_WAVE['DISP']) - 1]['data'], 4)); } if (isset($thisfile_riff_WAVE['INFO']) && is_array($thisfile_riff_WAVE['INFO'])) { - $this->RIFFcommentsParse($thisfile_riff_WAVE['INFO'], $thisfile_riff['comments']); + self::parseComments($thisfile_riff_WAVE['INFO'], $thisfile_riff['comments']); } if (isset($thisfile_riff['AVI ']['INFO']) && is_array($thisfile_riff['AVI ']['INFO'])) { - $this->RIFFcommentsParse($thisfile_riff['AVI ']['INFO'], $thisfile_riff['comments']); + self::parseComments($thisfile_riff['AVI ']['INFO'], $thisfile_riff['comments']); } if (empty($thisfile_audio['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version'])) { @@ -1220,8 +1320,439 @@ class getid3_riff extends getid3_handler return true; } + public function ParseRIFFAMV($startoffset, $maxoffset) { + // AMV files are RIFF-AVI files with parts of the spec deliberately broken, such as chunk size fields hardcoded to zero (because players known in hardware that these fields are always a certain size - static function RIFFcommentsParse(&$RIFFinfoArray, &$CommentsTargetArray) { + // https://code.google.com/p/amv-codec-tools/wiki/AmvDocumentation + //typedef struct _amvmainheader { + //FOURCC fcc; // 'amvh' + //DWORD cb; + //DWORD dwMicroSecPerFrame; + //BYTE reserve[28]; + //DWORD dwWidth; + //DWORD dwHeight; + //DWORD dwSpeed; + //DWORD reserve0; + //DWORD reserve1; + //BYTE bTimeSec; + //BYTE bTimeMin; + //WORD wTimeHour; + //} AMVMAINHEADER; + + $info = &$this->getid3->info; + $RIFFchunk = false; + + try { + + $this->fseek($startoffset); + $maxoffset = min($maxoffset, $info['avdataend']); + $AMVheader = $this->fread(284); + if (substr($AMVheader, 0, 8) != 'hdrlamvh') { + throw new Exception('expecting "hdrlamv" at offset '.($startoffset + 0).', found "'.substr($AMVheader, 0, 8).'"'); + } + if (substr($AMVheader, 8, 4) != "\x38\x00\x00\x00") { // "amvh" chunk size, hardcoded to 0x38 = 56 bytes + throw new Exception('expecting "0x38000000" at offset '.($startoffset + 8).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 8, 4)).'"'); + } + $RIFFchunk = array(); + $RIFFchunk['amvh']['us_per_frame'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 12, 4)); + $RIFFchunk['amvh']['reserved28'] = substr($AMVheader, 16, 28); // null? reserved? + $RIFFchunk['amvh']['resolution_x'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 44, 4)); + $RIFFchunk['amvh']['resolution_y'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 48, 4)); + $RIFFchunk['amvh']['frame_rate_int'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 52, 4)); + $RIFFchunk['amvh']['reserved0'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 56, 4)); // 1? reserved? + $RIFFchunk['amvh']['reserved1'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 60, 4)); // 0? reserved? + $RIFFchunk['amvh']['runtime_sec'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 64, 1)); + $RIFFchunk['amvh']['runtime_min'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 65, 1)); + $RIFFchunk['amvh']['runtime_hrs'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 66, 2)); + + $info['video']['frame_rate'] = 1000000 / $RIFFchunk['amvh']['us_per_frame']; + $info['video']['resolution_x'] = $RIFFchunk['amvh']['resolution_x']; + $info['video']['resolution_y'] = $RIFFchunk['amvh']['resolution_y']; + $info['playtime_seconds'] = ($RIFFchunk['amvh']['runtime_hrs'] * 3600) + ($RIFFchunk['amvh']['runtime_min'] * 60) + $RIFFchunk['amvh']['runtime_sec']; + + // the rest is all hardcoded(?) and does not appear to be useful until you get to audio info at offset 256, even then everything is probably hardcoded + + if (substr($AMVheader, 68, 20) != 'LIST'."\x00\x00\x00\x00".'strlstrh'."\x38\x00\x00\x00") { + throw new Exception('expecting "LIST<0x00000000>strlstrh<0x38000000>" at offset '.($startoffset + 68).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 68, 20)).'"'); + } + // followed by 56 bytes of null: substr($AMVheader, 88, 56) -> 144 + if (substr($AMVheader, 144, 8) != 'strf'."\x24\x00\x00\x00") { + throw new Exception('expecting "strf<0x24000000>" at offset '.($startoffset + 144).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 144, 8)).'"'); + } + // followed by 36 bytes of null: substr($AMVheader, 144, 36) -> 180 + + if (substr($AMVheader, 188, 20) != 'LIST'."\x00\x00\x00\x00".'strlstrh'."\x30\x00\x00\x00") { + throw new Exception('expecting "LIST<0x00000000>strlstrh<0x30000000>" at offset '.($startoffset + 188).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 188, 20)).'"'); + } + // followed by 48 bytes of null: substr($AMVheader, 208, 48) -> 256 + if (substr($AMVheader, 256, 8) != 'strf'."\x14\x00\x00\x00") { + throw new Exception('expecting "strf<0x14000000>" at offset '.($startoffset + 256).', found "'.getid3_lib::PrintHexBytes(substr($AMVheader, 256, 8)).'"'); + } + // followed by 20 bytes of a modified WAVEFORMATEX: + // typedef struct { + // WORD wFormatTag; //(Fixme: this is equal to PCM's 0x01 format code) + // WORD nChannels; //(Fixme: this is always 1) + // DWORD nSamplesPerSec; //(Fixme: for all known sample files this is equal to 22050) + // DWORD nAvgBytesPerSec; //(Fixme: for all known sample files this is equal to 44100) + // WORD nBlockAlign; //(Fixme: this seems to be 2 in AMV files, is this correct ?) + // WORD wBitsPerSample; //(Fixme: this seems to be 16 in AMV files instead of the expected 4) + // WORD cbSize; //(Fixme: this seems to be 0 in AMV files) + // WORD reserved; + // } WAVEFORMATEX; + $RIFFchunk['strf']['wformattag'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 264, 2)); + $RIFFchunk['strf']['nchannels'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 266, 2)); + $RIFFchunk['strf']['nsamplespersec'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 268, 4)); + $RIFFchunk['strf']['navgbytespersec'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 272, 4)); + $RIFFchunk['strf']['nblockalign'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 276, 2)); + $RIFFchunk['strf']['wbitspersample'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 278, 2)); + $RIFFchunk['strf']['cbsize'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 280, 2)); + $RIFFchunk['strf']['reserved'] = getid3_lib::LittleEndian2Int(substr($AMVheader, 282, 2)); + + + $info['audio']['lossless'] = false; + $info['audio']['sample_rate'] = $RIFFchunk['strf']['nsamplespersec']; + $info['audio']['channels'] = $RIFFchunk['strf']['nchannels']; + $info['audio']['bits_per_sample'] = $RIFFchunk['strf']['wbitspersample']; + $info['audio']['bitrate'] = $info['audio']['sample_rate'] * $info['audio']['channels'] * $info['audio']['bits_per_sample']; + $info['audio']['bitrate_mode'] = 'cbr'; + + + } catch (getid3_exception $e) { + if ($e->getCode() == 10) { + $this->warning('RIFFAMV parser: '.$e->getMessage()); + } else { + throw $e; + } + } + + return $RIFFchunk; + } + + + public function ParseRIFF($startoffset, $maxoffset) { + $info = &$this->getid3->info; + + $RIFFchunk = false; + $FoundAllChunksWeNeed = false; + + try { + $this->fseek($startoffset); + $maxoffset = min($maxoffset, $info['avdataend']); + while ($this->ftell() < $maxoffset) { + $chunknamesize = $this->fread(8); + //$chunkname = substr($chunknamesize, 0, 4); + $chunkname = str_replace("\x00", '_', substr($chunknamesize, 0, 4)); // note: chunk names of 4 null bytes do appear to be legal (has been observed inside INFO and PRMI chunks, for example), but makes traversing array keys more difficult + $chunksize = $this->EitherEndian2Int(substr($chunknamesize, 4, 4)); + //if (strlen(trim($chunkname, "\x00")) < 4) { + if (strlen($chunkname) < 4) { + $this->error('Expecting chunk name at offset '.($this->ftell() - 8).' but found nothing. Aborting RIFF parsing.'); + break; + } + if (($chunksize == 0) && ($chunkname != 'JUNK')) { + $this->warning('Chunk ('.$chunkname.') size at offset '.($this->ftell() - 4).' is zero. Aborting RIFF parsing.'); + break; + } + if (($chunksize % 2) != 0) { + // all structures are packed on word boundaries + $chunksize++; + } + + switch ($chunkname) { + case 'LIST': + $listname = $this->fread(4); + if (preg_match('#^(movi|rec )$#i', $listname)) { + $RIFFchunk[$listname]['offset'] = $this->ftell() - 4; + $RIFFchunk[$listname]['size'] = $chunksize; + + if (!$FoundAllChunksWeNeed) { + $WhereWeWere = $this->ftell(); + $AudioChunkHeader = $this->fread(12); + $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); + $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); + $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); + + if ($AudioChunkStreamType == 'wb') { + $FirstFourBytes = substr($AudioChunkHeader, 8, 4); + if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', $FirstFourBytes)) { + // MP3 + if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; + $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; + $getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__); + $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); + if (isset($getid3_temp->info['mpeg']['audio'])) { + $info['mpeg']['audio'] = $getid3_temp->info['mpeg']['audio']; + $info['audio'] = $getid3_temp->info['audio']; + $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + //$info['bitrate'] = $info['audio']['bitrate']; + } + unset($getid3_temp, $getid3_mp3); + } + + } elseif (strpos($FirstFourBytes, getid3_ac3::syncword) === 0) { + + // AC3 + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; + $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; + $getid3_ac3 = new getid3_ac3($getid3_temp); + $getid3_ac3->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['ac3'] = $getid3_temp->info['ac3']; + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $key => $value) { + $info['warning'][] = $value; + } + } + } + unset($getid3_temp, $getid3_ac3); + } + } + $FoundAllChunksWeNeed = true; + $this->fseek($WhereWeWere); + } + $this->fseek($chunksize - 4, SEEK_CUR); + + } else { + + if (!isset($RIFFchunk[$listname])) { + $RIFFchunk[$listname] = array(); + } + $LISTchunkParent = $listname; + $LISTchunkMaxOffset = $this->ftell() - 4 + $chunksize; + if ($parsedChunk = $this->ParseRIFF($this->ftell(), $LISTchunkMaxOffset)) { + $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); + } + + } + break; + + default: + if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) { + $this->fseek($chunksize, SEEK_CUR); + break; + } + $thisindex = 0; + if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { + $thisindex = count($RIFFchunk[$chunkname]); + } + $RIFFchunk[$chunkname][$thisindex]['offset'] = $this->ftell() - 8; + $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; + switch ($chunkname) { + case 'data': + $info['avdataoffset'] = $this->ftell(); + $info['avdataend'] = $info['avdataoffset'] + $chunksize; + + $testData = $this->fread(36); + if ($testData === '') { + break; + } + if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', substr($testData, 0, 4))) { + + // Probably is MP3 data + if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($testData, 0, 4))) { + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + $getid3_mp3 = new getid3_mp3($getid3_temp, __CLASS__); + $getid3_mp3->getOnlyMPEGaudioInfo($info['avdataoffset'], false); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['mpeg'] = $getid3_temp->info['mpeg']; + } + unset($getid3_temp, $getid3_mp3); + } + + } elseif (($isRegularAC3 = (substr($testData, 0, 2) == getid3_ac3::syncword)) || substr($testData, 8, 2) == strrev(getid3_ac3::syncword)) { + + // This is probably AC-3 data + $getid3_temp = new getID3(); + if ($isRegularAC3) { + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + } + $getid3_ac3 = new getid3_ac3($getid3_temp); + if ($isRegularAC3) { + $getid3_ac3->Analyze(); + } else { + // Dolby Digital WAV + // AC-3 content, but not encoded in same format as normal AC-3 file + // For one thing, byte order is swapped + $ac3_data = ''; + for ($i = 0; $i < 28; $i += 2) { + $ac3_data .= substr($testData, 8 + $i + 1, 1); + $ac3_data .= substr($testData, 8 + $i + 0, 1); + } + $getid3_ac3->AnalyzeString($ac3_data); + } + + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['ac3'] = $getid3_temp->info['ac3']; + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + $this->warning('getid3_ac3() says: ['.$newerror.']'); + } + } + } + unset($getid3_temp, $getid3_ac3); + + } elseif (preg_match('/^('.implode('|', array_map('preg_quote', getid3_dts::$syncwords)).')/', $testData)) { + + // This is probably DTS data + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_dts = new getid3_dts($getid3_temp); + $getid3_dts->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['dts'] = $getid3_temp->info['dts']; + $info['playtime_seconds'] = $getid3_temp->info['playtime_seconds']; // may not match RIFF calculations since DTS-WAV often used 14/16 bit-word packing + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + $this->warning('getid3_dts() says: ['.$newerror.']'); + } + } + } + + unset($getid3_temp, $getid3_dts); + + } elseif (substr($testData, 0, 4) == 'wvpk') { + + // This is WavPack data + $info['wavpack']['offset'] = $info['avdataoffset']; + $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($testData, 4, 4)); + $this->parseWavPackHeader(substr($testData, 8, 28)); + + } else { + // This is some other kind of data (quite possibly just PCM) + // do nothing special, just skip it + } + $nextoffset = $info['avdataend']; + $this->fseek($nextoffset); + break; + + case 'iXML': + case 'bext': + case 'cart': + case 'fmt ': + case 'strh': + case 'strf': + case 'indx': + case 'MEXT': + case 'DISP': + // always read data in + case 'JUNK': + // should be: never read data in + // but some programs write their version strings in a JUNK chunk (e.g. VirtualDub, AVIdemux, etc) + if ($chunksize < 1048576) { + if ($chunksize > 0) { + $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize); + if ($chunkname == 'JUNK') { + if (preg_match('#^([\\x20-\\x7F]+)#', $RIFFchunk[$chunkname][$thisindex]['data'], $matches)) { + // only keep text characters [chr(32)-chr(127)] + $info['riff']['comments']['junk'][] = trim($matches[1]); + } + // but if nothing there, ignore + // remove the key in either case + unset($RIFFchunk[$chunkname][$thisindex]['data']); + } + } + } else { + $this->warning('Chunk "'.$chunkname.'" at offset '.$this->ftell().' is unexpectedly larger than 1MB (claims to be '.number_format($chunksize).' bytes), skipping data'); + $this->fseek($chunksize, SEEK_CUR); + } + break; + + //case 'IDVX': + // $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunksize)); + // break; + + default: + if (!empty($LISTchunkParent) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) { + $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; + $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; + unset($RIFFchunk[$chunkname][$thisindex]['offset']); + unset($RIFFchunk[$chunkname][$thisindex]['size']); + if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) { + unset($RIFFchunk[$chunkname][$thisindex]); + } + if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { + unset($RIFFchunk[$chunkname]); + } + $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = $this->fread($chunksize); + } elseif ($chunksize < 2048) { + // only read data in if smaller than 2kB + $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize); + } else { + $this->fseek($chunksize, SEEK_CUR); + } + break; + } + break; + } + } + + } catch (getid3_exception $e) { + if ($e->getCode() == 10) { + $this->warning('RIFF parser: '.$e->getMessage()); + } else { + throw $e; + } + } + + return $RIFFchunk; + } + + public function ParseRIFFdata(&$RIFFdata) { + $info = &$this->getid3->info; + if ($RIFFdata) { + $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); + $fp_temp = fopen($tempfile, 'wb'); + $RIFFdataLength = strlen($RIFFdata); + $NewLengthString = getid3_lib::LittleEndian2String($RIFFdataLength, 4); + for ($i = 0; $i < 4; $i++) { + $RIFFdata[($i + 4)] = $NewLengthString[$i]; + } + fwrite($fp_temp, $RIFFdata); + fclose($fp_temp); + + $getid3_temp = new getID3(); + $getid3_temp->openfile($tempfile); + $getid3_temp->info['filesize'] = $RIFFdataLength; + $getid3_temp->info['filenamepath'] = $info['filenamepath']; + $getid3_temp->info['tags'] = $info['tags']; + $getid3_temp->info['warning'] = $info['warning']; + $getid3_temp->info['error'] = $info['error']; + $getid3_temp->info['comments'] = $info['comments']; + $getid3_temp->info['audio'] = (isset($info['audio']) ? $info['audio'] : array()); + $getid3_temp->info['video'] = (isset($info['video']) ? $info['video'] : array()); + $getid3_riff = new getid3_riff($getid3_temp); + $getid3_riff->Analyze(); + + $info['riff'] = $getid3_temp->info['riff']; + $info['warning'] = $getid3_temp->info['warning']; + $info['error'] = $getid3_temp->info['error']; + $info['tags'] = $getid3_temp->info['tags']; + $info['comments'] = $getid3_temp->info['comments']; + unset($getid3_riff, $getid3_temp); + unlink($tempfile); + } + return false; + } + + public static function parseComments(&$RIFFinfoArray, &$CommentsTargetArray) { $RIFFinfoKeyLookup = array( 'IARL'=>'archivallocation', 'IART'=>'artist', @@ -1261,7 +1792,8 @@ class getid3_riff extends getid3_handler 'ISTR'=>'starring', 'ITCH'=>'encoded_by', 'IWEB'=>'url', - 'IWRI'=>'writer' + 'IWRI'=>'writer', + '____'=>'comment', ); foreach ($RIFFinfoKeyLookup as $key => $value) { if (isset($RIFFinfoArray[$key])) { @@ -1279,387 +1811,23 @@ class getid3_riff extends getid3_handler return true; } - function ParseRIFF($startoffset, $maxoffset) { - $info = &$this->getid3->info; - - $maxoffset = min($maxoffset, $info['avdataend']); - - $RIFFchunk = false; - $FoundAllChunksWeNeed = false; - - if (($startoffset < 0) || !getid3_lib::intValueSupported($startoffset)) { - $info['warning'][] = 'Unable to ParseRIFF() at '.$startoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; - return false; - } - $max_usable_offset = min(PHP_INT_MAX - 1024, $maxoffset); - if ($maxoffset > $max_usable_offset) { - $info['warning'][] = 'ParseRIFF() may return incomplete data for chunk starting at '.$startoffset.' because beyond it extends to '.$maxoffset.', which is beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; - } - fseek($this->getid3->fp, $startoffset, SEEK_SET); - - while (ftell($this->getid3->fp) < $max_usable_offset) { - $chunknamesize = fread($this->getid3->fp, 8); - $chunkname = substr($chunknamesize, 0, 4); - $chunksize = $this->EitherEndian2Int(substr($chunknamesize, 4, 4)); - if (strlen($chunkname) < 4) { - $info['error'][] = 'Expecting chunk name at offset '.(ftell($this->getid3->fp) - 4).' but found nothing. Aborting RIFF parsing.'; - break; - } - if ($chunksize == 0) { - if ($chunkname == 'JUNK') { - // we'll allow zero-size JUNK frames - } else { - $info['warning'][] = 'Chunk size at offset '.(ftell($this->getid3->fp) - 4).' is zero. Aborting RIFF parsing.'; - break; - } - } - if (($chunksize % 2) != 0) { - // all structures are packed on word boundaries - $chunksize++; - } - - switch ($chunkname) { - case 'LIST': - $listname = fread($this->getid3->fp, 4); - if (preg_match('#^(movi|rec )$#i', $listname)) { - $RIFFchunk[$listname]['offset'] = ftell($this->getid3->fp) - 4; - $RIFFchunk[$listname]['size'] = $chunksize; - - if ($FoundAllChunksWeNeed) { - - // skip over - - } else { - - $WhereWeWere = ftell($this->getid3->fp); - $AudioChunkHeader = fread($this->getid3->fp, 12); - $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); - $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); - $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); - - if ($AudioChunkStreamType == 'wb') { - $FirstFourBytes = substr($AudioChunkHeader, 8, 4); - if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', $FirstFourBytes)) { - // MP3 - if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { - $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); - $getid3_temp->info['avdataoffset'] = ftell($this->getid3->fp) - 4; - $getid3_temp->info['avdataend'] = ftell($this->getid3->fp) + $AudioChunkSize; - $getid3_mp3 = new getid3_mp3($getid3_temp); - $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); - if (isset($getid3_temp->info['mpeg']['audio'])) { - $info['mpeg']['audio'] = $getid3_temp->info['mpeg']['audio']; - $info['audio'] = $getid3_temp->info['audio']; - $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer']; - $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; - $info['audio']['channels'] = $info['mpeg']['audio']['channels']; - $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; - $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); - //$info['bitrate'] = $info['audio']['bitrate']; - } - unset($getid3_temp, $getid3_mp3); - } - - } elseif (preg_match('/^\x0B\x77/s', $FirstFourBytes)) { - - // AC3 - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { - $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); - $getid3_temp->info['avdataoffset'] = ftell($this->getid3->fp) - 4; - $getid3_temp->info['avdataend'] = ftell($this->getid3->fp) + $AudioChunkSize; - $getid3_ac3 = new getid3_ac3($getid3_temp); - $getid3_ac3->Analyze(); - if (empty($getid3_temp->info['error'])) { - $info['audio'] = $getid3_temp->info['audio']; - $info['ac3'] = $getid3_temp->info['ac3']; - if (!empty($getid3_temp->info['warning'])) { - foreach ($getid3_temp->info['warning'] as $key => $value) { - $info['warning'][] = $value; - } - } - } - unset($getid3_temp, $getid3_ac3); - } - - } - - } - - $FoundAllChunksWeNeed = true; - fseek($this->getid3->fp, $WhereWeWere, SEEK_SET); - - } - fseek($this->getid3->fp, $chunksize - 4, SEEK_CUR); - - //} elseif (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#i', $listname)) { - // - // // data chunk, ignore - // - } else { - - if (!isset($RIFFchunk[$listname])) { - $RIFFchunk[$listname] = array(); - } - $LISTchunkParent = $listname; - $LISTchunkMaxOffset = ftell($this->getid3->fp) - 4 + $chunksize; - if ($parsedChunk = $this->ParseRIFF(ftell($this->getid3->fp), ftell($this->getid3->fp) + $chunksize - 4)) { - $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); - } - - } - break; - - default: - if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) { - $nextoffset = ftell($this->getid3->fp) + $chunksize; - if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { - $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; - break 2; - } - fseek($this->getid3->fp, $nextoffset, SEEK_SET); - break; - } - $thisindex = 0; - if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { - $thisindex = count($RIFFchunk[$chunkname]); - } - $RIFFchunk[$chunkname][$thisindex]['offset'] = ftell($this->getid3->fp) - 8; - $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; - switch ($chunkname) { - case 'data': - $info['avdataoffset'] = ftell($this->getid3->fp); - $info['avdataend'] = $info['avdataoffset'] + $chunksize; - - $RIFFdataChunkContentsTest = fread($this->getid3->fp, 36); - - if ((strlen($RIFFdataChunkContentsTest) > 0) && preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', substr($RIFFdataChunkContentsTest, 0, 4))) { - - // Probably is MP3 data - if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($RIFFdataChunkContentsTest, 0, 4))) { - $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); - $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; - $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; - $getid3_mp3 = new getid3_mp3($getid3_temp); - $getid3_mp3->getOnlyMPEGaudioInfo($RIFFchunk[$chunkname][$thisindex]['offset'], false); - if (empty($getid3_temp->info['error'])) { - $info['mpeg'] = $getid3_temp->info['mpeg']; - $info['audio'] = $getid3_temp->info['audio']; - } - unset($getid3_temp, $getid3_mp3); - } - - } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 0, 2) == "\x0B\x77")) { - - // This is probably AC-3 data - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { - $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); - $getid3_temp->info['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; - $getid3_temp->info['avdataend'] = $RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']; - $getid3_ac3 = new getid3_ac3($getid3_temp); - $getid3_ac3->Analyze(); - if (empty($getid3_temp->info['error'])) { - $info['audio'] = $getid3_temp->info['audio']; - $info['ac3'] = $getid3_temp->info['ac3']; - $info['warning'] = $getid3_temp->info['warning']; - } - unset($getid3_temp, $getid3_ac3); - } - - } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 8, 2) == "\x77\x0B")) { - - // Dolby Digital WAV - // AC-3 content, but not encoded in same format as normal AC-3 file - // For one thing, byte order is swapped - - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { - - // ok to use tmpfile here - only 56 bytes - if ($RIFFtempfilename = tempnam(GETID3_TEMP_DIR, 'id3')) { - if ($fd_temp = fopen($RIFFtempfilename, 'wb')) { - for ($i = 0; $i < 28; $i += 2) { - // swap byte order - fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 1, 1)); - fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 0, 1)); - } - fclose($fd_temp); - - $getid3_temp = new getID3(); - $getid3_temp->openfile($RIFFtempfilename); - $getid3_temp->info['avdataend'] = 20; - $getid3_ac3 = new getid3_ac3($getid3_temp); - $getid3_ac3->Analyze(); - if (empty($getid3_temp->info['error'])) { - $info['audio'] = $getid3_temp->info['audio']; - $info['ac3'] = $getid3_temp->info['ac3']; - $info['warning'] = $getid3_temp->info['warning']; - } else { - $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): '.implode(';', $getid3_temp->info['error']); - } - unset($getid3_ac3, $getid3_temp); - } else { - $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; - } - unlink($RIFFtempfilename); - - } else { - $info['error'][] = 'Error parsing Dolby Digital WAV (AC3-in-RIFF): failed to write temp file'; - } - - } - - } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 0, 4) == 'wvpk')) { - - // This is WavPack data - $info['wavpack']['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; - $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($RIFFdataChunkContentsTest, 4, 4)); - $this->RIFFparseWavPackHeader(substr($RIFFdataChunkContentsTest, 8, 28)); - - } else { - - // This is some other kind of data (quite possibly just PCM) - // do nothing special, just skip it - - } - $nextoffset = $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize; - if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { - $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; - break 3; - } - fseek($this->getid3->fp, $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize, SEEK_SET); - break; - - case 'iXML': - case 'bext': - case 'cart': - case 'fmt ': - case 'strh': - case 'strf': - case 'indx': - case 'MEXT': - case 'DISP': - // always read data in - case 'JUNK': - // should be: never read data in - // but some programs write their version strings in a JUNK chunk (e.g. VirtualDub, AVIdemux, etc) - if ($chunksize < 1048576) { - if ($chunksize > 0) { - $RIFFchunk[$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); - if ($chunkname == 'JUNK') { - if (preg_match('#^([\\x20-\\x7F]+)#', $RIFFchunk[$chunkname][$thisindex]['data'], $matches)) { - // only keep text characters [chr(32)-chr(127)] - $info['riff']['comments']['junk'][] = trim($matches[1]); - } - // but if nothing there, ignore - // remove the key in either case - unset($RIFFchunk[$chunkname][$thisindex]['data']); - } - } - } else { - $info['warning'][] = 'chunk "'.$chunkname.'" at offset '.ftell($this->getid3->fp).' is unexpectedly larger than 1MB (claims to be '.number_format($chunksize).' bytes), skipping data'; - $nextoffset = ftell($this->getid3->fp) + $chunksize; - if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { - $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; - break 3; - } - fseek($this->getid3->fp, $nextoffset, SEEK_SET); - } - break; - - default: - if (!preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname) && !empty($LISTchunkParent) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) { - $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; - $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; - unset($RIFFchunk[$chunkname][$thisindex]['offset']); - unset($RIFFchunk[$chunkname][$thisindex]['size']); - if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) { - unset($RIFFchunk[$chunkname][$thisindex]); - } - if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { - unset($RIFFchunk[$chunkname]); - } - $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); - //} elseif (in_array($chunkname, array('ID3 ')) || (($chunksize > 0) && ($chunksize < 2048))) { - } elseif (($chunksize > 0) && ($chunksize < 2048)) { - // only read data in if smaller than 2kB - $RIFFchunk[$chunkname][$thisindex]['data'] = fread($this->getid3->fp, $chunksize); - } else { - $nextoffset = ftell($this->getid3->fp) + $chunksize; - if (($nextoffset < 0) || !getid3_lib::intValueSupported($nextoffset)) { - $info['warning'][] = 'Unable to parse chunk at offset '.$nextoffset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; - break 3; - } - fseek($this->getid3->fp, $nextoffset, SEEK_SET); - } - break; - } - break; - - } - - } - - return $RIFFchunk; - } - - - function ParseRIFFdata(&$RIFFdata) { - $info = &$this->getid3->info; - if ($RIFFdata) { - $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); - $fp_temp = fopen($tempfile, 'wb'); - $RIFFdataLength = strlen($RIFFdata); - $NewLengthString = getid3_lib::LittleEndian2String($RIFFdataLength, 4); - for ($i = 0; $i < 4; $i++) { - $RIFFdata{$i + 4} = $NewLengthString{$i}; - } - fwrite($fp_temp, $RIFFdata); - fclose($fp_temp); - - $getid3_temp = new getID3(); - $getid3_temp->openfile($tempfile); - $getid3_temp->info['filesize'] = $RIFFdataLength; - $getid3_temp->info['filenamepath'] = $info['filenamepath']; - $getid3_temp->info['tags'] = $info['tags']; - $getid3_temp->info['warning'] = $info['warning']; - $getid3_temp->info['error'] = $info['error']; - $getid3_temp->info['comments'] = $info['comments']; - $getid3_temp->info['audio'] = (isset($info['audio']) ? $info['audio'] : array()); - $getid3_temp->info['video'] = (isset($info['video']) ? $info['video'] : array()); - $getid3_riff = new getid3_riff($getid3_temp); - $getid3_riff->Analyze(); - - $info['riff'] = $getid3_temp->info['riff']; - $info['warning'] = $getid3_temp->info['warning']; - $info['error'] = $getid3_temp->info['error']; - $info['tags'] = $getid3_temp->info['tags']; - $info['comments'] = $getid3_temp->info['comments']; - unset($getid3_riff, $getid3_temp); - unlink($tempfile); - } - return false; - } - - - public static function RIFFparseWAVEFORMATex($WaveFormatExData) { + public static function parseWAVEFORMATex($WaveFormatExData) { // shortcut $WaveFormatEx['raw'] = array(); $WaveFormatEx_raw = &$WaveFormatEx['raw']; - $WaveFormatEx_raw['wFormatTag'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 0, 2)); - $WaveFormatEx_raw['nChannels'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 2, 2)); - $WaveFormatEx_raw['nSamplesPerSec'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 4, 4)); - $WaveFormatEx_raw['nAvgBytesPerSec'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 8, 4)); - $WaveFormatEx_raw['nBlockAlign'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 12, 2)); - $WaveFormatEx_raw['wBitsPerSample'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 14, 2)); + $WaveFormatEx_raw['wFormatTag'] = substr($WaveFormatExData, 0, 2); + $WaveFormatEx_raw['nChannels'] = substr($WaveFormatExData, 2, 2); + $WaveFormatEx_raw['nSamplesPerSec'] = substr($WaveFormatExData, 4, 4); + $WaveFormatEx_raw['nAvgBytesPerSec'] = substr($WaveFormatExData, 8, 4); + $WaveFormatEx_raw['nBlockAlign'] = substr($WaveFormatExData, 12, 2); + $WaveFormatEx_raw['wBitsPerSample'] = substr($WaveFormatExData, 14, 2); if (strlen($WaveFormatExData) > 16) { - $WaveFormatEx_raw['cbSize'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 16, 2)); + $WaveFormatEx_raw['cbSize'] = substr($WaveFormatExData, 16, 2); } + $WaveFormatEx_raw = array_map('getid3_lib::LittleEndian2Int', $WaveFormatEx_raw); - $WaveFormatEx['codec'] = getid3_riff::RIFFwFormatTagLookup($WaveFormatEx_raw['wFormatTag']); + $WaveFormatEx['codec'] = self::wFormatTagLookup($WaveFormatEx_raw['wFormatTag']); $WaveFormatEx['channels'] = $WaveFormatEx_raw['nChannels']; $WaveFormatEx['sample_rate'] = $WaveFormatEx_raw['nSamplesPerSec']; $WaveFormatEx['bitrate'] = $WaveFormatEx_raw['nAvgBytesPerSec'] * 8; @@ -1668,8 +1836,7 @@ class getid3_riff extends getid3_handler return $WaveFormatEx; } - - function RIFFparseWavPackHeader($WavPackChunkData) { + public function parseWavPackHeader($WavPackChunkData) { // typedef struct { // char ckID [4]; // long ckSize; @@ -1732,23 +1899,24 @@ class getid3_riff extends getid3_handler public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) { - $functionname = ($littleEndian ? 'LittleEndian2Int' : 'BigEndian2Int'); - $parsed['biSize'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 0, 4)); // number of bytes required by the BITMAPINFOHEADER structure - $parsed['biWidth'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 4, 4)); // width of the bitmap in pixels - $parsed['biHeight'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 8, 4)); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner - $parsed['biPlanes'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 12, 2)); // number of color planes on the target device. In most cases this value must be set to 1 - $parsed['biBitCount'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 14, 2)); // Specifies the number of bits per pixels - $parsed['fourcc'] = substr($BITMAPINFOHEADER, 16, 4); // compression identifier - $parsed['biSizeImage'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 20, 4)); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) - $parsed['biXPelsPerMeter'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 24, 4)); // horizontal resolution, in pixels per metre, of the target device - $parsed['biYPelsPerMeter'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 28, 4)); // vertical resolution, in pixels per metre, of the target device - $parsed['biClrUsed'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 32, 4)); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression - $parsed['biClrImportant'] = getid3_lib::$functionname(substr($BITMAPINFOHEADER, 36, 4)); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important + $parsed['biSize'] = substr($BITMAPINFOHEADER, 0, 4); // number of bytes required by the BITMAPINFOHEADER structure + $parsed['biWidth'] = substr($BITMAPINFOHEADER, 4, 4); // width of the bitmap in pixels + $parsed['biHeight'] = substr($BITMAPINFOHEADER, 8, 4); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner + $parsed['biPlanes'] = substr($BITMAPINFOHEADER, 12, 2); // number of color planes on the target device. In most cases this value must be set to 1 + $parsed['biBitCount'] = substr($BITMAPINFOHEADER, 14, 2); // Specifies the number of bits per pixels + $parsed['biSizeImage'] = substr($BITMAPINFOHEADER, 20, 4); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) + $parsed['biXPelsPerMeter'] = substr($BITMAPINFOHEADER, 24, 4); // horizontal resolution, in pixels per metre, of the target device + $parsed['biYPelsPerMeter'] = substr($BITMAPINFOHEADER, 28, 4); // vertical resolution, in pixels per metre, of the target device + $parsed['biClrUsed'] = substr($BITMAPINFOHEADER, 32, 4); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression + $parsed['biClrImportant'] = substr($BITMAPINFOHEADER, 36, 4); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important + $parsed = array_map('getid3_lib::'.($littleEndian ? 'Little' : 'Big').'Endian2Int', $parsed); + + $parsed['fourcc'] = substr($BITMAPINFOHEADER, 16, 4); // compression identifier return $parsed; } - static function ParseDIVXTAG($DIVXTAG) { + public static function ParseDIVXTAG($DIVXTAG, $raw=false) { // structure from "IDivX" source, Form1.frm, by "Greg Frazier of Daemonic Software Group", email: gfrazier@icestorm.net, web: http://dsg.cjb.net/ // source available at http://files.divx-digest.com/download/c663efe7ef8ad2e90bf4af4d3ea6188a/on0SWN2r/edit/IDivX.zip // 'Byte Layout: '1111111111111111 @@ -1784,14 +1952,14 @@ class getid3_riff extends getid3_handler 19 => 'Sci Fi', 20 => 'Thriller', 21 => 'Western', - ); - static $DIVXTAGrating = array( - 0=>'Unrated', - 1=>'G', - 2=>'PG', - 3=>'PG-13', - 4=>'R', - 5=>'NC-17' + ), + $DIVXTAGrating = array( + 0 => 'Unrated', + 1 => 'G', + 2 => 'PG', + 3 => 'PG-13', + 4 => 'R', + 5 => 'NC-17', ); $parsed['title'] = trim(substr($DIVXTAG, 0, 32)); @@ -1805,33 +1973,47 @@ class getid3_riff extends getid3_handler $parsed['genre'] = (isset($DIVXTAGgenre[$parsed['genre_id']]) ? $DIVXTAGgenre[$parsed['genre_id']] : $parsed['genre_id']); $parsed['rating'] = (isset($DIVXTAGrating[$parsed['rating_id']]) ? $DIVXTAGrating[$parsed['rating_id']] : $parsed['rating_id']); + + if (!$raw) { + unset($parsed['genre_id'], $parsed['rating_id']); + foreach ($parsed as $key => $value) { + if (!$value === '') { + unset($parsed['key']); + } + } + } + + foreach ($parsed as $tag => $value) { + $parsed[$tag] = array($value); + } + return $parsed; } - static function RIFFwaveSNDMtagLookup($tagshortname) { + public static function waveSNDMtagLookup($tagshortname) { $begin = __LINE__; /** This is not a comment! - kwd keywords - BPM bpm - trt tracktitle - des description - gen category - fin featuredinstrument - LID longid - bex bwdescription - pub publisher - cdt cdtitle - alb library - com composer + ©kwd keywords + ©BPM bpm + ©trt tracktitle + ©des description + ©gen category + ©fin featuredinstrument + ©LID longid + ©bex bwdescription + ©pub publisher + ©cdt cdtitle + ©alb library + ©com composer */ return getid3_lib::EmbeddedLookup($tagshortname, $begin, __LINE__, __FILE__, 'riff-sndm'); } - static function RIFFwFormatTagLookup($wFormatTag) { + public static function wFormatTagLookup($wFormatTag) { $begin = __LINE__; @@ -1998,11 +2180,9 @@ class getid3_riff extends getid3_handler */ return getid3_lib::EmbeddedLookup('0x'.str_pad(strtoupper(dechex($wFormatTag)), 4, '0', STR_PAD_LEFT), $begin, __LINE__, __FILE__, 'riff-wFormatTag'); - } - - public static function RIFFfourccLookup($fourcc) { + public static function fourccLookup($fourcc) { $begin = __LINE__; @@ -2137,7 +2317,7 @@ class getid3_riff extends getid3_handler IY41 Interlaced version of Y41P (www.leadtools.com) IYU1 12 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec IEEE standard IYU2 24 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec IEEE standard - IYUV Planar YUV format (8-bpp Y plane, followed by 8-bpp 22 U and V planes) + IYUV Planar YUV format (8-bpp Y plane, followed by 8-bpp 2×2 U and V planes) i263 Intel ITU H.263 Videoconferencing (i263) I420 Intel Indeo 4 IAN Intel Indeo 4 (RDX) @@ -2396,14 +2576,11 @@ class getid3_riff extends getid3_handler return getid3_lib::EmbeddedLookup($fourcc, $begin, __LINE__, __FILE__, 'riff-fourcc'); } - - function EitherEndian2Int($byteword, $signed=false) { - if ($this->getid3->info['fileformat'] == 'riff') { + private function EitherEndian2Int($byteword, $signed=false) { + if ($this->container == 'riff') { return getid3_lib::LittleEndian2Int($byteword, $signed); } return getid3_lib::BigEndian2Int($byteword, false, $signed); } -} - -?> \ No newline at end of file +} \ No newline at end of file diff --git a/app/library/getid3/module.audio-video.swf.php b/app/library/getid3/getid3/module.audio-video.swf.php old mode 100644 new mode 100755 similarity index 94% rename from app/library/getid3/module.audio-video.swf.php rename to app/library/getid3/getid3/module.audio-video.swf.php index a3d49f95..e4f7651e --- a/app/library/getid3/module.audio-video.swf.php +++ b/app/library/getid3/getid3/module.audio-video.swf.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -16,9 +17,9 @@ class getid3_swf extends getid3_handler { - var $ReturnAllTagData = false; + public $ReturnAllTagData = false; - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'swf'; @@ -26,9 +27,9 @@ class getid3_swf extends getid3_handler // http://www.openswf.org/spec/SWFfileformat.html - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); - $SWFfileData = fread($this->getid3->fp, $info['avdataend'] - $info['avdataoffset']); // 8 + 2 + 2 + max(9) bytes NOT including Frame_Size RECT data + $SWFfileData = $this->fread($info['avdataend'] - $info['avdataoffset']); // 8 + 2 + 2 + max(9) bytes NOT including Frame_Size RECT data $info['swf']['header']['signature'] = substr($SWFfileData, 0, 3); switch ($info['swf']['header']['signature']) { @@ -137,6 +138,3 @@ class getid3_swf extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/getid3/module.audio-video.ts.php b/app/library/getid3/getid3/module.audio-video.ts.php new file mode 100755 index 00000000..b32e3ad7 --- /dev/null +++ b/app/library/getid3/getid3/module.audio-video.ts.php @@ -0,0 +1,79 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.ts.php // +// module for analyzing MPEG Transport Stream (.ts) files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_ts extends getid3_handler +{ + + public function Analyze() { + $info = &$this->getid3->info; + + $this->fseek($info['avdataoffset']); + $TSheader = $this->fread(19); + $magic = "\x47"; + if (substr($TSheader, 0, 1) != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at '.$info['avdataoffset'].', found '.getid3_lib::PrintHexBytes(substr($TSheader, 0, 1)).' instead.'; + return false; + } + $info['fileformat'] = 'ts'; + + // http://en.wikipedia.org/wiki/.ts + + $offset = 0; + $info['ts']['packet']['sync'] = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 1)); $offset += 1; + $pid_flags_raw = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 2)); $offset += 2; + $SAC_raw = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 1)); $offset += 1; + $info['ts']['packet']['flags']['transport_error_indicator'] = (bool) ($pid_flags_raw & 0x8000); // Set by demodulator if can't correct errors in the stream, to tell the demultiplexer that the packet has an uncorrectable error + $info['ts']['packet']['flags']['payload_unit_start_indicator'] = (bool) ($pid_flags_raw & 0x4000); // 1 means start of PES data or PSI otherwise zero only. + $info['ts']['packet']['flags']['transport_high_priority'] = (bool) ($pid_flags_raw & 0x2000); // 1 means higher priority than other packets with the same PID. + $info['ts']['packet']['packet_id'] = ($pid_flags_raw & 0x1FFF) >> 0; + + $info['ts']['packet']['raw']['scrambling_control'] = ($SAC_raw & 0xC0) >> 6; + $info['ts']['packet']['flags']['adaption_field_exists'] = (bool) ($SAC_raw & 0x20); + $info['ts']['packet']['flags']['payload_exists'] = (bool) ($SAC_raw & 0x10); + $info['ts']['packet']['continuity_counter'] = ($SAC_raw & 0x0F) >> 0; // Incremented only when a payload is present + $info['ts']['packet']['scrambling_control'] = $this->TSscramblingControlLookup($info['ts']['packet']['raw']['scrambling_control']); + + if ($info['ts']['packet']['flags']['adaption_field_exists']) { + $AdaptionField_raw = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 2)); $offset += 2; + $info['ts']['packet']['adaption']['field_length'] = ($AdaptionField_raw & 0xFF00) >> 8; // Number of bytes in the adaptation field immediately following this byte + $info['ts']['packet']['adaption']['flags']['discontinuity'] = (bool) ($AdaptionField_raw & 0x0080); // Set to 1 if current TS packet is in a discontinuity state with respect to either the continuity counter or the program clock reference + $info['ts']['packet']['adaption']['flags']['random_access'] = (bool) ($AdaptionField_raw & 0x0040); // Set to 1 if the PES packet in this TS packet starts a video/audio sequence + $info['ts']['packet']['adaption']['flags']['high_priority'] = (bool) ($AdaptionField_raw & 0x0020); // 1 = higher priority + $info['ts']['packet']['adaption']['flags']['pcr'] = (bool) ($AdaptionField_raw & 0x0010); // 1 means adaptation field does contain a PCR field + $info['ts']['packet']['adaption']['flags']['opcr'] = (bool) ($AdaptionField_raw & 0x0008); // 1 means adaptation field does contain an OPCR field + $info['ts']['packet']['adaption']['flags']['splice_point'] = (bool) ($AdaptionField_raw & 0x0004); // 1 means presence of splice countdown field in adaptation field + $info['ts']['packet']['adaption']['flags']['private_data'] = (bool) ($AdaptionField_raw & 0x0002); // 1 means presence of private data bytes in adaptation field + $info['ts']['packet']['adaption']['flags']['extension'] = (bool) ($AdaptionField_raw & 0x0001); // 1 means presence of adaptation field extension + if ($info['ts']['packet']['adaption']['flags']['pcr']) { + $info['ts']['packet']['adaption']['raw']['pcr'] = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 6)); $offset += 6; + } + if ($info['ts']['packet']['adaption']['flags']['opcr']) { + $info['ts']['packet']['adaption']['raw']['opcr'] = getid3_lib::BigEndian2Int(substr($TSheader, $offset, 6)); $offset += 6; + } + } + +$info['error'][] = 'MPEG Transport Stream (.ts) parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; +return false; + + } + + + public function TSscramblingControlLookup($raw) { + $TSscramblingControlLookup = array(0x00=>'not scrambled', 0x01=>'reserved', 0x02=>'scrambled, even key', 0x03=>'scrambled, odd key'); + return (isset($TSscramblingControlLookup[$raw]) ? $TSscramblingControlLookup[$raw] : 'invalid'); + } +} diff --git a/app/library/getid3/module.audio.aa.php b/app/library/getid3/getid3/module.audio.aa.php old mode 100644 new mode 100755 similarity index 70% rename from app/library/getid3/module.audio.aa.php rename to app/library/getid3/getid3/module.audio.aa.php index 39cb77c8..f5aa1015 --- a/app/library/getid3/module.audio.aa.php +++ b/app/library/getid3/getid3/module.audio.aa.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,11 +18,11 @@ class getid3_aa extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $AAheader = fread($this->getid3->fp, 8); + $this->fseek($info['avdataoffset']); + $AAheader = $this->fread(8); $magic = "\x57\x90\x75\x36"; if (substr($AAheader, 4, 4) != $magic) { @@ -31,21 +32,23 @@ class getid3_aa extends getid3_handler // shortcut $info['aa'] = array(); - $thisfile_au = &$info['aa']; + $thisfile_aa = &$info['aa']; $info['fileformat'] = 'aa'; $info['audio']['dataformat'] = 'aa'; +$info['error'][] = 'Audible Audiobook (.aa) parsing not enabled in this version of getID3() ['.$this->getid3->version().']'; +return false; $info['audio']['bitrate_mode'] = 'cbr'; // is it? - $thisfile_au['encoding'] = 'ISO-8859-1'; + $thisfile_aa['encoding'] = 'ISO-8859-1'; - $thisfile_au['filesize'] = getid3_lib::BigEndian2Int(substr($AUheader, 0, 4)); - if ($thisfile_au['filesize'] > ($info['avdataend'] - $info['avdataoffset'])) { - $info['warning'][] = 'Possible truncated file - expecting "'.$thisfile_au['filesize'].'" bytes of data, only found '.($info['avdataend'] - $info['avdataoffset']).' bytes"'; + $thisfile_aa['filesize'] = getid3_lib::BigEndian2Int(substr($AUheader, 0, 4)); + if ($thisfile_aa['filesize'] > ($info['avdataend'] - $info['avdataoffset'])) { + $info['warning'][] = 'Possible truncated file - expecting "'.$thisfile_aa['filesize'].'" bytes of data, only found '.($info['avdataend'] - $info['avdataoffset']).' bytes"'; } $info['audio']['bits_per_sample'] = 16; // is it? - $info['audio']['sample_rate'] = $thisfile_au['sample_rate']; - $info['audio']['channels'] = $thisfile_au['channels']; + $info['audio']['sample_rate'] = $thisfile_aa['sample_rate']; + $info['audio']['channels'] = $thisfile_aa['channels']; //$info['playtime_seconds'] = 0; //$info['audio']['bitrate'] = 0; @@ -54,6 +57,3 @@ class getid3_aa extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.aac.php b/app/library/getid3/getid3/module.audio.aac.php old mode 100644 new mode 100755 similarity index 95% rename from app/library/getid3/module.audio.aac.php rename to app/library/getid3/getid3/module.audio.aac.php index d573e11d..cc07085e --- a/app/library/getid3/module.audio.aac.php +++ b/app/library/getid3/getid3/module.audio.aac.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -16,10 +17,10 @@ class getid3_aac extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - if (fread($this->getid3->fp, 4) == 'ADIF') { + $this->fseek($info['avdataoffset']); + if ($this->fread(4) == 'ADIF') { $this->getAACADIFheaderFilepointer(); } else { $this->getAACADTSheaderFilepointer(); @@ -29,14 +30,14 @@ class getid3_aac extends getid3_handler - function getAACADIFheaderFilepointer() { + public function getAACADIFheaderFilepointer() { $info = &$this->getid3->info; $info['fileformat'] = 'aac'; $info['audio']['dataformat'] = 'aac'; $info['audio']['lossless'] = false; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $AACheader = fread($this->getid3->fp, 1024); + $this->fseek($info['avdataoffset']); + $AACheader = $this->fread(1024); $offset = 0; if (substr($AACheader, 0, 4) == 'ADIF') { @@ -257,10 +258,10 @@ class getid3_aac extends getid3_handler } - function getAACADTSheaderFilepointer($MaxFramesToScan=1000000, $ReturnExtendedInfo=false) { + public function getAACADTSheaderFilepointer($MaxFramesToScan=1000000, $ReturnExtendedInfo=false) { $info = &$this->getid3->info; - // based loosely on code from AACfile by Jurgen Faul + // based loosely on code from AACfile by Jurgen Faul // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html @@ -310,16 +311,16 @@ class getid3_aac extends getid3_handler // or MaxFramesToScan frames have been scanned if (!getid3_lib::intValueSupported($byteoffset)) { - $info['warning'][] = 'Unable to parse AAC file beyond '.ftell($this->getid3->fp).' (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)'; + $info['warning'][] = 'Unable to parse AAC file beyond '.$this->ftell().' (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)'; return false; } - fseek($this->getid3->fp, $byteoffset, SEEK_SET); + $this->fseek($byteoffset); // First get substring - $substring = fread($this->getid3->fp, 9); // header is 7 bytes (or 9 if CRC is present) + $substring = $this->fread(9); // header is 7 bytes (or 9 if CRC is present) $substringlength = strlen($substring); if ($substringlength != 9) { - $info['error'][] = 'Failed to read 7 bytes at offset '.(ftell($this->getid3->fp) - $substringlength).' (only read '.$substringlength.' bytes)'; + $info['error'][] = 'Failed to read 7 bytes at offset '.($this->ftell() - $substringlength).' (only read '.$substringlength.' bytes)'; return false; } // this would be easier with 64-bit math, but split it up to allow for 32-bit: @@ -329,7 +330,7 @@ class getid3_aac extends getid3_handler $info['aac']['header']['raw']['syncword'] = ($header1 & 0xFFF0) >> 4; if ($info['aac']['header']['raw']['syncword'] != 0x0FFF) { - $info['error'][] = 'Synch pattern (0x0FFF) not found at offset '.(ftell($this->getid3->fp) - $substringlength).' (found 0x0'.strtoupper(dechex($info['aac']['header']['raw']['syncword'])).' instead)'; + $info['error'][] = 'Synch pattern (0x0FFF) not found at offset '.($this->ftell() - $substringlength).' (found 0x0'.strtoupper(dechex($info['aac']['header']['raw']['syncword'])).' instead)'; //if ($info['fileformat'] == 'aac') { // return true; //} @@ -510,6 +511,3 @@ class getid3_aac extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.ac3.php b/app/library/getid3/getid3/module.audio.ac3.php old mode 100644 new mode 100755 similarity index 62% rename from app/library/getid3/module.audio.ac3.php rename to app/library/getid3/getid3/module.audio.ac3.php index ffe01746..2dc52f48 --- a/app/library/getid3/module.audio.ac3.php +++ b/app/library/getid3/getid3/module.audio.ac3.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -16,9 +17,10 @@ class getid3_ac3 extends getid3_handler { - private $AC3header = ''; + private $AC3header = array(); private $BSIoffset = 0; + const syncword = "\x0B\x77"; public function Analyze() { $info = &$this->getid3->info; @@ -32,7 +34,7 @@ class getid3_ac3 extends getid3_handler // http://www.atsc.org/standards/a_52a.pdf - $info['fileformat'] = 'ac3'; + $info['fileformat'] = 'ac3'; // An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames // Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256 @@ -45,21 +47,6 @@ class getid3_ac3 extends getid3_handler // // syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $this->AC3header['syncinfo'] = fread($this->getid3->fp, 5); - $thisfile_ac3_raw['synchinfo']['synchword'] = substr($this->AC3header['syncinfo'], 0, 2); - - $magic = "\x0B\x77"; - if ($thisfile_ac3_raw['synchinfo']['synchword'] != $magic) { - $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_ac3_raw['synchinfo']['synchword']).'"'; - unset($info['fileformat'], $info['ac3']); - return false; - } - - $info['audio']['dataformat'] = 'ac3'; - $info['audio']['bitrate_mode'] = 'cbr'; - $info['audio']['lossless'] = false; - // syncinfo() { // syncword 16 // crc1 16 @@ -67,21 +54,40 @@ class getid3_ac3 extends getid3_handler // frmsizecod 6 // } /* end of syncinfo */ - $thisfile_ac3_raw['synchinfo']['crc1'] = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], 2, 2)); - $ac3_synchinfo_fscod_frmsizecod = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], 4, 1)); + $this->fseek($info['avdataoffset']); + $this->AC3header['syncinfo'] = $this->fread(5); + + if (strpos($this->AC3header['syncinfo'], self::syncword) === 0) { + $thisfile_ac3_raw['synchinfo']['synchword'] = self::syncword; + $offset = 2; + } else { + if (!$this->isDependencyFor('matroska')) { + unset($info['fileformat'], $info['ac3']); + return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($this->AC3header['syncinfo'], 0, 2)).'"'); + } + $offset = 0; + $this->fseek(-2, SEEK_CUR); + } + + $info['audio']['dataformat'] = 'ac3'; + $info['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['lossless'] = false; + + $thisfile_ac3_raw['synchinfo']['crc1'] = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], $offset, 2)); + $ac3_synchinfo_fscod_frmsizecod = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], ($offset + 2), 1)); $thisfile_ac3_raw['synchinfo']['fscod'] = ($ac3_synchinfo_fscod_frmsizecod & 0xC0) >> 6; $thisfile_ac3_raw['synchinfo']['frmsizecod'] = ($ac3_synchinfo_fscod_frmsizecod & 0x3F); - $thisfile_ac3['sample_rate'] = $this->AC3sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']); + $thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']); if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) { $info['audio']['sample_rate'] = $thisfile_ac3['sample_rate']; } - $thisfile_ac3['frame_length'] = $this->AC3frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']); - $thisfile_ac3['bitrate'] = $this->AC3bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']); + $thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']); + $thisfile_ac3['bitrate'] = self::bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']); $info['audio']['bitrate'] = $thisfile_ac3['bitrate']; - $this->AC3header['bsi'] = getid3_lib::BigEndian2Bin(fread($this->getid3->fp, 15)); + $this->AC3header['bsi'] = getid3_lib::BigEndian2Bin($this->fread(15)); $ac3_bsi_offset = 0; $thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5); @@ -89,16 +95,16 @@ class getid3_ac3 extends getid3_handler // Decoders which can decode version 8 will thus be able to decode version numbers less than 8. // If this standard is extended by the addition of additional elements or features, a value of bsid greater than 8 will be used. // Decoders built to this version of the standard will not be able to decode versions with bsid greater than 8. - $info['error'][] = 'Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8'; - unset($thisfile_ac3); + $this->error('Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8'); + unset($info['ac3']); return false; } $thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3); $thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3); - $thisfile_ac3['service_type'] = $this->AC3serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']); - $ac3_coding_mode = $this->AC3audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']); + $thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']); + $ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']); foreach($ac3_coding_mode as $key => $value) { $thisfile_ac3[$key] = $value; } @@ -120,19 +126,19 @@ class getid3_ac3 extends getid3_handler if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) { // If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream. $thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2); - $thisfile_ac3['center_mix_level'] = $this->AC3centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']); + $thisfile_ac3['center_mix_level'] = self::centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']); } if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) { // If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream. $thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2); - $thisfile_ac3['surround_mix_level'] = $this->AC3surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']); + $thisfile_ac3['surround_mix_level'] = self::surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']); } if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) { // When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround. $thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2); - $thisfile_ac3['dolby_surround_mode'] = $this->AC3dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']); + $thisfile_ac3['dolby_surround_mode'] = self::dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']); } $thisfile_ac3_raw_bsi['lfeon'] = (bool) $this->readHeaderBSI(1); @@ -142,9 +148,9 @@ class getid3_ac3 extends getid3_handler $info['audio']['channels'] .= '.1'; } - $thisfile_ac3['channels_enabled'] = $this->AC3channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']); + $thisfile_ac3['channels_enabled'] = self::channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']); - // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 131. + // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31. // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent. $thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5); $thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB'; @@ -152,7 +158,7 @@ class getid3_ac3 extends getid3_handler $thisfile_ac3_raw_bsi['compre_flag'] = (bool) $this->readHeaderBSI(1); if ($thisfile_ac3_raw_bsi['compre_flag']) { $thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8); - $thisfile_ac3['heavy_compression'] = $this->AC3heavyCompression($thisfile_ac3_raw_bsi['compr']); + $thisfile_ac3['heavy_compression'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr']); } $thisfile_ac3_raw_bsi['langcode_flag'] = (bool) $this->readHeaderBSI(1); @@ -166,7 +172,7 @@ class getid3_ac3 extends getid3_handler $thisfile_ac3_raw_bsi['roomtyp'] = $this->readHeaderBSI(2); $thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB'; - $thisfile_ac3['room_type'] = $this->AC3roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']); + $thisfile_ac3['room_type'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']); } if ($thisfile_ac3_raw_bsi['acmod'] == 0x00) { @@ -174,7 +180,7 @@ class getid3_ac3 extends getid3_handler // are encoded into the bit stream, and are referenced as Ch1, Ch2. In this case, // a number of additional items are present in BSI or audblk to fully describe Ch2. - // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 131. + // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31. // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent. $thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5); $thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB'; @@ -182,7 +188,7 @@ class getid3_ac3 extends getid3_handler $thisfile_ac3_raw_bsi['compre_flag2'] = (bool) $this->readHeaderBSI(1); if ($thisfile_ac3_raw_bsi['compre_flag2']) { $thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8); - $thisfile_ac3['heavy_compression2'] = $this->AC3heavyCompression($thisfile_ac3_raw_bsi['compr2']); + $thisfile_ac3['heavy_compression2'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr2']); } $thisfile_ac3_raw_bsi['langcode_flag2'] = (bool) $this->readHeaderBSI(1); @@ -196,7 +202,7 @@ class getid3_ac3 extends getid3_handler $thisfile_ac3_raw_bsi['roomtyp2'] = $this->readHeaderBSI(2); $thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB'; - $thisfile_ac3['room_type2'] = $this->AC3roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']); + $thisfile_ac3['room_type2'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']); } } @@ -219,7 +225,7 @@ class getid3_ac3 extends getid3_handler if ($thisfile_ac3_raw_bsi['addbsi_flag']) { $thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6); - $this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin(fread($this->getid3->fp, $thisfile_ac3_raw_bsi['addbsi_length'])); + $this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin($this->fread($thisfile_ac3_raw_bsi['addbsi_length'])); $thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8); $this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8; @@ -235,93 +241,90 @@ class getid3_ac3 extends getid3_handler return bindec($data); } - public static function AC3sampleRateCodeLookup($fscod) { - static $AC3sampleRateCodeLookup = array( + public static function sampleRateCodeLookup($fscod) { + static $sampleRateCodeLookup = array( 0 => 48000, 1 => 44100, 2 => 32000, 3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute. ); - return (isset($AC3sampleRateCodeLookup[$fscod]) ? $AC3sampleRateCodeLookup[$fscod] : false); + return (isset($sampleRateCodeLookup[$fscod]) ? $sampleRateCodeLookup[$fscod] : false); } - public static function AC3serviceTypeLookup($bsmod, $acmod) { - static $AC3serviceTypeLookup = array(); - if (empty($AC3serviceTypeLookup)) { + public static function serviceTypeLookup($bsmod, $acmod) { + static $serviceTypeLookup = array(); + if (empty($serviceTypeLookup)) { for ($i = 0; $i <= 7; $i++) { - $AC3serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)'; - $AC3serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)'; - $AC3serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)'; - $AC3serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)'; - $AC3serviceTypeLookup[4][$i] = 'associated service: dialogue (D)'; - $AC3serviceTypeLookup[5][$i] = 'associated service: commentary (C)'; - $AC3serviceTypeLookup[6][$i] = 'associated service: emergency (E)'; + $serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)'; + $serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)'; + $serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)'; + $serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)'; + $serviceTypeLookup[4][$i] = 'associated service: dialogue (D)'; + $serviceTypeLookup[5][$i] = 'associated service: commentary (C)'; + $serviceTypeLookup[6][$i] = 'associated service: emergency (E)'; } - $AC3serviceTypeLookup[7][1] = 'associated service: voice over (VO)'; + $serviceTypeLookup[7][1] = 'associated service: voice over (VO)'; for ($i = 2; $i <= 7; $i++) { - $AC3serviceTypeLookup[7][$i] = 'main audio service: karaoke'; + $serviceTypeLookup[7][$i] = 'main audio service: karaoke'; } } - return (isset($AC3serviceTypeLookup[$bsmod][$acmod]) ? $AC3serviceTypeLookup[$bsmod][$acmod] : false); + return (isset($serviceTypeLookup[$bsmod][$acmod]) ? $serviceTypeLookup[$bsmod][$acmod] : false); } - public static function AC3audioCodingModeLookup($acmod) { - static $AC3audioCodingModeLookup = array(); - if (empty($AC3audioCodingModeLookup)) { - // array(channel configuration, # channels (not incl LFE), channel order) - $AC3audioCodingModeLookup = array ( - 0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'), - 1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'), - 2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'), - 3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'), - 4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'), - 5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'), - 6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'), - 7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR') - ); - } - return (isset($AC3audioCodingModeLookup[$acmod]) ? $AC3audioCodingModeLookup[$acmod] : false); + public static function audioCodingModeLookup($acmod) { + // array(channel configuration, # channels (not incl LFE), channel order) + static $audioCodingModeLookup = array ( + 0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'), + 1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'), + 2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'), + 3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'), + 4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'), + 5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'), + 6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'), + 7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR'), + ); + return (isset($audioCodingModeLookup[$acmod]) ? $audioCodingModeLookup[$acmod] : false); } - public static function AC3centerMixLevelLookup($cmixlev) { - static $AC3centerMixLevelLookup; - if (empty($AC3centerMixLevelLookup)) { - $AC3centerMixLevelLookup = array( - 0 => pow(2, -3.0 / 6), // 0.707 (3.0 dB) - 1 => pow(2, -4.5 / 6), // 0.595 (4.5 dB) - 2 => pow(2, -6.0 / 6), // 0.500 (6.0 dB) + public static function centerMixLevelLookup($cmixlev) { + static $centerMixLevelLookup; + if (empty($centerMixLevelLookup)) { + $centerMixLevelLookup = array( + 0 => pow(2, -3.0 / 6), // 0.707 (-3.0 dB) + 1 => pow(2, -4.5 / 6), // 0.595 (-4.5 dB) + 2 => pow(2, -6.0 / 6), // 0.500 (-6.0 dB) 3 => 'reserved' ); } - return (isset($AC3centerMixLevelLookup[$cmixlev]) ? $AC3centerMixLevelLookup[$cmixlev] : false); + return (isset($centerMixLevelLookup[$cmixlev]) ? $centerMixLevelLookup[$cmixlev] : false); } - public static function AC3surroundMixLevelLookup($surmixlev) { - static $AC3surroundMixLevelLookup; - if (empty($AC3surroundMixLevelLookup)) { - $AC3surroundMixLevelLookup = array( + public static function surroundMixLevelLookup($surmixlev) { + static $surroundMixLevelLookup; + if (empty($surroundMixLevelLookup)) { + $surroundMixLevelLookup = array( 0 => pow(2, -3.0 / 6), 1 => pow(2, -6.0 / 6), 2 => 0, 3 => 'reserved' ); } - return (isset($AC3surroundMixLevelLookup[$surmixlev]) ? $AC3surroundMixLevelLookup[$surmixlev] : false); + return (isset($surroundMixLevelLookup[$surmixlev]) ? $surroundMixLevelLookup[$surmixlev] : false); } - public static function AC3dolbySurroundModeLookup($dsurmod) { - static $AC3dolbySurroundModeLookup = array( + public static function dolbySurroundModeLookup($dsurmod) { + static $dolbySurroundModeLookup = array( 0 => 'not indicated', 1 => 'Not Dolby Surround encoded', 2 => 'Dolby Surround encoded', 3 => 'reserved' ); - return (isset($AC3dolbySurroundModeLookup[$dsurmod]) ? $AC3dolbySurroundModeLookup[$dsurmod] : false); + return (isset($dolbySurroundModeLookup[$dsurmod]) ? $dolbySurroundModeLookup[$dsurmod] : false); } - public static function AC3channelsEnabledLookup($acmod, $lfeon) { - $AC3channelsEnabledLookup = array( + public static function channelsEnabledLookup($acmod, $lfeon) { + $lookup = array( 'ch1'=>(bool) ($acmod == 0), 'ch2'=>(bool) ($acmod == 0), 'left'=>(bool) ($acmod > 1), @@ -334,25 +337,25 @@ class getid3_ac3 extends getid3_handler switch ($acmod) { case 4: case 5: - $AC3channelsEnabledLookup['surround_mono'] = true; + $lookup['surround_mono'] = true; break; case 6: case 7: - $AC3channelsEnabledLookup['surround_left'] = true; - $AC3channelsEnabledLookup['surround_right'] = true; + $lookup['surround_left'] = true; + $lookup['surround_right'] = true; break; } - return $AC3channelsEnabledLookup; + return $lookup; } - public static function AC3heavyCompression($compre) { + public static function heavyCompression($compre) { // The first four bits indicate gain changes in 6.02dB increments which can be // implemented with an arithmetic shift operation. The following four bits // indicate linear gain changes, and require a 5-bit multiply. // We will represent the two 4-bit fields of compr as follows: // X0 X1 X2 X3 . Y4 Y5 Y6 Y7 // The meaning of the X values is most simply described by considering X to represent a 4-bit - // signed integer with values from 8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The + // signed integer with values from -8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The // following table shows this in detail. // Meaning of 4 msb of compr @@ -365,13 +368,13 @@ class getid3_ac3 extends getid3_handler // 1 +12.04 dB // 0 +6.02 dB // -1 0 dB - // -2 6.02 dB - // -3 12.04 dB - // -4 18.06 dB - // -5 24.08 dB - // -6 30.10 dB - // -7 36.12 dB - // -8 42.14 dB + // -2 -6.02 dB + // -3 -12.04 dB + // -4 -18.06 dB + // -5 -24.08 dB + // -6 -30.10 dB + // -7 -36.12 dB + // -8 -42.14 dB $fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT); if ($fourbit{0} == '1') { @@ -381,37 +384,37 @@ class getid3_ac3 extends getid3_handler } $log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2); - // The value of Y is a linear representation of a gain change of up to 6 dB. Y is considered to + // The value of Y is a linear representation of a gain change of up to -6 dB. Y is considered to // be an unsigned fractional integer, with a leading value of 1, or: 0.1 Y4 Y5 Y6 Y7 (base 2). Y can // represent values between 0.111112 (or 31/32) and 0.100002 (or 1/2). Thus, Y can represent gain - // changes from 0.28 dB to 6.02 dB. + // changes from -0.28 dB to -6.02 dB. $lin_gain = (16 + ($compre & 0x0F)) / 32; // The combination of X and Y values allows compr to indicate gain changes from - // 48.16 0.28 = +47.89 dB, to - // 42.14 6.02 = 48.16 dB. + // 48.16 - 0.28 = +47.89 dB, to + // -42.14 - 6.02 = -48.16 dB. return $log_gain - $lin_gain; } - public static function AC3roomTypeLookup($roomtyp) { - static $AC3roomTypeLookup = array( + public static function roomTypeLookup($roomtyp) { + static $roomTypeLookup = array( 0 => 'not indicated', 1 => 'large room, X curve monitor', 2 => 'small room, flat monitor', 3 => 'reserved' ); - return (isset($AC3roomTypeLookup[$roomtyp]) ? $AC3roomTypeLookup[$roomtyp] : false); + return (isset($roomTypeLookup[$roomtyp]) ? $roomTypeLookup[$roomtyp] : false); } - public static function AC3frameSizeLookup($frmsizecod, $fscod) { + public static function frameSizeLookup($frmsizecod, $fscod) { $padding = (bool) ($frmsizecod % 2); $framesizeid = floor($frmsizecod / 2); - static $AC3frameSizeLookup = array(); - if (empty($AC3frameSizeLookup)) { - $AC3frameSizeLookup = array ( + static $frameSizeLookup = array(); + if (empty($frameSizeLookup)) { + $frameSizeLookup = array ( 0 => array(128, 138, 192), 1 => array(40, 160, 174, 240), 2 => array(48, 192, 208, 288), @@ -435,15 +438,15 @@ class getid3_ac3 extends getid3_handler } if (($fscod == 1) && $padding) { // frame lengths are padded by 1 word (16 bits) at 44100 - $AC3frameSizeLookup[$frmsizecod] += 2; + $frameSizeLookup[$frmsizecod] += 2; } - return (isset($AC3frameSizeLookup[$framesizeid][$fscod]) ? $AC3frameSizeLookup[$framesizeid][$fscod] : false); + return (isset($frameSizeLookup[$framesizeid][$fscod]) ? $frameSizeLookup[$framesizeid][$fscod] : false); } - public static function AC3bitrateLookup($frmsizecod) { + public static function bitrateLookup($frmsizecod) { $framesizeid = floor($frmsizecod / 2); - static $AC3bitrateLookup = array( + static $bitrateLookup = array( 0 => 32000, 1 => 40000, 2 => 48000, @@ -464,10 +467,8 @@ class getid3_ac3 extends getid3_handler 17 => 576000, 18 => 640000 ); - return (isset($AC3bitrateLookup[$framesizeid]) ? $AC3bitrateLookup[$framesizeid] : false); + return (isset($bitrateLookup[$framesizeid]) ? $bitrateLookup[$framesizeid] : false); } } - -?> \ No newline at end of file diff --git a/app/library/getid3/getid3/module.audio.amr.php b/app/library/getid3/getid3/module.audio.amr.php new file mode 100755 index 00000000..571061ee --- /dev/null +++ b/app/library/getid3/getid3/module.audio.amr.php @@ -0,0 +1,97 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.aa.php // +// module for analyzing Audible Audiobook files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_amr extends getid3_handler +{ + + public function Analyze() { + $info = &$this->getid3->info; + + $this->fseek($info['avdataoffset']); + $AMRheader = $this->fread(6); + + $magic = '#!AMR'."\x0A"; + if (substr($AMRheader, 0, 6) != $magic) { + $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($AMRheader, 0, 6)).'"'; + return false; + } + + // shortcut + $info['amr'] = array(); + $thisfile_amr = &$info['amr']; + + $info['fileformat'] = 'amr'; + $info['audio']['dataformat'] = 'amr'; + $info['audio']['bitrate_mode'] = 'vbr'; // within a small predefined range: 4.75kbps to 12.2kbps + $info['audio']['bits_per_sample'] = 13; // http://en.wikipedia.org/wiki/Adaptive_Multi-Rate_audio_codec: "Sampling frequency 8 kHz/13-bit (160 samples for 20 ms frames), filtered to 200–3400 Hz" + $info['audio']['sample_rate'] = 8000; // http://en.wikipedia.org/wiki/Adaptive_Multi-Rate_audio_codec: "Sampling frequency 8 kHz/13-bit (160 samples for 20 ms frames), filtered to 200–3400 Hz" + $info['audio']['channels'] = 1; + $thisfile_amr['frame_mode_count'] = array(0=>0, 1=>0, 2=>0, 3=>0, 4=>0, 5=>0, 6=>0, 7=>0); + + $buffer = ''; + do { + if ((strlen($buffer) < $this->getid3->fread_buffer_size()) && !feof($this->getid3->fp)) { + $buffer .= $this->fread($this->getid3->fread_buffer_size() * 2); + } + $AMR_frame_header = ord(substr($buffer, 0, 1)); + $codec_mode_request = ($AMR_frame_header & 0x78) >> 3; // The 2nd bit through 5th bit (counting the most significant bit as the first bit) comprise the CMR (Codec Mode Request), values 0-7 being valid for AMR. The top bit of the CMR can actually be ignored, though it is used when AMR forms RTP payloads. The lower 3-bits of the header are reserved and are not used. Viewing the header from most significant bit to least significant bit, the encoding is XCCCCXXX, where Xs are reserved (typically 0) and the Cs are the CMR. + if ($codec_mode_request > 7) { + $info['error'][] = ''; + break; + } + $thisfile_amr['frame_mode_count'][$codec_mode_request]++; + $buffer = substr($buffer, $this->amr_mode_bytes_per_frame($codec_mode_request)); + } while (strlen($buffer) > 0); + + $info['playtime_seconds'] = array_sum($thisfile_amr['frame_mode_count']) * 0.020; // each frame contain 160 samples and is 20 milliseconds long + $info['audio']['bitrate'] = (8 * ($info['avdataend'] - $info['avdataoffset'])) / $info['playtime_seconds']; // bitrate could be calculated from average bitrate by distributation of frame types. That would give effective audio bitrate, this gives overall file bitrate which will be a little bit higher since every frame will waste 8 bits for header, plus a few bits for octet padding + $info['bitrate'] = $info['audio']['bitrate']; + + return true; + } + + + public function amr_mode_bitrate($key) { + static $amr_mode_bitrate = array( + 0 => 4750, + 1 => 5150, + 2 => 5900, + 3 => 6700, + 4 => 7400, + 5 => 7950, + 6 => 10200, + 7 => 12200, + ); + return (isset($amr_mode_bitrate[$key]) ? $amr_mode_bitrate[$key] : false); + } + + public function amr_mode_bytes_per_frame($key) { + static $amr_mode_bitrate = array( + 0 => 13, // 1-byte frame header + 95 bits [padded to: 12 bytes] audio data + 1 => 14, // 1-byte frame header + 103 bits [padded to: 13 bytes] audio data + 2 => 16, // 1-byte frame header + 118 bits [padded to: 15 bytes] audio data + 3 => 18, // 1-byte frame header + 134 bits [padded to: 17 bytes] audio data + 4 => 20, // 1-byte frame header + 148 bits [padded to: 19 bytes] audio data + 5 => 21, // 1-byte frame header + 159 bits [padded to: 20 bytes] audio data + 6 => 27, // 1-byte frame header + 204 bits [padded to: 26 bytes] audio data + 7 => 32, // 1-byte frame header + 244 bits [padded to: 31 bytes] audio data + ); + return (isset($amr_mode_bitrate[$key]) ? $amr_mode_bitrate[$key] : false); + } + + +} diff --git a/app/library/getid3/module.audio.au.php b/app/library/getid3/getid3/module.audio.au.php old mode 100644 new mode 100755 similarity index 92% rename from app/library/getid3/module.audio.au.php rename to app/library/getid3/getid3/module.audio.au.php index a1094dbc..26547962 --- a/app/library/getid3/module.audio.au.php +++ b/app/library/getid3/getid3/module.audio.au.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,11 +18,11 @@ class getid3_au extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $AUheader = fread($this->getid3->fp, 8); + $this->fseek($info['avdataoffset']); + $AUheader = $this->fread(8); $magic = '.snd'; if (substr($AUheader, 0, 4) != $magic) { @@ -39,7 +40,7 @@ class getid3_au extends getid3_handler $thisfile_au['encoding'] = 'ISO-8859-1'; $thisfile_au['header_length'] = getid3_lib::BigEndian2Int(substr($AUheader, 4, 4)); - $AUheader .= fread($this->getid3->fp, $thisfile_au['header_length'] - 8); + $AUheader .= $this->fread($thisfile_au['header_length'] - 8); $info['avdataoffset'] += $thisfile_au['header_length']; $thisfile_au['data_size'] = getid3_lib::BigEndian2Int(substr($AUheader, 8, 4)); @@ -69,7 +70,7 @@ class getid3_au extends getid3_handler return true; } - function AUdataFormatNameLookup($id) { + public function AUdataFormatNameLookup($id) { static $AUdataFormatNameLookup = array( 0 => 'unspecified format', 1 => '8-bit mu-law', @@ -103,7 +104,7 @@ class getid3_au extends getid3_handler return (isset($AUdataFormatNameLookup[$id]) ? $AUdataFormatNameLookup[$id] : false); } - function AUdataFormatBitsPerSampleLookup($id) { + public function AUdataFormatBitsPerSampleLookup($id) { static $AUdataFormatBitsPerSampleLookup = array( 1 => 8, 2 => 8, @@ -131,7 +132,7 @@ class getid3_au extends getid3_handler return (isset($AUdataFormatBitsPerSampleLookup[$id]) ? $AUdataFormatBitsPerSampleLookup[$id] : false); } - function AUdataFormatUsedBitsPerSampleLookup($id) { + public function AUdataFormatUsedBitsPerSampleLookup($id) { static $AUdataFormatUsedBitsPerSampleLookup = array( 1 => 8, 2 => 8, @@ -160,6 +161,3 @@ class getid3_au extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.avr.php b/app/library/getid3/getid3/module.audio.avr.php old mode 100644 new mode 100755 similarity index 97% rename from app/library/getid3/module.audio.avr.php rename to app/library/getid3/getid3/module.audio.avr.php index 9c6d6650..16c9a20d --- a/app/library/getid3/module.audio.avr.php +++ b/app/library/getid3/getid3/module.audio.avr.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,7 +18,7 @@ class getid3_avr extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; // http://cui.unige.ch/OSG/info/AudioFormats/ap11.html @@ -62,8 +63,8 @@ class getid3_avr extends getid3_handler $info['fileformat'] = 'avr'; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $AVRheader = fread($this->getid3->fp, 128); + $this->fseek($info['avdataoffset']); + $AVRheader = $this->fread(128); $info['avr']['raw']['magic'] = substr($AVRheader, 0, 4); $magic = '2BIT'; @@ -122,6 +123,3 @@ class getid3_avr extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.bonk.php b/app/library/getid3/getid3/module.audio.bonk.php old mode 100644 new mode 100755 similarity index 87% rename from app/library/getid3/module.audio.bonk.php rename to app/library/getid3/getid3/module.audio.bonk.php index 9f5187e3..661421a6 --- a/app/library/getid3/module.audio.bonk.php +++ b/app/library/getid3/getid3/module.audio.bonk.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -16,7 +17,7 @@ class getid3_bonk extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; // shortcut @@ -33,13 +34,13 @@ class getid3_bonk extends getid3_handler } else { // scan-from-end method, for v0.6 and higher - fseek($this->getid3->fp, $thisfile_bonk['dataend'] - 8, SEEK_SET); - $PossibleBonkTag = fread($this->getid3->fp, 8); + $this->fseek($thisfile_bonk['dataend'] - 8); + $PossibleBonkTag = $this->fread(8); while ($this->BonkIsValidTagName(substr($PossibleBonkTag, 4, 4), true)) { $BonkTagSize = getid3_lib::LittleEndian2Int(substr($PossibleBonkTag, 0, 4)); - fseek($this->getid3->fp, 0 - $BonkTagSize, SEEK_CUR); - $BonkTagOffset = ftell($this->getid3->fp); - $TagHeaderTest = fread($this->getid3->fp, 5); + $this->fseek(0 - $BonkTagSize, SEEK_CUR); + $BonkTagOffset = $this->ftell(); + $TagHeaderTest = $this->fread(5); if (($TagHeaderTest{0} != "\x00") || (substr($PossibleBonkTag, 4, 4) != strtolower(substr($PossibleBonkTag, 4, 4)))) { $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes("\x00".strtoupper(substr($PossibleBonkTag, 4, 4))).'" at offset '.$BonkTagOffset.', found "'.getid3_lib::PrintHexBytes($TagHeaderTest).'"'; return false; @@ -56,17 +57,17 @@ class getid3_bonk extends getid3_handler } return true; } - fseek($this->getid3->fp, $NextTagEndOffset, SEEK_SET); - $PossibleBonkTag = fread($this->getid3->fp, 8); + $this->fseek($NextTagEndOffset); + $PossibleBonkTag = $this->fread(8); } } // seek-from-beginning method for v0.4 and v0.5 if (empty($thisfile_bonk['BONK'])) { - fseek($this->getid3->fp, $thisfile_bonk['dataoffset'], SEEK_SET); + $this->fseek($thisfile_bonk['dataoffset']); do { - $TagHeaderTest = fread($this->getid3->fp, 5); + $TagHeaderTest = $this->fread(5); switch ($TagHeaderTest) { case "\x00".'BONK': if (empty($info['audio']['encoder'])) { @@ -91,8 +92,8 @@ class getid3_bonk extends getid3_handler // parse META block for v0.6 - v0.8 if (empty($thisfile_bonk['INFO']) && isset($thisfile_bonk['META']['tags']['info'])) { - fseek($this->getid3->fp, $thisfile_bonk['META']['tags']['info'], SEEK_SET); - $TagHeaderTest = fread($this->getid3->fp, 5); + $this->fseek($thisfile_bonk['META']['tags']['info']); + $TagHeaderTest = $this->fread(5); if ($TagHeaderTest == "\x00".'INFO') { $info['audio']['encoder'] = 'Extended BONK v0.6 - v0.8'; @@ -113,14 +114,14 @@ class getid3_bonk extends getid3_handler } - function HandleBonkTags($BonkTagName) { + public function HandleBonkTags($BonkTagName) { $info = &$this->getid3->info; switch ($BonkTagName) { case 'BONK': // shortcut $thisfile_bonk_BONK = &$info['bonk']['BONK']; - $BonkData = "\x00".'BONK'.fread($this->getid3->fp, 17); + $BonkData = "\x00".'BONK'.$this->fread(17); $thisfile_bonk_BONK['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); $thisfile_bonk_BONK['number_samples'] = getid3_lib::LittleEndian2Int(substr($BonkData, 6, 4)); $thisfile_bonk_BONK['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BonkData, 10, 4)); @@ -154,18 +155,18 @@ class getid3_bonk extends getid3_handler // shortcut $thisfile_bonk_INFO = &$info['bonk']['INFO']; - $thisfile_bonk_INFO['version'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1)); + $thisfile_bonk_INFO['version'] = getid3_lib::LittleEndian2Int($this->fread(1)); $thisfile_bonk_INFO['entries_count'] = 0; - $NextInfoDataPair = fread($this->getid3->fp, 5); + $NextInfoDataPair = $this->fread(5); if (!$this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { while (!feof($this->getid3->fp)) { //$CurrentSeekInfo['offset'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 0, 4)); //$CurrentSeekInfo['nextbit'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 4, 1)); //$thisfile_bonk_INFO[] = $CurrentSeekInfo; - $NextInfoDataPair = fread($this->getid3->fp, 5); + $NextInfoDataPair = $this->fread(5); if ($this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { - fseek($this->getid3->fp, -5, SEEK_CUR); + $this->fseek(-5, SEEK_CUR); break; } $thisfile_bonk_INFO['entries_count']++; @@ -174,10 +175,10 @@ class getid3_bonk extends getid3_handler break; case 'META': - $BonkData = "\x00".'META'.fread($this->getid3->fp, $info['bonk']['META']['size'] - 5); + $BonkData = "\x00".'META'.$this->fread($info['bonk']['META']['size'] - 5); $info['bonk']['META']['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); - $MetaTagEntries = floor(((strlen($BonkData) - 8) - 6) / 8); // BonkData - xxxxmeta - META + $MetaTagEntries = floor(((strlen($BonkData) - 8) - 6) / 8); // BonkData - xxxxmeta - ØMETA $offset = 6; for ($i = 0; $i < $MetaTagEntries; $i++) { $MetaEntryTagName = substr($BonkData, $offset, 4); @@ -212,7 +213,7 @@ class getid3_bonk extends getid3_handler } } - static function BonkIsValidTagName($PossibleBonkTag, $ignorecase=false) { + public static function BonkIsValidTagName($PossibleBonkTag, $ignorecase=false) { static $BonkIsValidTagName = array('BONK', 'INFO', ' ID3', 'META'); foreach ($BonkIsValidTagName as $validtagname) { if ($validtagname == $PossibleBonkTag) { @@ -225,6 +226,3 @@ class getid3_bonk extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/getid3/module.audio.dss.php b/app/library/getid3/getid3/module.audio.dss.php new file mode 100755 index 00000000..2a5b1a73 --- /dev/null +++ b/app/library/getid3/getid3/module.audio.dss.php @@ -0,0 +1,93 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.dss.php // +// module for analyzing Digital Speech Standard (DSS) files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_dss extends getid3_handler +{ + + public function Analyze() { + $info = &$this->getid3->info; + + $this->fseek($info['avdataoffset']); + $DSSheader = $this->fread(1540); + + if (!preg_match('#^(\x02|\x03)ds[s2]#', $DSSheader)) { + $info['error'][] = 'Expecting "[02-03] 64 73 [73|32]" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSSheader, 0, 4)).'"'; + return false; + } + + // some structure information taken from http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm + $info['encoding'] = 'ISO-8859-1'; // not certain, but assumed + $info['dss'] = array(); + + $info['fileformat'] = 'dss'; + $info['mime_type'] = 'audio/x-'.substr($DSSheader, 1, 3); // "audio/x-dss" or "audio/x-ds2" + $info['audio']['dataformat'] = substr($DSSheader, 1, 3); // "dss" or "ds2" + $info['audio']['bitrate_mode'] = 'cbr'; + + $info['dss']['version'] = ord(substr($DSSheader, 0, 1)); + $info['dss']['hardware'] = trim(substr($DSSheader, 12, 16)); // identification string for hardware used to create the file, e.g. "DPM 9600", "DS2400" + $info['dss']['unknown1'] = getid3_lib::LittleEndian2Int(substr($DSSheader, 28, 4)); + // 32-37 = "FE FF FE FF F7 FF" in all the sample files I've seen + $info['dss']['date_create'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 38, 12)); + $info['dss']['date_complete'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 50, 12)); + $info['dss']['playtime_sec'] = intval((substr($DSSheader, 62, 2) * 3600) + (substr($DSSheader, 64, 2) * 60) + substr($DSSheader, 66, 2)); // approximate file playtime in HHMMSS + $info['dss']['playtime_ms'] = getid3_lib::LittleEndian2Int(substr($DSSheader, 512, 4)); // exact file playtime in milliseconds. Has also been observed at offset 530 in one sample file, with something else (unknown) at offset 512 + $info['dss']['priority'] = ord(substr($DSSheader, 793, 1)); + $info['dss']['comments'] = trim(substr($DSSheader, 798, 100)); + $info['dss']['sample_rate_index'] = ord(substr($DSSheader, 1538, 1)); // this isn't certain, this may or may not be where the sample rate info is stored, but it seems consistent on my small selection of sample files + + $info['audio']['bits_per_sample'] = 16; // maybe, maybe not -- most compressed audio formats don't have a fixed bits-per-sample value, but this is a reasonable approximation + $info['audio']['sample_rate'] = $this->DSSsampleRateLookup($info['dss']['sample_rate_index']); + $info['audio']['channels'] = 1; + + $info['playtime_seconds'] = $info['dss']['playtime_ms'] / 1000; + if (floor($info['dss']['playtime_ms'] / 1000) != $info['dss']['playtime_sec']) { + // *should* just be playtime_ms / 1000 but at least one sample file has playtime_ms at offset 530 instead of offset 512, so safety check + $info['playtime_seconds'] = $info['dss']['playtime_sec']; + $this->getid3->warning('playtime_ms ('.number_format($info['dss']['playtime_ms'] / 1000, 3).') does not match playtime_sec ('.number_format($info['dss']['playtime_sec']).') - using playtime_sec value'); + } + $info['audio']['bitrate'] = ($info['filesize'] * 8) / $info['playtime_seconds']; + + return true; + } + + public function DSSdateStringToUnixDate($datestring) { + $y = substr($datestring, 0, 2); + $m = substr($datestring, 2, 2); + $d = substr($datestring, 4, 2); + $h = substr($datestring, 6, 2); + $i = substr($datestring, 8, 2); + $s = substr($datestring, 10, 2); + $y += (($y < 95) ? 2000 : 1900); + return mktime($h, $i, $s, $m, $d, $y); + } + + public function DSSsampleRateLookup($sample_rate_index) { + static $dssSampleRateLookup = array( + 0x0A => 16000, + 0x0C => 11025, + 0x0D => 12000, + 0x15 => 8000, + ); + if (!array_key_exists($sample_rate_index, $dssSampleRateLookup)) { + $this->getid3->warning('unknown sample_rate_index: '.$sample_rate_index); + return false; + } + return $dssSampleRateLookup[$sample_rate_index]; + } + +} diff --git a/app/library/getid3/module.audio.dts.php b/app/library/getid3/getid3/module.audio.dts.php old mode 100644 new mode 100755 similarity index 53% rename from app/library/getid3/module.audio.dts.php rename to app/library/getid3/getid3/module.audio.dts.php index 8102ba8b..bdc78f0b --- a/app/library/getid3/module.audio.dts.php +++ b/app/library/getid3/getid3/module.audio.dts.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -14,70 +15,112 @@ ///////////////////////////////////////////////////////////////// +/** +* @tutorial http://wiki.multimedia.cx/index.php?title=DTS +*/ class getid3_dts extends getid3_handler { + /** + * Default DTS syncword used in native .cpt or .dts formats + */ + const syncword = "\x7F\xFE\x80\x01"; + + private $readBinDataOffset = 0; + + /** + * Possible syncwords indicating bitstream encoding + */ + public static $syncwords = array( + 0 => "\x7F\xFE\x80\x01", // raw big-endian + 1 => "\xFE\x7F\x01\x80", // raw little-endian + 2 => "\x1F\xFF\xE8\x00", // 14-bit big-endian + 3 => "\xFF\x1F\x00\xE8"); // 14-bit little-endian public function Analyze() { $info = &$this->getid3->info; - - // Specs taken from "DTS Coherent Acoustics;Core and Extensions, ETSI TS 102 114 V1.2.1 (2002-12)" - // (http://pda.etsi.org/pda/queryform.asp) - // With thanks to Gambit http://mac.sourceforge.net/atl/ - $info['fileformat'] = 'dts'; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $DTSheader = fread($this->getid3->fp, 16); - $info['dts']['raw']['magic'] = substr($DTSheader, 0, 4); + $this->fseek($info['avdataoffset']); + $DTSheader = $this->fread(20); // we only need 2 words magic + 6 words frame header, but these words may be normal 16-bit words OR 14-bit words with 2 highest bits set to zero, so 8 words can be either 8*16/8 = 16 bytes OR 8*16*(16/14)/8 = 18.3 bytes + + // check syncword + $sync = substr($DTSheader, 0, 4); + if (($encoding = array_search($sync, self::$syncwords)) !== false) { + + $info['dts']['raw']['magic'] = $sync; + $this->readBinDataOffset = 32; + + } elseif ($this->isDependencyFor('matroska')) { + + // Matroska contains DTS without syncword encoded as raw big-endian format + $encoding = 0; + $this->readBinDataOffset = 0; + + } else { + + unset($info['fileformat']); + return $this->error('Expecting "'.implode('| ', array_map('getid3_lib::PrintHexBytes', self::$syncwords)).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($sync).'"'); - $magic = "\x7F\xFE\x80\x01"; - if ($info['dts']['raw']['magic'] != $magic) { - $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['dts']['raw']['magic']).'"'; - unset($info['fileformat'], $info['dts']); - return false; } - $fhBS = getid3_lib::BigEndian2Bin(substr($DTSheader, 4, 12)); - $bsOffset = 0; - $info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, $bsOffset, 5); - $info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, $bsOffset, 7); - $info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, $bsOffset, 14); - $info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, $bsOffset, 6); - $info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, $bsOffset, 4); - $info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, $bsOffset, 5); - $info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, $bsOffset, 3); - $info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, $bsOffset, 2); - $info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); + // decode header + $fhBS = ''; + for ($word_offset = 0; $word_offset <= strlen($DTSheader); $word_offset += 2) { + switch ($encoding) { + case 0: // raw big-endian + $fhBS .= getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) ); + break; + case 1: // raw little-endian + $fhBS .= getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))); + break; + case 2: // 14-bit big-endian + $fhBS .= substr(getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) ), 2, 14); + break; + case 3: // 14-bit little-endian + $fhBS .= substr(getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))), 2, 14); + break; + } + } + + $info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, 1); + $info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, 5); + $info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, 7); + $info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, 14); + $info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, 6); + $info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, 4); + $info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, 5); + $info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, 3); + $info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, 2); + $info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, 1); if ($info['dts']['flags']['crc_present']) { - $info['dts']['raw']['crc16'] = $this->readBinData($fhBS, $bsOffset, 16); + $info['dts']['raw']['crc16'] = $this->readBinData($fhBS, 16); } - $info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, $bsOffset, 4); - $info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, $bsOffset, 2); - $info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, $bsOffset, 2); - $info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1); - $info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, $bsOffset, 4); + $info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, 4); + $info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, 2); + $info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, 2); + $info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, 4); - $info['dts']['bitrate'] = self::DTSbitrateLookup($info['dts']['raw']['bitrate']); - $info['dts']['bits_per_sample'] = self::DTSbitPerSampleLookup($info['dts']['raw']['bits_per_sample']); - $info['dts']['sample_rate'] = self::DTSsampleRateLookup($info['dts']['raw']['sample_frequency']); - $info['dts']['dialog_normalization'] = self::DTSdialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']); + $info['dts']['bitrate'] = self::bitrateLookup($info['dts']['raw']['bitrate']); + $info['dts']['bits_per_sample'] = self::bitPerSampleLookup($info['dts']['raw']['bits_per_sample']); + $info['dts']['sample_rate'] = self::sampleRateLookup($info['dts']['raw']['sample_frequency']); + $info['dts']['dialog_normalization'] = self::dialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']); $info['dts']['flags']['lossless'] = (($info['dts']['raw']['bitrate'] == 31) ? true : false); $info['dts']['bitrate_mode'] = (($info['dts']['raw']['bitrate'] == 30) ? 'vbr' : 'cbr'); - $info['dts']['channels'] = self::DTSnumChannelsLookup($info['dts']['raw']['channel_arrangement']); - $info['dts']['channel_arrangement'] = self::DTSchannelArrangementLookup($info['dts']['raw']['channel_arrangement']); + $info['dts']['channels'] = self::numChannelsLookup($info['dts']['raw']['channel_arrangement']); + $info['dts']['channel_arrangement'] = self::channelArrangementLookup($info['dts']['raw']['channel_arrangement']); $info['audio']['dataformat'] = 'dts'; $info['audio']['lossless'] = $info['dts']['flags']['lossless']; @@ -86,21 +129,25 @@ class getid3_dts extends getid3_handler $info['audio']['sample_rate'] = $info['dts']['sample_rate']; $info['audio']['channels'] = $info['dts']['channels']; $info['audio']['bitrate'] = $info['dts']['bitrate']; - if (isset($info['avdataend'])) { + if (isset($info['avdataend']) && !empty($info['dts']['bitrate']) && is_numeric($info['dts']['bitrate'])) { $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8); + if (($encoding == 2) || ($encoding == 3)) { + // 14-bit data packed into 16-bit words, so the playtime is wrong because only (14/16) of the bytes in the data portion of the file are used at the specified bitrate + $info['playtime_seconds'] *= (14 / 16); + } } - return true; } - private function readBinData($bin, &$offset, $length) { - $data = substr($bin, $offset, $length); - $offset += $length; + private function readBinData($bin, $length) { + $data = substr($bin, $this->readBinDataOffset, $length); + $this->readBinDataOffset += $length; + return bindec($data); } - private static function DTSbitrateLookup($index) { - $DTSbitrateLookup = array( + public static function bitrateLookup($index) { + static $lookup = array( 0 => 32000, 1 => 56000, 2 => 64000, @@ -132,13 +179,13 @@ class getid3_dts extends getid3_handler 28 => 3840000, 29 => 'open', 30 => 'variable', - 31 => 'lossless' + 31 => 'lossless', ); - return (isset($DTSbitrateLookup[$index]) ? $DTSbitrateLookup[$index] : false); + return (isset($lookup[$index]) ? $lookup[$index] : false); } - private static function DTSsampleRateLookup($index) { - $DTSsampleRateLookup = array( + public static function sampleRateLookup($index) { + static $lookup = array( 0 => 'invalid', 1 => 8000, 2 => 16000, @@ -154,22 +201,22 @@ class getid3_dts extends getid3_handler 12 => 24000, 13 => 48000, 14 => 'invalid', - 15 => 'invalid' + 15 => 'invalid', ); - return (isset($DTSsampleRateLookup[$index]) ? $DTSsampleRateLookup[$index] : false); + return (isset($lookup[$index]) ? $lookup[$index] : false); } - private static function DTSbitPerSampleLookup($index) { - $DTSbitPerSampleLookup = array( + public static function bitPerSampleLookup($index) { + static $lookup = array( 0 => 16, 1 => 20, 2 => 24, 3 => 24, ); - return (isset($DTSbitPerSampleLookup[$index]) ? $DTSbitPerSampleLookup[$index] : false); + return (isset($lookup[$index]) ? $lookup[$index] : false); } - private static function DTSnumChannelsLookup($index) { + public static function numChannelsLookup($index) { switch ($index) { case 0: return 1; @@ -207,8 +254,8 @@ class getid3_dts extends getid3_handler return false; } - private static function DTSchannelArrangementLookup($index) { - $DTSchannelArrangementLookup = array( + public static function channelArrangementLookup($index) { + static $lookup = array( 0 => 'A', 1 => 'A + B (dual mono)', 2 => 'L + R (stereo)', @@ -226,10 +273,10 @@ class getid3_dts extends getid3_handler 14 => 'CL + CR + L + R + SL1 + SL2 + SR1 + SR2', 15 => 'CL + C+ CR + L + R + SL + S + SR', ); - return (isset($DTSchannelArrangementLookup[$index]) ? $DTSchannelArrangementLookup[$index] : 'user-defined'); + return (isset($lookup[$index]) ? $lookup[$index] : 'user-defined'); } - private static function DTSdialogNormalization($index, $version) { + public static function dialogNormalization($index, $version) { switch ($version) { case 7: return 0 - $index; @@ -242,5 +289,3 @@ class getid3_dts extends getid3_handler } } - -?> \ No newline at end of file diff --git a/app/library/getid3/getid3/module.audio.flac.php b/app/library/getid3/getid3/module.audio.flac.php new file mode 100755 index 00000000..348cce32 --- /dev/null +++ b/app/library/getid3/getid3/module.audio.flac.php @@ -0,0 +1,453 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.flac.php // +// module for analyzing FLAC and OggFLAC audio files // +// dependencies: module.audio.ogg.php // +// /// +///////////////////////////////////////////////////////////////// + + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); + +/** +* @tutorial http://flac.sourceforge.net/format.html +*/ +class getid3_flac extends getid3_handler +{ + const syncword = 'fLaC'; + + public function Analyze() { + $info = &$this->getid3->info; + + $this->fseek($info['avdataoffset']); + $StreamMarker = $this->fread(4); + if ($StreamMarker != self::syncword) { + return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"'); + } + $info['fileformat'] = 'flac'; + $info['audio']['dataformat'] = 'flac'; + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['lossless'] = true; + + // parse flac container + return $this->parseMETAdata(); + } + + public function parseMETAdata() { + $info = &$this->getid3->info; + do { + $BlockOffset = $this->ftell(); + $BlockHeader = $this->fread(4); + $LBFBT = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1)); + $LastBlockFlag = (bool) ($LBFBT & 0x80); + $BlockType = ($LBFBT & 0x7F); + $BlockLength = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3)); + $BlockTypeText = self::metaBlockTypeLookup($BlockType); + + if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) { + $this->error('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file'); + break; + } + if ($BlockLength < 1) { + $this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid'); + break; + } + + $info['flac'][$BlockTypeText]['raw'] = array(); + $BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw']; + + $BlockTypeText_raw['offset'] = $BlockOffset; + $BlockTypeText_raw['last_meta_block'] = $LastBlockFlag; + $BlockTypeText_raw['block_type'] = $BlockType; + $BlockTypeText_raw['block_type_text'] = $BlockTypeText; + $BlockTypeText_raw['block_length'] = $BlockLength; + if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically + $BlockTypeText_raw['block_data'] = $this->fread($BlockLength); + } + + switch ($BlockTypeText) { + case 'STREAMINFO': // 0x00 + if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'PADDING': // 0x01 + unset($info['flac']['PADDING']); // ignore + break; + + case 'APPLICATION': // 0x02 + if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'SEEKTABLE': // 0x03 + if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'VORBIS_COMMENT': // 0x04 + if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'CUESHEET': // 0x05 + if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) { + return false; + } + break; + + case 'PICTURE': // 0x06 + if (!$this->parsePICTURE()) { + return false; + } + break; + + default: + $this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset); + } + + unset($info['flac'][$BlockTypeText]['raw']); + $info['avdataoffset'] = $this->ftell(); + } + while ($LastBlockFlag === false); + + // handle tags + if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) { + $info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments']; + } + if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) { + $info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']); + } + + // copy attachments to 'comments' array if nesesary + if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) { + foreach ($info['flac']['PICTURE'] as $entry) { + if (!empty($entry['data'])) { + if (!isset($info['flac']['comments']['picture'])) { + $info['flac']['comments']['picture'] = array(); + } + $comments_picture_data = array(); + foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) { + if (isset($entry[$picture_key])) { + $comments_picture_data[$picture_key] = $entry[$picture_key]; + } + } + $info['flac']['comments']['picture'][] = $comments_picture_data; + unset($comments_picture_data); + } + } + } + + if (isset($info['flac']['STREAMINFO'])) { + if (!$this->isDependencyFor('matroska')) { + $info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset']; + } + $info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8); + if ($info['flac']['uncompressed_audio_bytes'] == 0) { + return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero'); + } + if (!empty($info['flac']['compressed_audio_bytes'])) { + $info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes']; + } + } + + // set md5_data_source - built into flac 0.5+ + if (isset($info['flac']['STREAMINFO']['audio_signature'])) { + + if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) { + $this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'); + } + else { + $info['md5_data_source'] = ''; + $md5 = $info['flac']['STREAMINFO']['audio_signature']; + for ($i = 0; $i < strlen($md5); $i++) { + $info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT); + } + if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) { + unset($info['md5_data_source']); + } + } + } + + if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) { + $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; + if ($info['audio']['bits_per_sample'] == 8) { + // special case + // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value + // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed + $this->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file'); + } + } + + return true; + } + + private function parseSTREAMINFO($BlockData) { + $info = &$this->getid3->info; + + $info['flac']['STREAMINFO'] = array(); + $streaminfo = &$info['flac']['STREAMINFO']; + + $streaminfo['min_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2)); + $streaminfo['max_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2)); + $streaminfo['min_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3)); + $streaminfo['max_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3)); + + $SRCSBSS = getid3_lib::BigEndian2Bin(substr($BlockData, 10, 8)); + $streaminfo['sample_rate'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 0, 20)); + $streaminfo['channels'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 20, 3)) + 1; + $streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1; + $streaminfo['samples_stream'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36)); + + $streaminfo['audio_signature'] = substr($BlockData, 18, 16); + + if (!empty($streaminfo['sample_rate'])) { + + $info['audio']['bitrate_mode'] = 'vbr'; + $info['audio']['sample_rate'] = $streaminfo['sample_rate']; + $info['audio']['channels'] = $streaminfo['channels']; + $info['audio']['bits_per_sample'] = $streaminfo['bits_per_sample']; + $info['playtime_seconds'] = $streaminfo['samples_stream'] / $streaminfo['sample_rate']; + if ($info['playtime_seconds'] > 0) { + if (!$this->isDependencyFor('matroska')) { + $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + } + else { + $this->warning('Cannot determine audio bitrate because total stream size is unknown'); + } + } + + } else { + return $this->error('Corrupt METAdata block: STREAMINFO'); + } + + return true; + } + + private function parseAPPLICATION($BlockData) { + $info = &$this->getid3->info; + + $ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4)); + $info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID); + $info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4); + + return true; + } + + private function parseSEEKTABLE($BlockData) { + $info = &$this->getid3->info; + + $offset = 0; + $BlockLength = strlen($BlockData); + $placeholderpattern = str_repeat("\xFF", 8); + while ($offset < $BlockLength) { + $SampleNumberString = substr($BlockData, $offset, 8); + $offset += 8; + if ($SampleNumberString == $placeholderpattern) { + + // placeholder point + getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1); + $offset += 10; + + } else { + + $SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString); + $info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); + $offset += 8; + $info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 2)); + $offset += 2; + + } + } + + return true; + } + + private function parseVORBIS_COMMENT($BlockData) { + $info = &$this->getid3->info; + + $getid3_ogg = new getid3_ogg($this->getid3); + if ($this->isDependencyFor('matroska')) { + $getid3_ogg->setStringMode($this->data_string); + } + $getid3_ogg->ParseVorbisComments(); + if (isset($info['ogg'])) { + unset($info['ogg']['comments_raw']); + $info['flac']['VORBIS_COMMENT'] = $info['ogg']; + unset($info['ogg']); + } + + unset($getid3_ogg); + + return true; + } + + private function parseCUESHEET($BlockData) { + $info = &$this->getid3->info; + $offset = 0; + $info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($BlockData, $offset, 128), "\0"); + $offset += 128; + $info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); + $offset += 8; + $info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80); + $offset += 1; + + $offset += 258; // reserved + + $info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); + $offset += 1; + + for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) { + $TrackSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); + $offset += 8; + $TrackNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); + $offset += 1; + + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset; + + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($BlockData, $offset, 12); + $offset += 12; + + $TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); + $offset += 1; + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80); + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40); + + $offset += 13; // reserved + + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); + $offset += 1; + + for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) { + $IndexSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); + $offset += 8; + $IndexNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); + $offset += 1; + + $offset += 3; // reserved + + $info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset; + } + } + + return true; + } + + /** + * Parse METADATA_BLOCK_PICTURE flac structure and extract attachment + * External usage: audio.ogg + */ + public function parsePICTURE() { + $info = &$this->getid3->info; + + $picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4)); + $picture['picturetype'] = self::pictureTypeLookup($picture['typeid']); + $picture['image_mime'] = $this->fread(getid3_lib::BigEndian2Int($this->fread(4))); + $descr_length = getid3_lib::BigEndian2Int($this->fread(4)); + if ($descr_length) { + $picture['description'] = $this->fread($descr_length); + } + $picture['image_width'] = getid3_lib::BigEndian2Int($this->fread(4)); + $picture['image_height'] = getid3_lib::BigEndian2Int($this->fread(4)); + $picture['color_depth'] = getid3_lib::BigEndian2Int($this->fread(4)); + $picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4)); + $picture['datalength'] = getid3_lib::BigEndian2Int($this->fread(4)); + + if ($picture['image_mime'] == '-->') { + $picture['data'] = $this->fread($picture['datalength']); + } else { + $picture['data'] = $this->saveAttachment( + str_replace('/', '_', $picture['picturetype']).'_'.$this->ftell(), + $this->ftell(), + $picture['datalength'], + $picture['image_mime']); + } + + $info['flac']['PICTURE'][] = $picture; + + return true; + } + + public static function metaBlockTypeLookup($blocktype) { + static $lookup = array( + 0 => 'STREAMINFO', + 1 => 'PADDING', + 2 => 'APPLICATION', + 3 => 'SEEKTABLE', + 4 => 'VORBIS_COMMENT', + 5 => 'CUESHEET', + 6 => 'PICTURE', + ); + return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved'); + } + + public static function applicationIDLookup($applicationid) { + // http://flac.sourceforge.net/id.html + static $lookup = array( + 0x41544348 => 'FlacFile', // "ATCH" + 0x42534F4C => 'beSolo', // "BSOL" + 0x42554753 => 'Bugs Player', // "BUGS" + 0x43756573 => 'GoldWave cue points (specification)', // "Cues" + 0x46696361 => 'CUE Splitter', // "Fica" + 0x46746F6C => 'flac-tools', // "Ftol" + 0x4D4F5442 => 'MOTB MetaCzar', // "MOTB" + 0x4D505345 => 'MP3 Stream Editor', // "MPSE" + 0x4D754D4C => 'MusicML: Music Metadata Language', // "MuML" + 0x52494646 => 'Sound Devices RIFF chunk storage', // "RIFF" + 0x5346464C => 'Sound Font FLAC', // "SFFL" + 0x534F4E59 => 'Sony Creative Software', // "SONY" + 0x5351455A => 'flacsqueeze', // "SQEZ" + 0x54745776 => 'TwistedWave', // "TtWv" + 0x55495453 => 'UITS Embedding tools', // "UITS" + 0x61696666 => 'FLAC AIFF chunk storage', // "aiff" + 0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks', // "imag" + 0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // "peem" + 0x71667374 => 'QFLAC Studio', // "qfst" + 0x72696666 => 'FLAC RIFF chunk storage', // "riff" + 0x74756E65 => 'TagTuner', // "tune" + 0x78626174 => 'XBAT', // "xbat" + 0x786D6364 => 'xmcd', // "xmcd" + ); + return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved'); + } + + public static function pictureTypeLookup($type_id) { + static $lookup = array ( + 0 => 'Other', + 1 => '32x32 pixels \'file icon\' (PNG only)', + 2 => 'Other file icon', + 3 => 'Cover (front)', + 4 => 'Cover (back)', + 5 => 'Leaflet page', + 6 => 'Media (e.g. label side of CD)', + 7 => 'Lead artist/lead performer/soloist', + 8 => 'Artist/performer', + 9 => 'Conductor', + 10 => 'Band/Orchestra', + 11 => 'Composer', + 12 => 'Lyricist/text writer', + 13 => 'Recording Location', + 14 => 'During recording', + 15 => 'During performance', + 16 => 'Movie/video screen capture', + 17 => 'A bright coloured fish', + 18 => 'Illustration', + 19 => 'Band/artist logotype', + 20 => 'Publisher/Studio logotype', + ); + return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved'); + } + +} diff --git a/app/library/getid3/module.audio.la.php b/app/library/getid3/getid3/module.audio.la.php old mode 100644 new mode 100755 similarity index 95% rename from app/library/getid3/module.audio.la.php rename to app/library/getid3/getid3/module.audio.la.php index 98d80a6d..181b8427 --- a/app/library/getid3/module.audio.la.php +++ b/app/library/getid3/getid3/module.audio.la.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -18,12 +19,12 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', class getid3_la extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $offset = 0; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $rawdata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); + $this->fseek($info['avdataoffset']); + $rawdata = $this->fread($this->getid3->fread_buffer_size()); switch (substr($rawdata, $offset, 4)) { case 'LA02': @@ -112,7 +113,7 @@ class getid3_la extends getid3_handler $info['la']['original_crc'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); $offset += 4; - // mikebevin*de + // mikeØbevin*de // Basically, the blocksize/seekevery are 61440/19 in La0.4 and 73728/16 // in earlier versions. A seekpoint is added every blocksize * seekevery // samples, so 4 * int(totalSamples / (blockSize * seekEvery)) should @@ -166,8 +167,8 @@ class getid3_la extends getid3_handler $RIFFdata .= substr($rawdata, 16, 24); } if ($info['la']['footerstart'] < $info['avdataend']) { - fseek($this->getid3->fp, $info['la']['footerstart'], SEEK_SET); - $RIFFdata .= fread($this->getid3->fp, $info['avdataend'] - $info['la']['footerstart']); + $this->fseek($info['la']['footerstart']); + $RIFFdata .= $this->fread($info['avdataend'] - $info['la']['footerstart']); } $RIFFdata = 'RIFF'.getid3_lib::LittleEndian2String(strlen($RIFFdata), 4, false).$RIFFdata; fwrite($RIFF_fp, $RIFFdata, strlen($RIFFdata)); @@ -193,7 +194,6 @@ class getid3_la extends getid3_handler $info['avdataend'] = $info['avdataoffset'] + $info['la']['footerstart']; $info['avdataoffset'] = $info['avdataoffset'] + $offset; - //$info['la']['codec'] = RIFFwFormatTagLookup($info['la']['raw']['format']); $info['la']['compression_ratio'] = (float) (($info['avdataend'] - $info['avdataoffset']) / $info['la']['uncompressed_size']); $info['playtime_seconds'] = (float) ($info['la']['samples'] / $info['la']['sample_rate']) / $info['la']['channels']; if ($info['playtime_seconds'] == 0) { @@ -224,6 +224,3 @@ class getid3_la extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.lpac.php b/app/library/getid3/getid3/module.audio.lpac.php old mode 100644 new mode 100755 similarity index 97% rename from app/library/getid3/module.audio.lpac.php rename to app/library/getid3/getid3/module.audio.lpac.php index 6ef0fb8a..06190b40 --- a/app/library/getid3/module.audio.lpac.php +++ b/app/library/getid3/getid3/module.audio.lpac.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -18,11 +19,11 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', class getid3_lpac extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $LPACheader = fread($this->getid3->fp, 14); + $this->fseek($info['avdataoffset']); + $LPACheader = $this->fread(14); if (substr($LPACheader, 0, 4) != 'LPAC') { $info['error'][] = 'Expected "LPAC" at offset '.$info['avdataoffset'].', found "'.$StreamMarker.'"'; return false; @@ -125,6 +126,3 @@ class getid3_lpac extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.midi.php b/app/library/getid3/getid3/module.audio.midi.php old mode 100644 new mode 100755 similarity index 95% rename from app/library/getid3/module.audio.midi.php rename to app/library/getid3/getid3/module.audio.midi.php index 7b839cf1..8878bee2 --- a/app/library/getid3/module.audio.midi.php +++ b/app/library/getid3/getid3/module.audio.midi.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -18,9 +19,9 @@ define('GETID3_MIDI_MAGIC_MTRK', 'MTrk'); // MIDI track header magic class getid3_midi extends getid3_handler { - var $scanwholefile = true; + public $scanwholefile = true; - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; // shortcut @@ -31,8 +32,8 @@ class getid3_midi extends getid3_handler $info['fileformat'] = 'midi'; $info['audio']['dataformat'] = 'midi'; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $MIDIdata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); + $this->fseek($info['avdataoffset']); + $MIDIdata = $this->fread($this->getid3->fread_buffer_size()); $offset = 0; $MIDIheaderID = substr($MIDIdata, $offset, 4); // 'MThd' if ($MIDIheaderID != GETID3_MIDI_MAGIC_MTHD) { @@ -52,14 +53,20 @@ class getid3_midi extends getid3_handler for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) { while ((strlen($MIDIdata) - $offset) < 8) { - $MIDIdata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size()); + if ($buffer = $this->fread($this->getid3->fread_buffer_size())) { + $MIDIdata .= $buffer; + } else { + $info['warning'][] = 'only processed '.($i - 1).' of '.$thisfile_midi_raw['tracks'].' tracks'; + $info['error'][] = 'Unabled to read more file data at '.$this->ftell().' (trying to seek to : '.$offset.'), was expecting at least 8 more bytes'; + return false; + } } $trackID = substr($MIDIdata, $offset, 4); $offset += 4; if ($trackID == GETID3_MIDI_MAGIC_MTRK) { $tracksize = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4)); $offset += 4; - // $thisfile_midi['tracks'][$i]['size'] = $tracksize; + //$thisfile_midi['tracks'][$i]['size'] = $tracksize; $trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize); $offset += $tracksize; } else { @@ -322,7 +329,7 @@ class getid3_midi extends getid3_handler return true; } - function GeneralMIDIinstrumentLookup($instrumentid) { + public function GeneralMIDIinstrumentLookup($instrumentid) { $begin = __LINE__; @@ -462,7 +469,7 @@ class getid3_midi extends getid3_handler return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIinstrument'); } - function GeneralMIDIpercussionLookup($instrumentid) { + public function GeneralMIDIpercussionLookup($instrumentid) { $begin = __LINE__; @@ -521,6 +528,3 @@ class getid3_midi extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.mod.php b/app/library/getid3/getid3/module.audio.mod.php old mode 100644 new mode 100755 similarity index 80% rename from app/library/getid3/module.audio.mod.php rename to app/library/getid3/getid3/module.audio.mod.php index b8817694..1af187c2 --- a/app/library/getid3/module.audio.mod.php +++ b/app/library/getid3/getid3/module.audio.mod.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,10 +18,10 @@ class getid3_mod extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $fileheader = fread($this->getid3->fp, 1088); + $this->fseek($info['avdataoffset']); + $fileheader = $this->fread(1088); if (preg_match('#^IMPM#', $fileheader)) { return $this->getITheaderFilepointer(); } elseif (preg_match('#^Extended Module#', $fileheader)) { @@ -35,10 +36,10 @@ class getid3_mod extends getid3_handler } - function getMODheaderFilepointer() { + public function getMODheaderFilepointer() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'] + 1080); - $FormatID = fread($this->getid3->fp, 4); + $this->fseek($info['avdataoffset'] + 1080); + $FormatID = $this->fread(4); if (!preg_match('#^(M.K.|[5-9]CHN|[1-3][0-9]CH)$#', $FormatID)) { $info['error'][] = 'This is not a known type of MOD file'; return false; @@ -50,10 +51,10 @@ class getid3_mod extends getid3_handler return false; } - function getXMheaderFilepointer() { + public function getXMheaderFilepointer() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset']); - $FormatID = fread($this->getid3->fp, 15); + $this->fseek($info['avdataoffset']); + $FormatID = $this->fread(15); if (!preg_match('#^Extended Module$#', $FormatID)) { $info['error'][] = 'This is not a known type of XM-MOD file'; return false; @@ -65,10 +66,10 @@ class getid3_mod extends getid3_handler return false; } - function getS3MheaderFilepointer() { + public function getS3MheaderFilepointer() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'] + 44); - $FormatID = fread($this->getid3->fp, 4); + $this->fseek($info['avdataoffset'] + 44); + $FormatID = $this->fread(4); if (!preg_match('#^SCRM$#', $FormatID)) { $info['error'][] = 'This is not a ScreamTracker MOD file'; return false; @@ -80,10 +81,10 @@ class getid3_mod extends getid3_handler return false; } - function getITheaderFilepointer() { + public function getITheaderFilepointer() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset']); - $FormatID = fread($this->getid3->fp, 4); + $this->fseek($info['avdataoffset']); + $FormatID = $this->fread(4); if (!preg_match('#^IMPM$#', $FormatID)) { $info['error'][] = 'This is not an ImpulseTracker MOD file'; return false; @@ -96,6 +97,3 @@ class getid3_mod extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.monkey.php b/app/library/getid3/getid3/module.audio.monkey.php old mode 100644 new mode 100755 similarity index 96% rename from app/library/getid3/module.audio.monkey.php rename to app/library/getid3/getid3/module.audio.monkey.php index ffaeae9f..e989c51d --- a/app/library/getid3/module.audio.monkey.php +++ b/app/library/getid3/getid3/module.audio.monkey.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,10 +18,10 @@ class getid3_monkey extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - // based loosely on code from TMonkey by Jurgen Faul + // based loosely on code from TMonkey by Jurgen Faul // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html $info['fileformat'] = 'mac'; @@ -32,8 +33,8 @@ class getid3_monkey extends getid3_handler $thisfile_monkeysaudio = &$info['monkeys_audio']; $thisfile_monkeysaudio_raw = &$thisfile_monkeysaudio['raw']; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $MACheaderData = fread($this->getid3->fp, 74); + $this->fseek($info['avdataoffset']); + $MACheaderData = $this->fread(74); $thisfile_monkeysaudio_raw['magic'] = substr($MACheaderData, 0, 4); $magic = 'MAC '; @@ -176,7 +177,7 @@ class getid3_monkey extends getid3_handler return true; } - function MonkeyCompressionLevelNameLookup($compressionlevel) { + public function MonkeyCompressionLevelNameLookup($compressionlevel) { static $MonkeyCompressionLevelNameLookup = array( 0 => 'unknown', 1000 => 'fast', @@ -188,7 +189,7 @@ class getid3_monkey extends getid3_handler return (isset($MonkeyCompressionLevelNameLookup[$compressionlevel]) ? $MonkeyCompressionLevelNameLookup[$compressionlevel] : 'invalid'); } - function MonkeySamplesPerFrame($versionid, $compressionlevel) { + public function MonkeySamplesPerFrame($versionid, $compressionlevel) { if ($versionid >= 3950) { return 73728 * 4; } elseif ($versionid >= 3900) { @@ -201,5 +202,3 @@ class getid3_monkey extends getid3_handler } } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.mp3.php b/app/library/getid3/getid3/module.audio.mp3.php old mode 100644 new mode 100755 similarity index 91% rename from app/library/getid3/module.audio.mp3.php rename to app/library/getid3/getid3/module.audio.mp3.php index 909646e1..329f7a67 --- a/app/library/getid3/module.audio.mp3.php +++ b/app/library/getid3/getid3/module.audio.mp3.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -24,9 +25,9 @@ define('GETID3_MP3_VALID_CHECK_FRAMES', 35); class getid3_mp3 extends getid3_handler { - var $allow_bruteforce = false; // forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, unrecommended, but may provide data from otherwise-unusuable files + public $allow_bruteforce = false; // forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, unrecommended, but may provide data from otherwise-unusuable files - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $initialOffset = $info['avdataoffset']; @@ -95,8 +96,8 @@ class getid3_mp3 extends getid3_handler // Not sure what version of LAME this is - look in padding of last frame for longer version string $PossibleLAMEversionStringOffset = $info['avdataend'] - $PossiblyLongerLAMEversion_FrameLength; - fseek($this->getid3->fp, $PossibleLAMEversionStringOffset); - $PossiblyLongerLAMEversion_Data = fread($this->getid3->fp, $PossiblyLongerLAMEversion_FrameLength); + $this->fseek($PossibleLAMEversionStringOffset); + $PossiblyLongerLAMEversion_Data = $this->fread($PossiblyLongerLAMEversion_FrameLength); switch (substr($CurrentDataLAMEversionString, -1)) { case 'a': case 'b': @@ -160,7 +161,7 @@ class getid3_mp3 extends getid3_handler } - function GuessEncoderOptions() { + public function GuessEncoderOptions() { // shortcuts $info = &$this->getid3->info; if (!empty($info['mpeg']['audio'])) { @@ -404,7 +405,7 @@ class getid3_mp3 extends getid3_handler } - function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) { + public function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) { static $MPEGaudioVersionLookup; static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; @@ -413,21 +414,21 @@ class getid3_mp3 extends getid3_handler static $MPEGaudioModeExtensionLookup; static $MPEGaudioEmphasisLookup; if (empty($MPEGaudioVersionLookup)) { - $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray(); - $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray(); - $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray(); - $MPEGaudioFrequencyLookup = getid3_mp3::MPEGaudioFrequencyArray(); - $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray(); - $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray(); - $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray(); + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); } - if (fseek($this->getid3->fp, $offset, SEEK_SET) != 0) { + if ($this->fseek($offset) != 0) { $info['error'][] = 'decodeMPEGaudioHeader() failed to seek to next offset at '.$offset; return false; } - //$headerstring = fread($this->getid3->fp, 1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame - $headerstring = fread($this->getid3->fp, 226); // LAME header at offset 36 + 190 bytes of Xing/LAME data + //$headerstring = $this->fread(1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame + $headerstring = $this->fread(226); // LAME header at offset 36 + 190 bytes of Xing/LAME data // MP3 audio frame structure: // $aa $aa $aa $aa [$bb $bb] $cc... @@ -441,14 +442,14 @@ class getid3_mp3 extends getid3_handler if (isset($MPEGaudioHeaderDecodeCache[$head4])) { $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4]; } else { - $MPEGheaderRawArray = getid3_mp3::MPEGaudioHeaderDecode($head4); + $MPEGheaderRawArray = self::MPEGaudioHeaderDecode($head4); $MPEGaudioHeaderDecodeCache[$head4] = $MPEGheaderRawArray; } static $MPEGaudioHeaderValidCache = array(); if (!isset($MPEGaudioHeaderValidCache[$head4])) { // Not in cache - //$MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true); // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1) - $MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false); + //$MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true); // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1) + $MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false); } // shortcut @@ -533,7 +534,7 @@ class getid3_mp3 extends getid3_handler if ($info['audio']['sample_rate'] > 0) { - $thisfile_mpeg_audio['framelength'] = getid3_mp3::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $info['audio']['sample_rate']); + $thisfile_mpeg_audio['framelength'] = self::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $info['audio']['sample_rate']); } $nextframetestoffset = $offset + 1; @@ -594,7 +595,7 @@ class getid3_mp3 extends getid3_handler // Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36) // depending on MPEG layer and number of channels - $VBRidOffset = getid3_mp3::XingVBRidOffset($thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['channelmode']); + $VBRidOffset = self::XingVBRidOffset($thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['channelmode']); $SideInfoData = substr($headerstring, 4 + 2, $VBRidOffset - 4); if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) { @@ -720,7 +721,7 @@ class getid3_mp3 extends getid3_handler $thisfile_mpeg_audio_lame['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4; $thisfile_mpeg_audio_lame_raw['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F; - $thisfile_mpeg_audio_lame['vbr_method'] = getid3_mp3::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']); + $thisfile_mpeg_audio_lame['vbr_method'] = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']); $thisfile_mpeg_audio['bitrate_mode'] = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr' // byte $A6 Lowpass filter value @@ -819,9 +820,9 @@ class getid3_mp3 extends getid3_handler $thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5; $thisfile_mpeg_audio_lame_raw['source_sample_freq'] = ($MiscByte & 0xC0) >> 6; $thisfile_mpeg_audio_lame['noise_shaping'] = $thisfile_mpeg_audio_lame_raw['noise_shaping']; - $thisfile_mpeg_audio_lame['stereo_mode'] = getid3_mp3::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']); + $thisfile_mpeg_audio_lame['stereo_mode'] = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']); $thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality']; - $thisfile_mpeg_audio_lame['source_sample_freq'] = getid3_mp3::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']); + $thisfile_mpeg_audio_lame['source_sample_freq'] = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']); // byte $B5 MP3 Gain $thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true); @@ -832,9 +833,9 @@ class getid3_mp3 extends getid3_handler $PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2)); // Reserved = ($PresetSurroundBytes & 0xC000); $thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800); - $thisfile_mpeg_audio_lame['surround_info'] = getid3_mp3::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']); + $thisfile_mpeg_audio_lame['surround_info'] = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']); $thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF); - $thisfile_mpeg_audio_lame['preset_used'] = getid3_mp3::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame); + $thisfile_mpeg_audio_lame['preset_used'] = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame); if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) { $info['warning'][] = 'Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org'; } @@ -858,7 +859,7 @@ class getid3_mp3 extends getid3_handler if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; - $thisfile_mpeg_audio['bitrate'] = getid3_mp3::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']); + $thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']); $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) { // $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min']; @@ -890,19 +891,21 @@ class getid3_mp3 extends getid3_handler if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($info['avdataend'] - $info['avdataoffset']))) { if ($ExpectedNumberOfAudioBytes > ($info['avdataend'] - $info['avdataoffset'])) { - if (isset($info['fileformat']) && ($info['fileformat'] == 'riff')) { + if ($this->isDependencyFor('matroska') || $this->isDependencyFor('riff')) { // ignore, audio data is broken into chunks so will always be data "missing" - } elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) { - $info['warning'][] = 'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)'; - } else { - $info['warning'][] = 'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)'; + } + elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) { + $this->warning('Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)'); + } + else { + $this->warning('Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)'); } } else { if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) { - // $prenullbytefileoffset = ftell($this->getid3->fp); - // fseek($this->getid3->fp, $info['avdataend'], SEEK_SET); - // $PossibleNullByte = fread($this->getid3->fp, 1); - // fseek($this->getid3->fp, $prenullbytefileoffset, SEEK_SET); + // $prenullbytefileoffset = $this->ftell(); + // $this->fseek($info['avdataend']); + // $PossibleNullByte = $this->fread(1); + // $this->fseek($prenullbytefileoffset); // if ($PossibleNullByte === "\x00") { $info['avdataend']--; // $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; @@ -1069,7 +1072,7 @@ class getid3_mp3 extends getid3_handler return true; } - function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) { + public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) { $info = &$this->getid3->info; $firstframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); $this->decodeMPEGaudioHeader($offset, $firstframetestarray, false); @@ -1115,11 +1118,11 @@ class getid3_mp3 extends getid3_handler return true; } - function FreeFormatFrameLength($offset, $deepscan=false) { + public function FreeFormatFrameLength($offset, $deepscan=false) { $info = &$this->getid3->info; - fseek($this->getid3->fp, $offset, SEEK_SET); - $MPEGaudioData = fread($this->getid3->fp, 32768); + $this->fseek($offset); + $MPEGaudioData = $this->fread(32768); $SyncPattern1 = substr($MPEGaudioData, 0, 4); // may be different pattern due to padding @@ -1166,8 +1169,8 @@ class getid3_mp3 extends getid3_handler $ActualFrameLengthValues = array(); $nextoffset = $offset + $framelength; while ($nextoffset < ($info['avdataend'] - 6)) { - fseek($this->getid3->fp, $nextoffset - 1, SEEK_SET); - $NextSyncPattern = fread($this->getid3->fp, 6); + $this->fseek($nextoffset - 1); + $NextSyncPattern = $this->fread(6); if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) { // good - found where expected $ActualFrameLengthValues[] = $framelength; @@ -1192,17 +1195,17 @@ class getid3_mp3 extends getid3_handler return $framelength; } - function getOnlyMPEGaudioInfoBruteForce() { + public function getOnlyMPEGaudioInfoBruteForce() { $MPEGaudioHeaderDecodeCache = array(); $MPEGaudioHeaderValidCache = array(); $MPEGaudioHeaderLengthCache = array(); - $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray(); - $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray(); - $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray(); - $MPEGaudioFrequencyLookup = getid3_mp3::MPEGaudioFrequencyArray(); - $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray(); - $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray(); - $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray(); + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); $LongMPEGversionLookup = array(); $LongMPEGlayerLookup = array(); $LongMPEGbitrateLookup = array(); @@ -1215,32 +1218,32 @@ class getid3_mp3 extends getid3_handler $Distribution['padding'] = array(); $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); $max_frames_scan = 5000; $frames_scanned = 0; $previousvalidframe = $info['avdataoffset']; - while (ftell($this->getid3->fp) < $info['avdataend']) { + while ($this->ftell() < $info['avdataend']) { set_time_limit(30); - $head4 = fread($this->getid3->fp, 4); + $head4 = $this->fread(4); if (strlen($head4) < 4) { break; } if ($head4{0} != "\xFF") { for ($i = 1; $i < 4; $i++) { if ($head4{$i} == "\xFF") { - fseek($this->getid3->fp, $i - 4, SEEK_CUR); + $this->fseek($i - 4, SEEK_CUR); continue 2; } } continue; } if (!isset($MPEGaudioHeaderDecodeCache[$head4])) { - $MPEGaudioHeaderDecodeCache[$head4] = getid3_mp3::MPEGaudioHeaderDecode($head4); + $MPEGaudioHeaderDecodeCache[$head4] = self::MPEGaudioHeaderDecode($head4); } if (!isset($MPEGaudioHeaderValidCache[$head4])) { - $MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false); + $MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false); } if ($MPEGaudioHeaderValidCache[$head4]) { @@ -1250,7 +1253,7 @@ class getid3_mp3 extends getid3_handler $LongMPEGbitrateLookup[$head4] = $MPEGaudioBitrateLookup[$LongMPEGversionLookup[$head4]][$LongMPEGlayerLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['bitrate']]; $LongMPEGpaddingLookup[$head4] = (bool) $MPEGaudioHeaderDecodeCache[$head4]['padding']; $LongMPEGfrequencyLookup[$head4] = $MPEGaudioFrequencyLookup[$LongMPEGversionLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['sample_rate']]; - $MPEGaudioHeaderLengthCache[$head4] = getid3_mp3::MPEGaudioFrameLength( + $MPEGaudioHeaderLengthCache[$head4] = self::MPEGaudioFrameLength( $LongMPEGbitrateLookup[$head4], $LongMPEGversionLookup[$head4], $LongMPEGlayerLookup[$head4], @@ -1258,18 +1261,18 @@ class getid3_mp3 extends getid3_handler $LongMPEGfrequencyLookup[$head4]); } if ($MPEGaudioHeaderLengthCache[$head4] > 4) { - $WhereWeWere = ftell($this->getid3->fp); - fseek($this->getid3->fp, $MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR); - $next4 = fread($this->getid3->fp, 4); + $WhereWeWere = $this->ftell(); + $this->fseek($MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR); + $next4 = $this->fread(4); if ($next4{0} == "\xFF") { if (!isset($MPEGaudioHeaderDecodeCache[$next4])) { - $MPEGaudioHeaderDecodeCache[$next4] = getid3_mp3::MPEGaudioHeaderDecode($next4); + $MPEGaudioHeaderDecodeCache[$next4] = self::MPEGaudioHeaderDecode($next4); } if (!isset($MPEGaudioHeaderValidCache[$next4])) { - $MPEGaudioHeaderValidCache[$next4] = getid3_mp3::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false); + $MPEGaudioHeaderValidCache[$next4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false); } if ($MPEGaudioHeaderValidCache[$next4]) { - fseek($this->getid3->fp, -4, SEEK_CUR); + $this->fseek(-4, SEEK_CUR); getid3_lib::safe_inc($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]); getid3_lib::safe_inc($Distribution['layer'][$LongMPEGlayerLookup[$head4]]); @@ -1277,7 +1280,7 @@ class getid3_mp3 extends getid3_handler getid3_lib::safe_inc($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]); getid3_lib::safe_inc($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]); if ($max_frames_scan && (++$frames_scanned >= $max_frames_scan)) { - $pct_data_scanned = (ftell($this->getid3->fp) - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']); + $pct_data_scanned = ($this->ftell() - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']); $info['warning'][] = 'too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; foreach ($Distribution as $key1 => $value1) { foreach ($value1 as $key2 => $value2) { @@ -1290,7 +1293,7 @@ class getid3_mp3 extends getid3_handler } } unset($next4); - fseek($this->getid3->fp, $WhereWeWere - 3, SEEK_SET); + $this->fseek($WhereWeWere - 3); } } @@ -1340,7 +1343,7 @@ class getid3_mp3 extends getid3_handler } - function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) { + public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) { // looks for synch, decodes MPEG audio header $info = &$this->getid3->info; @@ -1349,19 +1352,19 @@ class getid3_mp3 extends getid3_handler static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; if (empty($MPEGaudioVersionLookup)) { - $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray(); - $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray(); - $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray(); + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); } - fseek($this->getid3->fp, $avdataoffset, SEEK_SET); + $this->fseek($avdataoffset); $sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset); if ($sync_seek_buffer_size <= 0) { $info['error'][] = 'Invalid $sync_seek_buffer_size at offset '.$avdataoffset; return false; } - $header = fread($this->getid3->fp, $sync_seek_buffer_size); + $header = $this->fread($sync_seek_buffer_size); $sync_seek_buffer_size = strlen($header); $SynchSeekOffset = 0; while ($SynchSeekOffset < $sync_seek_buffer_size) { @@ -1473,7 +1476,7 @@ class getid3_mp3 extends getid3_handler $dummy = array('error'=>$info['error'], 'warning'=>$info['warning'], 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); $synchstartoffset = $info['avdataoffset']; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); // you can play with these numbers: $max_frames_scan = 50000; @@ -1488,13 +1491,13 @@ class getid3_mp3 extends getid3_handler $pct_data_scanned = 0; for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) { $frames_scanned_this_segment = 0; - if (ftell($this->getid3->fp) >= $info['avdataend']) { + if ($this->ftell() >= $info['avdataend']) { break; } - $scan_start_offset[$current_segment] = max(ftell($this->getid3->fp), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments))); + $scan_start_offset[$current_segment] = max($this->ftell(), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments))); if ($current_segment > 0) { - fseek($this->getid3->fp, $scan_start_offset[$current_segment], SEEK_SET); - $buffer_4k = fread($this->getid3->fp, 4096); + $this->fseek($scan_start_offset[$current_segment]); + $buffer_4k = $this->fread(4096); for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) { if (($buffer_4k{$j} == "\xFF") && ($buffer_4k{($j + 1)} > "\xE0")) { // synch detected if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) { @@ -1523,7 +1526,7 @@ class getid3_mp3 extends getid3_handler } $frames_scanned++; if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) { - $this_pct_scanned = (ftell($this->getid3->fp) - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']); + $this_pct_scanned = ($this->ftell() - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']); if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) { // file likely contains < $max_frames_scan, just scan as one segment $max_scan_segments = 1; @@ -1620,17 +1623,17 @@ class getid3_mp3 extends getid3_handler } - static function MPEGaudioVersionArray() { + public static function MPEGaudioVersionArray() { static $MPEGaudioVersion = array('2.5', false, '2', '1'); return $MPEGaudioVersion; } - static function MPEGaudioLayerArray() { + public static function MPEGaudioLayerArray() { static $MPEGaudioLayer = array(false, 3, 2, 1); return $MPEGaudioLayer; } - static function MPEGaudioBitrateArray() { + public static function MPEGaudioBitrateArray() { static $MPEGaudioBitrate; if (empty($MPEGaudioBitrate)) { $MPEGaudioBitrate = array ( @@ -1649,7 +1652,7 @@ class getid3_mp3 extends getid3_handler return $MPEGaudioBitrate; } - static function MPEGaudioFrequencyArray() { + public static function MPEGaudioFrequencyArray() { static $MPEGaudioFrequency; if (empty($MPEGaudioFrequency)) { $MPEGaudioFrequency = array ( @@ -1661,12 +1664,12 @@ class getid3_mp3 extends getid3_handler return $MPEGaudioFrequency; } - static function MPEGaudioChannelModeArray() { + public static function MPEGaudioChannelModeArray() { static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono'); return $MPEGaudioChannelMode; } - static function MPEGaudioModeExtensionArray() { + public static function MPEGaudioModeExtensionArray() { static $MPEGaudioModeExtension; if (empty($MPEGaudioModeExtension)) { $MPEGaudioModeExtension = array ( @@ -1678,16 +1681,16 @@ class getid3_mp3 extends getid3_handler return $MPEGaudioModeExtension; } - static function MPEGaudioEmphasisArray() { + public static function MPEGaudioEmphasisArray() { static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17'); return $MPEGaudioEmphasis; } - static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) { - return getid3_mp3::MPEGaudioHeaderValid(getid3_mp3::MPEGaudioHeaderDecode($head4), false, $allowBitrate15); + public static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) { + return self::MPEGaudioHeaderValid(self::MPEGaudioHeaderDecode($head4), false, $allowBitrate15); } - static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) { + public static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) { if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) { return false; } @@ -1700,13 +1703,13 @@ class getid3_mp3 extends getid3_handler static $MPEGaudioModeExtensionLookup; static $MPEGaudioEmphasisLookup; if (empty($MPEGaudioVersionLookup)) { - $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray(); - $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray(); - $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray(); - $MPEGaudioFrequencyLookup = getid3_mp3::MPEGaudioFrequencyArray(); - $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray(); - $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray(); - $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray(); + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); } if (isset($MPEGaudioVersionLookup[$rawarray['version']])) { @@ -1759,7 +1762,7 @@ class getid3_mp3 extends getid3_handler return true; } - static function MPEGaudioHeaderDecode($Header4Bytes) { + public static function MPEGaudioHeaderDecode($Header4Bytes) { // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM // A - Frame sync (all bits set) // B - MPEG Audio version ID @@ -1796,7 +1799,7 @@ class getid3_mp3 extends getid3_handler return $MPEGrawHeader; } - static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { + public static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { static $AudioFrameLengthCache = array(); if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) { @@ -1857,7 +1860,7 @@ class getid3_mp3 extends getid3_handler return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate]; } - static function ClosestStandardMP3Bitrate($bit_rate) { + public static function ClosestStandardMP3Bitrate($bit_rate) { static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000); static $bit_rate_table = array (0=>'-'); $round_bit_rate = intval(round($bit_rate, -3)); @@ -1877,7 +1880,7 @@ class getid3_mp3 extends getid3_handler return $bit_rate_table[$round_bit_rate]; } - static function XingVBRidOffset($version, $channelmode) { + public static function XingVBRidOffset($version, $channelmode) { static $XingVBRidOffsetCache = array(); if (empty($XingVBRidOffset)) { $XingVBRidOffset = array ( @@ -1903,7 +1906,7 @@ class getid3_mp3 extends getid3_handler return $XingVBRidOffset[$version][$channelmode]; } - static function LAMEvbrMethodLookup($VBRmethodID) { + public static function LAMEvbrMethodLookup($VBRmethodID) { static $LAMEvbrMethodLookup = array( 0x00 => 'unknown', 0x01 => 'cbr', @@ -1919,7 +1922,7 @@ class getid3_mp3 extends getid3_handler return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : ''); } - static function LAMEmiscStereoModeLookup($StereoModeID) { + public static function LAMEmiscStereoModeLookup($StereoModeID) { static $LAMEmiscStereoModeLookup = array( 0 => 'mono', 1 => 'stereo', @@ -1933,7 +1936,7 @@ class getid3_mp3 extends getid3_handler return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : ''); } - static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { + public static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { static $LAMEmiscSourceSampleFrequencyLookup = array( 0 => '<= 32 kHz', 1 => '44.1 kHz', @@ -1943,7 +1946,7 @@ class getid3_mp3 extends getid3_handler return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : ''); } - static function LAMEsurroundInfoLookup($SurroundInfoID) { + public static function LAMEsurroundInfoLookup($SurroundInfoID) { static $LAMEsurroundInfoLookup = array( 0 => 'no surround info', 1 => 'DPL encoding', @@ -1953,7 +1956,7 @@ class getid3_mp3 extends getid3_handler return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved'); } - static function LAMEpresetUsedLookup($LAMEtag) { + public static function LAMEpresetUsedLookup($LAMEtag) { if ($LAMEtag['preset_used_id'] == 0) { // no preset used (LAME >=3.93) @@ -2007,5 +2010,3 @@ class getid3_mp3 extends getid3_handler } } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.mpc.php b/app/library/getid3/getid3/module.audio.mpc.php old mode 100644 new mode 100755 similarity index 95% rename from app/library/getid3/module.audio.mpc.php rename to app/library/getid3/getid3/module.audio.mpc.php index 9a0b16d9..f3905de2 --- a/app/library/getid3/module.audio.mpc.php +++ b/app/library/getid3/getid3/module.audio.mpc.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,7 +18,7 @@ class getid3_mpc extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['mpc']['header'] = array(); @@ -29,8 +30,8 @@ class getid3_mpc extends getid3_handler $info['audio']['channels'] = 2; // up to SV7 the format appears to have been hardcoded for stereo only $info['audio']['lossless'] = false; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $MPCheaderData = fread($this->getid3->fp, 4); + $this->fseek($info['avdataoffset']); + $MPCheaderData = $this->fread(4); $info['mpc']['header']['preamble'] = substr($MPCheaderData, 0, 4); // should be 'MPCK' (SV8) or 'MP+' (SV7), otherwise possible stream data (SV4-SV6) if (preg_match('#^MPCK#', $info['mpc']['header']['preamble'])) { @@ -59,7 +60,7 @@ class getid3_mpc extends getid3_handler } - function ParseMPCsv8() { + public function ParseMPCsv8() { // this is SV8 // http://trac.musepack.net/trac/wiki/SV8Specification @@ -69,7 +70,7 @@ class getid3_mpc extends getid3_handler $keyNameSize = 2; $maxHandledPacketLength = 9; // specs say: "n*8; 0 < n < 10" - $offset = ftell($this->getid3->fp); + $offset = $this->ftell(); while ($offset < $info['avdataend']) { $thisPacket = array(); $thisPacket['offset'] = $offset; @@ -77,7 +78,7 @@ class getid3_mpc extends getid3_handler // Size is a variable-size field, could be 1-4 bytes (possibly more?) // read enough data in and figure out the exact size later - $MPCheaderData = fread($this->getid3->fp, $keyNameSize + $maxHandledPacketLength); + $MPCheaderData = $this->fread($keyNameSize + $maxHandledPacketLength); $packet_offset += $keyNameSize; $thisPacket['key'] = substr($MPCheaderData, 0, $keyNameSize); $thisPacket['key_name'] = $this->MPCsv8PacketName($thisPacket['key']); @@ -98,7 +99,7 @@ class getid3_mpc extends getid3_handler case 'SH': // Stream Header $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; if ($moreBytesToRead > 0) { - $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); + $MPCheaderData .= $this->fread($moreBytesToRead); } $thisPacket['crc'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 4)); $packet_offset += 4; @@ -136,7 +137,7 @@ class getid3_mpc extends getid3_handler case 'RG': // Replay Gain $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; if ($moreBytesToRead > 0) { - $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); + $MPCheaderData .= $this->fread($moreBytesToRead); } $thisPacket['replaygain_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; @@ -158,7 +159,7 @@ class getid3_mpc extends getid3_handler case 'EI': // Encoder Info $moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength; if ($moreBytesToRead > 0) { - $MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead); + $MPCheaderData .= $this->fread($moreBytesToRead); } $profile_pns = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1)); $packet_offset += 1; @@ -201,13 +202,13 @@ class getid3_mpc extends getid3_handler if (!empty($thisPacket)) { $info['mpc']['packets'][] = $thisPacket; } - fseek($this->getid3->fp, $offset); + $this->fseek($offset); } $thisfile_mpc_header['size'] = $offset; return true; } - function ParseMPCsv7() { + public function ParseMPCsv7() { // this is SV7 // http://www.uni-jena.de/~pfk/mpp/sv8/header.html @@ -217,7 +218,7 @@ class getid3_mpc extends getid3_handler $thisfile_mpc_header['size'] = 28; $MPCheaderData = $info['mpc']['header']['preamble']; - $MPCheaderData .= fread($this->getid3->fp, $thisfile_mpc_header['size'] - strlen($info['mpc']['header']['preamble'])); + $MPCheaderData .= $this->fread($thisfile_mpc_header['size'] - strlen($info['mpc']['header']['preamble'])); $offset = strlen('MP+'); $StreamVersionByte = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1)); @@ -321,7 +322,7 @@ class getid3_mpc extends getid3_handler return true; } - function ParseMPCsv6() { + public function ParseMPCsv6() { // this is SV4 - SV6 $info = &$this->getid3->info; @@ -329,8 +330,8 @@ class getid3_mpc extends getid3_handler $offset = 0; $thisfile_mpc_header['size'] = 8; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $MPCheaderData = fread($this->getid3->fp, $thisfile_mpc_header['size']); + $this->fseek($info['avdataoffset']); + $MPCheaderData = $this->fread($thisfile_mpc_header['size']); // add size of file header to avdataoffset - calc bitrate correctly + MD5 data $info['avdataoffset'] += $thisfile_mpc_header['size']; @@ -397,7 +398,7 @@ class getid3_mpc extends getid3_handler } - function MPCprofileNameLookup($profileid) { + public function MPCprofileNameLookup($profileid) { static $MPCprofileNameLookup = array( 0 => 'no profile', 1 => 'Experimental', @@ -419,7 +420,7 @@ class getid3_mpc extends getid3_handler return (isset($MPCprofileNameLookup[$profileid]) ? $MPCprofileNameLookup[$profileid] : 'invalid'); } - function MPCfrequencyLookup($frequencyid) { + public function MPCfrequencyLookup($frequencyid) { static $MPCfrequencyLookup = array( 0 => 44100, 1 => 48000, @@ -429,14 +430,14 @@ class getid3_mpc extends getid3_handler return (isset($MPCfrequencyLookup[$frequencyid]) ? $MPCfrequencyLookup[$frequencyid] : 'invalid'); } - function MPCpeakDBLookup($intvalue) { + public function MPCpeakDBLookup($intvalue) { if ($intvalue > 0) { return ((log10($intvalue) / log10(2)) - 15) * 6; } return false; } - function MPCencoderVersionLookup($encoderversion) { + public function MPCencoderVersionLookup($encoderversion) { //Encoder version * 100 (106 = 1.06) //EncoderVersion % 10 == 0 Release (1.0) //EncoderVersion % 2 == 0 Beta (1.06) @@ -463,7 +464,7 @@ class getid3_mpc extends getid3_handler return number_format($encoderversion / 100, 2).' alpha'; } - function SV8variableLengthInteger($data, &$packetLength, $maxHandledPacketLength=9) { + public function SV8variableLengthInteger($data, &$packetLength, $maxHandledPacketLength=9) { $packet_size = 0; for ($packetLength = 1; $packetLength <= $maxHandledPacketLength; $packetLength++) { // variable-length size field: @@ -487,7 +488,7 @@ class getid3_mpc extends getid3_handler return $packet_size; } - function MPCsv8PacketName($packetKey) { + public function MPCsv8PacketName($packetKey) { static $MPCsv8PacketName = array(); if (empty($MPCsv8PacketName)) { $MPCsv8PacketName = array( @@ -504,6 +505,3 @@ class getid3_mpc extends getid3_handler return (isset($MPCsv8PacketName[$packetKey]) ? $MPCsv8PacketName[$packetKey] : $packetKey); } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.ogg.php b/app/library/getid3/getid3/module.audio.ogg.php old mode 100644 new mode 100755 similarity index 63% rename from app/library/getid3/module.audio.ogg.php rename to app/library/getid3/getid3/module.audio.ogg.php index c987fa67..2a77768b --- a/app/library/getid3/module.audio.ogg.php +++ b/app/library/getid3/getid3/module.audio.ogg.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,9 +18,8 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE class getid3_ogg extends getid3_handler { - var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory - - function Analyze() { + // http://xiph.org/vorbis/doc/Vorbis_I_spec.html + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'ogg'; @@ -38,19 +38,19 @@ class getid3_ogg extends getid3_handler // Page 1 - Stream Header - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); $oggpageinfo = $this->ParseOggPageHeader(); $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; - if (ftell($this->getid3->fp) >= $this->getid3->fread_buffer_size()) { + if ($this->ftell() >= $this->getid3->fread_buffer_size()) { $info['error'][] = 'Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)'; unset($info['fileformat']); unset($info['ogg']); return false; } - $filedata = fread($this->getid3->fp, $oggpageinfo['page_length']); + $filedata = $this->fread($oggpageinfo['page_length']); $filedataoffset = 0; if (substr($filedata, 0, 4) == 'fLaC') { @@ -63,6 +63,12 @@ class getid3_ogg extends getid3_handler $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); + } elseif (substr($filedata, 0, 8) == 'OpusHead') { + + if( $this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) == false ) { + return false; + } + } elseif (substr($filedata, 0, 8) == 'Speex ') { // http://www.speex.org/manual/node10.html @@ -115,6 +121,66 @@ class getid3_ogg extends getid3_handler $info['audio']['bitrate_mode'] = 'vbr'; } + } elseif (substr($filedata, 0, 7) == "\x80".'theora') { + + // http://www.theora.org/doc/Theora.pdf (section 6.2) + + $info['ogg']['pageheader']['theora']['theora_magic'] = substr($filedata, $filedataoffset, 7); // hard-coded to "\x80.'theora' + $filedataoffset += 7; + $info['ogg']['pageheader']['theora']['version_major'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['version_minor'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['version_revision'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['frame_width_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + $info['ogg']['pageheader']['theora']['frame_height_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + $info['ogg']['pageheader']['theora']['resolution_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); + $filedataoffset += 3; + $info['ogg']['pageheader']['theora']['resolution_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); + $filedataoffset += 3; + $info['ogg']['pageheader']['theora']['picture_offset_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['picture_offset_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['frame_rate_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader']['theora']['frame_rate_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); + $filedataoffset += 3; + $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); + $filedataoffset += 3; + $info['ogg']['pageheader']['theora']['color_space_id'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $info['ogg']['pageheader']['theora']['nominal_bitrate'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); + $filedataoffset += 3; + $info['ogg']['pageheader']['theora']['flags'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + + $info['ogg']['pageheader']['theora']['quality'] = ($info['ogg']['pageheader']['theora']['flags'] & 0xFC00) >> 10; + $info['ogg']['pageheader']['theora']['kfg_shift'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x03E0) >> 5; + $info['ogg']['pageheader']['theora']['pixel_format_id'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0018) >> 3; + $info['ogg']['pageheader']['theora']['reserved'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0007) >> 0; // should be 0 + $info['ogg']['pageheader']['theora']['color_space'] = self::TheoraColorSpace($info['ogg']['pageheader']['theora']['color_space_id']); + $info['ogg']['pageheader']['theora']['pixel_format'] = self::TheoraPixelFormat($info['ogg']['pageheader']['theora']['pixel_format_id']); + + $info['video']['dataformat'] = 'theora'; + $info['mime_type'] = 'video/ogg'; + //$info['audio']['bitrate_mode'] = 'abr'; + //$info['audio']['lossless'] = false; + $info['video']['resolution_x'] = $info['ogg']['pageheader']['theora']['resolution_x']; + $info['video']['resolution_y'] = $info['ogg']['pageheader']['theora']['resolution_y']; + if ($info['ogg']['pageheader']['theora']['frame_rate_denominator'] > 0) { + $info['video']['frame_rate'] = (float) $info['ogg']['pageheader']['theora']['frame_rate_numerator'] / $info['ogg']['pageheader']['theora']['frame_rate_denominator']; + } + if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) { + $info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator']; + } +$info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable'; + } elseif (substr($filedata, 0, 8) == "fishead\x00") { @@ -146,8 +212,8 @@ class getid3_ogg extends getid3_handler do { $oggpageinfo = $this->ParseOggPageHeader(); $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo; - $filedata = fread($this->getid3->fp, $oggpageinfo['page_length']); - fseek($this->getid3->fp, $oggpageinfo['page_end_offset'], SEEK_SET); + $filedata = $this->fread($oggpageinfo['page_length']); + $this->fseek($oggpageinfo['page_end_offset']); if (substr($filedata, 0, 8) == "fisbone\x00") { @@ -173,30 +239,29 @@ class getid3_ogg extends getid3_handler } elseif (substr($filedata, 1, 6) == 'theora') { - $info['video']['dataformat'] = 'theora'; -$info['error'][] = 'Ogg Theora not correctly handled in this version of getID3 ['.$this->getid3->version().']'; -//break; + $info['video']['dataformat'] = 'theora1'; + $info['error'][] = 'Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']'; + //break; } elseif (substr($filedata, 1, 6) == 'vorbis') { $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); } else { -$info['error'][] = 'unexpected'; -//break; + $info['error'][] = 'unexpected'; + //break; } //} while ($oggpageinfo['page_seqno'] == 0); } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00")); - fseek($this->getid3->fp, $oggpageinfo['page_start_offset'], SEEK_SET); + $this->fseek($oggpageinfo['page_start_offset']); -$info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']'; -//return false; - + $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']'; + //return false; } else { - $info['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"'; + $info['error'][] = 'Expecting either "Speex ", "OpusHead" or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"'; unset($info['ogg']); unset($info['mime_type']); return false; @@ -209,44 +274,52 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 switch ($info['audio']['dataformat']) { case 'vorbis': - $filedata = fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1)); $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis' - $this->ParseVorbisCommentsFilepointer(); + $this->ParseVorbisComments(); break; case 'flac': - $getid3_flac = new getid3_flac($this->getid3); - if (!$getid3_flac->FLACparseMETAdata()) { + $flac = new getid3_flac($this->getid3); + if (!$flac->parseMETAdata()) { $info['error'][] = 'Failed to parse FLAC headers'; return false; } - unset($getid3_flac); + unset($flac); break; case 'speex': - fseek($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); - $this->ParseVorbisCommentsFilepointer(); + $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); + $this->ParseVorbisComments(); + break; + + case 'opus': + $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 0, 8); // hard-coded to 'OpusTags' + if(substr($filedata, 0, 8) != 'OpusTags') { + $info['error'][] = 'Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"'; + return false; + } + + $this->ParseVorbisComments(); break; } - - // Last Page - Number of Samples - if (!getid3_lib::intValueSupported($info['avdataend'])) { $info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)'; } else { - fseek($this->getid3->fp, max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0), SEEK_SET); - $LastChunkOfOgg = strrev(fread($this->getid3->fp, $this->getid3->fread_buffer_size())); + $this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0)); + $LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size())); if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) { - fseek($this->getid3->fp, $info['avdataend'] - ($LastOggSpostion + strlen('SggO')), SEEK_SET); - $info['avdataend'] = ftell($this->getid3->fp); + $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO'))); + $info['avdataend'] = $this->ftell(); $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader(); $info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position']; if ($info['ogg']['samples'] == 0) { @@ -305,7 +378,7 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 return true; } - function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { + public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { $info = &$this->getid3->info; $info['audio']['dataformat'] = 'vorbis'; $info['audio']['lossless'] = false; @@ -353,19 +426,70 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 return true; } - function ParseOggPageHeader() { - // http://xiph.org/ogg/vorbis/doc/framing.html - $oggheader['page_start_offset'] = ftell($this->getid3->fp); // where we started from in the file + // http://tools.ietf.org/html/draft-ietf-codec-oggopus-03 + public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { + $info = &$this->getid3->info; + $info['audio']['dataformat'] = 'opus'; + $info['mime_type'] = 'audio/ogg; codecs=opus'; - $filedata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); + /** @todo find a usable way to detect abr (vbr that is padded to be abr) */ + $info['audio']['bitrate_mode'] = 'vbr'; + + $info['audio']['lossless'] = false; + + $info['ogg']['pageheader']['opus']['opus_magic'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'OpusHead' + $filedataoffset += 8; + $info['ogg']['pageheader']['opus']['version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + + if ($info['ogg']['pageheader']['opus']['version'] < 1 || $info['ogg']['pageheader']['opus']['version'] > 15) { + $info['error'][] = 'Unknown opus version number (only accepting 1-15)'; + return false; + } + + $info['ogg']['pageheader']['opus']['out_channel_count'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + + if ($info['ogg']['pageheader']['opus']['out_channel_count'] == 0) { + $info['error'][] = 'Invalid channel count in opus header (must not be zero)'; + return false; + } + + $info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); + $filedataoffset += 2; + + $info['ogg']['pageheader']['opus']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + + //$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); + //$filedataoffset += 2; + + //$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + //$filedataoffset += 1; + + $info['opus']['opus_version'] = $info['ogg']['pageheader']['opus']['version']; + $info['opus']['sample_rate'] = $info['ogg']['pageheader']['opus']['sample_rate']; + $info['opus']['out_channel_count'] = $info['ogg']['pageheader']['opus']['out_channel_count']; + + $info['audio']['channels'] = $info['opus']['out_channel_count']; + $info['audio']['sample_rate'] = $info['opus']['sample_rate']; + return true; + } + + + public function ParseOggPageHeader() { + // http://xiph.org/ogg/vorbis/doc/framing.html + $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file + + $filedata = $this->fread($this->getid3->fread_buffer_size()); $filedataoffset = 0; while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) { - if ((ftell($this->getid3->fp) - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) { + if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) { // should be found before here return false; } if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) { - if (feof($this->getid3->fp) || (($filedata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size())) === false)) { + if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) { // get some more data, unless eof, in which case fail return false; } @@ -399,45 +523,45 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 } $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset; $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length']; - fseek($this->getid3->fp, $oggheader['header_end_offset'], SEEK_SET); + $this->fseek($oggheader['header_end_offset']); return $oggheader; } - - function ParseVorbisCommentsFilepointer() { + // http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005 + public function ParseVorbisComments() { $info = &$this->getid3->info; - $OriginalOffset = ftell($this->getid3->fp); + $OriginalOffset = $this->ftell(); $commentdataoffset = 0; $VorbisCommentPage = 1; switch ($info['audio']['dataformat']) { case 'vorbis': + case 'speex': + case 'opus': $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block - fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET); + $this->fseek($CommentStartOffset); $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; - $commentdata = fread($this->getid3->fp, getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); + $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); + + if ($info['audio']['dataformat'] == 'vorbis') { + $commentdataoffset += (strlen('vorbis') + 1); + } + else if ($info['audio']['dataformat'] == 'opus') { + $commentdataoffset += strlen('OpusTags'); + } - $commentdataoffset += (strlen('vorbis') + 1); break; case 'flac': $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4; - fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET); - $commentdata = fread($this->getid3->fp, $info['flac']['VORBIS_COMMENT']['raw']['block_length']); - break; - - case 'speex': - $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block - fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET); - $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; - $commentdata = fread($this->getid3->fp, getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); + $this->fseek($CommentStartOffset); + $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']); break; default: return false; - break; } $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); @@ -454,9 +578,15 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw']; for ($i = 0; $i < $CommentsCount; $i++) { + if ($i >= 10000) { + // https://github.com/owncloud/music/issues/212#issuecomment-43082336 + $info['warning'][] = 'Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments'; + break; + } + $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset; - if (ftell($this->getid3->fp) < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) { + if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) { if ($oggpageinfo = $this->ParseOggPageHeader()) { $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; @@ -475,8 +605,8 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 // Finally, stick the unused data back on the end $commentdata .= $AsYetUnusedData; - //$commentdata .= fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); - $commentdata .= fread($this->getid3->fp, $this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1)); + //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1)); } } @@ -510,17 +640,17 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 // Finally, stick the unused data back on the end $commentdata .= $AsYetUnusedData; - //$commentdata .= fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) { - $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($this->getid3->fp); + $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell(); break; } - $readlength = getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1); + $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1); if ($readlength <= 0) { - $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($this->getid3->fp); + $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell(); break; } - $commentdata .= fread($this->getid3->fp, $readlength); + $commentdata .= $this->fread($readlength); //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset']; } @@ -536,75 +666,57 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 } elseif (strstr($commentstring, '=')) { $commentexploded = explode('=', $commentstring, 2); - $ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]); - $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : ''); - $ThisFileInfo_ogg_comments_raw[$i]['data'] = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']); - $ThisFileInfo_ogg_comments_raw[$i]['data_length'] = strlen($ThisFileInfo_ogg_comments_raw[$i]['data']); + $ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]); + $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : ''); - if (preg_match('#^(BM|GIF|\xFF\xD8\xFF|\x89\x50\x4E\x47\x0D\x0A\x1A\x0A|II\x2A\x00|MM\x00\x2A)#s', $ThisFileInfo_ogg_comments_raw[$i]['data'])) { - $imageinfo = array(); - $imagechunkcheck = getid3_lib::GetDataImageSize($ThisFileInfo_ogg_comments_raw[$i]['data'], $imageinfo); - unset($imageinfo); - if (!empty($imagechunkcheck)) { - $ThisFileInfo_ogg_comments_raw[$i]['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]); - if ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] && ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] != 'application/octet-stream')) { - unset($ThisFileInfo_ogg_comments_raw[$i]['value']); - } + if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') { + + // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE + // The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard. + // http://flac.sourceforge.net/format.html#metadata_block_picture + $flac = new getid3_flac($this->getid3); + $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value'])); + $flac->parsePICTURE(); + $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0]; + unset($flac); + + } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') { + + $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']); + $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure'); + /** @todo use 'coverartmime' where available */ + $imageinfo = getid3_lib::GetDataImageSize($data); + if ($imageinfo === false || !isset($imageinfo['mime'])) { + $this->warning('COVERART vorbiscomment tag contains invalid image'); + continue; } - } - if (isset($ThisFileInfo_ogg_comments_raw[$i]['value'])) { - unset($ThisFileInfo_ogg_comments_raw[$i]['data']); - $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value']; + $ogg = new self($this->getid3); + $ogg->setStringMode($data); + $info['ogg']['comments']['picture'][] = array( + 'image_mime' => $imageinfo['mime'], + 'datalength' => strlen($data), + 'picturetype' => 'cover art', + 'image_height' => $imageinfo['height'], + 'image_width' => $imageinfo['width'], + 'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']), + ); + unset($ogg); + } else { - do { - if ($this->inline_attachments === false) { - // skip entirely - unset($ThisFileInfo_ogg_comments_raw[$i]['data']); - break; - } - if ($this->inline_attachments === true) { - // great - } elseif (is_int($this->inline_attachments)) { - if ($this->inline_attachments < $ThisFileInfo_ogg_comments_raw[$i]['data_length']) { - // too big, skip - $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' is too large to process inline ('.number_format($ThisFileInfo_ogg_comments_raw[$i]['data_length']).' bytes)'; - unset($ThisFileInfo_ogg_comments_raw[$i]['data']); - break; - } - } elseif (is_string($this->inline_attachments)) { - $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); - if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) { - // cannot write, skip - $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)'; - unset($ThisFileInfo_ogg_comments_raw[$i]['data']); - break; - } - } - // if we get this far, must be OK - if (is_string($this->inline_attachments)) { - $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$ThisFileInfo_ogg_comments_raw[$i]['offset']; - if (!file_exists($destination_filename) || is_writable($destination_filename)) { - file_put_contents($destination_filename, $ThisFileInfo_ogg_comments_raw[$i]['data']); - } else { - $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)'; - } - $ThisFileInfo_ogg_comments_raw[$i]['data_filename'] = $destination_filename; - unset($ThisFileInfo_ogg_comments_raw[$i]['data']); - } else { - $info['ogg']['comments']['picture'][] = array('data'=>$ThisFileInfo_ogg_comments_raw[$i]['data'], 'image_mime'=>$ThisFileInfo_ogg_comments_raw[$i]['image_mime']); - } - } while (false); + + $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value']; } - } else { $info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring; } + unset($ThisFileInfo_ogg_comments_raw[$i]); } + unset($ThisFileInfo_ogg_comments_raw); // Replay Gain Adjustment @@ -647,12 +759,12 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 } } - fseek($this->getid3->fp, $OriginalOffset, SEEK_SET); + $this->fseek($OriginalOffset); return true; } - static function SpeexBandModeLookup($mode) { + public static function SpeexBandModeLookup($mode) { static $SpeexBandModeLookup = array(); if (empty($SpeexBandModeLookup)) { $SpeexBandModeLookup[0] = 'narrow'; @@ -663,7 +775,7 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 } - static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { + public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { for ($i = 0; $i < $SegmentNumber; $i++) { $segmentlength = 0; foreach ($OggInfoArray['segment_table'] as $key => $value) { @@ -677,7 +789,7 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 } - static function get_quality_from_nominal_bitrate($nominal_bitrate) { + public static function get_quality_from_nominal_bitrate($nominal_bitrate) { // decrease precision $nominal_bitrate = $nominal_bitrate / 1000; @@ -700,6 +812,28 @@ $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 return round($qval, 1); // 5 or 4.9 } -} + public static function TheoraColorSpace($colorspace_id) { + // http://www.theora.org/doc/Theora.pdf (table 6.3) + static $TheoraColorSpaceLookup = array(); + if (empty($TheoraColorSpaceLookup)) { + $TheoraColorSpaceLookup[0] = 'Undefined'; + $TheoraColorSpaceLookup[1] = 'Rec. 470M'; + $TheoraColorSpaceLookup[2] = 'Rec. 470BG'; + $TheoraColorSpaceLookup[3] = 'Reserved'; + } + return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null); + } -?> \ No newline at end of file + public static function TheoraPixelFormat($pixelformat_id) { + // http://www.theora.org/doc/Theora.pdf (table 6.4) + static $TheoraPixelFormatLookup = array(); + if (empty($TheoraPixelFormatLookup)) { + $TheoraPixelFormatLookup[0] = '4:2:0'; + $TheoraPixelFormatLookup[1] = 'Reserved'; + $TheoraPixelFormatLookup[2] = '4:2:2'; + $TheoraPixelFormatLookup[3] = '4:4:4'; + } + return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null); + } + +} diff --git a/app/library/getid3/module.audio.optimfrog.php b/app/library/getid3/getid3/module.audio.optimfrog.php old mode 100644 new mode 100755 similarity index 89% rename from app/library/getid3/module.audio.optimfrog.php rename to app/library/getid3/getid3/module.audio.optimfrog.php index c1c89638..19d2723b --- a/app/library/getid3/module.audio.optimfrog.php +++ b/app/library/getid3/getid3/module.audio.optimfrog.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -18,7 +19,7 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', class getid3_optimfrog extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'ofr'; @@ -26,8 +27,8 @@ class getid3_optimfrog extends getid3_handler $info['audio']['bitrate_mode'] = 'vbr'; $info['audio']['lossless'] = true; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $OFRheader = fread($this->getid3->fp, 8); + $this->fseek($info['avdataoffset']); + $OFRheader = $this->fread(8); if (substr($OFRheader, 0, 5) == '*RIFF') { return $this->ParseOptimFROGheader42(); @@ -44,12 +45,12 @@ class getid3_optimfrog extends getid3_handler } - function ParseOptimFROGheader42() { + public function ParseOptimFROGheader42() { // for fileformat of v4.21 and older $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $OptimFROGheaderData = fread($this->getid3->fp, 45); + $this->fseek($info['avdataoffset']); + $OptimFROGheaderData = $this->fread(45); $info['avdataoffset'] = 45; $OptimFROGencoderVersion_raw = getid3_lib::LittleEndian2Int(substr($OptimFROGheaderData, 0, 1)); @@ -61,8 +62,8 @@ class getid3_optimfrog extends getid3_handler if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { $info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); - fseek($this->getid3->fp, $info['avdataend'], SEEK_SET); - $RIFFdata .= fread($this->getid3->fp, $OrignalRIFFheaderSize - $OrignalRIFFdataSize); + $this->fseek($info['avdataend']); + $RIFFdata .= $this->fread($OrignalRIFFheaderSize - $OrignalRIFFdataSize); } // move the data chunk after all other chunks (if any) @@ -91,15 +92,15 @@ class getid3_optimfrog extends getid3_handler } - function ParseOptimFROGheader45() { + public function ParseOptimFROGheader45() { // for fileformat of v4.50a and higher $info = &$this->getid3->info; $RIFFdata = ''; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - while (!feof($this->getid3->fp) && (ftell($this->getid3->fp) < $info['avdataend'])) { - $BlockOffset = ftell($this->getid3->fp); - $BlockData = fread($this->getid3->fp, 8); + $this->fseek($info['avdataoffset']); + while (!feof($this->getid3->fp) && ($this->ftell() < $info['avdataend'])) { + $BlockOffset = $this->ftell(); + $BlockData = $this->fread(8); $offset = 8; $BlockName = substr($BlockData, 0, 4); $BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4)); @@ -130,7 +131,7 @@ class getid3_optimfrog extends getid3_handler $info['warning'][] = '"'.$BlockName.'" contains more data than expected (expected 12 or 15 bytes, found '.$BlockSize.' bytes)'; break; } - $BlockData .= fread($this->getid3->fp, $BlockSize); + $BlockData .= $this->fread($BlockSize); $thisfile_ofr_thisblock['total_samples'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 6)); $offset += 6; @@ -186,8 +187,8 @@ class getid3_optimfrog extends getid3_handler } // Only interested in first 14 bytes (only first 12 needed for v4.50 alpha), not actual audio data - $BlockData .= fread($this->getid3->fp, 14); - fseek($this->getid3->fp, $BlockSize - 14, SEEK_CUR); + $BlockData .= $this->fread(14); + $this->fseek($BlockSize - 14, SEEK_CUR); $COMPdata['crc_32'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4)); $offset += 4; @@ -224,7 +225,7 @@ class getid3_optimfrog extends getid3_handler $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - $RIFFdata .= fread($this->getid3->fp, $BlockSize); + $RIFFdata .= $this->fread($BlockSize); break; case 'TAIL': @@ -232,7 +233,7 @@ class getid3_optimfrog extends getid3_handler $thisfile_ofr_thisblock['size'] = $BlockSize; if ($BlockSize > 0) { - $RIFFdata .= fread($this->getid3->fp, $BlockSize); + $RIFFdata .= $this->fread($BlockSize); } break; @@ -242,7 +243,7 @@ class getid3_optimfrog extends getid3_handler $thisfile_ofr_thisblock['offset'] = $BlockOffset; $thisfile_ofr_thisblock['size'] = $BlockSize; - fseek($this->getid3->fp, $BlockSize, SEEK_CUR); + $this->fseek($BlockSize, SEEK_CUR); break; @@ -253,7 +254,7 @@ class getid3_optimfrog extends getid3_handler $thisfile_ofr_thisblock['size'] = $BlockSize; $info['warning'][] = 'APEtag processing inside OptimFROG not supported in this version ('.$this->getid3->version().') of getID3()'; - fseek($this->getid3->fp, $BlockSize, SEEK_CUR); + $this->fseek($BlockSize, SEEK_CUR); break; @@ -265,14 +266,14 @@ class getid3_optimfrog extends getid3_handler if ($BlockSize == 16) { - $thisfile_ofr_thisblock['md5_binary'] = fread($this->getid3->fp, $BlockSize); + $thisfile_ofr_thisblock['md5_binary'] = $this->fread($BlockSize); $thisfile_ofr_thisblock['md5_string'] = getid3_lib::PrintHexBytes($thisfile_ofr_thisblock['md5_binary'], true, false, false); $info['md5_data_source'] = $thisfile_ofr_thisblock['md5_string']; } else { $info['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found '.$BlockSize.' instead'; - fseek($this->getid3->fp, $BlockSize, SEEK_CUR); + $this->fseek($BlockSize, SEEK_CUR); } break; @@ -283,7 +284,7 @@ class getid3_optimfrog extends getid3_handler $thisfile_ofr_thisblock['size'] = $BlockSize; $info['warning'][] = 'Unhandled OptimFROG block type "'.$BlockName.'" at offset '.$thisfile_ofr_thisblock['offset']; - fseek($this->getid3->fp, $BlockSize, SEEK_CUR); + $this->fseek($BlockSize, SEEK_CUR); break; } } @@ -313,7 +314,7 @@ class getid3_optimfrog extends getid3_handler } - static function OptimFROGsampleTypeLookup($SampleType) { + public static function OptimFROGsampleTypeLookup($SampleType) { static $OptimFROGsampleTypeLookup = array( 0 => 'unsigned int (8-bit)', 1 => 'signed int (8-bit)', @@ -330,7 +331,7 @@ class getid3_optimfrog extends getid3_handler return (isset($OptimFROGsampleTypeLookup[$SampleType]) ? $OptimFROGsampleTypeLookup[$SampleType] : false); } - static function OptimFROGbitsPerSampleTypeLookup($SampleType) { + public static function OptimFROGbitsPerSampleTypeLookup($SampleType) { static $OptimFROGbitsPerSampleTypeLookup = array( 0 => 8, 1 => 8, @@ -347,7 +348,7 @@ class getid3_optimfrog extends getid3_handler return (isset($OptimFROGbitsPerSampleTypeLookup[$SampleType]) ? $OptimFROGbitsPerSampleTypeLookup[$SampleType] : false); } - static function OptimFROGchannelConfigurationLookup($ChannelConfiguration) { + public static function OptimFROGchannelConfigurationLookup($ChannelConfiguration) { static $OptimFROGchannelConfigurationLookup = array( 0 => 'mono', 1 => 'stereo' @@ -355,7 +356,7 @@ class getid3_optimfrog extends getid3_handler return (isset($OptimFROGchannelConfigurationLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigurationLookup[$ChannelConfiguration] : false); } - static function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration) { + public static function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration) { static $OptimFROGchannelConfigNumChannelsLookup = array( 0 => 1, 1 => 2 @@ -371,7 +372,7 @@ class getid3_optimfrog extends getid3_handler // } - static function OptimFROGencoderNameLookup($EncoderID) { + public static function OptimFROGencoderNameLookup($EncoderID) { // version = (encoderID >> 4) + 4500 // system = encoderID & 0xF @@ -386,7 +387,7 @@ class getid3_optimfrog extends getid3_handler return $EncoderVersion.' ('.(isset($OptimFROGencoderSystemLookup[$EncoderSystemID]) ? $OptimFROGencoderSystemLookup[$EncoderSystemID] : 'undefined encoder type (0x'.dechex($EncoderSystemID).')').')'; } - static function OptimFROGcompressionLookup($CompressionID) { + public static function OptimFROGcompressionLookup($CompressionID) { // mode = compression >> 3 // speedup = compression & 0x07 @@ -408,7 +409,7 @@ class getid3_optimfrog extends getid3_handler return (isset($OptimFROGencoderModeLookup[$CompressionModeID]) ? $OptimFROGencoderModeLookup[$CompressionModeID] : 'undefined mode (0x'.str_pad(dechex($CompressionModeID), 2, '0', STR_PAD_LEFT).')'); } - static function OptimFROGspeedupLookup($CompressionID) { + public static function OptimFROGspeedupLookup($CompressionID) { // mode = compression >> 3 // speedup = compression & 0x07 @@ -424,6 +425,3 @@ class getid3_optimfrog extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.rkau.php b/app/library/getid3/getid3/module.audio.rkau.php old mode 100644 new mode 100755 similarity index 94% rename from app/library/getid3/module.audio.rkau.php rename to app/library/getid3/getid3/module.audio.rkau.php index c4420762..598b13ed --- a/app/library/getid3/module.audio.rkau.php +++ b/app/library/getid3/getid3/module.audio.rkau.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,11 +18,11 @@ class getid3_rkau extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $RKAUHeader = fread($this->getid3->fp, 20); + $this->fseek($info['avdataoffset']); + $RKAUHeader = $this->fread(20); $magic = 'RKA'; if (substr($RKAUHeader, 0, 3) != $magic) { $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($RKAUHeader, 0, 3)).'"'; @@ -76,7 +77,7 @@ class getid3_rkau extends getid3_handler } - function RKAUqualityLookup(&$RKAUdata) { + public function RKAUqualityLookup(&$RKAUdata) { $level = ($RKAUdata['raw']['quality'] & 0xF0) >> 4; $quality = $RKAUdata['raw']['quality'] & 0x0F; @@ -90,5 +91,3 @@ class getid3_rkau extends getid3_handler } } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.shorten.php b/app/library/getid3/getid3/module.audio.shorten.php old mode 100644 new mode 100755 similarity index 92% rename from app/library/getid3/module.audio.shorten.php rename to app/library/getid3/getid3/module.audio.shorten.php index 7b3d312e..970559a1 --- a/app/library/getid3/module.audio.shorten.php +++ b/app/library/getid3/getid3/module.audio.shorten.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,12 +18,12 @@ class getid3_shorten extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); - $ShortenHeader = fread($this->getid3->fp, 8); + $ShortenHeader = $this->fread(8); $magic = 'ajkg'; if (substr($ShortenHeader, 0, 4) != $magic) { $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($ShortenHeader, 0, 4)).'"'; @@ -35,14 +36,14 @@ class getid3_shorten extends getid3_handler $info['shn']['version'] = getid3_lib::LittleEndian2Int(substr($ShortenHeader, 4, 1)); - fseek($this->getid3->fp, $info['avdataend'] - 12, SEEK_SET); - $SeekTableSignatureTest = fread($this->getid3->fp, 12); + $this->fseek($info['avdataend'] - 12); + $SeekTableSignatureTest = $this->fread(12); $info['shn']['seektable']['present'] = (bool) (substr($SeekTableSignatureTest, 4, 8) == 'SHNAMPSK'); if ($info['shn']['seektable']['present']) { $info['shn']['seektable']['length'] = getid3_lib::LittleEndian2Int(substr($SeekTableSignatureTest, 0, 4)); $info['shn']['seektable']['offset'] = $info['avdataend'] - $info['shn']['seektable']['length']; - fseek($this->getid3->fp, $info['shn']['seektable']['offset'], SEEK_SET); - $SeekTableMagic = fread($this->getid3->fp, 4); + $this->fseek($info['shn']['seektable']['offset']); + $SeekTableMagic = $this->fread(4); $magic = 'SEEK'; if ($SeekTableMagic != $magic) { @@ -67,7 +68,7 @@ class getid3_shorten extends getid3_handler // long Offset1[4]; // }TSeekEntry; - $SeekTableData = fread($this->getid3->fp, $info['shn']['seektable']['length'] - 16); + $SeekTableData = $this->fread($info['shn']['seektable']['length'] - 16); $info['shn']['seektable']['entry_count'] = floor(strlen($SeekTableData) / 80); //$info['shn']['seektable']['entries'] = array(); //$SeekTableOffset = 0; @@ -150,7 +151,7 @@ class getid3_shorten extends getid3_handler getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); $fmt_size = getid3_lib::LittleEndian2Int(substr($output, 16, 4)); - $DecodedWAVFORMATEX = getid3_riff::RIFFparseWAVEFORMATex(substr($output, 20, $fmt_size)); + $DecodedWAVFORMATEX = getid3_riff::parseWAVEFORMATex(substr($output, 20, $fmt_size)); $info['audio']['channels'] = $DecodedWAVFORMATEX['channels']; $info['audio']['bits_per_sample'] = $DecodedWAVFORMATEX['bits_per_sample']; $info['audio']['sample_rate'] = $DecodedWAVFORMATEX['sample_rate']; @@ -179,5 +180,3 @@ class getid3_shorten extends getid3_handler } } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.tta.php b/app/library/getid3/getid3/module.audio.tta.php old mode 100644 new mode 100755 similarity index 95% rename from app/library/getid3/module.audio.tta.php rename to app/library/getid3/getid3/module.audio.tta.php index 1c646ee6..e5c7e1b2 --- a/app/library/getid3/module.audio.tta.php +++ b/app/library/getid3/getid3/module.audio.tta.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,7 +18,7 @@ class getid3_tta extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'tta'; @@ -25,8 +26,8 @@ class getid3_tta extends getid3_handler $info['audio']['lossless'] = true; $info['audio']['bitrate_mode'] = 'vbr'; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $ttaheader = fread($this->getid3->fp, 26); + $this->fseek($info['avdataoffset']); + $ttaheader = $this->fread(26); $info['tta']['magic'] = substr($ttaheader, 0, 3); $magic = 'TTA'; @@ -77,7 +78,7 @@ class getid3_tta extends getid3_handler $info['tta']['major_version'] = 3; $info['avdataoffset'] += 26; - $info['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2)); // getid3_riff::RIFFwFormatTagLookup() + $info['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2)); // getid3_riff::wFormatTagLookup() $info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2)); $info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 2)); $info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 10, 4)); @@ -104,6 +105,3 @@ class getid3_tta extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.voc.php b/app/library/getid3/getid3/module.audio.voc.php old mode 100644 new mode 100755 similarity index 88% rename from app/library/getid3/module.audio.voc.php rename to app/library/getid3/getid3/module.audio.voc.php index 1186a4cc..ecd33364 --- a/app/library/getid3/module.audio.voc.php +++ b/app/library/getid3/getid3/module.audio.voc.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,12 +18,12 @@ class getid3_voc extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $OriginalAVdataOffset = $info['avdataoffset']; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $VOCheader = fread($this->getid3->fp, 26); + $this->fseek($info['avdataoffset']); + $VOCheader = $this->fread(26); $magic = 'Creative Voice File'; if (substr($VOCheader, 0, 19) != $magic) { @@ -56,8 +57,8 @@ class getid3_voc extends getid3_handler do { - $BlockOffset = ftell($this->getid3->fp); - $BlockData = fread($this->getid3->fp, 4); + $BlockOffset = $this->ftell(); + $BlockData = $this->fread(4); $BlockType = ord($BlockData{0}); $BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 1, 3)); $ThisBlock = array(); @@ -69,11 +70,11 @@ class getid3_voc extends getid3_handler break; case 1: // Sound data - $BlockData .= fread($this->getid3->fp, 2); + $BlockData .= $this->fread(2); if ($info['avdataoffset'] <= $OriginalAVdataOffset) { - $info['avdataoffset'] = ftell($this->getid3->fp); + $info['avdataoffset'] = $this->ftell(); } - fseek($this->getid3->fp, $BlockSize - 2, SEEK_CUR); + $this->fseek($BlockSize - 2, SEEK_CUR); $ThisBlock['sample_rate_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 1)); $ThisBlock['compression_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, 5, 1)); @@ -96,11 +97,11 @@ class getid3_voc extends getid3_handler case 6: // Repeat case 7: // End repeat // nothing useful, just skip - fseek($this->getid3->fp, $BlockSize, SEEK_CUR); + $this->fseek($BlockSize, SEEK_CUR); break; case 8: // Extended - $BlockData .= fread($this->getid3->fp, 4); + $BlockData .= $this->fread(4); //00-01 Time Constant: // Mono: 65536 - (256000000 / sample_rate) @@ -114,11 +115,11 @@ class getid3_voc extends getid3_handler break; case 9: // data block that supersedes blocks 1 and 8. Used for stereo, 16 bit - $BlockData .= fread($this->getid3->fp, 12); + $BlockData .= $this->fread(12); if ($info['avdataoffset'] <= $OriginalAVdataOffset) { - $info['avdataoffset'] = ftell($this->getid3->fp); + $info['avdataoffset'] = $this->ftell(); } - fseek($this->getid3->fp, $BlockSize - 12, SEEK_CUR); + $this->fseek($BlockSize - 12, SEEK_CUR); $ThisBlock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4)); $ThisBlock['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($BlockData, 8, 1)); @@ -137,7 +138,7 @@ class getid3_voc extends getid3_handler default: $info['warning'][] = 'Unhandled block type "'.$BlockType.'" at offset '.$BlockOffset; - fseek($this->getid3->fp, $BlockSize, SEEK_CUR); + $this->fseek($BlockSize, SEEK_CUR); break; } @@ -151,7 +152,7 @@ class getid3_voc extends getid3_handler } while (!feof($this->getid3->fp) && ($BlockType != 0)); // Terminator block doesn't have size field, so seek back 3 spaces - fseek($this->getid3->fp, -3, SEEK_CUR); + $this->fseek(-3, SEEK_CUR); ksort($thisfile_voc['blocktypes']); @@ -163,7 +164,7 @@ class getid3_voc extends getid3_handler return true; } - function VOCcompressionTypeLookup($index) { + public function VOCcompressionTypeLookup($index) { static $VOCcompressionTypeLookup = array( 0 => '8-bit', 1 => '4-bit', @@ -173,7 +174,7 @@ class getid3_voc extends getid3_handler return (isset($VOCcompressionTypeLookup[$index]) ? $VOCcompressionTypeLookup[$index] : 'Multi DAC ('.($index - 3).') channels'); } - function VOCwFormatLookup($index) { + public function VOCwFormatLookup($index) { static $VOCwFormatLookup = array( 0x0000 => '8-bit unsigned PCM', 0x0001 => 'Creative 8-bit to 4-bit ADPCM', @@ -187,21 +188,18 @@ class getid3_voc extends getid3_handler return (isset($VOCwFormatLookup[$index]) ? $VOCwFormatLookup[$index] : false); } - function VOCwFormatActualBitsPerSampleLookup($index) { + public function VOCwFormatActualBitsPerSampleLookup($index) { static $VOCwFormatLookup = array( - 0x0000 => 8, - 0x0001 => 4, - 0x0002 => 3, - 0x0003 => 2, + 0x0000 => 8, + 0x0001 => 4, + 0x0002 => 3, + 0x0003 => 2, 0x0004 => 16, - 0x0006 => 8, - 0x0007 => 8, - 0x2000 => 4 + 0x0006 => 8, + 0x0007 => 8, + 0x2000 => 4 ); return (isset($VOCwFormatLookup[$index]) ? $VOCwFormatLookup[$index] : false); } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.vqf.php b/app/library/getid3/getid3/module.audio.vqf.php old mode 100644 new mode 100755 similarity index 90% rename from app/library/getid3/module.audio.vqf.php rename to app/library/getid3/getid3/module.audio.vqf.php index dc6ff5ec..51d0e830 --- a/app/library/getid3/module.audio.vqf.php +++ b/app/library/getid3/getid3/module.audio.vqf.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -16,10 +17,10 @@ class getid3_vqf extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - // based loosely on code from TTwinVQ by Jurgen Faul + // based loosely on code from TTwinVQ by Jurgen Faul // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html $info['fileformat'] = 'vqf'; @@ -32,8 +33,8 @@ class getid3_vqf extends getid3_handler $thisfile_vqf = &$info['vqf']; $thisfile_vqf_raw = &$thisfile_vqf['raw']; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $VQFheaderData = fread($this->getid3->fp, 16); + $this->fseek($info['avdataoffset']); + $VQFheaderData = $this->fread(16); $offset = 0; $thisfile_vqf_raw['header_tag'] = substr($VQFheaderData, $offset, 4); @@ -50,11 +51,11 @@ class getid3_vqf extends getid3_handler $thisfile_vqf_raw['size'] = getid3_lib::BigEndian2Int(substr($VQFheaderData, $offset, 4)); $offset += 4; - while (ftell($this->getid3->fp) < $info['avdataend']) { + while ($this->ftell() < $info['avdataend']) { - $ChunkBaseOffset = ftell($this->getid3->fp); + $ChunkBaseOffset = $this->ftell(); $chunkoffset = 0; - $ChunkData = fread($this->getid3->fp, 8); + $ChunkData = $this->fread(8); $ChunkName = substr($ChunkData, $chunkoffset, 4); if ($ChunkName == 'DATA') { $info['avdataoffset'] = $ChunkBaseOffset; @@ -63,12 +64,12 @@ class getid3_vqf extends getid3_handler $chunkoffset += 4; $ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); $chunkoffset += 4; - if ($ChunkSize > ($info['avdataend'] - ftell($this->getid3->fp))) { + if ($ChunkSize > ($info['avdataend'] - $this->ftell())) { $info['error'][] = 'Invalid chunk size ('.$ChunkSize.') for chunk "'.$ChunkName.'" at offset '.$ChunkBaseOffset; break; } if ($ChunkSize > 0) { - $ChunkData .= fread($this->getid3->fp, $ChunkSize); + $ChunkData .= $this->fread($ChunkSize); } switch ($ChunkName) { @@ -135,7 +136,7 @@ class getid3_vqf extends getid3_handler return true; } - function VQFchannelFrequencyLookup($frequencyid) { + public function VQFchannelFrequencyLookup($frequencyid) { static $VQFchannelFrequencyLookup = array( 11 => 11025, 22 => 22050, @@ -144,7 +145,7 @@ class getid3_vqf extends getid3_handler return (isset($VQFchannelFrequencyLookup[$frequencyid]) ? $VQFchannelFrequencyLookup[$frequencyid] : $frequencyid * 1000); } - function VQFcommentNiceNameLookup($shortname) { + public function VQFcommentNiceNameLookup($shortname) { static $VQFcommentNiceNameLookup = array( 'NAME' => 'title', 'AUTH' => 'artist', @@ -157,6 +158,3 @@ class getid3_vqf extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.wavpack.php b/app/library/getid3/getid3/module.audio.wavpack.php old mode 100644 new mode 100755 similarity index 94% rename from app/library/getid3/module.audio.wavpack.php rename to app/library/getid3/getid3/module.audio.wavpack.php index 6ab5b438..a6a1a475 --- a/app/library/getid3/module.audio.wavpack.php +++ b/app/library/getid3/getid3/module.audio.wavpack.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,16 +18,16 @@ class getid3_wavpack extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); while (true) { - $wavpackheader = fread($this->getid3->fp, 32); + $wavpackheader = $this->fread(32); - if (ftell($this->getid3->fp) >= $info['avdataend']) { + if ($this->ftell() >= $info['avdataend']) { break; } elseif (feof($this->getid3->fp)) { break; @@ -40,7 +41,7 @@ class getid3_wavpack extends getid3_handler break; } - $blockheader_offset = ftell($this->getid3->fp) - 32; + $blockheader_offset = $this->ftell() - 32; $blockheader_magic = substr($wavpackheader, 0, 4); $blockheader_size = getid3_lib::LittleEndian2Int(substr($wavpackheader, 4, 4)); @@ -143,10 +144,10 @@ class getid3_wavpack extends getid3_handler $info['audio']['lossless'] = !$info['wavpack']['blockheader']['flags']['hybrid']; } - while (!feof($this->getid3->fp) && (ftell($this->getid3->fp) < ($blockheader_offset + $blockheader_size + 8))) { + while (!feof($this->getid3->fp) && ($this->ftell() < ($blockheader_offset + $blockheader_size + 8))) { - $metablock = array('offset'=>ftell($this->getid3->fp)); - $metablockheader = fread($this->getid3->fp, 2); + $metablock = array('offset'=>$this->ftell()); + $metablockheader = $this->fread(2); if (feof($this->getid3->fp)) { break; } @@ -166,7 +167,7 @@ class getid3_wavpack extends getid3_handler $metablock['padded_data'] = (bool) ($metablock['id'] & 0x40); $metablock['large_block'] = (bool) ($metablock['id'] & 0x80); if ($metablock['large_block']) { - $metablockheader .= fread($this->getid3->fp, 2); + $metablockheader .= $this->fread(2); } $metablock['size'] = getid3_lib::LittleEndian2Int(substr($metablockheader, 1)) * 2; // size is stored in words $metablock['data'] = null; @@ -180,7 +181,7 @@ class getid3_wavpack extends getid3_handler case 0x24: // ID_CUESHEET case 0x25: // ID_CONFIG_BLOCK case 0x26: // ID_MD5_CHECKSUM - $metablock['data'] = fread($this->getid3->fp, $metablock['size']); + $metablock['data'] = $this->fread($metablock['size']); if ($metablock['padded_data']) { // padded to the nearest even byte @@ -203,12 +204,12 @@ class getid3_wavpack extends getid3_handler case 0x0B: // ID_WVC_BITSTREAM case 0x0C: // ID_WVX_BITSTREAM case 0x0D: // ID_CHANNEL_INFO - fseek($this->getid3->fp, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET); + $this->fseek($metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size']); break; default: $info['warning'][] = 'Unexpected metablock type "0x'.str_pad(dechex($metablock['function_id']), 2, '0', STR_PAD_LEFT).'" at offset '.$metablock['offset']; - fseek($this->getid3->fp, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET); + $this->fseek($metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size']); break; } @@ -242,12 +243,12 @@ class getid3_wavpack extends getid3_handler $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_temp->info['avdataend'] = $info['avdataend']; - $getid3_temp->info['fileformat'] = 'riff'; + //$getid3_temp->info['fileformat'] = 'riff'; $getid3_riff = new getid3_riff($getid3_temp); $metablock['riff'] = $getid3_riff->ParseRIFF($startoffset, $startoffset + $metablock['size']); if (!empty($metablock['riff']['INFO'])) { - $getid3_riff->RIFFcommentsParse($metablock['riff']['INFO'], $metablock['comments']); + getid3_riff::parseComments($metablock['riff']['INFO'], $metablock['comments']); $info['tags']['riff'] = $metablock['comments']; } unset($getid3_temp, $getid3_riff); @@ -368,7 +369,7 @@ class getid3_wavpack extends getid3_handler } - function WavPackMetablockNameLookup(&$id) { + public function WavPackMetablockNameLookup(&$id) { static $WavPackMetablockNameLookup = array( 0x00 => 'Dummy', 0x01 => 'Encoder Info', @@ -395,6 +396,3 @@ class getid3_wavpack extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.graphic.bmp.php b/app/library/getid3/getid3/module.graphic.bmp.php old mode 100644 new mode 100755 similarity index 97% rename from app/library/getid3/module.graphic.bmp.php rename to app/library/getid3/getid3/module.graphic.bmp.php index 5ac671fb..02da2cc5 --- a/app/library/getid3/module.graphic.bmp.php +++ b/app/library/getid3/getid3/module.graphic.bmp.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -16,10 +17,10 @@ class getid3_bmp extends getid3_handler { - var $ExtractPalette = false; - var $ExtractData = false; + public $ExtractPalette = false; + public $ExtractData = false; - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; // shortcuts @@ -36,9 +37,9 @@ class getid3_bmp extends getid3_handler // WORD bfReserved2; // DWORD bfOffBits; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); $offset = 0; - $BMPheader = fread($this->getid3->fp, 14 + 40); + $BMPheader = $this->fread(14 + 40); $thisfile_bmp_header_raw['identifier'] = substr($BMPheader, $offset, 2); $offset += 2; @@ -220,7 +221,7 @@ class getid3_bmp extends getid3_handler if (($thisfile_bmp['type_version'] >= 4) || ($thisfile_bmp_header_raw['compression'] == 3)) { // should only be v4+, but BMPs with type_version==1 and BI_BITFIELDS compression have been seen - $BMPheader .= fread($this->getid3->fp, 44); + $BMPheader .= $this->fread(44); // BITMAPV4HEADER - [44 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_2k1e.asp // Win95+, WinNT4.0+ @@ -262,7 +263,7 @@ class getid3_bmp extends getid3_handler } if ($thisfile_bmp['type_version'] >= 5) { - $BMPheader .= fread($this->getid3->fp, 16); + $BMPheader .= $this->fread(16); // BITMAPV5HEADER - [16 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_7c36.asp // Win98+, Win2000+ @@ -296,7 +297,7 @@ class getid3_bmp extends getid3_handler $PaletteEntries = $thisfile_bmp_header_raw['colors_used']; } if ($PaletteEntries > 0) { - $BMPpalette = fread($this->getid3->fp, 4 * $PaletteEntries); + $BMPpalette = $this->fread(4 * $PaletteEntries); $paletteoffset = 0; for ($i = 0; $i < $PaletteEntries; $i++) { // RGBQUAD - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_5f8y.asp @@ -318,9 +319,9 @@ class getid3_bmp extends getid3_handler } if ($this->ExtractData) { - fseek($this->getid3->fp, $thisfile_bmp_header_raw['data_offset'], SEEK_SET); + $this->fseek($thisfile_bmp_header_raw['data_offset']); $RowByteLength = ceil(($thisfile_bmp_header_raw['width'] * ($thisfile_bmp_header_raw['bits_per_pixel'] / 8)) / 4) * 4; // round up to nearest DWORD boundry - $BMPpixelData = fread($this->getid3->fp, $thisfile_bmp_header_raw['height'] * $RowByteLength); + $BMPpixelData = $this->fread($thisfile_bmp_header_raw['height'] * $RowByteLength); $pixeldataoffset = 0; $thisfile_bmp_header_raw['compression'] = (isset($thisfile_bmp_header_raw['compression']) ? $thisfile_bmp_header_raw['compression'] : ''); switch ($thisfile_bmp_header_raw['compression']) { @@ -625,7 +626,7 @@ class getid3_bmp extends getid3_handler } - function PlotBMP(&$BMPinfo) { + public function PlotBMP(&$BMPinfo) { $starttime = time(); if (!isset($BMPinfo['bmp']['data']) || !is_array($BMPinfo['bmp']['data'])) { echo 'ERROR: no pixel data
'; @@ -661,7 +662,7 @@ class getid3_bmp extends getid3_handler return false; } - function BMPcompressionWindowsLookup($compressionid) { + public function BMPcompressionWindowsLookup($compressionid) { static $BMPcompressionWindowsLookup = array( 0 => 'BI_RGB', 1 => 'BI_RLE8', @@ -673,7 +674,7 @@ class getid3_bmp extends getid3_handler return (isset($BMPcompressionWindowsLookup[$compressionid]) ? $BMPcompressionWindowsLookup[$compressionid] : 'invalid'); } - function BMPcompressionOS2Lookup($compressionid) { + public function BMPcompressionOS2Lookup($compressionid) { static $BMPcompressionOS2Lookup = array( 0 => 'BI_RGB', 1 => 'BI_RLE8', @@ -685,6 +686,3 @@ class getid3_bmp extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.graphic.efax.php b/app/library/getid3/getid3/module.graphic.efax.php old mode 100644 new mode 100755 similarity index 93% rename from app/library/getid3/module.graphic.efax.php rename to app/library/getid3/getid3/module.graphic.efax.php index 2a57302e..b49d7a29 --- a/app/library/getid3/module.graphic.efax.php +++ b/app/library/getid3/getid3/module.graphic.efax.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,11 +18,11 @@ class getid3_efax extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $efaxheader = fread($this->getid3->fp, 1024); + $this->fseek($info['avdataoffset']); + $efaxheader = $this->fread(1024); $info['efax']['header']['magic'] = substr($efaxheader, 0, 2); if ($info['efax']['header']['magic'] != "\xDC\xFE") { @@ -48,6 +49,3 @@ return false; } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.graphic.gif.php b/app/library/getid3/getid3/module.graphic.gif.php old mode 100644 new mode 100755 similarity index 89% rename from app/library/getid3/module.graphic.gif.php rename to app/library/getid3/getid3/module.graphic.gif.php index 0b3e1379..67ef73b4 --- a/app/library/getid3/module.graphic.gif.php +++ b/app/library/getid3/getid3/module.graphic.gif.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,7 +18,7 @@ class getid3_gif extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'gif'; @@ -25,8 +26,8 @@ class getid3_gif extends getid3_handler $info['video']['lossless'] = true; $info['video']['pixel_aspect_ratio'] = (float) 1; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $GIFheader = fread($this->getid3->fp, 13); + $this->fseek($info['avdataoffset']); + $GIFheader = $this->fread(13); $offset = 0; $info['gif']['header']['raw']['identifier'] = substr($GIFheader, $offset, 3); @@ -78,7 +79,7 @@ class getid3_gif extends getid3_handler } // if ($info['gif']['header']['flags']['global_color_table']) { -// $GIFcolorTable = fread($this->getid3->fp, 3 * $info['gif']['header']['global_color_size']); +// $GIFcolorTable = $this->fread(3 * $info['gif']['header']['global_color_size']); // $offset = 0; // for ($i = 0; $i < $info['gif']['header']['global_color_size']; $i++) { // $red = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1)); @@ -90,12 +91,12 @@ class getid3_gif extends getid3_handler // // // Image Descriptor // while (!feof($this->getid3->fp)) { -// $NextBlockTest = fread($this->getid3->fp, 1); +// $NextBlockTest = $this->fread(1); // switch ($NextBlockTest) { // // case ',': // ',' - Image separator character // -// $ImageDescriptorData = $NextBlockTest.fread($this->getid3->fp, 9); +// $ImageDescriptorData = $NextBlockTest.$this->fread(9); // $ImageDescriptor = array(); // $ImageDescriptor['image_left'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 1, 2)); // $ImageDescriptor['image_top'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 3, 2)); @@ -112,10 +113,10 @@ class getid3_gif extends getid3_handler // return true; // // } -//echo 'Start of raster data: '.ftell($this->getid3->fp).'
'; +//echo 'Start of raster data: '.$this->ftell().'
'; // $RasterData = array(); -// $RasterData['code_size'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1)); -// $RasterData['block_byte_count'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1)); +// $RasterData['code_size'] = getid3_lib::LittleEndian2Int($this->fread(1)); +// $RasterData['block_byte_count'] = getid3_lib::LittleEndian2Int($this->fread(1)); // $info['gif']['raster_data'][count($info['gif']['image_descriptor']) - 1] = $RasterData; // // $CurrentCodeSize = $RasterData['code_size'] + 1; @@ -143,16 +144,16 @@ class getid3_gif extends getid3_handler // // case '!': // // GIF Extension Block -// $ExtensionBlockData = $NextBlockTest.fread($this->getid3->fp, 2); +// $ExtensionBlockData = $NextBlockTest.$this->fread(2); // $ExtensionBlock = array(); // $ExtensionBlock['function_code'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 1, 1)); // $ExtensionBlock['byte_length'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 2, 1)); -// $ExtensionBlock['data'] = fread($this->getid3->fp, $ExtensionBlock['byte_length']); +// $ExtensionBlock['data'] = $this->fread($ExtensionBlock['byte_length']); // $info['gif']['extension_blocks'][] = $ExtensionBlock; // break; // // case ';': -// $info['gif']['terminator_offset'] = ftell($this->getid3->fp) - 1; +// $info['gif']['terminator_offset'] = $this->ftell() - 1; // // GIF Terminator // break; // @@ -167,10 +168,10 @@ class getid3_gif extends getid3_handler } - function GetLSBits($bits) { + public function GetLSBits($bits) { static $bitbuffer = ''; while (strlen($bitbuffer) < $bits) { - $bitbuffer = str_pad(decbin(ord(fread($this->getid3->fp, 1))), 8, '0', STR_PAD_LEFT).$bitbuffer; + $bitbuffer = str_pad(decbin(ord($this->fread(1))), 8, '0', STR_PAD_LEFT).$bitbuffer; } $value = bindec(substr($bitbuffer, 0 - $bits)); $bitbuffer = substr($bitbuffer, 0, 0 - $bits); @@ -179,6 +180,3 @@ class getid3_gif extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.graphic.jpg.php b/app/library/getid3/getid3/module.graphic.jpg.php old mode 100644 new mode 100755 similarity index 84% rename from app/library/getid3/module.graphic.jpg.php rename to app/library/getid3/getid3/module.graphic.jpg.php index 153ebcd0..64a019c3 --- a/app/library/getid3/module.graphic.jpg.php +++ b/app/library/getid3/getid3/module.graphic.jpg.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -19,7 +20,7 @@ class getid3_jpg extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'jpg'; @@ -28,10 +29,11 @@ class getid3_jpg extends getid3_handler $info['video']['bits_per_sample'] = 24; $info['video']['pixel_aspect_ratio'] = (float) 1; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); $imageinfo = array(); - list($width, $height, $type) = getid3_lib::GetDataImageSize(fread($this->getid3->fp, $info['filesize']), $imageinfo); + //list($width, $height, $type) = getid3_lib::GetDataImageSize($this->fread($info['filesize']), $imageinfo); + list($width, $height, $type) = getimagesize($info['filenamepath'], $imageinfo); // http://www.getid3.org/phpBB3/viewtopic.php?t=1474 if (isset($imageinfo['APP13'])) { @@ -45,10 +47,10 @@ class getid3_jpg extends getid3_handler foreach ($iptc_values as $key => $value) { $IPTCrecordName = $this->IPTCrecordName($iptc_record); $IPTCrecordTagName = $this->IPTCrecordTagName($iptc_record, $iptc_tagkey); - if (isset($info['iptc'][$IPTCrecordName][$IPTCrecordTagName])) { - $info['iptc'][$IPTCrecordName][$IPTCrecordTagName][] = $value; + if (isset($info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName])) { + $info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName][] = $value; } else { - $info['iptc'][$IPTCrecordName][$IPTCrecordTagName] = array($value); + $info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName] = array($value); } } } @@ -64,7 +66,9 @@ class getid3_jpg extends getid3_handler if (isset($imageinfo['APP1'])) { if (function_exists('exif_read_data')) { if (substr($imageinfo['APP1'], 0, 4) == 'Exif') { - $info['jpg']['exif'] = @exif_read_data($info['filenamepath'], '', true, false); +//$info['warning'][] = 'known issue: https://bugs.php.net/bug.php?id=62523'; +//return false; + $info['jpg']['exif'] = exif_read_data($info['filenamepath'], null, true, false); } else { $info['warning'][] = 'exif_read_data() cannot parse non-EXIF data in APP1 (expected "Exif", found "'.substr($imageinfo['APP1'], 0, 4).'")'; } @@ -105,19 +109,13 @@ class getid3_jpg extends getid3_handler $computed_time[3] = (isset($explodedGPSDateStamp[1]) ? $explodedGPSDateStamp[1] : ''); $computed_time[4] = (isset($explodedGPSDateStamp[2]) ? $explodedGPSDateStamp[2] : ''); - if (function_exists('date_default_timezone_set')) { - date_default_timezone_set('UTC'); - } else { - ini_set('date.timezone', 'UTC'); - } - $computed_time = array(0=>0, 1=>0, 2=>0, 3=>0, 4=>0, 5=>0); if (isset($info['jpg']['exif']['GPS']['GPSTimeStamp']) && is_array($info['jpg']['exif']['GPS']['GPSTimeStamp'])) { foreach ($info['jpg']['exif']['GPS']['GPSTimeStamp'] as $key => $value) { $computed_time[$key] = getid3_lib::DecimalizeFraction($value); } } - $info['jpg']['exif']['GPS']['computed']['timestamp'] = mktime($computed_time[0], $computed_time[1], $computed_time[2], $computed_time[3], $computed_time[4], $computed_time[5]); + $info['jpg']['exif']['GPS']['computed']['timestamp'] = gmmktime($computed_time[0], $computed_time[1], $computed_time[2], $computed_time[3], $computed_time[4], $computed_time[5]); } if (isset($info['jpg']['exif']['GPS']['GPSLatitude']) && is_array($info['jpg']['exif']['GPS']['GPSLatitude'])) { @@ -135,22 +133,27 @@ class getid3_jpg extends getid3_handler } $info['jpg']['exif']['GPS']['computed']['longitude'] = $direction_multiplier * ($computed_longitude[0] + ($computed_longitude[1] / 60) + ($computed_longitude[2] / 3600)); } - + if (isset($info['jpg']['exif']['GPS']['GPSAltitudeRef'])) { + $info['jpg']['exif']['GPS']['GPSAltitudeRef'] = ord($info['jpg']['exif']['GPS']['GPSAltitudeRef']); // 0 = above sea level; 1 = below sea level + } if (isset($info['jpg']['exif']['GPS']['GPSAltitude'])) { - $direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSAltitudeRef']) && ($info['jpg']['exif']['GPS']['GPSAltitudeRef'] === chr(1))) ? -1 : 1); + $direction_multiplier = (!empty($info['jpg']['exif']['GPS']['GPSAltitudeRef']) ? -1 : 1); // 0 = above sea level; 1 = below sea level $info['jpg']['exif']['GPS']['computed']['altitude'] = $direction_multiplier * getid3_lib::DecimalizeFraction($info['jpg']['exif']['GPS']['GPSAltitude']); } } - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.xmp.php', __FILE__, false)) { - if (isset($info['filenamepath'])) { - $image_xmp = new Image_XMP($info['filenamepath']); - $xmp_raw = $image_xmp->getAllTags(); - foreach ($xmp_raw as $key => $value) { + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.xmp.php', __FILE__, true); + if (isset($info['filenamepath'])) { + $image_xmp = new Image_XMP($info['filenamepath']); + $xmp_raw = $image_xmp->getAllTags(); + foreach ($xmp_raw as $key => $value) { + if (strpos($key, ':')) { list($subsection, $tagname) = explode(':', $key); $info['xmp'][$subsection][$tagname] = $this->CastAsAppropriate($value); + } else { + $info['warning'][] = 'XMP: expecting ":", found "'.$key.'"'; } } } @@ -163,7 +166,7 @@ class getid3_jpg extends getid3_handler } - function CastAsAppropriate($value) { + public function CastAsAppropriate($value) { if (is_array($value)) { return $value; } elseif (preg_match('#^[0-9]+/[0-9]+$#', $value)) { @@ -177,7 +180,7 @@ class getid3_jpg extends getid3_handler } - function IPTCrecordName($iptc_record) { + public function IPTCrecordName($iptc_record) { // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html static $IPTCrecordName = array(); if (empty($IPTCrecordName)) { @@ -194,7 +197,7 @@ class getid3_jpg extends getid3_handler } - function IPTCrecordTagName($iptc_record, $iptc_tagkey) { + public function IPTCrecordTagName($iptc_record, $iptc_tagkey) { // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html static $IPTCrecordTagName = array(); if (empty($IPTCrecordTagName)) { @@ -333,6 +336,3 @@ class getid3_jpg extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.graphic.pcd.php b/app/library/getid3/getid3/module.graphic.pcd.php old mode 100644 new mode 100755 similarity index 85% rename from app/library/getid3/module.graphic.pcd.php rename to app/library/getid3/getid3/module.graphic.pcd.php index dcbc6763..3915ebc8 --- a/app/library/getid3/module.graphic.pcd.php +++ b/app/library/getid3/getid3/module.graphic.pcd.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -16,9 +17,9 @@ class getid3_pcd extends getid3_handler { - var $ExtractData = 0; + public $ExtractData = 0; - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'pcd'; @@ -26,9 +27,9 @@ class getid3_pcd extends getid3_handler $info['video']['lossless'] = false; - fseek($this->getid3->fp, $info['avdataoffset'] + 72, SEEK_SET); + $this->fseek($info['avdataoffset'] + 72); - $PCDflags = fread($this->getid3->fp, 1); + $PCDflags = $this->fread(1); $PCDisVertical = ((ord($PCDflags) & 0x01) ? true : false); @@ -56,19 +57,19 @@ class getid3_pcd extends getid3_handler list($PCD_width, $PCD_height, $PCD_dataOffset) = $PCD_levels[3]; - fseek($this->getid3->fp, $info['avdataoffset'] + $PCD_dataOffset, SEEK_SET); + $this->fseek($info['avdataoffset'] + $PCD_dataOffset); for ($y = 0; $y < $PCD_height; $y += 2) { // The image-data of these subtypes start at the respective offsets of 02000h, 0b800h and 30000h. // To decode the YcbYr to the more usual RGB-code, three lines of data have to be read, each - // consisting of w bytes, where w is the width of the image-subtype. The first w bytes and - // the first half of the third w bytes contain data for the first RGB-line, the second w bytes - // and the second half of the third w bytes contain data for a second RGB-line. + // consisting of ‘w’ bytes, where ‘w’ is the width of the image-subtype. The first ‘w’ bytes and + // the first half of the third ‘w’ bytes contain data for the first RGB-line, the second ‘w’ bytes + // and the second half of the third ‘w’ bytes contain data for a second RGB-line. - $PCD_data_Y1 = fread($this->getid3->fp, $PCD_width); - $PCD_data_Y2 = fread($this->getid3->fp, $PCD_width); - $PCD_data_Cb = fread($this->getid3->fp, intval(round($PCD_width / 2))); - $PCD_data_Cr = fread($this->getid3->fp, intval(round($PCD_width / 2))); + $PCD_data_Y1 = $this->fread($PCD_width); + $PCD_data_Y2 = $this->fread($PCD_width); + $PCD_data_Cb = $this->fread(intval(round($PCD_width / 2))); + $PCD_data_Cr = $this->fread(intval(round($PCD_width / 2))); for ($x = 0; $x < $PCD_width; $x++) { if ($PCDisVertical) { @@ -98,7 +99,7 @@ class getid3_pcd extends getid3_handler } - function YCbCr2RGB($Y, $Cb, $Cr) { + public function YCbCr2RGB($Y, $Cb, $Cr) { static $YCbCr_constants = array(); if (empty($YCbCr_constants)) { $YCbCr_constants['red']['Y'] = 0.0054980 * 256; @@ -130,5 +131,3 @@ class getid3_pcd extends getid3_handler } } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.graphic.png.php b/app/library/getid3/getid3/module.graphic.png.php old mode 100644 new mode 100755 similarity index 96% rename from app/library/getid3/module.graphic.png.php rename to app/library/getid3/getid3/module.graphic.png.php index 9109c436..11ae3ede --- a/app/library/getid3/module.graphic.png.php +++ b/app/library/getid3/getid3/module.graphic.png.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,7 +18,7 @@ class getid3_png extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; // shortcut @@ -28,8 +29,8 @@ class getid3_png extends getid3_handler $info['video']['dataformat'] = 'png'; $info['video']['lossless'] = false; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $PNGfiledata = fread($this->getid3->fp, $this->getid3->fread_buffer_size()); + $this->fseek($info['avdataoffset']); + $PNGfiledata = $this->fread($this->getid3->fread_buffer_size()); $offset = 0; $PNGidentifier = substr($PNGfiledata, $offset, 8); // $89 $50 $4E $47 $0D $0A $1A $0A @@ -41,11 +42,11 @@ class getid3_png extends getid3_handler return false; } - while (((ftell($this->getid3->fp) - (strlen($PNGfiledata) - $offset)) < $info['filesize'])) { + while ((($this->ftell() - (strlen($PNGfiledata) - $offset)) < $info['filesize'])) { $chunk['data_length'] = getid3_lib::BigEndian2Int(substr($PNGfiledata, $offset, 4)); $offset += 4; - while (((strlen($PNGfiledata) - $offset) < ($chunk['data_length'] + 4)) && (ftell($this->getid3->fp) < $info['filesize'])) { - $PNGfiledata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size()); + while (((strlen($PNGfiledata) - $offset) < ($chunk['data_length'] + 4)) && ($this->ftell() < $info['filesize'])) { + $PNGfiledata .= $this->fread($this->getid3->fread_buffer_size()); } $chunk['type_text'] = substr($PNGfiledata, $offset, 4); $offset += 4; @@ -438,7 +439,7 @@ class getid3_png extends getid3_handler return true; } - function PNGsRGBintentLookup($sRGB) { + public function PNGsRGBintentLookup($sRGB) { static $PNGsRGBintentLookup = array( 0 => 'Perceptual', 1 => 'Relative colorimetric', @@ -448,14 +449,14 @@ class getid3_png extends getid3_handler return (isset($PNGsRGBintentLookup[$sRGB]) ? $PNGsRGBintentLookup[$sRGB] : 'invalid'); } - function PNGcompressionMethodLookup($compressionmethod) { + public function PNGcompressionMethodLookup($compressionmethod) { static $PNGcompressionMethodLookup = array( 0 => 'deflate/inflate' ); return (isset($PNGcompressionMethodLookup[$compressionmethod]) ? $PNGcompressionMethodLookup[$compressionmethod] : 'invalid'); } - function PNGpHYsUnitLookup($unitid) { + public function PNGpHYsUnitLookup($unitid) { static $PNGpHYsUnitLookup = array( 0 => 'unknown', 1 => 'meter' @@ -463,7 +464,7 @@ class getid3_png extends getid3_handler return (isset($PNGpHYsUnitLookup[$unitid]) ? $PNGpHYsUnitLookup[$unitid] : 'invalid'); } - function PNGoFFsUnitLookup($unitid) { + public function PNGoFFsUnitLookup($unitid) { static $PNGoFFsUnitLookup = array( 0 => 'pixel', 1 => 'micrometer' @@ -471,7 +472,7 @@ class getid3_png extends getid3_handler return (isset($PNGoFFsUnitLookup[$unitid]) ? $PNGoFFsUnitLookup[$unitid] : 'invalid'); } - function PNGpCALequationTypeLookup($equationtype) { + public function PNGpCALequationTypeLookup($equationtype) { static $PNGpCALequationTypeLookup = array( 0 => 'Linear mapping', 1 => 'Base-e exponential mapping', @@ -481,7 +482,7 @@ class getid3_png extends getid3_handler return (isset($PNGpCALequationTypeLookup[$equationtype]) ? $PNGpCALequationTypeLookup[$equationtype] : 'invalid'); } - function PNGsCALUnitLookup($unitid) { + public function PNGsCALUnitLookup($unitid) { static $PNGsCALUnitLookup = array( 0 => 'meter', 1 => 'radian' @@ -489,7 +490,7 @@ class getid3_png extends getid3_handler return (isset($PNGsCALUnitLookup[$unitid]) ? $PNGsCALUnitLookup[$unitid] : 'invalid'); } - function IHDRcalculateBitsPerSample($color_type, $bit_depth) { + public function IHDRcalculateBitsPerSample($color_type, $bit_depth) { switch ($color_type) { case 0: // Each pixel is a grayscale sample. return $bit_depth; @@ -515,6 +516,3 @@ class getid3_png extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.graphic.svg.php b/app/library/getid3/getid3/module.graphic.svg.php old mode 100644 new mode 100755 similarity index 95% rename from app/library/getid3/module.graphic.svg.php rename to app/library/getid3/getid3/module.graphic.svg.php index d9d52d74..184cdc0d --- a/app/library/getid3/module.graphic.svg.php +++ b/app/library/getid3/getid3/module.graphic.svg.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -18,12 +19,12 @@ class getid3_svg extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $this->fseek($info['avdataoffset']); - $SVGheader = fread($this->getid3->fp, 4096); + $SVGheader = $this->fread(4096); if (preg_match('#\<\?xml([^\>]+)\?\>#i', $SVGheader, $matches)) { $info['svg']['xml']['raw'] = $matches; } @@ -99,6 +100,3 @@ class getid3_svg extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.graphic.tiff.php b/app/library/getid3/getid3/module.graphic.tiff.php old mode 100644 new mode 100755 similarity index 85% rename from app/library/getid3/module.graphic.tiff.php rename to app/library/getid3/getid3/module.graphic.tiff.php index e9771242..8504172f --- a/app/library/getid3/module.graphic.tiff.php +++ b/app/library/getid3/getid3/module.graphic.tiff.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,11 +18,11 @@ class getid3_tiff extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $TIFFheader = fread($this->getid3->fp, 4); + $this->fseek($info['avdataoffset']); + $TIFFheader = $this->fread(4); switch (substr($TIFFheader, 0, 2)) { case 'II': @@ -44,20 +45,20 @@ class getid3_tiff extends getid3_handler $FieldTypeByteLength = array(1=>1, 2=>1, 3=>2, 4=>4, 5=>8); - $nextIFDoffset = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']); + $nextIFDoffset = $this->TIFFendian2Int($this->fread(4), $info['tiff']['byte_order']); while ($nextIFDoffset > 0) { $CurrentIFD['offset'] = $nextIFDoffset; - fseek($this->getid3->fp, $info['avdataoffset'] + $nextIFDoffset, SEEK_SET); - $CurrentIFD['fieldcount'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']); + $this->fseek($info['avdataoffset'] + $nextIFDoffset); + $CurrentIFD['fieldcount'] = $this->TIFFendian2Int($this->fread(2), $info['tiff']['byte_order']); for ($i = 0; $i < $CurrentIFD['fieldcount']; $i++) { - $CurrentIFD['fields'][$i]['raw']['tag'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']); - $CurrentIFD['fields'][$i]['raw']['type'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']); - $CurrentIFD['fields'][$i]['raw']['length'] = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']); - $CurrentIFD['fields'][$i]['raw']['offset'] = fread($this->getid3->fp, 4); + $CurrentIFD['fields'][$i]['raw']['tag'] = $this->TIFFendian2Int($this->fread(2), $info['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['raw']['type'] = $this->TIFFendian2Int($this->fread(2), $info['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['raw']['length'] = $this->TIFFendian2Int($this->fread(4), $info['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['raw']['offset'] = $this->fread(4); switch ($CurrentIFD['fields'][$i]['raw']['type']) { case 1: // BYTE An 8-bit unsigned integer. @@ -99,7 +100,7 @@ class getid3_tiff extends getid3_handler $info['tiff']['ifd'][] = $CurrentIFD; $CurrentIFD = array(); - $nextIFDoffset = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']); + $nextIFDoffset = $this->TIFFendian2Int($this->fread(4), $info['tiff']['byte_order']); } @@ -111,8 +112,8 @@ class getid3_tiff extends getid3_handler case 258: // BitsPerSample case 259: // Compression if (!isset($fieldarray['value'])) { - fseek($this->getid3->fp, $fieldarray['offset'], SEEK_SET); - $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($this->getid3->fp, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); + $this->fseek($fieldarray['offset']); + $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $this->fread($fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); } break; @@ -127,8 +128,8 @@ class getid3_tiff extends getid3_handler if (isset($fieldarray['value'])) { $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $fieldarray['value']; } else { - fseek($this->getid3->fp, $fieldarray['offset'], SEEK_SET); - $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($this->getid3->fp, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); + $this->fseek($fieldarray['offset']); + $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $this->fread($fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); } break; @@ -182,7 +183,7 @@ class getid3_tiff extends getid3_handler } - function TIFFendian2Int($bytestring, $byteorder) { + public function TIFFendian2Int($bytestring, $byteorder) { if ($byteorder == 'Intel') { return getid3_lib::LittleEndian2Int($bytestring); } elseif ($byteorder == 'Motorola') { @@ -191,7 +192,7 @@ class getid3_tiff extends getid3_handler return false; } - function TIFFcompressionMethod($id) { + public function TIFFcompressionMethod($id) { static $TIFFcompressionMethod = array(); if (empty($TIFFcompressionMethod)) { $TIFFcompressionMethod = array( @@ -205,7 +206,7 @@ class getid3_tiff extends getid3_handler return (isset($TIFFcompressionMethod[$id]) ? $TIFFcompressionMethod[$id] : 'unknown/invalid ('.$id.')'); } - function TIFFcommentName($id) { + public function TIFFcommentName($id) { static $TIFFcommentName = array(); if (empty($TIFFcommentName)) { $TIFFcommentName = array( @@ -222,6 +223,3 @@ class getid3_tiff extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.misc.cue.php b/app/library/getid3/getid3/module.misc.cue.php old mode 100644 new mode 100755 similarity index 92% rename from app/library/getid3/module.misc.cue.php rename to app/library/getid3/getid3/module.misc.cue.php index c99ce247..9c42799e --- a/app/library/getid3/module.misc.cue.php +++ b/app/library/getid3/getid3/module.misc.cue.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -14,7 +15,7 @@ ///////////////////////////////////////////////////////////////// // // // Module originally written [2009-Mar-25] by // -// Nigel Barnes // +// Nigel Barnes // // Minor reformatting and similar small changes to integrate // // into getID3 by James Heinrich // // /// @@ -32,9 +33,9 @@ */ class getid3_cue extends getid3_handler { - var $cuesheet = array(); + public $cuesheet = array(); - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'cue'; @@ -45,7 +46,7 @@ class getid3_cue extends getid3_handler - function readCueSheetFilename($filename) + public function readCueSheetFilename($filename) { $filedata = file_get_contents($filename); return $this->readCueSheet($filedata); @@ -55,7 +56,7 @@ class getid3_cue extends getid3_handler * * @param string $filename - The filename for the cue sheet to open. */ - function readCueSheet(&$filedata) + public function readCueSheet(&$filedata) { $cue_lines = array(); foreach (explode("\n", str_replace("\r", null, $filedata)) as $line) @@ -75,7 +76,7 @@ class getid3_cue extends getid3_handler * * @param array $file - The cuesheet as an array of each line. */ - function parseCueSheet($file) + public function parseCueSheet($file) { //-1 means still global, all others are track specific $track_on = -1; @@ -129,7 +130,7 @@ class getid3_cue extends getid3_handler * @param string $line - The line in the cue file that contains the TRACK command. * @param integer $track_on - The track currently processing. */ - function parseComment($line, $track_on) + public function parseComment($line, $track_on) { $explodedline = explode(' ', $line, 3); $comment_REM = (isset($explodedline[0]) ? $explodedline[0] : ''); @@ -152,7 +153,7 @@ class getid3_cue extends getid3_handler * @param string $line - The line in the cue file that contains the FILE command. * @return array - Array of FILENAME and TYPE of file.. */ - function parseFile($line) + public function parseFile($line) { $line = substr($line, strpos($line, ' ') + 1); $type = strtolower(substr($line, strrpos($line, ' '))); @@ -172,7 +173,7 @@ class getid3_cue extends getid3_handler * @param string $line - The line in the cue file that contains the TRACK command. * @param integer $track_on - The track currently processing. */ - function parseFlags($line, $track_on) + public function parseFlags($line, $track_on) { if ($track_on != -1) { @@ -210,7 +211,7 @@ class getid3_cue extends getid3_handler * @param string $line - The line in the cue file that contains the TRACK command. * @param integer $track_on - The track currently processing. */ - function parseGarbage($line, $track_on) + public function parseGarbage($line, $track_on) { if ( strlen($line) > 0 ) { @@ -231,7 +232,7 @@ class getid3_cue extends getid3_handler * @param string $line - The line in the cue file that contains the TRACK command. * @param integer $track_on - The track currently processing. */ - function parseIndex($line, $track_on) + public function parseIndex($line, $track_on) { $type = strtolower(substr($line, 0, strpos($line, ' '))); $line = substr($line, strpos($line, ' ') + 1); @@ -260,7 +261,7 @@ class getid3_cue extends getid3_handler } } - function parseString($line, $track_on) + public function parseString($line, $track_on) { $category = strtolower(substr($line, 0, strpos($line, ' '))); $line = substr($line, strpos($line, ' ') + 1); @@ -296,7 +297,7 @@ class getid3_cue extends getid3_handler * @param string $line - The line in the cue file that contains the TRACK command. * @param integer $track_on - The track currently processing. */ - function parseTrack($line, $track_on) + public function parseTrack($line, $track_on) { $line = substr($line, strpos($line, ' ') + 1); $track = ltrim(substr($line, 0, strpos($line, ' ')), '0'); @@ -309,4 +310,3 @@ class getid3_cue extends getid3_handler } -?> diff --git a/app/library/getid3/module.misc.exe.php b/app/library/getid3/getid3/module.misc.exe.php old mode 100644 new mode 100755 similarity index 95% rename from app/library/getid3/module.misc.exe.php rename to app/library/getid3/getid3/module.misc.exe.php index 108e62ec..fd58a07e --- a/app/library/getid3/module.misc.exe.php +++ b/app/library/getid3/getid3/module.misc.exe.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,11 +18,11 @@ class getid3_exe extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $EXEheader = fread($this->getid3->fp, 28); + $this->fseek($info['avdataoffset']); + $EXEheader = $this->fread(28); $magic = 'MZ'; if (substr($EXEheader, 0, 2) != $magic) { @@ -56,6 +57,3 @@ return false; } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.misc.iso.php b/app/library/getid3/getid3/module.misc.iso.php old mode 100644 new mode 100755 similarity index 96% rename from app/library/getid3/module.misc.iso.php rename to app/library/getid3/getid3/module.misc.iso.php index 727fdf87..065b7532 --- a/app/library/getid3/module.misc.iso.php +++ b/app/library/getid3/getid3/module.misc.iso.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,14 +18,14 @@ class getid3_iso extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'iso'; for ($i = 16; $i <= 19; $i++) { - fseek($this->getid3->fp, 2048 * $i, SEEK_SET); - $ISOheader = fread($this->getid3->fp, 2048); + $this->fseek(2048 * $i); + $ISOheader = $this->fread(2048); if (substr($ISOheader, 1, 5) == 'CD001') { switch (ord($ISOheader{0})) { case 1: @@ -55,7 +56,7 @@ class getid3_iso extends getid3_handler } - function ParsePrimaryVolumeDescriptor(&$ISOheader) { + public function ParsePrimaryVolumeDescriptor(&$ISOheader) { // ISO integer values are stored *BOTH* Little-Endian AND Big-Endian format!! // ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field @@ -129,7 +130,7 @@ class getid3_iso extends getid3_handler } - function ParseSupplementaryVolumeDescriptor(&$ISOheader) { + public function ParseSupplementaryVolumeDescriptor(&$ISOheader) { // ISO integer values are stored Both-Endian format!! // ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field @@ -208,7 +209,7 @@ class getid3_iso extends getid3_handler } - function ParsePathTable() { + public function ParsePathTable() { $info = &$this->getid3->info; if (!isset($info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']) && !isset($info['iso']['primary_volume_descriptor']['raw']['path_table_l_location'])) { return false; @@ -229,8 +230,8 @@ class getid3_iso extends getid3_handler } $info['iso']['path_table']['offset'] = $PathTableLocation * 2048; - fseek($this->getid3->fp, $info['iso']['path_table']['offset'], SEEK_SET); - $info['iso']['path_table']['raw'] = fread($this->getid3->fp, $PathTableSize); + $this->fseek($info['iso']['path_table']['offset']); + $info['iso']['path_table']['raw'] = $this->fread($PathTableSize); $offset = 0; $pathcounter = 1; @@ -267,7 +268,7 @@ class getid3_iso extends getid3_handler } - function ParseDirectoryRecord($directorydata) { + public function ParseDirectoryRecord($directorydata) { $info = &$this->getid3->info; if (isset($info['iso']['supplementary_volume_descriptor'])) { $TextEncoding = 'UTF-16BE'; // Big-Endian Unicode @@ -275,12 +276,12 @@ class getid3_iso extends getid3_handler $TextEncoding = 'ISO-8859-1'; // Latin-1 } - fseek($this->getid3->fp, $directorydata['location_bytes'], SEEK_SET); - $DirectoryRecordData = fread($this->getid3->fp, 1); + $this->fseek($directorydata['location_bytes']); + $DirectoryRecordData = $this->fread(1); while (ord($DirectoryRecordData{0}) > 33) { - $DirectoryRecordData .= fread($this->getid3->fp, ord($DirectoryRecordData{0}) - 1); + $DirectoryRecordData .= $this->fread(ord($DirectoryRecordData{0}) - 1); $ThisDirectoryRecord['raw']['length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 0, 1)); $ThisDirectoryRecord['raw']['extended_attribute_length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 1, 1)); @@ -314,13 +315,13 @@ class getid3_iso extends getid3_handler } $DirectoryRecord[] = $ThisDirectoryRecord; - $DirectoryRecordData = fread($this->getid3->fp, 1); + $DirectoryRecordData = $this->fread(1); } return $DirectoryRecord; } - function ISOstripFilenameVersion($ISOfilename) { + public function ISOstripFilenameVersion($ISOfilename) { // convert 'filename.ext;1' to 'filename.ext' if (!strstr($ISOfilename, ';')) { return $ISOfilename; @@ -329,7 +330,7 @@ class getid3_iso extends getid3_handler } } - function ISOtimeText2UNIXtime($ISOtime) { + public function ISOtimeText2UNIXtime($ISOtime) { $UNIXyear = (int) substr($ISOtime, 0, 4); $UNIXmonth = (int) substr($ISOtime, 4, 2); @@ -344,7 +345,7 @@ class getid3_iso extends getid3_handler return gmmktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear); } - function ISOtime2UNIXtime($ISOtime) { + public function ISOtime2UNIXtime($ISOtime) { // Represented by seven bytes: // 1: Number of years since 1900 // 2: Month of the year from 1 to 12 @@ -365,7 +366,7 @@ class getid3_iso extends getid3_handler return gmmktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear); } - function TwosCompliment2Decimal($BinaryValue) { + public function TwosCompliment2Decimal($BinaryValue) { // http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html // First check if the number is negative or positive by looking at the sign bit. // If it is positive, simply convert it to decimal. @@ -385,5 +386,3 @@ class getid3_iso extends getid3_handler } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.misc.msoffice.php b/app/library/getid3/getid3/module.misc.msoffice.php old mode 100644 new mode 100755 similarity index 85% rename from app/library/getid3/module.misc.msoffice.php rename to app/library/getid3/getid3/module.misc.msoffice.php index 8fea1085..beaa813e --- a/app/library/getid3/module.misc.msoffice.php +++ b/app/library/getid3/getid3/module.misc.msoffice.php @@ -3,11 +3,12 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// // // -// module.archive.doc.php // +// module.misc.msoffice.php // // module for analyzing MS Office (.doc, .xls, etc) files // // dependencies: NONE // // /// @@ -17,11 +18,11 @@ class getid3_msoffice extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $DOCFILEheader = fread($this->getid3->fp, 8); + $this->fseek($info['avdataoffset']); + $DOCFILEheader = $this->fread(8); $magic = "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"; if (substr($DOCFILEheader, 0, 8) != $magic) { $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at '.$info['avdataoffset'].', found '.getid3_lib::PrintHexBytes(substr($DOCFILEheader, 0, 8)).' instead.'; @@ -35,6 +36,3 @@ return false; } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.misc.par2.php b/app/library/getid3/getid3/module.misc.par2.php old mode 100644 new mode 100755 similarity index 91% rename from app/library/getid3/module.misc.par2.php rename to app/library/getid3/getid3/module.misc.par2.php index d8b0a388..26d3788a --- a/app/library/getid3/module.misc.par2.php +++ b/app/library/getid3/getid3/module.misc.par2.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,7 +18,7 @@ class getid3_par2 extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'par2'; @@ -28,6 +29,3 @@ class getid3_par2 extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.misc.pdf.php b/app/library/getid3/getid3/module.misc.pdf.php old mode 100644 new mode 100755 similarity index 92% rename from app/library/getid3/module.misc.pdf.php rename to app/library/getid3/getid3/module.misc.pdf.php index 38ed646a..2053af69 --- a/app/library/getid3/module.misc.pdf.php +++ b/app/library/getid3/getid3/module.misc.pdf.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,7 +18,7 @@ class getid3_pdf extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; $info['fileformat'] = 'pdf'; @@ -28,6 +29,3 @@ class getid3_pdf extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.tag.apetag.php b/app/library/getid3/getid3/module.tag.apetag.php old mode 100644 new mode 100755 similarity index 64% rename from app/library/getid3/module.tag.apetag.php rename to app/library/getid3/getid3/module.tag.apetag.php index 6522475f..724b8b0f --- a/app/library/getid3/module.tag.apetag.php +++ b/app/library/getid3/getid3/module.tag.apetag.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -15,10 +16,10 @@ class getid3_apetag extends getid3_handler { - var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory - var $overrideendoffset = 0; + public $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory + public $overrideendoffset = 0; - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; if (!getid3_lib::intValueSupported($info['filesize'])) { @@ -32,8 +33,8 @@ class getid3_apetag extends getid3_handler if ($this->overrideendoffset == 0) { - fseek($this->getid3->fp, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END); - $APEfooterID3v1 = fread($this->getid3->fp, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize); + $this->fseek(0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END); + $APEfooterID3v1 = $this->fread($id3v1tagsize + $apetagheadersize + $lyrics3tagsize); //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) { if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') { @@ -51,8 +52,8 @@ class getid3_apetag extends getid3_handler } else { - fseek($this->getid3->fp, $this->overrideendoffset - $apetagheadersize, SEEK_SET); - if (fread($this->getid3->fp, 8) == 'APETAGEX') { + $this->fseek($this->overrideendoffset - $apetagheadersize); + if ($this->fread(8) == 'APETAGEX') { $info['ape']['tag_offset_end'] = $this->overrideendoffset; } @@ -68,21 +69,21 @@ class getid3_apetag extends getid3_handler // shortcut $thisfile_ape = &$info['ape']; - fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET); - $APEfooterData = fread($this->getid3->fp, 32); + $this->fseek($thisfile_ape['tag_offset_end'] - $apetagheadersize); + $APEfooterData = $this->fread(32); if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) { $info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']; return false; } if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) { - fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET); - $thisfile_ape['tag_offset_start'] = ftell($this->getid3->fp); - $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize); + $this->fseek($thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize); + $thisfile_ape['tag_offset_start'] = $this->ftell(); + $APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize); } else { $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize']; - fseek($this->getid3->fp, $thisfile_ape['tag_offset_start'], SEEK_SET); - $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize']); + $this->fseek($thisfile_ape['tag_offset_start']); + $APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize']); } $info['avdataend'] = $thisfile_ape['tag_offset_start']; @@ -137,58 +138,88 @@ class getid3_apetag extends getid3_handler $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags); switch ($thisfile_ape_items_current['flags']['item_contents_raw']) { case 0: // UTF-8 - case 3: // Locator (URL, filename, etc), UTF-8 encoded - $thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data'])); + case 2: // Locator (URL, filename, etc), UTF-8 encoded + $thisfile_ape_items_current['data'] = explode("\x00", $thisfile_ape_items_current['data']); break; - default: // binary data + case 1: // binary data + default: break; } switch (strtolower($item_key)) { + // http://wiki.hydrogenaud.io/index.php?title=ReplayGain#MP3Gain case 'replaygain_track_gain': - $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! - $thisfile_replaygain['track']['originator'] = 'unspecified'; + if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { + $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['track']['originator'] = 'unspecified'; + } else { + $info['warning'][] = 'MP3gainTrackGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; + } break; case 'replaygain_track_peak': - $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! - $thisfile_replaygain['track']['originator'] = 'unspecified'; - if ($thisfile_replaygain['track']['peak'] <= 0) { - $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { + $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['track']['originator'] = 'unspecified'; + if ($thisfile_replaygain['track']['peak'] <= 0) { + $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + } + } else { + $info['warning'][] = 'MP3gainTrackPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; } break; case 'replaygain_album_gain': - $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! - $thisfile_replaygain['album']['originator'] = 'unspecified'; + if (preg_match('#^[\\-\\+][0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { + $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['album']['originator'] = 'unspecified'; + } else { + $info['warning'][] = 'MP3gainAlbumGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; + } break; case 'replaygain_album_peak': - $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! - $thisfile_replaygain['album']['originator'] = 'unspecified'; - if ($thisfile_replaygain['album']['peak'] <= 0) { - $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + if (preg_match('#^[0-9\\.,]{8}$#', $thisfile_ape_items_current['data'][0])) { + $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['album']['originator'] = 'unspecified'; + if ($thisfile_replaygain['album']['peak'] <= 0) { + $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + } + } else { + $info['warning'][] = 'MP3gainAlbumPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; } break; case 'mp3gain_undo': - list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]); - $thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left); - $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right); - $thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false); + if (preg_match('#^[\\-\\+][0-9]{3},[\\-\\+][0-9]{3},[NW]$#', $thisfile_ape_items_current['data'][0])) { + list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left); + $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right); + $thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false); + } else { + $info['warning'][] = 'MP3gainUndo value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; + } break; case 'mp3gain_minmax': - list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]); - $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min); - $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max); + if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) { + list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min); + $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max); + } else { + $info['warning'][] = 'MP3gainMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; + } break; case 'mp3gain_album_minmax': - list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]); - $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min); - $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max); + if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) { + list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min); + $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max); + } else { + $info['warning'][] = 'MP3gainAlbumMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"'; + } break; case 'tracknumber': @@ -221,6 +252,10 @@ class getid3_apetag extends getid3_handler case 'cover art (recording)': case 'cover art (studio)': // list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html + if (is_array($thisfile_ape_items_current['data'])) { + $info['warning'][] = 'APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8'; + $thisfile_ape_items_current['data'] = implode("\x00", $thisfile_ape_items_current['data']); + } list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2); $thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00"); $thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']); @@ -268,7 +303,14 @@ class getid3_apetag extends getid3_handler if (!isset($info['ape']['comments']['picture'])) { $info['ape']['comments']['picture'] = array(); } - $info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']); + $comments_picture_data = array(); + foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) { + if (isset($thisfile_ape_items_current[$picture_key])) { + $comments_picture_data[$picture_key] = $thisfile_ape_items_current[$picture_key]; + } + } + $info['ape']['comments']['picture'][] = $comments_picture_data; + unset($comments_picture_data); } } while (false); break; @@ -289,7 +331,7 @@ class getid3_apetag extends getid3_handler return true; } - function parseAPEheaderFooter($APEheaderFooterData) { + public function parseAPEheaderFooter($APEheaderFooterData) { // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html // shortcut @@ -313,10 +355,10 @@ class getid3_apetag extends getid3_handler return $headerfooterinfo; } - function parseAPEtagFlags($rawflagint) { + public function parseAPEtagFlags($rawflagint) { // "Note: APE Tags 1.0 do not use any of the APE Tag flags. // All are set to zero on creation and ignored on reading." - // http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html + // http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags $flags['header'] = (bool) ($rawflagint & 0x80000000); $flags['footer'] = (bool) ($rawflagint & 0x40000000); $flags['this_is_header'] = (bool) ($rawflagint & 0x20000000); @@ -328,7 +370,7 @@ class getid3_apetag extends getid3_handler return $flags; } - function APEcontentTypeFlagLookup($contenttypeid) { + public function APEcontentTypeFlagLookup($contenttypeid) { static $APEcontentTypeFlagLookup = array( 0 => 'utf-8', 1 => 'binary', @@ -338,7 +380,7 @@ class getid3_apetag extends getid3_handler return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid'); } - function APEtagItemIsUTF8Lookup($itemkey) { + public function APEtagItemIsUTF8Lookup($itemkey) { static $APEtagItemIsUTF8Lookup = array( 'title', 'subtitle', @@ -368,5 +410,3 @@ class getid3_apetag extends getid3_handler } } - -?> \ No newline at end of file diff --git a/app/library/getid3/module.tag.id3v1.php b/app/library/getid3/getid3/module.tag.id3v1.php old mode 100644 new mode 100755 similarity index 92% rename from app/library/getid3/module.tag.id3v1.php rename to app/library/getid3/getid3/module.tag.id3v1.php index a9932d13..3b4edfd2 --- a/app/library/getid3/module.tag.id3v1.php +++ b/app/library/getid3/getid3/module.tag.id3v1.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,7 +18,7 @@ class getid3_id3v1 extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; if (!getid3_lib::intValueSupported($info['filesize'])) { @@ -25,9 +26,9 @@ class getid3_id3v1 extends getid3_handler return false; } - fseek($this->getid3->fp, -256, SEEK_END); - $preid3v1 = fread($this->getid3->fp, 128); - $id3v1tag = fread($this->getid3->fp, 128); + $this->fseek(-256, SEEK_END); + $preid3v1 = $this->fread(128); + $id3v1tag = $this->fread(128); if (substr($id3v1tag, 0, 3) == 'TAG') { @@ -102,11 +103,11 @@ class getid3_id3v1 extends getid3_handler return true; } - static function cutfield($str) { + public static function cutfield($str) { return trim(substr($str, 0, strcspn($str, "\x00"))); } - static function ArrayOfGenres($allowSCMPXextended=false) { + public static function ArrayOfGenres($allowSCMPXextended=false) { static $GenreLookup = array( 0 => 'Blues', 1 => 'Classic Rock', @@ -290,7 +291,7 @@ class getid3_id3v1 extends getid3_handler return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup); } - static function LookupGenreName($genreid, $allowSCMPXextended=true) { + public static function LookupGenreName($genreid, $allowSCMPXextended=true) { switch ($genreid) { case 'RX': case 'CR': @@ -302,12 +303,12 @@ class getid3_id3v1 extends getid3_handler $genreid = intval($genreid); // to handle 3 or '3' or '03' break; } - $GenreLookup = getid3_id3v1::ArrayOfGenres($allowSCMPXextended); + $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); } - static function LookupGenreID($genre, $allowSCMPXextended=false) { - $GenreLookup = getid3_id3v1::ArrayOfGenres($allowSCMPXextended); + public static function LookupGenreID($genre, $allowSCMPXextended=false) { + $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre)); foreach ($GenreLookup as $key => $value) { if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) { @@ -317,14 +318,14 @@ class getid3_id3v1 extends getid3_handler return false; } - static function StandardiseID3v1GenreName($OriginalGenre) { - if (($GenreID = getid3_id3v1::LookupGenreID($OriginalGenre)) !== false) { - return getid3_id3v1::LookupGenreName($GenreID); + public static function StandardiseID3v1GenreName($OriginalGenre) { + if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) { + return self::LookupGenreName($GenreID); } return $OriginalGenre; } - static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { + public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { $ID3v1Tag = 'TAG'; $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT); $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT); @@ -357,6 +358,3 @@ class getid3_id3v1 extends getid3_handler } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.tag.id3v2.php b/app/library/getid3/getid3/module.tag.id3v2.php old mode 100644 new mode 100755 similarity index 85% rename from app/library/getid3/module.tag.id3v2.php rename to app/library/getid3/getid3/module.tag.id3v2.php index 56adeb95..70925048 --- a/app/library/getid3/module.tag.id3v2.php +++ b/app/library/getid3/getid3/module.tag.id3v2.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,10 +18,9 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE_ class getid3_id3v2 extends getid3_handler { - var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory - var $StartingOffset = 0; + public $StartingOffset = 0; - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; // Overall tag structure: @@ -52,8 +52,8 @@ class getid3_id3v2 extends getid3_handler $thisfile_id3v2_flags = &$thisfile_id3v2['flags']; - fseek($this->getid3->fp, $this->StartingOffset, SEEK_SET); - $header = fread($this->getid3->fp, 10); + $this->fseek($this->StartingOffset); + $header = $this->fread(10); if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) { $thisfile_id3v2['majorversion'] = ord($header{3}); @@ -132,7 +132,7 @@ class getid3_id3v2 extends getid3_handler } if ($sizeofframes > 0) { - $framedata = fread($this->getid3->fp, $sizeofframes); // read all frames from file into $framedata variable + $framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x) if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) { @@ -424,7 +424,7 @@ class getid3_id3v2 extends getid3_handler // ID3v2 size 4 * %0xxxxxxx if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) { - $footer = fread($this->getid3->fp, 10); + $footer = $this->fread(10); if (substr($footer, 0, 3) == '3DI') { $thisfile_id3v2['footer'] = true; $thisfile_id3v2['majorversion_footer'] = ord($footer{3}); @@ -495,7 +495,7 @@ class getid3_id3v2 extends getid3_handler } - function ParseID3v2GenreString($genrestring) { + public function ParseID3v2GenreString($genrestring) { // Parse genres into arrays of genreName and genreID // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)' // ID3v2.4.x: '21' $00 'Eurodisco' $00 @@ -518,7 +518,7 @@ class getid3_id3v2 extends getid3_handler } - function ParseID3v2Frame(&$parsedFrame) { + public function ParseID3v2Frame(&$parsedFrame) { // shortcuts $info = &$this->getid3->info; @@ -625,12 +625,13 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -640,10 +641,15 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); - $parsedFrame['description'] = $frame_description; - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $frame_description)); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); + $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0)); + if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); + } else { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); + } } //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain @@ -667,10 +673,38 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); - $string = rtrim($string, "\x00"); // remove possible terminating null (put by encoding id or software bug) - $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string; - unset($string); + // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with / + // This of course breaks when an artist name contains slash character, e.g. "AC/DC" + // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense + // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user + switch ($parsedFrame['encoding']) { + case 'UTF-16': + case 'UTF-16BE': + case 'UTF-16LE': + $wordsize = 2; + break; + case 'ISO-8859-1': + case 'UTF-8': + default: + $wordsize = 1; + break; + } + $Txxx_elements = array(); + $Txxx_elements_start_offset = 0; + for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) { + if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) { + $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset); + $Txxx_elements_start_offset = $i + $wordsize; + } + } + $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset); + foreach ($Txxx_elements as $Txxx_element) { + $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element); + if (!empty($string)) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string; + } + } + unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset); } } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame @@ -684,11 +718,13 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -696,10 +732,10 @@ class getid3_id3v2 extends getid3_handler if (ord($frame_description) === 0) { $frame_description = ''; } - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } if ($frame_terminatorpos) { @@ -738,6 +774,7 @@ class getid3_id3v2 extends getid3_handler } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only) (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only) + // http://id3.org/id3v2.3.0#sec4.4 // There may only be one 'IPL' frame in each tag //
// Text encoding $xx @@ -750,10 +787,68 @@ class getid3_id3v2 extends getid3_handler } $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); + $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset); + + // http://www.getid3.org/phpBB3/viewtopic.php?t=1369 + // "this tag typically contains null terminated strings, which are associated in pairs" + // "there are users that use the tag incorrectly" + $IPLS_parts = array(); + if (strpos($parsedFrame['data_raw'], "\x00") !== false) { + $IPLS_parts_unsorted = array(); + if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) { + // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding + $thisILPS = ''; + for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) { + $twobytes = substr($parsedFrame['data_raw'], $i, 2); + if ($twobytes === "\x00\x00") { + $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); + $thisILPS = ''; + } else { + $thisILPS .= $twobytes; + } + } + if (strlen($thisILPS) > 2) { // 2-byte BOM + $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); + } + } else { + // ISO-8859-1 or UTF-8 or other single-byte-null character set + $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']); + } + if (count($IPLS_parts_unsorted) == 1) { + // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson" + foreach ($IPLS_parts_unsorted as $key => $value) { + $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value); + $position = ''; + foreach ($IPLS_parts_sorted as $person) { + $IPLS_parts[] = array('position'=>$position, 'person'=>$person); + } + } + } elseif ((count($IPLS_parts_unsorted) % 2) == 0) { + $position = ''; + $person = ''; + foreach ($IPLS_parts_unsorted as $key => $value) { + if (($key % 2) == 0) { + $position = $value; + } else { + $person = $value; + $IPLS_parts[] = array('position'=>$position, 'person'=>$person); + $position = ''; + $person = ''; + } + } + } else { + foreach ($IPLS_parts_unsorted as $key => $value) { + $IPLS_parts[] = array($value); + } + } + + } else { + $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']); + } + $parsedFrame['data'] = $IPLS_parts; - $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; } @@ -864,20 +959,22 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); @@ -910,8 +1007,10 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; @@ -928,16 +1027,16 @@ class getid3_id3v2 extends getid3_handler $frame_remainingdata = substr($parsedFrame['data'], $frame_offset); while (strlen($frame_remainingdata)) { $frame_offset = 0; - $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding)); + $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator); if ($frame_terminatorpos === false) { $frame_remainingdata = ''; } else { - if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); - $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator)); if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) { // timestamp probably omitted for first data item } else { @@ -968,20 +1067,22 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } $frame_language = substr($parsedFrame['data'], $frame_offset, 3); $frame_offset += 3; - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } - $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); $parsedFrame['encodingid'] = $frame_textencoding; $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); @@ -991,7 +1092,12 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['description'] = $frame_description; $parsedFrame['data'] = $frame_text; if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0)); + if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + } else { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + } } } @@ -1233,15 +1339,17 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) { $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3); if (strtolower($frame_imagetype) == 'ima') { // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted - // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffpacbell*net) + // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net) $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_mimetype) === 0) { @@ -1270,8 +1378,8 @@ class getid3_id3v2 extends getid3_handler if ($frame_offset >= $parsedFrame['datalength']) { $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset); } else { - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -1289,7 +1397,7 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['picturetypeid'] = $frame_picturetype; $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); $parsedFrame['description'] = $frame_description; - $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); $parsedFrame['datalength'] = strlen($parsedFrame['data']); $parsedFrame['image_mime'] = ''; @@ -1306,32 +1414,34 @@ class getid3_id3v2 extends getid3_handler } do { - if ($this->inline_attachments === false) { + if ($this->getid3->option_save_attachments === false) { // skip entirely unset($parsedFrame['data']); break; } - if ($this->inline_attachments === true) { + if ($this->getid3->option_save_attachments === true) { // great - } elseif (is_int($this->inline_attachments)) { - if ($this->inline_attachments < $parsedFrame['data_length']) { +/* + } elseif (is_int($this->getid3->option_save_attachments)) { + if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) { // too big, skip $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)'; unset($parsedFrame['data']); break; } - } elseif (is_string($this->inline_attachments)) { - $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); - if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) { +*/ + } elseif (is_string($this->getid3->option_save_attachments)) { + $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($dir) || !is_writable($dir)) { // cannot write, skip - $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$this->inline_attachments.'" (not writable)'; + $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)'; unset($parsedFrame['data']); break; } } // if we get this far, must be OK - if (is_string($this->inline_attachments)) { - $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset; + if (is_string($this->getid3->option_save_attachments)) { + $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset; if (!file_exists($destination_filename) || is_writable($destination_filename)) { file_put_contents($destination_filename, $parsedFrame['data']); } else { @@ -1344,7 +1454,14 @@ class getid3_id3v2 extends getid3_handler if (!isset($info['id3v2']['comments']['picture'])) { $info['id3v2']['comments']['picture'] = array(); } - $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']); + $comments_picture_data = array(); + foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) { + if (isset($parsedFrame[$picture_key])) { + $comments_picture_data[$picture_key] = $parsedFrame[$picture_key]; + } + } + $info['id3v2']['comments']['picture'][] = $comments_picture_data; + unset($comments_picture_data); } } } while (false); @@ -1363,8 +1480,10 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -1373,25 +1492,25 @@ class getid3_id3v2 extends getid3_handler } $frame_offset = $frame_terminatorpos + strlen("\x00"); - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_filename) === 0) { $frame_filename = ''; } - $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } - $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset); $parsedFrame['encodingid'] = $frame_textencoding; @@ -1508,7 +1627,7 @@ class getid3_id3v2 extends getid3_handler } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information - (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information // There may be more than one 'LINK' frame in a tag, // but only one with the same contents //
@@ -1536,7 +1655,7 @@ class getid3_id3v2 extends getid3_handler $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset); if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { - $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']); + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']); } unset($parsedFrame['data']); @@ -1630,8 +1749,10 @@ class getid3_id3v2 extends getid3_handler $frame_offset = 0; $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding); if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + $frame_textencoding_terminator = "\x00"; } $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); @@ -1653,25 +1774,25 @@ class getid3_id3v2 extends getid3_handler $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_sellername) === 0) { $frame_sellername = ''; } - $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); - $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); - if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) { $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 } $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); if (ord($frame_description) === 0) { $frame_description = ''; } - $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator); $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); @@ -1797,7 +1918,7 @@ class getid3_id3v2 extends getid3_handler $frame_offset += 2; $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8); - for ($i = 0; $i < $frame_indexpoints; $i++) { + for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) { $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint)); $frame_offset += $frame_bytesperpoint; } @@ -1845,17 +1966,197 @@ class getid3_id3v2 extends getid3_handler unset($parsedFrame['data']); + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only) + // http://id3.org/id3v2-chapters-1.0 + // (10 bytes) + // Element ID $00 + // Start time $xx xx xx xx + // End time $xx xx xx xx + // Start offset $xx xx xx xx + // End offset $xx xx xx xx + // + + $frame_offset = 0; + @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); + $frame_offset += strlen($parsedFrame['element_id']."\x00"); + $parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $parsedFrame['time_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") { + // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized." + $parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + } + $frame_offset += 4; + if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") { + // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized." + $parsedFrame['offset_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + } + $frame_offset += 4; + + if ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame['subframes'] = array(); + while ($frame_offset < strlen($parsedFrame['data'])) { + // + $subframe = array(); + $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4); + $frame_offset += 4; + $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) { + $info['warning'][] = 'CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)'; + break; + } + $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']); + $frame_offset += $subframe['size']; + + $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1)); + $subframe['text'] = substr($subframe_rawdata, 1); + $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']); + $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));; + switch (substr($encoding_converted_text, 0, 2)) { + case "\xFF\xFE": + case "\xFE\xFF": + switch (strtoupper($info['id3v2']['encoding'])) { + case 'ISO-8859-1': + case 'UTF-8': + $encoding_converted_text = substr($encoding_converted_text, 2); + // remove unwanted byte-order-marks + break; + default: + // ignore + break; + } + break; + default: + // do not remove BOM + break; + } + + if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) { + if ($subframe['name'] == 'TIT2') { + $parsedFrame['chapter_name'] = $encoding_converted_text; + } elseif ($subframe['name'] == 'TIT3') { + $parsedFrame['chapter_description'] = $encoding_converted_text; + } + $parsedFrame['subframes'][] = $subframe; + } else { + $info['warning'][] = 'ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)'; + } + } + unset($subframe_rawdata, $subframe, $encoding_converted_text); + } + + $id3v2_chapter_entry = array(); + foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description') as $id3v2_chapter_key) { + if (isset($parsedFrame[$id3v2_chapter_key])) { + $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key]; + } + } + if (!isset($info['id3v2']['chapters'])) { + $info['id3v2']['chapters'] = array(); + } + $info['id3v2']['chapters'][] = $id3v2_chapter_entry; + unset($id3v2_chapter_entry, $id3v2_chapter_key); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only) + // http://id3.org/id3v2-chapters-1.0 + // (10 bytes) + // Element ID $00 + // CTOC flags %xx + // Entry count $xx + // Child Element ID $00 /* zero or more child CHAP or CTOC entries */ + // + + $frame_offset = 0; + @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2); + $frame_offset += strlen($parsedFrame['element_id']."\x00"); + $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1)); + $frame_offset += 1; + $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1)); + $frame_offset += 1; + + $terminator_position = null; + for ($i = 0; $i < $parsedFrame['entry_count']; $i++) { + $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset); + $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset); + $frame_offset = $terminator_position + 1; + } + + $parsedFrame['ctoc_flags']['ordered'] = (bool) ($ctoc_flags_raw & 0x01); + $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03); + + unset($ctoc_flags_raw, $terminator_position); + + if ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame['subframes'] = array(); + while ($frame_offset < strlen($parsedFrame['data'])) { + // + $subframe = array(); + $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4); + $frame_offset += 4; + $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) { + $info['warning'][] = 'CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)'; + break; + } + $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']); + $frame_offset += $subframe['size']; + + $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1)); + $subframe['text'] = substr($subframe_rawdata, 1); + $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']); + $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));; + switch (substr($encoding_converted_text, 0, 2)) { + case "\xFF\xFE": + case "\xFE\xFF": + switch (strtoupper($info['id3v2']['encoding'])) { + case 'ISO-8859-1': + case 'UTF-8': + $encoding_converted_text = substr($encoding_converted_text, 2); + // remove unwanted byte-order-marks + break; + default: + // ignore + break; + } + break; + default: + // do not remove BOM + break; + } + + if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) { + if ($subframe['name'] == 'TIT2') { + $parsedFrame['toc_name'] = $encoding_converted_text; + } elseif ($subframe['name'] == 'TIT3') { + $parsedFrame['toc_description'] = $encoding_converted_text; + } + $parsedFrame['subframes'][] = $subframe; + } else { + $info['warning'][] = 'ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)'; + } + } + unset($subframe_rawdata, $subframe, $encoding_converted_text); + } + } return true; } - function DeUnsynchronise($data) { + public function DeUnsynchronise($data) { return str_replace("\xFF\x00", "\xFF", $data); } - function LookupExtendedHeaderRestrictionsTagSizeLimits($index) { + public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) { static $LookupExtendedHeaderRestrictionsTagSizeLimits = array( 0x00 => 'No more than 128 frames and 1 MB total tag size', 0x01 => 'No more than 64 frames and 128 KB total tag size', @@ -1865,7 +2166,7 @@ class getid3_id3v2 extends getid3_handler return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : ''); } - function LookupExtendedHeaderRestrictionsTextEncodings($index) { + public function LookupExtendedHeaderRestrictionsTextEncodings($index) { static $LookupExtendedHeaderRestrictionsTextEncodings = array( 0x00 => 'No restrictions', 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8', @@ -1873,7 +2174,7 @@ class getid3_id3v2 extends getid3_handler return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : ''); } - function LookupExtendedHeaderRestrictionsTextFieldSize($index) { + public function LookupExtendedHeaderRestrictionsTextFieldSize($index) { static $LookupExtendedHeaderRestrictionsTextFieldSize = array( 0x00 => 'No restrictions', 0x01 => 'No string is longer than 1024 characters', @@ -1883,7 +2184,7 @@ class getid3_id3v2 extends getid3_handler return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : ''); } - function LookupExtendedHeaderRestrictionsImageEncoding($index) { + public function LookupExtendedHeaderRestrictionsImageEncoding($index) { static $LookupExtendedHeaderRestrictionsImageEncoding = array( 0x00 => 'No restrictions', 0x01 => 'Images are encoded only with PNG or JPEG', @@ -1891,7 +2192,7 @@ class getid3_id3v2 extends getid3_handler return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : ''); } - function LookupExtendedHeaderRestrictionsImageSizeSize($index) { + public function LookupExtendedHeaderRestrictionsImageSizeSize($index) { static $LookupExtendedHeaderRestrictionsImageSizeSize = array( 0x00 => 'No restrictions', 0x01 => 'All images are 256x256 pixels or smaller', @@ -1901,7 +2202,7 @@ class getid3_id3v2 extends getid3_handler return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : ''); } - function LookupCurrencyUnits($currencyid) { + public function LookupCurrencyUnits($currencyid) { $begin = __LINE__; @@ -2098,7 +2399,7 @@ class getid3_id3v2 extends getid3_handler } - function LookupCurrencyCountry($currencyid) { + public function LookupCurrencyCountry($currencyid) { $begin = __LINE__; @@ -2250,7 +2551,7 @@ class getid3_id3v2 extends getid3_handler SOS Somalia SPL Seborga SRG Suriname - STD So Tome and Principe + STD São Tome and Principe SVC El Salvador SYP Syria SZL Swaziland @@ -2274,13 +2575,13 @@ class getid3_id3v2 extends getid3_handler VND Viet Nam VUV Vanuatu WST Samoa - XAF Communaut Financire Africaine + XAF Communauté Financière Africaine XAG Silver XAU Gold XCD East Caribbean XDR International Monetary Fund XPD Palladium - XPF Comptoirs Franais du Pacifique + XPF Comptoirs Français du Pacifique XPT Platinum YER Yemen YUM Yugoslavia @@ -2295,7 +2596,7 @@ class getid3_id3v2 extends getid3_handler - static function LanguageLookup($languagecode, $casesensitive=false) { + public static function LanguageLookup($languagecode, $casesensitive=false) { if (!$casesensitive) { $languagecode = strtolower($languagecode); @@ -2724,7 +3025,7 @@ class getid3_id3v2 extends getid3_handler vai Vai ven Venda vie Vietnamese - vol Volapk + vol Volapük vot Votic wak Wakashan Languages wal Walamo @@ -2751,7 +3052,7 @@ class getid3_id3v2 extends getid3_handler } - static function ETCOEventLookup($index) { + public static function ETCOEventLookup($index) { if (($index >= 0x17) && ($index <= 0xDF)) { return 'reserved for future use'; } @@ -2794,7 +3095,7 @@ class getid3_id3v2 extends getid3_handler return (isset($EventLookup[$index]) ? $EventLookup[$index] : ''); } - static function SYTLContentTypeLookup($index) { + public static function SYTLContentTypeLookup($index) { static $SYTLContentTypeLookup = array( 0x00 => 'other', 0x01 => 'lyrics', @@ -2810,7 +3111,7 @@ class getid3_id3v2 extends getid3_handler return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : ''); } - static function APICPictureTypeLookup($index, $returnarray=false) { + public static function APICPictureTypeLookup($index, $returnarray=false) { static $APICPictureTypeLookup = array( 0x00 => 'Other', 0x01 => '32x32 pixels \'file icon\' (PNG only)', @@ -2840,7 +3141,7 @@ class getid3_id3v2 extends getid3_handler return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : ''); } - static function COMRReceivedAsLookup($index) { + public static function COMRReceivedAsLookup($index) { static $COMRReceivedAsLookup = array( 0x00 => 'Other', 0x01 => 'Standard CD album with other songs', @@ -2856,7 +3157,7 @@ class getid3_id3v2 extends getid3_handler return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : ''); } - static function RVA2ChannelTypeLookup($index) { + public static function RVA2ChannelTypeLookup($index) { static $RVA2ChannelTypeLookup = array( 0x00 => 'Other', 0x01 => 'Master volume', @@ -2872,7 +3173,7 @@ class getid3_id3v2 extends getid3_handler return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : ''); } - static function FrameNameLongLookup($framename) { + public static function FrameNameLongLookup($framename) { $begin = __LINE__; @@ -3056,7 +3357,7 @@ class getid3_id3v2 extends getid3_handler } - static function FrameNameShortLookup($framename) { + public static function FrameNameShortLookup($framename) { $begin = __LINE__; @@ -3197,7 +3498,7 @@ class getid3_id3v2 extends getid3_handler TSSE encoder_settings TSST set_subtitle TST title_sort_order - TT1 description + TT1 content_group_description TT2 title TT3 subtitle TXT lyricist @@ -3235,7 +3536,7 @@ class getid3_id3v2 extends getid3_handler return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short'); } - static function TextEncodingTerminatorLookup($encoding) { + public static function TextEncodingTerminatorLookup($encoding) { // http://www.id3.org/id3v2.4.0-structure.txt // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: static $TextEncodingTerminatorLookup = array( @@ -3245,10 +3546,10 @@ class getid3_id3v2 extends getid3_handler 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00. 255 => "\x00\x00" ); - return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : ''); + return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00"); } - static function TextEncodingNameLookup($encoding) { + public static function TextEncodingNameLookup($encoding) { // http://www.id3.org/id3v2.4.0-structure.txt // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: static $TextEncodingNameLookup = array( @@ -3261,7 +3562,7 @@ class getid3_id3v2 extends getid3_handler return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1'); } - static function IsValidID3v2FrameName($framename, $id3v2majorversion) { + public static function IsValidID3v2FrameName($framename, $id3v2majorversion) { switch ($id3v2majorversion) { case 2: return preg_match('#[A-Z][A-Z0-9]{2}#', $framename); @@ -3275,7 +3576,7 @@ class getid3_id3v2 extends getid3_handler return false; } - static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { + public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { for ($i = 0; $i < strlen($numberstring); $i++) { if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) { if (($numberstring{$i} == '.') && $allowdecimal) { @@ -3290,7 +3591,7 @@ class getid3_id3v2 extends getid3_handler return true; } - static function IsValidDateStampString($datestamp) { + public static function IsValidDateStampString($datestamp) { if (strlen($datestamp) != 8) { return false; } @@ -3318,10 +3619,9 @@ class getid3_id3v2 extends getid3_handler return true; } - static function ID3v2HeaderLength($majorversion) { + public static function ID3v2HeaderLength($majorversion) { return (($majorversion == 2) ? 6 : 10); } } -?> diff --git a/app/library/getid3/module.tag.lyrics3.php b/app/library/getid3/getid3/module.tag.lyrics3.php old mode 100644 new mode 100755 similarity index 88% rename from app/library/getid3/module.tag.lyrics3.php rename to app/library/getid3/getid3/module.tag.lyrics3.php index aaff7178..419888bf --- a/app/library/getid3/module.tag.lyrics3.php +++ b/app/library/getid3/getid3/module.tag.lyrics3.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,7 +18,7 @@ class getid3_lyrics3 extends getid3_handler { - function Analyze() { + public function Analyze() { $info = &$this->getid3->info; // http://www.volweb.cz/str/tags.htm @@ -27,8 +28,8 @@ class getid3_lyrics3 extends getid3_handler return false; } - fseek($this->getid3->fp, (0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size] - $lyrics3_id3v1 = fread($this->getid3->fp, 128 + 9 + 6); + $this->fseek((0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size] + $lyrics3_id3v1 = $this->fread(128 + 9 + 6); $lyrics3lsz = substr($lyrics3_id3v1, 0, 6); // Lyrics3size $lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200 $id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1 @@ -68,9 +69,9 @@ class getid3_lyrics3 extends getid3_handler if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) { - fseek($this->getid3->fp, $info['ape']['tag_offset_start'] - 15, SEEK_SET); - $lyrics3lsz = fread($this->getid3->fp, 6); - $lyrics3end = fread($this->getid3->fp, 9); + $this->fseek($info['ape']['tag_offset_start'] - 15); + $lyrics3lsz = $this->fread(6); + $lyrics3end = $this->fread(9); if ($lyrics3end == 'LYRICSEND') { // Lyrics3v1, APE, maybe ID3v1 @@ -100,8 +101,9 @@ class getid3_lyrics3 extends getid3_handler $this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size); if (!isset($info['ape'])) { - $GETID3_ERRORARRAY = &$info['warning']; - if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, false)) { + if (isset($info['lyrics3']['tag_offset_start'])) { + $GETID3_ERRORARRAY = &$info['warning']; + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true); $getid3_temp = new getID3(); $getid3_temp->openfile($this->getid3->filename); $getid3_apetag = new getid3_apetag($getid3_temp); @@ -114,6 +116,8 @@ class getid3_lyrics3 extends getid3_handler $info['replay_gain'] = $getid3_temp->info['replay_gain']; } unset($getid3_temp, $getid3_apetag); + } else { + $info['warning'][] = 'Lyrics3 and APE tags appear to have become entangled (most likely due to updating the APE tags with a non-Lyrics3-aware tagger)'; } } @@ -122,7 +126,7 @@ class getid3_lyrics3 extends getid3_handler return true; } - function getLyrics3Data($endoffset, $version, $length) { + public function getLyrics3Data($endoffset, $version, $length) { // http://www.volweb.cz/str/tags.htm $info = &$this->getid3->info; @@ -132,11 +136,11 @@ class getid3_lyrics3 extends getid3_handler return false; } - fseek($this->getid3->fp, $endoffset, SEEK_SET); + $this->fseek($endoffset); if ($length <= 0) { return false; } - $rawdata = fread($this->getid3->fp, $length); + $rawdata = $this->fread($length); $ParsedLyrics3['raw']['lyrics3version'] = $version; $ParsedLyrics3['raw']['lyrics3tagsize'] = $length; @@ -169,7 +173,7 @@ class getid3_lyrics3 extends getid3_handler $ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9)); $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); } else { - $info['error'][] = '"LYRICSEND" expected at '.(ftell($this->getid3->fp) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; + $info['error'][] = '"LYRICSEND" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; return false; } break; @@ -217,7 +221,7 @@ class getid3_lyrics3 extends getid3_handler $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); } } else { - $info['error'][] = '"LYRICS200" expected at '.(ftell($this->getid3->fp) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; + $info['error'][] = '"LYRICS200" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; return false; } break; @@ -246,14 +250,14 @@ class getid3_lyrics3 extends getid3_handler return true; } - function Lyrics3Timestamp2Seconds($rawtimestamp) { + public function Lyrics3Timestamp2Seconds($rawtimestamp) { if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) { return (int) (($regs[1] * 60) + $regs[2]); } return false; } - function Lyrics3LyricsTimestampParse(&$Lyrics3data) { + public function Lyrics3LyricsTimestampParse(&$Lyrics3data) { $lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']); foreach ($lyricsarray as $key => $lyricline) { $regs = array(); @@ -283,7 +287,7 @@ class getid3_lyrics3 extends getid3_handler return true; } - function IntString2Bool($char) { + public function IntString2Bool($char) { if ($char == '1') { return true; } elseif ($char == '0') { @@ -292,6 +296,3 @@ class getid3_lyrics3 extends getid3_handler return null; } } - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.tag.xmp.php b/app/library/getid3/getid3/module.tag.xmp.php old mode 100644 new mode 100755 similarity index 96% rename from app/library/getid3/module.tag.xmp.php rename to app/library/getid3/getid3/module.tag.xmp.php index 141fd09d..ea769876 --- a/app/library/getid3/module.tag.xmp.php +++ b/app/library/getid3/getid3/module.tag.xmp.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -14,7 +15,7 @@ ///////////////////////////////////////////////////////////////// // // // Module originally written [2009-Mar-26] by // -// Nigel Barnes // +// Nigel Barnes // // Bundled into getID3 with permission // // called by getID3 in module.graphic.jpg.php // // /// @@ -37,21 +38,21 @@ class Image_XMP * The name of the image file that contains the XMP fields to extract and modify. * @see Image_XMP() */ - var $_sFilename = null; + public $_sFilename = null; /** * @var array * The XMP fields that were extracted from the image or updated by this class. * @see getAllTags() */ - var $_aXMP = array(); + public $_aXMP = array(); /** * @var boolean * True if an APP1 segment was found to contain XMP metadata. * @see isValid() */ - var $_bXMPParse = false; + public $_bXMPParse = false; /** * Returns the status of XMP parsing during instantiation @@ -61,7 +62,7 @@ class Image_XMP * @return boolean * Returns true if an APP1 segment was found to contain XMP metadata. */ - function isValid() + public function isValid() { return $this->_bXMPParse; } @@ -71,7 +72,7 @@ class Image_XMP * * @return array - An array of XMP fields as it extracted by the XMPparse() function */ - function getAllTags() + public function getAllTags() { return $this->_aXMP; } @@ -83,7 +84,7 @@ class Image_XMP * @return array $headerdata - Array of JPEG header segments * @return boolean FALSE - if headers could not be read */ - function _get_jpeg_header_data($filename) + public function _get_jpeg_header_data($filename) { // prevent refresh from aborting file operations and hosing file ignore_user_abort(true); @@ -193,7 +194,7 @@ class Image_XMP * @return string $xmp_data - the string of raw XML text * @return boolean FALSE - if an APP 1 XMP segment could not be found, or if an error occured */ - function _get_XMP_text($filename) + public function _get_XMP_text($filename) { //Get JPEG header data $jpeg_header_data = $this->_get_jpeg_header_data($filename); @@ -226,7 +227,7 @@ class Image_XMP * @return array $xmp_array - an array containing all xmp details retrieved. * @return boolean FALSE - couldn't parse the XMP data */ - function read_XMP_array_from_text($xmltext) + public function read_XMP_array_from_text($xmltext) { // Check if there actually is any text to parse if (trim($xmltext) == '') @@ -302,7 +303,8 @@ class Image_XMP foreach (array_keys($xml_elem['attributes']) as $key) { // Check whether we want this details from this attribute - if (in_array($key, $GLOBALS['XMP_tag_captions'])) +// if (in_array($key, $GLOBALS['XMP_tag_captions'])) + if (true) { // Attribute wanted $xmp_array[$key] = $xml_elem['attributes'][$key]; @@ -359,7 +361,8 @@ class Image_XMP default: // Check whether we want the details from this attribute - if (in_array($xml_elem['tag'], $GLOBALS['XMP_tag_captions'])) +// if (in_array($xml_elem['tag'], $GLOBALS['XMP_tag_captions'])) + if (true) { switch ($xml_elem['type']) { @@ -375,7 +378,7 @@ class Image_XMP case 'complete': // store attribute value - $xmp_array[$xml_elem['tag']] = (isset($xml_elem['value']) ? $xml_elem['value'] : ''); + $xmp_array[$xml_elem['tag']] = (isset($xml_elem['attributes']) ? $xml_elem['attributes'] : (isset($xml_elem['value']) ? $xml_elem['value'] : '')); break; case 'cdata': @@ -396,7 +399,7 @@ class Image_XMP * * @param string - Name of the image file to access and extract XMP information from. */ - function Image_XMP($sFilename) + public function Image_XMP($sFilename) { $this->_sFilename = $sFilename; @@ -420,6 +423,7 @@ class Image_XMP * The Property names of all known XMP fields. * Note: this is a full list with unrequired properties commented out. */ +/* $GLOBALS['XMP_tag_captions'] = array( // IPTC Core 'Iptc4xmpCore:CiAdrCity', @@ -688,7 +692,7 @@ $GLOBALS['XMP_tag_captions'] = array( 'exif:Rows', 'exif:Settings', ); - +*/ /** * Global Variable: JPEG_Segment_Names @@ -762,5 +766,3 @@ $GLOBALS['JPEG_Segment_Names'] = array( 0xFD => 'JPG13', 0xFE => 'COM', ); - -?> \ No newline at end of file diff --git a/app/library/getid3/write.apetag.php b/app/library/getid3/getid3/write.apetag.php old mode 100644 new mode 100755 similarity index 84% rename from app/library/getid3/write.apetag.php rename to app/library/getid3/getid3/write.apetag.php index 2b553699..f1f82bc4 --- a/app/library/getid3/write.apetag.php +++ b/app/library/getid3/getid3/write.apetag.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -19,17 +20,17 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE class getid3_write_apetag { - var $filename; - var $tag_data; - var $always_preserve_replaygain = true; // ReplayGain / MP3gain tags will be copied from old tag even if not passed in data - var $warnings = array(); // any non-critical errors will be stored here - var $errors = array(); // any critical errors will be stored here + public $filename; + public $tag_data; + public $always_preserve_replaygain = true; // ReplayGain / MP3gain tags will be copied from old tag even if not passed in data + public $warnings = array(); // any non-critical errors will be stored here + public $errors = array(); // any critical errors will be stored here - function getid3_write_apetag() { + public function getid3_write_apetag() { return true; } - function WriteAPEtag() { + public function WriteAPEtag() { // NOTE: All data passed to this function must be UTF-8 format $getID3 = new getID3; @@ -67,15 +68,15 @@ class getid3_write_apetag if (isset($ThisFileInfo['lyrics3']['tag_offset_start'])) { $PostAPEdataOffset = max($PostAPEdataOffset, $ThisFileInfo['lyrics3']['tag_offset_start']); } - fseek($fp, $PostAPEdataOffset, SEEK_SET); + fseek($fp, $PostAPEdataOffset); $PostAPEdata = ''; if ($ThisFileInfo['filesize'] > $PostAPEdataOffset) { $PostAPEdata = fread($fp, $ThisFileInfo['filesize'] - $PostAPEdataOffset); } - fseek($fp, $PostAPEdataOffset, SEEK_SET); + fseek($fp, $PostAPEdataOffset); if (isset($ThisFileInfo['ape']['tag_offset_start'])) { - fseek($fp, $ThisFileInfo['ape']['tag_offset_start'], SEEK_SET); + fseek($fp, $ThisFileInfo['ape']['tag_offset_start']); } ftruncate($fp, ftell($fp)); fwrite($fp, $APEtag, strlen($APEtag)); @@ -91,7 +92,7 @@ class getid3_write_apetag return false; } - function DeleteAPEtag() { + public function DeleteAPEtag() { $getID3 = new getID3; $ThisFileInfo = $getID3->analyze($this->filename); if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['ape']['tag_offset_end'])) { @@ -100,14 +101,14 @@ class getid3_write_apetag flock($fp, LOCK_EX); $oldignoreuserabort = ignore_user_abort(true); - fseek($fp, $ThisFileInfo['ape']['tag_offset_end'], SEEK_SET); + fseek($fp, $ThisFileInfo['ape']['tag_offset_end']); $DataAfterAPE = ''; if ($ThisFileInfo['filesize'] > $ThisFileInfo['ape']['tag_offset_end']) { $DataAfterAPE = fread($fp, $ThisFileInfo['filesize'] - $ThisFileInfo['ape']['tag_offset_end']); } ftruncate($fp, $ThisFileInfo['ape']['tag_offset_start']); - fseek($fp, $ThisFileInfo['ape']['tag_offset_start'], SEEK_SET); + fseek($fp, $ThisFileInfo['ape']['tag_offset_start']); if (!empty($DataAfterAPE)) { fwrite($fp, $DataAfterAPE, strlen($DataAfterAPE)); @@ -125,7 +126,7 @@ class getid3_write_apetag } - function GenerateAPEtag() { + public function GenerateAPEtag() { // NOTE: All data passed to this function must be UTF-8 format $items = array(); @@ -159,7 +160,7 @@ class getid3_write_apetag return $this->GenerateAPEtagHeaderFooter($items, true).implode('', $items).$this->GenerateAPEtagHeaderFooter($items, false); } - function GenerateAPEtagHeaderFooter(&$items, $isheader=false) { + public function GenerateAPEtagHeaderFooter(&$items, $isheader=false) { $tagdatalength = 0; foreach ($items as $itemdata) { $tagdatalength += strlen($itemdata); @@ -175,7 +176,7 @@ class getid3_write_apetag return $APEheader; } - function GenerateAPEtagFlags($header=true, $footer=true, $isheader=false, $encodingid=0, $readonly=false) { + public function GenerateAPEtagFlags($header=true, $footer=true, $isheader=false, $encodingid=0, $readonly=false) { $APEtagFlags = array_fill(0, 4, 0); if ($header) { $APEtagFlags[0] |= 0x80; // Tag contains a header @@ -188,8 +189,8 @@ class getid3_write_apetag } // 0: Item contains text information coded in UTF-8 - // 1: Item contains binary information ) - // 2: Item is a locator of external stored information ) + // 1: Item contains binary information °) + // 2: Item is a locator of external stored information °°) // 3: reserved $APEtagFlags[3] |= ($encodingid << 1); @@ -200,7 +201,7 @@ class getid3_write_apetag return chr($APEtagFlags[3]).chr($APEtagFlags[2]).chr($APEtagFlags[1]).chr($APEtagFlags[0]); } - function CleanAPEtagItemKey($itemkey) { + public function CleanAPEtagItemKey($itemkey) { $itemkey = preg_replace("#[^\x20-\x7E]#i", '', $itemkey); // http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html @@ -221,5 +222,3 @@ class getid3_write_apetag } } - -?> \ No newline at end of file diff --git a/app/library/getid3/write.id3v1.php b/app/library/getid3/getid3/write.id3v1.php old mode 100644 new mode 100755 similarity index 92% rename from app/library/getid3/write.id3v1.php rename to app/library/getid3/getid3/write.id3v1.php index cecccd8a..878f9902 --- a/app/library/getid3/write.id3v1.php +++ b/app/library/getid3/getid3/write.id3v1.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,17 +18,17 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE_ class getid3_write_id3v1 { - var $filename; - var $filesize; - var $tag_data; - var $warnings = array(); // any non-critical errors will be stored here - var $errors = array(); // any critical errors will be stored here + public $filename; + public $filesize; + public $tag_data; + public $warnings = array(); // any non-critical errors will be stored here + public $errors = array(); // any critical errors will be stored here - function getid3_write_id3v1() { + public function getid3_write_id3v1() { return true; } - function WriteID3v1() { + public function WriteID3v1() { // File MUST be writeable - CHMOD(646) at least if (!empty($this->filename) && is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename)) { $this->setRealFileSize(); @@ -65,7 +66,7 @@ class getid3_write_id3v1 return false; } - function FixID3v1Padding() { + public function FixID3v1Padding() { // ID3v1 data is supposed to be padded with NULL characters, but some taggers incorrectly use spaces // This function rewrites the ID3v1 tag with correct padding @@ -87,7 +88,7 @@ class getid3_write_id3v1 return false; } - function RemoveID3v1() { + public function RemoveID3v1() { // File MUST be writeable - CHMOD(646) at least if (!empty($this->filename) && is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename)) { $this->setRealFileSize(); @@ -115,7 +116,7 @@ class getid3_write_id3v1 return false; } - function setRealFileSize() { + public function setRealFileSize() { if (PHP_INT_MAX > 2147483647) { $this->filesize = filesize($this->filename); return true; @@ -134,5 +135,3 @@ class getid3_write_id3v1 } } - -?> \ No newline at end of file diff --git a/app/library/getid3/write.id3v2.php b/app/library/getid3/getid3/write.id3v2.php old mode 100644 new mode 100755 similarity index 96% rename from app/library/getid3/write.id3v2.php rename to app/library/getid3/getid3/write.id3v2.php index ee7c5de2..7b6eaa1d --- a/app/library/getid3/write.id3v2.php +++ b/app/library/getid3/getid3/write.id3v2.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,23 +18,23 @@ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE_ class getid3_write_id3v2 { - var $filename; - var $tag_data; - var $fread_buffer_size = 32768; // read buffer size in bytes - var $paddedlength = 4096; // minimum length of ID3v2 tag in bytes - var $majorversion = 3; // ID3v2 major version (2, 3 (recommended), 4) - var $minorversion = 0; // ID3v2 minor version - always 0 - var $merge_existing_data = false; // if true, merge new data with existing tags; if false, delete old tag data and only write new tags - var $id3v2_default_encodingid = 0; // default text encoding (ISO-8859-1) if not explicitly passed - var $id3v2_use_unsynchronisation = false; // the specs say it should be TRUE, but most other ID3v2-aware programs are broken if unsynchronization is used, so by default don't use it. - var $warnings = array(); // any non-critical errors will be stored here - var $errors = array(); // any critical errors will be stored here + public $filename; + public $tag_data; + public $fread_buffer_size = 32768; // read buffer size in bytes + public $paddedlength = 4096; // minimum length of ID3v2 tag in bytes + public $majorversion = 3; // ID3v2 major version (2, 3 (recommended), 4) + public $minorversion = 0; // ID3v2 minor version - always 0 + public $merge_existing_data = false; // if true, merge new data with existing tags; if false, delete old tag data and only write new tags + public $id3v2_default_encodingid = 0; // default text encoding (ISO-8859-1) if not explicitly passed + public $id3v2_use_unsynchronisation = false; // the specs say it should be TRUE, but most other ID3v2-aware programs are broken if unsynchronization is used, so by default don't use it. + public $warnings = array(); // any non-critical errors will be stored here + public $errors = array(); // any critical errors will be stored here - function getid3_write_id3v2() { + public function getid3_write_id3v2() { return true; } - function WriteID3v2() { + public function WriteID3v2() { // File MUST be writeable - CHMOD(646) at least. It's best if the // directory is also writeable, because that method is both faster and less susceptible to errors. @@ -91,7 +92,7 @@ class getid3_write_id3v2 rewind($fp_source); if (!empty($OldThisFileInfo['avdataoffset'])) { - fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); + fseek($fp_source, $OldThisFileInfo['avdataoffset']); } while ($buffer = fread($fp_source, $this->fread_buffer_size)) { @@ -133,7 +134,7 @@ class getid3_write_id3v2 return false; } - function RemoveID3v2() { + public function RemoveID3v2() { // File MUST be writeable - CHMOD(646) at least. It's best if the // directory is also writeable, because that method is both faster and less susceptible to errors. if (is_writeable(dirname($this->filename))) { @@ -152,7 +153,7 @@ class getid3_write_id3v2 } rewind($fp_source); if ($OldThisFileInfo['avdataoffset'] !== false) { - fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); + fseek($fp_source, $OldThisFileInfo['avdataoffset']); } if (is_writable($this->filename) && is_file($this->filename) && ($fp_temp = fopen($this->filename.'getid3tmp', 'w+b'))) { while ($buffer = fread($fp_source, $this->fread_buffer_size)) { @@ -187,7 +188,7 @@ class getid3_write_id3v2 } rewind($fp_source); if ($OldThisFileInfo['avdataoffset'] !== false) { - fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); + fseek($fp_source, $OldThisFileInfo['avdataoffset']); } if ($fp_temp = tmpfile()) { while ($buffer = fread($fp_source, $this->fread_buffer_size)) { @@ -225,7 +226,7 @@ class getid3_write_id3v2 } - function GenerateID3v2TagFlags($flags) { + public function GenerateID3v2TagFlags($flags) { switch ($this->majorversion) { case 4: // %abcd0000 @@ -259,7 +260,7 @@ class getid3_write_id3v2 } - function GenerateID3v2FrameFlags($TagAlter=false, $FileAlter=false, $ReadOnly=false, $Compression=false, $Encryption=false, $GroupingIdentity=false, $Unsynchronisation=false, $DataLengthIndicator=false) { + public function GenerateID3v2FrameFlags($TagAlter=false, $FileAlter=false, $ReadOnly=false, $Compression=false, $Encryption=false, $GroupingIdentity=false, $Unsynchronisation=false, $DataLengthIndicator=false) { switch ($this->majorversion) { case 4: // %0abc0000 %0h00kmnp @@ -299,7 +300,7 @@ class getid3_write_id3v2 return chr(bindec($flag1)).chr(bindec($flag2)); } - function GenerateID3v2FrameData($frame_name, $source_data_array) { + public function GenerateID3v2FrameData($frame_name, $source_data_array) { if (!getid3_id3v2::IsValidID3v2FrameName($frame_name, $this->majorversion)) { return false; } @@ -1173,7 +1174,7 @@ class getid3_write_id3v2 return $framedata; } - function ID3v2FrameIsAllowed($frame_name, $source_data_array) { + public function ID3v2FrameIsAllowed($frame_name, $source_data_array) { static $PreviousFrames = array(); if ($frame_name === null) { @@ -1530,7 +1531,7 @@ class getid3_write_id3v2 return true; } - function GenerateID3v2Tag($noerrorsonly=true) { + public function GenerateID3v2Tag($noerrorsonly=true) { $this->ID3v2FrameIsAllowed(null, ''); // clear static array in case this isn't the first call to $this->GenerateID3v2Tag() $tagstring = ''; @@ -1542,6 +1543,9 @@ class getid3_write_id3v2 unset($frame_flags); $frame_data = false; if ($this->ID3v2FrameIsAllowed($frame_name, $source_data_array)) { + if(array_key_exists('description', $source_data_array) && array_key_exists('encodingid', $source_data_array) && array_key_exists('encoding', $this->tag_data)) { + $source_data_array['description'] = getid3_lib::iconv_fallback($this->tag_data['encoding'], $source_data_array['encoding'], $source_data_array['description']); + } if ($frame_data = $this->GenerateID3v2FrameData($frame_name, $source_data_array)) { $FrameUnsynchronisation = false; if ($this->majorversion >= 4) { @@ -1641,7 +1645,7 @@ class getid3_write_id3v2 return false; } - function ID3v2IsValidPriceString($pricestring) { + public function ID3v2IsValidPriceString($pricestring) { if (getid3_id3v2::LanguageLookup(substr($pricestring, 0, 3), true) == '') { return false; } elseif (!$this->IsANumber(substr($pricestring, 3), true)) { @@ -1650,7 +1654,7 @@ class getid3_write_id3v2 return true; } - function ID3v2FrameFlagsLookupTagAlter($framename) { + public function ID3v2FrameFlagsLookupTagAlter($framename) { // unfinished switch ($framename) { case 'RGAD': @@ -1662,7 +1666,7 @@ class getid3_write_id3v2 return $allow; } - function ID3v2FrameFlagsLookupFileAlter($framename) { + public function ID3v2FrameFlagsLookupFileAlter($framename) { // unfinished switch ($framename) { case 'RGAD': @@ -1675,7 +1679,7 @@ class getid3_write_id3v2 } } - function ID3v2IsValidETCOevent($eventid) { + public function ID3v2IsValidETCOevent($eventid) { if (($eventid < 0) || ($eventid > 0xFF)) { // outside range of 1 byte return false; @@ -1695,7 +1699,7 @@ class getid3_write_id3v2 return true; } - function ID3v2IsValidSYLTtype($contenttype) { + public function ID3v2IsValidSYLTtype($contenttype) { if (($contenttype >= 0) && ($contenttype <= 8) && ($this->majorversion == 4)) { return true; } elseif (($contenttype >= 0) && ($contenttype <= 6) && ($this->majorversion == 3)) { @@ -1704,21 +1708,21 @@ class getid3_write_id3v2 return false; } - function ID3v2IsValidRVA2channeltype($channeltype) { + public function ID3v2IsValidRVA2channeltype($channeltype) { if (($channeltype >= 0) && ($channeltype <= 8) && ($this->majorversion == 4)) { return true; } return false; } - function ID3v2IsValidAPICpicturetype($picturetype) { + public function ID3v2IsValidAPICpicturetype($picturetype) { if (($picturetype >= 0) && ($picturetype <= 0x14) && ($this->majorversion >= 2) && ($this->majorversion <= 4)) { return true; } return false; } - function ID3v2IsValidAPICimageformat($imageformat) { + public function ID3v2IsValidAPICimageformat($imageformat) { if ($imageformat == '-->') { return true; } elseif ($this->majorversion == 2) { @@ -1733,28 +1737,28 @@ class getid3_write_id3v2 return false; } - function ID3v2IsValidCOMRreceivedAs($receivedas) { + public function ID3v2IsValidCOMRreceivedAs($receivedas) { if (($this->majorversion >= 3) && ($receivedas >= 0) && ($receivedas <= 8)) { return true; } return false; } - function ID3v2IsValidRGADname($RGADname) { + public function ID3v2IsValidRGADname($RGADname) { if (($RGADname >= 0) && ($RGADname <= 2)) { return true; } return false; } - function ID3v2IsValidRGADoriginator($RGADoriginator) { + public function ID3v2IsValidRGADoriginator($RGADoriginator) { if (($RGADoriginator >= 0) && ($RGADoriginator <= 3)) { return true; } return false; } - function ID3v2IsValidTextEncoding($textencodingbyte) { + public function ID3v2IsValidTextEncoding($textencodingbyte) { static $ID3v2IsValidTextEncoding_cache = array( 2 => array(true, true), 3 => array(true, true), @@ -1762,7 +1766,7 @@ class getid3_write_id3v2 return isset($ID3v2IsValidTextEncoding_cache[$this->majorversion][$textencodingbyte]); } - function Unsynchronise($data) { + public function Unsynchronise($data) { // Whenever a false synchronisation is found within the tag, one zeroed // byte is inserted after the first false synchronisation byte. The // format of a correct sync that should be altered by ID3 encoders is as @@ -1792,8 +1796,8 @@ class getid3_write_id3v2 return $unsyncheddata; } - function is_hash($var) { - // written by dev-nullchristophe*vg + public function is_hash($var) { + // written by dev-nullØchristophe*vg // taken from http://www.php.net/manual/en/function.array-merge-recursive.php if (is_array($var)) { $keys = array_keys($var); @@ -1807,8 +1811,8 @@ class getid3_write_id3v2 return false; } - function array_join_merge($arr1, $arr2) { - // written by dev-nullchristophe*vg + public function array_join_merge($arr1, $arr2) { + // written by dev-nullØchristophe*vg // taken from http://www.php.net/manual/en/function.array-merge-recursive.php if (is_array($arr1) && is_array($arr2)) { // the same -> merge @@ -1831,14 +1835,14 @@ class getid3_write_id3v2 } } - function IsValidMIMEstring($mimestring) { + public function IsValidMIMEstring($mimestring) { if ((strlen($mimestring) >= 3) && (strpos($mimestring, '/') > 0) && (strpos($mimestring, '/') < (strlen($mimestring) - 1))) { return true; } return false; } - function IsWithinBitRange($number, $maxbits, $signed=false) { + public function IsWithinBitRange($number, $maxbits, $signed=false) { if ($signed) { if (($number > (0 - pow(2, $maxbits - 1))) && ($number <= pow(2, $maxbits - 1))) { return true; @@ -1851,7 +1855,7 @@ class getid3_write_id3v2 return false; } - function safe_parse_url($url) { + public function safe_parse_url($url) { $parts = @parse_url($url); $parts['scheme'] = (isset($parts['scheme']) ? $parts['scheme'] : ''); $parts['host'] = (isset($parts['host']) ? $parts['host'] : ''); @@ -1862,7 +1866,7 @@ class getid3_write_id3v2 return $parts; } - function IsValidURL($url, $allowUserPass=false) { + public function IsValidURL($url, $allowUserPass=false) { if ($url == '') { return false; } @@ -1893,7 +1897,7 @@ class getid3_write_id3v2 return false; } - static function ID3v2ShortFrameNameLookup($majorversion, $long_description) { + public static function ID3v2ShortFrameNameLookup($majorversion, $long_description) { $long_description = str_replace(' ', '_', strtolower(trim($long_description))); static $ID3v2ShortFrameNameLookup = array(); if (empty($ID3v2ShortFrameNameLookup)) { @@ -1920,6 +1924,7 @@ class getid3_write_id3v2 $ID3v2ShortFrameNameLookup[2]['publisher'] = 'TPB'; $ID3v2ShortFrameNameLookup[2]['isrc'] = 'TRC'; $ID3v2ShortFrameNameLookup[2]['tracknumber'] = 'TRK'; + $ID3v2ShortFrameNameLookup[2]['track_number'] = 'TRK'; $ID3v2ShortFrameNameLookup[2]['size'] = 'TSI'; $ID3v2ShortFrameNameLookup[2]['encoder_settings'] = 'TSS'; $ID3v2ShortFrameNameLookup[2]['description'] = 'TT1'; @@ -1940,6 +1945,7 @@ class getid3_write_id3v2 // The following are common to ID3v2.3 and ID3v2.4 $ID3v2ShortFrameNameLookup[3]['audio_encryption'] = 'AENC'; $ID3v2ShortFrameNameLookup[3]['attached_picture'] = 'APIC'; + $ID3v2ShortFrameNameLookup[3]['picture'] = 'APIC'; $ID3v2ShortFrameNameLookup[3]['comment'] = 'COMM'; $ID3v2ShortFrameNameLookup[3]['commercial'] = 'COMR'; $ID3v2ShortFrameNameLookup[3]['encryption_method_registration'] = 'ENCR'; @@ -1987,6 +1993,7 @@ class getid3_write_id3v2 $ID3v2ShortFrameNameLookup[3]['part_of_a_set'] = 'TPOS'; $ID3v2ShortFrameNameLookup[3]['publisher'] = 'TPUB'; $ID3v2ShortFrameNameLookup[3]['tracknumber'] = 'TRCK'; + $ID3v2ShortFrameNameLookup[3]['track_number'] = 'TRCK'; $ID3v2ShortFrameNameLookup[3]['internet_radio_station_name'] = 'TRSN'; $ID3v2ShortFrameNameLookup[3]['internet_radio_station_owner'] = 'TRSO'; $ID3v2ShortFrameNameLookup[3]['isrc'] = 'TSRC'; @@ -2047,4 +2054,3 @@ class getid3_write_id3v2 } -?> diff --git a/app/library/getid3/write.lyrics3.php b/app/library/getid3/getid3/write.lyrics3.php old mode 100644 new mode 100755 similarity index 79% rename from app/library/getid3/write.lyrics3.php rename to app/library/getid3/getid3/write.lyrics3.php index fa49cd16..6546c3eb --- a/app/library/getid3/write.lyrics3.php +++ b/app/library/getid3/getid3/write.lyrics3.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -16,21 +17,21 @@ class getid3_write_lyrics3 { - var $filename; - var $tag_data; - //var $lyrics3_version = 2; // 1 or 2 - var $warnings = array(); // any non-critical errors will be stored here - var $errors = array(); // any critical errors will be stored here + public $filename; + public $tag_data; + //public $lyrics3_version = 2; // 1 or 2 + public $warnings = array(); // any non-critical errors will be stored here + public $errors = array(); // any critical errors will be stored here - function getid3_write_lyrics3() { + public function getid3_write_lyrics3() { return true; } - function WriteLyrics3() { + public function WriteLyrics3() { $this->errors[] = 'WriteLyrics3() not yet functional - cannot write Lyrics3'; return false; } - function DeleteLyrics3() { + public function DeleteLyrics3() { // Initialize getID3 engine $getID3 = new getID3; $ThisFileInfo = $getID3->analyze($this->filename); @@ -40,7 +41,7 @@ class getid3_write_lyrics3 flock($fp, LOCK_EX); $oldignoreuserabort = ignore_user_abort(true); - fseek($fp, $ThisFileInfo['lyrics3']['tag_offset_end'], SEEK_SET); + fseek($fp, $ThisFileInfo['lyrics3']['tag_offset_end']); $DataAfterLyrics3 = ''; if ($ThisFileInfo['filesize'] > $ThisFileInfo['lyrics3']['tag_offset_end']) { $DataAfterLyrics3 = fread($fp, $ThisFileInfo['filesize'] - $ThisFileInfo['lyrics3']['tag_offset_end']); @@ -49,7 +50,7 @@ class getid3_write_lyrics3 ftruncate($fp, $ThisFileInfo['lyrics3']['tag_offset_start']); if (!empty($DataAfterLyrics3)) { - fseek($fp, $ThisFileInfo['lyrics3']['tag_offset_start'], SEEK_SET); + fseek($fp, $ThisFileInfo['lyrics3']['tag_offset_start']); fwrite($fp, $DataAfterLyrics3, strlen($DataAfterLyrics3)); } @@ -69,5 +70,3 @@ class getid3_write_lyrics3 } } - -?> \ No newline at end of file diff --git a/app/library/getid3/write.metaflac.php b/app/library/getid3/getid3/write.metaflac.php old mode 100644 new mode 100755 similarity index 91% rename from app/library/getid3/write.metaflac.php rename to app/library/getid3/getid3/write.metaflac.php index dfd6950a..d7bee70a --- a/app/library/getid3/write.metaflac.php +++ b/app/library/getid3/getid3/write.metaflac.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,16 +18,16 @@ class getid3_write_metaflac { - var $filename; - var $tag_data; - var $warnings = array(); // any non-critical errors will be stored here - var $errors = array(); // any critical errors will be stored here + public $filename; + public $tag_data; + public $warnings = array(); // any non-critical errors will be stored here + public $errors = array(); // any critical errors will be stored here - function getid3_write_metaflac() { + public function getid3_write_metaflac() { return true; } - function WriteMetaFLAC() { + public function WriteMetaFLAC() { if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { $this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call metaflac, tags not written'; @@ -100,7 +101,7 @@ class getid3_write_metaflac } - function DeleteMetaFLAC() { + public function DeleteMetaFLAC() { if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { $this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call metaflac, tags not deleted'; @@ -146,18 +147,16 @@ class getid3_write_metaflac } - function CleanmetaflacName($originalcommentname) { + public function CleanmetaflacName($originalcommentname) { // A case-insensitive field name that may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded. // ASCII 0x41 through 0x5A inclusive (A-Z) is to be considered equivalent to ASCII 0x61 through // 0x7A inclusive (a-z). // replace invalid chars with a space, return uppercase text - // Thanks Chris Bolt for improving this function + // Thanks Chris Bolt for improving this function // note: *reg_replace() replaces nulls with empty string (not space) return strtoupper(preg_replace('#[^ -<>-}]#', ' ', str_replace("\x00", ' ', $originalcommentname))); } } - -?> \ No newline at end of file diff --git a/app/library/getid3/write.php b/app/library/getid3/getid3/write.php old mode 100644 new mode 100755 similarity index 91% rename from app/library/getid3/write.php rename to app/library/getid3/getid3/write.php index 16b19c7d..4c3ed620 --- a/app/library/getid3/write.php +++ b/app/library/getid3/getid3/write.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -22,7 +23,7 @@ if (!defined('GETID3_INCLUDEPATH')) { throw new Exception('getid3.php MUST be included before calling getid3_writetags'); } -if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { +if (!include_once(GETID3_INCLUDEPATH . 'getid3.lib.php')) { throw new Exception('write.php depends on getid3.lib.php, which is missing.'); } @@ -47,28 +48,28 @@ if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { class getid3_writetags { // public - var $filename; // absolute filename of file to write tags to - var $tagformats = array(); // array of tag formats to write ('id3v1', 'id3v2.2', 'id2v2.3', 'id3v2.4', 'ape', 'vorbiscomment', 'metaflac', 'real') - var $tag_data = array(array()); // 2-dimensional array of tag data (ex: $data['ARTIST'][0] = 'Elvis') - var $tag_encoding = 'ISO-8859-1'; // text encoding used for tag data ('ISO-8859-1', 'UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE', ) - var $overwrite_tags = true; // if true will erase existing tag data and write only passed data; if false will merge passed data with existing tag data - var $remove_other_tags = false; // if true will erase remove all existing tags and only write those passed in $tagformats; if false will ignore any tags not mentioned in $tagformats + public $filename; // absolute filename of file to write tags to + public $tagformats = array(); // array of tag formats to write ('id3v1', 'id3v2.2', 'id2v2.3', 'id3v2.4', 'ape', 'vorbiscomment', 'metaflac', 'real') + public $tag_data = array(array()); // 2-dimensional array of tag data (ex: $data['ARTIST'][0] = 'Elvis') + public $tag_encoding = 'ISO-8859-1'; // text encoding used for tag data ('ISO-8859-1', 'UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE', ) + public $overwrite_tags = true; // if true will erase existing tag data and write only passed data; if false will merge passed data with existing tag data + public $remove_other_tags = false; // if true will erase remove all existing tags and only write those passed in $tagformats; if false will ignore any tags not mentioned in $tagformats - var $id3v2_tag_language = 'eng'; // ISO-639-2 3-character language code needed for some ID3v2 frames (http://www.id3.org/iso639-2.html) - var $id3v2_paddedlength = 4096; // minimum length of ID3v2 tags (will be padded to this length if tag data is shorter) + public $id3v2_tag_language = 'eng'; // ISO-639-2 3-character language code needed for some ID3v2 frames (http://www.id3.org/iso639-2.html) + public $id3v2_paddedlength = 4096; // minimum length of ID3v2 tags (will be padded to this length if tag data is shorter) - var $warnings = array(); // any non-critical errors will be stored here - var $errors = array(); // any critical errors will be stored here + public $warnings = array(); // any non-critical errors will be stored here + public $errors = array(); // any critical errors will be stored here // private - var $ThisFileInfo; // analysis of file before writing + private $ThisFileInfo; // analysis of file before writing - function getid3_writetags() { + public function getid3_writetags() { return true; } - function WriteTags() { + public function WriteTags() { if (empty($this->filename)) { $this->errors[] = 'filename is undefined in getid3_writetags'; @@ -178,9 +179,7 @@ class getid3_writetags switch ($tagformat) { case 'ape': $GETID3_ERRORARRAY = &$this->errors; - if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.apetag.php', __FILE__, false)) { - return false; - } + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.apetag.php', __FILE__, true); break; case 'id3v1': @@ -189,9 +188,7 @@ class getid3_writetags case 'metaflac': case 'real': $GETID3_ERRORARRAY = &$this->errors; - if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.'.$tagformat.'.php', __FILE__, false)) { - return false; - } + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.'.$tagformat.'.php', __FILE__, true); break; case 'id3v2.2': @@ -199,9 +196,7 @@ class getid3_writetags case 'id3v2.4': case 'id3v2': $GETID3_ERRORARRAY = &$this->errors; - if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.id3v2.php', __FILE__, false)) { - return false; - } + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.id3v2.php', __FILE__, true); break; default: @@ -341,7 +336,7 @@ class getid3_writetags } - function DeleteTags($TagFormatsToDelete) { + public function DeleteTags($TagFormatsToDelete) { foreach ($TagFormatsToDelete as $DeleteTagFormat) { $success = false; // overridden if tag deletion is successful switch ($DeleteTagFormat) { @@ -414,7 +409,7 @@ class getid3_writetags } - function MergeExistingTagData($TagFormat, &$tag_data) { + public function MergeExistingTagData($TagFormat, &$tag_data) { // Merge supplied data with existing data, if requested if ($this->overwrite_tags) { // do nothing - ignore previous data @@ -428,7 +423,7 @@ throw new Exception('$this->overwrite_tags=false is known to be buggy in this ve return true; } - function FormatDataForAPE() { + public function FormatDataForAPE() { $ape_tag_data = array(); foreach ($this->tag_data as $tag_key => $valuearray) { switch ($tag_key) { @@ -455,7 +450,7 @@ throw new Exception('$this->overwrite_tags=false is known to be buggy in this ve } - function FormatDataForID3v1() { + public function FormatDataForID3v1() { $tag_data_id3v1['genreid'] = 255; if (!empty($this->tag_data['GENRE'])) { foreach ($this->tag_data['GENRE'] as $key => $value) { @@ -479,7 +474,7 @@ throw new Exception('$this->overwrite_tags=false is known to be buggy in this ve return $tag_data_id3v1; } - function FormatDataForID3v2($id3v2_majorversion) { + public function FormatDataForID3v2($id3v2_majorversion) { $tag_data_id3v2 = array(); $ID3v2_text_encoding_lookup[2] = array('ISO-8859-1'=>0, 'UTF-16'=>1); @@ -565,7 +560,7 @@ throw new Exception('$this->overwrite_tags=false is known to be buggy in this ve return $tag_data_id3v2; } - function FormatDataForVorbisComment() { + public function FormatDataForVorbisComment() { $tag_data_vorbiscomment = $this->tag_data; // check for multi-line comment values - split out to multiple comments if neccesary @@ -594,13 +589,13 @@ throw new Exception('$this->overwrite_tags=false is known to be buggy in this ve return $tag_data_vorbiscomment; } - function FormatDataForMetaFLAC() { + public function FormatDataForMetaFLAC() { // FLAC & OggFLAC use VorbisComments same as OggVorbis // but require metaflac to do the writing rather than vorbiscomment return $this->FormatDataForVorbisComment(); } - function FormatDataForReal() { + public function FormatDataForReal() { $tag_data_real['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE'] ) ? $this->tag_data['TITLE'] : array()))); $tag_data_real['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST'] ) ? $this->tag_data['ARTIST'] : array()))); $tag_data_real['copyright'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COPYRIGHT']) ? $this->tag_data['COPYRIGHT'] : array()))); @@ -611,5 +606,3 @@ throw new Exception('$this->overwrite_tags=false is known to be buggy in this ve } } - -?> \ No newline at end of file diff --git a/app/library/getid3/write.real.php b/app/library/getid3/getid3/write.real.php old mode 100644 new mode 100755 similarity index 91% rename from app/library/getid3/write.real.php rename to app/library/getid3/getid3/write.real.php index ad37e74a..04210e04 --- a/app/library/getid3/write.real.php +++ b/app/library/getid3/getid3/write.real.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -15,18 +16,18 @@ class getid3_write_real { - var $filename; - var $tag_data = array(); - var $fread_buffer_size = 32768; // read buffer size in bytes - var $warnings = array(); // any non-critical errors will be stored here - var $errors = array(); // any critical errors will be stored here - var $paddedlength = 512; // minimum length of CONT tag in bytes + public $filename; + public $tag_data = array(); + public $fread_buffer_size = 32768; // read buffer size in bytes + public $warnings = array(); // any non-critical errors will be stored here + public $errors = array(); // any critical errors will be stored here + public $paddedlength = 512; // minimum length of CONT tag in bytes - function getid3_write_real() { + public function getid3_write_real() { return true; } - function WriteReal() { + public function WriteReal() { // File MUST be writeable - CHMOD(646) at least if (is_writeable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) { @@ -56,7 +57,7 @@ class getid3_write_real $new__RMF_tag_data = $this->GenerateRMFchunk($OldThisFileInfo['real']['chunks']); if (isset($oldChunkInfo['.RMF']['length']) && ($oldChunkInfo['.RMF']['length'] == strlen($new__RMF_tag_data))) { - fseek($fp_source, $oldChunkInfo['.RMF']['offset'], SEEK_SET); + fseek($fp_source, $oldChunkInfo['.RMF']['offset']); fwrite($fp_source, $new__RMF_tag_data); } else { $this->errors[] = 'new .RMF tag ('.strlen($new__RMF_tag_data).' bytes) different length than old .RMF tag ('.$oldChunkInfo['.RMF']['length'].' bytes)'; @@ -65,7 +66,7 @@ class getid3_write_real } if (isset($oldChunkInfo['PROP']['length']) && ($oldChunkInfo['PROP']['length'] == strlen($new_PROP_tag_data))) { - fseek($fp_source, $oldChunkInfo['PROP']['offset'], SEEK_SET); + fseek($fp_source, $oldChunkInfo['PROP']['offset']); fwrite($fp_source, $new_PROP_tag_data); } else { $this->errors[] = 'new PROP tag ('.strlen($new_PROP_tag_data).' bytes) different length than old PROP tag ('.$oldChunkInfo['PROP']['length'].' bytes)'; @@ -76,7 +77,7 @@ class getid3_write_real if (isset($oldChunkInfo['CONT']['length']) && ($oldChunkInfo['CONT']['length'] == strlen($new_CONT_tag_data))) { // new data length is same as old data length - just overwrite - fseek($fp_source, $oldChunkInfo['CONT']['offset'], SEEK_SET); + fseek($fp_source, $oldChunkInfo['CONT']['offset']); fwrite($fp_source, $new_CONT_tag_data); fclose($fp_source); return true; @@ -98,7 +99,7 @@ class getid3_write_real rewind($fp_source); fwrite($fp_temp, fread($fp_source, $BeforeOffset)); fwrite($fp_temp, $new_CONT_tag_data); - fseek($fp_source, $AfterOffset, SEEK_SET); + fseek($fp_source, $AfterOffset); while ($buffer = fread($fp_source, $this->fread_buffer_size)) { fwrite($fp_temp, $buffer, strlen($buffer)); } @@ -126,7 +127,7 @@ class getid3_write_real return false; } - function GenerateRMFchunk(&$chunks) { + public function GenerateRMFchunk(&$chunks) { $oldCONTexists = false; foreach ($chunks as $key => $chunk) { $chunkNameKeys[$chunk['name']] = $key; @@ -144,7 +145,7 @@ class getid3_write_real return $RMFchunk; } - function GeneratePROPchunk(&$chunks, &$new_CONT_tag_data) { + public function GeneratePROPchunk(&$chunks, &$new_CONT_tag_data) { $old_CONT_length = 0; $old_DATA_offset = 0; $old_INDX_offset = 0; @@ -181,7 +182,7 @@ class getid3_write_real return $PROPchunk; } - function GenerateCONTchunk() { + public function GenerateCONTchunk() { foreach ($this->tag_data as $key => $value) { // limit each value to 0xFFFF bytes $this->tag_data[$key] = substr($value, 0, 65535); @@ -210,7 +211,7 @@ class getid3_write_real return $CONTchunk; } - function RemoveReal() { + public function RemoveReal() { // File MUST be writeable - CHMOD(646) at least if (is_writeable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) { @@ -245,7 +246,7 @@ class getid3_write_real rewind($fp_source); fwrite($fp_temp, fread($fp_source, $BeforeOffset)); - fseek($fp_source, $AfterOffset, SEEK_SET); + fseek($fp_source, $AfterOffset); while ($buffer = fread($fp_source, $this->fread_buffer_size)) { fwrite($fp_temp, $buffer, strlen($buffer)); } @@ -271,5 +272,3 @@ class getid3_write_real } } - -?> \ No newline at end of file diff --git a/app/library/getid3/write.vorbiscomment.php b/app/library/getid3/getid3/write.vorbiscomment.php old mode 100644 new mode 100755 similarity index 89% rename from app/library/getid3/write.vorbiscomment.php rename to app/library/getid3/getid3/write.vorbiscomment.php index ac8dc693..65f34b39 --- a/app/library/getid3/write.vorbiscomment.php +++ b/app/library/getid3/getid3/write.vorbiscomment.php @@ -3,6 +3,7 @@ /// getID3() by James Heinrich // // available at http://getid3.sourceforge.net // // or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // ///////////////////////////////////////////////////////////////// // See readme.txt for more details // ///////////////////////////////////////////////////////////////// @@ -17,16 +18,16 @@ class getid3_write_vorbiscomment { - var $filename; - var $tag_data; - var $warnings = array(); // any non-critical errors will be stored here - var $errors = array(); // any critical errors will be stored here + public $filename; + public $tag_data; + public $warnings = array(); // any non-critical errors will be stored here + public $errors = array(); // any critical errors will be stored here - function getid3_write_vorbiscomment() { + public function getid3_write_vorbiscomment() { return true; } - function WriteVorbisComment() { + public function WriteVorbisComment() { if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { $this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call vorbiscomment, tags not written'; @@ -99,23 +100,21 @@ class getid3_write_vorbiscomment return true; } - function DeleteVorbisComment() { + public function DeleteVorbisComment() { $this->tag_data = array(array()); return $this->WriteVorbisComment(); } - function CleanVorbisCommentName($originalcommentname) { + public function CleanVorbisCommentName($originalcommentname) { // A case-insensitive field name that may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded. // ASCII 0x41 through 0x5A inclusive (A-Z) is to be considered equivalent to ASCII 0x61 through // 0x7A inclusive (a-z). // replace invalid chars with a space, return uppercase text - // Thanks Chris Bolt for improving this function + // Thanks Chris Bolt for improving this function // note: *reg_replace() replaces nulls with empty string (not space) return strtoupper(preg_replace('#[^ -<>-}]#', ' ', str_replace("\x00", ' ', $originalcommentname))); } } - -?> \ No newline at end of file diff --git a/app/library/getid3/helperapps/head.exe b/app/library/getid3/helperapps/head.exe new file mode 100755 index 0000000000000000000000000000000000000000..7f5ef8764a259cccdad6c4f1acdf94ec241d2c13 GIT binary patch literal 24064 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 rFu1i+`Fd&Hopma)_%WpZ&i0E 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 z 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�naVAJJ}=s18$2tvlhhV}N8#QFX}E?OPv@ z(gnO~Yi0l~X}UPz8OBl79@}wpV8=;!5QHD-p&S5+E1_e;i!NDpo#!UIE{LQyX+;D5 zV8Rf4(yE4CLOq!dA;Bz3T2)&wn!agT38{j&NMOeMtvo=`b_%d&0lec1Fhi+zRq3XA z?Ef%a&ur2@tIk9u0>|U}9)3@Uwi;-_M32oMLKZ`;8*(2UUS!qLw9zU~58UNY&*L&y zJF`xGZH7a4vSBfT3GjzEI=-a#ZaBAuvOV2iES7(*#Y zV`v5!ltK_dQw|Readd;XnqVKga<`dYL?gQaPlf}RQe)8tBRR){&!I1>YO>)F>65Fu{bb6}?6cogG zAzg~UQ3oXIwa&weqHIPtCaO8jg-pGfmSpa!bh*WuF-eJqpE-bafWvD}PM<-2P zXF`&qHfVi@|Dx0v0BRcUI1P9^f}GT0Sd5lN#flJ6Xzmf)(!uc|Ki!T?#>+=lopHR| zC?jlQ%Oy&{M`NZErbtVdM+!QS;NqlUxzT2~KW(#-#!xb)dO*7nzl=R&$Jyg&cbq=E z!+xe@(IyFW=M6JuglC}Bw?@Ie437-R;R6G5WMpt?KQ8+Q`!J~);U@1|I25>#b ziYlzj6;B)GMdV@AsC~y*Jn17Y1u=cTWr%PnW;j#kFt$&idJwmACD-+|S#&!FnMcN6 zv@>80xc!nb>aal#rO5GNm4--ZIe?oaRm5th$5;UeksE|iiBc-iz_5cBtx`XlF=-4v z=fXoCWX8H(LnOn=}b^p+ZMFMFilie5fgI4DCZj#5@+=E6tL#z zX`x~2vqY|<;Hm40-z=qEP{$sk9Fz)e5i$wbxW%+n)ER=y=xq8!N`{sr1m-6uC3cGt zxFYT62FO7(vbJ}{cd-Sd4J+b3vU|gs8)H)`WIkioIKkZAEz`=M^^u!W#q@+6W8;Ss ztb2|Z$M;m2W^=j>iGt_3l4j~94{G6}k#d~VckqU-QB1Cfm0sOEW!)36EzAQ*m*}i8 zuGBD|-7{h|^-`Rr58dry#jzBFNw#@DASZpszBw8Vo~~lc%eJw*NG&(91mAFFT(4dr zUPaC%rjuhhEVRIzQ!0mu;>*Fb0W}KE0<151;z@qGA`f<<=reI?pK%0G?twGJ3R^a? z^}+%8MHBc_R4tq&0RivRC+R61G{%u$2SP`&Ht!f8Ml!FHsQfWJo%YfLKfUV+S?x(x zKH{qOiAa1Yyh8|X##~WZvr1<-p%0U6kpZ8M=7pj9qP9e}0r)#`U!m|go`#L6}os~kMW`6>Qy=-_bx92zO*zf_q5LScDO%QbWie z!}_p|Qj1sz^oHlYo`8_?V0vvjkv7;LSaX%nSLg%{FqOPWX`7EBxY4<7u1~JgirsN_ zy6J#n|BiqolK^Qcl+TAmWJZH&!Dd^hIyQq$gHK~}u%Zjz-YrBtDWQuD53(6>B%Z@0 z(?qj!>6ED{#v8fbnLiG(Y=aqu#Yb%q;?)hXuo)p3+}MA>+W^MA)JQRv!3vd|S(qMA zW^&mRbIII%VtOv^-s;I}4E*fCY{` z=5lF~X*E1JJUBv`Is1o3hVV~W;UBJ|JGe@;8Fc3~M|eH$D=|pzoNZOXcCggUF!9+rg4}ug8hQq_q?2 z@rCK!Trxo&Y`RZXg7>o-=-e*#)qPz*aQ+?b1Lx2Ap-D#|#pXlXY$J&YJj1rTdg~5D zR^e}DQ6LY+f7XW<#^ZF}4mmHXxz-i@z7%C9(#vH!_Jew@4-y^XRxu8f5-%))%T09% zSA9Z5%}WRdo`>$kL|}V`++P<&FBtrK7rm>}2Tq`xsL&g|DA7CAu?|i0IXoBViOKk6 z!e0^LW`$Mfok^VK>;Z+Jw*TNk?e_&>`MUFhZR}kV>Ke&Te1^dvLrP zqB^8(C>?)O%#E7~(`%`ckvpdToN;5F@A@GIZo z!{NZIbnC)0r=r5Mv*849NAVpla%gCj|L@3_+ggj9Colf{ayOaY{z%yL^yvKj=+x96 zwuO)Td9Tx7hrv{g+G8;P7}ZUW3tYZYruBQXrf^Qal=m*cppQQ_(D^z z@%Qst5LwlyZ!&|Ag_=)QOEU&#%XxYL*JD^C0%dv)AJ)QYr4R7iJ03NJb4Gj`>F|-%ldnxr&&9M4C*+6ENd_(7Nk0xeMCMPmIrIvRkN!h(tCuvjFd~D@=9`0wB*{4>1s;s={sy za62e;haKYHu6SC7j6jNg4cJT-nf49(+~>BoH6y$_&) z(|9=?yvr%>WVY#Em*=<>QRqrvAG}UY=ur4Mx_%FlPbX&%XOFm&;wpd(Y14@Z^inI? zWI7$}p+9_oedV|Hug|JU3xOKJ*6VKtzF)|;0^eVMEAWE?)F1cFg4Dl$cY*5PCZOui z!#_M$mu=2dH!Mf=0$@*8d+=v(A@4tippf>jOXEeHtfS+5;G*V|)`HESUuZt8xlKH- z`J4E6eIC)=+dLqy6jwBNi+FQabGyhli=r$_&C}wU&DCaI%!!3&uK84PZF5FUibV6y z=10UQ#mAao5?^b6vH4kXueh&yuK5%3Z_R%f_lSRJzFPcg^Dmo!A#Q8FPrO6Ct@#Jd zmx`B*KWhH2cwX~&n(Lz7dUoXZBfrfR#Ydxto5_x#!(XC5ckB$6N>z5-BibPtm_AkaB z61%v4N$fY;zuNwl*idYL`|9>+?6US?>>2IfjybU#+w<+Cv8TmO zw2#E5+lSlZvG>P5-2PPip4)zY?B-aj-HbkK{g(CTME`jGmC+YRU%38P>wg`+ zJ^G&YKaak7{q^gwiT>02KS#e4{nzy$M}NBhpX)z}emeSh>)%-aO7s)!e;fT!^aJY? z>r>HmG_`&_dfoc5^-Q!DZLHt4ekOWqy&5ev*|*J}@3)RIgFRDRgV8 zik)2~jH_Yy)Ds8W zQ^(UM@EXNTCOMH6vv3yRZ0b-N_5mp`sk0SI`I$H$%da&Q3wS|0UOi6%nW~M9I!e1% xyOKL8C44Ke0!M7O(uQ~02?NGtCPgm8@ByA)^r$}mcelCset!Kq1^%xn@IScCSxx`| literal 0 HcmV?d00001 diff --git a/app/library/getid3/helperapps/readme.helperapps.txt b/app/library/getid3/helperapps/readme.helperapps.txt new file mode 100755 index 00000000..6eb2fd2c --- /dev/null +++ b/app/library/getid3/helperapps/readme.helperapps.txt @@ -0,0 +1,56 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// +// // +// /helperapps/readme.txt - part of getID3() // +// List of binary files required under Windows for some // +// features and/or file formats // +// See /readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +This directory should contain binaries of various helper applications +that getID3() depends on to handle some file formats under Windows. + +The location of this directory is configurable in /getid3/getid3.php +as GETID3_HELPERAPPSDIR + +If this directory is empty, or you are missing any files, please +download the latest version of the "getID3()-WindowsSupport" package +from the usual download location (http://getid3.sourceforge.net) + + + +Included files: +===================================================== + +Taken from http://www.cygwin.com/ +* cygwin1.dll + +Taken from http://unxutils.sourceforge.net/ +* head.exe +* md5sum.exe +* tail.exe + +Taken from http://ebible.org/mpj/software.htm +* sha1sum.exe + +Taken from http://www.vorbis.com/download.psp +* vorbiscomment.exe + +Taken from http://flac.sourceforge.net/download.html +* metaflac.exe + +Taken from http://www.etree.org/shncom.html +* shorten.exe + + +///////////////////////////////////////////////////////////////// + +Changelog: + +2003.12.29: + * Initial release \ No newline at end of file diff --git a/app/library/getid3/helperapps/shorten.exe b/app/library/getid3/helperapps/shorten.exe new file mode 100755 index 0000000000000000000000000000000000000000..b82d6c350bcd31219af65d7e81c520f694391f57 GIT binary patch literal 60928 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 literal 0 HcmV?d00001 diff --git a/app/library/getid3/helperapps/tail.exe b/app/library/getid3/helperapps/tail.exe new file mode 100755 index 0000000000000000000000000000000000000000..36c2abc22fc8e5f5db6f8594b0438f991f68c4dd GIT binary patch literal 35328 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 |gR@n=XR&Hy8d2$~5zUXW_u)tuHG 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

eA^TcKXfd`EcwOiO5uTl4{ISlU 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 literal 0 HcmV?d00001 diff --git a/app/library/getid3/license.txt b/app/library/getid3/license.txt new file mode 100755 index 00000000..205c7e61 --- /dev/null +++ b/app/library/getid3/license.txt @@ -0,0 +1,29 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// + +***************************************************************** +***************************************************************** + + getID3() is released under multiple licenses. You may choose + from the following licenses, and use getID3 according to the + terms of the license most suitable to your project. + +GNU GPL: https://gnu.org/licenses/gpl.html (v3) + https://gnu.org/licenses/old-licenses/gpl-2.0.html (v2) + https://gnu.org/licenses/old-licenses/gpl-1.0.html (v1) + +GNU LGPL: https://gnu.org/licenses/lgpl.html (v3) + +Mozilla MPL: http://www.mozilla.org/MPL/2.0/ (v2) + +getID3 Commercial License: http://getid3.org/#gCL (payment required) + +***************************************************************** +***************************************************************** + +Copies of each of the above licenses are included in the 'licenses' +directory of the getID3 distribution. diff --git a/app/library/getid3/licenses/licence.gpl-10.txt b/app/library/getid3/licenses/licence.gpl-10.txt new file mode 100755 index 00000000..8de98afa --- /dev/null +++ b/app/library/getid3/licenses/licence.gpl-10.txt @@ -0,0 +1,251 @@ + + GNU GENERAL PUBLIC LICENSE + Version 1, February 1989 + + Copyright (C) 1989 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The license agreements of most software companies try to keep users +at the mercy of those companies. By contrast, our General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. The +General Public License applies to the Free Software Foundation's +software and to any other program whose authors commit to using it. +You can use it for your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Specifically, the General Public License is designed to make +sure that you have the freedom to give away or sell copies of free +software, that you receive source code or can get it if you want it, +that you can change the software or use pieces of it in new free +programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of a such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must tell them their rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work based +on the Program" means either the Program or any work containing the +Program or a portion of it, either verbatim or with modifications. Each +licensee is addressed as "you". + + 1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +General Public License and to the absence of any warranty; and give any +other recipients of the Program a copy of this General Public License +along with the Program. You may charge a fee for the physical act of +transferring a copy. + + 2. You may modify your copy or copies of the Program or any portion of +it, and copy and distribute such modifications under the terms of Paragraph +1 above, provided that you also do the following: + + a) cause the modified files to carry prominent notices stating that + you changed the files and the date of any change; and + + b) cause the whole of any work that you distribute or publish, that + in whole or in part contains the Program or any part thereof, either + with or without modifications, to be licensed at no charge to all + third parties under the terms of this General Public License (except + that you may choose to grant warranty protection to some or all + third parties, at your option). + + c) If the modified program normally reads commands interactively when + run, you must cause it, when started running for such interactive use + in the simplest and most usual way, to print or display an + announcement including an appropriate copyright notice and a notice + that there is no warranty (or else, saying that you provide a + warranty) and that users may redistribute the program under these + conditions, and telling the user how to view a copy of this General + Public License. + + d) You may charge a fee for the physical act of transferring a + copy, and you may at your option offer warranty protection in + exchange for a fee. + +Mere aggregation of another independent work with the Program (or its +derivative) on a volume of a storage or distribution medium does not bring +the other work under the scope of these terms. + + 3. You may copy and distribute the Program (or a portion or derivative of +it, under Paragraph 2) in object code or executable form under the terms of +Paragraphs 1 and 2 above provided that you also do one of the following: + + a) accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of + Paragraphs 1 and 2 above; or, + + b) accompany it with a written offer, valid for at least three + years, to give any third party free (except for a nominal charge + for the cost of distribution) a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of + Paragraphs 1 and 2 above; or, + + c) accompany it with the information you received as to where the + corresponding source code may be obtained. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form alone.) + +Source code for a work means the preferred form of the work for making +modifications to it. For an executable file, complete source code means +all the source code for all modules it contains; but, as a special +exception, it need not include source code for modules which are standard +libraries that accompany the operating system on which the executable +file runs, or for standard header files or definitions files that +accompany that operating system. + + 4. You may not copy, modify, sublicense, distribute or transfer the +Program except as expressly provided under this General Public License. +Any attempt otherwise to copy, modify, sublicense, distribute or transfer +the Program is void, and will automatically terminate your rights to use +the Program under this License. However, parties who have received +copies, or rights to use copies, from you under this General Public +License will not have their licenses terminated so long as such parties +remain in full compliance. + + 5. By copying, distributing or modifying the Program (or any work based +on the Program) you indicate your acceptance of this license to do so, +and all its terms and conditions. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these +terms and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. + + 7. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of the license which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +the license, you may choose any version ever published by the Free Software +Foundation. + + 8. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to humanity, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19xx name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than `show w' and `show +c'; they could even be mouse-clicks or menu items--whatever suits your +program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + program `Gnomovision' (a program to direct compilers to make passes + at assemblers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/app/library/getid3/licenses/licence.gpl-20.txt b/app/library/getid3/licenses/licence.gpl-20.txt new file mode 100755 index 00000000..d159169d --- /dev/null +++ b/app/library/getid3/licenses/licence.gpl-20.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/app/library/getid3/licenses/licence.gpl-30.txt b/app/library/getid3/licenses/licence.gpl-30.txt new file mode 100755 index 00000000..94a9ed02 --- /dev/null +++ b/app/library/getid3/licenses/licence.gpl-30.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/app/library/getid3/licenses/licence.lgpl-30.txt b/app/library/getid3/licenses/licence.lgpl-30.txt new file mode 100755 index 00000000..65c5ca88 --- /dev/null +++ b/app/library/getid3/licenses/licence.lgpl-30.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/app/library/getid3/licenses/licence.mpl-20.txt b/app/library/getid3/licenses/licence.mpl-20.txt new file mode 100755 index 00000000..14e2f777 --- /dev/null +++ b/app/library/getid3/licenses/licence.mpl-20.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + 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. License Grants and Conditions +-------------------------------- + +2.1. Grants + +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 such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +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 Software 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 placed in a text file included with all distributions of the Covered +Software under this License. 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. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software 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 Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any 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 Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, 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. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the 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. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/app/library/getid3/licenses/license.commercial.txt b/app/library/getid3/licenses/license.commercial.txt new file mode 100755 index 00000000..416e5a14 --- /dev/null +++ b/app/library/getid3/licenses/license.commercial.txt @@ -0,0 +1,27 @@ + getID3() Commercial License + =========================== + +getID3() is licensed under the "GNU Public License" (GPL) and/or the +"getID3() Commercial License" (gCL). This document describes the gCL. + +--------------------------------------------------------------------- + +The license is non-exclusively granted to a single person or company, +per payment of the license fee, for the lifetime of that person or +company. The license is non-transferrable. + +The gCL grants the licensee the right to use getID3() in commercial +closed-source projects. Modifications may be made to getID3() with no +obligation to release the modified source code. getID3() (or pieces +thereof) may be included in any number of projects authored (in whole +or in part) by the licensee. + +The licensee may use any version of getID3(), past, present or future, +as is most convenient. This license does not entitle the licensee to +receive any technical support, updates or bugfixes, except as such are +made publicly available to all getID3() users. + +The licensee may not sub-license getID3() itself, meaning that any +commercially released product containing all or parts of getID3() must +have added functionality beyond what is available in getID3(); +getID3() itself may not be re-licensed by the licensee. diff --git a/app/library/getid3/module.audio-video.mpeg.php b/app/library/getid3/module.audio-video.mpeg.php deleted file mode 100644 index 499b740c..00000000 --- a/app/library/getid3/module.audio-video.mpeg.php +++ /dev/null @@ -1,299 +0,0 @@ - // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // -///////////////////////////////////////////////////////////////// -// // -// module.audio-video.mpeg.php // -// module for analyzing MPEG files // -// dependencies: module.audio.mp3.php // -// /// -///////////////////////////////////////////////////////////////// - -getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); - -define('GETID3_MPEG_VIDEO_PICTURE_START', "\x00\x00\x01\x00"); -define('GETID3_MPEG_VIDEO_USER_DATA_START', "\x00\x00\x01\xB2"); -define('GETID3_MPEG_VIDEO_SEQUENCE_HEADER', "\x00\x00\x01\xB3"); -define('GETID3_MPEG_VIDEO_SEQUENCE_ERROR', "\x00\x00\x01\xB4"); -define('GETID3_MPEG_VIDEO_EXTENSION_START', "\x00\x00\x01\xB5"); -define('GETID3_MPEG_VIDEO_SEQUENCE_END', "\x00\x00\x01\xB7"); -define('GETID3_MPEG_VIDEO_GROUP_START', "\x00\x00\x01\xB8"); -define('GETID3_MPEG_AUDIO_START', "\x00\x00\x01\xC0"); - - -class getid3_mpeg extends getid3_handler -{ - - function Analyze() { - $info = &$this->getid3->info; - - if ($info['avdataend'] <= $info['avdataoffset']) { - $info['error'][] = '"avdataend" ('.$info['avdataend'].') is unexpectedly less-than-or-equal-to "avdataoffset" ('.$info['avdataoffset'].')'; - return false; - } - $info['fileformat'] = 'mpeg'; - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $MPEGstreamData = fread($this->getid3->fp, min(100000, $info['avdataend'] - $info['avdataoffset'])); - $MPEGstreamDataLength = strlen($MPEGstreamData); - - $foundVideo = true; - $VideoChunkOffset = 0; - while (substr($MPEGstreamData, $VideoChunkOffset++, 4) !== GETID3_MPEG_VIDEO_SEQUENCE_HEADER) { - if ($VideoChunkOffset >= $MPEGstreamDataLength) { - $foundVideo = false; - break; - } - } - if ($foundVideo) { - - // Start code 32 bits - // horizontal frame size 12 bits - // vertical frame size 12 bits - // pixel aspect ratio 4 bits - // frame rate 4 bits - // bitrate 18 bits - // marker bit 1 bit - // VBV buffer size 10 bits - // constrained parameter flag 1 bit - // intra quant. matrix flag 1 bit - // intra quant. matrix values 512 bits (present if matrix flag == 1) - // non-intra quant. matrix flag 1 bit - // non-intra quant. matrix values 512 bits (present if matrix flag == 1) - - $info['video']['dataformat'] = 'mpeg'; - - $VideoChunkOffset += (strlen(GETID3_MPEG_VIDEO_SEQUENCE_HEADER) - 1); - - $FrameSizeDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 3)); - $VideoChunkOffset += 3; - - $AspectRatioFrameRateDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 1)); - $VideoChunkOffset += 1; - - $assortedinformation = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 4)); - $VideoChunkOffset += 4; - - $info['mpeg']['video']['raw']['framesize_horizontal'] = ($FrameSizeDWORD & 0xFFF000) >> 12; // 12 bits for horizontal frame size - $info['mpeg']['video']['raw']['framesize_vertical'] = ($FrameSizeDWORD & 0x000FFF); // 12 bits for vertical frame size - $info['mpeg']['video']['raw']['pixel_aspect_ratio'] = ($AspectRatioFrameRateDWORD & 0xF0) >> 4; - $info['mpeg']['video']['raw']['frame_rate'] = ($AspectRatioFrameRateDWORD & 0x0F); - - $info['mpeg']['video']['framesize_horizontal'] = $info['mpeg']['video']['raw']['framesize_horizontal']; - $info['mpeg']['video']['framesize_vertical'] = $info['mpeg']['video']['raw']['framesize_vertical']; - - $info['mpeg']['video']['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']); - $info['mpeg']['video']['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']); - $info['mpeg']['video']['frame_rate'] = $this->MPEGvideoFramerateLookup($info['mpeg']['video']['raw']['frame_rate']); - - $info['mpeg']['video']['raw']['bitrate'] = getid3_lib::Bin2Dec(substr($assortedinformation, 0, 18)); - $info['mpeg']['video']['raw']['marker_bit'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 18, 1)); - $info['mpeg']['video']['raw']['vbv_buffer_size'] = getid3_lib::Bin2Dec(substr($assortedinformation, 19, 10)); - $info['mpeg']['video']['raw']['constrained_param_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 29, 1)); - $info['mpeg']['video']['raw']['intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 30, 1)); - if ($info['mpeg']['video']['raw']['intra_quant_flag']) { - - // read 512 bits - $info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)); - $VideoChunkOffset += 64; - - $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($info['mpeg']['video']['raw']['intra_quant'], 511, 1)); - $info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)).substr(getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)), 0, 511); - - if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) { - $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); - $VideoChunkOffset += 64; - } - - } else { - - $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)); - if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) { - $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); - $VideoChunkOffset += 64; - } - - } - - if ($info['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits - - $info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] cannot determine average bitrate of VBR MPEG video files'; - $info['mpeg']['video']['bitrate_mode'] = 'vbr'; - - } else { - - $info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400; - $info['mpeg']['video']['bitrate_mode'] = 'cbr'; - $info['video']['bitrate'] = $info['mpeg']['video']['bitrate']; - - } - - $info['video']['resolution_x'] = $info['mpeg']['video']['framesize_horizontal']; - $info['video']['resolution_y'] = $info['mpeg']['video']['framesize_vertical']; - $info['video']['frame_rate'] = $info['mpeg']['video']['frame_rate']; - $info['video']['bitrate_mode'] = $info['mpeg']['video']['bitrate_mode']; - $info['video']['pixel_aspect_ratio'] = $info['mpeg']['video']['pixel_aspect_ratio']; - $info['video']['lossless'] = false; - $info['video']['bits_per_sample'] = 24; - - } else { - - $info['error'][] = 'Could not find start of video block in the first 100,000 bytes (or before end of file) - this might not be an MPEG-video file?'; - - } - - //0x000001B3 begins the sequence_header of every MPEG video stream. - //But in MPEG-2, this header must immediately be followed by an - //extension_start_code (0x000001B5) with a sequence_extension ID (1). - //(This extension contains all the additional MPEG-2 stuff.) - //MPEG-1 doesn't have this extension, so that's a sure way to tell the - //difference between MPEG-1 and MPEG-2 video streams. - - if (substr($MPEGstreamData, $VideoChunkOffset, 4) == GETID3_MPEG_VIDEO_EXTENSION_START) { - $info['video']['codec'] = 'MPEG-2'; - } else { - $info['video']['codec'] = 'MPEG-1'; - } - - - $AudioChunkOffset = 0; - while (true) { - while (substr($MPEGstreamData, $AudioChunkOffset++, 4) !== GETID3_MPEG_AUDIO_START) { - if ($AudioChunkOffset >= $MPEGstreamDataLength) { - break 2; - } - } - - $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); - $getid3_temp->info = $info; - $getid3_mp3 = new getid3_mp3($getid3_temp); - for ($i = 0; $i <= 7; $i++) { - // some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after - // I have no idea why or what the difference is, so this is a stupid hack. - // If anybody has any better idea of what's going on, please let me know - info@getid3.org - fseek($getid3_temp->fp, ftell($this->getid3->fp), SEEK_SET); - $getid3_temp->info = $info; // only overwrite real data if valid header found - if ($getid3_mp3->decodeMPEGaudioHeader(($AudioChunkOffset + 3) + 8 + $i, $getid3_temp->info, false)) { - $info = $getid3_temp->info; - $info['audio']['bitrate_mode'] = 'cbr'; - $info['audio']['lossless'] = false; - unset($getid3_temp, $getid3_mp3); - break 2; - } - } - unset($getid3_temp, $getid3_mp3); - } - - // Temporary hack to account for interleaving overhead: - if (!empty($info['video']['bitrate']) && !empty($info['audio']['bitrate'])) { - $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['video']['bitrate'] + $info['audio']['bitrate']); - - // Interleaved MPEG audio/video files have a certain amount of overhead that varies - // by both video and audio bitrates, and not in any sensible, linear/logarithmic patter - // Use interpolated lookup tables to approximately guess how much is overhead, because - // playtime is calculated as filesize / total-bitrate - $info['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']); - - //switch ($info['video']['bitrate']) { - // case('5000000'): - // $multiplier = 0.93292642112380355828048824319889; - // break; - // case('5500000'): - // $multiplier = 0.93582895375200989965359777343219; - // break; - // case('6000000'): - // $multiplier = 0.93796247714820932532911373859139; - // break; - // case('7000000'): - // $multiplier = 0.9413264083635103463010117778776; - // break; - // default: - // $multiplier = 1; - // break; - //} - //$info['playtime_seconds'] *= $multiplier; - //$info['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.'; - if ($info['video']['bitrate'] < 50000) { - $info['warning'][] = 'Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.'; - } - } - - return true; - } - - - function MPEGsystemNonOverheadPercentage($VideoBitrate, $AudioBitrate) { - $OverheadPercentage = 0; - - $AudioBitrate = max(min($AudioBitrate / 1000, 384), 32); // limit to range of 32kbps - 384kbps (should be only legal bitrates, but maybe VBR?) - $VideoBitrate = max(min($VideoBitrate / 1000, 10000), 10); // limit to range of 10kbps - 10Mbps (beyond that curves flatten anyways, no big loss) - - - //OMBB[audiobitrate] = array(video-10kbps, video-100kbps, video-1000kbps, video-10000kbps) - $OverheadMultiplierByBitrate[32] = array(0, 0.9676287944368530, 0.9802276264360310, 0.9844916183244460, 0.9852821845179940); - $OverheadMultiplierByBitrate[48] = array(0, 0.9779100089209830, 0.9787770035359320, 0.9846738664076130, 0.9852683013799960); - $OverheadMultiplierByBitrate[56] = array(0, 0.9731249855367600, 0.9776624308938040, 0.9832606361852130, 0.9843922606633340); - $OverheadMultiplierByBitrate[64] = array(0, 0.9755642683275760, 0.9795256705493390, 0.9836573009193170, 0.9851122539404470); - $OverheadMultiplierByBitrate[96] = array(0, 0.9788025247497290, 0.9798553314148700, 0.9822956869792560, 0.9834815119124690); - $OverheadMultiplierByBitrate[128] = array(0, 0.9816940050925480, 0.9821675936072120, 0.9829756927470870, 0.9839763420152050); - $OverheadMultiplierByBitrate[160] = array(0, 0.9825894094561180, 0.9820913399073960, 0.9823907143253970, 0.9832821783651570); - $OverheadMultiplierByBitrate[192] = array(0, 0.9832038474336260, 0.9825731694317960, 0.9821028622712400, 0.9828262076447620); - $OverheadMultiplierByBitrate[224] = array(0, 0.9836516298538770, 0.9824718601823890, 0.9818302180625380, 0.9823735101626480); - $OverheadMultiplierByBitrate[256] = array(0, 0.9845863022094920, 0.9837229411967540, 0.9824521662210830, 0.9828645172100790); - $OverheadMultiplierByBitrate[320] = array(0, 0.9849565280263180, 0.9837683142805110, 0.9822885275960400, 0.9824424382727190); - $OverheadMultiplierByBitrate[384] = array(0, 0.9856094774357600, 0.9844573394432720, 0.9825970399837330, 0.9824673808303890); - - $BitrateToUseMin = 32; - $BitrateToUseMax = 32; - $previousBitrate = 32; - foreach ($OverheadMultiplierByBitrate as $key => $value) { - if ($AudioBitrate >= $previousBitrate) { - $BitrateToUseMin = $previousBitrate; - } - if ($AudioBitrate < $key) { - $BitrateToUseMax = $key; - break; - } - $previousBitrate = $key; - } - $FactorA = ($BitrateToUseMax - $AudioBitrate) / ($BitrateToUseMax - $BitrateToUseMin); - - $VideoBitrateLog10 = log10($VideoBitrate); - $VideoFactorMin1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][floor($VideoBitrateLog10)]; - $VideoFactorMin2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][floor($VideoBitrateLog10)]; - $VideoFactorMax1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][ceil($VideoBitrateLog10)]; - $VideoFactorMax2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][ceil($VideoBitrateLog10)]; - $FactorV = $VideoBitrateLog10 - floor($VideoBitrateLog10); - - $OverheadPercentage = $VideoFactorMin1 * $FactorA * $FactorV; - $OverheadPercentage += $VideoFactorMin2 * (1 - $FactorA) * $FactorV; - $OverheadPercentage += $VideoFactorMax1 * $FactorA * (1 - $FactorV); - $OverheadPercentage += $VideoFactorMax2 * (1 - $FactorA) * (1 - $FactorV); - - return $OverheadPercentage; - } - - - function MPEGvideoFramerateLookup($rawframerate) { - $MPEGvideoFramerateLookup = array(0, 23.976, 24, 25, 29.97, 30, 50, 59.94, 60); - return (isset($MPEGvideoFramerateLookup[$rawframerate]) ? (float) $MPEGvideoFramerateLookup[$rawframerate] : (float) 0); - } - - function MPEGvideoAspectRatioLookup($rawaspectratio) { - $MPEGvideoAspectRatioLookup = array(0, 1, 0.6735, 0.7031, 0.7615, 0.8055, 0.8437, 0.8935, 0.9157, 0.9815, 1.0255, 1.0695, 1.0950, 1.1575, 1.2015, 0); - return (isset($MPEGvideoAspectRatioLookup[$rawaspectratio]) ? (float) $MPEGvideoAspectRatioLookup[$rawaspectratio] : (float) 0); - } - - function MPEGvideoAspectRatioTextLookup($rawaspectratio) { - $MPEGvideoAspectRatioTextLookup = array('forbidden', 'square pixels', '0.6735', '16:9, 625 line, PAL', '0.7615', '0.8055', '16:9, 525 line, NTSC', '0.8935', '4:3, 625 line, PAL, CCIR601', '0.9815', '1.0255', '1.0695', '4:3, 525 line, NTSC, CCIR601', '1.1575', '1.2015', 'reserved'); - return (isset($MPEGvideoAspectRatioTextLookup[$rawaspectratio]) ? $MPEGvideoAspectRatioTextLookup[$rawaspectratio] : ''); - } - -} - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.dss.php b/app/library/getid3/module.audio.dss.php deleted file mode 100644 index b7b43676..00000000 --- a/app/library/getid3/module.audio.dss.php +++ /dev/null @@ -1,75 +0,0 @@ - // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // -///////////////////////////////////////////////////////////////// -// // -// module.audio.dss.php // -// module for analyzing Digital Speech Standard (DSS) files // -// dependencies: NONE // -// /// -///////////////////////////////////////////////////////////////// - - -class getid3_dss extends getid3_handler -{ - - function Analyze() { - $info = &$this->getid3->info; - - fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); - $DSSheader = fread($this->getid3->fp, 1256); - - if (!preg_match('#^(\x02|\x03)dss#', $DSSheader)) { - $info['error'][] = 'Expecting "[02-03] 64 73 73" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSSheader, 0, 4)).'"'; - return false; - } - - // some structure information taken from http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm - - // shortcut - $info['dss'] = array(); - $thisfile_dss = &$info['dss']; - - $info['fileformat'] = 'dss'; - $info['audio']['dataformat'] = 'dss'; - $info['audio']['bitrate_mode'] = 'cbr'; - //$thisfile_dss['encoding'] = 'ISO-8859-1'; - - $thisfile_dss['version'] = ord(substr($DSSheader, 0, 1)); - $thisfile_dss['date_create'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 38, 12)); - $thisfile_dss['date_complete'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 50, 12)); - //$thisfile_dss['length'] = intval(substr($DSSheader, 62, 6)); // I thought time was in seconds, it's actually HHMMSS - $thisfile_dss['length'] = intval((substr($DSSheader, 62, 2) * 3600) + (substr($DSSheader, 64, 2) * 60) + substr($DSSheader, 66, 2)); - $thisfile_dss['priority'] = ord(substr($DSSheader, 793, 1)); - $thisfile_dss['comments'] = trim(substr($DSSheader, 798, 100)); - - - //$info['audio']['bits_per_sample'] = ?; - //$info['audio']['sample_rate'] = ?; - $info['audio']['channels'] = 1; - - $info['playtime_seconds'] = $thisfile_dss['length']; - $info['audio']['bitrate'] = ($info['filesize'] * 8) / $info['playtime_seconds']; - - return true; - } - - function DSSdateStringToUnixDate($datestring) { - $y = substr($datestring, 0, 2); - $m = substr($datestring, 2, 2); - $d = substr($datestring, 4, 2); - $h = substr($datestring, 6, 2); - $i = substr($datestring, 8, 2); - $s = substr($datestring, 10, 2); - $y += (($y < 95) ? 2000 : 1900); - return mktime($h, $i, $s, $m, $d, $y); - } - -} - - -?> \ No newline at end of file diff --git a/app/library/getid3/module.audio.flac.php b/app/library/getid3/module.audio.flac.php deleted file mode 100644 index 98daec0f..00000000 --- a/app/library/getid3/module.audio.flac.php +++ /dev/null @@ -1,480 +0,0 @@ - // -// available at http://getid3.sourceforge.net // -// or http://www.getid3.org // -///////////////////////////////////////////////////////////////// -// See readme.txt for more details // -///////////////////////////////////////////////////////////////// -// // -// module.audio.flac.php // -// module for analyzing FLAC and OggFLAC audio files // -// dependencies: module.audio.ogg.php // -// /// -///////////////////////////////////////////////////////////////// - - -getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); - -class getid3_flac extends getid3_handler -{ - var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory - - function Analyze() { - $info = &$this->getid3->info; - - // http://flac.sourceforge.net/format.html - - $this->fseek($info['avdataoffset'], SEEK_SET); - $StreamMarker = $this->fread(4); - $magic = 'fLaC'; - if ($StreamMarker != $magic) { - $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"'; - return false; - } - $info['fileformat'] = 'flac'; - $info['audio']['dataformat'] = 'flac'; - $info['audio']['bitrate_mode'] = 'vbr'; - $info['audio']['lossless'] = true; - - return $this->FLACparseMETAdata(); - } - - - function FLACparseMETAdata() { - $info = &$this->getid3->info; - do { - $METAdataBlockOffset = $this->ftell(); - $METAdataBlockHeader = $this->fread(4); - $METAdataLastBlockFlag = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x80); - $METAdataBlockType = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x7F; - $METAdataBlockLength = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 1, 3)); - $METAdataBlockTypeText = getid3_flac::FLACmetaBlockTypeLookup($METAdataBlockType); - - if ($METAdataBlockLength < 0) { - $info['error'][] = 'corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset; - break; - } - - $info['flac'][$METAdataBlockTypeText]['raw'] = array(); - $ThisFileInfo_flac_METAdataBlockTypeText_raw = &$info['flac'][$METAdataBlockTypeText]['raw']; - - $ThisFileInfo_flac_METAdataBlockTypeText_raw['offset'] = $METAdataBlockOffset; - $ThisFileInfo_flac_METAdataBlockTypeText_raw['last_meta_block'] = $METAdataLastBlockFlag; - $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type'] = $METAdataBlockType; - $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type_text'] = $METAdataBlockTypeText; - $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_length'] = $METAdataBlockLength; - if (($METAdataBlockOffset + 4 + $METAdataBlockLength) > $info['avdataend']) { - $info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset.' extends beyond end of file'; - break; - } - if ($METAdataBlockLength < 1) { - $info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$METAdataBlockLength.') at offset '.$METAdataBlockOffset.' is invalid'; - break; - } - $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'] = $this->fread($METAdataBlockLength); - $info['avdataoffset'] = $this->ftell(); - - switch ($METAdataBlockTypeText) { - case 'STREAMINFO': // 0x00 - if (!$this->FLACparseSTREAMINFO($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { - return false; - } - break; - - case 'PADDING': // 0x01 - // ignore - break; - - case 'APPLICATION': // 0x02 - if (!$this->FLACparseAPPLICATION($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { - return false; - } - break; - - case 'SEEKTABLE': // 0x03 - if (!$this->FLACparseSEEKTABLE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { - return false; - } - break; - - case 'VORBIS_COMMENT': // 0x04 - $getid3_temp = new getID3(); - $getid3_temp->openfile($this->getid3->filename); - $getid3_temp->info['avdataoffset'] = $this->ftell() - $METAdataBlockLength; - $getid3_temp->info['audio']['dataformat'] = 'flac'; - $getid3_temp->info['flac'] = $info['flac']; - $getid3_ogg = new getid3_ogg($getid3_temp); - $getid3_ogg->ParseVorbisCommentsFilepointer(); - $maybe_copy_keys = array('vendor', 'comments_raw', 'comments', 'replay_gain'); - foreach ($maybe_copy_keys as $maybe_copy_key) { - if (!empty($getid3_temp->info['ogg'][$maybe_copy_key])) { - $info['ogg'][$maybe_copy_key] = $getid3_temp->info['ogg'][$maybe_copy_key]; - } - } - if (!empty($getid3_temp->info['replay_gain'])) { - $info['replay_gain'] = $getid3_temp->info['replay_gain']; - } - unset($getid3_temp, $getid3_ogg); - break; - - case 'CUESHEET': // 0x05 - if (!getid3_flac::FLACparseCUESHEET($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { - return false; - } - break; - - case 'PICTURE': // 0x06 - if (!getid3_flac::FLACparsePICTURE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) { - return false; - } - break; - - default: - $info['warning'][] = 'Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset; - break; - } - - } while ($METAdataLastBlockFlag === false); - - if (isset($info['flac']['PICTURE'])) { - foreach ($info['flac']['PICTURE'] as $key => $valuearray) { - if (!empty($valuearray['image_mime']) && !empty($valuearray['data'])) { - $info['ogg']['comments']['picture'][] = array('image_mime'=>$valuearray['image_mime'], 'data'=>$valuearray['data']); - } - } - } - - if (isset($info['flac']['STREAMINFO'])) { - $info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset']; - $info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8); - if ($info['flac']['uncompressed_audio_bytes'] == 0) { - $info['error'][] = 'Corrupt FLAC file: uncompressed_audio_bytes == zero'; - return false; - } - $info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes']; - } - - // set md5_data_source - built into flac 0.5+ - if (isset($info['flac']['STREAMINFO']['audio_signature'])) { - - if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) { - - $info['warning'][] = 'FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'; - - } else { - - $info['md5_data_source'] = ''; - $md5 = $info['flac']['STREAMINFO']['audio_signature']; - for ($i = 0; $i < strlen($md5); $i++) { - $info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT); - } - if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) { - unset($info['md5_data_source']); - } - - } - - } - - $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; - if ($info['audio']['bits_per_sample'] == 8) { - // special case - // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value - // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed - $info['warning'][] = 'FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file'; - } - if (!empty($info['ogg']['vendor'])) { - $info['audio']['encoder'] = $info['ogg']['vendor']; - } - - return true; - } - - static function FLACmetaBlockTypeLookup($blocktype) { - static $FLACmetaBlockTypeLookup = array(); - if (empty($FLACmetaBlockTypeLookup)) { - $FLACmetaBlockTypeLookup[0] = 'STREAMINFO'; - $FLACmetaBlockTypeLookup[1] = 'PADDING'; - $FLACmetaBlockTypeLookup[2] = 'APPLICATION'; - $FLACmetaBlockTypeLookup[3] = 'SEEKTABLE'; - $FLACmetaBlockTypeLookup[4] = 'VORBIS_COMMENT'; - $FLACmetaBlockTypeLookup[5] = 'CUESHEET'; - $FLACmetaBlockTypeLookup[6] = 'PICTURE'; - } - return (isset($FLACmetaBlockTypeLookup[$blocktype]) ? $FLACmetaBlockTypeLookup[$blocktype] : 'reserved'); - } - - static function FLACapplicationIDLookup($applicationid) { - static $FLACapplicationIDLookup = array(); - if (empty($FLACapplicationIDLookup)) { - // http://flac.sourceforge.net/id.html - $FLACapplicationIDLookup[0x46746F6C] = 'flac-tools'; // 'Ftol' - $FLACapplicationIDLookup[0x46746F6C] = 'Sound Font FLAC'; // 'SFFL' - } - return (isset($FLACapplicationIDLookup[$applicationid]) ? $FLACapplicationIDLookup[$applicationid] : 'reserved'); - } - - static function FLACpictureTypeLookup($type_id) { - static $lookup = array ( - 0 => 'Other', - 1 => '32x32 pixels \'file icon\' (PNG only)', - 2 => 'Other file icon', - 3 => 'Cover (front)', - 4 => 'Cover (back)', - 5 => 'Leaflet page', - 6 => 'Media (e.g. label side of CD)', - 7 => 'Lead artist/lead performer/soloist', - 8 => 'Artist/performer', - 9 => 'Conductor', - 10 => 'Band/Orchestra', - 11 => 'Composer', - 12 => 'Lyricist/text writer', - 13 => 'Recording Location', - 14 => 'During recording', - 15 => 'During performance', - 16 => 'Movie/video screen capture', - 17 => 'A bright coloured fish', - 18 => 'Illustration', - 19 => 'Band/artist logotype', - 20 => 'Publisher/Studio logotype', - ); - return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved'); - } - - function FLACparseSTREAMINFO($METAdataBlockData) { - $info = &$this->getid3->info; - - $offset = 0; - $info['flac']['STREAMINFO']['min_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); - $offset += 2; - $info['flac']['STREAMINFO']['max_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); - $offset += 2; - $info['flac']['STREAMINFO']['min_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3)); - $offset += 3; - $info['flac']['STREAMINFO']['max_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3)); - $offset += 3; - - $SampleRateChannelsSampleBitsStreamSamples = getid3_lib::BigEndian2Bin(substr($METAdataBlockData, $offset, 8)); - $info['flac']['STREAMINFO']['sample_rate'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 0, 20)); - $info['flac']['STREAMINFO']['channels'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 20, 3)) + 1; - $info['flac']['STREAMINFO']['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 23, 5)) + 1; - $info['flac']['STREAMINFO']['samples_stream'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 28, 36)); - $offset += 8; - - $info['flac']['STREAMINFO']['audio_signature'] = substr($METAdataBlockData, $offset, 16); - $offset += 16; - - if (!empty($info['flac']['STREAMINFO']['sample_rate'])) { - - $info['audio']['bitrate_mode'] = 'vbr'; - $info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate']; - $info['audio']['channels'] = $info['flac']['STREAMINFO']['channels']; - $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; - $info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate']; - if ($info['playtime_seconds'] > 0) { - $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; - } - - } else { - $info['error'][] = 'Corrupt METAdata block: STREAMINFO'; - return false; - } - unset($info['flac']['STREAMINFO']['raw']); - return true; - } - - - function FLACparseAPPLICATION($METAdataBlockData) { - $info = &$this->getid3->info; - - $offset = 0; - $ApplicationID = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 4)); - $offset += 4; - $info['flac']['APPLICATION'][$ApplicationID]['name'] = getid3_flac::FLACapplicationIDLookup($ApplicationID); - $info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($METAdataBlockData, $offset); - $offset = $METAdataBlockLength; - - unset($info['flac']['APPLICATION']['raw']); - return true; - } - - - function FLACparseSEEKTABLE($METAdataBlockData) { - $info = &$this->getid3->info; - - $offset = 0; - $METAdataBlockLength = strlen($METAdataBlockData); - $placeholderpattern = str_repeat("\xFF", 8); - while ($offset < $METAdataBlockLength) { - $SampleNumberString = substr($METAdataBlockData, $offset, 8); - $offset += 8; - if ($SampleNumberString == $placeholderpattern) { - - // placeholder point - getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1); - $offset += 10; - - } else { - - $SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString); - $info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); - $offset += 8; - $info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); - $offset += 2; - - } - } - - unset($info['flac']['SEEKTABLE']['raw']); - - return true; - } - - function FLACparseCUESHEET($METAdataBlockData) { - $info = &$this->getid3->info; - $offset = 0; - $info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($METAdataBlockData, $offset, 128), "\0"); - $offset += 128; - $info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); - $offset += 8; - $info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)) & 0x80); - $offset += 1; - - $offset += 258; // reserved - - $info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); - $offset += 1; - - for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) { - $TrackSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); - $offset += 8; - $TrackNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); - $offset += 1; - - $info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset; - - $info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($METAdataBlockData, $offset, 12); - $offset += 12; - - $TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); - $offset += 1; - $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80); - $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40); - - $offset += 13; // reserved - - $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); - $offset += 1; - - for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) { - $IndexSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); - $offset += 8; - $IndexNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); - $offset += 1; - - $offset += 3; // reserved - - $info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset; - } - } - - unset($info['flac']['CUESHEET']['raw']); - - return true; - } - - - function FLACparsePICTURE($meta_data_block_data) { - $info = &$this->getid3->info; - $picture = &$info['flac']['PICTURE'][sizeof($info['flac']['PICTURE']) - 1]; - $picture['offset'] = $info['flac']['PICTURE']['raw']['offset']; - unset($info['flac']['PICTURE']['raw']); - - $offset = 0; - - $picture['typeid'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); - $picture['type'] = getid3_flac::FLACpictureTypeLookup($picture['typeid']); - $offset += 4; - - $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); - $offset += 4; - - $picture['image_mime'] = substr($meta_data_block_data, $offset, $length); - $offset += $length; - - $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); - $offset += 4; - - $picture['description'] = substr($meta_data_block_data, $offset, $length); - $offset += $length; - - $picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); - $offset += 4; - - $picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); - $offset += 4; - - $picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); - $offset += 4; - - $picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); - $offset += 4; - - $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); - $offset += 4; - - $picture['data'] = substr($meta_data_block_data, $offset, $length); - $offset += $length; - $picture['data_length'] = strlen($picture['data']); - - - do { - if ($this->inline_attachments === false) { - // skip entirely - unset($picture['data']); - break; - } - if ($this->inline_attachments === true) { - // great - } elseif (is_int($this->inline_attachments)) { - if ($this->inline_attachments < $picture['data_length']) { - // too big, skip - $info['warning'][] = 'attachment at '.$picture['offset'].' is too large to process inline ('.number_format($picture['data_length']).' bytes)'; - unset($picture['data']); - break; - } - } elseif (is_string($this->inline_attachments)) { - $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR); - if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) { - // cannot write, skip - $info['warning'][] = 'attachment at '.$picture['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)'; - unset($picture['data']); - break; - } - } - // if we get this far, must be OK - if (is_string($this->inline_attachments)) { - $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$picture['offset']; - if (!file_exists($destination_filename) || is_writable($destination_filename)) { - file_put_contents($destination_filename, $picture['data']); - } else { - $info['warning'][] = 'attachment at '.$picture['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)'; - } - $picture['data_filename'] = $destination_filename; - unset($picture['data']); - } else { - if (!isset($info['flac']['comments']['picture'])) { - $info['flac']['comments']['picture'] = array(); - } - $info['flac']['comments']['picture'][] = array('data'=>$picture['data'], 'image_mime'=>$picture['image_mime']); - } - } while (false); - - - - return true; - } -} - -?> \ No newline at end of file diff --git a/app/library/getid3/readme.txt b/app/library/getid3/readme.txt new file mode 100755 index 00000000..deebf4dc --- /dev/null +++ b/app/library/getid3/readme.txt @@ -0,0 +1,606 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// + +***************************************************************** +***************************************************************** + + getID3() is released under multiple licenses. You may choose + from the following licenses, and use getID3 according to the + terms of the license most suitable to your project. + +GNU GPL: https://gnu.org/licenses/gpl.html (v3) + https://gnu.org/licenses/old-licenses/gpl-2.0.html (v2) + https://gnu.org/licenses/old-licenses/gpl-1.0.html (v1) + +GNU LGPL: https://gnu.org/licenses/lgpl.html (v3) + +Mozilla MPL: http://www.mozilla.org/MPL/2.0/ (v2) + +getID3 Commercial License: http://getid3.org/#gCL (payment required) + +***************************************************************** +***************************************************************** +Copies of each of the above licenses are included in the 'licenses' +directory of the getID3 distribution. + + + +---------------------------------------------+ + | If you want to donate, there is a link on | + | http://www.getid3.org for PayPal donations. | + +---------------------------------------------+ + + +Quick Start +=========================================================================== + +Q: How can I check that getID3() works on my server/files? +A: Unzip getID3() to a directory, then access /demos/demo.browse.php + + + +Support +=========================================================================== + +Q: I have a question, or I found a bug. What do I do? +A: The preferred method of support requests and/or bug reports is the + forum at http://support.getid3.org/ + + + +Sourceforge Notification +=========================================================================== + +It's highly recommended that you sign up for notification from +Sourceforge for when new versions are released. Please visit: +http://sourceforge.net/project/showfiles.php?group_id=55859 +and click the little "monitor package" icon/link. If you're +previously signed up for the mailing list, be aware that it has +been discontinued, only the automated Sourceforge notification +will be used from now on. + + + +What does getID3() do? +=========================================================================== + +Reads & parses (to varying degrees): + ¤ tags: + * APE (v1 and v2) + * ID3v1 (& ID3v1.1) + * ID3v2 (v2.4, v2.3, v2.2) + * Lyrics3 (v1 & v2) + + ¤ audio-lossy: + * MP3/MP2/MP1 + * MPC / Musepack + * Ogg (Vorbis, OggFLAC, Speex, Opus) + * AAC / MP4 + * AC3 + * DTS + * RealAudio + * Speex + * DSS + * VQF + + ¤ audio-lossless: + * AIFF + * AU + * Bonk + * CD-audio (*.cda) + * FLAC + * LA (Lossless Audio) + * LiteWave + * LPAC + * MIDI + * Monkey's Audio + * OptimFROG + * RKAU + * Shorten + * TTA + * VOC + * WAV (RIFF) + * WavPack + + ¤ audio-video: + * ASF: ASF, Windows Media Audio (WMA), Windows Media Video (WMV) + * AVI (RIFF) + * Flash + * Matroska (MKV) + * MPEG-1 / MPEG-2 + * NSV (Nullsoft Streaming Video) + * Quicktime (including MP4) + * RealVideo + + ¤ still image: + * BMP + * GIF + * JPEG + * PNG + * TIFF + * SWF (Flash) + * PhotoCD + + ¤ data: + * ISO-9660 CD-ROM image (directory structure) + * SZIP (limited support) + * ZIP (directory structure) + * TAR + * CUE + + +Writes: + * ID3v1 (& ID3v1.1) + * ID3v2 (v2.3 & v2.4) + * VorbisComment on OggVorbis + * VorbisComment on FLAC (not OggFLAC) + * APE v2 + * Lyrics3 (delete only) + + + +Requirements +=========================================================================== + +* PHP 4.2.0 up to 5.2.x for getID3() 1.7.x (and earlier) +* PHP 5.0.5 (or higher) for getID3() 1.8.x (and up) +* PHP 5.0.5 (or higher) for getID3() 2.0.x (and up) +* at least 4MB memory for PHP. 8MB or more is highly recommended. + 12MB is required with all modules loaded. + + + +Usage +=========================================================================== + +See /demos/demo.basic.php for a very basic use of getID3() with no +fancy output, just scanning one file. + +See structure.txt for the returned data structure. + +*> For an example of a complete directory-browsing, <* +*> file-scanning implementation of getID3(), please run <* +*> /demos/demo.browse.php <* + +See /demos/demo.mysql.php for a sample recursive scanning code that +scans every file in a given directory, and all sub-directories, stores +the results in a database and allows various analysis / maintenance +operations + +To analyze remote files over HTTP or FTP you need to copy the file +locally first before running getID3(). Your code would look something +like this: + +// Copy remote file locally to scan with getID3() +$remotefilename = 'http://www.example.com/filename.mp3'; +if ($fp_remote = fopen($remotefilename, 'rb')) { + $localtempfilename = tempnam('/tmp', 'getID3'); + if ($fp_local = fopen($localtempfilename, 'wb')) { + while ($buffer = fread($fp_remote, 8192)) { + fwrite($fp_local, $buffer); + } + fclose($fp_local); + + // Initialize getID3 engine + $getID3 = new getID3; + + $ThisFileInfo = $getID3->analyze($filename); + + // Delete temporary file + unlink($localtempfilename); + } + fclose($fp_remote); +} + + +See /demos/demo.write.php for how to write tags. + + + +What does the returned data structure look like? +=========================================================================== + +See structure.txt + +It is recommended that you look at the output of +/demos/demo.browse.php scanning the file(s) you're interested in to +confirm what data is actually returned for any particular filetype in +general, and your files in particular, as the actual data returned +may vary considerably depending on what information is available in +the file itself. + + + +Notes +=========================================================================== + +getID3() 1.x: +If the format parser encounters a critical problem, it will return +something in $fileinfo['error'], describing the encountered error. If +a less critical error or notice is generated it will appear in +$fileinfo['warning']. Both keys may contain more than one warning or +error. If something is returned in ['error'] then the file was not +correctly parsed and returned data may or may not be correct and/or +complete. If something is returned in ['warning'] (and not ['error']) +then the data that is returned is OK - usually getID3() is reporting +errors in the file that have been worked around due to known bugs in +other programs. Some warnings may indicate that the data that is +returned is OK but that some data could not be extracted due to +errors in the file. + +getID3() 2.x: +See above except errors are thrown (so you will only get one error). + + + +Disclaimer +=========================================================================== + +getID3() has been tested on many systems, on many types of files, +under many operating systems, and is generally believe to be stable +and safe. That being said, there is still the chance there is an +undiscovered and/or unfixed bug that may potentially corrupt your +file, especially within the writing functions. By using getID3() you +agree that it's not my fault if any of your files are corrupted. +In fact, I'm not liable for anything :) + + + +License +=========================================================================== + +GNU General Public License - see license.txt + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to: +Free Software Foundation, Inc. +59 Temple Place - Suite 330 +Boston, MA 02111-1307, USA. + +FAQ: +Q: Can I use getID3() in my program? Do I need a commercial license? +A: You're generally free to use getID3 however you see fit. The only + case in which you would require a commercial license is if you're + selling your closed-source program that integrates getID3. If you + sell your program including a copy of getID3, that's fine as long + as you include a copy of the sourcecode when you sell it. Or you + can distribute your code without getID3 and say "download it from + getid3.sourceforge.net" + + + +Why is it called "getID3()" if it does so much more than just that? +=========================================================================== + +v0.1 did in fact just do that. I don't have a copy of code that old, but I +could essentially write it today with a one-line function: + function getID3($filename) { return unpack('a3TAG/a30title/a30artist/a30album/a4year/a28comment/c1track/c1genreid', substr(file_get_contents($filename), -128)); } + + +Future Plans +=========================================================================== +http://www.getid3.org/phpBB3/viewforum.php?f=7 + +* Better support for MP4 container format +* Scan for appended ID3v2 tag at end of file per ID3v2.4 specs (Section 5.0) +* Support for JPEG-2000 (http://www.morgan-multimedia.com/jpeg2000_overview.htm) +* Support for MOD (mod/stm/s3m/it/xm/mtm/ult/669) +* Support for ACE (thanks Vince) +* Support for Ogg other than Vorbis, Speex and OggFlac (ie. Ogg+Xvid) +* Ability to create Xing/LAME VBR header for VBR MP3s that are missing VBR header +* Ability to "clean" ID3v2 padding (replace invalid padding with valid padding) +* Warn if MP3s change version mid-stream (in full-scan mode) +* check for corrupt/broken mid-file MP3 streams in histogram scan +* Support for lossless-compression formats + (http://www.firstpr.com.au/audiocomp/lossless/#Links) + (http://compression.ca/act-sound.html) + (http://web.inter.nl.net/users/hvdh/lossless/lossless.htm) +* Support for RIFF-INFO chunks + * http://lotto.st-andrews.ac.uk/~njh/tag_interchange.html + (thanks Nick Humfrey ) + * http://abcavi.narod.ru/sof/abcavi/infotags.htm + (thanks Kibi) +* Better support for Bink video +* http://www.hr/josip/DSP/AudioFile2.html +* http://www.pcisys.net/~melanson/codecs/ +* Detect mp3PRO +* Support for PSD +* Support for JPC +* Support for JP2 +* Support for JPX +* Support for JB2 +* Support for IFF +* Support for ICO +* Support for ANI +* Support for EXE (comments, author, etc) (thanks p*quaedackersØplanet*nl) +* Support for DVD-IFO (region, subtitles, aspect ratio, etc) + (thanks p*quaedackersØplanet*nl) +* More complete support for SWF - parsing encapsulated MP3 and/or JPEG content + (thanks n8n8Øyahoo*com) +* Support for a2b +* Optional scan-through-frames for AVI verification + (thanks rockcohenØmassive-interactive*nl) +* Support for TTF (thanks infoØbutterflyx*com) +* Support for DSS (http://www.getid3.org/phpBB3/viewtopic.php?t=171) +* Support for SMAF (http://smaf-yamaha.com/what/demo.html) + http://www.getid3.org/phpBB3/viewtopic.php?t=182 +* Support for AMR (http://www.getid3.org/phpBB3/viewtopic.php?t=195) +* Support for 3gpp (http://www.getid3.org/phpBB3/viewtopic.php?t=195) +* Support for ID4 (http://www.wackysoft.cjb.net grizlyY2KØhotmail*com) +* Parse XML data returned in Ogg comments +* Parse XML data from Quicktime SMIL metafiles (klausrathØmac*com) +* ID3v2 genre string creator function +* More complete parsing of JPG +* Support for all old-style ASF packets +* ASF/WMA/WMV tag writing +* Parse declared T??? ID3v2 text information frames, where appropriate + (thanks Christian Fritz for the idea) +* Recognize encoder: + http://www.guerillasoft.com/EncSpot2/index.html + http://ff123.net/identify.html + http://www.hydrogenaudio.org/?act=ST&f=16&t=9414 + http://www.hydrogenaudio.org/?showtopic=11785 +* Support for other OS/2 bitmap structures: Bitmap Array('BA'), + Color Icon('CI'), Color Pointer('CP'), Icon('IC'), Pointer ('PT') + http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm +* Support for WavPack RAW mode +* ASF/WMA/WMV data packet parsing +* ID3v2FrameFlagsLookupTagAlter() +* ID3v2FrameFlagsLookupFileAlter() +* obey ID3v2 tag alter/preserve/discard rules +* http://www.geocities.com/SiliconValley/Sector/9654/Softdoc/Illyrium/Aolyr.htm +* proper checking for LINK/LNK frame validity in ID3v2 writing +* proper checking for ASPI-TLEN frame validity in ID3v2 writing +* proper checking for COMR frame validity in ID3v2 writing +* http://www.geocities.co.jp/SiliconValley-Oakland/3664/index.html +* decode GEOB ID3v2 structure as encoded by RealJukebox, + decode NCON ID3v2 structure as encoded by MusicMatch + (probably won't happen - the formats are proprietary) + + + +Known Bugs/Issues in getID3() that may be fixed eventually +=========================================================================== +http://www.getid3.org/phpBB3/viewtopic.php?t=25 + +* Cannot determine bitrate for MPEG video with VBR video data + (need documentation) +* Interlace/progressive cannot be determined for MPEG video + (need documentation) +* MIDI playtime is sometimes inaccurate +* AAC-RAW mode files cannot be identified +* WavPack-RAW mode files cannot be identified +* mp4 files report lots of "Unknown QuickTime atom type" + (need documentation) +* Encrypted ASF/WMA/WMV files warn about "unhandled GUID + ASF_Content_Encryption_Object" +* Bitrate split between audio and video cannot be calculated for + NSV, only the total bitrate. (need documentation) +* All Ogg formats (Vorbis, OggFLAC, Speex) are affected by the + problem of large VorbisComments spanning multiple Ogg pages, but + but only OggVorbis files can be processed with vorbiscomment. +* The version of "head" supplied with Mac OS 10.2.8 (maybe other + versions too) does only understands a single option (-n) and + therefore fails. getID3 ignores this and returns wrong md5_data. + + + +Known Bugs/Issues in getID3() that cannot be fixed +-------------------------------------------------- +http://www.getid3.org/phpBB3/viewtopic.php?t=25 + +* 32-bit PHP installations only: + Files larger than 2GB cannot always be parsed fully by getID3() + due to limitations in the 32-bit PHP filesystem functions. + NOTE: Since v1.7.8b3 there is partial support for larger-than- + 2GB files, most of which will parse OK, as long as no critical + data is located beyond the 2GB offset. + Known will-work: + * all file formats on 64-bit PHP + * ZIP (format doesn't support files >2GB) + * FLAC (current encoders don't support files >2GB) + Known will-not-work: + * ID3v1 tags (always located at end-of-file) + * Lyrics3 tags (always located at end-of-file) + * APE tags (always located at end-of-file) + Maybe-will-work: + * Quicktime (will work if needed metadata is before 2GB offset, + that is if the file has been hinted/optimized for streaming) + * RIFF.WAV (should work fine, but gives warnings about not being + able to parse all chunks) + * RIFF.AVI (playtime will probably be wrong, is only based on + "movi" chunk that fits in the first 2GB, should issue error + to show that playtime is incorrect. Other data should be mostly + correct, assuming that data is constant throughout the file) +* PHP <= v5 on Windows cannot read UTF-8 filenames + + +Known Bugs/Issues in other programs +----------------------------------- +http://www.getid3.org/phpBB3/viewtopic.php?t=25 + +* PZ TagEditor v4.53.408 has been known to insert ID3v2.3 frames + into an existing ID3v2.2 tag which, of course, breaks things +* Windows Media Player (up to v11) and iTunes (up to v10+) do + not correctly handle ID3v2.3 tags with UTF-16BE+BOM + encoding (they assume the data is UTF-16LE+BOM and either + crash (WMP) or output Asian character set (iTunes) +* Winamp (up to v2.80 at least) does not support ID3v2.4 tags, + only ID3v2.3 + see: http://forums.winamp.com/showthread.php?postid=387524 +* Some versions of Helium2 (www.helium2.com) do not write + ID3v2.4-compliant Frame Sizes, even though the tag is marked + as ID3v2.4) (detected by getID3()) +* MP3ext V3.3.17 places a non-compliant padding string at the end + of the ID3v2 header. This is supposedly fixed in v3.4b21 but + only if you manually add a registry key. This fix is not yet + confirmed. (detected by getID3()) +* CDex v1.40 (fixed by v1.50b7) writes non-compliant Ogg comment + strings, supposed to be in the format "NAME=value" but actually + written just "value" (detected by getID3()) +* Oggenc 0.9-rc3 flags the encoded file as ABR whether it's + actually ABR or VBR. +* iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably + other versions are too) writes ID3v2.3 comment tags using a + frame name 'COM ' which is not valid for ID3v2.3+ (it's an + ID3v2.2-style frame name) (detected by getID3()) +* MP2enc does not encode mono CBR MP2 files properly (half speed + sound and double playtime) +* MP2enc does not encode mono VBR MP2 files properly (actually + encoded as stereo) +* tooLAME does not encode mono VBR MP2 files properly (actually + encoded as stereo) +* AACenc encodes files in VBR mode (actually ABR) even if CBR is + specified +* AAC/ADIF - bitrate_mode = cbr for vbr files +* LAME 3.90-3.92 prepends one frame of null data (space for the + LAME/VBR header, but it never gets written) when encoding in CBR + mode with the DLL +* Ahead Nero encodes TwinVQF with a DSIZ value (which is supposed + to be the filesize in bytes) of "0" for TwinVQF v1.0 and "1" for + TwinVQF v2.0 (detected by getID3()) +* Ahead Nero encodes TwinVQF files 1 second shorter than they + should be +* AAC-ADTS files are always actually encoded VBR, even if CBR mode + is specified (the CBR-mode switches on the encoder enable ABR + mode, not CBR as such, but it's not possible to tell the + difference between such ABR files and true VBR) +* STREAMINFO.audio_signature in OggFLAC is always null. "The reason + it's like that is because there is no seeking support in + libOggFLAC yet, so it has no way to go back and write the + computed sum after encoding. Seeking support in Ogg FLAC is the + #1 item for the next release." - Josh Coalson (FLAC developer) + NOTE: getID3() will calculate md5_data in a method similar to + other file formats, but that value cannot be compared to the + md5_data value from FLAC data in a FLAC file format. +* STREAMINFO.audio_signature is not calculated in FLAC v0.3.0 & + v0.4.0 - getID3() will calculate md5_data in a method similar to + other file formats, but that value cannot be compared to the + md5_data value from FLAC v0.5.0+ +* RioPort (various versions including 2.0 and 3.11) tags ID3v2 with + a WCOM frame that has no data portion +* Earlier versions of Coolplayer adds illegal ID3 tags to Ogg Vorbis + files, thus making them corrupt. +* Meracl ID3 Tag Writer v1.3.4 (and older) incorrectly truncates the + last byte of data from an MP3 file when appending a new ID3v1 tag. + (detected by getID3()) +* Lossless-Audio files encoded with and without the -noseek switch + do actually differ internally and therefore cannot match md5_data +* iTunes has been known to append a new ID3v1 tag on the end of an + existing ID3v1 tag when ID3v2 tag is also present + (detected by getID3()) +* MediaMonkey may write a blank RGAD ID3v2 frame but put actual + replay gain adjustments in a series of user-defined TXXX frames + (detected and handled by getID3() since v1.9.2) + + + + +Reference material: +=========================================================================== + +[www.id3.org material now mirrored at http://id3lib.sourceforge.net/id3/] +* http://www.id3.org/id3v2.4.0-structure.txt +* http://www.id3.org/id3v2.4.0-frames.txt +* http://www.id3.org/id3v2.4.0-changes.txt +* http://www.id3.org/id3v2.3.0.txt +* http://www.id3.org/id3v2-00.txt +* http://www.id3.org/mp3frame.html +* http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html +* http://www.dv.co.yu/mpgscript/mpeghdr.htm +* http://www.mp3-tech.org/programmer/frame_header.html +* http://users.belgacom.net/gc247244/extra/tag.html +* http://gabriel.mp3-tech.org/mp3infotag.html +* http://www.id3.org/iso4217.html +* http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-1.TXT +* http://www.xiph.org/ogg/vorbis/doc/framing.html +* http://www.xiph.org/ogg/vorbis/doc/v-comment.html +* http://leknor.com/code/php/class.ogg.php.txt +* http://www.id3.org/iso639-2.html +* http://www.id3.org/lyrics3.html +* http://www.id3.org/lyrics3200.html +* http://www.psc.edu/general/software/packages/ieee/ieee.html +* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html +* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html +* http://www.jmcgowan.com/avi.html +* http://www.wotsit.org/ +* http://www.herdsoft.com/ti/davincie/davp3xo2.htm +* http://www.mathdogs.com/vorbis-illuminated/bitstream-appendix.html +* "Standard MIDI File Format" by Dustin Caldwell (from www.wotsit.org) +* http://midistudio.com/Help/GMSpecs_Patches.htm +* http://www.xiph.org/archives/vorbis/200109/0459.html +* http://www.replaygain.org/ +* http://www.lossless-audio.com/ +* http://download.microsoft.com/download/winmediatech40/Doc/1.0/WIN98MeXP/EN-US/ASF_Specification_v.1.0.exe +* http://mediaxw.sourceforge.net/files/doc/Active%20Streaming%20Format%20(ASF)%201.0%20Specification.pdf +* http://www.uni-jena.de/~pfk/mpp/sv8/ (archived at http://www.hydrogenaudio.org/musepack/klemm/www.personal.uni-jena.de/~pfk/mpp/sv8/) +* http://jfaul.de/atl/ +* http://www.uni-jena.de/~pfk/mpp/ (archived at http://www.hydrogenaudio.org/musepack/klemm/www.personal.uni-jena.de/~pfk/mpp/) +* http://www.libpng.org/pub/png/spec/png-1.2-pdg.html +* http://www.real.com/devzone/library/creating/rmsdk/doc/rmff.htm +* http://www.fastgraph.com/help/bmp_os2_header_format.html +* http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm +* http://flac.sourceforge.net/format.html +* http://www.research.att.com/projects/mpegaudio/mpeg2.html +* http://www.audiocoding.com/wiki/index.php?page=AAC +* http://libmpeg.org/mpeg4/doc/w2203tfs.pdf +* http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt +* http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/frameset.htm +* http://www.nullsoft.com/nsv/ +* http://www.wotsit.org/download.asp?f=iso9660 +* http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html +* http://www.cdroller.com/htm/readdata.html +* http://www.speex.org/manual/node10.html +* http://www.harmony-central.com/Computer/Programming/aiff-file-format.doc +* http://www.faqs.org/rfcs/rfc2361.html +* http://ghido.shelter.ro/ +* http://www.ebu.ch/tech_t3285.pdf +* http://www.sr.se/utveckling/tu/bwf +* http://ftp.aessc.org/pub/aes46-2002.pdf +* http://cartchunk.org:8080/ +* http://www.broadcastpapers.com/radio/cartchunk01.htm +* http://www.hr/josip/DSP/AudioFile2.html +* http://home.attbi.com/~chris.bagwell/AudioFormats-11.html +* http://www.pure-mac.com/extkey.html +* http://cesnet.dl.sourceforge.net/sourceforge/bonkenc/bonk-binary-format-0.9.txt +* http://www.headbands.com/gspot/ +* http://www.openswf.org/spec/SWFfileformat.html +* http://j-faul.virtualave.net/ +* http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html +* http://cui.unige.ch/OSG/info/AudioFormats/ap11.html +* http://sswf.sourceforge.net/SWFalexref.html +* http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt +* http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm +* http://developer.apple.com/quicktime/icefloe/dispatch012.html +* http://www.csdn.net/Dev/Format/graphics/PCD.htm +* http://tta.iszf.irk.ru/ +* http://www.atsc.org/standards/a_52a.pdf +* http://www.alanwood.net/unicode/ +* http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html +* http://www.its.msstate.edu/net/real/reports/config/tags.stats +* http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt +* http://brennan.young.net/Comp/LiveStage/things.html +* http://www.multiweb.cz/twoinches/MP3inside.htm +* http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended +* http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ +* http://www.unicode.org/unicode/faq/utf_bom.html +* http://tta.corecodec.org/?menu=format +* http://www.scvi.net/nsvformat.htm +* http://pda.etsi.org/pda/queryform.asp +* http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm +* http://trac.musepack.net/trac/wiki/SV8Specification +* http://wyday.com/cuesharp/specification.php +* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html diff --git a/app/library/getid3/structure.txt b/app/library/getid3/structure.txt new file mode 100755 index 00000000..fdb2336b --- /dev/null +++ b/app/library/getid3/structure.txt @@ -0,0 +1,2252 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +// also https://github.com/JamesHeinrich/getID3 // +///////////////////////////////////////////////////////////////// +// // +// changelog.txt - part of getID3() // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +What does the returned data structure look like? +================================================ + +Hint: If you take a look at the nicely-formatted output of +/demos/demo.browse.php you can generally see where the data you want +is returned. + +Note that what is described below is only a rough guide to what data +is actually returned by getID3(), since the actual data returned +depends entirely on what data is in your file, what type of file it +is, what kind of data is in the tags, etc. In addition, some formats +(Quicktime for example) use a freeform recursive structure that is +impossible to document completely. + +In the vast majority of cases, all the data you'll need is located +in the root of the array or the special arrays described below in +Section 1 (['audio'], ['video'], ['tags_html'], ['replay_gain']). + +It is suggested that for most applications you should use tag data +from the root ['tags_html'] array, as this is the only location +where data is stored in a consistant format: HTML-compatible +character entities (ie Ӓ) for characters outside the 0x20-0x7F +range (printable ISO-8859-1 characters). This data can be used as-is +for output in HTML, and can be converted to whatever character set +you wish to use if the output is not HTML. + +If you want to merge all available tags (for example, ID3v2 + ID3v1) +into one array, you can call +getid3_lib::CopyTagsToComments($ThisFileInfo) +and you'll then have ['comments'] and ['comments_html'] which are +identical to ['tags'] and ['tags_html'] except the array is one +dimension shorter (no tag type array keys). For example, artist is: +['tags_html']['id3v1']['artist'][0] or ['comments_html']['artist'][0] + + +Some commonly-used information is found in these locations: + +File type: ['fileformat'] // ex 'mp3' +Song length: ['playtime_string'] // ex '3:45' (minutes:seconds) + ['playtime_seconds'] // ex 225.13 (seconds) +Overall bitrate: ['bitrate'] // ex 113485.71 (bits-per-second - divide by 1000 for kbps) +Audio frequency: ['audio']['sample_rate'] // ex 44100 (Hertz) +Artist name: ['comments_html']['artist'][0] // ex 'Elvis' (if CopyTagsToComments() is used - see above) + // more than one artist may be present, you may want to use implode: + // implode(' & ', ['comments_html']['artist']) + + +///////////////////////////////////////////////////////////////// + +array() { + // SECTION 1: Values that are present for most or all file types + + ['getID3version']=>string() // version of getID3() that scanned this file (ex: '1.6.2') + ['error']=>array() // if present, contains one or more fatal error messages + ['warning']=>array() // if present, contains one or more non-fatal warning messages + ['exist']=>boolean() // does this file actually exist? + ['fileformat']=>string() // one of the standard filetype abbreviations ('mp3', 'riff', 'quicktime', etc) + ['filename']=>string() // filename only, no path + ['filenamepath']=>string() // full filename with path + ['filepath']=>string() // path to file, not including filename + ['filesize']=>integer() // filesize in bytes + ['md5_file']=>string() // md5 hash of entire file + ['md5_data']=>string() // md5 hash of portion of file excluding prepended and appeneded metainformation tags (ID3, APE, etc) - may be identical to ['md5_file'] + ['md5_data_source']=>string() // md5 hash of original source file before compression (currently used by FLAC, OptimFROG, WavPack v4+) + ['sha1_file']=>string() // sha1 hash of entire file + ['sha1_data']=>string() // sha1 hash of portion of file excluding prepended and appeneded metainformation tags (ID3, APE, etc) - may be identical to ['md5_file'] + ['avdataoffset']=>integer() // offset in bytes where audio/video data starts and prepended tags end + ['avdataend']=>integer() // offset in bytes where audio/video data ends and appended tags start + ['bitrate']=>double() // average bitrate for entire file (all audio/video streams), in bits per second + ['mime_type']=>string() // if present, MIME type of scanned file + ['playtime_seconds']=>double() // playing time of file, in seconds + ['playtime_string']=>string() // playing time of file, formatted as : + ['tags']=>array() // array of all metainformation tags present in file ('id3v1', 'id3v2', 'ape', 'riff', 'asf', etc) + ['audio']=>array() { + ['bitrate']=>double() // average bitrate for audio portion of file (all audio streams), in bits per second + ['bitrate_mode']=>string() // 'cbr' (Constant Bit Rate) or 'vbr' (Variable Bit Rate) + ['bits_per_sample']=>integer() // + ['channelmode']=>string() // 'mono' or 'stereo' + ['channels']=>integer() // number of audio channels + ['codec']=>string() // name of audio compression codec + ['compression_ratio']=>double() // ratio of compressed byte size of audio to uncompressed size + ['dataformat']=>string() // one of the standard filetype abbreviations ('mp3', 'wma', etc) + ['encoder']=>string() // name and version of encoder used to create file, if known + ['lossless']=>boolean() // true = lossless compression; false = lossy compression + ['sample_rate']=>integer() + } + ['video']=>array() { + ['bitrate']=>integer() // average bitrate for video portion of file (all video streams), in bits per second + ['bitrate_mode']=>string() // 'cbr' (Constant Bit Rate) or 'vbr' (Variable Bit Rate) + ['bits_per_sample']=>integer() // + ['codec']=>string() // name of video compression codec + ['compression_ratio']=>double() // ratio of compressed byte size of video to uncompressed size + ['dataformat']=>string() // one of the standard filetype abbreviations ('avi', 'mpeg', etc) + ['encoder']=>string() // name and version of encoder used to create file, if known + ['frame_rate']=>double() // frames per second + ['lossless']=>boolean() // true = lossless compression; false = lossy compression + ['resolution_x']=>integer() // horizontal dimension of video/image in pixels + ['resolution_y']=>integer() // vertical dimension of video/image in pixels + ['pixel_aspect_ratio']=>double() // pixel display aspect ratio + } + ['tags']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + []=>array() // can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } + ['tags_html']=>array() { // identical to ['tags'], but with all entries converted to HTML entities as appropriate from various source encodings + []=>array() // + } + ['replay_gain']=>array() { // replay gain information combined from any source that contains this information (LAME, ID3v2, Vorbis, APE, etc) + ['audiophile']=>array() { + ['adjustment']=>double() + ['originator']=>string() + ['peak']=>double() + } + ['radio']=>array() { + ['adjustment']=>double() + ['originator']=>string() + ['peak']=>double() + } + } + + + // SECTION 2: Values that are present for specific file types only + + ['aac']=>array() { // AAC - Advanced Audio Coding / MPEG-4 + ['bitrate_distribution']=>array() // + ['header']=>array() { // + ['channel_configuration']=>integer() // + ['crc_present']=>boolean() // + ['home']=>boolean() // + ['layer']=>integer() // + ['mpeg_version']=>integer() // + ['original']=>boolean() // + ['private']=>boolean() // + ['profile_id']=>integer() // + ['profile_text']=>string() // + ['sample_frequency']=>integer() // + ['sample_frequency_index']=>integer() // + ['synch']=>integer() // + } // + ['header_type']=>string() // + } // + // + ['ape']=>array() // + { // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + []=>array() // can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['footer']=>array() // + { // + ['flags']=>array() // + ['raw']=>array() // + ['tag_version']=>integer() // + } // + ['header']=>array() // + { // + ['flags']=>array() // + ['raw']=>array() // + ['tag_version']=>integer() // + } // + ['items']=>array() { // array of array of strings containing metainformation + []=>array() { // can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + ['data']=>array() { // array of one or more Unicode values + ['data_ascii']=>array() { // array of values converted approximately from Unicode to ASCII + ['flags']=>array() // + } // + } // + ['tag_offset_end']=>integer() // + ['tag_offset_start']=>integer() // + } // + + + ['asf']=>array() { // ASF - Advanced Streaming Format (ASF, Windows Media Audio (WMA), Windows Media Video (WMV)) + ['audio_media']=>array() { // + []=>array() { // + ['bitrate']=>integer() // + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['codec']=>string() // + ['codec_data']=>string() // + ['codec_data_size']=>integer() // + ['raw']=>array() { // + ['nAvgBytesPerSec']=>integer() // + ['wBitsPerSample']=>integer() // + ['nBlockAlign']=>integer() // + ['nChannels']=>integer() // + ['nSamplesPerSec']=>integer() // + ['wFormatTag']=>integer() // + } // + ['sample_rate']=>integer() // + } // + } // + ['codec_list']=>array() { // + ['codec_entries']=>array() { // + []=>array() { // + ['description']=>string() // + ['description_ascii']=>string() // + ['information']=>string() // + ['name']=>string() // + ['name_ascii']=>string() // + ['type']=>string() // + ['type_raw']=>integer() // + } // + } // + ['codec_entries_count']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['reserved']=>string() // + ['reserved_guid']=>string() // + } // + ['comments']=>array() { // array of comment values, derived from ['content_description'] + ['album']=>string() // + ['artist']=>string() // + ['comment']=>string() // + ['copyright']=>string() // + ['genre']=>string() // + ['title']=>string() // + ['track']=>string() // + ['year']=>string() // + } // + ['content_description']=>array() { // raw values - should use values from ['comments'] instead + ['author']=>string() // + ['author_ascii']=>string() // + ['author_length']=>integer() // + ['copyright']=>string() // + ['copyright_ascii']=>string() // + ['copyright_length']=>integer() // + ['description']=>string() // + ['description_ascii']=>string() // + ['description_length']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['rating']=>string() // + ['rating_ascii']=>string() // + ['rating_length']=>integer() // + ['title']=>string() // + ['title_ascii']=>string() // + ['title_length']=>integer() // + } // + ['data_object']=>array() { // + ['fileid']=>string() // + ['fileid_guid']=>string() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['reserved']=>integer() // + ['total_data_packets']=>integer() // + } // + ['extended_content_description']=>array() { // + ['content_descriptors']=>array() { // + []=>array() { // + ['name']=>string() // + ['name_ascii']=>string() // + ['name_length']=>integer() // + ['value']=>string() // + ['value_ascii']=>string() // + ['value_length']=>integer() // + ['value_type']=>integer() // + } // + } // + ['content_descriptors_count']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + } // + ['file_properties_object']=>array() { // + ['creation_date']=>double() // + ['creation_date_unix']=>double() // + ['data_packets']=>integer() // + ['fileid']=>string() // + ['fileid_guid']=>string() // + ['filesize']=>integer() // + ['flags']=>array() { // + ['broadcast']=>boolean() // + ['seekable']=>boolean() // + } // + ['flags_raw']=>integer() // + ['max_bitrate']=>integer() // + ['max_packet_size']=>integer() // + ['min_packet_size']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['play_duration']=>double() // + ['preroll']=>integer() // + ['send_duration']=>double() // + } // + ['header_extension_object']=>array() { // + ['extension_data']=>integer() // + ['extension_data_size']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['reserved_1']=>string() // + ['reserved_1_guid']=>string() // + ['reserved_2']=>integer() // + } // + ['header_object']=>array() { // + ['headerobjects']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['reserved1']=>integer() // + ['reserved2']=>integer() // + } // + ['marker_object']=>array() { // + ['markers_count']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['reserved']=>string() // + ['reserved_2']=>integer() // + ['reserved_guid']=>string() // + } // + ['stream_bitrate_properties']=>array() { // + ['bitrate_records']=>array() { // + []=>array() { // + ['bitrate']=>integer() // + ['flags_raw']=>integer() // + ['flags']=>array() { // + ['stream_number']=>integer() // + } // + } // + } // + ['bitrate_records_count']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + } // + ['stream_properties_object']=>array() { // + []=>array() { // + ['error_correct_data']=>string() // + ['error_correct_guid']=>string() // + ['error_correct_type']=>string() // + ['error_data_length']=>integer() // + ['flags_raw']=>integer() // + ['flags']=>array() { // + ['encrypted']=>boolean() // + } // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['stream_type']=>string() // + ['stream_type_guid']=>string() // + ['time_offset']=>integer() // + ['type_data_length']=>integer() // + ['type_specific_data']=>string() // + } // + } // + ['video_media']=>array() { // + []=>array() { // + ['flags']=>integer() // + ['format_data']=>array() { // + ['bits_per_pixel']=>integer() // + ['codec']=>string() // + ['codec_data']=>boolean() // + ['codec_fourcc']=>string() // + ['colors_important']=>integer() // + ['colors_used']=>integer() // + ['format_data_size']=>integer() // + ['horizontal_pels']=>integer() // + ['image_height']=>integer() // + ['image_size']=>integer() // + ['image_width']=>integer() // + ['reserved']=>integer() // + ['vertical_pels']=>integer() // + } // + ['format_data_size']=>integer() // + ['image_height']=>integer() // + ['image_width']=>integer() // + } // + } // + } // + + + ['au']=>array() { // AU - Next/Sun AUdio format + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['comment']=>string() // + ['data_format']=>string() // + ['data_format_id']=>integer() // + ['data_size']=>integer() // + ['header_length']=>integer() // + ['sample_rate']=>integer() // + ['used_bits_per_sample']=>integer() // + } // + + + ['bmp']=>array() { // BMP - OS/2 or Windows BitMaP + ['header']=>array() { // + ['compression']=>string() // + ['raw']=>array() { // + ['bits_per_pixel']=>integer() // + ['bmp_data_size']=>integer() // + ['colors_important']=>integer() // + ['colors_used']=>integer() // + ['compression']=>integer() // + ['data_offset']=>integer() // + ['filesize']=>integer() // + ['header_size']=>integer() // + ['height']=>integer() // + ['identifier']=>string() // + ['planes']=>integer() // + ['resolution_h']=>integer() // + ['resolution_v']=>integer() // + ['width']=>integer() // + } // + } // + ['type_os']=>string() // + ['type_version']=>integer() // + } // + + + ['bonk']=>array() { // BONK - lossy/lossless audio compression (www.bonkenc.org) + ['BONK']=>array() { // + ['channels']=>integer() // + ['downsampling_ratio']=>integer() // + ['joint_stereo']=>boolean() // + ['lossless']=>boolean() // + ['number_samples']=>integer() // + ['number_taps']=>integer() // + ['offset']=>integer() // + ['sample_rate']=>integer() // + ['samples_per_packet']=>integer() // + ['size']=>integer() // + ['version']=>integer() // + } // + ['INFO']=>array() { // + ['size']=>integer() // + ['offset']=>integer() // + ['version']=>integer() // + []=>array() { // + ['nextbit']=>integer() // + ['offset']=>integer() // + } // + } // + ['dataend']=>integer() // + ['dataoffset']=>integer() // + } // + + + ['flac']=>array() { // FLAC - Free Lossless Audio Compressor + ['SEEKTABLE']=>array() { // + []=>array() { // + ['offset']=>integer() // + ['samples']=>integer() // + } // + ['placeholders']=>integer() // + ['raw']=>array() { // + ['block_data']=>string() // + ['block_length']=>integer() // + ['block_type']=>integer() // + ['block_type_text']=>string() // + ['last_meta_block']=>boolean() // + ['offset']=>integer() // + } // + } // + ['STREAMINFO']=>array() { // + ['audio_signature']=>string() // + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['max_block_size']=>integer() // + ['max_frame_size']=>integer() // + ['min_block_size']=>integer() // + ['min_frame_size']=>integer() // + ['raw']=>array() { // + ['block_data']=>string() // + ['block_length']=>integer() // + ['block_type']=>integer() // + ['block_type_text']=>string() // + ['last_meta_block']=>boolean() // + ['offset']=>integer() // + } // + ['sample_rate']=>integer() // + ['samples_stream']=>integer() // + } // + ['VORBIS_COMMENT']=>array() { // + ['raw']=>array() { // + ['block_data']=>string() // + ['block_length']=>integer() // + ['block_type']=>integer() // + ['block_type_text']=>string() // + ['last_meta_block']=>boolean() // + ['offset']=>integer() // + } // + } // + ['compressed_audio_bytes']=>integer() // + ['compression_ratio']=>double() // + ['uncompressed_audio_bytes']=>integer() // + } // + + + ['gif']=>array() { // GIF - Graphics Interchange Format + ['global_color_table']=>array() { // + []=>integer() // + } // + ['header']=>array() { // + ['bits_per_pixel']=>integer() // + ['flags']=>array() { // + ['global_color_sorted']=>boolean() // + ['global_color_table']=>boolean() // + } // + ['global_color_size']=>integer() // + ['raw']=>array() { // + ['aspect_ratio']=>integer() // + ['bg_color_index']=>integer() // + ['flags']=>integer() // + ['height']=>integer() // + ['identifier']=>string() // + ['version']=>string() // + ['width']=>integer() // + } // + } // + ['version']=>string() // + } // + + + ['id3v1']=>array() { // ID3v1 + ['album']=>string() // + ['artist']=>string() // + ['comment']=>string() // + ['genre']=>string() // + ['genreid']=>integer() // + ['title']=>string() // + ['track']=>integer() // + ['year']=>string() // + ['padding_valid']=>boolean() // + ['comments']=>array() // + ['tag_offset_start']=>integer() // + ['tag_offset_end']=>integer() // + } // + + + ['id3v2']=>array() { // ID3v2 - www.id3.org + []=>array() { // can be any of the 4-character (3-character in ID3v2.2) frame names allowed in the ID3v2 spec. Exact contents of returned array data varies with frame type. + []=>array() { // some frames types allow multiple values ('COMM' for example), others do not and do not have this array level + ['asciidata']=>boolean() // + ['asciidescription']=>string() // + ['data']=>boolean() // + ['datalength']=>integer() // + ['dataoffset']=>integer() // + ['description']=>string() // + ['encoding']=>string() // + ['encodingid']=>integer() // + ['flags']=>array() { // + ['Encryption']=>boolean() // + ['FileAlterPreservation']=>boolean() // + ['GroupingIdentity']=>boolean() // + ['ReadOnly']=>boolean() // + ['TagAlterPreservation']=>boolean() // + ['compression']=>boolean() // + } // + ['framenamelong']=>string() // + ['language']=>string() // + ['languagename']=>string() // + } // + } // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + []=>array() // can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['flags']=>array() { // + ['experim']=>string() // + ['exthead']=>string() // + ['unsynch']=>string() // + } // + ['header']=>boolean() // + ['headerlength']=>integer() // + ['majorversion']=>integer() // + ['minorversion']=>integer() // + ['padding']=>array() { // + ['length']=>integer() // + ['start']=>integer() // + ['valid']=>boolean() // + } // + ['tag_offset_end']=>integer() // + ['tag_offset_start']=>integer() // + } // + + + ['iso']=>array() { // ISO-9660 - CD-ROM Image + ['directories']=>array() { // + []=>array() { // + []=>array() { // + ['file_flags']=>array() { // + ['associated']=>boolean() // + ['directory']=>boolean() // + ['extended']=>boolean() // + ['hidden']=>boolean() // + ['multiple']=>boolean() // + ['permissions']=>boolean() // + } // + ['file_identifier_ascii']=>string() // + ['filename']=>string() // + ['filesize']=>integer() // + ['offset_bytes']=>integer() // + ['raw']=>array() { // + ['extended_attribute_length']=>integer() // + ['file_flags']=>integer() // + ['file_identifier']=>string() // + ['file_identifier_length']=>integer() // + ['file_unit_size']=>integer() // + ['filesize']=>integer() // + ['interleave_gap_size']=>integer() // + ['length']=>integer() // + ['offset_logical']=>integer() // + ['recording_date_time']=>string() // + ['volume_sequence_number']=>integer() // + } // + ['recording_timestamp']=>integer() // + } // + } // + } // + ['files']=>array() { // multidimensional tree-structure array listing of all files and directories in image + []=>array() // entries of type array are directories (key is directory name), may contain files and/or other subdirectories + []=>integer() // entries of type integer are files (key is file name, value is file size in bytes) + } // + ['path_table']=>array() { // + ['directories']=>array() { // + []=>array() { // + ['extended_length']=>integer() // + ['full_path']=>string() // + ['length']=>integer() // + ['location_bytes']=>integer() // + ['location_logical']=>integer() // + ['name']=>string() // + ['name_ascii']=>string() // + ['parent_directory']=>integer() // + } // + } // + ['offset']=>integer() // + ['raw']=>string() // + } // + ['primary_volume_descriptor']=>array() { // + ['abstract_file_identifier']=>string() // + ['application_identifier']=>string() // + ['bibliographic_file_identifier']=>string() // + ['copyright_file_identifier']=>string() // + ['data_preparer_identifier']=>string() // + ['offset']=>integer() // + ['publisher_identifier']=>string() // + ['raw']=>array() { // + ['abstract_file_identifier']=>string() // + ['application_data']=>string() // + ['application_identifier']=>string() // + ['bibliographic_file_identifier']=>string() // + ['copyright_file_identifier']=>string() // + ['data_preparer_identifier']=>string() // + ['file_structure_version']=>integer() // + ['logical_block_size']=>integer() // + ['path_table_l_location']=>integer() // + ['path_table_l_opt_location']=>integer() // + ['path_table_m_location']=>integer() // + ['path_table_m_opt_location']=>integer() // + ['path_table_size']=>integer() // + ['publisher_identifier']=>string() // + ['root_directory_record']=>string() // + ['standard_identifier']=>string() // + ['system_identifier']=>string() // + ['unused_1']=>string() // + ['unused_2']=>string() // + ['unused_3']=>string() // + ['unused_4']=>integer() // + ['volume_creation_date_time']=>string() // + ['volume_descriptor_type']=>integer() // + ['volume_descriptor_version']=>integer() // + ['volume_effective_date_time']=>string() // + ['volume_expiration_date_time']=>string() // + ['volume_identifier']=>string() // + ['volume_modification_date_time']=>string() // + ['volume_sequence_number']=>integer() // + ['volume_set_identifier']=>string() // + ['volume_set_size']=>integer() // + ['volume_space_size']=>integer() // + } // + ['system_identifier']=>string() // + ['volume_creation_date_time']=>integer() // + ['volume_effective_date_time']=>boolean() // + ['volume_expiration_date_time']=>boolean() // + ['volume_identifier']=>string() // + ['volume_modification_date_time']=>integer() // + ['volume_set_identifier']=>string() // + } // + ['supplementary_volume_descriptor']=>array() { // + ['abstract_file_identifier']=>string() // + ['application_identifier']=>string() // + ['bibliographic_file_identifier']=>string() // + ['copyright_file_identifier']=>string() // + ['data_preparer_identifier']=>string() // + ['offset']=>integer() // + ['publisher_identifier']=>string() // + ['raw']=>array() { // + ['abstract_file_identifier']=>string() // + ['application_data']=>string() // + ['application_identifier']=>string() // + ['bibliographic_file_identifier']=>string() // + ['copyright_file_identifier']=>string() // + ['data_preparer_identifier']=>string() // + ['file_structure_version']=>integer() // + ['logical_block_size']=>integer() // + ['path_table_l_location']=>integer() // + ['path_table_l_opt_location']=>integer() // + ['path_table_m_location']=>integer() // + ['path_table_m_opt_location']=>integer() // + ['path_table_size']=>integer() // + ['publisher_identifier']=>string() // + ['root_directory_record']=>string() // + ['standard_identifier']=>string() // + ['system_identifier']=>string() // + ['unused_1']=>string() // + ['unused_2']=>string() // + ['unused_3']=>string() // + ['unused_4']=>integer() // + ['volume_creation_date_time']=>string() // + ['volume_descriptor_type']=>integer() // + ['volume_descriptor_version']=>integer() // + ['volume_effective_date_time']=>string() // + ['volume_expiration_date_time']=>string() // + ['volume_identifier']=>string() // + ['volume_modification_date_time']=>string() // + ['volume_sequence_number']=>integer() // + ['volume_set_identifier']=>string() // + ['volume_set_size']=>integer() // + ['volume_space_size']=>integer() // + } // + ['system_identifier']=>string() // + ['volume_creation_date_time']=>integer() // + ['volume_effective_date_time']=>boolean() // + ['volume_expiration_date_time']=>boolean() // + ['volume_identifier']=>string() // + ['volume_modification_date_time']=>integer() // + ['volume_set_identifier']=>string() // + } // + } // + + + ['jpg']=>array() { // JPEG - still image + ['exif']=>array() // data returned from PHP's exif_read_data() function + } // + + + ['la']=>array() { // LA - Lossless Audio (www.lossless-audio.com) + ['raw']=>array() { + ['format']=>integer() // + ['flags']=>integer() // + } // + ['flags']=>array() { // + ['seekable']=>boolean() // + ['high_compression']=>boolean() // + } // + ['bits_per_sample']=>integer() // + ['bytes_per_sample']=>integer() // + ['bytes_per_second']=>integer() // + ['channels']=>integer() // + ['compression_ratio']=>double() // + ['format_size']=>integer() // + ['header_size']=>integer() // + ['original_crc']=>double() // + ['sample_rate']=>integer() // + ['samples']=>integer() // + ['uncompressed_size']=>integer() // + ['version']=>double() // + ['version_major']=>integer() // + ['version_minor']=>integer() // + ['footerstart']=>double() // + } + + + ['lpac']=>array() { // LPAC - Lossless Predictive Audio Compressor + ['block_length']=>integer() // + ['file_version']=>integer() // + ['flags']=>array() { // + ['16_bit']=>boolean() // + ['24_bit']=>boolean() // + ['adaptive_prediction_order']=>boolean() // + ['adaptive_quantization']=>boolean() // + ['fast_compress']=>boolean() // + ['is_wave']=>boolean() // + ['joint_stereo']=>boolean() // + ['max_prediction_order']=>integer() // + ['quantization']=>integer() // + ['random_access']=>boolean() // + ['stereo']=>boolean() // + } // + ['raw']=>array() { // + ['audio_type']=>integer() // + ['parameters']=>double() // + } // + ['total_samples']=>integer() // + } // + + + ['lyrics3']=>array() { // Lyrics3 - metainformation tags + ['comments']=>array() { // + ['album']=>string() // + ['artist']=>string() // + ['author']=>string() // + ['comment']=>string() // + ['title']=>string() // + } // + ['flags']=>array() { // + ['lyrics']=>boolean() // + ['timestamps']=>boolean() // + } // + ['images']=>array() { // + []=>array() { // + ['description']=>string() // + ['filename']=>string() // + ['timestamp']=>integer() // + } // + } // + ['raw']=>array() { // + ['offset_start']=>integer() // + ['offset_end']=>integer() // + ['AUT']=>string() // + ['EAL']=>string() // + ['EAR']=>string() // + ['ETT']=>string() // + ['IMG']=>string() // + ['IND']=>string() // + ['INF']=>string() // + ['LYR']=>string() // + ['lyrics3tagsize']=>integer() // + ['lyrics3version']=>integer() // + ['unparsed']=>string() // + } // + ['synchedlyrics']=>array() { // + []=>string() // + } // + ['unsynchedlyrics']=>string() // + } // + + + ['midi']=>array() { // MIDI (Musical Instrument Digital Interface) - sequenced music + ['comments']=>array() { // + ['comment']=>string() // + ['copyright']=>string() // + } // + ['keysignature']=>array() { // + []=>string() // + } // + ['raw']=>array() { // + ['events']=>array() { // + []=>array() { // + []=>array() { // + ['us_qnote']=>integer() // + } // + } // + } // + ['fileformat']=>integer() // + ['headersize']=>integer() // + ['ticksperqnote']=>integer() // + ['track']=>array() { // + []=>array() { // + ['instrument']=>string() // + ['instrumentid']=>integer() // + ['name']=>string() // + } // + } // + ['tracks']=>integer() // + } // + ['timesignature']=>array() { // + []=>string() // + } // + ['totalticks']=>integer() // + } // + + + ['monkeys_audio']=>array() { // Monkey's Audio - lossless audio compression + ['bitrate']=>double() // + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['compressed_size']=>integer() // + ['compression']=>string() // + ['compression_ratio']=>double() // + ['flags']=>array() { // + ['24-bit']=>boolean() // + ['8-bit']=>boolean() // + ['crc-32']=>boolean() // + ['no_wav_header']=>boolean() // + ['peak_level']=>boolean() // + ['seek_elements']=>boolean() // + } // + ['frames']=>integer() // + ['peak_level']=>integer() // + ['peak_ratio']=>double() // + ['playtime']=>double() // + ['raw']=>array() { // + ['header_tag']=>string() // + ['nChannels']=>integer() // + ['nCompressionLevel']=>integer() // + ['nFinalFrameSamples']=>integer() // + ['nFormatFlags']=>integer() // + ['nPeakLevel']=>integer() // + ['nSampleRate']=>integer() // + ['nSeekElements']=>integer() // + ['nTotalFrames']=>integer() // + ['nVersion']=>integer() // + ['nWAVHeaderBytes']=>integer() // + ['nWAVTerminatingBytes']=>integer() // + } // + ['sample_rate']=>integer() // + ['samples']=>integer() // + ['samples_per_frame']=>integer() // + ['uncompressed_size']=>integer() // + ['version']=>double() // + } // + + + ['mpc']=>array() { // MPC (Musepack) - lossy audio compression + ['header']=>array() { // + ['album_gain_db']=>integer() // + ['album_peak']=>integer() // + ['album_peak_db']=>boolean() // + ['title_gain_db']=>integer() // + ['title_peak']=>integer() // + ['title_peak_db']=>boolean() // + ['begin_loud']=>boolean() // + ['end_loud']=>boolean() // + ['encoder_version']=>string() // + ['frame_count']=>integer() // + ['intensity_stereo']=>boolean() // + ['last_frame_length']=>integer() // + ['max_level']=>integer() // + ['max_subband']=>integer() // + ['mid_side_stereo']=>boolean() // + ['profile']=>string() // + ['sample_rate']=>integer() // + ['samples']=>integer() // + ['size']=>integer() // + ['stream_major_version']=>integer() // + ['stream_minor_version']=>integer() // + ['true_gapless']=>boolean() // + ['raw']=>array() { // + ['album_gain']=>integer() // + ['album_peak']=>integer() // + ['encoder_version']=>integer() // + ['preamble']=>string() // + ['profile']=>integer() // + ['sample_rate']=>integer() // + ['title_gain']=>integer() // + ['title_peak']=>integer() // + } // + } // + } // + + + ['mpeg']=>array() { // MPEG (Motion Picture Experts Group) - MPEG video and/or MPEG audio (MP3/MP2/MP1) + ['audio']=>array() { // + ['LAME']=>array() { // + ['RGAD']=>array() { // + ['peak_amplitude']=>double() // + } // + ['ath_type']=>integer() // + ['audio_bytes']=>integer() // + ['bitrate_min']=>integer() // + ['encoder_delay']=>integer() // + ['encoding_flags']=>array() { // + ['nogap_next']=>boolean() // + ['nogap_prev']=>boolean() // + ['nspsytune']=>boolean() // + ['nssafejoint']=>boolean() // + } // + ['end_padding']=>integer() // + ['lame_tag_crc']=>integer() // + ['lowpass_frequency']=>integer() // + ['mp3_gain_db']=>double() // + ['mp3_gain_factor']=>double() // + ['mp3_gain_raw']=>integer() // + ['music_crc']=>integer() // + ['noise_shaping']=>integer() // + ['noise_shaping_raw']=>integer() // + ['not_optimal_quality']=>boolean() // + ['not_optimal_quality_raw']=>integer() // + ['preset_used_id']=>integer() // + ['short_version']=>string() // ex: "LAME 3.93" + ['long_version']=>string() // (pre-v3.90 only) ex: "LAME 3.88 (alpha)" + ['source_sample_freq']=>string() // + ['source_sample_freq_raw']=>integer() // + ['stereo_mode']=>string() // + ['stereo_mode_raw']=>integer() // + ['surround_info']=>string() // + ['surround_info_id']=>integer() // + ['tag_revision']=>integer() // + ['vbr_method']=>string() // + ['vbr_method_raw']=>integer() // + } // + ['VBR_bitrate']=>double() // + ['VBR_bytes']=>integer() // + ['VBR_frames']=>integer() // + ['VBR_method']=>string() // + ['VBR_scale']=>integer() // + ['bitrate']=>integer() // + ['bitrate_distribution']=>array() { // + ['free']=>integer() // + ['8']=>integer() // + ['16']=>integer() // + ['24']=>integer() // + ['32']=>integer() // + ['40']=>integer() // + ['48']=>integer() // + ['56']=>integer() // + ['64']=>integer() // + ['80']=>integer() // + ['96']=>integer() // + ['112']=>integer() // + ['128']=>integer() // + ['144']=>integer() // + ['160']=>integer() // + } // + ['bitrate_mode']=>string() // + ['channelmode']=>string() // + ['channels']=>integer() // + ['copyright']=>boolean() // + ['crc']=>integer() // + ['emphasis']=>string() // + ['frame_count']=>integer() // + ['framelength']=>integer() // + ['layer']=>integer() // + ['modeextension']=>string() // + ['original']=>boolean() // + ['padding']=>boolean() // + ['private']=>boolean() // + ['protection']=>boolean() // + ['raw']=>array() { // + ['bitrate']=>integer() // + ['channelmode']=>integer() // + ['copyright']=>integer() // + ['emphasis']=>integer() // + ['layer']=>integer() // + ['modeextension']=>integer() // + ['original']=>integer() // + ['padding']=>integer() // + ['private']=>integer() // + ['protection']=>integer() // + ['sample_rate']=>integer() // + ['synch']=>integer() // + ['version']=>integer() // + } // + ['sample_rate']=>integer() // + ['stereo_distribution']=>array() { // + ['dual channel']=>integer() // + ['joint stereo']=>integer() // + ['mono']=>integer() // + ['stereo']=>integer() // + } // + ['toc']=>array() { // + []=>integer() // + } // + ['version']=>string() // + ['version_distribution']=>array() { // + []=>integer() // + []=>integer() // + ['2.5']=>integer() // + } // + ['xing_flags']=>array() { // + ['bytes']=>boolean() // + ['frames']=>boolean() // + ['toc']=>boolean() // + ['vbr_scale']=>boolean() // + } // + ['xing_flags_raw']=>string() // + } // + ['video']=>array() { // + ['bitrate']=>integer() // + ['bitrate_mode']=>string() // + ['frame_rate']=>double() // + ['framesize_horizontal']=>integer() // + ['framesize_vertical']=>integer() // + ['pixel_aspect_ratio']=>double() // + ['pixel_aspect_ratio_text']=>string() // + ['raw']=>array() { // + ['bitrate']=>integer() // + ['constrained_param_flag']=>integer() // + ['frame_rate']=>integer() // + ['framesize_horizontal']=>integer() // + ['framesize_vertical']=>integer() // + ['intra_quant_flag']=>integer() // + ['marker_bit']=>integer() // + ['pixel_aspect_ratio']=>integer() // + ['vbv_buffer_size']=>integer() // + } // + } // + } // + + + ['nsv']=>array() { // NSV - Nullsoft Streaming Video + ['NSVf']=>array() { // + ['TOC_entries_1']=>integer() // + ['TOC_entries_2']=>integer() // + ['file_size']=>integer() // + ['header_length']=>integer() // + ['identifier']=>string() // + ['meta_size']=>integer() // + ['metadata']=>string() // + ['playtime_ms']=>integer() // + } // + ['NSVs']=>array() { // + ['audio_codec']=>string() // + ['frame_rate']=>double() // + ['framerate_index']=>integer() // + ['identifier']=>string() // + ['offset']=>integer() // + ['resolution_x']=>integer() // + ['resolution_y']=>integer() // + ['unknown1b']=>integer() // + ['unknown1c']=>integer() // + ['unknown1d']=>integer() // + ['unknown2a']=>integer() // + ['unknown2b']=>integer() // + ['unknown2c']=>integer() // + ['unknown2d']=>integer() // + ['unknown3a']=>integer() // + ['unknown3b']=>integer() // + ['unknown3c']=>integer() // + ['unknown3d']=>integer() // + ['video_codec']=>string() // + } // + ['comments']=>array() { // + ['aspect']=>string() // + ['title']=>string() // + } // + } // + + + ['ofr']=>array() { // OFR (OptimFROG) - lossless audio compression + ['COMP']=>array() { // + []=>array() { // + ['channel_configuration']=>string() // + ['crc_32']=>boolean() // + ['encoder']=>string() // + ['offset']=>integer() // + ['raw']=>array() { // + ['algorithm_id']=>integer() // + ['channel_configuration']=>integer() // + ['encoder_id']=>integer() // + ['sample_type']=>integer() // + } // + ['sample_count']=>integer() // + ['sample_type']=>string() // + ['size']=>integer() // + } // + } // + ['HEAD']=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + ['OFR ']=>array() { // + ['channel_config']=>integer() // + ['channels']=>integer() // + ['compression']=>string() // + ['encoder']=>string() // + ['offset']=>integer() // + ['raw']=>array() { // + ['compression']=>integer() // + ['encoder_id']=>integer() // + ['sample_type']=>integer() // + } // + ['sample_rate']=>integer() // + ['sample_type']=>string() // + ['size']=>integer() // + ['total_samples']=>integer() // + } // + ['TAIL']=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + + + ['ogg']=>array() { // OGG - container format for Ogg Vorbis, OggFLAC, Speex, etc + ['bitrate_average']=>double() // + ['bitrate_max']=>integer() // + ['bitrate_min']=>integer() // + ['bitrate_nominal']=>integer() // + ['bitstreamversion']=>integer() // + ['blocksize_large']=>integer() // + ['blocksize_small']=>integer() // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + []=>array() // can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['comments_raw']=>array() { // + []=>array() { // + ['dataoffset']=>integer() // + ['key']=>string() // + ['size']=>integer() // + ['value']=>string() // + } // + } // + ['numberofchannels']=>integer() // + ['pageheader']=>array() { // + []=>array() { // + ['flags']=>array() { // + ['bos']=>boolean() // + ['eos']=>boolean() // + ['fresh']=>boolean() // + } // + ['flags_raw']=>integer() // + ['header_end_offset']=>integer() // + ['packet_type']=>integer() // + ['page_checksum']=>double() // + ['page_end_offset']=>integer() // + ['page_length']=>integer() // + ['page_segments']=>integer() // + ['page_seqno']=>integer() // + ['page_start_offset']=>integer() // + ['pcm_abs_position']=>integer() // + ['segment_table']=>array() { // + []=>integer() // + } // + ['stream_serialno']=>integer() // + ['stream_structver']=>integer() // + ['stream_type']=>string() // + } // + ['eos']=>array() { // + ['flags']=>array() { // + ['bos']=>boolean() // + ['eos']=>boolean() // + ['fresh']=>boolean() // + } // + ['flags_raw']=>integer() // + ['header_end_offset']=>integer() // + ['page_checksum']=>double() // + ['page_end_offset']=>integer() // + ['page_length']=>integer() // + ['page_segments']=>integer() // + ['page_seqno']=>integer() // + ['page_start_offset']=>integer() // + ['pcm_abs_position']=>integer() // + ['segment_table']=>array() { // + []=>integer() // + } // + ['stream_serialno']=>integer() // + ['stream_structver']=>integer() // + } // + } // + ['samplerate']=>integer() // + ['samples']=>integer() // + ['stop_bit']=>integer() // + ['vendor']=>string() // + } // + + + ['png']=>array() { // PNG (Portable Network Graphics) - still image + ['IDAT']=>array() { // + []=>array() { // + ['header']=>array() { // + ['crc']=>integer() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + } // + } // + ['IEND']=>array() { // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + } // + ['IHDR']=>array() { // + ['color_type']=>array() { // + ['alpha']=>boolean() // + ['palette']=>boolean() // + ['true_color']=>boolean() // + } // + ['compression_method_text']=>string() // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['height']=>integer() // + ['raw']=>array() { // + ['bit_depth']=>integer() // + ['color_type']=>integer() // + ['compression_method']=>integer() // + ['filter_method']=>integer() // + ['interlace_method']=>integer() // + } // + ['width']=>integer() // + } // + ['PLTE']=>array() { // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + []=>integer() // + } // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + []=>array() // can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['gAMA']=>array() { // + ['gamma']=>double() // + ['header']=>array() { // + ['crc']=>integer() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + } // + ['oFFs']=>array() { // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['position_x']=>integer() // + ['position_y']=>integer() // + ['unit']=>string() // + ['unit_specifier']=>integer() // + } // + ['pHYs']=>array() { // + ['header']=>array() { // + ['crc']=>integer() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['pixels_per_unit_x']=>integer() // + ['pixels_per_unit_y']=>integer() // + ['unit']=>string() // + ['unit_specifier']=>integer() // + } // + ['pcLb']=>array() { // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + } // + ['tEXt']=>array() { // + ['header']=>array() { // + ['crc']=>integer() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['keyword']=>string() // + ['text']=>string() // + } // + ['tIME']=>array() { // + ['day']=>integer() // + ['header']=>array() { // + ['crc']=>integer() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['hour']=>integer() // + ['minute']=>integer() // + ['month']=>integer() // + ['second']=>integer() // + ['unix']=>integer() // + ['year']=>integer() // + } // + ['tRNS']=>array() { // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['transparent_color_blue']=>integer() // + ['transparent_color_green']=>integer() // + ['transparent_color_red']=>integer() // + } // + ['zTXt']=>array() { // + ['compressed_text']=>string() // + ['compression_method']=>integer() // + ['compression_method_text']=>string() // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['keyword']=>string() // + ['text']=>string() // + } // + } // + + + ['quicktime']=>array() { // Quicktime - video/audio + ['']=>array() { // + ['name']=>boolean() // + ['offset']=>integer() // + ['size']=>integer() // + } // + ['audio']=>array() { // + ['bit_depth']=>integer() // + ['channels']=>integer() // + ['codec']=>string() // + ['sample_rate']=>double() // + } // + ['free']=>array() { // + ['name']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + ['mdat']=>array() { // + ['name']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + ['moov']=>array() { // + ['hierarchy']=>string() // + ['name']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + ['subatoms']=>array() // This is an undocumentably-complex recursive array, typically containing a huge amount of seemingly disorganized data. Avoid this like the plague. + } // + ['time_scale']=>integer() // + ['display_scale']=>integer() // 1 = normal; 0.5 = half; 2 = double + ['video']=>array() { // + ['codec']=>string() // + ['color_depth']=>integer() // + ['color_depth_name']=>string() // + ['resolution_x']=>double() // + ['resolution_y']=>double() // + } // + ['wide']=>array() { // + ['name']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + + + ['real']=>array() { // Real (RealAudio / RealVideo) - audio/video + ['chunks']=>array() { // + []=>array() { // + ['file_version']=>integer() // + ['headers_count']=>integer() // + ['length']=>integer() // + ['name']=>string() // + ['object_version']=>integer() // + ['offset']=>integer() // + } // + []=>array() { // + ['avg_bit_rate']=>integer() // + ['avg_packet_size']=>integer() // + ['data_offset']=>integer() // + ['duration']=>integer() // + ['flags']=>array() { // + ['live_broadcast']=>boolean() // + ['perfect_play']=>boolean() // + ['save_enabled']=>boolean() // + } // + ['flags_raw']=>integer() // + ['index_offset']=>integer() // + ['length']=>integer() // + ['max_bit_rate']=>integer() // + ['max_packet_size']=>integer() // + ['name']=>string() // + ['num_packets']=>integer() // + ['num_streams']=>integer() // + ['object_version']=>integer() // + ['offset']=>integer() // + ['preroll']=>integer() // + } // + } // + ['comments']=>array() { // + ['artist']=>string() // + ['comment']=>string() // + ['title']=>string() // + } // + } // + + + ['riff']=>array() { // RIFF (Resource Interchange File Format) - audio/video container format (AVI, WAV, CDDA, etc) + ['AIFC']=>array() { // + ['COMM']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['FVER']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['INST']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['MARK']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['SSND']=>array() { // + []=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + ['AIFF']=>array() { // + ['(c) ']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['COMM']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['SSND']=>array() { // + []=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + ['AVI ']=>array() { // + ['JUNK']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['hdrl']=>array() { // + ['avih']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['odml']=>array() { // + ['dmlh']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + ['strl']=>array() { // + ['JUNK']=>array() { // + []=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['strf']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['strh']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['strn']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + } // + ['idx1']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['movi']=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['CDDA']=>array() { // + ['fmt ']=>array() { // + []=>array() { // + ['data']=>string() // + ['disc_id']=>integer() // + ['offset']=>integer() // + ['playtime_frames']=>integer() // + ['playtime_seconds']=>double() // + ['size']=>integer() // + ['start_offset_frame']=>integer() // + ['start_offset_seconds']=>double() // + ['track_num']=>integer() // + ['unknown1']=>integer() // + ['unknown6']=>integer() // + ['unknown7']=>integer() // + } // + } // + } // + ['WAVE']=>array() { // + ['DISP']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['INFO']=>array() { // + ['IART']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ICMT']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ICOP']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['IENG']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['IGNR']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['IKEY']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['IMED']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['INAM']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ISBJ']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ISFT']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ISRC']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ISRF']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ITCH']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + ['MEXT']=>array() { // + []=>array() { // + ['anciliary_data_length']=>integer() // + ['data']=>string() // + ['flags']=>array() { // + ['anciliary_data_free']=>boolean() // + ['anciliary_data_left']=>boolean() // + ['anciliary_data_right']=>boolean() // + ['homogenous']=>boolean() // + } // + ['offset']=>integer() // + ['raw']=>array() { // + ['anciliary_data_def']=>integer() // + ['sound_information']=>integer() // + } // + ['size']=>integer() // + } // + } // + ['bext']=>array() { // + []=>array() { // + ['author']=>string() // + ['bwf_version']=>integer() // + ['coding_history']=>array() { // + []=>string() // + } // + ['data']=>string() // + ['offset']=>integer() // + ['origin_date']=>string() // + ['origin_date_unix']=>integer() // + ['origin_time']=>string() // + ['reference']=>string() // + ['reserved']=>integer() // + ['size']=>integer() // + ['time_reference']=>integer() // + ['title']=>string() // + } // + } // + ['cart']=>array() { // + []=>array() { // + ['artist']=>string() // + ['category']=>string() // + ['classification']=>string() // + ['client_id']=>string() // + ['cut_id']=>string() // + ['data']=>string() // + ['end_date']=>string() // + ['end_time']=>string() // + ['offset']=>integer() // + ['out_cue']=>string() // + ['post_time']=>array() { // + []=>array() { // + ['timer_value']=>integer() // + ['usage_fourcc']=>string() // + } // + } // + ['producer_app_id']=>string() // + ['producer_app_version']=>string() // + ['size']=>integer() // + ['start_date']=>string() // + ['start_time']=>string() // + ['tag_text']=>array() { // + []=>string() // + } // + ['title']=>string() // + ['url']=>string() // + ['user_defined_text']=>string() // + ['version']=>string() // + ['zero_db_reference']=>integer() // + } // + } // + ['data']=>array() { // + []=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['fact']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['fmt ']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['rgad']=>array() { // + []=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + ['audio']=>array() { // + []=>array() { // + ['bitrate']=>integer() // + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['codec']=>string() // + ['sample_rate']=>integer() // + } // + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['codec_fourcc']=>string() // + ['codec_name']=>string() // + ['sample_rate']=>integer() // + ['total_samples']=>integer() // + } // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + []=>array() // can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['header_size']=>integer() // + ['raw']=>array() { // + ['avih']=>array() { // + ['dwFlags']=>integer() // + ['dwHeight']=>integer() // + ['dwInitialFrames']=>integer() // + ['dwLength']=>integer() // + ['dwMaxBytesPerSec']=>integer() // + ['dwMicroSecPerFrame']=>integer() // + ['dwPaddingGranularity']=>integer() // + ['dwRate']=>integer() // + ['dwScale']=>integer() // + ['dwStart']=>integer() // + ['dwStreams']=>integer() // + ['dwSuggestedBufferSize']=>integer() // + ['dwTotalFrames']=>integer() // + ['dwWidth']=>integer() // + ['flags']=>array() { // + ['capturedfile']=>boolean() // + ['copyrighted']=>boolean() // + ['hasindex']=>boolean() // + ['interleaved']=>boolean() // + ['mustuseindex']=>boolean() // + ['trustcktype']=>boolean() // + } // + } // + ['fact']=>array() { // + ['NumberOfSamples']=>integer() // + } // + ['fmt ']=>array() { // + ['nAvgBytesPerSec']=>integer() // + ['wBitsPerSample']=>integer() // + ['nBlockAlign']=>integer() // + ['nChannels']=>integer() // + ['nSamplesPerSec']=>integer() // + ['wFormatTag']=>integer() // + } // + ['rgad']=>array() { // + ['audiophile']=>array() { // + ['adjustment']=>integer() // + ['name']=>integer() // + ['originator']=>integer() // + ['signbit']=>integer() // + } // + ['fPeakAmplitude']=>double() // + ['nAudiophileRgAdjust']=>integer() // + ['nRadioRgAdjust']=>integer() // + ['radio']=>array() { // + ['adjustment']=>integer() // + ['name']=>integer() // + ['originator']=>integer() // + ['signbit']=>integer() // + } // + } // + ['strf']=>array() { // + ['auds']=>array() { // + []=>array() { // + ['nAvgBytesPerSec']=>integer() // + ['wBitsPerSample']=>integer() // + ['nBlockAlign']=>integer() // + ['nChannels']=>integer() // + ['nSamplesPerSec']=>integer() // + ['wFormatTag']=>integer() // + } // + } // + ['vids']=>array() { // + []=>array() { // + ['biBitCount']=>integer() // + ['biClrImportant']=>integer() // + ['biClrUsed']=>integer() // + ['biHeight']=>integer() // + ['biPlanes']=>integer() // + ['biSize']=>integer() // + ['biSizeImage']=>integer() // + ['biWidth']=>integer() // + ['biXPelsPerMeter']=>integer() // + ['biYPelsPerMeter']=>integer() // + ['fourcc']=>string() // + } // + } // + } // + ['strh']=>array() { // + []=>array() { // + ['dwFlags']=>integer() // + ['dwInitialFrames']=>integer() // + ['dwLength']=>integer() // + ['dwQuality']=>integer() // + ['dwRate']=>integer() // + ['dwSampleSize']=>integer() // + ['dwScale']=>integer() // + ['dwStart']=>integer() // + ['dwSuggestedBufferSize']=>integer() // + ['fccHandler']=>string() // + ['fccType']=>string() // + ['rcFrame']=>integer() // + ['wLanguage']=>integer() // + ['wPriority']=>integer() // + } // + } // + } // + ['rgad']=>array() { // + ['audiophile']=>array() { // + ['adjustment']=>double() // + ['name']=>string() // + ['originator']=>string() // + } // + ['peakamplitude']=>double() // + ['radio']=>array() { // + ['adjustment']=>double() // + ['name']=>string() // + ['originator']=>string() // + } // + } // + ['video']=>array() { // + []=>array() { // + ['codec']=>string() // + ['frame_height']=>integer() // + ['frame_rate']=>double() // + ['frame_width']=>integer() // + } // + } // + ['litewave']=>array() { // http://www.clearjump.com + ['raw']=>array() { // + ['compression_method']=>integer() // 1=lossy; 2=lossless + ['compression_flags']=>integer() // + ['m_dwScale']=>integer() // scalefactor for lossy compression - related to m_wQuality as: $m_wQuality = round((2000 - $m_dwScale) / 20) + ['m_dwBlockSize']=>integer() // number of samples in encoded blocks + ['m_wQuality']=>integer() // quality factor (0=most compressed lossy; 99=best quality lossy; 100=lossless) + ['m_wMarkDistance']=>integer() // distance between marks in bytes + ['m_wReserved']=>integer() // + ['m_dwOrgSize']=>integer() // original file size in bytes + ['m_bFactExists']=>integer() // indicates if 'fact' chunk exists in the original file + ['m_dwRiffChunkSize']=>integer() // riff chunk size in the original file + } // + ['quality_factor']=>integer() // alias of ['raw']['m_wQuality'] + } // + } // + + + ['shn']=>array() { // Shorten - lossless audio compression + ['seektable']=>array() { // + ['length']=>integer() // + ['offset']=>integer() // + ['present']=>boolean() // + } // + ['version']=>integer() // + } // + + + ['swf']=>array() { // SWF - ShockWave Flash (www.openswf.org) + ['header']=>array() { // + ['frame_count']=>integer() // + ['frame_height']=>integer() // + ['frame_width']=>integer() // + ['length']=>integer() // + ['signature']=>string() // + ['version']=>integer() // + } // + ['bgcolor']=>string() // + ['tags']=>array() // + } // + + + ['voc']=>array() { // VOC - SoundBlaster VOC audio format + ['blocks']=>array() { // + []=>array() { // + ['bits_per_sample']=>integer() // + ['block_offset']=>integer() // + ['block_size']=>integer() // + ['block_type_id']=>integer() // + ['channels']=>integer() // + ['compression_name']=>string() // + ['compression_type']=>integer() // + ['pack_method']=>integer() // + ['sample_rate']=>integer() // + ['sample_rate_id']=>integer() // + ['stereo']=>boolean() // + ['time_constant']=>integer() // + ['wFormat']=>integer() // + } // + } // + ['compressed_bits_per_sample']=>integer() // + ['header']=>array() { // + ['datablock_offset']=>integer() // + ['major_version']=>integer() // + ['minor_version']=>integer() // + } // + } // + + + ['vqf']=>array() { // VQF - transform-domain weighted interleave Vector Quantization Format (lossy audio) + ['COMM']=>array() { // + ['bitrate']=>integer() // + ['channel_mode']=>integer() // + ['sample_rate']=>integer() // + ['security_level']=>integer() // + } // + ['DSIZ']=>integer() // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + []=>array() // can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['raw']=>array() { // + ['header_tag']=>string() // + ['size']=>integer() // + ['version']=>string() // + } // + } // + + + ['wavpack']=>array() { // WavPack - lossless audio compression + ['bits']=>integer() // + ['crc1']=>double() // + ['crc2']=>integer() // + ['extension']=>string() // + ['extra_bc']=>string() // + ['extras']=>string() // + ['flags_raw']=>integer() // + ['offset']=>integer() // + ['shift']=>integer() // + ['size']=>integer() // + ['total_samples']=>integer() // + ['version']=>integer() // + } // + + + ['zip']=>array() { // ZIP - lossless data compression + ['central_directory']=>array() { // + []=>array() { // + ['compressed_size']=>integer() // + ['compression_method']=>string() // + ['create_version']=>string() // + ['entry_offset']=>integer() // + ['extract_version']=>string() // + ['filename']=>string() // + ['flags']=>array() { // + ['compression_speed']=>string() // + ['data_descriptor_used']=>boolean() // + ['encrypted']=>boolean() // + } // + ['host_os']=>string() // + ['last_modified_timestamp']=>integer() // + ['offset']=>integer() // + ['raw']=>array() { // + ['compressed_size']=>integer() // + ['compression_method']=>integer() // + ['crc_32']=>double() // + ['create_version']=>integer() // + ['disk_number_start']=>integer() // + ['external_file_attrib']=>double() // + ['extra_field_length']=>integer() // + ['extract_version']=>integer() // + ['file_comment_length']=>integer() // + ['filename_length']=>integer() // + ['general_flags']=>integer() // + ['internal_file_attrib']=>integer() // + ['last_mod_file_date']=>integer() // + ['last_mod_file_time']=>integer() // + ['local_header_offset']=>integer() // + ['signature']=>integer() // + ['uncompressed_size']=>integer() // + } // + ['uncompressed_size']=>integer() // + } // + } // + ['comments']=>array() { // + ['comment']=>string() // + } // + ['compressed_size']=>integer() // + ['compression_method']=>string() // + ['compression_speed']=>string() // + ['end_central_directory']=>array() { // + ['comment']=>string() // + ['comment_length']=>integer() // + ['directory_entries_this_disk']=>integer() // + ['directory_entries_total']=>integer() // + ['directory_offset']=>integer() // + ['directory_size']=>integer() // + ['disk_number_current']=>integer() // + ['disk_number_start_directory']=>integer() // + ['offset']=>integer() // + ['signature']=>integer() // + } // + ['entries']=>array() { // + []=>array() { // + ['compressed_size']=>integer() // + ['compression_method']=>string() // + ['extract_version']=>string() // + ['filename']=>string() // + ['flags']=>array() { // + ['compression_speed']=>string() // + ['data_descriptor_used']=>boolean() // + ['encrypted']=>boolean() // + } // + ['host_os']=>string() // + ['last_modified_timestamp']=>integer() // + ['offset']=>integer() // + ['raw']=>array() { // + ['compressed_size']=>integer() // + ['compression_method']=>integer() // + ['crc_32']=>integer() // + ['extra_field_length']=>integer() // + ['extract_version']=>integer() // + ['filename_length']=>integer() // + ['general_flags']=>integer() // + ['last_mod_file_date']=>integer() // + ['last_mod_file_time']=>integer() // + ['signature']=>integer() // + ['uncompressed_size']=>integer() // + } // + ['uncompressed_size']=>integer() // + } // + } // + ['entries_count']=>integer() // + ['files']=>array() { // multidimensional tree-structure array listing of all files and directories in image + []=>array() // entries of type array are directories (key is directory name), may contain files and/or other subdirectories + []=>integer() // entries of type integer are files (key is file name, value is file size in bytes) + } // + ['uncompressed_size']=>integer() // + } // +} // diff --git a/app/library/helperapps/metaflac.exe b/app/library/helperapps/metaflac.exe deleted file mode 100644 index 75dff4d9d151f6c6fb9234950a306c21fcfb6516..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151552 zcmeFaeSFl#wLiYeF3FN?*hMzD>Y}Tz8YQjKjWxPK7s!)5?gP7v1p#kqyKZSqaTjPC zF}PV|Go-~{+S*>*3%A;@*K&JpOM9_;F(H%yDk56NVq3Jmb<%*1#X_J$zVCBpcC&e) zz1Z*T_xk-Iygr%z%$zxM=FFKhXU;r)W`BI6!DuiTOn3$c42JCp>7S4P{_Q^!$>T2j z`8dP2u`gV{ytqBFhgB8_;mnq zEl8v=@1M_L=*WBh@aKmd_>co1a^OP_e8_7lTVX^31 zv>-o!AQX;7yX)LMg_hr44N4EC4=2lF%_vd+@Dr*c^W2Wq-T`si)A8pYo_y(!)G3qo zfp1c+P9vJ4+T-uvu_J*j#uwVgx9z-jV$#{#k+8P~3h)vsNRCT&7N&L=wss_scshE= zT+)#`GyvXA1MP&_)3IY2GBfh?!jQCyWo}0}B|>jF5Quw z6ps0UKP3PAGY0%EMq+B0ZSkVJJRJ+}>_QVdMT1KiQXRid^^aNg_JBM6-UU8`m}82Z zeEpKWp(|eq$e%DPEuz2I9tp`bxDC{zpIAqnPY)!fK)7tE)J>wnf?#Y&&V4|!Zz5uX z{*ZjO8ASD`c3IK`NozP(#Uxz^k`NV=Us$D}At`r(DwJ|NF!w%;AG_(tS3`2mS_bL; zPDmctKffjhW`WkFc=q9W8P6Md-o`_M#O*65f)F8dv>rqI?m^*l|J|y{I=9mwmNtS~ z=&nG;JOg2QH@)_uQg+CtXK(t1_3NEic4kn?j#cpDyEyoOsD5yAl(79PV1(f!Dz%U=a&$Ko!Hk1u<>E}eO0-1 zhc%rT7yvDwM1-b9aVOhJn26X>5CB4QG?+adTF6Pf<%N{Hk9$b^M7Hh+mt^a1_eZ3i zK$=-zJjWvPsw@GC+v>v+8BwqPs9b2y*5-0C*Aj@xnMk7kWnoE6ltyGv3nRXg`q57# z@(J}Sx`^;D^jEnaF`|!k#vnPF4)sS>_r-sEGYGMWfrS!vTk{KOOb=LFA?gM&MOZFw zH(iWE&^_juF>crbVwT$#^os^l*dLHz$Q3dI=To5bc9MUz@y!rJJKo2JrFD&}{KO!7 z)B~i^wDOn$Sf`&ANLcoKia8LLRre}zRv^R*aGa0|bLy1K`d~N8_Z)0{$n5YL2Hami zdZEt%`t0b+79!M4+^_{f=%Go3$KHiVhv#5=FF{dX1teF=LXdWXy!3K{baWxb4k#1q ziWDULWgI2gMkYAR^Adb!WU>%mGs%$aB9*qC#F9$43%?l%6?t4950bVnbno^%3?D@e zX5m?h=c{-g$FmF1K0G7;73hD3g+`-kOp%t#wMTaWRJ6LEM?lKLbHa1jbIdc~-JMCe z?GOX@MqAf?Pk|jmKq`=U0XcF9M4?~4{dpoB1C6!y#{`B6t|c@J6B|D_;jd8IY|hWg#)?YE{2Q2UTfJVGeZ=dG=mBO*0WkQkd9D72=h;|;8_ zfn>_nkm@vf`!a~IJ`jVd49R#dXS$JK%ScVf%r8SjcKQNYEe#Dx9cJw=s(zQ);HHYP zGKC%$_8$dfuN=%*>Z5^&{G0*M$PF;J>fF~Oif?gaR}ZpE+;j*M79DsM1ylqEUIoh0 zSWP_!B|oGD(hp&b*?~k$vQD+Rq8~_W+MRywvQF-VGRKkx}QwiJ;7Ia1A_wWi9xPVs^-d%|9X&5m+IsoPL z?2V#NTf{UU0%{IY!Rvktcp5!d)RJz33^_#+n-Sjb#7(LG!UU!l*7PQ7vj$TJ)(l?P zaTCrlqxRL}T5oTX5**MPP06FZ%V6TB_pbaTimVb1W^qXLhzl|hk#;``r(2YI_atux z=Hv}x8W|whA+}1C&HvF;{)zCZUsV4y)@W&p}nJmLI)tkIs+czOKIcEC?1pZjF z1~m@{oox=pmT@8#v&)?7FS0(kxEM>OzCDvDq*;W2PY=Z_E&c@hkonZ0| zz-pOyeN8a7Jd3O`HDFuO6I+%AVbgjT;j#JIR1D}HZFQ4v{y_xOQ-3w(BY-N;7mHXO z=N0ud^`SBN>=^VZ=+E@t#3f?3IaZ7GR1Yl3sQh-A`Z-M|7j%ho8f*cO+UY`aFc2@m zoa6xFvJk_F6x5;>x-c6gfQ)SGy04-*PX{`jPgxCmof)TRD?50l=D6hpRnogB=%=H!ot0C&^{wSb>u_{G-n%+=LH5IY0d)f%gaUpanJ?)Pp5w&s|UvKj4^&YTp z>J*)6YH?@6+BV&Zk=;(C1Dq(ismL%UQSM;Ol_h3)>U5JRMb*rT7|e6jXjeTPj*XV> z@4v$8>OBOWgZ{RVIT(8bzez`0dM_K62fb;!*&o}Z0Z3?I_*m1mpm-Q)`^>R{*!C>o zutX7ZY=07IskaJRP8TG`Y=0at{@BCk06#I^Q8-kuwwwc!2!vW+$jSJLR%q=YmV%=O z>pB`B@#dsASY}Q!(`D=#P8$Z?K&qkooGXzAiUa3Lq=BO2T!}PLJbA9f`iiH> zW$W!qj(KV`fI$344LhPshAV@y=5rv5E$0MLUj?=8?MloPvnyFulb-(b+(A7(tkOAk zkM^N_6GT|4_#pEAJTIRZ*ng?da3@CQzh34uYPMt#E zNS!K7PE4(?uqAD4SG(%A8Vt!|_D}bgc~10>O`RG8iwj14SjHJA8EfNuzYo!K1Y?^H z!Emt=!sK--W+`G=bHkX|UbCf?Jfv0&}Y8;>6NBtm!f6++^y4z$%0eHPi)C zJAJVlAPZ(EmKM*6u>9F)RV{auW(Fo7=JzDs0r?EHAasi83(JRw0WtE#^+X_(#;!#Z zaOd25jQiumvNc(_fBDRTNQjKQieNiNBYz+&ms9`lOYL%{Pb6lBWk6*FW6Hv^F8h)^ zj_kHzSiZ$EWYS+otrLwMfVt$C0>e8nVf5nl6DD&F%2UX zA4wmJT3f#j=a@Lder|SgTU*y7W=G%auoZsMW^22&ZQrdgCogT?o0!<qdmvb!+ne1^a`xVKaAh`R%UKY*F1JEW$b5gGp>Ql4?|LJF+zOfk2Ww=Yo= z87OaNQcR*4T&y8!0dJi^N~{Q|iC-|xQ@`~fBIk@4N(6b%QXRHn9Na&~vuMnT(JLU` zAdCl2HnVhfmgH9_=XT)gqz^*{SV6cAW!mBe0r{OAz8k68HEBaAa}QD`E==vRcR#Qh z)YCt-Db+fC%-V_>otq|(!Kv*^d zx#u7l-xhH;QqiYZ?7*1e0(TrV{StzT#2mhY=kN|HbVUaB+=giM#%=?d6+8ol^M|=K zgsChc{X;vn_7pKNBpbdACDvDwN*E&66A<$O#kz#$sC~W*g1!7#Lj;=~>D5}W%@7(E zY-kDPkqEP%s6&>Vp3w5tMWoWY)fv5RCEiW#HbYEb(P9d3c^JK2dC1(rknbV0C?`f& z%z=bRbfDZt{RxI>6c+AH|BZ?MKsh-+lp5XTg8l0~lqd=1dI7k-otTYj0gQ<}efeI# z#Ab`pIwW<}#$Lz)nx*4$;<3p{44m%so}|{yu3ucxq;wD#Y~a0)fi1^Dk+`T z)@M<6Q=-^&qKC*3^%i&uN^hxISuA`eQPpcq?H_>T*h6wP<)y(Vb*d<7NuOvodC#iR zB-AJ`K_ZPy9n}94lb}8KVRU3iZ>3+(h8|;e-ZKwnpqu_Ked0bh>g&#{@9M*O3I{s8 zr-JB#ge%TRSRwcTlv4;$vOPuungwq|*bJ@i4xmfG=Ny$K6c4R#gV(F|H>e}Ea#-HW z>i{(DLA!q{`vUDw?wPPOaWzQ0yP`20$_mLJVPGfy6qMI|n`%LYeg*i#vN4-IF(E8( z&Aud$!(+!~QF@MH0pWuIR*<}sx*&=lAB{Z&i3!O78xE*1Y)uD;Y#;7CBjPM z`TSPqyhQqXlED_(6`&CoxcyDV7gLVvjxFle`^e29S2aZI8JjcBEDXHk{-Ts5W` zw%#$Req+KKm6Z%l-Dqs@^V?Z!$yl|EmrH+%JY=@UEWe&===lzs0BDxQjj{&4h7q&N z^$030lZu3>eE$;PA;F^b4wSbM=y-^dGISD_df@uA5rV>nFH^hhy#cx7faZSk(umwbkjiojk=ecnkU<4`4a##tOY8s$ z*aJphsBkjMN3tj_Sp0?80Ss`b2QZJVz>K3Zfd%QG<;uAzB5xT+5tS3*b+2lO0jbFv zVNcg=L+NS+r}dgx3{ZZi28GOW&x&-@LYP5%!AFwW?z^y-O^h0B3;G3Yw$xDvrb+3& z*7Q+oN}5IcEl?uYiY9YJ%y(rlE_zNFdqA;m!k>i=0&htjD-b2o)Nc%Mb1ieb?p@Pv zShqY#z==$PUy+REO5s8gYt?OxNS}zTD<6zj=Ox|g`^#U9p?wAuK69Eaxu))Fg7E z^L7yOB*B!h0zZ5O>`H$bLPJu4LGjdh>x18gRWRUgudVUXg7duFvaVYK%Xi)4-4vD? zA_6J~FM7Jz09S1by2$`#2+POh7|F8 zxHN|n8F3FOqH6>|L&cCH{%r)noN5uN3It=TbyY24dEaoLREIgWyCy8mnQ@pKWFjWW zbOx(Aeha9n%tS0|e3f;>GXjp3TxO0ftQMN&}nef4m_ zF#5J#S`p2BLnWTMcvj%~I-c!# z4&ynCC)XMAgN%9JW+T705WH1;>(~<%tj+c`Cw56~LMntkBzp-A_AU>osoPA})~})i zhvYvq0GWh`MRBVC!h|E$??_At$r&nvRRPtb3oFv!Uk4;K)boh^4CG7x&dbH+;Aun- z$tgLk$Fa@qxBN!x|59{r8f2Y;SSyWJvbBz~>k+gI6m-0UWiji6_d$n2le)#b?-t=P!6JzSU=#j%OL3Z{yj6Ctp56zgaXnv95Is`CESd5EVlbK%=}# z8x~|&9EBL<=4X%tGNCL=v#{vt@dddoU@`oCvh(X2*L{}(*2JyMb9F+Mo2wJ5H6_Sm zQ#vRg83@h8SMdJ=pR%7xD5IhQ+pVmv%Q0ACf2Lnv{&W10)M<|0cRd!yMTB&$GwZkF zS?Yl!d0f!WCh)6k?HM|S+I&5Bl>rxO;|yw3i)T6yBU^6nke98!p4JD+9U%@;!L;!< z0a$rc4e`#j+cX)wP%W|Ls#x;$>76Im)+G?MXgFQ~hl3mO=vUrfSkp5oR=FU=UwJYJ zH0*TrzL5ABV98~XqY|6b0<^kH#TS<>9RtC3w(p*c*m5$p`CA8ZZqMLCqR9Ho`sgjr04Rci0O zN{nNU2ISLW%~9)yx4}>l>V6tn3ezXH18eJhVB`mI8<@qE(LL_8+xGV19`{nQ$z26H)Z9kN7rESX@|E*;6X{)QIwvMk zlkK2}KGA5gS<}~%fW#Q`mLXUZT}q z4FagFfWUCfQqOH(@g@m9Pc*LwR4t(3Nv=WQAIxMAAdV2w?#-6d0|PYJ^`Wh4r)!a) z419Jk40$8(m`=mW&6PcWJd5?wO^ilK2*x99yNsDY0Z(-;UT&a7iPn6Cls#o@cPSok za#wglP{Wn+eoBHqJ9P5M)O4qt|;*OgUFP2cP}5s-Ay~!J~mwQ zOwdCySm#h3g#1yt4g#6V`lHZ;ospnuatOaOfJGE+m-sZ=lq-*hB{%hu#hPq4B=4Lp zuYHGmsg)xN<>M^C&sig3c4kKZ^`&=nI?Ot$&qPtW|~}?CO2lCnWoxIlP}X$ zmuafZG*xAqsxwVBnWm;p(=64KX*?z`bT|STw9)zONCp5)QyU_0tUE#mW1y0d2qaUw3B0rdy^^T=O*S zwm~QHVjBvAYG$vviCr`BX1xDL60lcQqs0#3jh7a%ox+BS*ob|-MG9P$aT(o{J2Ul7 ziFtmQ?f3h-_S+WV`JdT8hEK!+L+MT5~2u>#@>mdz7lC#^~NglDCO;?L}}{2rUL7_e*B7G z9F{XKVdB8{%apXl4)p*TnGUZ$%i!4K0GEi~%NM^SEHBJuxlk3qo^p_%llI^daC}l& zzI$;Nu|xB%mcVG!at_Q9miuyG_rx7xR_F3(h{GZ?ke%;p>Sc_E7*ZnzdB`m|`c>~1NtXvlsaA$XQL5uus=suV z5A(4%8htQoVaF$Kgaev?WVJ0HPRhMNh7;D_;-Mh*?#9$^7mk1YK-Bw0vX%?D9tHH9 zR!stSOH&p*XC-fO7Gb>)SuM!=lBkD6b}+s+{{?c5t)%r$o)e%PTP$Y@)06q>d{|A2 z*|P#XLhvk643wf$b$BSG$Da2)8FF&A1rjDqdK#i z2E#!#%Rd+zkdc{Bm|TjQCI0{#5@(HO)<1xTW>OkW%|C#~Hi)M3ywO~Y%+(>k>?ubd z#8336T!T427`CWi{>v!XOy`a55+W#$qa?FNLD%;-N#*%SlR{_o%QNR#>3YgYcL2O`Kq&vdabd<~;w$o$P?_h|^o7B@XRi!$K$a-q^d6$1LmtW$S z-@8DS-@e=ouh(PQ@)K_HvD8`Hib-o>v>Obo%5gsJQs!e7tdnei!$~C=6WZDggTj~z zl;$S#w33!j=F;;fb1IWr>X+}2&nNRNB!t;yZT%uS9Eeow1E0fBMXvS1f5PvcCj8mq zKkq`9aiQ=eJvgM$42fk~E%t14(TPc-=;gP)-S>hu`zMWHO&}b5{uuf@H(-9-*@iKt z_Ppt%!yDD{GMrblmXDoTCk^yz{)tYhv=Oe)&ghe&vrsj1_qor*1QoxZXOFsJJ;C zYX&(zpMp^Du%CC?Gr4S=Uw&>#*~@cfUrv%}T5)metcytaFWAe-lHX(EuDGe^`S(%0 z;e3m?rdwdTGYRAUvUgm5^=DHrp5o88ed0#(B0FU~2jvw(%(Ld0y!%#7T2Uk>dJmAm zP8vxWw^)>$j088=WW(jP?++hr6uZDLZzh_Djo80GZ**Q}!34jQWk3gC2mBwkP@e%T zeBZ=P0b>}R7YkD7xvPVbaIA(J-8Tj^{8!GqI5h@i@6H#gxM9@qId61|`f|U#ZY1@q zpl>j7CF+-z4x|17j0QWleZb--`sLHaj-kvl4;FU@NB4&?YXTLw^}LIt!_U?63t|7{ z71!>^!IgIS6y`hH{gwcl3JW)~WHMD2a|>(wekvUqd%v*b;qxx?Q`sV4Czk0N=89aM zU*tEh%N1$SMQV3UB#aqgtp8}9D`xoM)q38P&5F|;rN%FBC(3eTd;rQr4PNPWW3etD zJA`6KjuXSj_{4daUmU-P{qZ{%o_E$H95~hSi=)y_q$_s_Ehi<^mTuM(TR}F@{Fzb$ zjuKOI^}GkYUDxxwW+d=P$VLKHn9qg|uVd4B6IvXL5&2Sj(ZUp`AKcEg&Y`MlBHLMY(pd6OCz z<9e#Y1hb(F`cn&c6BWao{v$Mc?}AVVdj7i~LcRRF3k&doR}hu=4k;~(5sgbrPGZ%E z?t>}K29z|+K86r?H&&C|Jg2!9en27$qw*bUk6e}evQ_?H5m7UQ2>*E#F`L*PhtKJr zAw-D5VetTJiSb}5xS-h2-g7-544*F4oHqei5CJqEr8=Bw@SR*RZDm=2LaR_{Z)Qj3 zd1Jkls|DZJ*GOt$q=>$N+&z)?aR%$_d0L2;4pT>6k?2zxwy*-*-t0Ex{rC?6{!E{O1s=sY46$`cjud97B6bA8-E05?(p?)S}Zfe@jtx+ z6`L0GL4kKquecFda>rUS(`hGkSU`UDzUdV&9m!0u@QdlLI0lTnP!I;9Aw^^ciwLMjT*N)!_ILzio0wCXYmdsm(;g@r zbVWqK7fl~rSuVa%3x!}4`)|1=QRYi^TD&_~ytjA}-B)DtEp9ihqcaZq|8n`=I=^d3 zepfDk=aDf+tZ5I+4tb;9M)nAW32c0R1;mhzktxYcGi9ckB_`}x3G#_Kwa`Z(0)n;n zJv5<4MEmp1*a}q#k*SZ(hm3bO)D0D<1}xoV#pyfj&B4Z!I|-+_U2*h{GI9VBTVf+(CZ#VnA-jCz2!h?-QY)01ybu*8|laH zy;NJ&+fn+P^-i-d)o&vSus(1bQc*3n%VubM=baNu)W%w%DR3;Vi7IqJpCLje)wZ#h zl9$9w{79~&WVMW*DOk){7j-RG$98fet>q~nzxVIqzZm!Jk(5G?X$8AraVDVd$Qg-# zWA?tw4$ndBj++~yAu3bvRIR=yb$0CiS75V43AT$pe0zaG>=C;GAb#~N0-Wd+ zFDG9aaUULL5T^9O#MROKy;X}76EUmkQ}xA`8_>knrF+M6HB~>rdW;P}gUhpxPSCaP z6$cU*ww!V`U)pl&zWXm~IW+|>gC5C@AC5n( z@6}#DZV~Y=ZoR}d5Y(mH@CuraHe*hq#*6L3TZjeJ+qaUAXaUI6(OvK`;umza)m=JJ zJ3l!lb+({4RC{moir9u6{2XKmQU*~je?Jw*NM<~BhuYQLc%h_|u@Y1zl`YCN6z&mQ z(3LH5Jh-2j-v!i-rgzSV;VfW33$g(DG&H%_XD~3e*hb<+r>$ zFX=dWh*=Xr2D(A8%^U?wX#o$#+}Y)q9x8_p!cL*>P@*qh8NprQ~ zLcDUC&!3sVBtx&gBaF1;BK-pz*UXve=*d}K_apX0U<-BPe$O%N)?mayiyeynfW?gu zrrpJcM!5D-VCt5FHRG*2CdN&W98w{G=pC<=gmp&|>4|AdPc+hWS@oz{3ac|TQhEzynT1Q!BC&5*)i)+Xs+(;>L$*>|Gi^; z)^sn1sU5!a&6lJJs1q0BberIuG8lTkL}+UM5j4cR9uJ}U0JY%louCL;EFJ?@*01jb zVZeo*EZBmanP)UxTVF#7Lx(C;@*|KHr#K@5rj)vTck-$Bc@s=fa3{rSvbY`-d&stX zwC7tRa+g@syAT;Kj8&uFI0EU0SC+@a!1O+9pv%?{f?!et1Tq2~qs*>5ct^h5Ua-FBp`6YGOIa&TY~po14<16E9M3HF!opexU?A5LYDNuWn|_{2FIr?Qt-?>wXlw%ki-a4ZM2Nlyn(Nq(IVT5FNe z_SzKMxENb{oO=Fi`p)qR^%j$V=2%9K)Y#+tJ*LA79rnG;aH(B&Inb>tDz;3Al{$RC zZz#&``hB$uQ@ebz3LT|sO3@8#;NK8>EBY0hqbG-=dqls_8W!)G z9h!9b%wH7Bgbv+0eB(V8zd?mWX|0Yrs`<4|h1g(0_q(ILu|*Y^z}$gOuUfPH3qCZw z4eO4%R*%SgY2vAxf!Y|1#(7{E@Ht*uu9!x#v5w1CxLk)W9UgFTJWQ+OLde}hp$XhAT2tq(&2kfomGePR7faHI%?Y`3S_wsT{?W_ zVimtthx2u4Rw1F-evtx+>CmCWqYf4Shz=L((5=G*7jinGXjVUCRXY6rM3r@u4(oLI zUb%|jtixFaEY+b?hp$Xf@vS;+(qWYf2}S<}3gi_XZqwl=9VT>Gr$dVl z<#^6VD4th8V~^`_gASMKuvUi-9roE&ksT@|6q|HZLWi?-=+fcyR+VM54*fcOe_W1= zt?FlNp$>gIe7{U(-KxVSI<)EV8A}dDO#O_xb@)oD%DPR5OLb`1;hqweo=_oG#HFM9 z#;T;vI`r$%pu=rumA+Jmb`=teUBxPCwGJzEc&JFluh(Id4(&QTFox3!#lz}nY?cm9 zI^1T`rRvbG!w#d0U#>!~h>ki`sFF77ut|sS7pVBHI$WYd-E#@WEFI<3p+ko@9d;OW zsXFxQFb_qQjyf`+l-rX!%<6{?`hAHGZ942Y%kV_Sa`iLj)}dL4+s`PJ%XR3~;eq#6 ze6tD(MYWE~mMZ_MK(^{I8{eYet91DL(>W?0RX=k&^I`oyPlqNQzH&;T&Z5)|AS#B} z^~qtN=b~z}f+H03bry>b`}8oCE$t2c{^+pyP3oOc)aj%u6_Uk|5s-%O#fvb2>x)L) zSGan1o*Eb!H1F8HP@@c=qN>B7F=&6p3kJ&PKRGr304R@7m`1X*BX?rhk8JWst$!ZQA#NAAj{#B^M{6$#2Z7$K-Z#>n&Q<%B<= z@lVhb9OJ9x-k5x9%N1vR$L#)-oMlt>(eg8uAIRhyHriVnhQL8 zV^P?UZF8(RzescY@+~#sB(67zScUmh!W6iQ(qY<_avT9FKMnzdN0%gf4C`*9S%_!YDlP*q7D2qm*EE)x65l~h$%IX}->eQJrqoN!$0_B)dP>uo08b(=@ zLs^qLV;U8uX#`5sC@66ci@TOl*5**wrp_2gMQI#?(l`oABT)Jnr7wrlmpW58D$2qU zC<{kHSqPMMjIu6=vMzO|U{sU^BTyEMg0cW8n;2zN4rNpN*vdO{x@~+`w`KK^J*(O# z=HbIF#hr-~=r%gAJgb+EvCU$HbRc||XD^iI%2Hgs+hB#O&kuz+%k3wH$Lf%D*F27! zM;eRa$^bW?6#jTSA?l;i&&E<+!EPYZ-1V;9?vXqi)A= z)Pt6B+%oDn3|9=e=ZV_HT{+Fr zTg><1VgOu*L#H&9(jOQ7sh~eq^aq#0EOYzlPZRy|)1UeDX9@j@(Vyk?XEohHO7u{u z2EeUc9KF-lEebd5@NpeJsl)9$?9kyJ9num$n8|!@1UAi^Py$wo`xNS(7jlw z!-cxaYqB1Z$cw87FtB6Jo;qvH-hUOFPib_>FPCOQnmS$KrZn=ti2osD3U{~bq zi3POsf)$lpK^e{y(+BE29U)#g!)^TVvmKzi=UZvZOFK+?F zMGF^ED&0OZ(I3Lunxu zdJZDAAs01|@m+D7_?*c*g+5M@GYO34e-A~&i>0t1RjiOW>fURGH-3GXzP26h| zT;#M&?Km-o3e7Z+Ga)*&Y@8XFsv&q!z4Z%^_th2qY4QNcADlm!(E7L+&Ma=Ei%^3m z?|zWKGg(9{+1{P2N>?D}-Z9vBvPj*pHbg{0r>U(#_m7F_8yBFDjLY_q5$vY+Kv~#h zfwSt-SSZwy~+ zC&?pvLTv?)w_h`yH7 zN0JNa`;5P8`CUiLUt5xM+wLq}kIhB;)mM8YX=&Sc#hzQAv$l?dAk-d9j%(Z3^5&72 zQ=6@=?}NPBKP4x$oPHM3sHFRB?G9_}{~&rht=A|z`dd!-C2R6anT%3&39hy~jrwPK zZC|qFiUYU4Y;FA>VW6cNh2i~{)BVZY^D*2wh~b(+49>PYO&Zzw+Wuq_AxnIG`$IgN{`z;c-K378Vhq3*Wq~ zz)-pqpCQY^^<6s%C-^f6{K*MhG7n~Z9t_IAT6}|Sg?39zOY^=XPvm1;oexQ$S8xCj zQ|aH}uh>DkaGN5&+t=1;ZiOO0hzKB@TwAchRQrjQ=GrH$=~oD}(Il*cR8MBAt?3sj z2`U`aJ7#!y-s@;ythpF4FvRBF(BE#l!LxT-TNA#oVomP@%6hUxdRP3I%0>sX z`BL5bI&JF%*X3`jQJ3VaM)2pcW02*}k+GL^wV>Z-;Cn9P7UPE8S1P6%RO@vcHsE)* zEeFxeA2rrJYV4tFM#J!EUTofw1zJoIF+=Gz8^-+WYuDUAS?jYtvBFFm>-LD;^jEek zh__l}vAW>}4jP*wg^xlCNr!@J9n-91G$`vUj8C4u8FQw|lxf0!2CN(5cVUYJ_tMgZ z0WhcN>Y~Ayi2Q}}6^$?#v_A@is0|+%jd*_%y*IRjMlJCI#>u*|M>!AwxJ66qOlp@C-ZRp3xn~n%}2U}}|lsDT)XSCSWVDcLG;SJFKi zVZ)Y2sNy+!vq0oBP3oE&Tu?)IqO$x&B(|{9<_3~NAk7=`scp@;HV`zy zEM1Os+}CBx!ZkhbETT^xBuWPN;Y4Md7F9(z=Y(5SCEPKuA0PDd-qh{ zZ^-i-x}uO|EFiI^3FKr=7p_l;5L%-6UNnjUZM0*6e7B%FL)SW&b~o5dk&QOGLHRoP z>bPiTkx6|gLW?_t(N+@zgHpa?3tb_H`wL|=Vo{{%8+w%zkd+ZgZ1Xk}I{XO$_%fys zbFksSgAxJ$+h|C$kfCeG%njdaHp8|-8L57w^})N)S#hI?qaF7f55R+lR!kqf-_Z=Y zxc34uf)4b6IW*|fkc%4IGmQ>ek8G;Pv>*kyQ#mjJaMG7QV9KJ1$4aOOIuY6W7y!_7 zaLxNh4@H`YPl=BcZYUwXc+5fjMCtA{S0ISY5}Ez-pDGwdrV&^o$*EEKCLuy8By}Ch z+hr%c(`8L9-Hn>(yoEF(i`4+HqtzyiF8MA#;Dh4zYhG>bT-$K~r)r|YRMTjt(JA-i zohWW}X=Or+3l=sp#W%>59Gl?8R&2t|xObX0v7oF$BGNOr!j;Gn9~Q7C7G$_%v8(3RDAF? zU?FDWFK(+D!==A;vdvvqC6_!UB=7=3JYq( zbI)jZgM%t6zs~xTlu@xx$JgAW$X6zRuCg(_{13cDx*KpD3z@38r{kucZ0l%!ViV)= zQAIj!j}O?|!Jo^7A7AqziNl<7zz-p}VbfO!ebBH-?8G#Kx}s7#+|EjMFux-5Be-N0R08D;T9A#H zN4QjOf6_m=15kdI2mK-XCw0`#DyRy3#=)+HO|y%)?xc3PyGYkl`P#ZK%oz6hR<2P7KQ8&z? zX>erLxH7momM=TH5YxLgPDp2Jr^$FagP%Tv?%|T?sznQ`XLEB&i}j{dq9(h)I4VQv zt$b4*?OY{2vYUvXi%b0PbK+~?_*i^R0q*|sn5K(k{%FP_;{b`uUHHp- znac3KyT&UPTg2|konk`j)>^oM8^P-(&L1yMQYyuLiAAxp7FIJw&o%nVkLl+uMq|y00wOWQUvKmg;KFG_m}Ro z<2tI5cd(9!XxKQMBrUJ%2(SU`~OMNMvd{{woAzi+i#+@lVekJLKVTWJ7ayf=M zC=dn?Vo;J7AO@t9USd>YF5Q}H0TK*>_(aWHd}b6($f9^?1d113!%(au_S^#h7a`)Y zG$?;=&9W#mG~uPZ1ZGvYh&u5(2KeWeq45-3=TW7oXQRI@;o{8TfEuT=gmVRtb0UU1 zzJ68J2owb5uSQ9z19`M55;xHPKpl;xYN8k$qDhJT1*QoYhZ9TxqUxBB7X&tgK!7^| zjw>qZ6H)v<^N%7M*iN?ug1D2^W_h}p8d=G&U`CZgGHE2BmhDIymAv$3B~yt6I0^qx zAt{S$bW-$+kBG6@ERN4Y++l`2i4XBYc`P?$3!54$`!9Jh)QpECUqCrEN&h# z{+qNldnWYjC+xmmj@aUdyAF88Hko zv^^C2Qn%ydyzG*Ze`PN@I=T^nncKizoDSmo02(2)YB&z?g6SPkN-=8$!PE)h>kn`y z!Wzm1%*kSWWMYP;*GQ@`0EGyX3MP>#$y!o|0iRZggb#vf!XX5*4ItELHQck8Fw))z zDhz$+ssYq6ZWISnNABz~GNxp_+hB%BA|RNQ`A) zADIP_C=`V`UKo^H$r?+zeeuFf5%`Y_QofBCQYCR?0Ml71=yjg^nz%8TX)w!Xg2$a0 zxNQO8H{y!7wQo~v%1=QGp11qm#%g)B9Gv$>f1t2l}kX+sL3nq>9 zD_c<==H}i!qjJF0mOvv!LgCIo8RdVX#^^!d;lgkf;^`*t;Ywy3KJSv(1SH+jfV>;f z9tBzNYj zlSeeklG0hmeb8Fg&>fz`P*`mjm3FblqWYN0mtccDEhl*jkvxHTClOEWGE|~Kygd?- zkCMA2fv;49p+#!ylIOMIFJhrz@C9{UK&1DpQPfZg6SsxLYrGuDMGfH-(C#+XWM|x} z0U$T5GjsdZa36kxxUNUiwMR{mIJ&#sf)IZHZEh!i&=NTUG}mU-5p5NMWJgr>x)XFA zx{~X)yC^%3|2+z@5a6g82RShcDDeZ0X@Ur5){)3(zOey+Mfu~vuw^Tu0f?);)<)R0 ztUDkOBMZeK6k7WhX)_e+pn>Td8wu5vCl$@y88w?m75XRTYJ##0@P*O{)jW+R>O^C?3=xr-rRuZRK6R#K|ck3PoaPr@BqNky9 zy$0vSQ5pW~mfCUwF>6J2l$hm-SXr(OQaGiL+SnjvKg12^P+@hUOmQY^5;v_!PIP&v zQh9t^Iz9!O7?u~6!0amsi)}r=Pjb@+;@&&PP^5kC*c|_dXApj*#W=>==<2;j87;O!?jsg`iR-SFm<#4@+gw`tu4&zCUH2bo z8vL#nH?5{>>3;1PWP0)e&F2P44KziqSmiSA53#}(H#KW&K@>h=kYzSLiGi+eCVQ}bj>%7RQy<$}Sl_ayuf$JL{)~dK00LJ+0Rk&r=o;kqOw-rXwsZg>3JUm^ zfA9o^I0r^2fJzGZd4C7&gDty|lSuc2E({Cw;Q%-1ga&mCB^~^>!%F)ac7woz*|JCH zL=Fd4S`8|w^vImL((^f|earK?GBJ26YR_4wqV`hGiPOjjkhMMH4B*=8AU*@F?RO_8 zaXgF3b#Hu+N!cO^d;kUl#@EhcMImQ246#P2?&#Iq-$SF|!_sI|9zTi&b}{EW{l$f# zR*vV-8<=^GDbFSmH>Xdmyf} zn9FMrfxI+XcLZqS6NJqM);h6`u-|0e)G0c%CIJ-*$}Ps7um;MI$<(yo+}UHEkvz#D zOs|^1JPE9rzdZk_?|(Uep_#%n++#+aq)Z)slJX^v#$;&dBxU;G+$DdKG7G~qxXmq< zH`DnkJRls+-!!8+P=F^}m<0V?-P(h>OA%@^2~Q245S}=mwRj%Jla=VKKei@b2#+-u zF%~&!Lr^U`9%gPc)aFC8f@$5xi4tL#@vo0#*&>P;y!Q$6PJ?Cr(EDWKC*> zi^LTWiAACl_AO0WhWyj^6B|x0wwNBtH=v%PgQ|QJj~!RuoJBb>Yf2JVhhqi+>(bcd zok9`Gn;=g%9}HXsD}J^14+Cgb=m?tLVGJbG7$1UwpBJwCyTnd>L7UAlm;x9FhMN*h zXU<;5>LJg6cw~jEq*a;q8a`; zN@yh6sDo^XSw%LqKkUMA`~t)^QMW{mYiXWy8SzC}@m+B!e`d}s7eG08!XPN7ZSSwS;C(jL_i6&R^l4&c=@-oh8AEqq9W%%R~hXysBH%*g8u zK`P1i4U+{`rC=FPS3V@o1hH2S^LJ#428HENKt?DR`2>(_u=M0UK!pJy<{dJ%n-1}Q zLn?j_?hvtgO|K{a9l|~xGrb?>m^WOWN!LNsjR%i--mu>6%hPrnH?BQugPhWQfuxxmZ9cubwOj!DjzI(-Z&3vcP@I)$S}o!$4^az47T8cO?oG57MjvBj%VEOe|W!ZOo-LavMIDJdCZU(K0UD2l*Z>KF(RKb2>-n{3_+aUaCD1P^WcW4HPwg+3|IYZSD#pWP_q- zA-mCC;whR72#eSFTQP-fe6T=FH$~}FMfF(y;RXg5_{LFiA^DJ)LIyoHbI-(rWtL^~ zP9lwj0i-c31kswln+(V)WXsc3JK8=SwmkC_4Ps4Cr_?es;YB&w@LR}q$9-Y0_Ue+b zu@83~y<-&nBjTlafxbfv_$zU%Ll9SXO?R}-bYP2h4eIogbQc_Wv!t`*ND)+R744mY zPO+xnB0bIqJdsPEYjcr3jjBC?c?MLPCKvEdg8wn#0q5yx-3y*Mtm|F|Z>XcA>tf5N zOWEYcQ$*KXlemx&0$WgCLJEWC-z+bi+)W^gga$YlF%4vvW1m%&#vG;rnXXnez}6?G zY;xCeCMM)s&O~OpW&&|VycE`Kh=2$2g5yu1z)H$VJkVAC6Rp^RcG5Uxvd_$!><&_t z)(37Nro&hTkTbQj!iN#aF7Rn6pdg9yK0xwgePBQE@fh3nS6C##-v=VEW zU*Z)jTFxOl%tSr`?efUed^{@ywM6j?7ekoo>$kxWl3=h`e$gD&Jvc)cYw&TTkKa6u4 zAasV^Y}@9Hb7aByn8imNFz+LDHx4%JbVCNA;N!HTou|Ik?|0cmADllmhD2#jGVjDW z1(SNGeIA(TJVoKNEm&G@r!LTO{QHS9aKs+M3bQr+e~{)m0Vh(tkX)~jCM*r!$LOFZ zDZ6I?U$RFVqEs3+sZ@M1YDEN9vSEMm99n$%LviVDe8Jfy`c@nymv7`8xCVYnHGp}K z>Z=-QjQ^{8phJ=}wC$)sE9KQgfBQOh#F(4bZ8x~!Om8?2mXSC28MhPf>47zKF+w`< z69c3j!P)_W+d+R;6^fIru$a5KZ)fgKR>I?=YtM%SP=D|4WYy%e>4E#7&9Qy$E<(}; zz2QQL=Fil809DzNK`rrRbZWCHqfiU~z1oVLJ`qiRN=)g)hECQi z(1CW=T#yyMFist?koTcfXqHOa_kd#%nTwNT$+)Rt137)brY?QK9Ocl5egCR&E$TS{ z5DYl>`1ROPwwxxtGKIy}V@Z?vQ*S|i9s%(}E>WBbfFAYpZ0;w%hob1HB06kL2Ys%b z=_p|5Qx~#{v+7%#!>I6d@a;*@u181G$N_Ou+$vt@SMl6jMelPA$ETNdWzGQv5q>mB zy8?~1(hi7ozyliRSk>MLo)}}DH&d9$O6sx1%A8c@%t&y1sJ|K2w&c!72V^ro0-kd| zLmrV#3(uw;2wp5XXEjm>4_61|?ZYx7Kj}G36w9vqo3rS(-P9lGBed_K0jc>q?wbTe zc3j_${3ISO#KKA2U5I5m>`>zA0q>MZWOGC#i3?>FL1Wa?&;^(n16p2$08u>o$W9xm zYwh$!Whi;zOJG1Yp@rvky*ucO2cEG|HUs&6P_E{YwbpAt=U zNDz0B50o3Am7v=!LHvH7OR(|oSR#NjGf$PbCASqK+d#x?6dzNBeT$cPQ}}4xEc&e4 zPkm@r?TFz$VO@NdqH8-T#4bL}(8cbZuI=z2Ach?;+len-+liN5>=)|VuC{Y^V4G$a zp1pWp#Pdr$hw;3L=WRU4@Epf;3eNx@roa3VoSVZXqL_q~6EB}yjd$-22@7sBHPLOP z7Dr9ZLt__GXuxR&zBT9&mY{^+7+Kv*12{k}hS)QOMcVWW&^ z03R;JeU)UNrT>FEZN0_YjZZ6DTU~gAsl4v%sF#?unNIW7+r3@)7K%fjz1ZzPQ`DP< zO_+X8R_b9q-+JBI9*bcQ`p(Hay?yr=V)qsxuqr(>~{ zi714wU&B^@1Jl{u?@7ccNQs;J!f%h9*flk2$&^!Mri|Ff872{5bBXklDI+dxxnCgu z=dLc|Pm-+Kq5J`NZhDGq0V+na>7v2vqoEvV*GBRgIg+ms9}2(+Y@p0CLA0=e%}n?X zHxS&*tG?268g}~1tJ|CUn4Xu9&}D)^lC8Y!*JB<>lcCzK-ix2Ifj!Kt$$JQHkWFZA z;)-tap8<@j^lvl=7+NK6@=uJY3$Yp8a=ZaG@xN&P;oxBBATL-HzB9Tgr0*Z(!Z$&s z76n=a=;_0Jfv7%8^N*oaqamiAyCFc04aD1+_;WVOo>3a*#F{KQNeh#*YF*DgD4xWU zH(io&us)QBTR|<94k@MA9lst!g5>FV{i4Bcn^jE;-kTZ(YA%>ZR+g9t^?F&yBPLEhX*8w=mWU)sER2SQL&Nyd#oW_TJ~cc1(@{#4-) z#)v=GQQfd-H%*;s`4M?z=l02I(#k{3g0XiVKl>M7;#i7*^oL_h|kKNDoECPBc|ld z-f>BfcMVp){V)+u6`~GKoY^9G!PUIw$j1A|aNDp29v>9Wcdaex>Ta-*n&7y_>Ai~< z&;>483aI=D410JUdBmx}9u4dk&P)_Om)M7Ob`(!E6ejWAuEo5G*031M0+Lla*ny`5%BEJj5$WZIR-I(*B(J=PYCw7WeG$G$YAF*I0 zi2qYLfzyCqtQs2oG&fI$Dk~?bEf%mBpKFIaaB0krMJEqjMKY1C@Y+cX^V zw>on5x9j>hN7Sb~z{111xM^Uah2ce+4Fp3lBGSU5NLx{_Bo1$(4}OR;dp7wPG#z>i zlCdiu<0^NbEU$=wE%f=2Z26zhm4))@iy|msK`u^}&z~C^ET8=OssL3!e}`nS{Lki+ z&ryDWTs!QTrd^y^F^T4_zt2XMTo4*3)xjO3DxoiM6Z*Z9Ri)`Jv~D z6#gleP6h2vkHm5UV=Ruj3{;gQ+!TaPSAGnNZ|Y&tb=@s zF!M7YISxngA-VQu^pu3VDI&Xn3GBF!0}knky!8MPilz8S(kajEBidI{=_W26RX}Me zEVRfskw(=%oEXQ}Zd6_m+zf!Z2Z2C7(cV1YpabOnY}A_b%{?{0fGzCl_>`DhME6{h z^$aAs28wW7YGBREB6a;|>`AJ@urpq(QUgWZ4P*uzGPkX|XTbg6j!qx0syAPO7}eNY z<^%C!tXV^8;vg8BcS-_h9#zMWsM%^}j%{iHT^I52LfVwv5htwKvtJ;TxGguSR#vES zUM~NY7pm2ovzWxLD&^rSC&!p^&lXyiH$EpO(E`5lMKOgug~olxJ^V>?<8$qc9L5*h z*VsFIurpwexx0a^BSUvF|0NfWLB5JV;1Q(5rgaz^LQ%#@LULOc!kV5>2H@@LyU@4< zq9{tMHn^vr{;E9fcNmU6>od&l@EK0)uoS<;c$VT}MW_F;9NDo_LpvYfaY$4}Ws{$% zp}~Xp$YRrCzw+}xroJKZC`1dLH@o>1J=>yBvv(I#ZE~_oMUcEivx>f-unr_HBmJhP zW_f$EGPkL78H=$=WN^nJ^5cG0S``BOFl6IQp2N5g=lVoBEgQk1YJ4eu0;d#KUM>92 zr~(zMv1DvCo=WXBXI7h0fiLwNUz~$X$=?p2K=KYKQO0W!<&Od*mhB#=_Upt@N8xVc z&M00`WfHZi*g``7dokq^`uotMsIT#?xQRUb6p~w?LRtr)aJ>#`xxskW`k$xafHd}F z)9@F~D{m2NA4hiaXMDdzl#| z>akb)UumBqJRsDEtHf+m23I$P4VejKM(@M%6W;dwC5Seb&EMGN!|l(u_AlF>qN6*< zA6vXgbkT9MGU``gpSRQcKpe25Q-Nupjq-sokO-S$i~@A>OYJkUpT6fh0?ReoXVQu|A zajBj@eP+u{cZx9XSrK+JZuhaAu*SLEUvMZJ>eoIKBjo*PFE+=l=`|RbL-H$72WUR( z#b}BtT|UDMJc0kq-uuT#SzY<#lgtnX7pXZ)??z!ild+xdTJcdsjK3)u4B|hdH9#OXQ$gPv`zy5pePrc*m}fO;h3CmNuJ` zZrSk=7ecJ2rR8%eum!UbT^WfXupAJ7r?-)1aK2P1w%_n>X;IDND#K%mL znZW)03LcZUXP>QmzgmNX6FetSF7KHDY6B(YCpMgQhnJN|XQ)rH8)xp(UJ{Z{4TLu(9^QY_6HR!B#s|*? zQDvi@)V}01I9QPV-^^&Qb+*jH(}JJNE{6+%ek(m=r?xT~Ct4Vs!x*I45mIL-$EX1X zA1S`@qT;`ekL2O_NcjqU7`!<+fExmT0=&>uWp?NCbcS=L12Hjkwm{}A*Ip*aO=mp` zM=mVZ!j2caAX=_~>2j-}YGEd<18GhkwAh5SWt#ZnJK!6)VU7|0> zeRVt})Q(qt^58fCOSiqU7LEmG7C|RXt_XX9Y1yY&T^%ZNYHw;c)nNHh?>nL0v|Iw8 zsSgaO51do8;HzhD;84ucrSlW0DR6m~XSCP{JFWXc2AgymF%+Y*qhZ|N2Lf}!H|q6# zm^6tHQ-IV87n}y6CWo58D~xZvi?iz(5;Asj`6+Oz^Y@*==QBj<5mop#APOy0Xp!~Q zS96~v(8RsYD73T-uTaXsX2YqW7=#=ZkjZ*xP_S9N@7g|tAMQ%`AG zEvVp3FA%k}v>ZKq?`;rpi3?UWYW|JkjP_aTq8pECm9Fe})%>rYz~?ihB59!R)wod@ z!U3wr8)LW~#p5q!{fD41fCutl?LdoZO$U_q2N>t~dNbZxC`M*@SoUtV3zzWP7s!;Z z5jSZIOM}}J%6#z7lrr&b_}M-SoTUUt2e%I*#vObMR%Ae*H_f^on4WB|b@BoW_5#jM z9EYBPsvJ1=dh707ZOiUoMOPGQc$o<_NDAC%eFI11E>IvIr>eCf3J#?frGW5KKEn@u z4e;oEHrn$dkYe4#>=Pr|Zz0B2_p0=WDemky>V6x=2OA&4htOlF0B>zi_`Pg;oS>P`2YKe_r!_-S)b;qk4@AyaKzM&!10OwfdGjY-ks00F*9D+W2 zo`Op`*sI}8#8~fmLv)V^@A8Wy9_3Qmy(_QK&T2R+&;Nqk-WXvX zTv{%J;3#hb)_|cULtWvC+@ckG$_;i#vqN5@tMC{M4c&2NhgV&ut#xaAQoEtV$r<*r z9C(#G{QgP7X_P9=ejE6qsb}dQ%n?kW`oV>Lvx7x{`{&9Ja5(%>Ne2J4A7JeA&(lQ)V0qljcR$h4j zN7iqG1N%%gHZXi(s@#MTa14Rj6LOpp$sA)MP*%86wkv<%fWTPn2AEy>yUxk40(TL* z|1&`vp9V1zZxFR{ehgzyEZGwKP`=xp6ig^!( z9aI>h{7VxSg78ux--ee02yg(1r$ZI*zF>noo9b^G26=Ya+%vRtJeCt$bD5Y{;jtW< zi26Qe0d@*p@OIe7<2o7ofO6#bf8Y7&6%eU0v13udvN8ldal}|JD>i5I)u`E(x+A+I zFhx8=z!}jb8&=4msAF|!ZWmtvjmoD{?}8lJj8mugPoH9Q@&7}f5KL( zxlWXYzb}LV$tR#7PeJyHy7$mQo*sj%y{j{FQC!0{T-EJG!v+xcDWVeah#n>5IMkPJs`|a;fGsud|DblD-(l)*Vu~6+2Q4SgIDzy zL&4q+K;UN&#vEXiCv_Qrn7uwp997z|4nIcuFl4KTI6r->mcJEiF}w=NTC6lai8NpM zr$P;2U`z|hydgYv#tQo?Ji3Pql?^c08&sl9i=tHwJ6f9@nL+&o*{5NApsq+Zg{!lx z;4(~mt);?~x*KcfepHvyZvB$tc=SO{3@dJCdPaxTMR0^-2=f6R(#THXs0`5UI-6FF zWTmMc(HN&rZ9QdvF8IJhXw7dd@bySJ_{+QRzB`FDrmF{WU@q=R9MY0QYSYP#OhA%M~s#gPkU5{@Sman|CbS=>Hh*54od=L+0=a?u^X-3W#-}_IWyXu^KbNM zKVm6#*9*~=2LmZ`D``L)@yNyD;IR~tPMW};(O!w885^d~eD8E#6~ZMZ+)vT@RdsM1 z{d1Y`*=dViA^6hyFC434NdceIA0I&T$yhLe#!viL2hR`I(Tf|{-ff+s;si+F;qeio zlv9f-h*z&2hDx54;m{`a(`uXyTK86A!Wc@BD3=<7&mHn%tM|7Zzy7!2yYJ}#0DM5L zQ$|#m1DbYfYtuso>HbTQ;KF_&JSN%-?kquygOj}PRJakN~V(v{+Q{}+BO~9`0%M+Amc`FKEf1WUh*zNC{%-O z{SIt#cMqQK#o1#K;$Y2?JEgt!YI@|Xu^{3}Rqm0iPtdv^$0qZcqYQtF;3hp6pJaqH z@$ur5hEIz6`&MKfoRn^g21?`SI151YKGa8Y&MZ_jF+1|H{c>W$? z{#EZxF^kG_}`F5Ou?gz(zuEh&agwgsM(e&&%rPOGX67Y8u&_MxPzrZ z?l9R5e>=AHR+f%=(Y4`B1BxlZZH#a9E=J>E!q>%S37#3NG;KzD?AxzsGdO_wpAXds z6M;0!5{CU8pD>iwH2Q?$t4XNi)fkuqYdOAz_W@J`-O2`y)#P<7ku2Sd=gt)BvIUeJ6rD6IEKN3;ymhO9!?BNH>A(zk$HK5CaVE ziQ32xrjdJ7?xB*gP6K0b zIetej(<+nY9xax12r)~Y1GBPsD@{|7HiY<5!83O$kA4ktAQHgt3;$v)(xLZd zN+c`{f5!Gcm5Bo~@;-FSnj~#s(}9&YN9$3VCP>cO_?*gyNr--k)Us|2LYPzD$YTDD zmh{gF{+S3(fE)ABMg&^&coBHCwsj0{0h|ERq+^gwOQO9A<=anW-)mFWMUcGOp6yiD zA7;i9Pvg#J*pUZDsF%R|%1|5gbu7aUX;ofmQT-`>=pZP~-{;Ym?n@5YCOuK=(JhdKo+F-dj`ejyN zN>iW_z%ZnCOgV|xO3;{hI$Bcm$K#rY>L33ZI){$6fY)G@nitsz`bXM8C!6Bvk`;V@rW8fqIHkdy5-K#XciX0|4 zC`W72A^9_l#!?U{$19Y>S<*;5O#_|M@tRGq;}{X$Z(h3V5IoL?g!agqfoT_{oxzYz z)hCX&sN+Oveg_B(T>FXkjIF^#2^m|}1P~f{5I)KMe5Dnd7$8(>{19}eJW6FjKezui zHF1N`fSiOMs*fMSMAUG+r!;;S@tYSTD>gC-E&Fax3V0e1;MLtc{wPQYj0<)o26rSj z9ta%D*cllyWoNK&5W4K|@V2(AG(1=Cr%F%5XxSIeB3IB^3!XCFa2%0f7lX>WzakVx zy??UdkQuwq@9dy*QE57e4CTH@k-M%NIm^WDEh=ywq|${lSC~`<5OZfni-!GQ=26|B^I=dIM8Bh zdn!I9rZ#_kN=$7z@hLI24MTNIZJzitF}0oh`d7jBLVQY0Z5!fKVrp9!pArLGZhT5i zZJzj)nA* zc64Q_)~g*2zwj!Ije1*>CLTpbT)zG?(4)PDj_95OWIGOj0d~C!cV$abK~B1|fwEan zmhvcVdGtC9o`f^Gg29}dwJ-cPF-wV&r7)T$w%M$qW>JLA?#9x7qB_L_zfv9~YJdq% zVP(X=hQ>W??komA+T4KN+%A6}n>&OgW+6RAd#QzMN0;K@FQpPa-2ux3$n73$xAay! zgNFC$cN&{^u2Gane}baH!5k$>*$U9)KL?FpW%v=6gMwVJ^aWwUk2NcS&U^HRp40|f zl7PHj?D(2*A)(^p5abU*P>?Y3sCEX0_5`M(^^p%<6&#$ma;hQTpjEe?*HvxpgkEXl zen=I#sb!8Tf9YexzsC5(pC`Sy%h^$jUAvqQHj$dNBxSR`wB|2Z<7v!*Y2kNhlV@sM z)yefR8b72Zwb(&f4&(Zg?)w0E$D!ra`ZHGsF3tTj@NZF*m^&_4rFIJpaW1XBQp^Q) z=kedn7=+^?n499fss2_AnxCv}PQdD&WhtSW8(7nz|++1++sR>6#&AnMOE#>Sj4>r?FYnJK1wI6b95t}hM7sHs2 z7q!tfYEUr3DcU34V_46eDs(5bQQ-EEXnO%)FxAKVBfc7+UP()#JGjlcDbYWsCf97n z-cm9uR5%LgcKB-2%~%Jvdd6*d>`h-L5}~Vr!zqBDh8z0O&|_ed4cK4@O?cO}A|J+R z0pvY5giD-+j4Z_^aX5j1&0q-(GAiKNf8s8Aq6Nyg_gU|hpFASACeW@DuBp2vCaEvKMd%Sb*2vz)ADjnXupsX#=K4+?yYR$-&m{c)~E zdn>fSnfhiath`4+oEBZL()0#0JhKt)6slI1%GFK-7ta2_AYSWA-Jvx84k4UrK8A** z{Q%$Oa%y7MYf}ez!R7wxh5HP zPt@y<%+y{B&32~lqFNMYzf zXC>IvVqEw>(-KOEjosBG*JyXGGf?txaM(>O}qBGT_g`8k?R1vV%CDu=#lo z?a|v#=x^c}%yw;Ao;}mj;%vF1W!KF;fvK2NlUsZVE!7F~dt9(RG1!#|hIKHb9Xm_? zZhD|x)DCjo3%kUM@^GPGhewur8$uNc{a=$du*t*YYO{InGe(>3B}v$I0)W+QGeLIt zp9ZuQVIMUD1KjQnJ~&1j+lsXQw~$g9UUd~xbX`Gw6PuANPiBDrVqKH-7Bi}66{w!9 zra~O7M6Zu`fiJ^Y1U@~OWi*QO?kP$oqkZ1*`3{WoVL`Aw%z>n;_MmI?9T*$g^H0S_zZ0W#0HcFle-$_WoiY3VgE9Mlf97V6scgun#*Zq>ag=3L zp)VLVs^hf^JBs1ENQbf@PStkH;l>5FtMIGlrrn=emfz8{5IAzGY&z9t z1r{xG$prP`$RGC%Ppz7Y6~)vyHQ)2E}osXJO!eOaoqqiom87pLlRsV{)hL4 z`-yyjH(2Y6wT0>OHsF@WNot?QmGe*0lQvwKBvsZ+m9PIbw#q~u5KJe_mNiI^t58ba zE2VxKQ|iLLb4byD_=ko`@nTQ8R+Eh5sQYdDMEdV(sNN>Ip0#78aVhKY#F^z~0ghPG zJ_mTeP?|Mn6|WC2#(SSS%*Tr zGc?nY-LaalLONfDXT`zPU7_XI!skG60Ct+_^~0kPsgDLGcqc>%cgBJ0mU5?hue(b1 zxr~h()A)$%6&X9LTNdzP6vAe?J>;0PL@jd3Zcfi*3ry83sM>!7<;{%xeoxPm6c|Hn zI6Dp}qAU&-jmn1dwEY?uxz(qS0Bm>F{-dhT9RtT^lGA@vX9=TvP(-4#Zalhsx3<92 zMoPUq8yo9EN@O>QtsVjSCR9&{UX&zKX3vwhpJ179TUur(1P1`hdi;dydmV2dSi`Bk zktQP_?+JqTjGT65-BKgxB76fS9#YN-(pXY8N$DT0EemWg?<{h*l#k5VSq17fP^vt? zKYk21a{~W2arYjrh|Z2v*1ds%?)FAF2WHaUCx79;M2`}V@p;ApL@7h~T zgkkRS`DLsJMqVyZX+B5=1Ex?bt_IUFWk%iIu*^I9p}`2l3RDdH{t0>ei}9yr)*zO*@}@yNT5()iFA_ccRO)~rom?e_!W^YMRC`RSrKbK7 zUJnLZGAqk*l<_^O8y!#@$wvD>MbVstGh_$+mM3Fn^8G#-@D$$m>sB^?f+oHej3aS)M+19HM{Wz$7-{h6kQU=Rz!|RmE%$k=?gvPKJKq=f4}rz=j7Sxt zFK9ndggv1bBK<04A%YG(G?8zAt)^pBZ}#3*DfL;|fvdDVV_x%*&VFn4rFe>V`^t&5 zW61I0zT$pagy5H(fmvrmwoh>etxN_Ewg_S;YQidDABMon|pR)S{nrIQO20oPcz)DFSK)5q9vT`2Kd1{$M0d^edz!O*wFB^Pbs&6(-nu zFz&ZyEzX6)Pac*;K5a6d>6Xm6na|8!_4i`=gv!x^y!s_Kt(8%*O#zRBRoLr*8nuBk z)P@=)u+EJP;L5Hq7xqJJ4P~QEjKi6#G~v{nXj{hGE8q^+Hlqq(q@oJG5)u=@hJ2xHSg*DC_^DJ`9GI%UA^a2~ua&xtFCx7M(ADqd~ zUvlJ$h=UTAHkrEy@yh`O)L)+CPiC-i2$-RpFppuCd2V%G z$~Yr?Is-u8x-k=&GOefX3+2D z6{VGUTI*@7E@+xF`zyO3?^Qd>p})35S#Jkafl-EF3?~n>Y%tbto(^Id@i_iqIhl{i zN~#m430Sf=k;VD1)aw6&@9gbu?M6@f}IQ2<9BC|APtGx@ftxXr=$Aa9OTH_#K~angFG?}-kn6h|*VTXk*=}I0mS^j#U(P|tKM`$B(h{-+D0qy^ zY_E;w;76J(7?+E#eCIh1L+|uyVGt9Wr9vdv^LkRHp_azdSpStJ-9YmP>Qh>lNXeRCu2sTA=dHAz$I8>UO9)qD6Y2I zA?=x0+&~Y~f5>|lSgNhQica!u7AVJjvQ$QekO(}ob#(9<5Z|=`%j4jZzt5)UdrXOjN`J=z(ZP)L7OQ);FhHu3WMe(Py|h2 zyXqOse>m~V&Bd7+oY-TrZ^<^$YCOS;H4VicNr5plR&jSh#&P(;mCEb>O;hN#LcJ6+ z2DbQ5JO$rW*;@mb&4!b~FRg*b%J75oyeFoS|H1w`OsS0HGiZ?zSOl;zkZ7@VN4*UY z4N~@Eo_4aeFM`ONF+E#PzD-1IpwPpa^e zwpNZCUIek`<|u|Hp$f2YcvpnIFi?n#!P?kf6fOnFQJ&~5D}`dw8~(;niNBJ=O*)7y z+5+4jqBv5Ti_PZ5p)Bppf`yB_a}8}14?dh_90HLD!7GhFVU`89E%%DIxGv$=#0X#% z=YOFWbMZ6AA#otdV34?)*UY65&eEFhBJ|B9MEq(j=dU6UgL4`5WscxMPnoY=cz6Fz zh{Ta!;4TxMJH%iQ6HsX!tHaaPK*l71#I|#msm*?)o#Xtaw<9J^Xos^m+Rh%mE?oS2 zse!N&gH5M+unKi$GG2nf603cIBiEEboj4%^-tlsV)u-j?lcY&UF^O2vP}A5cA5+zVr5uq#BI+N~FngE1U^q(M8vjW1a<+_ST$ z=TJ(UEszem2$o9a1&ZnYTadwk@@L~9Vd_RfLEBO2f*!AL5hj@lnd>_E)y9mdw zmkYW5$i-GeYnDkPH!4=$s7<2hB282_@C$`apj+ED?2rT}$zW!)t#9MDpm{Ae+EZiw z!nivzUM?Kc;U2WDva8;X(dR!(eUCZt1JNKAm%53Xp<7(g^zR65byPLXHb2+9x9R|;7 z*wld}k}Cm3t-Gt9vjaR@CyG}IBE;EE?Fh)5qo=8?<4dEDb?a$bs{~}Fu^L@#W&nGa3EE^3rM^J`7XxISkfD%lmQEEpdw(?^1y2tbH$^}QV8W7MCt0=mm%wD}63E&4csRQh^~u=Qnq=X>33XVz z`1-a}`Y{sEQ%idVcDxp@tXqxYiDJ5wx>fE_^-<_lS3u=EOjyMTfHH>L3yeY`ED8EJ zAsvez%xZxJdX6$=jp!hPUThTr`5Fu#oWT00;y5ki!0A)j-K(&7sp?do--}&_QK|}0 zwZ{A)0R)dZUQR*P8SSJ57q8ZPurPdi^4HJMeIJmZPjeq;`5h*en_g@jozFbWObczk zP^cNTVBo=vx=o56%BSg1AkDJT@vIl44mNlAdV}y|vO)2faSBg{omZaNsy@-mD(BgB zAA>TWd%iBz2aL!aWXzk+XTdCPxP!Gg8~Q}_n$z-)=y#gMI4|@c=~Wpy3r?GO2vr5z)D9V zHws>zCXdR-M&#QA$kGD9LKUN$;e>N6y&j@YoTza70Tg{C-IcsQm+I&r5g3W7?q7zg z3o=UquV5$RC2IJiOUUR(yU8?Uk>^iYQfFA-9xAnfrH~8$#c+Enc=*fxhsJcMs0J)S z-dfT@Stx^wsiZVc5m27G02Jp?rQFc5XK?Dkx?z2|{46F5wHuior2~BJGJ2ynJEVf!>*XW3gA+E+WBa8pu7T}|~ z(oRC6dq{m08fBo!8K^^shbuUc$PI4SpDcCOt;am@BEf}e<<@VY;VrimdCSd30#MeS zfl@s>qlHo?>MM>u;TgmEjrob`Nt;qOhBpSIuR6z0#^`)DoCZv$&8yctUY9=%fzW>d+0mN?y##^Vyf+o_Rts<99#zzFV>!2 z+H2>qvAtp_Y{+4Ler-H1#%^PdLU5M~I3?%IX&jbgQa5t|KOwyOD6Hhp_#C>ET4}_s zHtk^_=;gU;4^u??s8oBs1o1fDKJa9bYZl=6HFr3@r#oMOe^yk zlB`+&$@&b2jKz&(2#3*>H-0|N?7wj=45m3~=HW|*$g|GO<3C6WH(nz;!PkiDUPoSi zJ9|#%tQbsSa8CC|3DIny3O`3>#TEh={;3$6+L{im#-g<9>lNV;B18(~-UvCg+icly z*Il|gNgI{jT{oU`m~^Wvkt-z>IFO7u*iBe-u*QKZQYrLMURZ}Hgh^D!0JKXcs8P(r z?>|xZ#40YP5Oc;T#4a(!I#Gx{Ak~10`d`k1l~}+BfUyek8T#fYsk<_P@ySU!!GH4eX@5b6sXZ@Zj?3g`cuzwMSwF+Ccp|DB<>5IH8_=uvJ zd_g~x)6Gj@FODq}Y}npuX(ntft)lM2ai=5H4)vUNrsYh>$M%f3G}jaSozVkX_!rk; zJ01Q%Kry%u(53rV8yCzx!7t!{7z3sy6 zHdb8s%Zl)Ilg)9Dh0)7=1{s{a8sV|_7xOH4!^pls1O|5l+*lx5X7;bpS!qh|W;RSu zm8RE87}yUI0B-l#<7PrXM`_fMHpjncj?$FKlvbhuDj|6K`;?`8MOtpY=4uJ$G;42b zBZGfUzyjDA@+78i-G5fS0&wjg(8`MP)clrns5ZNqZI9!xb@#-#)MoMtwe=jKmJ!3X8K?t_@<*_6DQMF!M8XMca5rsoS0YFRqVxqk z5(-|dBxF)>HbTt3k9?~qfG|B;a~R?5{mS|`*_P;ow&WuSxdI)xVV7Z?gZ2P=a3eKM zAr&@WGIS2wgPcrRJ`(Z~&OsG92ko*22YC((C0E8f*cerBS;iJH8ES21wYKn&@C!2$ z^?z5}hc}wro$%m@V}iMMbuRIS4aXsZ08STjjSMx8e0D5nWodZaccmbDek&`-M$m@W zPk&D2qH^^yc1|zoQKffL0i-j2%Zp<%3bLmDG0JY_{vDKclx?{05C%Z`WXN8*EWAma zPT4OipTIs(-^)+OTSNc_mQlxfNlfQf@I>KlsVB$Ul!)gwuck!Dfs=z=F@8l6@ZL=D zURej;z0&Bg(s<1X^bJZsxM^XDTL7W6dA`k?TSzO{;9z1M4>zyT-Ft*_POGfG4Tl!3 zfAQu)wfICEzFy`c#j}w}J_d5fIx_CT{%ffWC;KLSwIfI5YoVg^ujB%-h(qg*Obitb zm?wC6X-B5C)C2+cn&2S?Edhc|<>7Zw7D~*6Xo-W+$~gpFj@MdK5K>z?75e{RfYARN z1f*F;)8CD*=B&W^2V3Mf8H2Pb@Jzx(Nq_I zPK>64v05TFcH$I3NbsA%t8oeBS4cxftyxBy5`428?Q5-EUb1&7O)_@6_NWXpOA5$3 zhdxnuiBL$X#n__4#1*az1_QTX4wFy9QE5xQE7dp;E4Fntm~utSJWB{Xant3WwbPQok9q3tB5;c^oXl8KI`XYJczx!bGvY zz}B+JaaD8c1%eLWznDvyaHKy3Q^F8TSc?X`))6LPMeqgvmD(&UAKNaqm25xzKa|FG zOorTr;gzShA)p?fYRvs1L^I{*?%ITYsyW%#%Ut16ZI;lEe$h#52C3pPjxcK1M9r>>I1$MLm1SnX6}Vh|Su(wB zW)kMAwi2R@Bsz&CZRjS&QoMA4DOklXZcTjPT^yLA5j}V zmV`w7vs!J&zi%S03ZHxNS%=SK_&klz^Z0DWXAeFH@rmH`1wNyahdxuU%(MIizuWNX z!pHixJjIN%+ zMXr#%$mc-5*HFldyj_iw6%CV(dp6aBL@fO;;3rz)6U-^)l;dY$8V5y=Z#sxH$Pl7E z$BAuBrW)7*8kznWm&v&@nIq@sp6a3;12<3x+^fIA=7izHKpl-@ZA5*ryfYaAx;r^| zb7Ej=RJ;KhplGgMe;cGFp$yspkQW(hRc11)zM?!=>OP3{;dOIkq z;NyZlObG~%jlJCyU7s7$&Zej4@jALh6|yUU}*qVJ0^L>z5U$D_|8FVwr>wC%c-53nwQ z8v!gPx$1bt^PS0cgt#tpp@1xRu?)qoD`b18LMyjbhM?BO9q^{X(DW9FXvqYnYdwJ? zHnrK1OgAo5H@|=g^^tA(yJ}A8hcEFLSFn6&#>!*&=^JIw3sF*OJjC*3ojkHa&FXl0 z2D1x9o=g^6*YS)4TRWqCGB*Cu=JaA6);F$kGev!bT}LgLB+7=5P&4eWlt({c6O){* zMK{1m*7Z66e34pE7OqoSOF5?3lCO0fO4PdX{LLfJF;41-M}kZ^kb5X2;{d);B@7f` zO#9?Ax`c(-RAls3mWQWfW(Bule;IqM0@%_(Lba^j_GiEa%m0QQM3lDX$NBNkPH6uK zto|fldx9fn?MBg*QeWymE&nIWAT$JSXZ40pS!2@GTFb+Gu&;*I1G%8Ax)%@}^!GaN z!MPe(M96juGawDRDvWCyAqqKmG#w9lTDpQ9Upa7t!fpNw%hX@X_-b_9h4K9XEsrt2 zZbD`#_Y6Hu+oi_%D2*>-s$zVpW9Im#8RN?rNzyRBZj5g_MQ*&_VyOt(Fp;c>6gwILgJP+G z1mDpq_K;}+7tr} z4~lRcq0q!!4mr3}v4nm{0?^j18=~8C-N*Ouh`;d3B}gUNrdju5M{MoJ#aQLxew4&} zw93OjX5c&mk1_D2eRTmyB-yCDp_79Fn>xgKHGgZqwy1TP^%HI0xr$Pq*_Nm7Sy);& z=t~F0ZKMc&hOgNf};Ct+g_I3T8i0HC;rukwXJR z_sTDQaznR4YYc9;xvIOcs<$l*rL}f7aZ(dsYDh>A>zFJ}c#Sw4^N}Y6uwdnc!jntB z@JA-a`4?g&nd+XU+F`9sA%yGNMp;L8B9S&SUapJ3mx^^=&YHG-Q82~6rxRsKyrzKj z<{@1J;fMbcy$fkFtlHk5L%2GJ<~&%$=!XQ%NH*qLZ%+j9q(v^pU)@DXDfkq4nXGME zZ)zXtAP4*H3ECt#n1ZZDle`E;oZ2K;G$t7#yl4il5V#6=u!~?z_%JD(=)OQ;aCK() zL8yR>gS(hVK^~L*bcPG@$AxRNNh?RnL(RNXz3ml@wZ%WKY49O>E=bc(rk(&yCs`x%@;XLlDQ$Z~j$grwgHvj{E~+(F zLmzGZsQ+P>uB?jMypBaLs}#e zCQ&C>-pbZOIln^VY*=c;39M1ux`{@Y@BkUF-GLnxsF%#9O6z3ZUxc24B)oDV7+QBo zxAGM13(`qFroA!9kN}ooI}hH2MYsTR4~WPP0m2ui!$ACIXPml3v0gHA-^-V6kPnER zNcH@KSO7&d)kjxai}Pn3)g;mfMVeNGSBdcS0Bpd;0-W{G6SNaX;9Z<{*sa459di(7 zh)_faZ?!1vsc+`QG0ZgmlY?TU?OHXKO1Ulzq-PwrchcZ~JnC{(!WR15h{O0pE91*# z9B$K84EmsYgw1)PKL_;&uEbgbP@s>b^9voaaj}qL6*2mwjiZrzSAq`LWciWEW{vw1 zuwW?Fo585S)w-qTj<4fnHQ1g^{lzST;->w$6MC5GNCC7`bEp2u`qhX^!H3`1U*w~Y zoc>Z zHes{!@*=L#pW<0DOgFsS_wWHdud{A+Rz9Zh!&~t?dyn!kXJ6guY;Z><_*;Zik&0*2 zXF(T5V@UqA;2gQ4w>+R3Xb64 zUqCA1o^5P&0g#&Y_A4ufwQUY50K*+P^wKaMCBc&>9Pp3tOU4wSvdnG`Bn{T{W<#eO zglja~`KFviVn?Nw=0GwB=5eAuC=O-hAt4|EDOIOtI+E#}pc*0=Y<@d*nvFh)Nm`@@ z5ALv;7u%9_ss<4DT4>=1CoLCyUI)&I+LfnRg$X>qzq&jM*64=7c<(t$pD*aG{Yf=J zEydhgPI{0eCjKVw*)FGw`{sP-;#0aZxQwMe{F8eumVavn3SMU4 zGTOH^!({*DuhpBvh*NJmCVw{pE$U6r$=?l#F&Hu1iJ=_^IX=GdD!D0$GmHeec2;TD zZO`Wrbd6XF^zQ?BT_c{w-*(j4HR5T6+!n?)<99FG>KgGlLe0qAHDUm%-fQypTToGI zM|+rB!DFz`h7<;m1)p&J!gh|_@b?EXR?rvC5K8x(rwLM!1q!EoiIwdLM(W!~Wz+910|INJkeH|Z5 z!a3|Cuaf=VL?puo3V+cus!07Ld&fhG@FIP7^n=OI5E0rWA|{lxu?^c|OKNAx_2cXl zYbR(YBID4{bQGG7D5O8bv8^2)@<6@17@Pjk#)H3yxoo}fr98_*-!nudB&24jOwXZd zGq(%5eazh~xm%h08N!2`t;Bg*Mab1qRvG4zx|GqOtxc3S+Y&>qum6#a)ZlL!46abZ zR}r$cXp`8U4NOB>`b#MfU_ub3+H?R$7dR(VL z7^!bs-?Q%3M4}hXG8y#WUmDZhboKPd$sVjNMVmjM^9<-;P1#58N9bkA=>%%GZXfP|X$+}c6;0nz;m-=Q1OK|{cqkA1A`z*bqf>>=j zY{l>OAH?Kk*1%seyiP#q=$jJkv=y%N+SXUO|Hn10TAd{HHc3cq z5=l&*MB1u~zGGwJ55%6)PRYcDZ7uxbV`i1({i8RvX~2bb`&_smYQH_;=o?W3qK8vc zt~`LQ3#dR}EtoHOjxuKbxkH~RL+j7w--U|)RH%4x)x?nN(bUAI_O+w6Pv!)VVmr9{ z1m;~_UDZ`YANud?y=f_`>b-8UKH1E5NJlmi8qUKWkMNjXk#EyKz*@X%vnm@_(X^D& z-hyA%CptQgOig{ux(gzpbr;kCr6EUKDFhUG@~*%h+~!E_>e63`<&*Mw^J=XP7SkH? zPEFlqeGNr$D;ScwY0jnH67u}W+6&3%D$VgDt)u>ZtRQwA+wp~oLCcs9t-GV&*>T7@ zrdvgNg_kEJWxj=j6%-U9dg{7zG=xe+w#T$$M`)?tsuiSlzy}l^A{XF7mVcs_l=}9< zkfXIUTm_V)%aL@v_nL-VvX0hJPV3)ncxHBws=!_jjgXetI&cbL?W)GT#Y0w1OuX)c zSG{7)QZS$RMD+q(p@xUUM5S9z3~sf}iF|5MeC-`H!KQ;39rYv~dqubjfJHAv#lDX# z=`giW*E-a-cD!V(u5~`M9AgUedu=QOU|FOj!vPCq#_TS*g)y#g!%;twGau%NYOy!8 zHbuN>g#2Dz)67T9RRsw<$)y|m}QZJkEIp4 z;Lu8!giRu2Iw0Z@)1u3V9B$P&#Fm&|4ySQ0_+@}r!l^m6v=-bp%zka#mOS9>OH>egZSq}Tn34!tbRy#}AL0O@*d(G!-Qqht07F{l$k$6Ul+8Pijnvm*G(1}a&&Oxns~^B1!S!X!V{Nm<%;;HsX?yu1mouZg7_v{gY|thfsHyHb zBG~NtLY*AL6j0t#5gbTfwStT)*nWuD?(j%h_^N0%P_aStX0J_#9&2oDEK1PHt5aG~ zCwCQa4~LUm=<33sffd66cD~oH!sKooI6U1&47rLxIX*2VFWj}s1R%t^HcuY#ty1BJ zPAU|z&1qLEodt}1Ng6)foK(0tl_BG0lSKda8+D1+T;U`;w*A&EiIsUw$GQ4 zzY(7g#UA4?1nW;q5vBsMaX*VglE-N--Fcc)MmM0 z2jx!@pP;GT*?YA!b&1+};+!64ek=C$(WNi-w6+7+bNrK#gqu!WQp{B`_~A&@c?o*z zswiC;4#XE6Iwux-=lgY6%4o?%x`MqX`og!E#r#W40!K{sB65F5D{?3!Vb5fTl6_Qe z@M8>~GcvX!{EEEnLw1#dOe^0(?i~OF6$0VM#XKGz@Q*18^qUoOPi_n#m3>agq3=NV zv8NPs zA5r5o7;9SoREASb0Q?hSy5??9LgO$6FdC0@ZyR@h14~KRj&mpbYc~ySMqV3K&uD2pqL5j9DwbRQx-S>ceHXi|-FzO=1@2PkiRtSDKreoxqq*o|9HakV2dEnGM3)s~`5R)U z2=V<&+-A)<&b^Y-NHcoSsasp^(yE==)pm%LBelJnYooRmCtdA<@5*45rS7e?_J{u! zPw9dl(X&}NKXPzW1r$)c_m&I!5AE+OmfwJ z{B@>w<5zdj2V;&}PxMpn#(R^gTe+#hphHsND$w&0^P@4hukxx-v|Y&kQTSPKMMmjk zk3nCkU80XS1#hm~wuvu*4-s^{8!)mCjEJ3t87dSpU;1!IolAcCj8 z+k+n@><^!QHv>NZP#~Gx?qt6kZ;T$NS_mf~{foXdJ*walS0SgU6cn>G3ElG~feyMW zjWwY(eC_8XJgz~s*1>@<{FW`p!3({79EXP`@KW9zK5zisGcqAIQr|CNgAeS{b1duw zVdWndS|tXfrkpTb!`K3kDIksUS~*uWK$ZxP9l3b69f?k$@iIYUGIoRM>iyXK#@M|9 zn-hS|alj@vZzsgRPvkz}a{};*SVdxq)5sEFbOJDn*pU(|y7Jk;=>*^uu~vx{h5HS_ z>I7gFu?Z3@D)~z{omUz+qOMIal;#@D>t}zW0-w0TI?20LzOcpYr_$&GR@g)7-2zJs zvNXFA;(F(w3(OY)^Wwf7_a?+L5_2&HNA7QhI~SpLSO$!t4T@{b)_K||+Cbn@{7AkK z8;s>(SU4CE6NknG^Uo9&g}y5)7jk=sn%y_H%V~zV7o651v6#_$eMuo#?&-HfkGJxE zEw@#cB?)iOoH=vG*ANU?gGWaV9_efEeWV^gdmrZCzJHa!?Y+fodJEU|Rj)yE@%_Dp z_xDxb&!E4z(BD_>XYk(M!h8Fw?`3d#Z{hO3>g5d9^cL3iRo5`MthaDkU-dEu@8~VO zqp$i72AB30F72yc%HZPO!o_{niy2(hTezsNdJ%)Z-a=nrwU5F1y@m7ps^>FU)LU58 zS6#$les5uZUv)l%xxIzCebu=P=JXck^i}6DnAuyH*;k#(;Pl?Y>3!AH8BFgjOz*2s zXE3d|Fs-jTjlq=O!j!)16b8M$h2FkuFN4Xwg~@%@$qagW3q5_+9tPdLh3>v;H-oO; zLRVk4i$P~^p|h{r$)KaR(9u`zV9?%MXz#1GGid8AwDncn5FFP#vhRv_dm+xv@!R^6 z29I#?aL-G{@bo3A-R5w8JASxikpBMx`ClH8k-@EFI3`OB&5%-g9PEgHHdeqI%ETNy4*UQS`;44{Biu;N*oY>}C*&dtG0o(D;4+t3|-&Rt(^`>`A>z@$RzG*L zf2t^Z@ag-h@{s4LhhD@sBCESjQ#+@f=m;l<9Glhmlod(p*=e0wJ*yJco_S~s*-`BZ z7mDxtsj}AbRG=TMAUA7o;C1c6Wb|>aW_w(<{U~r+8K)k@X+xa8kNA*HC2h~JO7N(Q z4+QK5v%m?*L)UU0hda%Jb6A1B=>PLudj?MTT9aU7kObQ$G&L`9wEtfvd-AHH8;_$v zvbw9|NTL;D!RfyE;cSJT9$Mh%ZI2{u&kKCYI(xo6{jT+T^#;_0`sM}x++T*urky_B z8+yDw)#O==6SmF-e4ZPRgTEE6QJycZPuQas-H$*c1AYXW8MqgL4Gb(tpp}6d1RiH# z83G#_xC4Qw7+8wH(+n&|;8_M1A+U)79|F%YFdu;z7$`#EB?j^lXk#E3fma#GL7<(1 zOa!(uFdczz2GSAu3j=8g>|-DWf!7)EBGAh~G6Dw~@E~xA0XG8gGvGoX%zzVtV+=SD zIKhA&fzKGQAuxadBhDr4i9u@GH#t6j8?mO3{8x>)21L)}z|+SZCXGi~;fGV`4;tRb z_Ga+Lop%pTZ^_H*2%K&>Oz{ZX{4v3P+ub#=PReZ z4Wu|FrBk`{ZKZara=uqN@}Ba=Ze^{a)b3Q)mVjXF!5#L72<5nkp6%!D3!GPDI=4HG zR@ojKLz{nRd+H|%9qJJiXk|s-31!6t=uqBg%AF6OQ(emEiAwDtp;^JWJfvkA(dYLC zx7vdp&a6)YJCu9=KDd=fVK_OctX7Y4{>@UFnxHg`PSdX%uU!}>&d+O%`B@k{KRG+i z>GV-_Zq8+&{@(fdXw3YS*=bDmE&V>UJnj5{F*{4l*;!)F&JuHWmYB1%#GIWa=Iksn zXJ?5yJ4?*jSz^x45_5Kzn6tCQoSh}+>?|>7XNfsGOU&6>V$RMIb9R=Pv$MpUoh9b% zEHP(ii8(t<%-LCD&dw5Zc9xj4v&5X8CFblbF=l5;baq_>pM3MhAiEDp97(m>VQ_pbr=(Vs?nLnI6E9RilIcOBKJ5f15U`&)Vfp`Dj#-8OI zk%{q_p?{b&*f_2>?8}I~hGVh=UJjK5j+hp>ZXDER+p&HgO0>>);7Dp(w`#*eX8g+z zK<#lodEV_7&GtK0v{ZQb2HjHr+#ITdyWu0Y)*T=P zmOEg1o(ZHufdsrgy#G%C4M%9Fdmi5ZwO*?F{H_ZT7|EUXTEbfi`_+I`UEqS_$e#eA z-@1(hb-R7qN!-h`-tOr5t99D$gtMxL`mUxWXsw@b&FTX-Eyo*7^CpOP1*_GiNXYVd zS6&~jabC2>a{IJTDTb6g0OPb>stff64)jk&5gGNr08cy)d^bsqpYpS}5_Uh_*Ut<+TOY&E$9i^EF7yo_`O5!Km|eVtW3Tp2>%jGLs5WdbC_n4! z&qbT@BmIk^k$$fKB_Lbv8_Et#b^2IE;hsYYeFVMh;l61eFYx41zi9YR^x3li=;{Bs z@e6fg5Zb8(zjEVX1LP^rmHyLMadZTV_NHMQWA&Ma`9)`icw4Jdd3X-A9l-&+vc3!p zZg60j0Zn=STaZ_3?bdg- z8i+f!4hA6Z)H)e}xKrz50OC%qn*oSBwH^i_-P9&C0Qu=!exE}`8UnS5NMQu)^&)V6 z&sk{pz@x11mZ*zU0_&mrSkg(z&Y%N;CJI?7ub04?kX?ofP)G+%AS7S{ApsKz379}g zzyv}9CJ+)J0s?phj#_lKhV{h*OTvkK{ozdaF@^Y;yd98Y&V85QCa_NbmVM-GIoep?=I}nXWHU( zdWTS@j0)O_j>?7e#va%k1wZ`)@CS78&k+gVLPau>ue~T9Y&;qTM+nR!eG$OJWMjm* z9~^6u$+6B6!0VOgt1xhc>ss)Yt$fRZe8n9&ER~>$8!nt5`KeijQ5;o!U=V{E(y-sX zLRgaZ-C}j|xxi7qAhdK?ejcG(4sG&^7rpCwLNzDx5{f%Cvr+5A9o#P`Di8C_XVCkb zL*K?>W5pWfdEYs$_d-*4g}l?$VPK1 z17Hlriy81Du!sTRuh_>x8UphfNJpTEf$0e3GmwcuE(19TwnKqdqE2ux?7NUNET zFbc0ov+mJqdoh&jEDeJ=K%*XK6Q*tvv`cx^#av8#&z5d$9NoG<1ikF*Eo#F;WZB9ad?fxcpP5i zFdm23IE=^P^()bLeJoyM3FvR&wQEK^UcDq<1FySd@T$}FEe1`$I_hFHeGp{Jfzz?9 z<8wHc<8T>=sSYeh`C4(&t+2_OJ{Lqt8Pc9L7emr{ifj_Tq39hrKu)#bGZFM{(GT!%-ae z;&2p;3fQT|%>7ur#vv(|5{KsSlI(a|6aTmdj`)r(xi;sp2#3!>G7{l%q%c(;Fo3C! zHV7~qx2Y~Ak? zu76+MjaZx%#!}S9$Meq2`2Nux+8oaXG5uq&=*slM@jSuX1kqmU@DJ}*9PF`(jvJde z&|?uXv`EA;wx8pDgV=>vq#I*wz7qpGE^906Z@~1#?A6EVr^Yxvs&A`?jnii^)*kAS z4m)}j3tk-DvBOjt%Z0uI?vK6^54SK7fjiI?JAXdm_Tqy11NW=v1iE|dkkO3xj-HMk z-jOk!@GD@b9R`CSMu*{FO&9_zh%Ju%cl3=;Iu%~rQqXuQbRQhA>X3ZT21s<296g;H z!)k`+(HH-hy>Eews@ncPFsLXPn5dXor_e-U6huuyl=mZf35b*r9AFR>7-D98lp2Pb z!wIWf-Rfqy>~-r_H@kJq5|NPHlrez3WL9154S0RL9-oX%I9Tsygftz0wZ*VVIw+l#5*D-!*BtD961$*V3??3P zZ!whfm}iTjgvY#F3`IQV(_$##G2a%$EFSY~F-+$%vBi+XV}n`@Sv(fdVo2w)pcX?i zj|I0F5_xP?iy@ZB#fF;9w>Q_P)WCW^UHYyn~; zG)=v?Lrn!3y-yIGN)3>a>3b{K-wU!qze&ENz3w0)!V^DB>H;D>KdI6%gTZn!=b`?rqog9 z9^;nynY)ZzBAUW7rGef!KvgsgBgnG&V(VV7sr9XK7j0;#FFVCuoEF|__F`BW#4rRr z`QmGL_M?X^Jv(28F^A(qz|>cR&^64>GW&Q&&0oub#|-9Qo9(qVvB%h0ZCUXI-dI*V zg|4Qnf2U{6=$msyr)Hn8$*R!}+VO^@D-;Y{XlA6MOp8by9S+$?DR053KRH1ry)s3`h7@g08TjKqGoI{8am zOq|(Ib5V2Fg6K1>0*NZs?7}fZyn?GQ+*#PO6Z9R3n)URcCdM89DgGQL_oUaVm8>4h z$ty<$n|_zfl19Ax>gQ2Hb&3$S)triNzFwqm!6_GaPYOHh1V>b)FC0&(+r;Y3# zl~9xEo}JP3oRZCK;e{iOU}3mEVFGPQU)HbU-T{0ablLj#_ZU*A&~_qp|7*luuP4xi9vFXDbCP3OUh*%R}s2 z+}In8DU&?1338MiK*&)@u1XZ*yAlQQQB-18(00)(^6)^PWm!iR^1N))bbT@2I#}P?s zswZ~yrh8f@V2;FzOK+T~mA{H-S(0N}0uPv~&2DIM)hT=jZ%`zRZ)Cd0XH4CtW*nC! zIVUUhwEr2&JG5DI60%5E&9g%nyfNp1bXwXTdd7O9zx7x*kL~axrTLm975@{Vr(r`} z9hm)TW0Q17+8cVpdeX=Glc&dCDF(I8r5SPhs|{A9xe|KX`fFcna}SRzQjWLgI?@FDp9wvQG*1luz-!TY zz;g)jH21e2?dEX^8-6rDLR|9kZw5RkYMvc7Y|G;bfM6+RRF3-hZLz{??zm5zK=a%wz2^x5P>c%s$qmo&<9MgW1mp^M}*G zeQ_3*@a$zh){9^cVla=g!CZG>x<%S=J=WKHx(C41`(J@X78`mQY&KDI zt>%ewt6~+voC_jkbhhkr}x?;7xfn$@9^A4S$n=d7puS%2#8 zaSk6r`XThN|Dn*sn(1)CvHGsb56-+-x?(+ZyY;8rfYcu}*F%5wKOFi4UT4*;?*Grm zPfkcXtVeyU&7K}Rq#sfDA^#sjf5dB_n$>sA@E)-r=RHvOPhCKchfw!n{~tpS;k94Q z>N_)iV&UG51PYLgR1`VsQ-|wVYaIN2K0TSbk_fm&~uve zHBW?8Cl9NUezzX?v7Yes_#Ir_N7DiXSsC=tx<-$?+)78Q{jA5_7;$_Rx<7Qc<{P{o zRkN)3(hCngE*-Qs_pu)F@HnVBgxa?dg77*Pb>E|Vxs>W=NB1vEyzX6cej+Z_kFBCd4TSI zC%nrw`=RDplh$8Q)y#TcIta@79%NNsyRnXr5%mL@@DR}5W3R2&EY!MX$x8vK^$0${ zDeJS}`vN4?)E$imDQ{`nr8(fS z33QIhg7AFL&_AigU9BhXpcdoxb`K(AhzY3o`PN}EsP{+@>*>B8XQfSe{ksSCb1duS zfA~N9F4f!1dc+H(%&NKGvdg0pM1+|Rb&q@XnX9P#SRZSX2i2`PfY($vQ3(+0((u4E zJ+=4_0Kj`036vp(&g}Y^FFl{6cJ~DcRKMnTBw%!gRxkeZOe>^Kh*|AGKviH6y6&E_ zYvYDpul0Bjk1f(48f(jbkF#Kd=TSvaP3?U?F)pu5 zE!NY$tiO7JU0>3iZ8_j^J@lgH8WIH8)P}9;<(?`XAUvG$@Hik{)a*xs%c0l7p=-xI zwj{Gf`kHX?Ygdo2rR$pQczG%GvSty+S?$=-e}rw28mabQdw4X0%UkjKV(2B!6~@SF zC#>IdU|74gUqQ`J?_JRh88ltG10xjPHwXfJj^;r(kFRqzJK!$wetdo2UGvpUe3wMJ z-&3=72EI-L+w*v}#DVXtz}5|o2j6o6cWyaP5?{7-ZmQP@zk7XfKK$T3e2JU6{bXrc zewVIzeQ+)OqU|wEBR=EiPdCO$2|3x2Zbos}7o-bddy>DDmV+;%k;s$XqVhnPvWTw8j_3OqraC(?zZj2a!7);%7eySXEz$Oh9DWY^osnYw%>> z&>}w!G3hlvaE@TfOgCy;W3nLqZHPqHz^3pM@bry^ZRtwjH5_kkjFf%uFa?GKLT`6U zSESv6*J@%SktCC+*`Cf4HRa>{EGmP0+d*RBPDqQI^HrB3F}?eeHK-ySiqUW6Jkytb z-&?wZLNe3Oz1b8<`N(vHyz2-W$_fo91riU-YTnKuvtmAlT>2(`F19f!sKu2Gw!JLFysMh|7PQ*I(KzotJu0eUj&AD-&C-m)uvnN2Z#|=c||yeyQTH zT!O}H`4@zRYp!te36D~6!`|w(qK1m1if@aQJfF7ZXQjX;AbaQ@$qVJb(cuRwcJngP zln`X5qAqx?h^C^j4?^;4TU2Cp`2LFH3KHaI^s+bOuJ)UuH0BqjG5h4kKsS6wZVcHN zMfN4~74>+;8q+s25+9z5TA6+wE)l}Nma?j^u1s&O_|*nBebtjP0c?pf5!n5r;1z&U zX1TOh{O$njykZ(5KzDCxJJ8)flKe4Ae(P7}w3=y>XNW%Hm}QLILxVeOn$o}@l|uN= z6wn`?5A_@gttn~w9(?gfFb%1h?1E=7VVR(r)mOW47Z;&hqVBJ*Tn)Vwsoc!R6i-S- zzv)eVB{q0GtDxdK`YWXO**Iq0I+1_R6z4hJYkZai8T4`DIx>5lC4Kn<`G&ALi&T5A zK?$@9XfgjQdZSpm4e}UP3n&>*?l~)h3t~&?zaS+n^+d? z8iYxr9M#f(9PgT_i{v5F7dZlVNOvG{jb{?Zj^=C4j#Nvc8|}pbNIX{<0|&XP>MxQe zh%Gqonk&{{U2FW9XCTeCKxMIrs=)j8vKnR+m;dxF&V5IBF%Y&wiSf!@yD zczeAD@^ZKI#;>Qv#%k2I9yZJxuA45O7^uhBOsDl2ZIa&c$bWh?tj{K21e zV;AxHSR@s2q81!Z%$7!Pjf)nj^MlqS6P@VxSThJ|FpUgp?rIrGuYsgsa^&BP4J$kY z4l!dE3bqWSq*5PG%CN`Oy)q0|ngo<1_3^`_@cm2NrTrEbmwNirldtgPD+`N{8|aNE z=E>pPO#{OBoBXFX&81xd3?QBwB;D+ZSGFwS=NI20xqVtWR%`B7o$j)1kJ%$W{Je!_ zS#(^fD*QY?Sse7~#_|M!zPZ}BdPmq@X1`_l4pDWE3vP1pExxLH*Q!R>W!ub0;5o0$ zfcmO~K)iV>uQxt?J1^7JAGHreKL)|RNsI0ba01H)5=bt9x6smuaD~=%we+MIcm?Kl z2n9Y3m5T+f*YdF_p*qe5pJ-jCrNWX2jiR$LGN)7*n;Oxu(gU8i5uFJkP52h~is@sjZy(@~QQC;t~oGqMje6r%I zUE#a(GRz9%vU>c|M`L|KKdn&gy-nWZBTVkq2`&lC>dk-B6VS&fq=4-i1;UWzwQLzp zVK`hJxE-Gqm_lx|(jk2C?&rtaL!`xYtRL+F1Nh@#ExV;HNiddKP2-k-i;tKRXZRj- zx^FXiUh^Y@W6$Eet2TQ_!LW)1=9keHkxjoSquE1Zs}u}DNb3iTJ%pYrUGpVSjIlCY zVrWO6Ah;xxyw`jOkS1qT)khYkqyHf_9Z|vPEnQ~d{qe6gtczN9C(YFCj(nYNW^kN0 zcG-m+`df!BL-hS9p9VVYv=q>?Ogwe zSq%FoSA72@hB{{1Fd8D0@KI}vJNFz4ujf5kgSMIIqkKV9?9!_s@XE_tsdt8Xpya|m z?xt5+Ba1{d3vwSd3zo&^yS(ZIsk_vN+MPbRYExv<+4yMc8=e5G5;Qt(U;(6D&F+~p zeZ_dbPOY6`Zz?|I;kEoe5CuFn2P`Is@A&fGUY=u4|@3rh*4EV|`VP4B#qsZ8)Udw53Zsh`(aIfXR zAXe=HGuC(vZV0akeh5D1%y6(MbNo0h5t!*N>Mk}&recgpz2Ndw-5hUA1nY%6QBAJb zbM=Wc2VI#_RX=f>S6w~p0P1Ydr=z7q94=`urwQJQYRxiyr{Q7)oobEa0tf4IT+Vy;9O$}dK5iv?m?zLYK)3+z{|}R?>M}Cx7k~H zO$?1O6{aDXN|2pCJ5!)AixCpz%tSeo&q@ghr%}TRc6?_9+ab5G4;&7v*Rps3P#KO7 zu7WqP9Sqw{2{D#|H{cCj(Jel(Z^Pj%9ODs>I7g&AnRtYE8y*n=*M4XNJmnJnk~>EI zr=VFHLTjjJOG3?FV~_a6kD`3gl2lxHE@Q3SJN2%r^@s% zvd(9Cn+mi^?vvR7u<=W$0njjsmss`Fm6=ry6K9}c&WQMY6Rl7`4OX5q*Iv0?W$+pw zzyv`dD17r&$u-^7t>`S0H+L!8hYP1cFm^jn=VK7fxA)T;l9fQpw3wqv6$;PcEmszK zXq0jZ6fJeDc8A>48+E&T)op`p(gQL?B*dQIL-7@bQsZ4=} zE!ArkDU=X51H$)vts)^4AC%t|csXzj`}TxLridklSaEx>W;}UX!pDJYd_KZcAFPZb zzeFB9aT@?rdP(M*UTLAS5d8`iao`2G|d5=ACVx9sKXl*~?$O=sxij=KP*x1|qt zFvP-YcgSaE&&Dp)TVQuWWJBepK;;49C$fQ@!os`0r6;9Pn~TUELE0950L*iakWM9& ziU_x@lba%$3m#vG^fuef6&vH6BP7icCTBA5{_eyY&O*Dp7mn~rIAB~KOuuL?nq-^6 zJV8!0y*4Z+pF~7k4K=AQb6h7kEvm#c-$XO%Z}@^88!|=9kQKwmHeB45+!6;Oo7i^} ztV}i#!naH@jj*lE-5e-QcLP;uy5AHFJihWpJ*+P>Fm2uQ6s%H<2Eoa0=!HV58;B6j zz^1wuMpO#;;0(%J+QuhvTNn``sV(H((Xd*NzcmdM^^d-^T)>~##e5y#BJqK z#I{wg^bAHp@l*)TdVaFSEf~50dJY{*4BpNDz2K$?9$}uyc(>v`>`~cRfgcI*rHdda zxIPs*gD7%?M4{#qcFb!9^zIzxU+dH&BE8mc(8F#r3D~>VJe>D{EH9!f88yiLIaNWn zx6-XO@h<4eriYe*6reXSWLY%kAu>?Ah#YLJ%a+|nQv_9Z`W$DN!nc^hJA*?SO?^JK zb-$!ZZ{1(@g-;{o9dwmUskVBcUaM$=eMi!hJ^ zx4rA$=3RpAoqL;ifwp(u+Pw3kcQBn+$9I)3zvyup%N#g~C0SM9$fEX<<5)?kF6vdC z?p2-WZ9VGlu^s*r({E$3w%;F7()X!O?_HgUMQJ(x?d_yz`-5N*$(s7vgXkV8wb#$R zTO>qc3klq@;?gt>28$vA2d*t$8{>Tii11DQ@Cbevu>%X|$J3kK$)o{BMh263AIqwA zjBMtz6SSpMzcRb86tXhAAN}~!Pk(8l8(b=0UUIiI6v#YS?K9lk z>>IilXVWF0=RW=%-!R$~AKtR)C%jzf$x@0^F&tQSl2q!IyD}Q}&>}1xgrYKvc~gzu z;n8yDjFnOSRXxdR9`G{LC-Z+EkMmgJn;yk#mpkDV+fmb{uIWv_+`3`u3goHM!v`kL zsv*6$Ce~eq<4z0V-xU+*ue8xW+n03-_C#o)AMV2=XwKxVS&c3m{=0uhJ8eSY(##Q| z3O)ErT{->-u!7vOn>Inf0JpZgVr30t>9Ow`iSZe2_rP7t_gJ#smv~9HWoWDb?PJge z=%O(m2fP3z`F$t<-kyQG!nZF8omx}tPNXGug||#o-}I(|)Dq^zl-!AT&DB_EHZ;AB z?LVlQKOvu}?(|s3C&mX!eiF|5(e4$L>ISlFz}^{1f3zP4-kFIkU?`tT`@<(lGu<^8 z198yF2cMKouevf|vCYe#cZ1}$Zan6oaCiRwZfxmFyQ8LfK=}9SM4|Plr8nLscN^f6 z>dO*EhFcf?26v=%qyR_uHitJXQ5S3IYnj@Y-lftPReZ<=o~AJaGG746ad_khvyj*{ z9F7;^3NQezg|?p1H+-up!eZzfx@VFl+sBgan^`l}WlmZ~QvoOh3qA0%xXs)R_>j74 zwn}$G-fY0ePpJ{iKF|s|5AEqH%_NglKc+0f72lL#)o-d!L~IvTRz$k7Q}cO!2|GkrSf=* zqX%C&|0j_pSIrixP6d%TfC6Jff14`Z1^1`Yd`~jp5uRvytmzQEqS6-ugPVSoV`H0s zkz)Z(&2lWZ>32Dn48cCVsR3SMDOY6E3B=%X9$I$wq&qOdPp|rdupJANi}VP29Q2na zxns-VBzI}I$y@TssPS>hnA~)kQJrUF45M7ItOun@_&Rf7qbW>^@unqP`r5&6eB=O5 z6*BOd+HJ@{Nn(8H2Pwg#S!p!$Gh??oPQ;b#{8fR6NO_MqQ5C{4(YDQ9$c zCiC|!J>o9)X6{jaHJ6o5o}?Gi4m-39pAbUVAP-1$k~<*2dVcm{&)n!>M9ZKlajF0lLO>)za>?L6O0^SR-ZnAwg6e2YZs=VvS{z z2)JJ)QR;T;QP{C9p1W`vYf0WkyXk^~ox6dZ#;Qb=ga6Q6mv{qBQu1S0YhoX}TDj8| zwhowib0cS3SU+(SN05CmVLTxL=6Cin1bh+!n-S@Q2aI#s`5WG^%s~y7m9yxVKAeEG zewIYhk~qk+k~XDURuUAJmC5*RtR$i!Bv@8XXHN_8E0>A-n0yX_cXAWHpiazL4G_UY z>=M2y626Z9gy-(J}4W*RCzMc~9#ZE~4uVNr=ygwN1fasUuj%9Si| zU94M~dy%TUe2=-iqQ7G$?vk{dQ{~cQB+4&6N;q6f4yEbKoj#x)U-K|&id)rYH)(qe zeepvOE^Ki%kx|`;a%e6sy+jX9hfz9eF^#d{D+#Cw^AEg&Y24v4o&g8G$>Fo4;_DDT zlILN3ia^Rb%qEm<>RK|m50lD0d3sF?wafx%b{(`0Op)ncG`Y79CMlj)p7>wu;C&AF zlOt$;qgBZYpQR@#uBD&lF6_DS6qX3TSneVn1-p-QOK{P%o-VC@>hVzOISMxx_trkO>@JwytyxDm!t0T^Yhq`;BhoRW3TDruzNoCV z&&O0=)7$95BF|j_!5u$M&){}V5ET%2uqN(cdh6%_+|edmXb{%f+{w{3_-P4>dZN(JA%op@@_le!# z?moHupvN}zpDpL4t%U5S@dGyl6}Q1A=Q-aUS0tS09vC~wdjKdES9e@JTMol7+hKt^ zLxt{$<|qcB4+a7Q-wD98fXx>v zFos%=%P@Ka#%_SobDm2o1l}z(38dD+^x2-)!IjSVp>MySR*(9{J_)GAv*lpR;i`iI zrdMx_TTi?Lbc@Lv=VO26OG6q!)BIounuCl6rY%fu&O{sNr2=>J?n4Z*-vY6}GsOB5 zV&S%dA(nt9#CkHs`T(K6fw;rfJ8fY%8MnE-B~P)IyueyQ`0dNb7w~%-__bcXix7OV z0TF2vjj+`${Tu9eEq;r@Z0N^DXzGa(0_p)XZD<|5m8JL+zxEEKA$05S76xu%;1&jM zVc-@9ZeidS2L6A>z*n1Ggmnn7A^Ziwgg}3b>Rp5t2&)kOhER_18p3-BA0yNuY(+ST z@H4_`go_BSRu{n&!52Y92tWu%2t$ZQxE~=4VLH+lA`~HBfv^O@7x$}h{VT%X5#B?0ItcNwkibU`3UC_|w4WRQu(g`V}p19~RjAW&Me{t|50Ckg`X zx};|mP@F9g%=C=PBp6*0l=svY3S>gK3t{?@Xdw}S-qUl+2a^}O;==M$8IcI%kuDe^ z0AUbb*e!V?JeeRTRbI(_?=K6y@GP^V9t)A!8j8|U<~a+ua-BA^KUokef*zrVdm zfA35zfivDj1V4nUk!T0PI)oPx79b=d3_=hPwoU|m2ssFY5Kc`1%m_~*6d}YS_#vE% zKw5-X5y}w~5&RHNjYoQf7Z6Gi#v`~Pd>I}sTyG_cpuftA#u4-C|L$`+^6&FUTTMsP zqg?(LYaRG`zo=oS*4Jkx2S@h#*UXJ?t-KAbuAJECm6`QJUfB`($|iT~$~FC>f_wC| z`euC_Ie*O)^}}mCBd5hiZoK!ct}Ja%lyCi)>0=@@vxe5cI?O9_z`1WWCN@+~yfd-6 zZq_G5CJY^zy5VwmJ)BTgPP|_F)y7_9swW0buc~|Ky?g5__sp)Fle~4pS0CopZM#O=E`ZangG?!*P>FGak&HFaZz+xZFCJX$xL{oB18j=uNLh+R)= zH$3(8Iacmnd&G^Cet3Sud-Zj7$-bW9nIQ-2dS>QN_`+pdcuR0~M8qE@5#3zeBBOSgH9U)za*)VnXIZ&bZ@F$9GwAiY zOC@P_mv;4?;8NZ=A?Ne5x<`*>Z`hZ#v-XePTgP{8k2kT)_KpI6_Ng7WW@I_cSel3ytekm+BKg~{%+H{%oiW8U3&Y6 zug44C8o*uOin>=d0zH+Y}bLP|W!#>%)4sprupp)-`oGgh`f;lHuT+C zzrL>4XX1ZE_l<`$Z?Ai}ICO%zbJO_h$#+dS)v&Z~%IxLiN35@j_|4$E;knn&uaBGB zed3uP57bRLG-~}{z8)Tt7P@=G{-Qn;22^dU)tCITK}x=N!;0Tov_ zhF@ENFr26Owno1y^C@9u{T7;N+`Dz{+68~|=Stl)PJga9_;dMR{&D#J zAO2QS-Ry@LrYi!uy5}+eB9hIbBA#`-t-IfV+xv{2I3{F}|1fR&{3Q!Z=H}@W)8eDD zG9R3A{;fD zM;1-a&do`=KPI;So&CJKxO#ZreeZ~Xh_KL6sY#Iu8B?ZDoA=OyN9U9l~ezfV#J zg!;H>CZi~F3LXic4z#+Jg+JE)(7iD|znkaQd+yxL{r;V@;*Kuq33smh&HY}lv)k_3 zzrT8fZpWm+f|oo;?_IGoq{pkP#&6h=Hn#lRUdfGHb|v0<BuMkFHC*+ z*y>d^Yn~qW)U4;m*AJ1T?~gy3apld&emP#SB4=7)-I@54pO2aK&ZkQk7k#u$JL3J& z;M4D%?EC&3GdxOOeX4Ziz9Hr3c3sb2`+d@+hYlqV7}I#AYfHmD;>TY-7h&EWP#N~e zD=%NU5WjximEYf5*!t&%2}fg^TYvf6&e|sJuEnS3m#-LAUz!lyf0A3ozp^)vd$;Rj zL;MEB_-~F7hF6HzVPC(z>h=YdX?@qcAnOzGl+eH5;$KKWqJ9$^VnT{Pozs zM+ClK{lb`%RZ-p}sxI_A{rI^V@4OfrS@iPDg@I2Ed*tM^zYhHPNV;JD*>dlg6BEK) zehZ(w_S_Tm9%`AAKJxOhlylc}&(>~f`fc&1kR21gUa`M*N7agT-;8=`;qKoygnjqm zt5*)5KH^#Yz_p#FmEW$Kb>Eh>f{}l9e|WjHZOQcatBuWTC(Y09>6UiXd-J5RV^+jJ z8JG}V{fICX&H-f8yeT6E@<^+OK0ymfr7m6f zue?3&%zXF3-5hr^Hy9-) z&D%fg*ALG6pY;9h*y({A-aPxyR}20$t{8IZiwayWrS8bWn%P^zDtCQ*W!X1U z?cehLy7=$w*G_oG^ZnN6KlEPvLD!xSz58R}$hV5doO`Xb@Z;LUkC@kKW{mmI$B``` z()cG`>Wep`go6ma?E8`QR1nre$Manaj;eb01L=G8tewAG`fvOC@B6Msy{W{IuPf0D ze|IgfC@wP<3PO%+p&ae)l3Q%ZgNG^C!V0}!5T14|)s-NN?1D! zTnkP5k`f`$wa}>7&lMV|90uq+fUitfiq^j1TBaM$olb5PlbO3r&U+A<8vhUkuoubS*K=4ha_KxT4|uvU!4#l9?Toku|&k6$^WT zKLRpQ$j!+wH^mp1nDiAw0WeXXo10r)hE7119NZ&!sjj$8S25ctcmXqbg`}lI72W|D zvk^BsQxSSY5MCB?bp>BSc}j&LD7fm;~3g@IcbxP^gR7`TOjTNt>7fm;~({~QCQdXuiO zM3*lPHsUawAPhE!iE$YjX>6Y56b#i}A@(F(Ro!PVY&3r}tDufzG58h8t$jrZl+f z=xQ*VL_;Coml`S-icGE6u@du(psYI7l@IN`plpaqgeEKILI0jB7FHNaMV68P>#!^+ z!4*)hq1XyC+*&P^Ref23SY|MZ#iivX`ci$FNnao?)SEhLqe(a0CE-!~MLSGOhmO@b%^Ymhfh!RKkQ0F)5sBI!zucwj*o5jS3+mztc9)G6_ z3k-T*CCmf)MPhk{eqOP`Y-HVS9KwKdsIx*3)fZcoQ&AW?2v0>Z-%w(xz$hDRrXeQ= zn9GXG3iJ!~1w-XLFs&%5DSeT?cy^IV&W`)}#RU%U3-rePisEwW6C@%$i(*PrN}NdM z8u^JJOi7K6i%&|8i)C<$@?V*`G*4e)FI#zTD1^<6>P#8~&-@%N{*J*qZLoO;tB0QP zGKd`5JF(mVT(kC3MxG0FrPY^~7wIsDI7s}t9mJ?9u}Nu29~+ewWv?$OGb2WwzCC#D z&y)e2l$srtoD?f0$3?|z)23wE%WJ<~rV3sDTw9y$?P1U5mWVX6Vq!TktX(Bwf7)Wg zxq>7S|ra%^0NjDLo0_Kpv!Tn>H&11gA0((6iddApT|g%vPf=JNE$2u7CZCo*z6b{_Q+ zz{ESEX%H^P5Opo@S1{R1pHIYLsfh$W=dRUVq9ESJB$nUsrasKF2{6C z^Y=m#V^Va?&f42<9!gtgXp`JIEhjowGE%qY#m?zTw%`+8k#3$|WJIEnC&&_$c$vBs z+!-ng^q61`G`(Y@9HC!87ct+gHx}vHEU&5qjK{Q4PRVBpGTx(=Y>b3+{7j9?NF~m% z6d~R-*m#dJo9fI3#b|ach5)AAVqKZiqbl5r<+=(Z$qEi7$UU!02f3<<+S_h&5n~Bv zYdss#jAm5rP|bC`*}O57f+ZD52GTnAH*dR)9)@SR?W}o34UXwmLK+v_h#1<3L0zaF zhWU=^8NJE$0Qk|q5+XVDvH`6u21^kGXcCARIaEwfipiRi5yxp5#i;A;yqwUH3qnVN z*I_+ES5crYHpIDDRa;c!$DwMXE1OKKN^c)K@;GqJrBeh~|G@%gg=ULOO7ydJB??`r%6Fu_cI#o3uVhujrsxAo zv)QBoToG9@o-rTmg|@oH07c{)N@69*odyR@GA3Of=zo5(sfgWjdBKU!RDF{f7dI&@ zDmqyv42Rz7L@#j4=0s1RMG~}8jS2?H+4(xGm8qEx*F|iSDesYmjy%c%b}<0@M?ry> zTFRO>R4mZv8<>d61|S6eFs^8*aMgJpHI3Xv`J}~kRyHjU7VAoiAEq9`svrjt3>BIR z#t_sOt_gKkMm}Z_rpvXLpWu~+B6;3Y=v&pV*=ZTkNtwAZX(=gjsqM~O9m?G8Wp(5i zPC6&pOrfs01Y^pfufj;CnWwMdN-3Al#36&B_<*dZWW{U8QgS;k!$jzb;yhi6nQM3~ zsjRc%X>ox)lMQzYzgM(nMguG(>4>~CrBHd)sq(7evu9KUF&mUz-Be0(hS{F6ZH>wd z=K#H`{!a4}8`MLAC@6q(ojN6%WSk1{fDZf3hCK(Ic3KCpYcd!hMODmh(|1m~Uk6B4 zjfF5ixv~NcWwV1sT^_^+i~@%=*ysR6L6jHg87lGsfGnkh(dkPH7<&xr0nwQL_{0ku zd7_D_EkrKtZ;{h|xzqhDr~725`>{^js*>zwZ2bGm=Q z>HZ1ayC{vtb%E1;fzy4u)BSj-`yi)#Kc{;ir+dNa{_Jtb_Wq1}HGKP=-fwk!|FP5k zdrtQ+INdLEx;Hu9Pj|Xcbh;nqbT2yHdpq3=xL5z3!ub{T-x1ua+k4RI{Vu2btxos# zPWS7a?%#8|U+r}Ngwy>Z+^gX!ceNv}T{SDeb?W;jLSRA#)jSyF9w^|Z(w8Rpyk3=2k+hDXzM9}ih zF@pMr$GJj|A_dxDq%G%DAl;Yh!PHa_Dqtv`r`JLT2Oni|1_x{cf>2Uy#BL=)z-(oN z#w`@-OR&=h?;&a^MauahY?bI1YRT}(->V-uh$hI-rFg9@32IHW(}imnwr7MEC7q(& zu!M3t+cTEXUMAU8)#=#1k_Kv>77w&)oXgMTwxcI1j4Tm~RGuM=EeeVl1F4fB{Gg`PfEqfC1^uroypWSx}*S8bNG(9Ete$J3)XNfQ@LNaO}R~d$DXb z`-KLxSgKpdt>0|;E)??&&=7`bRpV<24Dki@sLjY5LPC$MLn&o3T~iU*N!_$7V)Kh& zFg7!F9i_>1Oti%4F+$6V`yZFrW=x^D|0!&8S++4|GW_pgYj4a_iB9m5a#UKpKmqW zrk}HAD_N1l3JC}kP#RHud2uvl}5NGF`( zvn4)dtl9ABSe_2WXkkVd&1GbC2fE1oGFxA9gM8?9*wJZ#kC!2F#%-RiKujT9zPS`- z-NcwRu(c?8yatDi3>`Ik%zb0W34%R7TyW_9&c!lyz8oV~7aa3KL1R`kxWM8DF+nkB z(|C3?6~M!&Vokz3J3LX*F<2hc<>eRXv0FPBb;!$lg57#82vOv|!4Vd)nXn6Zyowj+n39|{cl^1`!>5n?LDQL)rbl+73xGlRkEMEul7JtC)d zFb|O|XT^LC8l0z8Hyb@`hl$%tX*^^V0S}}s!<@sIHr5SG%EOsupwL`W!fvTbafofn zU#q1wd_IXc0AR+|gT$n={NX|3=y75e5DQaXI`#+XMXi`=#$vk|8X6oVMjMPK04%0N ziDK}`kdP2TfCVrZmssme1X_88VK&65aIv_MCix(?yMS!{4B~j4 zF%UBig{JvBZlVz$fIuLlikXWm^w=>!k2pMtrF7QdT0C9L1>A2Sm_928w$ioZ*2+ zjvkUJgB#OuNikZ3_Eumyf!fo9_o0j^>dOMmcooGcm5pYWuLzSbBACH2)aPS*k_ic2 z9K`T0<3&b75VauysUZ{5#7$H-6r+713@XrhL|L0a>oIYb6aKJ%0^)>$6UcSw712&1 z;i4Xrojp74OMpmMT!>W`VnA$d2KiA#sqvVNk5_VGm3sj)6W?Cxla`DHyDe=7z5Vi3}t{{ z*$1P=v(i$Ot7Qu@OlDM814Pu0ttJZE6zfmK|JA=9f-K`WU33NWKq+7Vgi%@LLpOm= z=Ia(R`gO`6-;xqBTW={0cBAhPDpMp?(G{VV$i5_7n~a zkTBX92jLqpjIbkO{)AS^O^IlylUBq4jpfLd6(zDHW19`_abkvMYAgeeHt1BKEDY!N z+ZRCXP%s8e%VLpQy+QMAViVQ^7(9w-WGhsL2TEBX1bw{@+ke2Ka2kpmNoCfpE|$a` zVluWO93sweG4@KjgQJ*DxK{&*^0Zn9O;Ij2{l5lGt98~esVJ1gv`*Il2Ji^X*r0jh zSIYJU(nlD{l+y%<;YH(|v>QQ4V?2}Zd7wFoq*CZVB-gWcIq5fy;evZCRB29jeaWu< zFv6qpXyZH7|KRtar2=gG;-(x{8o^>8O0@V5RTjj_@M}|PX$GVHC-QE-X?%zqXNZU? z>e>vZ0kaW0jWV*>@ZrO0FV7SsI16@m5XMM6$F$GX5(1Q-rNA;pTlgHeNU<@>M?d6q zr%_LXf|;CTqtGJpYhFUTb+HUo z=Kr9To#Ih$yP6iAaln`x5r^`^hI3WBl{VYfv}g?y8&l9;TAgHjIhAcjYC~*0P0Nad%|d>w7|vuf0gXnjcA9KCo532yjMl70HjYeEBm<$7A3&|cnsza? zYEl1?xfq%#juB(j0@mOKV?xG^91}Wbl)63b7j{qZf*kqp9?(WSsGORL(wW+E59wp~ z$U^`xsH<-X8;s@HpF^ZeY#)8Y_NU3LX_S_>gkiTDAXT+frPqboL82L7FRI>gh8SQR z3V8x&or#!iXviy`t-UGuI)zE(O{RhrjH&7mBYY^&ZvYz+lo{Q_@Ia7JKACL67$NN! z8gR^cKAYI+oui(N-mozkXefsH*yB5&Yc90T!GwAhWE?YW!lbeYHom&ThYinyffJ@u zU4s-JAEd@{kZ4oTxjsYX*|kVMs}Uao{%wK}dRHNKs){DZc1*-}-Wi2CluheE5UuU% zuvr89Q=kzQusN6wFt!JsLymb1>Uwcmeuti%Q~+rfh1=B&Mo@fS<-92qYb>l>o7g1l!}88uLagn^-z%qto`M2xNnXM&=EGDTV|G zGs_9K4ziU2W!$yJ7vacgh9<*|qoni3@yR3%gA_&CcV$JO&;=dhoX@?49~(PTWIxu0 z*t7qTS+9i#0eZ#__7{->N5!`tVdHz;0FJ^{Nv~yUf)iK@<%d%>RG?!y${pK6$JI{K zHQ@4q$w;6xg%YrTP&NV^M93=n`XWOK$gC3>X+SBPg5h?;Xo6nL;kFeD45GalU7Ws= z1}N%io`)1gyMGmXt4wK1$yA#HE89z^F93Ry7!u`H`WwZtc1X%!A*gWC-+uH&-Z@N+ zT1WFU^cT*)?0xLN0Ok2RQj(ou(OPO1V}vM$`W|*O*#IA`##@fa;8I~#-k~3$D}gD- zD9)xsHOS1cgkd3%E*mQ7kde|xUY26fR_c>wHsTn3Oz|3!&xu|l9`Rq4v&etJB7fsz zkqvDcfl9xjr*zIez{-$y4yGvaodTlBC3-GxX0R;;2@q_VodOI%hS%v5GBQXE851Oq z9621uHF4yqATbn=MvWRC!gSYBW5v0N4|mKzfEi_t8@-Hx@`Q$TmE}aSh2=Fk29!4Qp>PF12s`EY4d3t@BomQN1p9z7)X3vvV ze8_u{$HBcs(|nA3ozbWVUBiBW>4y_1itC`QNW%C(9|yXUJe;SXR;!M&*+wi;Hjt^( zN1cGftrHE|@-m`>cFu;oU5K-xu_N5+WD_3MO9HHk3E^O*%wEmc^=Quv%8euT#aL4Z zZ3X-=5WHd-B}V9v5+!X$fLhkaoK9%#l~UDkVGaHFlA=A)Zv5z2%hs< zU{{t$Xj*a@h`?Qpt|Z?~CVD;`c^9x5mURm?Q$ACuFc;Q!9l}STP`y}Dffx_-J{N;J z9M?`F3J9J_Gx;P?r5d0@u_Xv9mCFw@zhYWdNAv}S486{KO9ud0PA$(FfwPc&)eu#2 zX2_Qypkn2-Lz`{si=H@2KYXtQYJl$_ZrN$G6N4#!?U$8CM-vE1w$5b6KJ^l`ObUFC zkXIXlbCUn*{E2PHl*tegpM(wTAVhX9McFaW!DU>T2m{5D9y3Hcgvo}o$m&G@wxfJB zhPB!Wj(yCk!3*k&aviK)D4fOR69mk}hi9a}u%j|`rc{H+fI0u7cbD4pyjfW1x5te{b(n#dEI=3}8E;A-0DV>(C z2`)wbWn!j%F@dCb83VNP*haR)tcQcz6@Z-exXDbR1%)Otayfc+B9R)AeHm{z zO)&=ZY)~tqoMd4Kdkia=Y4Udb*>MI8%Hz;JG-zp%Q7ifiV+3`zp&xm`#RK%cFsya-dI93>g*(>G3Pdi+!uzG^*p|IOH2cYG%(Jv5bca_)Hx=M79xuEBj&+R9C#Bn2TbJo zx^g-$)lT}+VPY&l!7s1b(CL_V9O?scol*F80gjJe26hl zeIo+rQLI}=$n9=hhX}kUlcAmW-v~O+tgLM5D@K6F7|?>Y?GvdFuqLGw1N6f&5=(S> zdMMrK4-!RUVxinPv{OVMpq|1KoZ~D9=Rk>$?}7UmqFFH@567@ox{;g=c$eQZ0&i13 z@Xh81Hc;5vKLXmZ4&a)f5#*DDIE2lMbo>xL4$^V#lZFUY?UWDwz{+-Llfv9pL!JxR z%29Z>@IiykP-wG>F?gm?))|YY**FK{D$b0%sY$U}i3<|rk`fZL7R1J-XC+P>5tEjj zmXSGQ#v)>@ZDCYede0*KQIgcClsK}d6Nlil0`*L0dR$CWyu5u0{8nD;Q*zs{3RAI} zP?R!h;vm0~s-;a7-=;s6l4u>9?W`v=Or`-sJI!TAMW|vtlFzo78L;8az&0HyxgD?0 zz>`+YCMeky&r2o^!EVWgl)K2G-&95F`0A;m-YJTke43q*rp)|I!eO}Vs4NHJ4o!{Y zdt*4`W;+8>9%ec_?`6ctFh{bZydziHfltK4q(|z;MUD*@lb8&L)+=nBBmkzzFeMMu zMaT4U6Y?#F0Rt3xp&c-6BS42b0=0^!Zr~Xwqp^bxTZPGLqHNj$DyFn2-Kyke)vjho zfmzwXCkyx_I@r%u6Z)XbV>22ZB<+y?Y~~ibI?#4gJJGal?W>JEXQLQy_@WlGeG)21 z7c@sx-zrU!1#s#H!Is@2aufXk)J54XQ;O!y%CymoQX${5&SWk-c_=Tryei8OZTbVc zawl-M-NgdQrVKiq(TE+79pg%oc1W0!Igm)S908{sQ`m+%zQ3RLrO0{{+j*}jO|s6! z_Q$Y}mnFA$hzWy@wyn@cEoz7Znr%1E`-nShv%PstL19=0g^-380QIc6Y$$~HHhN;m zb(4GqqG^lDJUxhsWL0_eGYqhKgK14f;4x~~HmnbNGRo?yMG zBR&jT;ha#t5>KNO=VI9jFyq~P1gI7+J1Pr1RGiNzMckt>r3j{#496I|hvaN|AC;+M zVaId;1k*%9%z?wGVrNXqCm@+T)oB@!S0E^n9*TnawlRid8fOlh#{d@Ft_HoR!@jp| ze{tTdre=xU7>IhA=NHC_I>H%W6n98UW(PX=Ond0^ATO6P1V9iZfT_t$#$W@oJ&eaE z#U;mrUXzRG(om7RBnY-BazD)qVLXMHxP7|q2tF)EP?e0FXcV_yf9M$ZG({kfYK+`t zXK5sBacc%fCqS_cP%6&`Hiidp4Gq93wgv}qSVzI+#NIKR29rr-g_O{g?QjN}ibR>R z{T{{(HomDyC?|A!kXs&Ms|^iJ@R`!v;` z4L(*9*~~~KQ`RkPW4)ac<@PG8DRP;%@>G^P*`<=b(e0GR=$7E)jbXWdKd^&2ij%DypiV$8`7ob-p@|e!K9Y!Yvz=^UltUthuzkq< zWHdXQB>SdtJpUuRM}#hhCFlThVCVRLHnt8ioq#E8#JdI?*}veiIVdoAWX}K9pZvj{ z{-c2$hR`F6F_QT(I+MMVK0|sTS#TrE8;@z+#U{nGB6k}xW&ch8wZ;l^*ZL23oz;I z1=``ZH3e2mIs`=mEYgA+u~z|8VG7}_1hc)2F{UWn%4b^#dZ4c``S5M8a61YOkd+UG z5PSXLxiZ3b1XAeGlnEMW&vc2$g4$F`_wAb|u@|UAGbDT%wlO){p4rTcRCZf)q9C+q zI;6BQvZ;{bj;29+Cz}EB&e`lo>2GZ2LwhG3jbCOj%rZL(9{gL#*khz~fK&aW(mpZF+zM&4)cwf9hVV}6MiX_HzqALGYbc}D9(<4(eg=LY;HzUOx(zJ zo>98h_y+0%1RP$#qA#t`mlWqQx(^vXa(D=xOu8?4T*!TtmOa50ngncW@LMOs(hJYG z+`Pc2Y}m`lOO4ZOcJ*1zJAw zklRuls5feO`#6+EC5Cn|rx5M9ajePr1ry%wxFWj(u_4}Qx$ET2!EO476(=XP!OOc_ z&J%K9uow`$AS#OafmPK*{ofX|JTE(Rqm)M(o!i9TLB*6e^(n2o5o+3$(*}NfnNW*t zeGV^te77IrO68dr+?g2%mHPSiV5Y*-c!koz7QWP}WO;r0DQOuG`XZ~o3A4(y4n1W6y?;G4q$bpteeZDrm&lZ zO-*e#n@3G~H=DvKPNG`)X$s5}l43)J`M8e2 zeMTJoS3i(I+&3yYdP+)edR!DGZ1bFwI6lwH0Pk!kJ%8RdeMVe-9EdC?E;l)CN-Q1W zM|q<{qJ=>Sz6gWi_&Q|xSa^FNoit)_;K<=43&nzHN9A$c*3)dOpr{%vFSwIZ9fh@ zX$^&1%${)eEvF$^xf@uK_nA`}c<=NaYL()fg;#?l9hIS$TskSoA(>E0-}xhl5>6G% z%Z-pk?e%dvMwJlq28YW2O#~8RfK9njh#?MAt-Y-FN};+OpSNv3?=IDIcrIcBI+IoS z=3QnkU=O(%N9A(Zd2aqxmf|5!8nHnaYbbEP3ZDl=)XJs-MKa3Mo95G|7I_cQXl$uw zCy$Icn}M<&@{Alknv`Me2qU*z*fj&uoB&d~x?Nao;;~~*>D|2XKFH!)jz>1a9tqwC0?+Q65idOSV zXxa|<t;L`&=P3j}tQ3#a3>Ni!3`YAq*qq^hTlwK@g`6_zp(v1v zoeg-|voGEAjs--h9Sf_BYqQjH8n#V9jw?u~+akyrk>Sz8)v##cJ%s59kqCnjJQ2Pe z7cIPjAn*BP-tcX`q2912Vv8E^U@l`CFpR#l@u34F+my=nX0~R+_r+kH8NMOdHZ7P! zu)zW+!|)*@xTu2c!bgv?d5G#IoGTV^{y8V*EfuG{BMU7V#~t6<;gL@V&`i|+-ekTs zaRUhd>+-Ozm~GbWpux`Vb+CNH_c!w&jjdxatJwh`!8fkcjxxecr)AK4wvQWok`c$w zWBHzTeB&GD2bK5O=Z*IXN%3RH>G<_NTrKF#iZdgFkF{bbigU8uv2Enk%Egy^bI7+d*giEej1Yz zlWQ|-+*Cf&dlF$0iDNL{xSr&+OjuwaNQJ$X*<_sB!c{!kG7}Y@mXUQ6>0qVJN=lsq zY_(e^_CqFQCE5{`IVC+E1(UJ&Ceq6mh?`2wl%96W%!*4+ZWn?M%2lU#uz@(?OPvn2 zw_7(;fn}6VNmaGJgSyq}JF8oruA{n{D(%pn?Zc=}2b+9uTD*;V+ijCNy}kR}FFPqU z2_(mjeNoAj^8pxMlCqMbl9L`}CjT4fYe#)<7cO;O9h+5dTwgmz!yDG8>RWtM0Z#1j zu`i6u8|Le%@20?*rNUCX<@~p8WJ9_WoU6<=?ber;kl<)LbQ%vgO*@R=wT~NcPj~^$ zg~PvG&J`KEYCkQbL%a2{U6@tn*;5Jv`9>$(h#>r%*&Gn*@UKgyAb26rL@@x@J8|_v z=!JMF!WhKIBaA~l09P%pLlEvkJRet@d!TL##fZbTrLY*+`3PjVNJp50crL<&h)=?m zY%lbkxG2OM5Vj!xCBinuF{KIL<9YyL58`JL&LiH6a2fGtTup)!lA>595Fv4iWCnAI) z9*FC3Tn8cCjrc5F^|%@k<{sz?Khwu*K>u}wG>!%3+M*Jy+ z=MaAd;ctjbxURx=8NyP;D_sR)ISAoNglfcJ!u9XCK8x@);vXS=f_N>$XNbRz>-)I= z1K~Bqf5P=RuBQ-AAbt_oYq+)`{DJr`gl`c45#cc6J8=C9*DnyPh{qtrBc6j;Elh==1k0oPFoA&B=z=!>`?LVv{F zaqWp~Hw0J2iKk!~Fn$4&5GEqf_gNIVR6aJV)ry1ji9fJ+vfZz}ck;7Y+NmOa=Y|Z4 z^aV}qv7?=KI6xr#lv2uaN)Po@R|;oGW_%=+lCb*ZG`6$ns$xV1FoMfmM%y1|mtjj3 zoFee^zO%4p#|Rg+9lj^0x$=8k769H}WzI0OqC0P*8va?Vs{bNAAC#Yj#XvW3hpPmltexm?T8XN_qAut*OqaiRF N0;3@?8Ulkm1OW4Len$WR diff --git a/app/library/helperapps/vorbiscomment.exe b/app/library/helperapps/vorbiscomment.exe deleted file mode 100644 index 8d19c27f804a9c6b032ffcdd589069c4e7a54e9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 299810 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

RLM#<6{=s;Ii0HVpM&g&m+ax{yjX4c@(KuJQN7 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 z%0D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 Qoverwrite_tags = true; @@ -514,4 +514,4 @@ private function getCacheKey($key) { return 'track-' . $this->id . '-' . $key; } - } \ No newline at end of file + }