1
0
Fork 0
mirror of https://github.com/Yetangitu/ampache synced 2025-10-03 01:39:28 +02:00
ampache/lib/class/waveform.class.php
Phyks (Lucas Verney) 2bb142eeb8 Fix issue #1260
Fix coding guidelines incoherences. Code should match PSR1/2 now, and
php-cs is set to check it on each commit.

Also fixed the Git hook to take into account only added, modified,
copied and renamed files (preventing errors when trying to check deleted
files).

Closes #1260.
2016-08-01 21:55:14 +02:00

337 lines
12 KiB
PHP

<?php
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
/**
*
* LICENSE: GNU Affero General Public License, version 3 (AGPLv3)
* Copyright 2001 - 2015 Ampache.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* Waveform code generation license:
*
*
* Copyright (c) 2011, Andrew Freiday
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* https://github.com/afreiday/php-waveform-png
*
*/
class Waveform
{
public $id;
/**
* Constructor
*/
private function __construct()
{
// Static
return false;
} // Constructor
/**
* Get a song waveform.
* @param int $song_id
* @return binary|string|null
*/
public static function get($song_id)
{
$song = new Song($song_id);
$waveform = null;
if ($song->id) {
$song->format();
$waveform = $song->waveform;
if (!$waveform) {
$catalog = Catalog::create_from_id($song->catalog);
if ($catalog->get_type() == 'local') {
$transcode_to = 'wav';
$transcode_cfg = AmpConfig::get('transcode');
$valid_types = $song->get_stream_types();
if ($song->type != $transcode_to) {
$basedir = Core::get_tmp_dir();
if ($basedir) {
if ($transcode_cfg != 'never' && in_array('transcode', $valid_types)) {
$tmpfile = tempnam($basedir, $transcode_to);
$tfp = fopen($tmpfile, 'wb');
if (!is_resource($tfp)) {
debug_event('waveform', "Failed to open " . $tmpfile, 3);
return null;
}
$transcoder = Stream::start_transcode($song, $transcode_to);
$fp = $transcoder['handle'];
if (!is_resource($fp)) {
debug_event('waveform', "Failed to open " . $song->file . " for waveform.", 3);
return null;
}
do {
$buf = fread($fp, 2048);
fwrite($tfp, $buf);
} while (!feof($fp));
fclose($fp);
fclose($tfp);
Stream::kill_process($transcoder);
$waveform = self::create_waveform($tmpfile);
//$waveform = self::create_waveform("C:\\tmp\\test.wav");
@unlink($tmpfile);
} else {
debug_event('waveform', 'transcode setting to wav required for waveform.', '3');
}
} else {
debug_event('waveform', 'tmp_dir_path setting required for waveform.', '3');
}
}
// Already wav file, no transcode required
else {
$waveform = self::create_waveform($song->file);
}
}
if ($waveform) {
self::save_to_db($song_id, $waveform);
}
}
}
return $waveform;
}
protected static function findValues($byte1, $byte2)
{
$byte1 = hexdec(bin2hex($byte1));
$byte2 = hexdec(bin2hex($byte2));
return ($byte1 + ($byte2*256));
}
/**
* Great function slightly modified as posted by Minux at
* http://forums.clantemplates.com/showthread.php?t=133805
* @param string $input
* @return array
*/
protected static function html2rgb($input)
{
$input=($input[0]=="#")?substr($input, 1, 6):substr($input, 0, 6);
return array(
hexdec(substr($input, 0, 2)),
hexdec(substr($input, 2, 2)),
hexdec(substr($input, 4, 2))
);
}
/**
* Create waveform from song file.
* @param string $filename
* @return binary|string|null
*/
protected static function create_waveform($filename)
{
if (!file_exists($filename)) {
debug_event('waveform', 'File ' . $filename . ' doesn\'t exists', 1);
return null;
}
if (!check_php_gd()) {
debug_event('waveform', 'GD extension must be loaded', 1);
return null;
}
$detail = 5;
$width = 400;
$height = 32;
$foreground = AmpConfig::get('waveform_color') ?: '#FF0000';
$background = '';
$draw_flat = true;
// generate foreground color
list($r, $g, $b) = self::html2rgb($foreground);
$handle = fopen($filename, "r");
// wav file header retrieval
$heading = array();
$heading[] = fread($handle, 4);
$heading[] = bin2hex(fread($handle, 4));
$heading[] = fread($handle, 4);
$heading[] = fread($handle, 4);
$heading[] = bin2hex(fread($handle, 4));
$heading[] = bin2hex(fread($handle, 2));
$heading[] = bin2hex(fread($handle, 2));
$heading[] = bin2hex(fread($handle, 4));
$heading[] = bin2hex(fread($handle, 4));
$heading[] = bin2hex(fread($handle, 2));
$heading[] = bin2hex(fread($handle, 2));
$heading[] = fread($handle, 4);
$heading[] = bin2hex(fread($handle, 4));
// wav bitrate
$peek = hexdec(substr($heading[10], 0, 2));
$byte = $peek / 8;
// checking whether a mono or stereo wav
$channel = hexdec(substr($heading[6], 0, 2));
$ratio = ($channel == 2 ? 40 : 80);
// start putting together the initial canvas
// $data_size = (size_of_file - header_bytes_read) / skipped_bytes + 1
$data_size = floor((Core::get_filesize($filename) - 44) / ($ratio + $byte) + 1);
$data_point = 0;
// create original image width based on amount of detail
// each waveform to be processed with be $height high, but will be condensed
// and resized later (if specified)
$img = imagecreatetruecolor($data_size / $detail, $height);
if ($img === false) {
debug_event('waveform', 'Cannot create image.', 1);
return null;
}
// fill background of image
if ($background == "") {
// transparent background specified
imagesavealpha($img, true);
$transparentColor = imagecolorallocatealpha($img, 0, 0, 0, 127);
imagefill($img, 0, 0, $transparentColor);
} else {
list($br, $bg, $bb) = self::html2rgb($background);
imagefilledrectangle($img, 0, 0, (int) ($data_size / $detail), $height, imagecolorallocate($img, $br, $bg, $bb));
}
while (!feof($handle) && $data_point < $data_size) {
if ($data_point++ % $detail == 0) {
$bytes = array();
// get number of bytes depending on bitrate
for ($i = 0; $i < $byte; $i++) {
$bytes[$i] = fgetc($handle);
}
switch ($byte) {
// get value for 8-bit wav
case 1:
$data = self::findValues($bytes[0], $bytes[1]);
break;
// get value for 16-bit wav
case 2:
if (ord($bytes[1]) & 128) {
$temp = 0;
} else {
$temp = 128;
}
$temp = chr((ord($bytes[1]) & 127) + $temp);
$data = floor(self::findValues($bytes[0], $temp) / 256);
break;
default:
$data = 0;
break;
}
// skip bytes for memory optimization
fseek($handle, $ratio, SEEK_CUR);
// draw this data point
// relative value based on height of image being generated
// data values can range between 0 and 255
$v = (int) ($data / 255 * $height);
// don't print flat values on the canvas if not necessary
if (!($v / $height == 0.5 && !$draw_flat)) {
// draw the line on the image using the $v value and centering it vertically on the canvas
imageline(
$img,
// x1
(int) ($data_point / $detail),
// y1: height of the image minus $v as a percentage of the height for the wave amplitude
$height - $v,
// x2
(int) ($data_point / $detail),
// y2: same as y1, but from the bottom of the image
$height - ($height - $v),
imagecolorallocate($img, $r, $g, $b)
);
}
} else {
// skip this one due to lack of detail
fseek($handle, $ratio + $byte, SEEK_CUR);
}
}
// close and cleanup
fclose($handle);
ob_start();
// want it resized?
if ($width) {
// resample the image to the proportions defined in the form
$rimg = imagecreatetruecolor($width, $height);
// save alpha from original image
imagesavealpha($rimg, true);
imagealphablending($rimg, false);
// copy to resized
imagecopyresampled($rimg, $img, 0, 0, 0, 0, $width, $height, imagesx($img), imagesy($img));
imagepng($rimg);
imagedestroy($rimg);
} else {
imagepng($img);
}
imagedestroy($img);
$imgdata = ob_get_contents();
ob_clean();
return $imgdata;
}
/**
* Save waveform to db.
* @param int $song_id
* @param binary|string $waveform
* @return boolean
*/
protected static function save_to_db($song_id, $waveform)
{
$sql = "UPDATE `song_data` SET `waveform` = ? WHERE `song_id` = ?";
return Dba::write($sql, array($waveform, $song_id));
}
} // Waveform class