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 */ 22 23declare(strict_types=0); 24 25namespace Ampache\Repository\Model; 26 27use Ampache\Module\Playback\Stream; 28use Ampache\Module\Playback\Stream_Url; 29use Ampache\Module\Statistics\Stats; 30use Ampache\Module\Authorization\Access; 31use Ampache\Module\System\Dba; 32use Ampache\Module\Util\ObjectTypeToClassNameMapper; 33use Ampache\Config\AmpConfig; 34use Ampache\Module\System\Core; 35use Ampache\Repository\ShoutRepositoryInterface; 36use Ampache\Repository\UserActivityRepositoryInterface; 37 38class Video extends database_object implements Media, library_item, GarbageCollectibleInterface 39{ 40 protected const DB_TABLENAME = 'video'; 41 42 /** 43 * @var integer $id 44 */ 45 public $id; 46 /** 47 * @var string $title 48 */ 49 public $title; 50 /** 51 * @var boolean $played 52 */ 53 public $played; 54 /** 55 * @var boolean $enabled 56 */ 57 public $enabled; 58 /** 59 * @var string $file 60 */ 61 public $file; 62 /** 63 * @var integer $size 64 */ 65 public $size; 66 /** 67 * @var string $video_codec 68 */ 69 public $video_codec; 70 /** 71 * @var string $audio_codec 72 */ 73 public $audio_codec; 74 /** 75 * @var integer $resolution_x 76 */ 77 public $resolution_x; 78 /** 79 * @var integer $resolution_y 80 */ 81 public $resolution_y; 82 /** 83 * @var integer $time 84 */ 85 public $time; 86 /** 87 * @var string $mime 88 */ 89 public $mime; 90 /** 91 * @var integer $release_date 92 */ 93 public $release_date; 94 /** 95 * @var integer $catalog 96 */ 97 public $catalog; 98 /** 99 * @var integer $bitrate 100 */ 101 public $bitrate; 102 /** 103 * @var string $mode 104 */ 105 public $mode; 106 /** 107 * @var integer $channels 108 */ 109 public $channels; 110 /** 111 * @var integer $display_x 112 */ 113 public $display_x; 114 /** 115 * @var integer $display_x 116 */ 117 public $display_y; 118 /** 119 * @var float $frame_rate 120 */ 121 public $frame_rate; 122 /** 123 * @var integer $video_bitrate 124 */ 125 public $video_bitrate; 126 127 /** 128 * @var string $type 129 */ 130 public $type; 131 /** 132 * @var array $tags 133 */ 134 public $tags; 135 /** 136 * @var integer $object_cnt 137 */ 138 public $object_cnt; 139 /** 140 * @var integer $total_count 141 */ 142 private $total_count; 143 /** 144 * @var integer $f_release_date 145 */ 146 public $update_time; 147 /** 148 * @var integer $f_release_date 149 */ 150 public $addition_time; 151 /** 152 * @var string $f_title 153 */ 154 public $f_title; 155 /** 156 * @var string $f_full_title 157 */ 158 public $f_full_title; 159 /** 160 * @var string $f_artist_full 161 */ 162 public $f_artist_full; 163 /** 164 * @var string $f_time 165 */ 166 public $f_time; 167 /** 168 * @var string $f_time_h 169 */ 170 public $f_time_h; 171 /** 172 * @var string $link 173 */ 174 public $link; 175 /** 176 * @var string $f_link 177 */ 178 public $f_link; 179 /** 180 * @var string $f_codec 181 */ 182 public $f_codec; 183 /** 184 * @var string $f_resolution 185 */ 186 public $f_resolution; 187 /** 188 * @var string $f_display 189 */ 190 public $f_display; 191 /** 192 * @var string $f_bitrate 193 */ 194 public $f_bitrate; 195 /** 196 * @var string $f_video_bitrate 197 */ 198 public $f_video_bitrate; 199 /** 200 * @var string $f_frame_rate 201 */ 202 public $f_frame_rate; 203 /** 204 * @var string $f_tags 205 */ 206 public $f_tags; 207 /** 208 * @var string $f_length 209 */ 210 public $f_length; 211 /** 212 * @var string $f_file 213 */ 214 public $f_file; 215 /** 216 * @var string $f_release_date 217 */ 218 public $f_release_date; 219 220 /** 221 * Constructor 222 * This pulls the information from the database and returns 223 * a constructed object 224 * @param integer|null $video_id 225 */ 226 public function __construct($video_id = null) 227 { 228 if ($video_id === null) { 229 return false; 230 } 231 232 // Load the data from the database 233 $info = $this->get_info($video_id, 'video'); 234 foreach ($info as $key => $value) { 235 $this->$key = $value; 236 } 237 238 $data = pathinfo($this->file); 239 $this->type = strtolower((string) $data['extension']); 240 $this->object_cnt = (int)$this->total_count; 241 242 return true; 243 } // Constructor 244 245 public function getId(): int 246 { 247 return (int) $this->id; 248 } 249 250 /** 251 * Create a video strongly typed object from its id. 252 * @param integer $video_id 253 * @return Video 254 */ 255 public static function create_from_id($video_id) 256 { 257 foreach (ObjectTypeToClassNameMapper::VIDEO_TYPES as $dtype) { 258 $sql = "SELECT `id` FROM `" . strtolower((string) $dtype) . "` WHERE `id` = ?"; 259 $db_results = Dba::read($sql, array($video_id)); 260 $results = Dba::fetch_assoc($db_results); 261 if ($results['id']) { 262 $class_name = ObjectTypeToClassNameMapper::map(strtolower($dtype)); 263 264 return new $class_name($video_id); 265 } 266 } 267 268 return new Video($video_id); 269 } 270 271 /** 272 * build_cache 273 * Build a cache based on the array of ids passed, saves lots of little queries 274 * @param integer[] $ids 275 * @return boolean 276 */ 277 public static function build_cache($ids) 278 { 279 if (empty($ids)) { 280 return false; 281 } 282 283 $idlist = '(' . implode(',', $ids) . ')'; 284 $sql = "SELECT * FROM `video` WHERE `video`.`id` IN $idlist"; 285 $db_results = Dba::read($sql); 286 287 while ($row = Dba::fetch_assoc($db_results)) { 288 parent::add_to_cache('video', $row['id'], $row); 289 } 290 291 return true; 292 } // build_cache 293 294 /** 295 * format 296 * This formats a video object so that it is human readable 297 * @param boolean $details 298 */ 299 public function format($details = true) 300 { 301 $this->f_title = filter_var($this->title, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES); 302 $this->f_full_title = $this->f_title; 303 $this->link = AmpConfig::get('web_path') . "/video.php?action=show_video&video_id=" . $this->id; 304 $this->f_link = "<a href=\"" . $this->link . "\" title=\"" . $this->f_title . "\"> " . $this->f_title . "</a>"; 305 $this->f_codec = $this->video_codec . ' / ' . $this->audio_codec; 306 if ($this->resolution_x || $this->resolution_y) { 307 $this->f_resolution = $this->resolution_x . 'x' . $this->resolution_y; 308 } 309 if ($this->display_x || $this->display_y) { 310 $this->f_display = $this->display_x . 'x' . $this->display_y; 311 } 312 313 // Format the Bitrate 314 $this->f_bitrate = (int) ($this->bitrate / 1000) . "-" . strtoupper((string) $this->mode); 315 $this->f_video_bitrate = (string) (int) ($this->video_bitrate / 1000); 316 if ($this->frame_rate) { 317 $this->f_frame_rate = $this->frame_rate . ' fps'; 318 } 319 320 // Format the Time 321 $min = floor($this->time / 60); 322 $sec = sprintf("%02d", ($this->time % 60)); 323 $this->f_time = $min . ":" . $sec; 324 $hour = sprintf("%02d", floor($min / 60)); 325 $min_h = sprintf("%02d", ($min % 60)); 326 $this->f_time_h = $hour . ":" . $min_h . ":" . $sec; 327 328 if ($details) { 329 // Get the top tags 330 $this->tags = Tag::get_top_tags('video', $this->id); 331 $this->f_tags = Tag::get_display($this->tags, true, 'video'); 332 } 333 334 $this->f_length = floor($this->time / 60) . ' ' . T_('minutes'); 335 $this->f_file = $this->f_title . '.' . $this->type; 336 if ($this->release_date) { 337 $this->f_release_date = get_datetime((int) $this->release_date, 'short', 'none'); 338 } 339 } // format 340 341 /** 342 * Get item keywords for metadata searches. 343 * @return array 344 */ 345 public function get_keywords() 346 { 347 $keywords = array(); 348 $keywords['title'] = array('important' => true, 349 'label' => T_('Title'), 350 'value' => $this->f_title); 351 352 return $keywords; 353 } 354 355 /** 356 * Get item fullname. 357 * @return string 358 */ 359 public function get_fullname() 360 { 361 return $this->f_title; 362 } 363 364 /** 365 * Get parent item description. 366 * @return array|null 367 */ 368 public function get_parent() 369 { 370 return null; 371 } 372 373 /** 374 * Get item childrens. 375 * @return array 376 */ 377 public function get_childrens() 378 { 379 return array(); 380 } 381 382 /** 383 * Search for item childrens. 384 * @param string $name 385 * @return array 386 */ 387 public function search_childrens($name) 388 { 389 debug_event(self::class, 'search_childrens ' . $name, 5); 390 391 return array(); 392 } 393 394 /** 395 * Get all childrens and sub-childrens medias. 396 * @param string $filter_type 397 * @return array 398 */ 399 public function get_medias($filter_type = null) 400 { 401 $medias = array(); 402 if ($filter_type === null || $filter_type == 'video') { 403 $medias[] = array( 404 'object_type' => 'video', 405 'object_id' => $this->id 406 ); 407 } 408 409 return $medias; 410 } 411 412 /** 413 * get_catalogs 414 * 415 * Get all catalog ids related to this item. 416 * @return integer[] 417 */ 418 public function get_catalogs() 419 { 420 return array($this->catalog); 421 } 422 423 /** 424 * Get item's owner. 425 * @return integer|null 426 */ 427 public function get_user_owner() 428 { 429 return null; 430 } 431 432 /** 433 * Get default art kind for this item. 434 * @return string 435 */ 436 public function get_default_art_kind() 437 { 438 return 'preview'; 439 } 440 441 /** 442 * @return string 443 */ 444 public function get_description() 445 { 446 return ''; 447 } 448 449 /** 450 * display_art 451 * @param integer $thumb 452 * @param boolean $force 453 */ 454 public function display_art($thumb = 2, $force = false) 455 { 456 if (Art::has_db($this->id, 'video') || $force) { 457 Art::display('video', $this->id, $this->get_fullname(), $thumb, $this->link); 458 } 459 } 460 461 /** 462 * garbage_collection 463 * 464 * Cleans up the inherited object tables 465 */ 466 public static function garbage_collection() 467 { 468 // delete files matching catalog_ignore_pattern 469 $ignore_pattern = AmpConfig::get('catalog_ignore_pattern'); 470 if ($ignore_pattern) { 471 Dba::write("DELETE FROM `video` WHERE `file` REGEXP ?;", array($ignore_pattern)); 472 } 473 // clean up missing catalogs 474 Dba::write("DELETE FROM `video` WHERE `video`.`catalog` NOT IN (SELECT `id` FROM `catalog`);"); 475 // clean up sub-tables of videos 476 Movie::garbage_collection(); 477 TVShow_Episode::garbage_collection(); 478 TVShow_Season::garbage_collection(); 479 TvShow::garbage_collection(); 480 Personal_Video::garbage_collection(); 481 Clip::garbage_collection(); 482 } 483 484 /** 485 * Get stream types. 486 * @param string $player 487 * @return array 488 */ 489 public function get_stream_types($player = null) 490 { 491 return Song::get_stream_types_for_type($this->type, $player); 492 } 493 494 /** 495 * play_url 496 * This returns a "PLAY" url for the video in question here, this currently feels a little 497 * like a hack, might need to adjust it in the future 498 * @param string $additional_params 499 * @param string $player 500 * @param boolean $local 501 * @param int|string $uid 502 * @return string 503 */ 504 public function play_url($additional_params = '', $player = '', $local = false, $uid = false) 505 { 506 if (!$this->id) { 507 return ''; 508 } 509 if (!$uid) { 510 // No user in the case of upnp. Set to 0 instead. required to fix database insertion errors 511 $uid = Core::get_global('user')->id ?: 0; 512 } 513 // set no use when using auth 514 if (!AmpConfig::get('use_auth') && !AmpConfig::get('require_session')) { 515 $uid = -1; 516 } 517 518 $type = $this->type; 519 520 $this->format(); 521 $media_name = $this->get_stream_name() . "." . $type; 522 $media_name = preg_replace("/[^a-zA-Z0-9\. ]+/", "-", $media_name); 523 $media_name = rawurlencode($media_name); 524 525 $url = Stream::get_base_url($local) . "type=video&oid=" . $this->id . "&uid=" . (string) $uid . $additional_params; 526 if ($player !== '') { 527 $url .= "&player=" . $player; 528 } 529 $url .= "&name=" . $media_name; 530 531 return Stream_Url::format($url); 532 } 533 534 /** 535 * Get stream name. 536 * @return string 537 */ 538 public function get_stream_name() 539 { 540 return $this->title; 541 } 542 543 /** 544 * get_transcode_settings 545 * @param string $target 546 * @param array $options 547 * @param string $player 548 * @return array 549 */ 550 public function get_transcode_settings($target = null, $player = null, $options = array()) 551 { 552 return Song::get_transcode_settings_for_media($this->type, $target, $player, 'video', $options); 553 } 554 555 /** 556 * type_to_mime 557 * 558 * Returns the mime type for the specified file extension/type 559 * @param string $type 560 * @return string 561 */ 562 public static function type_to_mime($type) 563 { 564 // FIXME: This should really be done the other way around. 565 // Store the mime type in the database, and provide a function 566 // to make it a human-friendly type. 567 switch ($type) { 568 case 'avi': 569 return 'video/avi'; 570 case 'ogg': 571 case 'ogv': 572 return 'application/ogg'; 573 case 'wmv': 574 return 'audio/x-ms-wmv'; 575 case 'mp4': 576 case 'm4v': 577 return 'video/mp4'; 578 case 'mkv': 579 return 'video/x-matroska'; 580 case 'mov': 581 return 'video/quicktime'; 582 case 'divx': 583 return 'video/x-divx'; 584 case 'webm': 585 return 'video/webm'; 586 case 'flv': 587 return 'video/x-flv'; 588 case 'ts': 589 return 'video/mp2t'; 590 case 'mpg': 591 case 'mpeg': 592 case 'm2ts': 593 default: 594 return 'video/mpeg'; 595 } 596 } 597 598 /** 599 * Insert new video. 600 * @param array $data 601 * @param array $gtypes 602 * @param array $options 603 * @return integer 604 */ 605 public static function insert(array $data, $gtypes = array(), $options = array()) 606 { 607 $check_file = Catalog::get_id_from_file($data['file'], 'video'); 608 if ($check_file > 0) { 609 return $check_file; 610 } 611 $bitrate = (int) $data['bitrate']; 612 $mode = $data['mode']; 613 $rezx = (int) $data['resolution_x']; 614 $rezy = (int) $data['resolution_y']; 615 $release_date = (int) $data['release_date']; 616 // No release date, then release date = production year 617 if (!$release_date && $data['year']) { 618 $release_date = strtotime((string) $data['year'] . '-01-01'); 619 } 620 $tags = $data['genre']; 621 $channels = (int) $data['channels']; 622 $disx = (int) $data['display_x']; 623 $disy = (int) $data['display_y']; 624 $frame_rate = (float) $data['frame_rate']; 625 $video_bitrate = (int) Catalog::check_int($data['video_bitrate'], 4294967294, 0); 626 627 $sql = "INSERT INTO `video` (`file`, `catalog`, `title`, `video_codec`, `audio_codec`, `resolution_x`, `resolution_y`, `size`, `time`, `mime`, `release_date`, `addition_time`, `bitrate`, `mode`, `channels`, `display_x`, `display_y`, `frame_rate`, `video_bitrate`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; 628 $params = array($data['file'], $data['catalog'], $data['title'], $data['video_codec'], $data['audio_codec'], $rezx, $rezy, $data['size'], $data['time'], $data['mime'], $release_date, time(), $bitrate, $mode, $channels, $disx, $disy, $frame_rate, $video_bitrate); 629 Dba::write($sql, $params); 630 $video_id = (int) Dba::insert_id(); 631 632 Catalog::update_map((int)$data['catalog'], 'video', $video_id); 633 634 if (is_array($tags)) { 635 foreach ($tags as $tag) { 636 $tag = trim((string) $tag); 637 if (!empty($tag)) { 638 Tag::add('video', $video_id, $tag, false); 639 } 640 } 641 } 642 643 if ($data['art'] && $options['gather_art']) { 644 $art = new Art((int) $video_id, 'video'); 645 $art->insert_url($data['art']); 646 } 647 648 $data['id'] = $video_id; 649 650 return self::insert_video_type($data, $gtypes, $options); 651 } 652 653 /** 654 * Insert video for derived type. 655 * @param array $data 656 * @param array $gtypes 657 * @param array $options 658 * @return integer 659 */ 660 private static function insert_video_type(array $data, $gtypes, $options = array()) 661 { 662 if (count($gtypes) > 0) { 663 $gtype = $gtypes[0]; 664 switch ($gtype) { 665 case 'tvshow': 666 return TVShow_Episode::insert($data, $gtypes, $options); 667 case 'movie': 668 return Movie::insert($data, $gtypes, $options); 669 case 'clip': 670 return Clip::insert($data, $gtypes, $options); 671 case 'personal_video': 672 return Personal_Video::insert($data, $gtypes, $options); 673 default: 674 // Do nothing, video entry already created and no additional data for now 675 break; 676 } 677 } 678 679 return $data['id']; 680 } 681 682 /** 683 * update 684 * This takes a key'd array of data as input and updates a video entry 685 * @param array $data 686 * @return integer 687 */ 688 public function update(array $data) 689 { 690 $sql = "UPDATE `video` SET `title` = ?"; 691 $title = isset($data['title']) 692 ? $data['title'] 693 : $this->title; 694 $params = array($title); 695 // don't require a release date when updating a video 696 if (isset($data['release_date'])) { 697 $f_release_date = (string) $data['release_date']; 698 $release_date = strtotime($f_release_date); 699 $this->release_date = $release_date; 700 $sql .= ", `release_date` = ?"; 701 $params[] = $release_date; 702 } 703 $sql .= " WHERE `id` = ?"; 704 $params[] = $this->id; 705 706 Dba::write($sql, $params); 707 708 if (isset($data['edit_tags'])) { 709 Tag::update_tag_list($data['edit_tags'], 'video', $this->id, true); 710 } 711 712 $this->title = $title; 713 714 return $this->id; 715 } // update 716 717 /** 718 * @param integer $video_id 719 * @param Video $new_video 720 */ 721 public static function update_video($video_id, Video $new_video) 722 { 723 $update_time = time(); 724 725 $sql = "UPDATE `video` SET `title` = ?, `bitrate` = ?, `size` = ?, `time` = ?, `video_codec` = ?, `audio_codec` = ?, `resolution_x` = ?, `resolution_y` = ?, `release_date` = ?, `channels` = ?, `display_x` = ?, `display_y` = ?, `frame_rate` = ?, `video_bitrate` = ?, `update_time` = ? WHERE `id` = ?"; 726 727 Dba::write($sql, array($new_video->title, $new_video->bitrate, $new_video->size, $new_video->time, $new_video->video_codec, $new_video->audio_codec, $new_video->resolution_x, $new_video->resolution_y, $new_video->release_date, $new_video->channels, $new_video->display_x, $new_video->display_y, $new_video->frame_rate, $new_video->video_bitrate, $update_time, $video_id)); 728 } 729 730 /** 731 * update_video_counts 732 * 733 * @param integer $video_id 734 */ 735 public static function update_video_counts($video_id) 736 { 737 if ($video_id > 0) { 738 $params = array($video_id); 739 $sql = "UPDATE `video` SET `total_count` = 0 WHERE `total_count` > 0 AND `id` NOT IN (SELECT `object_id` FROM `object_count` WHERE `object_count`.`object_id` = ? AND `object_count`.`object_type` = 'video' AND `object_count`.`count_type` = 'stream');"; 740 Dba::write($sql, $params); 741 $sql = "UPDATE `video` SET `total_skip` = 0 WHERE `total_skip` > 0 AND `id` NOT IN (SELECT `object_id` FROM `object_count` WHERE `object_count`.`object_id` = ? AND `object_count`.`object_type` = 'video' AND `object_count`.`count_type` = 'stream');"; 742 Dba::write($sql, $params); 743 $sql = "UPDATE `video` SET `video`.`played` = 0 WHERE `video`.`played` = 1 AND `video`.`id` NOT IN (SELECT `object_id` FROM `object_count` WHERE `object_count`.`object_id` = ? AND `object_type` = 'video' AND `count_type` = 'stream');"; 744 Dba::write($sql, $params); 745 $sql = "UPDATE `video` SET `video`.`played` = 1 WHERE `video`.`played` = 0 AND `video`.`id` IN (SELECT `object_id` FROM `object_count` WHERE `object_count`.`object_id` = ? AND `object_type` = 'video' AND `count_type` = 'stream');"; 746 Dba::write($sql, $params); 747 } 748 } 749 750 /** 751 * Get release item art. 752 * @return array 753 */ 754 public function get_release_item_art() 755 { 756 return array('object_type' => 'video', 757 'object_id' => $this->id 758 ); 759 } 760 761 /** 762 * generate_preview 763 * Generate video preview image from a video file 764 * @param integer $video_id 765 * @param boolean $overwrite 766 */ 767 public static function generate_preview($video_id, $overwrite = false) 768 { 769 if ($overwrite || !Art::has_db($video_id, 'video', 'preview')) { 770 $artp = new Art($video_id, 'video', 'preview'); 771 $video = new Video($video_id); 772 $image = Stream::get_image_preview($video); 773 $artp->insert($image, 'image/png'); 774 } 775 } 776 777 /** 778 * set_played 779 * this checks to see if the current object has been played 780 * if not then it sets it to played. In any case it updates stats. 781 * @param integer $user 782 * @param string $agent 783 * @param array $location 784 * @param integer $date 785 * @return boolean 786 */ 787 public function set_played($user, $agent, $location, $date = null) 788 { 789 // ignore duplicates or skip the last track 790 if (!$this->check_play_history($user, $agent, $date)) { 791 return false; 792 } 793 Stats::insert('video', $this->id, $user, $agent, $location, 'stream', $date); 794 795 if ($this->played) { 796 return true; 797 } 798 799 /* If it hasn't been played, set it! */ 800 Video::update_played(true, $this->id); 801 802 return true; 803 } // set_played 804 805 /** 806 * @param integer $user 807 * @param string $agent 808 * @param integer $date 809 * @return boolean 810 */ 811 public function check_play_history($user, $agent, $date) 812 { 813 return Stats::has_played_history('video', $this, $user, $agent, $date); 814 } 815 816 /** 817 * get_subtitles 818 * Get existing subtitles list for this video 819 * @return array 820 */ 821 public function get_subtitles() 822 { 823 $subtitles = array(); 824 $pinfo = pathinfo($this->file); 825 $filter = $pinfo['dirname'] . DIRECTORY_SEPARATOR . $pinfo['filename'] . '*.srt'; 826 827 foreach (glob($filter) as $srt) { 828 $psrt = explode('.', $srt); 829 $lang_code = '__'; 830 $lang_name = T_('Unknown'); 831 if (count($psrt) >= 2) { 832 $lang_code = $psrt[count($psrt) - 2]; 833 if (strlen((string) $lang_code) == 2) { 834 $lang_name = $this->get_language_name($lang_code); 835 } 836 } 837 $subtitles[] = array( 838 'file' => $pinfo['dirname'] . DIRECTORY_SEPARATOR . $srt, 839 'lang_code' => $lang_code, 840 'lang_name' => $lang_name 841 ); 842 } 843 844 return $subtitles; 845 } 846 847 /** 848 * Get language name from code. 849 * @param string $code 850 * @return string 851 */ 852 protected function get_language_name($code) 853 { 854 $languageCodes = array( 855 "aa" => T_("Afar"), 856 "ab" => T_("Abkhazian"), 857 "ae" => T_("Avestan"), 858 "af" => T_("Afrikaans"), 859 "ak" => T_("Akan"), 860 "am" => T_("Amharic"), 861 "an" => T_("Aragonese"), 862 "ar" => T_("Arabic"), 863 "as" => T_("Assamese"), 864 "av" => T_("Avaric"), 865 "ay" => T_("Aymara"), 866 "az" => T_("Azerbaijani"), 867 "ba" => T_("Bashkir"), 868 "be" => T_("Belarusian"), 869 "bg" => T_("Bulgarian"), 870 "bh" => T_("Bihari"), 871 "bi" => T_("Bislama"), 872 "bm" => T_("Bambara"), 873 "bn" => T_("Bengali"), 874 "bo" => T_("Tibetan"), 875 "br" => T_("Breton"), 876 "bs" => T_("Bosnian"), 877 "ca" => T_("Catalan"), 878 "ce" => T_("Chechen"), 879 "ch" => T_("Chamorro"), 880 "co" => T_("Corsican"), 881 "cr" => T_("Cree"), 882 "cs" => T_("Czech"), 883 "cu" => T_("Church Slavic"), 884 "cv" => T_("Chuvash"), 885 "cy" => T_("Welsh"), 886 "da" => T_("Danish"), 887 "de" => T_("German"), 888 "dv" => T_("Divehi"), 889 "dz" => T_("Dzongkha"), 890 "ee" => T_("Ewe"), 891 "el" => T_("Greek"), 892 "en" => T_("English"), 893 "eo" => T_("Esperanto"), 894 "es" => T_("Spanish"), 895 "et" => T_("Estonian"), 896 "eu" => T_("Basque"), 897 "fa" => T_("Persian"), 898 "ff" => T_("Fulah"), 899 "fi" => T_("Finnish"), 900 "fj" => T_("Fijian"), 901 "fo" => T_("Faroese"), 902 "fr" => T_("French"), 903 "fy" => T_("Western Frisian"), 904 "ga" => T_("Irish"), 905 "gd" => T_("Scottish Gaelic"), 906 "gl" => T_("Galician"), 907 "gn" => T_("Guarani"), 908 "gu" => T_("Gujarati"), 909 "gv" => T_("Manx"), 910 "ha" => T_("Hausa"), 911 "he" => T_("Hebrew"), 912 "hi" => T_("Hindi"), 913 "ho" => T_("Hiri Motu"), 914 "hr" => T_("Croatian"), 915 "ht" => T_("Haitian"), 916 "hu" => T_("Hungarian"), 917 "hy" => T_("Armenian"), 918 "hz" => T_("Herero"), 919 "ia" => T_("Interlingua (International Auxiliary Language Association)"), 920 "id" => T_("Indonesian"), 921 "ie" => T_("Interlingue"), 922 "ig" => T_("Igbo"), 923 "ii" => T_("Sichuan Yi"), 924 "ik" => T_("Inupiaq"), 925 "io" => T_("Ido"), 926 "is" => T_("Icelandic"), 927 "it" => T_("Italian"), 928 "iu" => T_("Inuktitut"), 929 "ja" => T_("Japanese"), 930 "jv" => T_("Javanese"), 931 "ka" => T_("Georgian"), 932 "kg" => T_("Kongo"), 933 "ki" => T_("Kikuyu"), 934 "kj" => T_("Kwanyama"), 935 "kk" => T_("Kazakh"), 936 "kl" => T_("Kalaallisut"), 937 "km" => T_("Khmer"), 938 "kn" => T_("Kannada"), 939 "ko" => T_("Korean"), 940 "kr" => T_("Kanuri"), 941 "ks" => T_("Kashmiri"), 942 "ku" => T_("Kurdish"), 943 "kv" => T_("Komi"), 944 "kw" => T_("Cornish"), 945 "ky" => T_("Kirghiz"), 946 "la" => T_("Latin"), 947 "lb" => T_("Luxembourgish"), 948 "lg" => T_("Ganda"), 949 "li" => T_("Limburgish"), 950 "ln" => T_("Lingala"), 951 "lo" => T_("Lao"), 952 "lt" => T_("Lithuanian"), 953 "lu" => T_("Luba-Katanga"), 954 "lv" => T_("Latvian"), 955 "mg" => T_("Malagasy"), 956 "mh" => T_("Marshallese"), 957 "mi" => T_("Maori"), 958 "mk" => T_("Macedonian"), 959 "ml" => T_("Malayalam"), 960 "mn" => T_("Mongolian"), 961 "mr" => T_("Marathi"), 962 "ms" => T_("Malay"), 963 "mt" => T_("Maltese"), 964 "my" => T_("Burmese"), 965 "na" => T_("Nauru"), 966 "nb" => T_("Norwegian Bokmal"), 967 "nd" => T_("North Ndebele"), 968 "ne" => T_("Nepali"), 969 "ng" => T_("Ndonga"), 970 "nl" => T_("Dutch"), 971 "nn" => T_("Norwegian Nynorsk"), 972 "no" => T_("Norwegian"), 973 "nr" => T_("South Ndebele"), 974 "nv" => T_("Navajo"), 975 "ny" => T_("Chichewa"), 976 "oc" => T_("Occitan"), 977 "oj" => T_("Ojibwa"), 978 "om" => T_("Oromo"), 979 "or" => T_("Oriya"), 980 "os" => T_("Ossetian"), 981 "pa" => T_("Panjabi"), 982 "pi" => T_("Pali"), 983 "pl" => T_("Polish"), 984 "ps" => T_("Pashto"), 985 "pt" => T_("Portuguese"), 986 "qu" => T_("Quechua"), 987 "rm" => T_("Raeto-Romance"), 988 "rn" => T_("Kirundi"), 989 "ro" => T_("Romanian"), 990 "ru" => T_("Russian"), 991 "rw" => T_("Kinyarwanda"), 992 "sa" => T_("Sanskrit"), 993 "sc" => T_("Sardinian"), 994 "sd" => T_("Sindhi"), 995 "se" => T_("Northern Sami"), 996 "sg" => T_("Sango"), 997 "si" => T_("Sinhala"), 998 "sk" => T_("Slovak"), 999 "sl" => T_("Slovenian"), 1000 "sm" => T_("Samoan"), 1001 "sn" => T_("Shona"), 1002 "so" => T_("Somali"), 1003 "sq" => T_("Albanian"), 1004 "sr" => T_("Serbian"), 1005 "ss" => T_("Swati"), 1006 "st" => T_("Southern Sotho"), 1007 "su" => T_("Sundanese"), 1008 "sv" => T_("Swedish"), 1009 "sw" => T_("Swahili"), 1010 "ta" => T_("Tamil"), 1011 "te" => T_("Telugu"), 1012 "tg" => T_("Tajik"), 1013 "th" => T_("Thai"), 1014 "ti" => T_("Tigrinya"), 1015 "tk" => T_("Turkmen"), 1016 "tl" => T_("Tagalog"), 1017 "tn" => T_("Tswana"), 1018 "to" => T_("Tonga"), 1019 "tr" => T_("Turkish"), 1020 "ts" => T_("Tsonga"), 1021 "tt" => T_("Tatar"), 1022 "tw" => T_("Twi"), 1023 "ty" => T_("Tahitian"), 1024 "ug" => T_("Uighur"), 1025 "uk" => T_("Ukrainian"), 1026 "ur" => T_("Urdu"), 1027 "uz" => T_("Uzbek"), 1028 "ve" => T_("Venda"), 1029 "vi" => T_("Vietnamese"), 1030 "vo" => T_("Volapuk"), 1031 "wa" => T_("Walloon"), 1032 "wo" => T_("Wolof"), 1033 "xh" => T_("Xhosa"), 1034 "yi" => T_("Yiddish"), 1035 "yo" => T_("Yoruba"), 1036 "za" => T_("Zhuang"), 1037 "zh" => T_("Chinese"), 1038 "zu" => T_("Zulu") 1039 ); 1040 1041 return $languageCodes[$code]; 1042 } 1043 1044 /** 1045 * Get subtitle file from language code. 1046 * @param string $lang_code 1047 * @return string 1048 */ 1049 public function get_subtitle_file($lang_code) 1050 { 1051 $subtitle = ''; 1052 if ($lang_code == '__' || $this->get_language_name($lang_code)) { 1053 $pinfo = pathinfo($this->file); 1054 $subtitle = $pinfo['dirname'] . DIRECTORY_SEPARATOR . $pinfo['filename']; 1055 if ($lang_code != '__') { 1056 $subtitle .= '.' . $lang_code; 1057 } 1058 $subtitle .= '.srt'; 1059 } 1060 1061 return $subtitle; 1062 } 1063 1064 /** 1065 * Remove the video from disk. 1066 */ 1067 public function remove() 1068 { 1069 if (file_exists($this->file)) { 1070 $deleted = unlink($this->file); 1071 } else { 1072 $deleted = true; 1073 } 1074 if ($deleted === true) { 1075 // keep details about deletions 1076 $params = array($this->id); 1077 $sql = "REPLACE INTO `deleted_video` (`id`, `addition_time`, `delete_time`, `title`, `file`, `catalog`, `total_count`, `total_skip`) SELECT `id`, `addition_time`, UNIX_TIMESTAMP(), `title`, `file`, `catalog`, `total_count`, `total_skip` FROM `video` WHERE `id` = ?;"; 1078 Dba::write($sql, $params); 1079 1080 $sql = "DELETE FROM `video` WHERE `id` = ?"; 1081 $deleted = Dba::write($sql, $params); 1082 if ($deleted) { 1083 Art::garbage_collection('video', $this->id); 1084 Userflag::garbage_collection('video', $this->id); 1085 Rating::garbage_collection('video', $this->id); 1086 $this->getShoutRepository()->collectGarbage('video', $this->getId()); 1087 $this->getUseractivityRepository()->collectGarbage('video', $this->getId()); 1088 } 1089 } else { 1090 debug_event(self::class, 'Cannot delete ' . $this->file . 'file. Please check permissions.', 1); 1091 } 1092 1093 return $deleted; 1094 } 1095 1096 /** 1097 * update_played 1098 * sets the played flag 1099 * @param boolean $new_played 1100 * @param integer $song_id 1101 */ 1102 public static function update_played($new_played, $song_id) 1103 { 1104 self::_update_item('played', ($new_played ? 1 : 0), $song_id, '25'); 1105 } // update_played 1106 1107 /** 1108 * _update_item 1109 * This is a private function that should only be called from within the video class. 1110 * It takes a field, value video id and level. first and foremost it checks the level 1111 * against Core::get_global('user') to make sure they are allowed to update this record 1112 * it then updates it and sets $this->{$field} to the new value 1113 * @param string $field 1114 * @param integer $value 1115 * @param integer $song_id 1116 * @param integer $level 1117 * @return boolean 1118 */ 1119 private static function _update_item($field, $value, $song_id, $level) 1120 { 1121 /* Check them Rights! */ 1122 if (!Access::check('interface', $level)) { 1123 return false; 1124 } 1125 1126 /* Can't update to blank */ 1127 if (!strlen(trim((string) $value))) { 1128 return false; 1129 } 1130 1131 $sql = "UPDATE `video` SET `$field` = ? WHERE `id` = ?"; 1132 Dba::write($sql, array($value, $song_id)); 1133 1134 return true; 1135 } // _update_item 1136 1137 /** 1138 * get_deleted 1139 * get items from the deleted_videos table 1140 * @return int[] 1141 */ 1142 public static function get_deleted() 1143 { 1144 $deleted = array(); 1145 $sql = "SELECT * FROM `deleted_video`"; 1146 $db_results = Dba::read($sql); 1147 while ($row = Dba::fetch_assoc($db_results)) { 1148 $deleted[] = $row; 1149 } 1150 1151 return $deleted; 1152 } // get_deleted 1153 1154 /** 1155 * compare_video_information 1156 * this compares the new ID3 tags of a file against 1157 * the ones in the database to see if they have changed 1158 * it returns false if nothing has changes, or the true 1159 * if they have. Static because it doesn't need this 1160 * @param Video $video 1161 * @param Video $new_video 1162 * @return array 1163 */ 1164 public static function compare_video_information(Video $video, Video $new_video) 1165 { 1166 // Remove some stuff we don't care about 1167 unset($video->catalog, $video->played, $video->enabled, $video->addition_time, $video->update_time, $video->type); 1168 $string_array = array('title', 'tags'); 1169 $skip_array = array('id', 'tag_id', 'mime', 'object_cnt', 'disabledMetadataFields'); 1170 1171 return Song::compare_media_information($video, $new_video, $string_array, $skip_array); 1172 } // compare_video_information 1173 1174 /** 1175 * @deprecated 1176 */ 1177 private function getShoutRepository(): ShoutRepositoryInterface 1178 { 1179 global $dic; 1180 1181 return $dic->get(ShoutRepositoryInterface::class); 1182 } 1183 1184 /** 1185 * @deprecated 1186 */ 1187 private function getUseractivityRepository(): UserActivityRepositoryInterface 1188 { 1189 global $dic; 1190 1191 return $dic->get(UserActivityRepositoryInterface::class); 1192 } 1193} 1194