write.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. <?php
  2. /////////////////////////////////////////////////////////////////
  3. /// getID3() by James Heinrich <info@getid3.org> //
  4. // available at https://github.com/JamesHeinrich/getID3 //
  5. // or https://www.getid3.org //
  6. // or http://getid3.sourceforge.net //
  7. // see readme.txt for more details //
  8. /////////////////////////////////////////////////////////////////
  9. /// //
  10. // write.php //
  11. // module for writing tags (APEv2, ID3v1, ID3v2) //
  12. // dependencies: getid3.lib.php //
  13. // write.apetag.php (optional) //
  14. // write.id3v1.php (optional) //
  15. // write.id3v2.php (optional) //
  16. // write.vorbiscomment.php (optional) //
  17. // write.metaflac.php (optional) //
  18. // write.lyrics3.php (optional) //
  19. // ///
  20. /////////////////////////////////////////////////////////////////
  21. if (!defined('GETID3_INCLUDEPATH')) {
  22. throw new Exception('getid3.php MUST be included before calling getid3_writetags');
  23. }
  24. if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
  25. throw new Exception('write.php depends on getid3.lib.php, which is missing.');
  26. }
  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. * The APEv2 Tag Items Keys definition says "TRACK" is correct but foobar2000 uses "TRACKNUMBER" instead
  40. * Pass data here as "TRACKNUMBER" for compatability with all formats
  41. *
  42. * @link http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html
  43. */
  44. class getid3_writetags
  45. {
  46. /**
  47. * Absolute filename of file to write tags to.
  48. *
  49. * @var string
  50. */
  51. public $filename;
  52. /**
  53. * Array of tag formats to write ('id3v1', 'id3v2.2', 'id2v2.3', 'id3v2.4', 'ape', 'vorbiscomment',
  54. * 'metaflac', 'real').
  55. *
  56. * @var array
  57. */
  58. public $tagformats = array();
  59. /**
  60. * 2-dimensional array of tag data (ex: $data['ARTIST'][0] = 'Elvis').
  61. *
  62. * @var array
  63. */
  64. public $tag_data = array(array());
  65. /**
  66. * Text encoding used for tag data ('ISO-8859-1', 'UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE', ).
  67. *
  68. * @var string
  69. */
  70. public $tag_encoding = 'ISO-8859-1';
  71. /**
  72. * If true will erase existing tag data and write only passed data; if false will merge passed data
  73. * with existing tag data.
  74. *
  75. * @var bool
  76. */
  77. public $overwrite_tags = true;
  78. /**
  79. * If true will erase remove all existing tags and only write those passed in $tagformats;
  80. * If false will ignore any tags not mentioned in $tagformats.
  81. *
  82. * @var bool
  83. */
  84. public $remove_other_tags = false;
  85. /**
  86. * ISO-639-2 3-character language code needed for some ID3v2 frames.
  87. *
  88. * @link http://www.id3.org/iso639-2.html
  89. *
  90. * @var string
  91. */
  92. public $id3v2_tag_language = 'eng';
  93. /**
  94. * Minimum length of ID3v2 tags (will be padded to this length if tag data is shorter).
  95. *
  96. * @var int
  97. */
  98. public $id3v2_paddedlength = 4096;
  99. /**
  100. * Any non-critical errors will be stored here.
  101. *
  102. * @var array
  103. */
  104. public $warnings = array();
  105. /**
  106. * Any critical errors will be stored here.
  107. *
  108. * @var array
  109. */
  110. public $errors = array();
  111. /**
  112. * Analysis of file before writing.
  113. *
  114. * @var array
  115. */
  116. private $ThisFileInfo;
  117. public function __construct() {
  118. }
  119. /**
  120. * @return bool
  121. */
  122. public function WriteTags() {
  123. if (empty($this->filename)) {
  124. $this->errors[] = 'filename is undefined in getid3_writetags';
  125. return false;
  126. } elseif (!file_exists($this->filename)) {
  127. $this->errors[] = 'filename set to non-existant file "'.$this->filename.'" in getid3_writetags';
  128. return false;
  129. }
  130. if (!is_array($this->tagformats)) {
  131. $this->errors[] = 'tagformats must be an array in getid3_writetags';
  132. return false;
  133. }
  134. // prevent duplicate tag formats
  135. $this->tagformats = array_unique($this->tagformats);
  136. // prevent trying to specify more than one version of ID3v2 tag to write simultaneously
  137. $id3typecounter = 0;
  138. foreach ($this->tagformats as $tagformat) {
  139. if (substr(strtolower($tagformat), 0, 6) == 'id3v2.') {
  140. $id3typecounter++;
  141. }
  142. }
  143. if ($id3typecounter > 1) {
  144. $this->errors[] = 'tagformats must not contain more than one version of ID3v2';
  145. return false;
  146. }
  147. $TagFormatsToRemove = array();
  148. $AllowedTagFormats = array();
  149. if (filesize($this->filename) == 0) {
  150. // empty file special case - allow any tag format, don't check existing format
  151. // could be useful if you want to generate tag data for a non-existant file
  152. $this->ThisFileInfo = array('fileformat'=>'');
  153. $AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3');
  154. } else {
  155. $getID3 = new getID3;
  156. $getID3->encoding = $this->tag_encoding;
  157. $this->ThisFileInfo = $getID3->analyze($this->filename);
  158. // check for what file types are allowed on this fileformat
  159. switch (isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : '') {
  160. case 'mp3':
  161. case 'mp2':
  162. case 'mp1':
  163. case 'riff': // maybe not officially, but people do it anyway
  164. $AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3');
  165. break;
  166. case 'mpc':
  167. $AllowedTagFormats = array('ape');
  168. break;
  169. case 'flac':
  170. $AllowedTagFormats = array('metaflac');
  171. break;
  172. case 'real':
  173. $AllowedTagFormats = array('real');
  174. break;
  175. case 'ogg':
  176. switch (isset($this->ThisFileInfo['audio']['dataformat']) ? $this->ThisFileInfo['audio']['dataformat'] : '') {
  177. case 'flac':
  178. //$AllowedTagFormats = array('metaflac');
  179. $this->errors[] = 'metaflac is not (yet) compatible with OggFLAC files';
  180. return false;
  181. case 'vorbis':
  182. $AllowedTagFormats = array('vorbiscomment');
  183. break;
  184. default:
  185. $this->errors[] = 'metaflac is not (yet) compatible with Ogg files other than OggVorbis';
  186. return false;
  187. }
  188. break;
  189. default:
  190. $AllowedTagFormats = array();
  191. break;
  192. }
  193. foreach ($this->tagformats as $requested_tag_format) {
  194. if (!in_array($requested_tag_format, $AllowedTagFormats)) {
  195. $errormessage = 'Tag format "'.$requested_tag_format.'" is not allowed on "'.(isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : '');
  196. $errormessage .= (isset($this->ThisFileInfo['audio']['dataformat']) ? '.'.$this->ThisFileInfo['audio']['dataformat'] : '');
  197. $errormessage .= '" files';
  198. $this->errors[] = $errormessage;
  199. return false;
  200. }
  201. }
  202. // List of other tag formats, removed if requested
  203. if ($this->remove_other_tags) {
  204. foreach ($AllowedTagFormats as $AllowedTagFormat) {
  205. switch ($AllowedTagFormat) {
  206. case 'id3v2.2':
  207. case 'id3v2.3':
  208. case 'id3v2.4':
  209. if (!in_array('id3v2', $TagFormatsToRemove) && !in_array('id3v2.2', $this->tagformats) && !in_array('id3v2.3', $this->tagformats) && !in_array('id3v2.4', $this->tagformats)) {
  210. $TagFormatsToRemove[] = 'id3v2';
  211. }
  212. break;
  213. default:
  214. if (!in_array($AllowedTagFormat, $this->tagformats)) {
  215. $TagFormatsToRemove[] = $AllowedTagFormat;
  216. }
  217. break;
  218. }
  219. }
  220. }
  221. }
  222. $WritingFilesToInclude = array_merge($this->tagformats, $TagFormatsToRemove);
  223. // Check for required include files and include them
  224. foreach ($WritingFilesToInclude as $tagformat) {
  225. switch ($tagformat) {
  226. case 'ape':
  227. $GETID3_ERRORARRAY = &$this->errors;
  228. getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.apetag.php', __FILE__, true);
  229. break;
  230. case 'id3v1':
  231. case 'lyrics3':
  232. case 'vorbiscomment':
  233. case 'metaflac':
  234. case 'real':
  235. $GETID3_ERRORARRAY = &$this->errors;
  236. getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.'.$tagformat.'.php', __FILE__, true);
  237. break;
  238. case 'id3v2.2':
  239. case 'id3v2.3':
  240. case 'id3v2.4':
  241. case 'id3v2':
  242. $GETID3_ERRORARRAY = &$this->errors;
  243. getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.id3v2.php', __FILE__, true);
  244. break;
  245. default:
  246. $this->errors[] = 'unknown tag format "'.$tagformat.'" in $tagformats in WriteTags()';
  247. return false;
  248. }
  249. }
  250. // Validation of supplied data
  251. if (!is_array($this->tag_data)) {
  252. $this->errors[] = '$this->tag_data is not an array in WriteTags()';
  253. return false;
  254. }
  255. // convert supplied data array keys to upper case, if they're not already
  256. foreach ($this->tag_data as $tag_key => $tag_array) {
  257. if (strtoupper($tag_key) !== $tag_key) {
  258. $this->tag_data[strtoupper($tag_key)] = $this->tag_data[$tag_key];
  259. unset($this->tag_data[$tag_key]);
  260. }
  261. }
  262. // convert source data array keys to upper case, if they're not already
  263. if (!empty($this->ThisFileInfo['tags'])) {
  264. foreach ($this->ThisFileInfo['tags'] as $tag_format => $tag_data_array) {
  265. foreach ($tag_data_array as $tag_key => $tag_array) {
  266. if (strtoupper($tag_key) !== $tag_key) {
  267. $this->ThisFileInfo['tags'][$tag_format][strtoupper($tag_key)] = $this->ThisFileInfo['tags'][$tag_format][$tag_key];
  268. unset($this->ThisFileInfo['tags'][$tag_format][$tag_key]);
  269. }
  270. }
  271. }
  272. }
  273. // Convert "TRACK" to "TRACK_NUMBER" (if needed) for compatability with all formats
  274. if (isset($this->tag_data['TRACK']) && !isset($this->tag_data['TRACK_NUMBER'])) {
  275. $this->tag_data['TRACK_NUMBER'] = $this->tag_data['TRACK'];
  276. unset($this->tag_data['TRACK']);
  277. }
  278. // Remove all other tag formats, if requested
  279. if ($this->remove_other_tags) {
  280. $this->DeleteTags($TagFormatsToRemove);
  281. }
  282. // Write data for each tag format
  283. foreach ($this->tagformats as $tagformat) {
  284. $success = false; // overridden if tag writing is successful
  285. switch ($tagformat) {
  286. case 'ape':
  287. $ape_writer = new getid3_write_apetag;
  288. if ($ape_writer->tag_data = $this->FormatDataForAPE()) {
  289. $ape_writer->filename = $this->filename;
  290. if (($success = $ape_writer->WriteAPEtag()) === false) {
  291. $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>';
  292. }
  293. } else {
  294. $this->errors[] = 'FormatDataForAPE() failed';
  295. }
  296. break;
  297. case 'id3v1':
  298. $id3v1_writer = new getid3_write_id3v1;
  299. if ($id3v1_writer->tag_data = $this->FormatDataForID3v1()) {
  300. $id3v1_writer->filename = $this->filename;
  301. if (($success = $id3v1_writer->WriteID3v1()) === false) {
  302. $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>';
  303. }
  304. } else {
  305. $this->errors[] = 'FormatDataForID3v1() failed';
  306. }
  307. break;
  308. case 'id3v2.2':
  309. case 'id3v2.3':
  310. case 'id3v2.4':
  311. $id3v2_writer = new getid3_write_id3v2;
  312. $id3v2_writer->majorversion = intval(substr($tagformat, -1));
  313. $id3v2_writer->paddedlength = $this->id3v2_paddedlength;
  314. $id3v2_writer_tag_data = $this->FormatDataForID3v2($id3v2_writer->majorversion);
  315. if ($id3v2_writer_tag_data !== false) {
  316. $id3v2_writer->tag_data = $id3v2_writer_tag_data;
  317. unset($id3v2_writer_tag_data);
  318. $id3v2_writer->filename = $this->filename;
  319. if (($success = $id3v2_writer->WriteID3v2()) === false) {
  320. $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>';
  321. }
  322. } else {
  323. $this->errors[] = 'FormatDataForID3v2() failed';
  324. }
  325. break;
  326. case 'vorbiscomment':
  327. $vorbiscomment_writer = new getid3_write_vorbiscomment;
  328. if ($vorbiscomment_writer->tag_data = $this->FormatDataForVorbisComment()) {
  329. $vorbiscomment_writer->filename = $this->filename;
  330. if (($success = $vorbiscomment_writer->WriteVorbisComment()) === false) {
  331. $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>';
  332. }
  333. } else {
  334. $this->errors[] = 'FormatDataForVorbisComment() failed';
  335. }
  336. break;
  337. case 'metaflac':
  338. $metaflac_writer = new getid3_write_metaflac;
  339. if ($metaflac_writer->tag_data = $this->FormatDataForMetaFLAC()) {
  340. $metaflac_writer->filename = $this->filename;
  341. if (($success = $metaflac_writer->WriteMetaFLAC()) === false) {
  342. $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>';
  343. }
  344. } else {
  345. $this->errors[] = 'FormatDataForMetaFLAC() failed';
  346. }
  347. break;
  348. case 'real':
  349. $real_writer = new getid3_write_real;
  350. if ($real_writer->tag_data = $this->FormatDataForReal()) {
  351. $real_writer->filename = $this->filename;
  352. if (($success = $real_writer->WriteReal()) === false) {
  353. $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>';
  354. }
  355. } else {
  356. $this->errors[] = 'FormatDataForReal() failed';
  357. }
  358. break;
  359. default:
  360. $this->errors[] = 'Invalid tag format to write: "'.$tagformat.'"';
  361. return false;
  362. }
  363. if (!$success) {
  364. return false;
  365. }
  366. }
  367. return true;
  368. }
  369. /**
  370. * @param string[] $TagFormatsToDelete
  371. *
  372. * @return bool
  373. */
  374. public function DeleteTags($TagFormatsToDelete) {
  375. foreach ($TagFormatsToDelete as $DeleteTagFormat) {
  376. $success = false; // overridden if tag deletion is successful
  377. switch ($DeleteTagFormat) {
  378. case 'id3v1':
  379. $id3v1_writer = new getid3_write_id3v1;
  380. $id3v1_writer->filename = $this->filename;
  381. if (($success = $id3v1_writer->RemoveID3v1()) === false) {
  382. $this->errors[] = 'RemoveID3v1() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v1_writer->errors)).'</LI></UL></PRE>';
  383. }
  384. break;
  385. case 'id3v2':
  386. $id3v2_writer = new getid3_write_id3v2;
  387. $id3v2_writer->filename = $this->filename;
  388. if (($success = $id3v2_writer->RemoveID3v2()) === false) {
  389. $this->errors[] = 'RemoveID3v2() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v2_writer->errors)).'</LI></UL></PRE>';
  390. }
  391. break;
  392. case 'ape':
  393. $ape_writer = new getid3_write_apetag;
  394. $ape_writer->filename = $this->filename;
  395. if (($success = $ape_writer->DeleteAPEtag()) === false) {
  396. $this->errors[] = 'DeleteAPEtag() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $ape_writer->errors)).'</LI></UL></PRE>';
  397. }
  398. break;
  399. case 'vorbiscomment':
  400. $vorbiscomment_writer = new getid3_write_vorbiscomment;
  401. $vorbiscomment_writer->filename = $this->filename;
  402. if (($success = $vorbiscomment_writer->DeleteVorbisComment()) === false) {
  403. $this->errors[] = 'DeleteVorbisComment() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $vorbiscomment_writer->errors)).'</LI></UL></PRE>';
  404. }
  405. break;
  406. case 'metaflac':
  407. $metaflac_writer = new getid3_write_metaflac;
  408. $metaflac_writer->filename = $this->filename;
  409. if (($success = $metaflac_writer->DeleteMetaFLAC()) === false) {
  410. $this->errors[] = 'DeleteMetaFLAC() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $metaflac_writer->errors)).'</LI></UL></PRE>';
  411. }
  412. break;
  413. case 'lyrics3':
  414. $lyrics3_writer = new getid3_write_lyrics3;
  415. $lyrics3_writer->filename = $this->filename;
  416. if (($success = $lyrics3_writer->DeleteLyrics3()) === false) {
  417. $this->errors[] = 'DeleteLyrics3() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $lyrics3_writer->errors)).'</LI></UL></PRE>';
  418. }
  419. break;
  420. case 'real':
  421. $real_writer = new getid3_write_real;
  422. $real_writer->filename = $this->filename;
  423. if (($success = $real_writer->RemoveReal()) === false) {
  424. $this->errors[] = 'RemoveReal() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $real_writer->errors)).'</LI></UL></PRE>';
  425. }
  426. break;
  427. default:
  428. $this->errors[] = 'Invalid tag format to delete: "'.$DeleteTagFormat.'"';
  429. return false;
  430. }
  431. if (!$success) {
  432. return false;
  433. }
  434. }
  435. return true;
  436. }
  437. /**
  438. * @param string $TagFormat
  439. * @param array $tag_data
  440. *
  441. * @return bool
  442. * @throws Exception
  443. */
  444. public function MergeExistingTagData($TagFormat, &$tag_data) {
  445. // Merge supplied data with existing data, if requested
  446. if ($this->overwrite_tags) {
  447. // do nothing - ignore previous data
  448. } else {
  449. throw new Exception('$this->overwrite_tags=false is known to be buggy in this version of getID3. Check http://github.com/JamesHeinrich/getID3 for a newer version.');
  450. // if (!isset($this->ThisFileInfo['tags'][$TagFormat])) {
  451. // return false;
  452. // }
  453. // $tag_data = array_merge_recursive($tag_data, $this->ThisFileInfo['tags'][$TagFormat]);
  454. }
  455. return true;
  456. }
  457. /**
  458. * @return array
  459. */
  460. public function FormatDataForAPE() {
  461. $ape_tag_data = array();
  462. foreach ($this->tag_data as $tag_key => $valuearray) {
  463. switch ($tag_key) {
  464. case 'ATTACHED_PICTURE':
  465. // ATTACHED_PICTURE is ID3v2 only - ignore
  466. $this->warnings[] = '$data['.$tag_key.'] is assumed to be ID3v2 APIC data - NOT written to APE tag';
  467. break;
  468. default:
  469. foreach ($valuearray as $key => $value) {
  470. if (is_string($value) || is_numeric($value)) {
  471. $ape_tag_data[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
  472. } else {
  473. $this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to APE tag';
  474. unset($ape_tag_data[$tag_key]);
  475. break;
  476. }
  477. }
  478. break;
  479. }
  480. }
  481. $this->MergeExistingTagData('ape', $ape_tag_data);
  482. return $ape_tag_data;
  483. }
  484. /**
  485. * @return array
  486. */
  487. public function FormatDataForID3v1() {
  488. $tag_data_id3v1 = array();
  489. $tag_data_id3v1['genreid'] = 255;
  490. if (!empty($this->tag_data['GENRE'])) {
  491. foreach ($this->tag_data['GENRE'] as $key => $value) {
  492. if (getid3_id3v1::LookupGenreID($value) !== false) {
  493. $tag_data_id3v1['genreid'] = getid3_id3v1::LookupGenreID($value);
  494. break;
  495. }
  496. }
  497. }
  498. $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())));
  499. $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())));
  500. $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())));
  501. $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())));
  502. $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())));
  503. $tag_data_id3v1['track_number'] = intval(getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TRACK_NUMBER']) ? $this->tag_data['TRACK_NUMBER'] : array()))));
  504. if ($tag_data_id3v1['track_number'] <= 0) {
  505. $tag_data_id3v1['track_number'] = '';
  506. }
  507. $this->MergeExistingTagData('id3v1', $tag_data_id3v1);
  508. return $tag_data_id3v1;
  509. }
  510. /**
  511. * @param int $id3v2_majorversion
  512. *
  513. * @return array|false
  514. */
  515. public function FormatDataForID3v2($id3v2_majorversion) {
  516. $tag_data_id3v2 = array();
  517. $ID3v2_text_encoding_lookup[2] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
  518. $ID3v2_text_encoding_lookup[3] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
  519. $ID3v2_text_encoding_lookup[4] = array('ISO-8859-1'=>0, 'UTF-16'=>1, 'UTF-16BE'=>2, 'UTF-8'=>3);
  520. foreach ($this->tag_data as $tag_key => $valuearray) {
  521. $ID3v2_framename = getid3_write_id3v2::ID3v2ShortFrameNameLookup($id3v2_majorversion, $tag_key);
  522. switch ($ID3v2_framename) {
  523. case 'APIC':
  524. foreach ($valuearray as $key => $apic_data_array) {
  525. if (isset($apic_data_array['data']) &&
  526. isset($apic_data_array['picturetypeid']) &&
  527. isset($apic_data_array['description']) &&
  528. isset($apic_data_array['mime'])) {
  529. $tag_data_id3v2['APIC'][] = $apic_data_array;
  530. } else {
  531. $this->errors[] = 'ID3v2 APIC data is not properly structured';
  532. return false;
  533. }
  534. }
  535. break;
  536. case 'POPM':
  537. if (isset($valuearray['email']) &&
  538. isset($valuearray['rating']) &&
  539. isset($valuearray['data'])) {
  540. $tag_data_id3v2['POPM'][] = $valuearray;
  541. } else {
  542. $this->errors[] = 'ID3v2 POPM data is not properly structured';
  543. return false;
  544. }
  545. break;
  546. case 'GRID':
  547. if (
  548. isset($valuearray['groupsymbol']) &&
  549. isset($valuearray['ownerid']) &&
  550. isset($valuearray['data'])
  551. ) {
  552. $tag_data_id3v2['GRID'][] = $valuearray;
  553. } else {
  554. $this->errors[] = 'ID3v2 GRID data is not properly structured';
  555. return false;
  556. }
  557. break;
  558. case 'UFID':
  559. if (isset($valuearray['ownerid']) &&
  560. isset($valuearray['data'])) {
  561. $tag_data_id3v2['UFID'][] = $valuearray;
  562. } else {
  563. $this->errors[] = 'ID3v2 UFID data is not properly structured';
  564. return false;
  565. }
  566. break;
  567. case 'TXXX':
  568. foreach ($valuearray as $key => $txxx_data_array) {
  569. if (isset($txxx_data_array['description']) && isset($txxx_data_array['data'])) {
  570. $tag_data_id3v2['TXXX'][] = $txxx_data_array;
  571. } else {
  572. $this->errors[] = 'ID3v2 TXXX data is not properly structured';
  573. return false;
  574. }
  575. }
  576. break;
  577. case '':
  578. $this->errors[] = 'ID3v2: Skipping "'.$tag_key.'" because cannot match it to a known ID3v2 frame type';
  579. // some other data type, don't know how to handle it, ignore it
  580. break;
  581. default:
  582. // most other (text) frames can be copied over as-is
  583. foreach ($valuearray as $key => $value) {
  584. if (isset($ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding])) {
  585. // source encoding is valid in ID3v2 - use it with no conversion
  586. $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = $ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding];
  587. $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
  588. } else {
  589. // source encoding is NOT valid in ID3v2 - convert it to an ID3v2-valid encoding first
  590. if ($id3v2_majorversion < 4) {
  591. // convert data from other encoding to UTF-16 (with BOM)
  592. // note: some software, notably Windows Media Player and iTunes are broken and treat files tagged with UTF-16BE (with BOM) as corrupt
  593. // therefore we force data to UTF-16LE and manually prepend the BOM
  594. $ID3v2_tag_data_converted = false;
  595. if (/*!$ID3v2_tag_data_converted && */($this->tag_encoding == 'ISO-8859-1')) {
  596. // great, leave data as-is for minimum compatability problems
  597. $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0;
  598. $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
  599. $ID3v2_tag_data_converted = true;
  600. }
  601. if (!$ID3v2_tag_data_converted && ($this->tag_encoding == 'UTF-8')) {
  602. do {
  603. // if UTF-8 string does not include any characters above chr(127) then it is identical to ISO-8859-1
  604. for ($i = 0; $i < strlen($value); $i++) {
  605. if (ord($value[$i]) > 127) {
  606. break 2;
  607. }
  608. }
  609. $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0;
  610. $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
  611. $ID3v2_tag_data_converted = true;
  612. } while (false);
  613. }
  614. if (!$ID3v2_tag_data_converted) {
  615. $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 1;
  616. //$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
  617. $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
  618. $ID3v2_tag_data_converted = true;
  619. }
  620. } else {
  621. // convert data from other encoding to UTF-8
  622. $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 3;
  623. $tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
  624. }
  625. }
  626. // These values are not needed for all frame types, but if they're not used no matter
  627. $tag_data_id3v2[$ID3v2_framename][$key]['description'] = '';
  628. $tag_data_id3v2[$ID3v2_framename][$key]['language'] = $this->id3v2_tag_language;
  629. }
  630. break;
  631. }
  632. }
  633. $this->MergeExistingTagData('id3v2', $tag_data_id3v2);
  634. return $tag_data_id3v2;
  635. }
  636. /**
  637. * @return array
  638. */
  639. public function FormatDataForVorbisComment() {
  640. $tag_data_vorbiscomment = $this->tag_data;
  641. // check for multi-line comment values - split out to multiple comments if neccesary
  642. // and convert data to UTF-8 strings
  643. foreach ($tag_data_vorbiscomment as $tag_key => $valuearray) {
  644. foreach ($valuearray as $key => $value) {
  645. if (($tag_key == 'ATTACHED_PICTURE') && is_array($value)) {
  646. continue; // handled separately in write.metaflac.php
  647. } else {
  648. str_replace("\r", "\n", $value);
  649. if (strstr($value, "\n")) {
  650. unset($tag_data_vorbiscomment[$tag_key][$key]);
  651. $multilineexploded = explode("\n", $value);
  652. foreach ($multilineexploded as $newcomment) {
  653. if (strlen(trim($newcomment)) > 0) {
  654. $tag_data_vorbiscomment[$tag_key][] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $newcomment);
  655. }
  656. }
  657. } elseif (is_string($value) || is_numeric($value)) {
  658. $tag_data_vorbiscomment[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
  659. } else {
  660. $this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to VorbisComment tag';
  661. unset($tag_data_vorbiscomment[$tag_key]);
  662. break;
  663. }
  664. }
  665. }
  666. }
  667. $this->MergeExistingTagData('vorbiscomment', $tag_data_vorbiscomment);
  668. return $tag_data_vorbiscomment;
  669. }
  670. /**
  671. * @return array
  672. */
  673. public function FormatDataForMetaFLAC() {
  674. // FLAC & OggFLAC use VorbisComments same as OggVorbis
  675. // but require metaflac to do the writing rather than vorbiscomment
  676. return $this->FormatDataForVorbisComment();
  677. }
  678. /**
  679. * @return array
  680. */
  681. public function FormatDataForReal() {
  682. $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())));
  683. $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())));
  684. $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())));
  685. $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())));
  686. $this->MergeExistingTagData('real', $tag_data_real);
  687. return $tag_data_real;
  688. }
  689. }