PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/class/waveform.class.php

https://gitlab.com/x33n/ampache
PHP | 321 lines | 176 code | 37 blank | 108 comment | 35 complexity | ddac27894b18b15eda6f3b782af1eda7 MD5 | raw file
  1. <?php
  2. /* vim:set softtabstop=4 shiftwidth=4 expandtab: */
  3. /**
  4. *
  5. * LICENSE: GNU General Public License, version 2 (GPLv2)
  6. * Copyright 2001 - 2015 Ampache.org
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License v2
  10. * as published by the Free Software Foundation.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20. *
  21. */
  22. /**
  23. * Waveform code generation license:
  24. *
  25. *
  26. * Copyright (c) 2011, Andrew Freiday
  27. * All rights reserved.
  28. *
  29. * Redistribution and use in source and binary forms, with or without modification,
  30. * are permitted provided that the following conditions are met:
  31. *
  32. * - Redistributions of source code must retain the above copyright notice,
  33. * this list of conditions and the following disclaimer.
  34. * - Redistributions in binary form must reproduce the above copyright notice,
  35. * this list of conditions and the following disclaimer in the documentation and/or
  36. * other materials provided with the distribution.
  37. *
  38. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
  39. * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  40. * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  41. * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  42. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  43. * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  45. * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  46. * POSSIBILITY OF SUCH DAMAGE.
  47. *
  48. *
  49. * https://github.com/afreiday/php-waveform-png
  50. *
  51. */
  52. class Waveform
  53. {
  54. public $id;
  55. /**
  56. * Constructor
  57. */
  58. private function __construct()
  59. {
  60. // Static
  61. return false;
  62. } // Constructor
  63. /**
  64. * Get a song waveform.
  65. * @param int $song_id
  66. * @return binary|string|null
  67. */
  68. public static function get($song_id)
  69. {
  70. $song = new Song($song_id);
  71. $waveform = null;
  72. if ($song->id) {
  73. $song->format();
  74. $waveform = $song->waveform;
  75. if (!$waveform) {
  76. $catalog = Catalog::create_from_id($song->catalog);
  77. if ($catalog->get_type() == 'local') {
  78. $transcode_to = 'wav';
  79. $transcode_cfg = AmpConfig::get('transcode');
  80. $valid_types = $song->get_stream_types();
  81. if ($song->type != $transcode_to) {
  82. $basedir = AmpConfig::get('tmp_dir_path');
  83. if ($basedir) {
  84. if ($transcode_cfg != 'never' && in_array('transcode', $valid_types)) {
  85. $tmpfile = tempnam($basedir, $transcode_to);
  86. $tfp = fopen($tmpfile, 'wb');
  87. if (!is_resource($tfp)) {
  88. debug_event('waveform', "Failed to open " . $tmpfile, 3);
  89. return null;
  90. }
  91. $transcoder = Stream::start_transcode($song, $transcode_to);
  92. $fp = $transcoder['handle'];
  93. if (!is_resource($fp)) {
  94. debug_event('waveform', "Failed to open " . $song->file . " for waveform.", 3);
  95. return null;
  96. }
  97. do {
  98. $buf = fread($fp, 2048);
  99. fwrite($tfp, $buf);
  100. } while (!feof($fp));
  101. fclose($fp);
  102. fclose($tfp);
  103. proc_terminate($transcoder['process']);
  104. $waveform = self::create_waveform($tmpfile);
  105. //$waveform = self::create_waveform("C:\\tmp\\test.wav");
  106. @unlink($tmpfile);
  107. } else {
  108. debug_event('waveform', 'transcode setting to wav required for waveform.', '3');
  109. }
  110. } else {
  111. debug_event('waveform', 'tmp_dir_path setting required for waveform.', '3');
  112. }
  113. }
  114. // Already wav file, no transcode required
  115. else {
  116. $waveform = self::create_waveform($song->file);
  117. }
  118. }
  119. if ($waveform) {
  120. self::save_to_db($song_id, $waveform);
  121. }
  122. }
  123. }
  124. return $waveform;
  125. }
  126. protected static function findValues($byte1, $byte2)
  127. {
  128. $byte1 = hexdec(bin2hex($byte1));
  129. $byte2 = hexdec(bin2hex($byte2));
  130. return ($byte1 + ($byte2*256));
  131. }
  132. /**
  133. * Great function slightly modified as posted by Minux at
  134. * http://forums.clantemplates.com/showthread.php?t=133805
  135. * @param string $input
  136. * @return array
  137. */
  138. protected static function html2rgb($input)
  139. {
  140. $input=($input[0]=="#")?substr($input, 1,6):substr($input, 0,6);
  141. return array(
  142. hexdec(substr($input, 0, 2)),
  143. hexdec(substr($input, 2, 2)),
  144. hexdec(substr($input, 4, 2))
  145. );
  146. }
  147. /**
  148. * Create waveform from song file.
  149. * @param string $filename
  150. * @return binary|string|null
  151. */
  152. protected static function create_waveform($filename)
  153. {
  154. $detail = 5;
  155. $width = 400;
  156. $height = 32;
  157. $foreground = AmpConfig::get('waveform_color') ?: '#FF0000';
  158. $background = '';
  159. $draw_flat = true;
  160. // generate foreground color
  161. list($r, $g, $b) = self::html2rgb($foreground);
  162. $handle = fopen($filename, "r");
  163. // wav file header retrieval
  164. $heading = array();
  165. $heading[] = fread($handle, 4);
  166. $heading[] = bin2hex(fread($handle, 4));
  167. $heading[] = fread($handle, 4);
  168. $heading[] = fread($handle, 4);
  169. $heading[] = bin2hex(fread($handle, 4));
  170. $heading[] = bin2hex(fread($handle, 2));
  171. $heading[] = bin2hex(fread($handle, 2));
  172. $heading[] = bin2hex(fread($handle, 4));
  173. $heading[] = bin2hex(fread($handle, 4));
  174. $heading[] = bin2hex(fread($handle, 2));
  175. $heading[] = bin2hex(fread($handle, 2));
  176. $heading[] = fread($handle, 4);
  177. $heading[] = bin2hex(fread($handle, 4));
  178. // wav bitrate
  179. $peek = hexdec(substr($heading[10], 0, 2));
  180. $byte = $peek / 8;
  181. // checking whether a mono or stereo wav
  182. $channel = hexdec(substr($heading[6], 0, 2));
  183. $ratio = ($channel == 2 ? 40 : 80);
  184. // start putting together the initial canvas
  185. // $data_size = (size_of_file - header_bytes_read) / skipped_bytes + 1
  186. $data_size = floor((Core::get_filesize($filename) - 44) / ($ratio + $byte) + 1);
  187. $data_point = 0;
  188. // create original image width based on amount of detail
  189. // each waveform to be processed with be $height high, but will be condensed
  190. // and resized later (if specified)
  191. $img = imagecreatetruecolor($data_size / $detail, $height);
  192. // fill background of image
  193. if ($background == "") {
  194. // transparent background specified
  195. imagesavealpha($img, true);
  196. $transparentColor = imagecolorallocatealpha($img, 0, 0, 0, 127);
  197. imagefill($img, 0, 0, $transparentColor);
  198. } else {
  199. list($br, $bg, $bb) = self::html2rgb($background);
  200. imagefilledrectangle($img, 0, 0, (int) ($data_size / $detail), $height, imagecolorallocate($img, $br, $bg, $bb));
  201. } while (!feof($handle) && $data_point < $data_size) {
  202. if ($data_point++ % $detail == 0) {
  203. $bytes = array();
  204. // get number of bytes depending on bitrate
  205. for ($i = 0; $i < $byte; $i++)
  206. $bytes[$i] = fgetc($handle);
  207. switch ($byte) {
  208. // get value for 8-bit wav
  209. case 1:
  210. $data = self::findValues($bytes[0], $bytes[1]);
  211. break;
  212. // get value for 16-bit wav
  213. case 2:
  214. if(ord($bytes[1]) & 128)
  215. $temp = 0;
  216. else
  217. $temp = 128;
  218. $temp = chr((ord($bytes[1]) & 127) + $temp);
  219. $data = floor(self::findValues($bytes[0], $temp) / 256);
  220. break;
  221. default:
  222. $data = 0;
  223. break;
  224. }
  225. // skip bytes for memory optimization
  226. fseek($handle, $ratio, SEEK_CUR);
  227. // draw this data point
  228. // relative value based on height of image being generated
  229. // data values can range between 0 and 255
  230. $v = (int) ($data / 255 * $height);
  231. // don't print flat values on the canvas if not necessary
  232. if (!($v / $height == 0.5 && !$draw_flat))
  233. // draw the line on the image using the $v value and centering it vertically on the canvas
  234. imageline(
  235. $img,
  236. // x1
  237. (int) ($data_point / $detail),
  238. // y1: height of the image minus $v as a percentage of the height for the wave amplitude
  239. $height - $v,
  240. // x2
  241. (int) ($data_point / $detail),
  242. // y2: same as y1, but from the bottom of the image
  243. $height - ($height - $v),
  244. imagecolorallocate($img, $r, $g, $b)
  245. );
  246. } else {
  247. // skip this one due to lack of detail
  248. fseek($handle, $ratio + $byte, SEEK_CUR);
  249. }
  250. }
  251. // close and cleanup
  252. fclose($handle);
  253. ob_start();
  254. // want it resized?
  255. if ($width) {
  256. // resample the image to the proportions defined in the form
  257. $rimg = imagecreatetruecolor($width, $height);
  258. // save alpha from original image
  259. imagesavealpha($rimg, true);
  260. imagealphablending($rimg, false);
  261. // copy to resized
  262. imagecopyresampled($rimg, $img, 0, 0, 0, 0, $width, $height, imagesx($img), imagesy($img));
  263. imagepng($rimg);
  264. imagedestroy($rimg);
  265. } else {
  266. imagepng($img);
  267. }
  268. imagedestroy($img);
  269. $imgdata = ob_get_contents();
  270. ob_clean ();
  271. return $imgdata;
  272. }
  273. /**
  274. * Save waveform to db.
  275. * @param int $song_id
  276. * @param binary|string $waveform
  277. * @return boolean
  278. */
  279. protected static function save_to_db($song_id, $waveform)
  280. {
  281. $sql = "UPDATE `song_data` SET `waveform` = ? WHERE `song_id` = ?";
  282. return Dba::write($sql, array($waveform, $song_id));
  283. }
  284. } // Waveform class