module.audio.flac.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  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. /////////////////////////////////////////////////////////////////
  7. // See readme.txt for more details //
  8. /////////////////////////////////////////////////////////////////
  9. // //
  10. // module.audio.flac.php //
  11. // module for analyzing FLAC and OggFLAC audio files //
  12. // dependencies: module.audio.ogg.php //
  13. // ///
  14. /////////////////////////////////////////////////////////////////
  15. getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true);
  16. class getid3_flac extends getid3_handler
  17. {
  18. 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
  19. function Analyze() {
  20. $info = &$this->getid3->info;
  21. // http://flac.sourceforge.net/format.html
  22. $this->fseek($info['avdataoffset'], SEEK_SET);
  23. $StreamMarker = $this->fread(4);
  24. $magic = 'fLaC';
  25. if ($StreamMarker != $magic) {
  26. $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"';
  27. return false;
  28. }
  29. $info['fileformat'] = 'flac';
  30. $info['audio']['dataformat'] = 'flac';
  31. $info['audio']['bitrate_mode'] = 'vbr';
  32. $info['audio']['lossless'] = true;
  33. return $this->FLACparseMETAdata();
  34. }
  35. function FLACparseMETAdata() {
  36. $info = &$this->getid3->info;
  37. do {
  38. $METAdataBlockOffset = $this->ftell();
  39. $METAdataBlockHeader = $this->fread(4);
  40. $METAdataLastBlockFlag = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x80);
  41. $METAdataBlockType = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x7F;
  42. $METAdataBlockLength = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 1, 3));
  43. $METAdataBlockTypeText = getid3_flac::FLACmetaBlockTypeLookup($METAdataBlockType);
  44. if ($METAdataBlockLength < 0) {
  45. $info['error'][] = 'corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset;
  46. break;
  47. }
  48. $info['flac'][$METAdataBlockTypeText]['raw'] = array();
  49. $ThisFileInfo_flac_METAdataBlockTypeText_raw = &$info['flac'][$METAdataBlockTypeText]['raw'];
  50. $ThisFileInfo_flac_METAdataBlockTypeText_raw['offset'] = $METAdataBlockOffset;
  51. $ThisFileInfo_flac_METAdataBlockTypeText_raw['last_meta_block'] = $METAdataLastBlockFlag;
  52. $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type'] = $METAdataBlockType;
  53. $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type_text'] = $METAdataBlockTypeText;
  54. $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_length'] = $METAdataBlockLength;
  55. if (($METAdataBlockOffset + 4 + $METAdataBlockLength) > $info['avdataend']) {
  56. $info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset.' extends beyond end of file';
  57. break;
  58. }
  59. if ($METAdataBlockLength < 1) {
  60. $info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$METAdataBlockLength.') at offset '.$METAdataBlockOffset.' is invalid';
  61. break;
  62. }
  63. $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'] = $this->fread($METAdataBlockLength);
  64. $info['avdataoffset'] = $this->ftell();
  65. switch ($METAdataBlockTypeText) {
  66. case 'STREAMINFO': // 0x00
  67. if (!$this->FLACparseSTREAMINFO($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) {
  68. return false;
  69. }
  70. break;
  71. case 'PADDING': // 0x01
  72. // ignore
  73. break;
  74. case 'APPLICATION': // 0x02
  75. if (!$this->FLACparseAPPLICATION($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) {
  76. return false;
  77. }
  78. break;
  79. case 'SEEKTABLE': // 0x03
  80. if (!$this->FLACparseSEEKTABLE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) {
  81. return false;
  82. }
  83. break;
  84. case 'VORBIS_COMMENT': // 0x04
  85. $getid3_temp = new getID3();
  86. $getid3_temp->openfile($this->getid3->filename);
  87. $getid3_temp->info['avdataoffset'] = $this->ftell() - $METAdataBlockLength;
  88. $getid3_temp->info['audio']['dataformat'] = 'flac';
  89. $getid3_temp->info['flac'] = $info['flac'];
  90. $getid3_ogg = new getid3_ogg($getid3_temp);
  91. $getid3_ogg->ParseVorbisCommentsFilepointer();
  92. $maybe_copy_keys = array('vendor', 'comments_raw', 'comments', 'replay_gain');
  93. foreach ($maybe_copy_keys as $maybe_copy_key) {
  94. if (!empty($getid3_temp->info['ogg'][$maybe_copy_key])) {
  95. $info['ogg'][$maybe_copy_key] = $getid3_temp->info['ogg'][$maybe_copy_key];
  96. }
  97. }
  98. if (!empty($getid3_temp->info['replay_gain'])) {
  99. $info['replay_gain'] = $getid3_temp->info['replay_gain'];
  100. }
  101. unset($getid3_temp, $getid3_ogg);
  102. break;
  103. case 'CUESHEET': // 0x05
  104. if (!getid3_flac::FLACparseCUESHEET($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) {
  105. return false;
  106. }
  107. break;
  108. case 'PICTURE': // 0x06
  109. if (!getid3_flac::FLACparsePICTURE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) {
  110. return false;
  111. }
  112. break;
  113. default:
  114. $info['warning'][] = 'Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset;
  115. break;
  116. }
  117. } while ($METAdataLastBlockFlag === false);
  118. if (isset($info['flac']['PICTURE'])) {
  119. foreach ($info['flac']['PICTURE'] as $key => $valuearray) {
  120. if (!empty($valuearray['image_mime']) && !empty($valuearray['data'])) {
  121. $info['ogg']['comments']['picture'][] = array('image_mime'=>$valuearray['image_mime'], 'data'=>$valuearray['data']);
  122. }
  123. }
  124. }
  125. if (isset($info['flac']['STREAMINFO'])) {
  126. $info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset'];
  127. $info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8);
  128. if ($info['flac']['uncompressed_audio_bytes'] == 0) {
  129. $info['error'][] = 'Corrupt FLAC file: uncompressed_audio_bytes == zero';
  130. return false;
  131. }
  132. $info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes'];
  133. }
  134. // set md5_data_source - built into flac 0.5+
  135. if (isset($info['flac']['STREAMINFO']['audio_signature'])) {
  136. if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
  137. $info['warning'][] = 'FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)';
  138. } else {
  139. $info['md5_data_source'] = '';
  140. $md5 = $info['flac']['STREAMINFO']['audio_signature'];
  141. for ($i = 0; $i < strlen($md5); $i++) {
  142. $info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT);
  143. }
  144. if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
  145. unset($info['md5_data_source']);
  146. }
  147. }
  148. }
  149. $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
  150. if ($info['audio']['bits_per_sample'] == 8) {
  151. // special case
  152. // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
  153. // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
  154. $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';
  155. }
  156. if (!empty($info['ogg']['vendor'])) {
  157. $info['audio']['encoder'] = $info['ogg']['vendor'];
  158. }
  159. return true;
  160. }
  161. static function FLACmetaBlockTypeLookup($blocktype) {
  162. static $FLACmetaBlockTypeLookup = array();
  163. if (empty($FLACmetaBlockTypeLookup)) {
  164. $FLACmetaBlockTypeLookup[0] = 'STREAMINFO';
  165. $FLACmetaBlockTypeLookup[1] = 'PADDING';
  166. $FLACmetaBlockTypeLookup[2] = 'APPLICATION';
  167. $FLACmetaBlockTypeLookup[3] = 'SEEKTABLE';
  168. $FLACmetaBlockTypeLookup[4] = 'VORBIS_COMMENT';
  169. $FLACmetaBlockTypeLookup[5] = 'CUESHEET';
  170. $FLACmetaBlockTypeLookup[6] = 'PICTURE';
  171. }
  172. return (isset($FLACmetaBlockTypeLookup[$blocktype]) ? $FLACmetaBlockTypeLookup[$blocktype] : 'reserved');
  173. }
  174. static function FLACapplicationIDLookup($applicationid) {
  175. static $FLACapplicationIDLookup = array();
  176. if (empty($FLACapplicationIDLookup)) {
  177. // http://flac.sourceforge.net/id.html
  178. $FLACapplicationIDLookup[0x46746F6C] = 'flac-tools'; // 'Ftol'
  179. $FLACapplicationIDLookup[0x46746F6C] = 'Sound Font FLAC'; // 'SFFL'
  180. }
  181. return (isset($FLACapplicationIDLookup[$applicationid]) ? $FLACapplicationIDLookup[$applicationid] : 'reserved');
  182. }
  183. static function FLACpictureTypeLookup($type_id) {
  184. static $lookup = array (
  185. 0 => 'Other',
  186. 1 => '32x32 pixels \'file icon\' (PNG only)',
  187. 2 => 'Other file icon',
  188. 3 => 'Cover (front)',
  189. 4 => 'Cover (back)',
  190. 5 => 'Leaflet page',
  191. 6 => 'Media (e.g. label side of CD)',
  192. 7 => 'Lead artist/lead performer/soloist',
  193. 8 => 'Artist/performer',
  194. 9 => 'Conductor',
  195. 10 => 'Band/Orchestra',
  196. 11 => 'Composer',
  197. 12 => 'Lyricist/text writer',
  198. 13 => 'Recording Location',
  199. 14 => 'During recording',
  200. 15 => 'During performance',
  201. 16 => 'Movie/video screen capture',
  202. 17 => 'A bright coloured fish',
  203. 18 => 'Illustration',
  204. 19 => 'Band/artist logotype',
  205. 20 => 'Publisher/Studio logotype',
  206. );
  207. return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
  208. }
  209. function FLACparseSTREAMINFO($METAdataBlockData) {
  210. $info = &$this->getid3->info;
  211. $offset = 0;
  212. $info['flac']['STREAMINFO']['min_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2));
  213. $offset += 2;
  214. $info['flac']['STREAMINFO']['max_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2));
  215. $offset += 2;
  216. $info['flac']['STREAMINFO']['min_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3));
  217. $offset += 3;
  218. $info['flac']['STREAMINFO']['max_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3));
  219. $offset += 3;
  220. $SampleRateChannelsSampleBitsStreamSamples = getid3_lib::BigEndian2Bin(substr($METAdataBlockData, $offset, 8));
  221. $info['flac']['STREAMINFO']['sample_rate'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 0, 20));
  222. $info['flac']['STREAMINFO']['channels'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 20, 3)) + 1;
  223. $info['flac']['STREAMINFO']['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 23, 5)) + 1;
  224. $info['flac']['STREAMINFO']['samples_stream'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 28, 36));
  225. $offset += 8;
  226. $info['flac']['STREAMINFO']['audio_signature'] = substr($METAdataBlockData, $offset, 16);
  227. $offset += 16;
  228. if (!empty($info['flac']['STREAMINFO']['sample_rate'])) {
  229. $info['audio']['bitrate_mode'] = 'vbr';
  230. $info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate'];
  231. $info['audio']['channels'] = $info['flac']['STREAMINFO']['channels'];
  232. $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
  233. $info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate'];
  234. if ($info['playtime_seconds'] > 0) {
  235. $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
  236. }
  237. } else {
  238. $info['error'][] = 'Corrupt METAdata block: STREAMINFO';
  239. return false;
  240. }
  241. unset($info['flac']['STREAMINFO']['raw']);
  242. return true;
  243. }
  244. function FLACparseAPPLICATION($METAdataBlockData) {
  245. $info = &$this->getid3->info;
  246. $offset = 0;
  247. $ApplicationID = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 4));
  248. $offset += 4;
  249. $info['flac']['APPLICATION'][$ApplicationID]['name'] = getid3_flac::FLACapplicationIDLookup($ApplicationID);
  250. $info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($METAdataBlockData, $offset);
  251. $offset = $METAdataBlockLength;
  252. unset($info['flac']['APPLICATION']['raw']);
  253. return true;
  254. }
  255. function FLACparseSEEKTABLE($METAdataBlockData) {
  256. $info = &$this->getid3->info;
  257. $offset = 0;
  258. $METAdataBlockLength = strlen($METAdataBlockData);
  259. $placeholderpattern = str_repeat("\xFF", 8);
  260. while ($offset < $METAdataBlockLength) {
  261. $SampleNumberString = substr($METAdataBlockData, $offset, 8);
  262. $offset += 8;
  263. if ($SampleNumberString == $placeholderpattern) {
  264. // placeholder point
  265. getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1);
  266. $offset += 10;
  267. } else {
  268. $SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString);
  269. $info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8));
  270. $offset += 8;
  271. $info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2));
  272. $offset += 2;
  273. }
  274. }
  275. unset($info['flac']['SEEKTABLE']['raw']);
  276. return true;
  277. }
  278. function FLACparseCUESHEET($METAdataBlockData) {
  279. $info = &$this->getid3->info;
  280. $offset = 0;
  281. $info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($METAdataBlockData, $offset, 128), "\0");
  282. $offset += 128;
  283. $info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8));
  284. $offset += 8;
  285. $info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)) & 0x80);
  286. $offset += 1;
  287. $offset += 258; // reserved
  288. $info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1));
  289. $offset += 1;
  290. for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) {
  291. $TrackSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8));
  292. $offset += 8;
  293. $TrackNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1));
  294. $offset += 1;
  295. $info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset;
  296. $info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($METAdataBlockData, $offset, 12);
  297. $offset += 12;
  298. $TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1));
  299. $offset += 1;
  300. $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80);
  301. $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40);
  302. $offset += 13; // reserved
  303. $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1));
  304. $offset += 1;
  305. for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) {
  306. $IndexSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8));
  307. $offset += 8;
  308. $IndexNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1));
  309. $offset += 1;
  310. $offset += 3; // reserved
  311. $info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset;
  312. }
  313. }
  314. unset($info['flac']['CUESHEET']['raw']);
  315. return true;
  316. }
  317. function FLACparsePICTURE($meta_data_block_data) {
  318. $info = &$this->getid3->info;
  319. $picture = &$info['flac']['PICTURE'][sizeof($info['flac']['PICTURE']) - 1];
  320. $picture['offset'] = $info['flac']['PICTURE']['raw']['offset'];
  321. unset($info['flac']['PICTURE']['raw']);
  322. $offset = 0;
  323. $picture['typeid'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  324. $picture['type'] = getid3_flac::FLACpictureTypeLookup($picture['typeid']);
  325. $offset += 4;
  326. $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  327. $offset += 4;
  328. $picture['image_mime'] = substr($meta_data_block_data, $offset, $length);
  329. $offset += $length;
  330. $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  331. $offset += 4;
  332. $picture['description'] = substr($meta_data_block_data, $offset, $length);
  333. $offset += $length;
  334. $picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  335. $offset += 4;
  336. $picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  337. $offset += 4;
  338. $picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  339. $offset += 4;
  340. $picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  341. $offset += 4;
  342. $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  343. $offset += 4;
  344. $picture['data'] = substr($meta_data_block_data, $offset, $length);
  345. $offset += $length;
  346. $picture['data_length'] = strlen($picture['data']);
  347. do {
  348. if ($this->inline_attachments === false) {
  349. // skip entirely
  350. unset($picture['data']);
  351. break;
  352. }
  353. if ($this->inline_attachments === true) {
  354. // great
  355. } elseif (is_int($this->inline_attachments)) {
  356. if ($this->inline_attachments < $picture['data_length']) {
  357. // too big, skip
  358. $info['warning'][] = 'attachment at '.$picture['offset'].' is too large to process inline ('.number_format($picture['data_length']).' bytes)';
  359. unset($picture['data']);
  360. break;
  361. }
  362. } elseif (is_string($this->inline_attachments)) {
  363. $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
  364. if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
  365. // cannot write, skip
  366. $info['warning'][] = 'attachment at '.$picture['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
  367. unset($picture['data']);
  368. break;
  369. }
  370. }
  371. // if we get this far, must be OK
  372. if (is_string($this->inline_attachments)) {
  373. $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$picture['offset'];
  374. if (!file_exists($destination_filename) || is_writable($destination_filename)) {
  375. file_put_contents($destination_filename, $picture['data']);
  376. } else {
  377. $info['warning'][] = 'attachment at '.$picture['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
  378. }
  379. $picture['data_filename'] = $destination_filename;
  380. unset($picture['data']);
  381. } else {
  382. if (!isset($info['flac']['comments']['picture'])) {
  383. $info['flac']['comments']['picture'] = array();
  384. }
  385. $info['flac']['comments']['picture'][] = array('data'=>$picture['data'], 'image_mime'=>$picture['image_mime']);
  386. }
  387. } while (false);
  388. return true;
  389. }
  390. }
  391. ?>