From 0ec26ed623c46f4bf5ed7e80be655ef00fbc25b6 Mon Sep 17 00:00:00 2001 From: Afterster Date: Sat, 4 Jan 2014 21:04:30 +0100 Subject: [PATCH] Add Waveform feature and few ShoutBox links --- config/ampache.cfg.php.dist | 26 +- lib/batch.lib.php | 8 +- lib/class/shoutbox.class.php | 32 +- lib/class/update.class.php | 19 ++ lib/class/waveform.class.php | 309 +++++++++++++++++++ lib/init.php | 2 +- server/search.ajax.php | 4 +- shout.php | 6 + templates/jplayer.midnight.black-iframed.css | 30 ++ templates/show_add_shout.inc.php | 12 +- templates/show_album_row.inc.php | 4 +- templates/show_edit_shout.inc.php | 2 +- templates/show_html5_player.inc.php | 43 ++- templates/show_song.inc.php | 14 + templates/show_song_row.inc.php | 4 +- waveform.php | 40 +++ 16 files changed, 517 insertions(+), 38 deletions(-) create mode 100644 lib/class/waveform.class.php create mode 100644 waveform.php diff --git a/config/ampache.cfg.php.dist b/config/ampache.cfg.php.dist index 5ab552a4..86e615bc 100644 --- a/config/ampache.cfg.php.dist +++ b/config/ampache.cfg.php.dist @@ -7,7 +7,7 @@ ; if this config file is up to date ; this is compared against a value hard-coded ; into the init script -config_version = 12 +config_version = 13 ;################### ; Path Vars # @@ -192,23 +192,30 @@ require_localnet_session = "true" ; File Zip Download ; This settings tells Ampache to attempt to save the zip file ; to the filesystem instead of creating it in memory, you must -; also set file_zip_path in order for this to work +; also set tmp_dir_path in order for this to work ; DEFAULT: false ;file_zip_download = "false" -; File Zip Path -; If File Zip Download is enabled this must be set to tell -; Ampache which directory to save the file to. Do not put a -; trailing slash or this will not work. -; DEFAULT: false -;file_zip_path = "false" - ; File Zip Comment ; This is an optional configuration option that adds a comment ; to your zip files, this only applies if you've got allow_zip_downloads ; DEFAULT: Ampache - Zip Batch Download ;file_zip_comment = "Ampache - Zip Batch Download" +; Waveform +; This settings tells Ampache to attempt to generate a waveform +; for each song. It requires transcode and encode_args_wav settings. +; You must also set tmp_dir_path in order for this to work +; DEFAULT: false +;waveform = "false" + +; Temporary Directory Path +; If File Zip Download or Waveform is enabled this must be set to tell +; Ampache which directory to save the temporary file to. Do not put a +; trailing slash or this will not work. +; DEFAULT: false +;tmp_dir_path = "false" + ; This setting throttles a persons downloading to the specified ; bytes per second. This is not a 100% guaranteed function, and ; you should really use a server based rate limiter if you want @@ -613,6 +620,7 @@ refresh_limit = "60" ;encode_args_mp3 = "-vn -b:a %SAMPLE%K -c:a libmp3lame -f mp3 pipe:1" ;encode_args_ogg = "-vn -b:a %SAMPLE%K -c:a libvorbis -f ogg pipe:1" ;encode_args_m4a = "-vn -b:a %SAMPLE%K -c:a libfdk_aac -f adts pipe:1" +;encode_args_wav = "-vn -b:a %SAMPLE%K -c:a pcm_s16le -f wav pipe:1" ;###################################################### ; these options allow you to configure your rss-feed diff --git a/lib/batch.lib.php b/lib/batch.lib.php index ab8c696e..d7910df5 100644 --- a/lib/batch.lib.php +++ b/lib/batch.lib.php @@ -65,14 +65,14 @@ function send_zip( $name, $song_files ) { // Check if they want to save it to a file, if so then make sure they've // got a defined path as well and that it's writable. - if (AmpConfig::get('file_zip_download') && AmpConfig::get('file_zip_path')) { + if (AmpConfig::get('file_zip_download') && AmpConfig::get('tmp_dir_path')) { // Check writeable - if (!is_writable(AmpConfig::get('file_zip_path'))) { + if (!is_writable(AmpConfig::get('tmp_dir_path'))) { $in_memory = '1'; - debug_event('Error','File Zip Path:' . AmpConfig::get('file_zip_path') . ' is not writable','1'); + debug_event('Error','File Zip Path:' . AmpConfig::get('tmp_dir_path') . ' is not writable','1'); } else { $in_memory = '0'; - $basedir = AmpConfig::get('file_zip_path'); + $basedir = AmpConfig::get('tmp_dir_path'); } } else { diff --git a/lib/class/shoutbox.class.php b/lib/class/shoutbox.class.php index 17ab47b0..0d129879 100644 --- a/lib/class/shoutbox.class.php +++ b/lib/class/shoutbox.class.php @@ -44,10 +44,8 @@ class Shoutbox */ private function _get_info($shout_id) { - $sticky_id = Dba::escape($shout_id); - - $sql = "SELECT * FROM `user_shout` WHERE `id`='$shout_id'"; - $db_results = Dba::read($sql); + $sql = "SELECT * FROM `user_shout` WHERE `id` = ?"; + $db_results = Dba::read($sql, array($shout_id)); $data = Dba::fetch_assoc($db_results); @@ -169,16 +167,10 @@ class Shoutbox */ public static function create($data) { - $user = Dba::escape($GLOBALS['user']->id); - $text = Dba::escape(strip_tags($data['comment'])); - $date = time(); $sticky = isset($data['sticky']) ? 1 : 0; - $object_id = Dba::escape($data['object_id']); - $object_type = Dba::escape($data['object_type']); - - $sql = "INSERT INTO `user_shout` (`user`,`date`,`text`,`sticky`,`object_id`,`object_type`) " . - "VALUES ('$user','$date','$text','$sticky','$object_id','$object_type')"; - $db_results = Dba::write($sql); + $sql = "INSERT INTO `user_shout` (`user`,`date`,`text`,`sticky`,`object_id`,`object_type`, `data`) " . + "VALUES (? , ?, ?, ?, ?, ?, ?)"; + $db_results = Dba::write($sql, array($GLOBALS['user']->id, time(), strip_tags($data['comment']), $sticky, $data['object_id'], $data['object_type'], $data['data'])); $insert_id = Dba::insert_id(); @@ -229,5 +221,19 @@ class Shoutbox $db_results = Dba::write($sql); } // delete + + public static function get_shouts($object_type, $object_id) + { + $sql = "SELECT * FROM `user_shout` WHERE `object_type` = ? AND `object_id` = ?"; + $db_results = Dba::read($sql, array($object_type, $object_id)); + $results = array(); + + while ($row = Dba::fetch_assoc($db_results)) + { + $results[] = $row; + } + + return $results; + } } // Shoutbox class diff --git a/lib/class/update.class.php b/lib/class/update.class.php index fe198cb5..1bf42e1b 100644 --- a/lib/class/update.class.php +++ b/lib/class/update.class.php @@ -345,6 +345,9 @@ class Update $update_string = '- Add check update automatically option.
'; $version[] = array('version' => '360032','description' => $update_string); + + $update_string = '- Add song waveform as song data.
'; + $version[] = array('version' => '360033','description' => $update_string); return $version; } @@ -1968,4 +1971,20 @@ class Update return true; } + + /** + * update_360033 + * + * Add song waveform as song data + */ + public static function update_360033() + { + $sql = "ALTER TABLE `song_data` ADD `waveform` MEDIUMBLOB NULL AFTER `language`"; + Dba::write($sql); + + $sql = "ALTER TABLE `user_shout` ADD `data` VARCHAR(256) NULL AFTER `object_type`"; + Dba::write($sql); + + return true; + } } diff --git a/lib/class/waveform.class.php b/lib/class/waveform.class.php new file mode 100644 index 00000000..0ff2cce9 --- /dev/null +++ b/lib/class/waveform.class.php @@ -0,0 +1,309 @@ +id) + { + $song->format(); + $waveform = $song->waveform; + if (!$waveform) + { + $catalog = Catalog::create_from_id($song->catalog); + if ($catalog->get_type() == 'local') + { + $transcode_to = 'wav'; + $valid_types = $song->get_stream_types(); + + if ($song->type != $transcode_to) + { + $basedir = AmpConfig::get('tmp_dir_path'); + 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); + + $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 + */ + 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)) + ); + } + + protected static function create_waveform($filename) + { + $detail = 5; + $width = 400; + $height = 32; + $foreground = '#FF0000'; + $background = ''; + $draw_flat = true; + + // generate foreground color + list($r, $g, $b) = self::html2rgb($foreground); + + $handle = fopen($filename, "r"); + // wav file header retrieval + $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((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); + + // 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; + } + + // 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; + } + + 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 diff --git a/lib/init.php b/lib/init.php index a05502f4..7072428e 100644 --- a/lib/init.php +++ b/lib/init.php @@ -64,7 +64,7 @@ if (!empty($link)) { /** This is the version.... fluf nothing more... **/ $results['version'] = '3.7-develop'; -$results['int_config_version'] = '12'; +$results['int_config_version'] = '13'; if (!empty($results['force_ssl'])) { $http_type = 'https://'; diff --git a/server/search.ajax.php b/server/search.ajax.php index 5fa02696..798f533f 100644 --- a/server/search.ajax.php +++ b/server/search.ajax.php @@ -54,8 +54,8 @@ switch ($_REQUEST['action']) { $results[] = array( 'type' => T_('Artists'), 'link' => $artist->f_link, - 'label' => $artist->f_name, - 'value' => $artist->f_name, + 'label' => $artist->name, + 'value' => $artist->name, 'rels' => '', ); } diff --git a/shout.php b/shout.php index 235e5280..037e1514 100644 --- a/shout.php +++ b/shout.php @@ -50,6 +50,12 @@ switch ($_REQUEST['action']) { Error::display('general'); break; } + + $object->format(); + if (strtolower(get_class($object)) == 'song') + { + $data = $_REQUEST['offset']; + } // Now go ahead and display the page where we let them add a comment etc require_once AmpConfig::get('prefix') . '/templates/show_add_shout.inc.php'; diff --git a/templates/jplayer.midnight.black-iframed.css b/templates/jplayer.midnight.black-iframed.css index 8c332154..341611f0 100644 --- a/templates/jplayer.midnight.black-iframed.css +++ b/templates/jplayer.midnight.black-iframed.css @@ -25,6 +25,9 @@ margin-right: auto; width: 500px; min-width: 500px; + } + +div.jp-area-center { margin-top: 20px; } @@ -488,6 +491,33 @@ div.playing_lyrics a { text-decoration: none; } +div.playing_actions { + position: absolute; + top: 60px; + left: 80px; + font-size:0.5em; +} + +div.waveform { + position: absolute; + top: 50px; + left: 40px; + height: 32px; +} + +div.waveform a { + cursor: crosshair; +} + +div.waveform-time { + position: absolute; + display: block; + height: 100%; + width: 0px; + border: 1px solid #ffcaca; + left: 0px; +} + div.jp-title, div.jp-playlist { top: 0px; diff --git a/templates/show_add_shout.inc.php b/templates/show_add_shout.inc.php index 22653d05..d53162c8 100644 --- a/templates/show_add_shout.inc.php +++ b/templates/show_add_shout.inc.php @@ -20,7 +20,14 @@ * */ ?> - +f_title; +if ($data) +{ + $boxtitle .= ' (' . $data . ')'; +} +UI::show_box_top($boxtitle, 'box box_add_shout'); +?>
@@ -31,7 +38,7 @@ - + @@ -39,6 +46,7 @@ + diff --git a/templates/show_album_row.inc.php b/templates/show_album_row.inc.php index 645c839a..60144952 100644 --- a/templates/show_album_row.inc.php +++ b/templates/show_album_row.inc.php @@ -56,12 +56,12 @@ if (Art::is_enabled()) { - +
- + - + diff --git a/templates/show_edit_shout.inc.php b/templates/show_edit_shout.inc.php index 9f5c6c13..46a093fd 100644 --- a/templates/show_edit_shout.inc.php +++ b/templates/show_edit_shout.inc.php @@ -34,7 +34,7 @@
sticky == "1") { echo "checked"; } ?>/> sticky == "1") { echo "checked"; } ?>/>
diff --git a/templates/show_html5_player.inc.php b/templates/show_html5_player.inc.php index 38856dc2..27190d9c 100644 --- a/templates/show_html5_player.inc.php +++ b/templates/show_html5_player.inc.php @@ -28,6 +28,7 @@ function NavigateTo(url) ?> @@ -182,12 +217,13 @@ if (!$isVideo) {
+
-
- + - + diff --git a/waveform.php b/waveform.php new file mode 100644 index 00000000..78613def --- /dev/null +++ b/waveform.php @@ -0,0 +1,40 @@ +