1<?php 2/* 3 * vim:set softtabstop=4 shiftwidth=4 expandtab: 4 * 5 * LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later) 6 * Copyright 2001 - 2020 Ampache.org 7 * 8 * This program is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU Affero General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Affero General Public License for more details. 17 * 18 * You should have received a copy of the GNU Affero General Public License 19 * along with this program. If not, see <https://www.gnu.org/licenses/>. 20 */ 21 22declare(strict_types=0); 23 24namespace Ampache\Repository\Model; 25 26use Ampache\Module\Playback\Stream; 27use Ampache\Module\Playback\Stream_Url; 28use Ampache\Module\Song\Deletion\SongDeleterInterface; 29use Ampache\Module\Song\Tag\SongTagWriterInterface; 30use Ampache\Module\Statistics\Stats; 31use Ampache\Module\System\Dba; 32use Ampache\Module\User\Activity\UserActivityPosterInterface; 33use Ampache\Module\Util\Recommendation; 34use Ampache\Module\Util\Ui; 35use Ampache\Repository\Model\Metadata\Metadata; 36use Ampache\Module\Authorization\Access; 37use Ampache\Config\AmpConfig; 38use Ampache\Module\System\Core; 39use Ampache\Repository\LicenseRepositoryInterface; 40use PDOStatement; 41 42class Song extends database_object implements Media, library_item, GarbageCollectibleInterface 43{ 44 use Metadata; 45 46 protected const DB_TABLENAME = 'song'; 47 48 /* Variables from DB */ 49 50 /** 51 * @var integer $id 52 */ 53 public $id; 54 /** 55 * @var string $file 56 */ 57 public $file; 58 /** 59 * @var integer $album 60 */ 61 public $album; 62 /** 63 * @var integer $artist 64 */ 65 public $artist; 66 /** 67 * @var string $title 68 */ 69 public $title; 70 /** 71 * @var integer $year 72 */ 73 public $year; 74 /** 75 * @var integer $bitrate 76 */ 77 public $bitrate; 78 /** 79 * @var integer $rate 80 */ 81 public $rate; 82 /** 83 * @var string $mode 84 */ 85 public $mode; 86 /** 87 * @var integer $size 88 */ 89 public $size; 90 /** 91 * @var integer $time 92 */ 93 public $time; 94 /** 95 * @var integer $track 96 */ 97 public $track; 98 /** 99 * @var string $album_mbid 100 */ 101 public $album_mbid; 102 /** 103 * @var string $artist_mbid 104 */ 105 public $artist_mbid; 106 /** 107 * @var string $albumartist_mbid 108 */ 109 public $albumartist_mbid; 110 /** 111 * @var string $type 112 */ 113 public $type; 114 /** 115 * @var string $mime 116 */ 117 public $mime; 118 /** 119 * @var boolean $played 120 */ 121 public $played; 122 /** 123 * @var boolean $enabled 124 */ 125 public $enabled; 126 /** 127 * @var integer $addition_time 128 */ 129 public $addition_time; 130 /** 131 * @var integer $update_time 132 */ 133 public $update_time; 134 /** 135 * MusicBrainz ID 136 * @var string $mbid 137 */ 138 public $mbid; 139 /** 140 * @var integer $catalog 141 */ 142 public $catalog; 143 /** 144 * @var integer|null $waveform 145 */ 146 public $waveform; 147 /** 148 * @var integer|null $user_upload 149 */ 150 public $user_upload; 151 /** 152 * @var integer|null $license 153 */ 154 public $license; 155 /** 156 * @var string $composer 157 */ 158 public $composer; 159 /** 160 * @var string $catalog_number 161 */ 162 public $catalog_number; 163 /** 164 * @var integer $channels 165 */ 166 public $channels; 167 168 /** 169 * @var array $tags 170 */ 171 public $tags; 172 /** 173 * @var string $label 174 */ 175 public $label; 176 /** 177 * @var string $language 178 */ 179 public $language; 180 /** 181 * @var string $comment 182 */ 183 public $comment; 184 /** 185 * @var string $lyrics 186 */ 187 public $lyrics; 188 /** 189 * @var float|null $replaygain_track_gain 190 */ 191 public $replaygain_track_gain; 192 /** 193 * @var float|null $replaygain_track_peak 194 */ 195 public $replaygain_track_peak; 196 /** 197 * @var float|null $replaygain_album_gain 198 */ 199 public $replaygain_album_gain; 200 /** 201 * @var float|null $replaygain_album_peak 202 */ 203 public $replaygain_album_peak; 204 /** 205 * @var integer|null $r128_album_gain 206 */ 207 public $r128_album_gain; 208 /** 209 * @var integer|null $r128_track_gain 210 */ 211 public $r128_track_gain; 212 /** 213 * @var string $f_title 214 */ 215 public $f_title; 216 /** 217 * @var string $f_artist 218 */ 219 public $f_artist; 220 /** 221 * @var string $f_album 222 */ 223 public $f_album; 224 /** 225 * @var string $f_artist_full 226 */ 227 public $f_artist_full; 228 /** 229 * @var integer $albumartist 230 */ 231 public $albumartist; 232 /** 233 * @var string $f_albumartist_full 234 */ 235 public $f_albumartist_full; 236 /** 237 * @var string $f_album_full 238 */ 239 public $f_album_full; 240 /** 241 * @var string $f_time 242 */ 243 public $f_time; 244 /** 245 * @var string $f_time_h 246 */ 247 public $f_time_h; 248 /** 249 * @var string $f_track 250 */ 251 public $f_track; 252 /** 253 * @var string $disk 254 */ 255 public $disk; 256 /** 257 * @var string $f_bitrate 258 */ 259 public $f_bitrate; 260 /** 261 * @var string $link 262 */ 263 public $link; 264 /** 265 * @var string $f_file 266 */ 267 public $f_file; 268 /** 269 * @var string $f_title_full 270 */ 271 public $f_title_full; 272 /** 273 * @var string $f_link 274 */ 275 public $f_link; 276 /** 277 * @var string $f_album_link 278 */ 279 public $f_album_link; 280 /** 281 * @var string $f_artist_link 282 */ 283 public $f_artist_link; 284 /** 285 * @var string $f_albumartist_link 286 */ 287 public $f_albumartist_link; 288 289 /** 290 * @var string $f_year_link 291 */ 292 public $f_year_link; 293 294 /** 295 * @var string $f_tags 296 */ 297 public $f_tags; 298 /** 299 * @var string $f_size 300 */ 301 public $f_size; 302 /** 303 * @var string $f_lyrics 304 */ 305 public $f_lyrics; 306 /** 307 * @var string $f_pattern 308 */ 309 public $f_pattern; 310 /** 311 * @var integer $count 312 */ 313 public $count; 314 /** 315 * @var string $f_publisher 316 */ 317 public $f_publisher; 318 /** 319 * @var string $f_composer 320 */ 321 public $f_composer; 322 /** 323 * @var string $f_license 324 */ 325 public $f_license; 326 327 /** @var int */ 328 public $skip_cnt; 329 330 /** @var int */ 331 public $object_cnt; 332 333 /** @var int */ 334 private $total_count; 335 336 /* Setting Variables */ 337 /** 338 * @var boolean $_fake 339 */ 340 public $_fake = false; // If this is a 'construct_from_array' object 341 342 /** 343 * Aliases used in insert function 344 */ 345 public static $aliases = array( 346 'mb_trackid', 347 'mbid', 348 'mb_albumid', 349 'mb_albumid_group', 350 'mb_artistid', 351 'mb_albumartistid', 352 'genre', 353 'publisher' 354 ); 355 356 /** 357 * Constructor 358 * 359 * Song class, for modifying a song. 360 * @param integer|null $songid 361 * @param string $limit_threshold 362 */ 363 public function __construct($songid = null, $limit_threshold = '') 364 { 365 if ($songid === null) { 366 return false; 367 } 368 369 $this->id = (int)($songid); 370 371 if (self::isCustomMetadataEnabled()) { 372 $this->initializeMetadata(); 373 } 374 375 $info = $this->has_info($limit_threshold); 376 if ($info !== false && is_array($info)) { 377 foreach ($info as $key => $value) { 378 $this->$key = $value; 379 } 380 $data = pathinfo($this->file); 381 $this->type = strtolower((string)$data['extension']); 382 $this->mime = self::type_to_mime($this->type); 383 $this->object_cnt = (int)$this->total_count; 384 } else { 385 $this->id = null; 386 387 return false; 388 } 389 390 return true; 391 } // constructor 392 393 public function getId(): int 394 { 395 return (int) $this->id; 396 } 397 398 /** 399 * insert 400 * 401 * This inserts the song described by the passed array 402 * @param array $results 403 * @return integer|boolean 404 */ 405 public static function insert(array $results) 406 { 407 $check_file = Catalog::get_id_from_file($results['file'], 'song'); 408 if ($check_file > 0) { 409 return $check_file; 410 } 411 $catalog = $results['catalog']; 412 $file = $results['file']; 413 $title = Catalog::check_length(Catalog::check_title($results['title'], $file)); 414 $artist = Catalog::check_length($results['artist']); 415 $album = Catalog::check_length($results['album']); 416 $albumartist = Catalog::check_length($results['albumartist']); 417 $albumartist = $albumartist ?: null; 418 $bitrate = $results['bitrate'] ?: 0; 419 $rate = $results['rate'] ?: 0; 420 $mode = $results['mode']; 421 $size = $results['size'] ?: 0; 422 $time = $results['time'] ?: 0; 423 $track = Catalog::check_track((string) $results['track']); 424 $track_mbid = $results['mb_trackid'] ?: $results['mbid']; 425 $track_mbid = $track_mbid ?: null; 426 $album_mbid = $results['mb_albumid']; 427 $album_mbid_group = $results['mb_albumid_group']; 428 $artist_mbid = $results['mb_artistid']; 429 $albumartist_mbid = $results['mb_albumartistid']; 430 $disk = (Album::sanitize_disk($results['disk']) > 0) ? Album::sanitize_disk($results['disk']) : 1; 431 $year = Catalog::normalize_year($results['year'] ?: 0); 432 $comment = $results['comment']; 433 $tags = $results['genre']; // multiple genre support makes this an array 434 $lyrics = $results['lyrics']; 435 $user_upload = isset($results['user_upload']) ? $results['user_upload'] : null; 436 $composer = isset($results['composer']) ? Catalog::check_length($results['composer']) : null; 437 $label = isset($results['publisher']) ? Catalog::get_unique_string(Catalog::check_length($results['publisher'], 128)) : null; 438 if ($label && AmpConfig::get('label')) { 439 // create the label if missing 440 foreach (array_map('trim', explode(';', $label)) as $label_name) { 441 Label::helper($label_name); 442 } 443 } 444 445 if (isset($results['license'])) { 446 $licenseRepository = static::getLicenseRepository(); 447 $licenseName = (string) $results['license']; 448 $licenseId = $licenseRepository->find($licenseName); 449 450 $license = $licenseId === 0 ? $licenseRepository->create($licenseName, '', '') : $licenseId; 451 } else { 452 $license = null; 453 } 454 455 $catalog_number = isset($results['catalog_number']) ? Catalog::check_length($results['catalog_number'], 64) : null; 456 $language = isset($results['language']) ? Catalog::check_length($results['language'], 128) : null; 457 $channels = $results['channels'] ?: 0; 458 $release_type = isset($results['release_type']) ? Catalog::check_length($results['release_type'], 32) : null; 459 $release_status = isset($results['release_status']) ? $results['release_status'] : null; 460 $replaygain_track_gain = isset($results['replaygain_track_gain']) ? $results['replaygain_track_gain'] : null; 461 $replaygain_track_peak = isset($results['replaygain_track_peak']) ? $results['replaygain_track_peak'] : null; 462 $replaygain_album_gain = isset($results['replaygain_album_gain']) ? $results['replaygain_album_gain'] : null; 463 $replaygain_album_peak = isset($results['replaygain_album_peak']) ? $results['replaygain_album_peak'] : null; 464 $r128_track_gain = isset($results['r128_track_gain']) ? $results['r128_track_gain'] : null; 465 $r128_album_gain = isset($results['r128_album_gain']) ? $results['r128_album_gain'] : null; 466 $original_year = Catalog::normalize_year($results['original_year'] ?: 0); 467 $barcode = Catalog::check_length($results['barcode'], 64); 468 469 if (!in_array($mode, ['vbr', 'cbr', 'abr'])) { 470 debug_event(self::class, 'Error analyzing: ' . $file . ' unknown file bitrate mode: ' . $mode, 2); 471 $mode = null; 472 } 473 if (!isset($results['albumartist_id'])) { 474 $albumartist_id = null; 475 if ($albumartist) { 476 // Multiple artist per songs not supported for now 477 $albumartist_mbid = Catalog::trim_slashed_list($albumartist_mbid); 478 $albumartist_id = Artist::check($albumartist, $albumartist_mbid); 479 } 480 } else { 481 $albumartist_id = (int)($results['albumartist_id']); 482 } 483 if (!isset($results['artist_id'])) { 484 // Multiple artist per songs not supported for now 485 $artist_mbid = Catalog::trim_slashed_list($artist_mbid); 486 $artist_id = Artist::check($artist, $artist_mbid); 487 } else { 488 $artist_id = (int)($results['artist_id']); 489 } 490 if (!isset($results['album_id'])) { 491 $album_id = Album::check($catalog, $album, $year, $disk, $album_mbid, $album_mbid_group, $albumartist_id, $release_type, $release_status, $original_year, $barcode, $catalog_number); 492 } else { 493 $album_id = (int)($results['album_id']); 494 } 495 $insert_time = time(); 496 497 $sql = "INSERT INTO `song` (`catalog`, `file`, `album`, `artist`, `title`, `bitrate`, `rate`, `mode`, `size`, `time`, `track`, `addition_time`, `update_time`, `year`, `mbid`, `user_upload`, `license`, `composer`, `channels`) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; 498 499 $db_results = Dba::write($sql, array( 500 $catalog, 501 $file, 502 $album_id, 503 $artist_id, 504 $title, 505 $bitrate, 506 $rate, 507 $mode, 508 $size, 509 $time, 510 $track, 511 $insert_time, 512 $insert_time, 513 $year, 514 $track_mbid, 515 $user_upload, 516 $license, 517 $composer, 518 $channels 519 )); 520 521 if (!$db_results) { 522 debug_event(self::class, 'Unable to insert ' . $file, 2); 523 524 return false; 525 } 526 527 $song_id = (int)Dba::insert_id(); 528 529 Catalog::update_map((int)$catalog, 'song', $song_id); 530 Album::update_album_counts($album_id); 531 Artist::update_artist_counts($artist_id); 532 533 if ($user_upload) { 534 static::getUserActivityPoster()->post((int) $user_upload, 'upload', 'song', (int) $song_id, time()); 535 } 536 537 // Allow scripts to populate new tags when injecting user uploads 538 if (!defined('NO_SESSION')) { 539 if ($user_upload && !Access::check('interface', 50, $user_upload)) { 540 $tags = Tag::clean_to_existing($tags); 541 } 542 } 543 if (is_array($tags)) { 544 foreach ($tags as $tag) { 545 $tag = trim((string)$tag); 546 if (!empty($tag)) { 547 Tag::add('song', $song_id, $tag, false); 548 Tag::add('album', $album_id, $tag, false); 549 Tag::add('artist', $artist_id, $tag, false); 550 } 551 } 552 } 553 554 $sql = "INSERT INTO `song_data` (`song_id`, `comment`, `lyrics`, `label`, `language`, `replaygain_track_gain`, `replaygain_track_peak`, `replaygain_album_gain`, `replaygain_album_peak`, `r128_track_gain`, `r128_album_gain`) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; 555 Dba::write($sql, array($song_id, $comment, $lyrics, $label, $language, $replaygain_track_gain, $replaygain_track_peak, $replaygain_album_gain, $replaygain_album_peak, $r128_track_gain, $r128_album_gain)); 556 557 return $song_id; 558 } 559 560 /** 561 * garbage_collection 562 * 563 * Cleans up the song_data table 564 */ 565 public static function garbage_collection() 566 { 567 // delete files matching catalog_ignore_pattern 568 $ignore_pattern = AmpConfig::get('catalog_ignore_pattern'); 569 if ($ignore_pattern) { 570 Dba::write("DELETE FROM `song` WHERE `file` REGEXP ?;", array($ignore_pattern)); 571 } 572 // delete duplicates 573 Dba::write("DELETE `dupe` FROM `song` AS `dupe`, `song` AS `orig` WHERE `dupe`.`id` > `orig`.`id` AND `dupe`.`file` <=> `orig`.`file`;"); 574 // clean up missing catalogs 575 Dba::write("DELETE FROM `song` WHERE `song`.`catalog` NOT IN (SELECT `id` FROM `catalog`);"); 576 // delete the rest 577 Dba::write("DELETE FROM `song_data` WHERE `song_data`.`song_id` NOT IN (SELECT `song`.`id` FROM `song`);"); 578 } 579 580 /** 581 * build_cache 582 * 583 * This attempts to reduce queries by asking for everything in the 584 * browse all at once and storing it in the cache, this can help if the 585 * db connection is the slow point. 586 * @param integer[] $song_ids 587 * @param string $limit_threshold 588 * @return boolean 589 */ 590 public static function build_cache($song_ids, $limit_threshold = '') 591 { 592 if (empty($song_ids)) { 593 return false; 594 } 595 596 $idlist = '(' . implode(',', $song_ids) . ')'; 597 if ($idlist == '()') { 598 return false; 599 } 600 $artists = array(); 601 $albums = array(); 602 $tags = array(); 603 604 // Song data cache 605 $sql = (AmpConfig::get('catalog_disable')) 606 ? "SELECT `song`.`id`, `file`, `catalog`, `album`, `year`, `artist`, `title`, `bitrate`, `rate`, `mode`, `size`, `time`, `track`, `played`, `song`.`enabled`, `update_time`, `tag_map`.`tag_id`, `mbid`, `addition_time`, `license`, `composer`, `user_upload`, `song`.`total_count`, `song`.`total_skip` FROM `song` LEFT JOIN `tag_map` ON `tag_map`.`object_id`=`song`.`id` AND `tag_map`.`object_type`='song' LEFT JOIN `catalog` ON `catalog`.`id` = `song`.`catalog` WHERE `song`.`id` IN $idlist AND `catalog`.`enabled` = '1' " 607 : "SELECT `song`.`id`, `file`, `catalog`, `album`, `year`, `artist`, `title`, `bitrate`, `rate`, `mode`, `size`, `time`, `track`, `played`, `song`.`enabled`, `update_time`, `tag_map`.`tag_id`, `mbid`, `addition_time`, `license`, `composer`, `user_upload`, `song`.`total_count`, `song`.`total_skip` FROM `song` LEFT JOIN `tag_map` ON `tag_map`.`object_id`=`song`.`id` AND `tag_map`.`object_type`='song' WHERE `song`.`id` IN $idlist"; 608 609 $db_results = Dba::read($sql); 610 while ($row = Dba::fetch_assoc($db_results)) { 611 if (AmpConfig::get('show_played_times')) { 612 $row['object_cnt'] = (!empty($limit_threshold)) 613 ? Stats::get_object_count('song', $row['id'], $limit_threshold) 614 : $row['total_count']; 615 } 616 if (AmpConfig::get('show_skipped_times')) { 617 $row['skip_cnt'] = (!empty($limit_threshold)) 618 ? Stats::get_object_count('song', $row['id'], $limit_threshold, 'skip') 619 : $row['total_skip']; 620 } 621 parent::add_to_cache('song', $row['id'], $row); 622 $artists[$row['artist']] = $row['artist']; 623 $albums[$row['album']] = $row['album']; 624 if ($row['tag_id']) { 625 $tags[$row['tag_id']] = $row['tag_id']; 626 } 627 } 628 629 Artist::build_cache($artists); 630 Album::build_cache($albums); 631 Tag::build_cache($tags); 632 Tag::build_map_cache('song', $song_ids); 633 Art::build_cache($albums); 634 635 // If we're rating this then cache them as well 636 if (AmpConfig::get('ratings')) { 637 Rating::build_cache('song', $song_ids); 638 } 639 if (AmpConfig::get('userflags')) { 640 Userflag::build_cache('song', $song_ids); 641 } 642 643 // Build a cache for the song's extended table 644 $sql = "SELECT * FROM `song_data` WHERE `song_id` IN $idlist"; 645 $db_results = Dba::read($sql); 646 647 while ($row = Dba::fetch_assoc($db_results)) { 648 parent::add_to_cache('song_data', $row['song_id'], $row); 649 } 650 651 return true; 652 } // build_cache 653 654 /** 655 * has_info 656 * @param string $limit_threshold 657 * @return array|boolean 658 */ 659 private function has_info($limit_threshold = '') 660 { 661 $song_id = $this->id; 662 663 if (parent::is_cached('song', $song_id)) { 664 return parent::get_from_cache('song', $song_id); 665 } 666 667 $sql = "SELECT `song`.`id`, `song`.`file`, `song`.`catalog`, `song`.`album`, `song`.`total_count`, `song`.`total_skip`, `album`.`album_artist` AS `albumartist`, `song`.`year`, `song`.`artist`, `song`.`title`, `song`.`bitrate`, `song`.`rate`, `song`.`mode`, `song`.`size`, `song`.`time`, `song`.`track`, `song`.`played`, `song`.`enabled`, `song`.`update_time`, `song`.`mbid`, `song`.`addition_time`, `song`.`license`, `song`.`composer`, `song`.`user_upload`, `album`.`disk`, `album`.`mbid` AS `album_mbid`, `artist`.`mbid` AS `artist_mbid`, `album_artist`.`mbid` AS `albumartist_mbid` FROM `song` LEFT JOIN `album` ON `album`.`id` = `song`.`album` LEFT JOIN `artist` ON `artist`.`id` = `song`.`artist` LEFT JOIN `artist` AS `album_artist` ON `album_artist`.`id` = `album`.`album_artist` WHERE `song`.`id` = ?"; 668 $db_results = Dba::read($sql, array($song_id)); 669 $results = Dba::fetch_assoc($db_results); 670 if (isset($results['id'])) { 671 if (AmpConfig::get('show_played_times')) { 672 $results['object_cnt'] = $results['total_count']; 673 } 674 if (AmpConfig::get('show_skipped_times')) { 675 $results['skip_cnt'] = $results['total_skip']; 676 } 677 678 parent::add_to_cache('song', $song_id, $results); 679 680 return $results; 681 } 682 683 return false; 684 } 685 686 /** 687 * can_scrobble 688 * 689 * return a song id based on a last.fm-style search in the database 690 * @param string $song_name 691 * @param string $artist_name 692 * @param string $album_name 693 * @param string $song_mbid 694 * @param string $artist_mbid 695 * @param string $album_mbid 696 * @return string 697 */ 698 public static function can_scrobble( 699 $song_name, 700 $artist_name, 701 $album_name, 702 $song_mbid = '', 703 $artist_mbid = '', 704 $album_mbid = '' 705 ) { 706 // by default require song, album, artist for any searches 707 $sql = "SELECT `song`.`id` FROM `song` LEFT JOIN `album` ON `album`.`id` = `song`.`album` LEFT JOIN `artist` ON `artist`.`id` = `song`.`artist` LEFT JOIN `artist` AS `album_artist` ON `album_artist`.`id` = `album`.`album_artist` WHERE `song`.`title` = '" . Dba::escape($song_name) . "' AND (`artist`.`name` = '" . Dba::escape($artist_name) . "' OR LTRIM(CONCAT(COALESCE(`artist`.`prefix`, ''), `artist`.`name`)) = '" . Dba::escape($artist_name) . "') AND (`album`.`name` = '" . Dba::escape($album_name) . "' OR LTRIM(CONCAT(COALESCE(`album`.`prefix`, ''), `album`.`name`)) = '" . Dba::escape($album_name) . "')"; 708 if ($song_mbid) { 709 $sql .= " AND `song`.`mbid` = '" . $song_mbid . "'"; 710 } 711 if ($artist_mbid) { 712 $sql .= " AND `artist`.`mbid` = '" . $song_mbid . "'"; 713 } 714 if ($album_mbid) { 715 $sql .= " AND `album`.`mbid` = '" . $song_mbid . "'"; 716 } 717 $db_results = Dba::read($sql); 718 $results = Dba::fetch_assoc($db_results); 719 if (isset($results['id'])) { 720 return $results['id']; 721 } 722 723 return ''; 724 } 725 726 /** 727 * _get_ext_info 728 * This function gathers information from the song_ext_info table and adds it to the 729 * current object 730 * @param string $select 731 * @return array 732 */ 733 public function _get_ext_info($select = '') 734 { 735 $song_id = (int) ($this->id); 736 $columns = (!empty($select)) ? Dba::escape($select) : '*'; 737 738 if (parent::is_cached('song_data', $song_id)) { 739 return parent::get_from_cache('song_data', $song_id); 740 } 741 742 $sql = "SELECT $columns FROM `song_data` WHERE `song_id` = ?"; 743 $db_results = Dba::read($sql, array($song_id)); 744 745 $results = Dba::fetch_assoc($db_results); 746 747 parent::add_to_cache('song_data', $song_id, $results); 748 749 return $results; 750 } // _get_ext_info 751 752 /** 753 * fill_ext_info 754 * This calls the _get_ext_info and then sets the correct vars 755 * @param string $data_filter 756 */ 757 public function fill_ext_info($data_filter = '') 758 { 759 $info = $this->_get_ext_info($data_filter); 760 761 if (!empty($info)) { 762 foreach ($info as $key => $value) { 763 if ($key != 'song_id') { 764 $this->$key = $value; 765 } 766 } // end foreach 767 } 768 } // fill_ext_info 769 770 /** 771 * type_to_mime 772 * 773 * Returns the mime type for the specified file extension/type 774 * @param string $type 775 * @return string 776 */ 777 public static function type_to_mime($type) 778 { 779 // FIXME: This should really be done the other way around. 780 // Store the mime type in the database, and provide a function 781 // to make it a human-friendly type. 782 switch ($type) { 783 case 'spx': 784 case 'ogg': 785 return 'application/ogg'; 786 case 'opus': 787 return 'audio/ogg; codecs=opus'; 788 case 'wma': 789 case 'asf': 790 return 'audio/x-ms-wma'; 791 case 'rm': 792 case 'ra': 793 return 'audio/x-realaudio'; 794 case 'flac': 795 return 'audio/x-flac'; 796 case 'wv': 797 return 'audio/x-wavpack'; 798 case 'aac': 799 case 'mp4': 800 case 'm4a': 801 case 'm4b': 802 return 'audio/mp4'; 803 case 'aacp': 804 return 'audio/aacp'; 805 case 'mpc': 806 return 'audio/x-musepack'; 807 case 'mkv': 808 return 'audio/x-matroska'; 809 case 'mpeg3': 810 case 'mp3': 811 default: 812 return 'audio/mpeg'; 813 } 814 } 815 816 /** 817 * get_disabled 818 * 819 * Gets a list of the disabled songs for and returns an array of Songs 820 * @param integer $count 821 * @return Song[] 822 */ 823 public static function get_disabled($count = 0) 824 { 825 $results = array(); 826 827 $sql = "SELECT `id` FROM `song` WHERE `enabled`='0'"; 828 if ($count) { 829 $sql .= " LIMIT $count"; 830 } 831 $db_results = Dba::read($sql); 832 833 while ($row = Dba::fetch_assoc($db_results)) { 834 $results[] = new Song($row['id']); 835 } 836 837 return $results; 838 } 839 840 /** 841 * find 842 * @param array $data 843 * @return boolean 844 */ 845 public static function find($data) 846 { 847 $sql_base = "SELECT `song`.`id` FROM `song`"; 848 if ($data['mb_trackid']) { 849 $sql = $sql_base . " WHERE `song`.`mbid` = ? LIMIT 1"; 850 $db_results = Dba::read($sql, array($data['mb_trackid'])); 851 if ($results = Dba::fetch_assoc($db_results)) { 852 return $results['id']; 853 } 854 } 855 if ($data['file']) { 856 $sql = $sql_base . " WHERE `song`.`file` = ? LIMIT 1"; 857 $db_results = Dba::read($sql, array($data['file'])); 858 if ($results = Dba::fetch_assoc($db_results)) { 859 return $results['id']; 860 } 861 } 862 863 $where = "WHERE `song`.`title` = ?"; 864 $sql = $sql_base; 865 $params = array($data['title']); 866 if ($data['track']) { 867 $where .= " AND `song`.`track` = ?"; 868 $params[] = $data['track']; 869 } 870 $sql .= " INNER JOIN `artist` ON `artist`.`id` = `song`.`artist`"; 871 $sql .= " INNER JOIN `album` ON `album`.`id` = `song`.`album`"; 872 873 if ($data['mb_artistid']) { 874 $where .= " AND `artist`.`mbid` = ?"; 875 $params[] = $data['mb_albumid']; 876 } else { 877 $where .= " AND `artist`.`name` = ?"; 878 $params[] = $data['artist']; 879 } 880 if ($data['mb_albumid']) { 881 $where .= " AND `album`.`mbid` = ?"; 882 $params[] = $data['mb_albumid']; 883 } else { 884 $where .= " AND `album`.`name` = ?"; 885 $params[] = $data['album']; 886 } 887 888 $sql .= $where . " LIMIT 1"; 889 $db_results = Dba::read($sql, $params); 890 if ($results = Dba::fetch_assoc($db_results)) { 891 return $results['id']; 892 } 893 894 return false; 895 } 896 897 /** 898 * Get duplicate information. 899 * @param array $dupe 900 * @param string $search_type 901 * @return integer[] 902 */ 903 public static function get_duplicate_info($dupe, $search_type) 904 { 905 $results = array(); 906 if (isset($dupe['id'])) { 907 $results[] = $dupe['id']; 908 } else { 909 $sql = "SELECT `id` FROM `song` WHERE `title`='" . Dba::escape($dupe['title']) . "' "; 910 911 if ($search_type == 'artist_title' || $search_type == 'artist_album_title') { 912 $sql .= "AND `artist`='" . Dba::escape($dupe['artist']) . "' "; 913 } 914 if ($search_type == 'artist_album_title') { 915 $sql .= "AND `album` = '" . Dba::escape($dupe['album']) . "' "; 916 } 917 $sql .= 'ORDER BY `time`, `bitrate`, `size`'; 918 919 if ($search_type == 'album') { 920 $sql = "SELECT `id` from `song` LEFT JOIN (SELECT MIN(`id`) AS `dupe_id1`, LTRIM(CONCAT(COALESCE(`album`.`prefix`, ''), ' ', `album`.`name`)) AS `fullname`, COUNT(LTRIM(CONCAT(COALESCE(`album`.`prefix`, ''), ' ', `album`.`name`))) AS `Counting` FROM `album` GROUP BY `album_artist`, LTRIM(CONCAT(COALESCE(`album`.`prefix`, ''), ' ', `album`.`name`)), `disk` HAVING `Counting` > 1) AS `dupe_search` ON `song`.`album` = `dupe_search`.`dupe_id1` LEFT JOIN (SELECT MAX(`id`) AS `dupe_id2`, LTRIM(CONCAT(COALESCE(`album`.`prefix`, ''), ' ', `album`.`name`)) AS `fullname`, COUNT(LTRIM(CONCAT(COALESCE(`album`.`prefix`, ''), ' ', `album`.`name`))) AS `Counting` FROM `album` GROUP BY `album_artist`, LTRIM(CONCAT(COALESCE(`album`.`prefix`, ''), ' ', `album`.`name`)), `disk` HAVING `Counting` > 1) AS `dupe_search2` ON `song`.`album` = `dupe_search2`.`dupe_id2` WHERE `dupe_search`.`dupe_id1` IS NOT NULL OR `dupe_search2`.`dupe_id2` IS NOT NULL ORDER BY `album`, `track`"; 921 } 922 923 $db_results = Dba::read($sql); 924 925 while ($item = Dba::fetch_assoc($db_results)) { 926 $results[] = (int)$item['id']; 927 } // end while 928 } 929 930 return $results; 931 } 932 933 /** 934 * get_album_name 935 * gets the name of $this->album, allows passing of id 936 * @param integer $album_id 937 * @return string 938 */ 939 public function get_album_name($album_id = 0) 940 { 941 if (!$album_id) { 942 $album_id = $this->album; 943 } 944 $album = new Album($album_id); 945 946 return $album->f_name; 947 } // get_album_name 948 949 /** 950 * get_album_catalog_number 951 * gets the catalog_number of $this->album, allows passing of id 952 * @param integer $album_id 953 * @return string 954 */ 955 public function get_album_catalog_number($album_id = null) 956 { 957 if ($album_id === null) { 958 $album_id = $this->album; 959 } 960 $album = new Album($album_id); 961 962 return $album->catalog_number; 963 } // get_album_catalog_number 964 965 /** 966 * get_album_original_year 967 * gets the original_year of $this->album, allows passing of id 968 * @param integer $album_id 969 * @return integer 970 */ 971 public function get_album_original_year($album_id = null) 972 { 973 if ($album_id === null) { 974 $album_id = $this->album; 975 } 976 $album = new Album($album_id); 977 978 return $album->original_year; 979 } // get_album_original_year 980 981 /** 982 * get_album_barcode 983 * gets the barcode of $this->album, allows passing of id 984 * @param integer $album_id 985 * @return string 986 */ 987 public function get_album_barcode($album_id = null) 988 { 989 if (!$album_id) { 990 $album_id = $this->album; 991 } 992 $album = new Album($album_id); 993 994 return $album->barcode; 995 } // get_album_barcode 996 997 /** 998 * get_artist_name 999 * gets the name of $this->artist, allows passing of id 1000 * @param integer $artist_id 1001 * @return string 1002 */ 1003 public function get_artist_name($artist_id = 0) 1004 { 1005 if (!$artist_id) { 1006 $artist_id = $this->artist; 1007 } 1008 $artist = new Artist($artist_id); 1009 if ($artist->id) { 1010 return $artist->f_name; 1011 } 1012 1013 return ''; 1014 } // get_artist_name 1015 1016 /** 1017 * get_album_artist_name 1018 * gets the name of $this->albumartist, allows passing of id 1019 * @param integer $album_artist_id 1020 * @return string 1021 */ 1022 public function get_album_artist_name($album_artist_id = 0) 1023 { 1024 if (!$album_artist_id) { 1025 $album_artist_id = $this->albumartist; 1026 } 1027 $album_artist = new Artist($album_artist_id); 1028 if ($album_artist->id) { 1029 return (string)$album_artist->f_name; 1030 } 1031 1032 return ''; 1033 } // get_album_artist_name 1034 1035 /** 1036 * set_played 1037 * this checks to see if the current object has been played 1038 * if not then it sets it to played. In any case it updates stats. 1039 * @param integer $user 1040 * @param string $agent 1041 * @param array $location 1042 * @param integer $date 1043 * @return boolean 1044 */ 1045 public function set_played($user, $agent, $location, $date = null) 1046 { 1047 // ignore duplicates or skip the last track 1048 if (!$this->check_play_history($user, $agent, $date)) { 1049 return false; 1050 } 1051 // insert stats for each object type 1052 if (Stats::insert('song', $this->id, $user, $agent, $location, 'stream', $date)) { 1053 Stats::insert('album', $this->album, $user, $agent, $location, 'stream', $date); 1054 Stats::insert('artist', $this->artist, $user, $agent, $location, 'stream', $date); 1055 // followup on some stats too 1056 $user_data = User::get_user_data($user, 'play_size'); 1057 $play_size = (isset($user_data['play_size'])) 1058 ? (int)$user_data['play_size'] 1059 : 0; 1060 User::set_user_data($user, 'play_size', ($play_size + $this->size)); 1061 } 1062 // If it hasn't been played, set it 1063 if (!$this->played) { 1064 self::update_played(true, $this->id); 1065 } 1066 1067 return true; 1068 } // set_played 1069 1070 /** 1071 * check_play_history 1072 * this checks to see if the current object has been played 1073 * if not then it sets it to played. In any case it updates stats. 1074 * @param integer $user 1075 * @param string $agent 1076 * @param integer $date 1077 * @return boolean 1078 */ 1079 public function check_play_history($user, $agent, $date) 1080 { 1081 return Stats::has_played_history('song', $this, $user, $agent, $date); 1082 } 1083 1084 /** 1085 * compare_song_information 1086 * this compares the new ID3 tags of a file against 1087 * the ones in the database to see if they have changed 1088 * it returns false if nothing has changes, or the true 1089 * if they have. Static because it doesn't need this 1090 * @param Song $song 1091 * @param Song $new_song 1092 * @return array 1093 */ 1094 public static function compare_song_information(Song $song, Song $new_song) 1095 { 1096 // Remove some stuff we don't care about as this function only needs to check song information. 1097 unset($song->catalog, $song->played, $song->enabled, $song->addition_time, $song->update_time, $song->type, $song->disk); 1098 $string_array = array('title', 'comment', 'lyrics', 'composer', 'tags', 'artist', 'album', 'time'); 1099 $skip_array = array( 1100 'id', 1101 'tag_id', 1102 'mime', 1103 'mbid', 1104 'waveform', 1105 'object_cnt', 1106 'skip_cnt', 1107 'albumartist', 1108 'artist_mbid', 1109 'album_mbid', 1110 'albumartist_mbid', 1111 'mb_albumid_group', 1112 'disabledMetadataFields' 1113 ); 1114 1115 return self::compare_media_information($song, $new_song, $string_array, $skip_array); 1116 } // compare_song_information 1117 1118 /** 1119 * compare_media_information 1120 * @param $media 1121 * @param $new_media 1122 * @param string[] $string_array 1123 * @param string[] $skip_array 1124 * @return array 1125 */ 1126 public static function compare_media_information($media, $new_media, $string_array, $skip_array) 1127 { 1128 $array = array(); 1129 1130 // Pull out all the currently set vars 1131 $fields = get_object_vars($media); 1132 1133 // Foreach them 1134 foreach ($fields as $key => $value) { 1135 $key = trim((string)$key); 1136 if (empty($key) || in_array($key, $skip_array)) { 1137 continue; 1138 } 1139 1140 // Represent the value as a string for simpler comparison. For array, ensure to sort similarly old/new values 1141 if (is_array($media->$key)) { 1142 $arr = $media->$key; 1143 sort($arr); 1144 $mediaData = implode(" ", $arr); 1145 } else { 1146 $mediaData = $media->$key; 1147 } 1148 1149 // Skip the item if it is no string nor something we can turn into a string 1150 if (!is_string($mediaData) && !is_numeric($mediaData) && !is_bool($mediaData)) { 1151 if (is_object($mediaData) && !method_exists($mediaData, '__toString')) { 1152 continue; 1153 } 1154 } 1155 1156 if (is_array($new_media->$key)) { 1157 $arr = $new_media->$key; 1158 sort($arr); 1159 $newMediaData = implode(" ", $arr); 1160 } else { 1161 $newMediaData = $new_media->$key; 1162 } 1163 1164 // If it's a string thing 1165 if (in_array($key, $string_array)) { 1166 $mediaData = self::clean_string_field_value($mediaData); 1167 $newMediaData = self::clean_string_field_value($newMediaData); 1168 if ($mediaData != $newMediaData) { 1169 $array['change'] = true; 1170 $array['element'][$key] = 'OLD: ' . $mediaData . ' --> ' . $newMediaData; 1171 } 1172 } // in array of strings 1173 elseif ($newMediaData !== null) { 1174 if ($media->$key != $new_media->$key) { 1175 $array['change'] = true; 1176 $array['element'][$key] = 'OLD:' . $mediaData . ' --> ' . $newMediaData; 1177 } 1178 } // end else 1179 } // end foreach 1180 1181 if ($array['change']) { 1182 debug_event(self::class, 'media-diff ' . json_encode($array['element']), 5); 1183 } 1184 1185 return $array; 1186 } 1187 1188 /** 1189 * clean_string_field_value 1190 * @param string $value 1191 * @return string 1192 */ 1193 private static function clean_string_field_value($value) 1194 { 1195 $value = trim(stripslashes(preg_replace('/\s+/', ' ', $value))); 1196 1197 // Strings containing only UTF-8 BOM = empty string 1198 if (strlen((string)$value) == 2 && (ord($value[0]) == 0xFF || ord($value[0]) == 0xFE)) { 1199 $value = ""; 1200 } 1201 1202 return $value; 1203 } 1204 1205 /** 1206 * update 1207 * This takes a key'd array of data does any cleaning it needs to 1208 * do and then calls the helper functions as needed. 1209 * @param array $data 1210 * @return integer 1211 */ 1212 public function update(array $data) 1213 { 1214 $changed = array(); 1215 foreach ($data as $key => $value) { 1216 debug_event(self::class, $key . '=' . $value, 5); 1217 1218 switch ($key) { 1219 case 'artist_name': 1220 // Create new artist name and id 1221 $old_artist_id = $this->artist; 1222 $new_artist_id = Artist::check($value); 1223 $this->artist = $new_artist_id; 1224 self::update_artist($new_artist_id, $this->id, $old_artist_id); 1225 $changed[] = (string) $key; 1226 break; 1227 case 'album_name': 1228 // Create new album name and id 1229 $old_album_id = $this->album; 1230 $new_album_id = Album::check($this->catalog, $value); 1231 $this->album = $new_album_id; 1232 self::update_album($new_album_id, $this->id, $old_album_id); 1233 $changed[] = (string) $key; 1234 break; 1235 case 'artist': 1236 // Change artist the song is assigned to 1237 if ($value != $this->$key) { 1238 $old_artist_id = $this->artist; 1239 $new_artist_id = $value; 1240 self::update_artist($new_artist_id, $this->id, $old_artist_id); 1241 $changed[] = (string) $key; 1242 } 1243 break; 1244 case 'album': 1245 // Change album the song is assigned to 1246 if ($value != $this->$key) { 1247 $old_album_id = $this->$key; 1248 $new_album_id = $value; 1249 self::update_album($new_album_id, $this->id, $old_album_id); 1250 $changed[] = (string) $key; 1251 } 1252 break; 1253 case 'year': 1254 case 'title': 1255 case 'track': 1256 case 'mbid': 1257 case 'license': 1258 case 'composer': 1259 case 'label': 1260 case 'language': 1261 case 'comment': 1262 // Check to see if it needs to be updated 1263 if ($value != $this->$key) { 1264 $function = 'update_' . $key; 1265 self::$function($value, $this->id); 1266 $this->$key = $value; 1267 $changed[] = (string) $key; 1268 } 1269 break; 1270 case 'edit_tags': 1271 Tag::update_tag_list($value, 'song', $this->id, true); 1272 $this->tags = Tag::get_top_tags('song', $this->id); 1273 $changed[] = (string) $key; 1274 break; 1275 case 'metadata': 1276 if (self::isCustomMetadataEnabled()) { 1277 $this->updateMetadata($value); 1278 } 1279 break; 1280 default: 1281 break; 1282 } // end whitelist 1283 } // end foreach 1284 1285 $this->getSongTagWriter()->write( 1286 $this 1287 ); 1288 1289 return $this->id; 1290 } // update 1291 1292 /** 1293 * update_song 1294 * this is the main updater for a song it actually 1295 * calls a whole bunch of mini functions to update 1296 * each little part of the song... lastly it updates 1297 * the "update_time" of the song 1298 * @param integer $song_id 1299 * @param Song $new_song 1300 */ 1301 public static function update_song($song_id, Song $new_song) 1302 { 1303 $update_time = time(); 1304 1305 $sql = "UPDATE `song` SET `album` = ?, `year` = ?, `artist` = ?, `title` = ?, `composer` = ?, `bitrate` = ?, `rate` = ?, `mode` = ?, `size` = ?, `time` = ?, `track` = ?, `mbid` = ?, `update_time` = ? WHERE `id` = ?"; 1306 Dba::write($sql, array($new_song->album, $new_song->year, $new_song->artist, 1307 $new_song->title, $new_song->composer, (int) $new_song->bitrate, (int) $new_song->rate, $new_song->mode, 1308 (int) $new_song->size, (int) $new_song->time, $new_song->track, $new_song->mbid, 1309 $update_time, $song_id)); 1310 1311 $sql = "UPDATE `song_data` SET `label` = ?, `lyrics` = ?, `language` = ?, `comment` = ?, `replaygain_track_gain` = ?, `replaygain_track_peak` = ?, `replaygain_album_gain` = ?, `replaygain_album_peak` = ?, `r128_track_gain` = ?, `r128_album_gain` = ? WHERE `song_id` = ?"; 1312 Dba::write($sql, array($new_song->label, $new_song->lyrics, $new_song->language, $new_song->comment, $new_song->replaygain_track_gain, 1313 $new_song->replaygain_track_peak, $new_song->replaygain_album_gain, $new_song->replaygain_album_peak, $new_song->r128_track_gain, $new_song->r128_album_gain, $song_id)); 1314 } // update_song 1315 1316 /** 1317 * update_year 1318 * update the year tag 1319 * @param integer $new_year 1320 * @param integer $song_id 1321 */ 1322 public static function update_year($new_year, $song_id) 1323 { 1324 self::_update_item('year', $new_year, $song_id, 50, true); 1325 } // update_year 1326 1327 /** 1328 * update_label 1329 * This updates the label tag of the song 1330 * @param string $new_value 1331 * @param integer $song_id 1332 */ 1333 public static function update_label($new_value, $song_id) 1334 { 1335 self::_update_ext_item('label', $new_value, $song_id, 50, true); 1336 } // update_label 1337 1338 /** 1339 * update_language 1340 * This updates the language tag of the song 1341 * @param string $new_lang 1342 * @param integer $song_id 1343 */ 1344 public static function update_language($new_lang, $song_id) 1345 { 1346 self::_update_ext_item('language', $new_lang, $song_id, 50, true); 1347 } // update_language 1348 1349 /** 1350 * update_comment 1351 * updates the comment field 1352 * @param string $new_comment 1353 * @param integer $song_id 1354 */ 1355 public static function update_comment($new_comment, $song_id) 1356 { 1357 self::_update_ext_item('comment', $new_comment, $song_id, 50, true); 1358 } // update_comment 1359 1360 /** 1361 * update_lyrics 1362 * updates the lyrics field 1363 * @param string $new_lyrics 1364 * @param integer $song_id 1365 */ 1366 public static function update_lyrics($new_lyrics, $song_id) 1367 { 1368 self::_update_ext_item('lyrics', $new_lyrics, $song_id, 50, true); 1369 } // update_lyrics 1370 1371 /** 1372 * update_title 1373 * updates the title field 1374 * @param string $new_title 1375 * @param integer $song_id 1376 */ 1377 public static function update_title($new_title, $song_id) 1378 { 1379 self::_update_item('title', $new_title, $song_id, 50, true); 1380 } // update_title 1381 1382 /** 1383 * update_composer 1384 * updates the composer field 1385 * @param string $new_value 1386 * @param integer $song_id 1387 */ 1388 public static function update_composer($new_value, $song_id) 1389 { 1390 self::_update_item('composer', $new_value, $song_id, 50, true); 1391 } // update_composer 1392 1393 /** 1394 * update_publisher 1395 * updates the publisher field 1396 * @param string $new_value 1397 * @param integer $song_id 1398 */ 1399 public static function update_publisher($new_value, $song_id) 1400 { 1401 self::_update_item('publisher', $new_value, $song_id, 50, true); 1402 } // update_publisher 1403 1404 /** 1405 * update_bitrate 1406 * updates the bitrate field 1407 * @param integer $new_bitrate 1408 * @param integer $song_id 1409 */ 1410 public static function update_bitrate($new_bitrate, $song_id) 1411 { 1412 self::_update_item('bitrate', $new_bitrate, $song_id, 50, true); 1413 } // update_bitrate 1414 1415 /** 1416 * update_rate 1417 * updates the rate field 1418 * @param integer $new_rate 1419 * @param integer $song_id 1420 */ 1421 public static function update_rate($new_rate, $song_id) 1422 { 1423 self::_update_item('rate', $new_rate, $song_id, 50, true); 1424 } // update_rate 1425 1426 /** 1427 * update_mode 1428 * updates the mode field 1429 * @param string $new_mode 1430 * @param integer $song_id 1431 */ 1432 public static function update_mode($new_mode, $song_id) 1433 { 1434 self::_update_item('mode', $new_mode, $song_id, 50, true); 1435 } // update_mode 1436 1437 /** 1438 * update_size 1439 * updates the size field 1440 * @param integer $new_size 1441 * @param integer $song_id 1442 */ 1443 public static function update_size($new_size, $song_id) 1444 { 1445 self::_update_item('size', $new_size, $song_id, 50); 1446 } // update_size 1447 1448 /** 1449 * update_time 1450 * updates the time field 1451 * @param integer $new_time 1452 * @param integer $song_id 1453 */ 1454 public static function update_time($new_time, $song_id) 1455 { 1456 self::_update_item('time', $new_time, $song_id, 50, true); 1457 } // update_time 1458 1459 /** 1460 * update_track 1461 * this updates the track field 1462 * @param integer $new_track 1463 * @param integer $song_id 1464 */ 1465 public static function update_track($new_track, $song_id) 1466 { 1467 self::_update_item('track', $new_track, $song_id, 50, true); 1468 } // update_track 1469 1470 /** 1471 * update_mbid 1472 * updates mbid field 1473 * @param string $new_mbid 1474 * @param integer $song_id 1475 */ 1476 public static function update_mbid($new_mbid, $song_id) 1477 { 1478 self::_update_item('mbid', $new_mbid, $song_id, 50); 1479 } // update_mbid 1480 1481 /** 1482 * update_license 1483 * updates license field 1484 * @param string $new_license 1485 * @param integer $song_id 1486 */ 1487 public static function update_license($new_license, $song_id) 1488 { 1489 self::_update_item('license', $new_license, $song_id, 50, true); 1490 } // update_license 1491 1492 /** 1493 * update_artist 1494 * updates the artist field 1495 * @param integer $new_artist 1496 * @param integer $song_id 1497 * @param integer $old_artist 1498 */ 1499 public static function update_artist($new_artist, $song_id, $old_artist) 1500 { 1501 self::_update_item('artist', $new_artist, $song_id, 50); 1502 1503 // migrate stats for the old artist 1504 Stats::migrate('artist', $old_artist, $new_artist); 1505 Useractivity::migrate('artist', $old_artist, $new_artist); 1506 Recommendation::migrate('artist', $old_artist, $new_artist); 1507 Share::migrate('artist', $old_artist, $new_artist); 1508 Shoutbox::migrate('artist', $old_artist, $new_artist); 1509 Tag::migrate('artist', $old_artist, $new_artist); 1510 Userflag::migrate('artist', $old_artist, $new_artist); 1511 Rating::migrate('artist', $old_artist, $new_artist); 1512 Art::duplicate('artist', $old_artist, $new_artist); 1513 Wanted::migrate('artist', $old_artist, $new_artist); 1514 Catalog::migrate_map('artist', $old_artist, $new_artist); 1515 Artist::update_artist_counts($new_artist); 1516 } // update_artist 1517 1518 /** 1519 * update_album 1520 * updates the album field 1521 * @param integer $new_album 1522 * @param integer $song_id 1523 * @param integer $old_album 1524 */ 1525 public static function update_album($new_album, $song_id, $old_album) 1526 { 1527 self::_update_item('album', $new_album, $song_id, 50, true); 1528 1529 // migrate stats for the old album 1530 Stats::migrate('album', $old_album, $new_album); 1531 Useractivity::migrate('album', $old_album, $new_album); 1532 Recommendation::migrate('album', $old_album, $new_album); 1533 Share::migrate('album', $old_album, $new_album); 1534 Shoutbox::migrate('album', $old_album, $new_album); 1535 Tag::migrate('album', $old_album, $new_album); 1536 Userflag::migrate('album', $old_album, $new_album); 1537 Rating::migrate('album', $old_album, $new_album); 1538 Art::duplicate('album', $old_album, $new_album); 1539 Catalog::migrate_map('album', $old_album, $new_album); 1540 Album::update_album_counts($new_album); 1541 } // update_album 1542 1543 /** 1544 * update_utime 1545 * sets a new update time 1546 * @param integer $song_id 1547 * @param integer $time 1548 */ 1549 public static function update_utime($song_id, $time = 0) 1550 { 1551 if (!$time) { 1552 $time = time(); 1553 } 1554 1555 self::_update_item('update_time', $time, $song_id, 75, true); 1556 } // update_utime 1557 1558 /** 1559 * update_played 1560 * sets the played flag 1561 * @param boolean $new_played 1562 * @param integer $song_id 1563 */ 1564 public static function update_played($new_played, $song_id) 1565 { 1566 self::_update_item('played', ($new_played ? 1 : 0), $song_id, 25); 1567 } // update_played 1568 1569 /** 1570 * update_enabled 1571 * sets the enabled flag 1572 * @param boolean $new_enabled 1573 * @param integer $song_id 1574 */ 1575 public static function update_enabled($new_enabled, $song_id) 1576 { 1577 self::_update_item('enabled', ($new_enabled ? 1 : 0), $song_id, 75, true); 1578 } // update_enabled 1579 1580 /** 1581 * _update_item 1582 * This is a private function that should only be called from within the song class. 1583 * It takes a field, value song id and level. first and foremost it checks the level 1584 * against Core::get_global('user') to make sure they are allowed to update this record 1585 * it then updates it and sets $this->{$field} to the new value 1586 * @param string $field 1587 * @param mixed $value 1588 * @param integer $song_id 1589 * @param integer $level 1590 * @param boolean $check_owner 1591 * @return PDOStatement|boolean 1592 */ 1593 private static function _update_item($field, $value, $song_id, $level, $check_owner = false) 1594 { 1595 if ($check_owner) { 1596 $item = new Song($song_id); 1597 if ($item->id && $item->get_user_owner() == Core::get_global('user')->id) { 1598 $level = 25; 1599 } 1600 } 1601 /* Check them Rights! */ 1602 if (!Access::check('interface', $level)) { 1603 return false; 1604 } 1605 1606 /* Can't update to blank */ 1607 if (!strlen(trim((string)$value)) && $field != 'comment') { 1608 return false; 1609 } 1610 1611 $sql = "UPDATE `song` SET `$field` = ? WHERE `id` = ?"; 1612 1613 return Dba::write($sql, array($value, $song_id)); 1614 } // _update_item 1615 1616 /** 1617 * _update_ext_item 1618 * This updates a song record that is housed in the song_ext_info table 1619 * These are items that aren't used normally, and often large/informational only 1620 * @param string $field 1621 * @param string $value 1622 * @param integer $song_id 1623 * @param integer $level 1624 * @param boolean $check_owner 1625 * @return PDOStatement|boolean 1626 */ 1627 private static function _update_ext_item($field, $value, $song_id, $level, $check_owner = false) 1628 { 1629 if ($check_owner) { 1630 $item = new Song($song_id); 1631 if ($item->id && $item->get_user_owner() == Core::get_global('user')->id) { 1632 $level = 25; 1633 } 1634 } 1635 1636 /* Check them rights boy! */ 1637 if (!Access::check('interface', $level)) { 1638 return false; 1639 } 1640 1641 $sql = "UPDATE `song_data` SET `$field` = ? WHERE `song_id` = ?"; 1642 1643 return Dba::write($sql, array($value, $song_id)); 1644 } // _update_ext_item 1645 1646 /** 1647 * format 1648 * This takes the current song object 1649 * and does a ton of formatting on it creating f_??? variables on the current 1650 * object 1651 * @param boolean $details 1652 */ 1653 public function format($details = true) 1654 { 1655 if ($details) { 1656 $this->fill_ext_info(); 1657 1658 // Get the top tags 1659 $this->tags = Tag::get_top_tags('song', $this->id); 1660 $this->f_tags = Tag::get_display($this->tags, true, 'song'); 1661 } 1662 // force the album artist. 1663 $album = new Album($this->album); 1664 $this->albumartist = (!empty($this->albumartist)) ? $this->albumartist : $album->album_artist; 1665 1666 // fix missing song disk (where is this coming from?) 1667 $this->disk = ($this->disk) ? $this->disk : $album->disk; 1668 1669 // Format the album name 1670 $this->f_album_full = $this->get_album_name(); 1671 $this->f_album = $this->f_album_full; 1672 1673 // Format the artist name 1674 $this->f_artist_full = $this->get_artist_name(); 1675 $this->f_artist = $this->f_artist_full; 1676 1677 // Format the album_artist name 1678 $this->f_albumartist_full = $this->get_album_artist_name(); 1679 1680 // Format the title 1681 $this->f_title = $this->title; 1682 $this->f_title_full = $this->f_title; 1683 1684 // Create Links for the different objects 1685 $this->link = AmpConfig::get('web_path') . "/song.php?action=show_song&song_id=" . $this->id; 1686 $this->f_link = "<a href=\"" . scrub_out($this->link) . "\" title=\"" . scrub_out($this->f_artist) . " - " . scrub_out($this->f_title) . "\"> " . scrub_out($this->f_title) . "</a>"; 1687 $this->f_album_link = "<a href=\"" . AmpConfig::get('web_path') . "/albums.php?action=show&album=" . $this->album . "\" title=\"" . scrub_out($this->f_album_full) . "\"> " . scrub_out($this->f_album) . "</a>"; 1688 $this->f_artist_link = "<a href=\"" . AmpConfig::get('web_path') . "/artists.php?action=show&artist=" . $this->artist . "\" title=\"" . scrub_out($this->f_artist_full) . "\"> " . scrub_out($this->f_artist) . "</a>"; 1689 if (!empty($this->albumartist)) { 1690 $this->f_albumartist_link = "<a href=\"" . AmpConfig::get('web_path') . "/artists.php?action=show&artist=" . $this->albumartist . "\" title=\"" . scrub_out($this->f_albumartist_full) . "\"> " . scrub_out($this->f_albumartist_full) . "</a>"; 1691 } 1692 1693 // Format the Bitrate 1694 $this->f_bitrate = (int)($this->bitrate / 1000) . "-" . strtoupper((string)$this->mode); 1695 1696 // Format the Time 1697 $min = floor($this->time / 60); 1698 $sec = sprintf("%02d", ($this->time % 60)); 1699 $this->f_time = $min . ":" . $sec; 1700 $hour = sprintf("%02d", floor($min / 60)); 1701 $min_h = sprintf("%02d", ($min % 60)); 1702 $this->f_time_h = $hour . ":" . $min_h . ":" . $sec; 1703 1704 // Format the track (there isn't really anything to do here) 1705 $this->f_track = (string)$this->track; 1706 1707 // Format the size 1708 $this->f_size = Ui::format_bytes($this->size); 1709 1710 $this->f_lyrics = "<a title=\"" . scrub_out($this->title) . "\" href=\"" . AmpConfig::get('web_path') . "/song.php?action=show_lyrics&song_id=" . $this->id . "\">" . T_('Show Lyrics') . "</a>"; 1711 1712 $this->f_file = $this->f_artist . ' - '; 1713 if ($this->track) { 1714 $this->f_file .= $this->track . ' - '; 1715 } 1716 $this->f_file .= $this->f_title . '.' . $this->type; 1717 1718 $this->f_publisher = $this->label; 1719 $this->f_composer = $this->composer; 1720 1721 $year = (int)$this->year; 1722 $this->f_year_link = "<a href=\"" . AmpConfig::get('web_path') . "/search.php?type=album&action=search&limit=0&rule_1=year&rule_1_operator=2&rule_1_input=" . $year . "\">" . $year . "</a>"; 1723 1724 if (AmpConfig::get('licensing') && $this->license !== null) { 1725 $license = new License($this->license); 1726 1727 $this->f_license = $license->getLinkFormatted(); 1728 } 1729 } // format 1730 1731 /** 1732 * Get item keywords for metadata searches. 1733 * @return array 1734 */ 1735 public function get_keywords() 1736 { 1737 $keywords = array(); 1738 $keywords['mb_trackid'] = array( 1739 'important' => false, 1740 'label' => T_('Track MusicBrainzID'), 1741 'value' => $this->mbid 1742 ); 1743 $keywords['artist'] = array( 1744 'important' => true, 1745 'label' => T_('Artist'), 1746 'value' => $this->f_artist 1747 ); 1748 $keywords['title'] = array( 1749 'important' => true, 1750 'label' => T_('Title'), 1751 'value' => $this->f_title 1752 ); 1753 1754 return $keywords; 1755 } 1756 1757 /** 1758 * Get total count 1759 * @return integer 1760 */ 1761 public function get_totalcount() 1762 { 1763 return $this->total_count; 1764 } 1765 1766 /** 1767 * Get item fullname. 1768 * @return string 1769 */ 1770 public function get_fullname() 1771 { 1772 return $this->f_title; 1773 } 1774 1775 /** 1776 * Get parent item description. 1777 * @return array|null 1778 */ 1779 public function get_parent() 1780 { 1781 return array('object_type' => 'album', 'object_id' => $this->album); 1782 } 1783 1784 /** 1785 * Get item children. 1786 * @return array 1787 */ 1788 public function get_childrens() 1789 { 1790 return array(); 1791 } 1792 1793 /** 1794 * Search for item children. 1795 * @param string $name 1796 * @return array 1797 */ 1798 public function search_childrens($name) 1799 { 1800 debug_event(self::class, 'search_childrens ' . $name, 5); 1801 1802 return array(); 1803 } 1804 1805 /** 1806 * Get all childrens and sub-childrens medias. 1807 * @param string $filter_type 1808 * @return array 1809 */ 1810 public function get_medias($filter_type = null) 1811 { 1812 $medias = array(); 1813 if ($filter_type === null || $filter_type == 'song') { 1814 $medias[] = array( 1815 'object_type' => 'song', 1816 'object_id' => $this->id 1817 ); 1818 } 1819 1820 return $medias; 1821 } 1822 1823 /** 1824 * get_catalogs 1825 * 1826 * Get all catalog ids related to this item. 1827 * @return integer[] 1828 */ 1829 public function get_catalogs() 1830 { 1831 return array($this->catalog); 1832 } 1833 1834 /** 1835 * Get item's owner. 1836 * @return integer|null 1837 */ 1838 public function get_user_owner() 1839 { 1840 if ($this->user_upload !== null) { 1841 return $this->user_upload; 1842 } 1843 1844 return null; 1845 } 1846 1847 /** 1848 * Get default art kind for this item. 1849 * @return string 1850 */ 1851 public function get_default_art_kind() 1852 { 1853 return 'default'; 1854 } 1855 1856 /** 1857 * get_description 1858 * @return string 1859 */ 1860 public function get_description() 1861 { 1862 if (!empty($this->comment)) { 1863 return $this->comment; 1864 } 1865 1866 $album = new Album($this->album); 1867 $album->format(); 1868 1869 return $album->get_description(); 1870 } 1871 1872 /** 1873 * display_art 1874 * @param integer $thumb 1875 * @param boolean $force 1876 */ 1877 public function display_art($thumb = 2, $force = false) 1878 { 1879 $object_id = null; 1880 $type = null; 1881 1882 if (Art::has_db($this->id, 'song')) { 1883 $object_id = $this->id; 1884 $type = 'song'; 1885 } else { 1886 if (Art::has_db($this->album, 'album')) { 1887 $object_id = $this->album; 1888 $type = 'album'; 1889 } else { 1890 if (Art::has_db($this->artist, 'artist') || $force) { 1891 $object_id = $this->artist; 1892 $type = 'artist'; 1893 } 1894 } 1895 } 1896 1897 if ($object_id !== null && $type !== null) { 1898 Art::display($type, $object_id, $this->get_fullname(), $thumb, $this->link); 1899 } 1900 } 1901 1902 /** 1903 * get_fields 1904 * This returns all of the 'data' fields for this object, we need to filter out some that we don't 1905 * want to present to a user, and add some that don't exist directly on the object but are related 1906 * @return array 1907 */ 1908 public static function get_fields() 1909 { 1910 $fields = get_class_vars(Song::class); 1911 1912 unset($fields['id'], $fields['_transcoded'], $fields['_fake'], $fields['cache_hit'], $fields['mime'], $fields['type']); 1913 1914 // Some additional fields 1915 $fields['tag'] = true; 1916 $fields['catalog'] = true; 1917 // FIXME: These are here to keep the ideas, don't want to have to worry about them for now 1918 // $fields['rating'] = true; 1919 // $fields['recently Played'] = true; 1920 1921 return $fields; 1922 } // get_fields 1923 1924 /** 1925 * get_from_path 1926 * This returns all of the songs that exist under the specified path 1927 * @param string $path 1928 * @return integer[] 1929 */ 1930 public static function get_from_path($path) 1931 { 1932 $path = Dba::escape($path); 1933 1934 $sql = "SELECT * FROM `song` WHERE `file` LIKE '$path%'"; 1935 $db_results = Dba::read($sql); 1936 1937 $songs = array(); 1938 1939 while ($row = Dba::fetch_assoc($db_results)) { 1940 $songs[] = (int)$row['id']; 1941 } 1942 1943 return $songs; 1944 } // get_from_path 1945 1946 /** 1947 * @function get_rel_path 1948 * @discussion returns the path of the song file stripped of the catalog path 1949 * used for mpd playback 1950 * @param string $file_path 1951 * @param integer $catalog_id 1952 * @return string 1953 */ 1954 public function get_rel_path($file_path = null, $catalog_id = 0) 1955 { 1956 $info = null; 1957 if ($file_path === null) { 1958 $info = $this->has_info(); 1959 $file_path = $info['file']; 1960 } 1961 if (!$catalog_id) { 1962 if (!is_array($info)) { 1963 $info = $this->has_info(); 1964 } 1965 $catalog_id = $info['catalog']; 1966 } 1967 $catalog = Catalog::create_from_id($catalog_id); 1968 1969 return $catalog->get_rel_path($file_path); 1970 } // get_rel_path 1971 1972 /** 1973 * play_url 1974 * This function takes all the song information and correctly formats a 1975 * a stream URL taking into account the downsampling mojo and everything 1976 * else, this is the true function 1977 * @param string $additional_params 1978 * @param string $player 1979 * @param boolean $local 1980 * @param int|string $uid 1981 * @return string 1982 */ 1983 public function play_url($additional_params = '', $player = '', $local = false, $uid = false) 1984 { 1985 if (!$this->id) { 1986 return ''; 1987 } 1988 if (!$uid) { 1989 // No user in the case of upnp. Set to 0 instead. required to fix database insertion errors 1990 $uid = Core::get_global('user')->id ?: 0; 1991 } 1992 // set no use when using auth 1993 if (!AmpConfig::get('use_auth') && !AmpConfig::get('require_session')) { 1994 $uid = -1; 1995 } 1996 1997 $type = $this->type; 1998 1999 $this->format(); 2000 $media_name = $this->get_stream_name() . "." . $type; 2001 $media_name = preg_replace("/[^a-zA-Z0-9\. ]+/", "-", $media_name); 2002 $media_name = rawurlencode($media_name); 2003 2004 $url = Stream::get_base_url($local) . "type=song&oid=" . $this->id . "&uid=" . (string) $uid . $additional_params; 2005 if ($player !== '') { 2006 $url .= "&player=" . $player; 2007 } 2008 $url .= "&name=" . $media_name; 2009 2010 return Stream_Url::format($url); 2011 } 2012 2013 /** 2014 * Get stream name. 2015 * @return string 2016 */ 2017 public function get_stream_name() 2018 { 2019 return $this->get_artist_name() . " - " . $this->title; 2020 } 2021 2022 /** 2023 * get_recently_played 2024 * This function returns the last X songs that have been played 2025 * it uses the popular threshold to figure out how many to pull 2026 * it will only return unique object 2027 * @param integer $user_id 2028 * @return array 2029 */ 2030 public static function get_recently_played($user_id = 0) 2031 { 2032 $personal_info_recent = 91; 2033 $personal_info_time = 92; 2034 $personal_info_agent = 93; 2035 $catalog_filter = AmpConfig::get('catalog_filter'); 2036 $user_id = ($catalog_filter) 2037 ? Core::get_global('user')->id 2038 : $user_id; 2039 2040 $results = array(); 2041 $valid = ($user_id > 0); 2042 $limit = AmpConfig::get('popular_threshold', 10); 2043 $sql = "SELECT `object_id`, `object_count`.`user`, `object_type`, `date`, `agent`, `geo_latitude`, `geo_longitude`, `geo_name`, `pref_recent`.`value` AS `user_recent`, `pref_time`.`value` AS `user_time`, `pref_agent`.`value` AS `user_agent` FROM `object_count` LEFT JOIN `user_preference` AS `pref_recent` ON `pref_recent`.`preference`='$personal_info_recent' AND `pref_recent`.`user` = `object_count`.`user` LEFT JOIN `user_preference` AS `pref_time` ON `pref_time`.`preference`='$personal_info_time' AND `pref_time`.`user` = `object_count`.`user` LEFT JOIN `user_preference` AS `pref_agent` ON `pref_agent`.`preference`='$personal_info_agent' AND `pref_agent`.`user` = `object_count`.`user` WHERE `object_type` = 'song' AND `count_type` = 'stream' "; 2044 if (AmpConfig::get('catalog_disable')) { 2045 $sql .= "AND " . Catalog::get_enable_filter('song', '`object_id`') . " "; 2046 } 2047 if ($catalog_filter && $valid) { 2048 $sql .= "AND" . Catalog::get_user_filter('object_count_song', $user_id) . " "; 2049 } 2050 if ($valid && !$catalog_filter) { 2051 // If user is not empty, we're looking directly to user personal info (admin view) 2052 $sql .= "AND `object_count`.`user`='$user_id' "; 2053 } else { 2054 if (!Access::check('interface', 100)) { 2055 // If user identifier is empty, we need to retrieve only users which have allowed view of personal info 2056 $current_user = (int) Core::get_global('user')->id; 2057 if ($current_user > 0) { 2058 $sql .= "AND `object_count`.`user` IN (SELECT `user` FROM `user_preference` WHERE (`preference`='$personal_info_recent' AND `value`='1') OR `user`='$current_user') "; 2059 } 2060 } 2061 } 2062 $sql .= "ORDER BY `date` DESC LIMIT " . (string)$limit . " "; 2063 //debug_event(self::class, 'get_recently_played ' . $sql, 5); 2064 2065 $db_results = Dba::read($sql); 2066 while ($row = Dba::fetch_assoc($db_results)) { 2067 if (empty($row['geo_name']) && $row['latitude'] && $row['longitude']) { 2068 $row['geo_name'] = Stats::get_cached_place_name($row['latitude'], $row['longitude']); 2069 } 2070 $results[] = $row; 2071 } 2072 2073 return $results; 2074 } // get_recently_played 2075 2076 /** 2077 * Get stream types. 2078 * @param string $player 2079 * @return array 2080 */ 2081 public function get_stream_types($player = null) 2082 { 2083 return self::get_stream_types_for_type($this->type, $player); 2084 } 2085 2086 /** 2087 * Get stream types for media type. 2088 * @param string $type 2089 * @param string $player 2090 * @return array 2091 */ 2092 public static function get_stream_types_for_type($type, $player = '') 2093 { 2094 $types = array(); 2095 $transcode = AmpConfig::get('transcode_' . $type); 2096 if ($player !== '') { 2097 $player_transcode = AmpConfig::get('transcode_player_' . $player . '_' . $type); 2098 if ($player_transcode) { 2099 $transcode = $player_transcode; 2100 } 2101 } 2102 2103 if ($transcode != 'required') { 2104 $types[] = 'native'; 2105 } 2106 if (make_bool($transcode)) { 2107 $types[] = 'transcode'; 2108 } 2109 2110 return $types; 2111 } 2112 2113 /** 2114 * Get transcode settings for media. 2115 * It can be confusing but when waveforms are enabled 2116 * it will transcode the file twice. 2117 * 2118 * @param string $source 2119 * @param string $target 2120 * @param string $player 2121 * @param string $media_type 2122 * @param array $options 2123 * @return array 2124 */ 2125 public static function get_transcode_settings_for_media( 2126 $source, 2127 $target = null, 2128 $player = null, 2129 $media_type = 'song', 2130 $options = array() 2131 ) { 2132 // default target for songs 2133 $setting_target = 'encode_target'; 2134 // default target for video 2135 if ($media_type != 'song') { 2136 $setting_target = 'encode_' . $media_type . '_target'; 2137 } 2138 // webplayer / api transcode actions 2139 $has_player_target = false; 2140 if ($player) { 2141 // encode target for songs in webplayer/api 2142 $player_setting_target = 'encode_player_' . $player . '_target'; 2143 if ($media_type != 'song') { 2144 // encode target for video in webplayer/api 2145 $player_setting_target = 'encode_' . $media_type . '_player_' . $player . '_target'; 2146 } 2147 $has_player_target = AmpConfig::get($player_setting_target); 2148 } 2149 $has_default_target = AmpConfig::get($setting_target); 2150 $has_codec_target = AmpConfig::get('encode_target_' . $source); 2151 2152 // Fall backwards from the specific transcode formats to default 2153 // TARGET > PLAYER > CODEC > DEFAULT 2154 if ($target) { 2155 debug_event(self::class, 'Explicit target requested: {' . $target . '} format for: ' . $source, 5); 2156 } elseif ($has_player_target) { 2157 $target = $has_player_target; 2158 debug_event(self::class, 'Transcoding for ' . $player . ': {' . $target . '} format for: ' . $source, 5); 2159 } elseif ($has_codec_target) { 2160 $target = $has_codec_target; 2161 debug_event(self::class, 'Transcoding for codec: {' . $target . '} format for: ' . $source, 5); 2162 } elseif ($has_default_target) { 2163 $target = $has_default_target; 2164 debug_event(self::class, 'Transcoding to default: {' . $target . '} format for: ' . $source, 5); 2165 } 2166 // fall back to resampling if no default 2167 if (!$target) { 2168 $target = $source; 2169 debug_event(self::class, 'No transcode target for: ' . $source . ', choosing to resample', 5); 2170 } 2171 2172 $cmd = AmpConfig::get('transcode_cmd_' . $source) ?: AmpConfig::get('transcode_cmd'); 2173 if (empty($cmd)) { 2174 debug_event(self::class, 'A valid transcode_cmd is required to transcode', 5); 2175 2176 return array(); 2177 } 2178 2179 $args = ''; 2180 if (AmpConfig::get('encode_ss_frame') && isset($options['frame'])) { 2181 $args .= ' ' . AmpConfig::get('encode_ss_frame'); 2182 } 2183 if (AmpConfig::get('encode_ss_duration') && isset($options['duration'])) { 2184 $args .= ' ' . AmpConfig::get('encode_ss_duration'); 2185 } 2186 $args .= ' ' . AmpConfig::get('transcode_input'); 2187 2188 if (AmpConfig::get('encode_srt') && $options['subtitle']) { 2189 debug_event(self::class, 'Using subtitle ' . $options['subtitle'], 5); 2190 $args .= ' ' . AmpConfig::get('encode_srt'); 2191 } 2192 2193 $argst = AmpConfig::get('encode_args_' . $target); 2194 if (!$args) { 2195 debug_event(self::class, 'Target format ' . $target . ' is not properly configured', 2); 2196 2197 return array(); 2198 } 2199 $args .= ' ' . $argst; 2200 2201 debug_event(self::class, 'Command: ' . $cmd . ' Arguments:' . $args, 5); 2202 2203 return array('format' => $target, 'command' => $cmd . $args); 2204 } 2205 2206 /** 2207 * Get transcode settings. 2208 * @param string $target 2209 * @param string $player 2210 * @param array $options 2211 * @return array 2212 */ 2213 public function get_transcode_settings($target = null, $player = null, $options = array()) 2214 { 2215 return self::get_transcode_settings_for_media($this->type, $target, $player, 'song', $options); 2216 } 2217 2218 /** 2219 * Get lyrics. 2220 * @return array 2221 */ 2222 public function get_lyrics() 2223 { 2224 if ($this->lyrics) { 2225 return array('text' => $this->lyrics); 2226 } 2227 2228 foreach (Plugin::get_plugins('get_lyrics') as $plugin_name) { 2229 $plugin = new Plugin($plugin_name); 2230 if ($plugin->load(Core::get_global('user'))) { 2231 $lyrics = $plugin->_plugin->get_lyrics($this); 2232 if ($lyrics != false) { 2233 return $lyrics; 2234 } 2235 } 2236 } 2237 2238 return null; 2239 } 2240 2241 /** 2242 * Run custom play action. 2243 * @param integer $action_index 2244 * @param string $codec 2245 * @return array 2246 */ 2247 public function run_custom_play_action($action_index, $codec = '') 2248 { 2249 $transcoder = array(); 2250 $actions = self::get_custom_play_actions(); 2251 if ($action_index <= count($actions)) { 2252 $action = $actions[$action_index - 1]; 2253 if (!$codec) { 2254 $codec = $this->type; 2255 } 2256 2257 $run = str_replace("%f", $this->file, $action['run']); 2258 $run = str_replace("%c", $codec, $run); 2259 $run = str_replace("%a", $this->f_artist, $run); 2260 $run = str_replace("%A", $this->f_album, $run); 2261 $run = str_replace("%t", $this->f_title, $run); 2262 2263 debug_event(self::class, "Running custom play action: " . $run, 3); 2264 2265 $descriptors = array(1 => array('pipe', 'w')); 2266 if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { 2267 // Windows doesn't like to provide stderr as a pipe 2268 $descriptors[2] = array('pipe', 'w'); 2269 } 2270 $process = proc_open($run, $descriptors, $pipes); 2271 2272 $transcoder['process'] = $process; 2273 $transcoder['handle'] = $pipes[1]; 2274 $transcoder['stderr'] = $pipes[2]; 2275 $transcoder['format'] = $codec; 2276 } 2277 2278 return $transcoder; 2279 } 2280 2281 /** 2282 * Get custom play actions. 2283 * @return array 2284 */ 2285 public static function get_custom_play_actions() 2286 { 2287 $actions = array(); 2288 $count = 0; 2289 while (AmpConfig::get('custom_play_action_title_' . $count)) { 2290 $actions[] = array( 2291 'index' => ($count + 1), 2292 'title' => AmpConfig::get('custom_play_action_title_' . $count), 2293 'icon' => AmpConfig::get('custom_play_action_icon_' . $count), 2294 'run' => AmpConfig::get('custom_play_action_run_' . $count), 2295 ); 2296 ++$count; 2297 } 2298 2299 return $actions; 2300 } 2301 2302 /** 2303 * Update Metadata from array 2304 * @param array $meta_value 2305 */ 2306 public function updateMetadata($meta_value) 2307 { 2308 foreach ($meta_value as $metadataId => $value) { 2309 $metadata = $this->metadataRepository->findById($metadataId); 2310 if (!$metadata || $value != $metadata->getData()) { 2311 $metadata->setData($value); 2312 $this->metadataRepository->update($metadata); 2313 } 2314 } 2315 } 2316 2317 /** 2318 * get_deleted 2319 * get items from the deleted_songs table 2320 * @return int[] 2321 */ 2322 public static function get_deleted() 2323 { 2324 $deleted = array(); 2325 $sql = "SELECT * FROM `deleted_song`"; 2326 $db_results = Dba::read($sql); 2327 while ($row = Dba::fetch_assoc($db_results)) { 2328 $deleted[] = $row; 2329 } 2330 2331 return $deleted; 2332 } // get_deleted 2333 2334 /** 2335 * remove 2336 * Remove the song from disk. 2337 * @return bool 2338 */ 2339 public function remove(): bool 2340 { 2341 return $this->getSongDeleter()->delete($this); 2342 } 2343 2344 /** 2345 * @deprecated 2346 */ 2347 private function getSongTagWriter(): SongTagWriterInterface 2348 { 2349 global $dic; 2350 2351 return $dic->get(SongTagWriterInterface::class); 2352 } 2353 2354 /** 2355 * @deprecated 2356 */ 2357 private static function getLicenseRepository(): LicenseRepositoryInterface 2358 { 2359 global $dic; 2360 2361 return $dic->get(LicenseRepositoryInterface::class); 2362 } 2363 2364 /** 2365 * @deprecated 2366 */ 2367 private function getSongDeleter(): SongDeleterInterface 2368 { 2369 global $dic; 2370 2371 return $dic->get(SongDeleterInterface::class); 2372 } 2373 2374 /** 2375 * @deprecated inject dependency 2376 */ 2377 private static function getUserActivityPoster(): UserActivityPosterInterface 2378 { 2379 global $dic; 2380 2381 return $dic->get(UserActivityPosterInterface::class); 2382 } 2383} 2384