write.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. <?php
  2. /////////////////////////////////////////////////////////////////
  3. /// getID3() by James Heinrich <info@getid3.org> //
  4. // available at http://getid3.sourceforge.net //
  5. // or http://www.getid3.org //
  6. // also https://github.com/JamesHeinrich/getID3 //
  7. /////////////////////////////////////////////////////////////////
  8. // See readme.txt for more details //
  9. /////////////////////////////////////////////////////////////////
  10. /// //
  11. // write.php //
  12. // module for writing tags (APEv2, ID3v1, ID3v2) //
  13. // dependencies: getid3.lib.php //
  14. // write.apetag.php (optional) //
  15. // write.id3v1.php (optional) //
  16. // write.id3v2.php (optional) //
  17. // write.vorbiscomment.php (optional) //
  18. // write.metaflac.php (optional) //
  19. // write.lyrics3.php (optional) //
  20. // ///
  21. /////////////////////////////////////////////////////////////////
  22. if (!defined('GETID3_INCLUDEPATH')) {
  23. throw new Exception('getid3.php MUST be included before calling getid3_writetags');
  24. }
  25. if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
  26. throw new Exception('write.php depends on getid3.lib.php, which is missing.');
  27. }
  28. // NOTES:
  29. //
  30. // You should pass data here with standard field names as follows:
  31. // * TITLE
  32. // * ARTIST
  33. // * ALBUM
  34. // * TRACKNUMBER
  35. // * COMMENT
  36. // * GENRE
  37. // * YEAR
  38. // * ATTACHED_PICTURE (ID3v2 only)
  39. //
  40. // http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html
  41. // The APEv2 Tag Items Keys definition says "TRACK" is correct but foobar2000 uses "TRACKNUMBER" instead
  42. // Pass data here as "TRACKNUMBER" for compatability with all formats
  43. class getid3_writetags
  44. {
  45. // public
  46. public $filename; // absolute filename of file to write tags to
  47. public $tagformats = array(); // array of tag formats to write ('id3v1', 'id3v2.2', 'id2v2.3', 'id3v2.4', 'ape', 'vorbiscomment', 'metaflac', 'real')
  48. public $tag_data = array(array()); // 2-dimensional array of tag data (ex: $data['ARTIST'][0] = 'Elvis')
  49. public $tag_encoding = 'ISO-8859-1'; // text encoding used for tag data ('ISO-8859-1', 'UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE', )
  50. 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
  51. 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
  52. public $id3v2_tag_language = 'eng'; // ISO-639-2 3-character language code needed for some ID3v2 frames (http://www.id3.org/iso639-2.html)
  53. public $id3v2_paddedlength = 4096; // minimum length of ID3v2 tags (will be padded to this length if tag data is shorter)
  54. public $warnings = array(); // any non-critical errors will be stored here
  55. public $errors = array(); // any critical errors will be stored here
  56. // private
  57. private $ThisFileInfo; // analysis of file before writing
  58. public function __construct() {
  59. return true;
  60. }
  61. public function WriteTags() {
  62. if (empty($this->filename)) {
  63. $this->errors[] = 'filename is undefined in getid3_writetags';
  64. return false;
  65. } elseif (!file_exists($this->filename)) {
  66. $this->errors[] = 'filename set to non-existant file "'.$this->filename.'" in getid3_writetags';
  67. return false;
  68. }
  69. if (!is_array($this->tagformats)) {
  70. $this->errors[] = 'tagformats must be an array in getid3_writetags';
  71. return false;
  72. }
  73. $TagFormatsToRemove = array();
  74. if (filesize($this->filename) == 0) {
  75. // empty file special case - allow any tag format, don't check existing format
  76. // could be useful if you want to generate tag data for a non-existant file
  77. $this->ThisFileInfo = array('fileformat'=>'');
  78. $AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3');
  79. } else {
  80. $getID3 = new getID3;
  81. $getID3->encoding = $this->tag_encoding;
  82. $this->ThisFileInfo = $getID3->analyze($this->filename);
  83. // check for what file types are allowed on this fileformat
  84. switch (isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : '') {
  85. case 'mp3':
  86. case 'mp2':
  87. case 'mp1':
  88. case 'riff': // maybe not officially, but people do it anyway
  89. $AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3');
  90. break;
  91. case 'mpc':
  92. $AllowedTagFormats = array('ape');
  93. break;
  94. case 'flac':
  95. $AllowedTagFormats = array('metaflac');
  96. break;
  97. case 'real':
  98. $AllowedTagFormats = array('real');
  99. break;
  100. case 'ogg':
  101. switch (isset($this->ThisFileInfo['audio']['dataformat']) ? $this->ThisFileInfo['audio']['dataformat'] : '') {
  102. case 'flac':
  103. //$AllowedTagFormats = array('metaflac');
  104. $this->errors[] = 'metaflac is not (yet) compatible with OggFLAC files';
  105. return false;
  106. break;
  107. case 'vorbis':
  108. $AllowedTagFormats = array('vorbiscomment');
  109. break;
  110. default:
  111. $this->errors[] = 'metaflac is not (yet) compatible with Ogg files other than OggVorbis';
  112. return false;
  113. break;
  114. }
  115. break;
  116. default:
  117. $AllowedTagFormats = array();
  118. break;
  119. }
  120. foreach ($this->tagformats as $requested_tag_format) {
  121. if (!in_array($requested_tag_format, $AllowedTagFormats)) {
  122. $errormessage = 'Tag format "'.$requested_tag_format.'" is not allowed on "'.(isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : '');
  123. $errormessage .= (isset($this->ThisFileInfo['audio']['dataformat']) ? '.'.$this->ThisFileInfo['audio']['dataformat'] : '');
  124. $errormessage .= '" files';
  125. $this->errors[] = $errormessage;
  126. return false;
  127. }
  128. }
  129. // List of other tag formats, removed if requested
  130. if ($this->remove_other_tags) {
  131. foreach ($AllowedTagFormats as $AllowedTagFormat) {
  132. switch ($AllowedTagFormat) {
  133. case 'id3v2.2':
  134. case 'id3v2.3':
  135. case 'id3v2.4':
  136. if (!in_array('id3v2', $TagFormatsToRemove) && !in_array('id3v2.2', $this->tagformats) && !in_array('id3v2.3', $this->tagformats) && !in_array('id3v2.4', $this->tagformats)) {
  137. $TagFormatsToRemove[] = 'id3v2';
  138. }
  139. break;
  140. default:
  141. if (!in_array($AllowedTagFormat, $this->tagformats)) {
  142. $TagFormatsToRemove[] = $AllowedTagFormat;
  143. }
  144. break;
  145. }
  146. }
  147. }
  148. }
  149. $WritingFilesToInclude = array_merge($this->tagformats, $TagFormatsToRemove);
  150. // Check for required include files and include them
  151. foreach ($WritingFilesToInclude as $tagformat) {
  152. switch ($tagformat) {
  153. case 'ape':
  154. $GETID3_ERRORARRAY = &$this->errors;
  155. getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.apetag.php', __FILE__, true);
  156. break;
  157. case 'id3v1':
  158. case 'lyrics3':
  159. case 'vorbiscomment':
  160. case 'metaflac':
  161. case 'real':
  162. $GETID3_ERRORARRAY = &$this->errors;
  163. getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.'.$tagformat.'.php', __FILE__, true);
  164. break;
  165. case 'id3v2.2':
  166. case 'id3v2.3':
  167. case 'id3v2.4':
  168. case 'id3v2':
  169. $GETID3_ERRORARRAY = &$this->errors;
  170. getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.id3v2.php', __FILE__, true);
  171. break;
  172. default:
  173. $this->errors[] = 'unknown tag format "'.$tagformat.'" in $tagformats in WriteTags()';
  174. return false;
  175. break;
  176. }
  177. }
  178. // Validation of supplied data
  179. if (!is_array($this->tag_data)) {
  180. $this->errors[] = '$this->tag_data is not an array in WriteTags()';
  181. return false;
  182. }
  183. // convert supplied data array keys to upper case, if they're not already
  184. foreach ($this->tag_data as $tag_key => $tag_array) {
  185. if (strtoupper($tag_key) !== $tag_key) {
  186. $this->tag_data[strtoupper($tag_key)] = $this->tag_data[$tag_key];
  187. unset($this->tag_data[$tag_key]);
  188. }
  189. }
  190. // convert source data array keys to upper case, if they're not already
  191. if (!empty($this->ThisFileInfo['tags'])) {
  192. foreach ($this->ThisFileInfo['tags'] as $tag_format => $tag_data_array) {
  193. foreach ($tag_data_array as $tag_key => $tag_array) {
  194. if (strtoupper($tag_key) !== $tag_key) {
  195. $this->ThisFileInfo['tags'][$tag_format][strtoupper($tag_key)] = $this->ThisFileInfo['tags'][$tag_format][$tag_key];
  196. unset($this->ThisFileInfo['tags'][$tag_format][$tag_key]);
  197. }
  198. }
  199. }
  200. }
  201. // Convert "TRACK" to "TRACKNUMBER" (if needed) for compatability with all formats
  202. if (isset($this->tag_data['TRACK']) && !isset($this->tag_data['TRACKNUMBER'])) {
  203. $this->tag_data['TRACKNUMBER'] = $this->tag_data['TRACK'];
  204. unset($this->tag_data['TRACK']);
  205. }
  206. // Remove all other tag formats, if requested
  207. if ($this->remove_other_tags) {
  208. $this->DeleteTags($TagFormatsToRemove);
  209. }
  210. // Write data for each tag format
  211. foreach ($this->tagformats as $tagformat) {
  212. $success = false; // overridden if tag writing is successful
  213. switch ($tagformat) {
  214. case 'ape':
  215. $ape_writer = new getid3_write_apetag;
  216. if (($ape_writer->tag_data = $this->FormatDataForAPE()) !== false) {
  217. $ape_writer->filename = $this->filename;
  218. if (($success = $ape_writer->WriteAPEtag()) === false) {
  219. $this->errors[] = 'WriteAPEtag() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $ape_writer->errors)))).'</li></ul></pre>';
  220. }
  221. } else {
  222. $this->errors[] = 'FormatDataForAPE() failed';
  223. }
  224. break;
  225. case 'id3v1':
  226. $id3v1_writer = new getid3_write_id3v1;
  227. if (($id3v1_writer->tag_data = $this->FormatDataForID3v1()) !== false) {
  228. $id3v1_writer->filename = $this->filename;
  229. if (($success = $id3v1_writer->WriteID3v1()) === false) {
  230. $this->errors[] = 'WriteID3v1() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $id3v1_writer->errors)))).'</li></ul></pre>';
  231. }
  232. } else {
  233. $this->errors[] = 'FormatDataForID3v1() failed';
  234. }
  235. break;
  236. case 'id3v2.2':
  237. case 'id3v2.3':
  238. case 'id3v2.4':
  239. $id3v2_writer = new getid3_write_id3v2;
  240. $id3v2_writer->majorversion = intval(substr($tagformat, -1));
  241. $id3v2_writer->paddedlength = $this->id3v2_paddedlength;
  242. if (($id3v2_writer->tag_data = $this->FormatDataForID3v2($id3v2_writer->majorversion)) !== false) {
  243. $id3v2_writer->filename = $this->filename;
  244. if (($success = $id3v2_writer->WriteID3v2()) === false) {
  245. $this->errors[] = 'WriteID3v2() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $id3v2_writer->errors)))).'</li></ul></pre>';
  246. }
  247. } else {
  248. $this->errors[] = 'FormatDataForID3v2() failed';
  249. }
  250. break;
  251. case 'vorbiscomment':
  252. $vorbiscomment_writer = new getid3_write_vorbiscomment;
  253. if (($vorbiscomment_writer->tag_data = $this->FormatDataForVorbisComment()) !== false) {
  254. $vorbiscomment_writer->filename = $this->filename;
  255. if (($success = $vorbiscomment_writer->WriteVorbisComment()) === false) {
  256. $this->errors[] = 'WriteVorbisComment() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $vorbiscomment_writer->errors)))).'</li></ul></pre>';
  257. }
  258. } else {
  259. $this->errors[] = 'FormatDataForVorbisComment() failed';
  260. }
  261. break;
  262. case 'metaflac':
  263. $metaflac_writer = new getid3_write_metaflac;
  264. if (($metaflac_writer->tag_data = $this->FormatDataForMetaFLAC()) !== false) {
  265. $metaflac_writer->filename = $this->filename;
  266. if (($success = $metaflac_writer->WriteMetaFLAC()) === false) {
  267. $this->errors[] = 'WriteMetaFLAC() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $metaflac_writer->errors)))).'</li></ul></pre>';
  268. }
  269. } else {
  270. $this->errors[] = 'FormatDataForMetaFLAC() failed';
  271. }
  272. break;
  273. case 'real':
  274. $real_writer = new getid3_write_real;
  275. if (($real_writer->tag_data = $this->FormatDataForReal()) !== false) {
  276. $real_writer->filename = $this->filename;
  277. if (($success = $real_writer->WriteReal()) === false) {
  278. $this->errors[] = 'WriteReal() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $real_writer->errors)))).'</li></ul></pre>';
  279. }
  280. } else {
  281. $this->errors[] = 'FormatDataForReal() failed';
  282. }
  283. break;
  284. default:
  285. $this->errors[] = 'Invalid tag format to write: "'.$tagformat.'"';
  286. return false;
  287. break;
  288. }
  289. if (!$success) {
  290. return false;
  291. }
  292. }
  293. return true;
  294. }
  295. public function DeleteTags($TagFormatsToDelete) {
  296. foreach ($TagFormatsToDelete as $DeleteTagFormat) {
  297. $success = false; // overridden if tag deletion is successful
  298. switch ($DeleteTagFormat) {
  299. case 'id3v1':
  300. $id3v1_writer = new getid3_write_id3v1;
  301. $id3v1_writer->filename = $this->filename;
  302. if (($success = $id3v1_writer->RemoveID3v1()) === false) {
  303. $this->errors[] = 'RemoveID3v1() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v1_writer->errors)).'</LI></UL></PRE>';
  304. }
  305. break;
  306. case 'id3v2':
  307. $id3v2_writer = new getid3_write_id3v2;
  308. $id3v2_writer->filename = $this->filename;
  309. if (($success = $id3v2_writer->RemoveID3v2()) === false) {
  310. $this->errors[] = 'RemoveID3v2() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v2_writer->errors)).'</LI></UL></PRE>';
  311. }
  312. break;
  313. case 'ape':
  314. $ape_writer = new getid3_write_apetag;
  315. $ape_writer->filename = $this->filename;
  316. if (($success = $ape_writer->DeleteAPEtag()) === false) {
  317. $this->errors[] = 'DeleteAPEtag() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $ape_writer->errors)).'</LI></UL></PRE>';
  318. }
  319. break;
  320. case 'vorbiscomment':
  321. $vorbiscomment_writer = new getid3_write_vorbiscomment;
  322. $vorbiscomment_writer->filename = $this->filename;
  323. if (($success = $vorbiscomment_writer->DeleteVorbisComment()) === false) {
  324. $this->errors[] = 'DeleteVorbisComment() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $vorbiscomment_writer->errors)).'</LI></UL></PRE>';
  325. }
  326. break;
  327. case 'metaflac':
  328. $metaflac_writer = new getid3_write_metaflac;
  329. $metaflac_writer->filename = $this->filename;
  330. if (($success = $metaflac_writer->DeleteMetaFLAC()) === false) {
  331. $this->errors[] = 'DeleteMetaFLAC() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $metaflac_writer->errors)).'</LI></UL></PRE>';
  332. }
  333. break;
  334. case 'lyrics3':
  335. $lyrics3_writer = new getid3_write_lyrics3;
  336. $lyrics3_writer->filename = $this->filename;
  337. if (($success = $lyrics3_writer->DeleteLyrics3()) === false) {
  338. $this->errors[] = 'DeleteLyrics3() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $lyrics3_writer->errors)).'</LI></UL></PRE>';
  339. }
  340. break;
  341. case 'real':
  342. $real_writer = new getid3_write_real;
  343. $real_writer->filename = $this->filename;
  344. if (($success = $real_writer->RemoveReal()) === false) {
  345. $this->errors[] = 'RemoveReal() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $real_writer->errors)).'</LI></UL></PRE>';
  346. }
  347. break;
  348. default:
  349. $this->errors[] = 'Invalid tag format to delete: "'.$tagformat.'"';
  350. return false;
  351. break;
  352. }
  353. if (!$success) {
  354. return false;
  355. }
  356. }
  357. return true;
  358. }
  359. public function MergeExistingTagData($TagFormat, &$tag_data) {
  360. // Merge supplied data with existing data, if requested
  361. if ($this->overwrite_tags) {
  362. // do nothing - ignore previous data
  363. } else {
  364. throw new Exception('$this->overwrite_tags=false is known to be buggy in this version of getID3. Will be fixed in the near future, check www.getid3.org for a newer version.');
  365. if (!isset($this->ThisFileInfo['tags'][$TagFormat])) {
  366. return false;
  367. }
  368. $tag_data = array_merge_recursive($tag_data, $this->ThisFileInfo['tags'][$TagFormat]);
  369. }
  370. return true;
  371. }
  372. public function FormatDataForAPE() {
  373. $ape_tag_data = array();
  374. foreach ($this->tag_data as $tag_key => $valuearray) {
  375. switch ($tag_key) {
  376. case 'ATTACHED_PICTURE':
  377. // ATTACHED_PICTURE is ID3v2 only - ignore
  378. $this->warnings[] = '$data['.$tag_key.'] is assumed to be ID3v2 APIC data - NOT written to APE tag';
  379. break;
  380. default:
  381. foreach ($valuearray as $key => $value) {
  382. if (is_string($value) || is_numeric($value)) {
  383. $ape_tag_data[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
  384. } else {
  385. $this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to APE tag';
  386. unset($ape_tag_data[$tag_key]);
  387. break;
  388. }
  389. }
  390. break;
  391. }
  392. }
  393. $this->MergeExistingTagData('ape', $ape_tag_data);
  394. return $ape_tag_data;
  395. }
  396. public function FormatDataForID3v1() {
  397. $tag_data_id3v1['genreid'] = 255;
  398. if (!empty($this->tag_data['GENRE'])) {
  399. foreach ($this->tag_data['GENRE'] as $key => $value) {
  400. if (getid3_id3v1::LookupGenreID($value) !== false) {
  401. $tag_data_id3v1['genreid'] = getid3_id3v1::LookupGenreID($value);
  402. break;
  403. }
  404. }
  405. }
  406. $tag_data_id3v1['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE'] ) ? $this->tag_data['TITLE'] : array())));
  407. $tag_data_id3v1['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST'] ) ? $this->tag_data['ARTIST'] : array())));
  408. $tag_data_id3v1['album'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ALBUM'] ) ? $this->tag_data['ALBUM'] : array())));
  409. $tag_data_id3v1['year'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['YEAR'] ) ? $this->tag_data['YEAR'] : array())));
  410. $tag_data_id3v1['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COMMENT'] ) ? $this->tag_data['COMMENT'] : array())));
  411. $tag_data_id3v1['track'] = intval(getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TRACKNUMBER']) ? $this->tag_data['TRACKNUMBER'] : array()))));
  412. if ($tag_data_id3v1['track'] <= 0) {
  413. $tag_data_id3v1['track'] = '';
  414. }
  415. $this->MergeExistingTagData('id3v1', $tag_data_id3v1);
  416. return $tag_data_id3v1;
  417. }
  418. public function FormatDataForID3v2($id3v2_majorversion) {
  419. $tag_data_id3v2 = array();
  420. $ID3v2_text_encoding_lookup[2] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
  421. $ID3v2_text_encoding_lookup[3] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
  422. $ID3v2_text_encoding_lookup[4] = array('ISO-8859-1'=>0, 'UTF-16'=>1, 'UTF-16BE'=>2, 'UTF-8'=>3);
  423. foreach ($this->tag_data as $tag_key => $valuearray) {
  424. $ID3v2_framename = getid3_write_id3v2::ID3v2ShortFrameNameLookup($id3v2_majorversion, $tag_key);
  425. switch ($ID3v2_framename) {
  426. case 'APIC':
  427. foreach ($valuearray as $key => $apic_data_array) {
  428. if (isset($apic_data_array['data']) &&
  429. isset($apic_data_array['picturetypeid']) &&
  430. isset($apic_data_array['description']) &&
  431. isset($apic_data_array['mime'])) {
  432. $tag_data_id3v2['APIC'][] = $apic_data_array;
  433. } else {
  434. $this->errors[] = 'ID3v2 APIC data is not properly structured';
  435. return false;
  436. }
  437. }
  438. break;
  439. case '':
  440. $this->errors[] = 'ID3v2: Skipping "'.$tag_key.'" because cannot match it to a known ID3v2 frame type';
  441. // some other data type, don't know how to handle it, ignore it
  442. break;
  443. default:
  444. // most other (text) frames can be copied over as-is
  445. foreach ($valuearray as $key => $value) {
  446. if (isset($ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding])) {
  447. // source encoding is valid in ID3v2 - use it with no conversion
  448. $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = $ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding];
  449. $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
  450. } else {
  451. // source encoding is NOT valid in ID3v2 - convert it to an ID3v2-valid encoding first
  452. if ($id3v2_majorversion < 4) {
  453. // convert data from other encoding to UTF-16 (with BOM)
  454. // note: some software, notably Windows Media Player and iTunes are broken and treat files tagged with UTF-16BE (with BOM) as corrupt
  455. // therefore we force data to UTF-16LE and manually prepend the BOM
  456. $ID3v2_tag_data_converted = false;
  457. if (!$ID3v2_tag_data_converted && ($this->tag_encoding == 'ISO-8859-1')) {
  458. // great, leave data as-is for minimum compatability problems
  459. $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0;
  460. $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
  461. $ID3v2_tag_data_converted = true;
  462. }
  463. if (!$ID3v2_tag_data_converted && ($this->tag_encoding == 'UTF-8')) {
  464. do {
  465. // if UTF-8 string does not include any characters above chr(127) then it is identical to ISO-8859-1
  466. for ($i = 0; $i < strlen($value); $i++) {
  467. if (ord($value{$i}) > 127) {
  468. break 2;
  469. }
  470. }
  471. $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0;
  472. $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
  473. $ID3v2_tag_data_converted = true;
  474. } while (false);
  475. }
  476. if (!$ID3v2_tag_data_converted) {
  477. $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 1;
  478. //$tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16', $value); // output is UTF-16LE+BOM or UTF-16BE+BOM depending on system architecture
  479. $tag_data_id3v2[$ID3v2_framename][$key]['data'] = "\xFF\xFE".getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16LE', $value); // force LittleEndian order version of UTF-16
  480. $ID3v2_tag_data_converted = true;
  481. }
  482. } else {
  483. // convert data from other encoding to UTF-8
  484. $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 3;
  485. $tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
  486. }
  487. }
  488. // These values are not needed for all frame types, but if they're not used no matter
  489. $tag_data_id3v2[$ID3v2_framename][$key]['description'] = '';
  490. $tag_data_id3v2[$ID3v2_framename][$key]['language'] = $this->id3v2_tag_language;
  491. }
  492. break;
  493. }
  494. }
  495. $this->MergeExistingTagData('id3v2', $tag_data_id3v2);
  496. return $tag_data_id3v2;
  497. }
  498. public function FormatDataForVorbisComment() {
  499. $tag_data_vorbiscomment = $this->tag_data;
  500. // check for multi-line comment values - split out to multiple comments if neccesary
  501. // and convert data to UTF-8 strings
  502. foreach ($tag_data_vorbiscomment as $tag_key => $valuearray) {
  503. foreach ($valuearray as $key => $value) {
  504. str_replace("\r", "\n", $value);
  505. if (strstr($value, "\n")) {
  506. unset($tag_data_vorbiscomment[$tag_key][$key]);
  507. $multilineexploded = explode("\n", $value);
  508. foreach ($multilineexploded as $newcomment) {
  509. if (strlen(trim($newcomment)) > 0) {
  510. $tag_data_vorbiscomment[$tag_key][] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $newcomment);
  511. }
  512. }
  513. } elseif (is_string($value) || is_numeric($value)) {
  514. $tag_data_vorbiscomment[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
  515. } else {
  516. $this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to VorbisComment tag';
  517. unset($tag_data_vorbiscomment[$tag_key]);
  518. break;
  519. }
  520. }
  521. }
  522. $this->MergeExistingTagData('vorbiscomment', $tag_data_vorbiscomment);
  523. return $tag_data_vorbiscomment;
  524. }
  525. public function FormatDataForMetaFLAC() {
  526. // FLAC & OggFLAC use VorbisComments same as OggVorbis
  527. // but require metaflac to do the writing rather than vorbiscomment
  528. return $this->FormatDataForVorbisComment();
  529. }
  530. public function FormatDataForReal() {
  531. $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())));
  532. $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())));
  533. $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())));
  534. $tag_data_real['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COMMENT'] ) ? $this->tag_data['COMMENT'] : array())));
  535. $this->MergeExistingTagData('real', $tag_data_real);
  536. return $tag_data_real;
  537. }
  538. }