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