src/Module/Util/VaInfo.php
<?php
declare(strict_types=0);
/**
* vim:set softtabstop=4 shiftwidth=4 expandtab:
*
* LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later)
* Copyright Ampache.org, 2001-2023
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Ampache\Module\Util;
use Ampache\Repository\Model\Plugin;
use Ampache\Config\ConfigContainerInterface;
use Ampache\Config\ConfigurationKeyEnum;
use Ampache\Repository\Model\Catalog;
use Ampache\Module\System\Core;
use Ampache\Repository\Model\User;
use Ampache\Repository\UserRepositoryInterface;
use Ampache\Module\System\LegacyLogger;
use Psr\Log\LoggerInterface;
use Exception;
use getID3;
use getid3_writetags;
/**
* This class handles the retrieval of media tags
*/
final class VaInfo implements VaInfoInterface
{
private const DEFAULT_INFO = array(
'albumartist' => null,
'album' => null,
'artist' => null,
'artists' => null,
'art' => null,
'audio_codec' => null,
'barcode' => null,
'bitrate' => null,
'catalog_number' => null,
'channels' => null,
'comment' => null,
'composer' => null,
'description' => null,
'disk' => null,
'disksubtitle' => null,
'display_x' => null,
'display_y' => null,
'encoding' => null,
'file' => null,
'frame_rate' => null,
'genre' => null,
'isrc' => null,
'language' => null,
'lyrics' => null,
'mb_albumartistid' => null,
'mb_albumartistid_array' => null,
'mb_albumid_group' => null,
'mb_albumid' => null,
'mb_artistid' => null,
'mb_artistid_array' => null,
'mb_trackid' => null,
'mime' => null,
'mode' => null,
'original_name' => null,
'original_year' => null,
'publisher' => null,
'r128_album_gain' => null,
'r128_track_gain' => null,
'rate' => null,
'rating' => null,
'release_date' => null,
'release_status' => null,
'release_type' => null,
'replaygain_album_gain' => null,
'replaygain_album_peak' => null,
'replaygain_track_gain' => null,
'replaygain_track_peak' => null,
'resolution_x' => null,
'resolution_y' => null,
'size' => null,
'version' => null,
'summary' => null,
'time' => null,
'title' => null,
'totaldisks' => null,
'totaltracks' => null,
'track' => null,
'tvshow_art' => null,
'tvshow_episode' => null,
'tvshow' => null,
'tvshow_season_art' => null,
'tvshow_season' => null,
'tvshow_summary' => null,
'tvshow_year' => null,
'video_bitrate' => null,
'video_codec' => null,
'year' => null
);
private const MBID_REGEX = '/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}/';
public $encoding = '';
public $encodingId3v1 = '';
public $encodingId3v2 = '';
public $filename = '';
public $type = '';
public $tags = array();
public $gatherTypes = array();
public $islocal;
protected $_raw = array();
protected $_getID3 = null;
protected $_forcedSize = 0;
protected $_file_encoding = '';
protected $_file_pattern = '';
protected $_dir_pattern = '';
private $_broken = false;
private $_pathinfo;
private UserRepositoryInterface $userRepository;
private ConfigContainerInterface $configContainer;
private LoggerInterface $logger;
/**
* Constructor
*
* This function just sets up the class, it doesn't pull the information.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @param UserRepositoryInterface $userRepository
* @param ConfigContainerInterface $configContainer
* @param LoggerInterface $logger
* @param string $file
* @param array $gatherTypes
* @param string $encoding
* @param string $encodingId3v1
* //TODO: where did this go? param string $encodingId3v2
* @param string $dirPattern
* @param string $filePattern
* @param bool $islocal
*/
public function __construct(
UserRepositoryInterface $userRepository,
ConfigContainerInterface $configContainer,
LoggerInterface $logger,
$file,
$gatherTypes = array(),
$encoding = null,
$encodingId3v1 = null,
$dirPattern = '',
$filePattern = '',
$islocal = true
) {
$this->islocal = $islocal;
$this->filename = $file;
$this->gatherTypes = $gatherTypes;
$this->encoding = $encoding ?? $configContainer->get(ConfigurationKeyEnum::SITE_CHARSET) ?? 'UTF-8';
/* These are needed for the filename mojo */
$this->_file_pattern = $filePattern;
$this->_dir_pattern = $dirPattern;
// FIXME: This looks ugly and probably wrong
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
$this->_pathinfo = str_replace('%3A', ':', urlencode($this->filename));
$this->_pathinfo = pathinfo(str_replace('%5C', '\\', $this->_pathinfo));
} else {
$this->_pathinfo = pathinfo(str_replace('%2F', '/', urlencode($this->filename)));
}
$this->_pathinfo['extension'] = strtolower($this->_pathinfo['extension']);
// convert all tag sources always to lowercase or results doesn't contains plugin results
$enabled_sources = array_map('strtolower', $this->get_metadata_order());
if (in_array('getid3', $enabled_sources) && $this->islocal) {
// Initialize getID3 engine
$this->_getID3 = new getID3();
$this->_getID3->option_md5_data = false;
$this->_getID3->option_md5_data_source = false;
$this->_getID3->option_tags_html = false;
$this->_getID3->option_extra_info = true;
$this->_getID3->option_tag_lyrics3 = true;
$this->_getID3->option_tags_process = true;
$this->_getID3->option_tag_apetag = true;
// get id3tag encoding (try to work around off-spec id3v1 tags)
try {
$this->_raw = $this->_getID3->analyze(Core::conv_lc_file($file));
} catch (Exception $error) {
$logger->error(
'getID3 Broken file detected: $file: ' . $error->getMessage(),
[LegacyLogger::CONTEXT_TYPE => __CLASS__]
);
$this->_broken = true;
return false;
}
//$logger->error('RAW TAGS: ' . print_r($this->_raw, true), [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
if ($configContainer->get(ConfigurationKeyEnum::MB_DETECT_ORDER)) {
$mb_order = $configContainer->get(ConfigurationKeyEnum::MB_DETECT_ORDER);
} elseif (function_exists('mb_detect_order')) {
$mb_order = (mb_detect_order()) ? implode(", ", mb_detect_order()) : 'auto';
} else {
$mb_order = 'auto';
}
$test_tags = array('artist', 'album', 'genre', 'title');
if ($encodingId3v1 !== null) {
$this->encodingId3v1 = $encodingId3v1;
} else {
$tags = array();
foreach ($test_tags as $tag) {
if (array_key_exists('id3v1', $this->_raw) && array_key_exists($tag, $this->_raw['id3v1']) && $value = $this->_raw['id3v1'][$tag]) {
$tags[$tag] = $value;
}
}
$this->encodingId3v1 = self::_detect_encoding($tags, $mb_order);
$this->_getID3->encoding_id3v1 = $this->encodingId3v1;
}
if ($configContainer->get(ConfigurationKeyEnum::GETID3_DETECT_ID3V2_ENCODING)) {
// The user has told us to be moronic, so let's do that thing
$tags = array();
foreach ($test_tags as $tag) {
if (array_key_exists('id3v2', $this->_raw) && array_key_exists($tag, $this->_raw['id3v2']) && $value = $this->_raw['id3v2']['comments'][$tag]) {
$tags[$tag] = $value;
}
}
$this->encodingId3v2 = self::_detect_encoding($tags, $mb_order);
$this->_getID3->encoding = $this->encodingId3v2;
}
}
$this->userRepository = $userRepository;
$this->configContainer = $configContainer;
$this->logger = $logger;
return true;
}
/**
* forceSize
*/
public function forceSize(int $size): void
{
$this->_forcedSize = $size;
}
/**
* _detect_encoding
*
* Takes an array of tags and attempts to automatically detect their
* encoding.
* @param $tags
* @param $mb_order
*/
private static function _detect_encoding($tags, $mb_order): string
{
if (!function_exists('mb_detect_encoding')) {
return 'ISO-8859-1';
}
$encodings = array();
if (is_array($tags)) {
foreach ($tags as $tag) {
if (is_array($tag)) {
$tag = implode(" ", $tag);
}
$enc = mb_detect_encoding($tag, $mb_order, true);
if ($enc !== false) {
if (!array_key_exists($enc, $encodings)) {
$encodings[$enc] = 0;
}
$encodings[$enc]++;
}
}
} else {
$enc = mb_detect_encoding($tags, $mb_order, true);
if ($enc !== false) {
$encodings[$enc] = 1;
}
}
//!!debug_event(self::class, 'encoding detection: ' . json_encode($encodings), 5);
$high = 0;
$encoding = 'ISO-8859-1';
foreach ($encodings as $key => $value) {
if ($value > $high) {
$encoding = $key;
$high = $value;
}
}
if ($encoding != 'ASCII') {
return (string)$encoding;
} else {
return 'ISO-8859-1';
}
}
/**
* get_info
*
* This function runs the various steps to gathering the metadata. Filling $this->tags
*/
public function gather_tags(): void
{
// If this is broken, don't waste time figuring it out a second time, just return their rotting carcass of a media file.
if ($this->_broken) {
$this->tags = $this->set_broken();
return;
}
$enabled_sources = (array)$this->get_metadata_order();
if (in_array('getid3', $enabled_sources) && $this->islocal) {
try {
$this->_raw = $this->_getID3->analyze(Core::conv_lc_file($this->filename));
} catch (Exception $error) {
$this->logger->error(
'getID3 Unable to catalog file: ' . $error->getMessage(),
[LegacyLogger::CONTEXT_TYPE => __CLASS__]
);
}
}
/* Figure out what type of file we are dealing with */
$this->type = $this->_get_type() ?? '';
if (in_array('filename', $enabled_sources)) {
$this->tags['filename'] = $this->_parse_filename($this->filename);
}
if (in_array('getid3', $enabled_sources) && $this->islocal) {
$this->tags['getid3'] = $this->_get_tags();
}
$this->_get_plugin_tags();
}
/**
* check_time
* check a cached file is close to the expected time
* @param int $time
*/
public function check_time($time): bool
{
$this->gather_tags();
foreach ($this->tags as $results) {
if (isset($results['time'])) {
return ($time >= $results['time'] - 2);
}
}
return false;
}
/**
* write_id3
* This function runs the various steps to gathering the metadata
* @param $tagData
* @throws Exception
*/
public function write_id3($tagData): void
{
$TaggingFormat = 'UTF-8';
$tagWriter = new getid3_writetags();
$extension = pathinfo($this->filename, PATHINFO_EXTENSION);
$extensionMap = [
'mp3' => 'id3v2.3',
'flac' => 'metaflac',
'oga' => 'vorbiscomment',
'ogg' => 'vorbiscomment'
];
if (!array_key_exists(strtolower($extension), $extensionMap)) {
$this->logger->debug(
sprintf('Writing Tags: Files with %s extensions are currently ignored.', $extension),
[LegacyLogger::CONTEXT_TYPE => __CLASS__]
);
return;
}
$format = $extensionMap[$extension];
$tagWriter->filename = $this->filename;
$tagWriter->tagformats = array($format);
$tagWriter->overwrite_tags = true;
$tagWriter->remove_other_tags = false;
$tagWriter->tag_encoding = $TaggingFormat;
$tagWriter->tag_data = $tagData;
/*
* Currently getid3 doesn't remove pictures on *nix, only vorbiscomments.
* This hasn't been tested on Windows and there is evidence that
* metaflac.exe behaves differently.
*/
if ($extension !== 'mp3') {
if (php_uname('s') == 'Linux') {
/* First check for installation of metaflac and
* vorbiscomment system tools.
*/
exec('which metaflac', $output, $retval);
exec('which vorbiscomment', $output, $retval1);
if ($retval !== 0 || $retval1 !== 0) {
$this->logger->debug(
'Metaflac and vorbiscomments must be installed to write tags to flac and oga files',
[LegacyLogger::CONTEXT_TYPE => __CLASS__]
);
return;
}
}
if (GETID3_OS_ISWINDOWS) {
$command = 'metaflac.exe --remove --block-type=PICTURE ' . escapeshellarg($this->filename);
} else {
$command = 'metaflac --remove --block-type=PICTURE ' . escapeshellarg($this->filename);
}
$commandError = `$command`;
}
if ($tagWriter->WriteTags()) {
foreach ($tagWriter->warnings as $message) {
$this->logger->debug(
'Warning Writing Tags: ' . $message,
[LegacyLogger::CONTEXT_TYPE => __CLASS__]
);
}
}
if (!empty($tagWriter->errors)) {
foreach ($tagWriter->errors as $message) {
$this->logger->error(
'Error Writing Tags: ' . $message,
[LegacyLogger::CONTEXT_TYPE => __CLASS__]
);
}
}
}
/**
* prepare_metadata_for_writing
* Prepares vorbiscomments/id3v2 metadata for writing tag to file
* @param array $frames
* @return array
*/
public function prepare_metadata_for_writing($frames): array
{
$ndata = array();
foreach ($frames as $key => $text) {
switch ($key) {
case 'text':
foreach ($text as $tkey => $data) {
$ndata['text'][] = array('data' => $data, 'description' => $tkey, 'encodingid' => 0);
}
break;
default:
$ndata[$key][] = $text[0];
break;
}
}
return $ndata;
}
/**
* read_id3
*
* This function runs the various steps to gathering the metadata
* @return array
*/
public function read_id3(): array
{
// Get the Raw file information
try {
$this->_raw = $this->_getID3->analyze($this->filename);
return $this->_raw;
} catch (Exception $error) {
$this->logger->error(
'Unable to read file:' . $error->getMessage(),
[LegacyLogger::CONTEXT_TYPE => __CLASS__]
);
}
return array();
}
/**
* get_tag_type
*
* This takes the result set and the tag_order defined in your config
* file and tries to figure out which tag type(s) it should use. If your
* tag_order doesn't match anything then it throws up its hands and uses
* everything in random order.
* @param array $results
* @param string $configKey
* @return array
*/
public static function get_tag_type($results, $configKey = 'metadata_order'): array
{
$tagorderMap = [
'metadata_order' => static::getConfigContainer()->get(ConfigurationKeyEnum::METADATA_ORDER),
'metadata_order_video' => static::getConfigContainer()->get(ConfigurationKeyEnum::METADATA_ORDER_VIDEO),
'getid3_tag_order' => static::getConfigContainer()->get(ConfigurationKeyEnum::GETID3_TAG_ORDER)
];
$order = array();
foreach ($tagorderMap[$configKey] ?? [] as $source) {
//debug_event(__CLASS__, "source: " . $source, true, 5);
$order[] = strtolower($source);
}
// Iterate through the defined key order adding them to an ordered array.
$returned_keys = array();
foreach ($order as $value) {
if (array_key_exists($value, $results)) {
$returned_keys[] = $value;
}
}
// return a default list of items (if you get here this is probably a bad file)
if (empty($returned_keys)) {
debug_event(__CLASS__, "get_tag_type: Couln't find tags, this is probably a bad file", 5);
$returned_keys = array(
'getid3',
'filename',
'general'
);
}
// Unless they explicitly set it, add bitrate/mode/mime/etc.
if (is_array($returned_keys)) {
if (!in_array('general', $returned_keys)) {
$returned_keys[] = 'general';
}
}
//debug_event(__CLASS__, "get_tag_type: " . $configKey . print_r($returned_keys, true), 5);
return $returned_keys;
}
/**
* clean_tag_info
*
* This function takes the array from vainfo along with the
* key we've decided on and the filename and returns it in a
* sanitized format that Ampache can actually use
* @param array $results
* @param array $keys
* @param string $filename
* @return array
*/
public static function clean_tag_info($results, $keys, $filename = null): array
{
$info = self::DEFAULT_INFO;
$info['file'] = $filename;
foreach ($keys as $key) {
// Ampache has a set list of columns to look for but we need to check through and fill it based on the collected tags
$tags = $results[$key] ?? array();
$info['file'] = (!$info['file'] && array_key_exists('file', $tags)) ? $tags['file'] : $info['file'];
$info['bitrate'] = (!$info['bitrate'] && array_key_exists('bitrate', $tags)) ? (int) $tags['bitrate'] : $info['bitrate'];
$info['rate'] = (!$info['rate'] && array_key_exists('rate', $tags)) ? (int) $tags['rate'] : $info['rate'];
$info['mode'] = (!$info['mode'] && array_key_exists('mode', $tags)) ? $tags['mode'] : $info['mode'];
$info['mime'] = (!$info['mime'] && array_key_exists('mime', $tags)) ? $tags['mime'] : $info['mime'];
$info['encoding'] = (!$info['encoding'] && array_key_exists('encoding', $tags)) ? $tags['encoding'] : $info['encoding'];
$info['rating'] = (!$info['rating'] && array_key_exists('rating', $tags)) ? $tags['rating'] : $info['rating'];
$info['time'] = (!$info['time'] && array_key_exists('time', $tags)) ? (int) $tags['time'] : $info['time'];
$info['channels'] = (!$info['channels'] && array_key_exists('channels', $tags)) ? $tags['channels'] : $info['channels'];
// This because video title are almost always bad...
$info['original_name'] = (!$info['original_name'] && array_key_exists('original_name', $tags)) ? stripslashes(trim((string)$tags['original_name'])) : $info['original_name'];
$info['title'] = (!$info['title'] && array_key_exists('title', $tags)) ? stripslashes(trim((string)$tags['title'])) : $info['title'];
// Not even sure if these can be negative, but better safe than llama.
$info['year'] = (!$info['year'] && array_key_exists('year', $tags)) ? Catalog::normalize_year((int) $tags['year']) : $info['year'];
$info['disk'] = (!$info['disk'] && array_key_exists('disk', $tags)) ? abs((int) $tags['disk']) : $info['disk'];
$info['totaldisks'] = (!$info['totaldisks'] && array_key_exists('totaldisks', $tags)) ? (int) $tags['totaldisks'] : $info['totaldisks'];
$info['disksubtitle'] = (!$info['disksubtitle'] && array_key_exists('disksubtitle', $tags)) ? trim((string)$tags['disksubtitle']) : $info['disksubtitle'];
$info['artist'] = (!$info['artist'] && array_key_exists('artist', $tags)) ? trim((string)$tags['artist']) : $info['artist'];
$info['albumartist'] = (!$info['albumartist'] && array_key_exists('albumartist', $tags)) ? trim((string)$tags['albumartist']) : $info['albumartist'];
$info['album'] = (!$info['album'] && array_key_exists('album', $tags)) ? trim((string)$tags['album']) : $info['album'];
$info['composer'] = (!$info['composer'] && array_key_exists('composer', $tags)) ? trim((string)$tags['composer']) : $info['composer'];
$info['publisher'] = (!$info['publisher'] && array_key_exists('publisher', $tags)) ? trim((string)$tags['publisher']) : $info['publisher'];
// genre is an array treat it as one
$info['genre'] = (!$info['genre'] && array_key_exists('genre', $tags) && !empty($tags['genre']))
? $tags['genre']
: $info['genre'];
$info['mb_trackid'] = (!$info['mb_trackid'] && array_key_exists('mb_trackid', $tags)) ? trim((string)$tags['mb_trackid']) : $info['mb_trackid'];
$info['isrc'] = (!$info['isrc'] && array_key_exists('isrc', $tags)) ? trim((string)$tags['isrc']) : $info['isrc'];
$info['mb_albumid'] = (!$info['mb_albumid'] && array_key_exists('mb_albumid', $tags)) ? trim((string)$tags['mb_albumid']) : $info['mb_albumid'];
$info['mb_albumid_group'] = (!$info['mb_albumid_group'] && array_key_exists('mb_albumid_group', $tags)) ? trim((string)$tags['mb_albumid_group']) : $info['mb_albumid_group'];
$info['mb_artistid'] = (!$info['mb_artistid'] && array_key_exists('mb_artistid', $tags)) ? trim((string)$tags['mb_artistid']) : $info['mb_artistid'];
$info['mb_albumartistid'] = (!$info['mb_albumartistid'] && array_key_exists('mb_albumartistid', $tags)) ? trim((string)$tags['mb_albumartistid']) : $info['mb_albumartistid'];
// groups of artists can be ID'd using their mbid easily
$info['mb_artistid_array'] = (!$info['mb_artistid_array'] && array_key_exists('mb_artistid_array', $tags) && !empty($tags['mb_artistid_array']))
? $tags['mb_artistid_array']
: $info['mb_artistid_array'];
$info['mb_albumartistid_array'] = (!$info['mb_albumartistid_array'] && array_key_exists('mb_albumartistid_array', $tags) && !empty($tags['mb_albumartistid_array']))
? $tags['mb_albumartistid_array']
: $info['mb_albumartistid_array'];
$info['release_type'] = (!$info['release_type'] && array_key_exists('release_type', $tags)) ? trim((string)$tags['release_type']) : $info['release_type'];
$info['release_status'] = (!$info['release_status'] && array_key_exists('release_status', $tags)) ? trim((string)$tags['release_status']) : $info['release_status'];
// artists is an array treat it as one
if (!empty($tags['artists']) && !is_array($tags['artists'])) {
$tags['artists'] = array($tags['artists']);
}
$info['artists'] = (!$info['artists'] && array_key_exists('artists', $tags) && !empty($tags['artists']))
? $tags['artists']
: $info['artists'];
$info['original_year'] = (!$info['original_year'] && array_key_exists('original_year', $tags)) ? trim((string)$tags['original_year']) : $info['original_year'];
$info['barcode'] = (!$info['barcode'] && array_key_exists('barcode', $tags)) ? trim((string)$tags['barcode']) : $info['barcode'];
$info['catalog_number'] = (!$info['catalog_number'] && array_key_exists('catalog_number', $tags)) ? trim((string)$tags['catalog_number']) : $info['catalog_number'];
$info['version'] = (!$info['version'] && array_key_exists('version', $tags)) ? trim((string)$tags['version']) : $info['version'];
$info['language'] = (!$info['language'] && array_key_exists('language', $tags)) ? trim((string)$tags['language']) : $info['language'];
$info['comment'] = (!$info['comment'] && array_key_exists('comment', $tags)) ? trim((string)$tags['comment']) : $info['comment'];
$info['lyrics'] = (!$info['lyrics'] && array_key_exists('lyrics', $tags)) ? strip_tags(nl2br((string) $tags['lyrics']), "<br>") : $info['lyrics'];
// extended checks to make sure "0" makes it through, which would otherwise eval to false
$info['replaygain_track_gain'] = (!$info['replaygain_track_gain'] && array_key_exists('replaygain_track_gain', $tags) && !is_null($tags['replaygain_track_gain'])) ? (float) $tags['replaygain_track_gain'] : $info['replaygain_track_gain'];
$info['replaygain_track_peak'] = (!$info['replaygain_track_peak'] && array_key_exists('replaygain_track_peak', $tags) && !is_null($tags['replaygain_track_peak'])) ? (float) $tags['replaygain_track_peak'] : $info['replaygain_track_peak'];
$info['replaygain_album_gain'] = (!$info['replaygain_album_gain'] && array_key_exists('replaygain_album_gain', $tags) && !is_null($tags['replaygain_album_gain'])) ? (float) $tags['replaygain_album_gain'] : $info['replaygain_album_gain'];
$info['replaygain_album_peak'] = (!$info['replaygain_album_peak'] && array_key_exists('replaygain_album_peak', $tags) && !is_null($tags['replaygain_album_peak'])) ? (float) $tags['replaygain_album_peak'] : $info['replaygain_album_peak'];
$info['r128_track_gain'] = (!$info['r128_track_gain'] && array_key_exists('r128_track_gain', $tags) && !is_null($tags['r128_track_gain'])) ? (int) $tags['r128_track_gain'] : $info['r128_track_gain'];
$info['r128_album_gain'] = (!$info['r128_album_gain'] && array_key_exists('r128_album_gain', $tags) && !is_null($tags['r128_album_gain'])) ? (int) $tags['r128_album_gain'] : $info['r128_album_gain'];
$info['track'] = (!$info['track'] && array_key_exists('track', $tags)) ? (int)$tags['track'] : $info['track'];
$info['totaltracks'] = (!$info['totaltracks'] && array_key_exists('totaltracks', $tags)) ? (int)$tags['totaltracks'] : $info['totaltracks'];
$info['resolution_x'] = (!$info['resolution_x'] && array_key_exists('resolution_x', $tags)) ? (int)$tags['resolution_x'] : $info['resolution_x'];
$info['resolution_y'] = (!$info['resolution_y'] && array_key_exists('resolution_y', $tags)) ? (int)$tags['resolution_y'] : $info['resolution_y'];
$info['display_x'] = (!$info['display_x'] && array_key_exists('display_x', $tags)) ? (int)$tags['display_x'] : $info['display_x'];
$info['display_y'] = (!$info['display_y'] && array_key_exists('display_y', $tags)) ? (int)$tags['display_y'] : $info['display_y'];
$info['frame_rate'] = (!$info['frame_rate'] && array_key_exists('frame_rate', $tags)) ? (float)$tags['frame_rate'] : $info['frame_rate'];
$info['video_bitrate'] = (!$info['video_bitrate'] && array_key_exists('video_bitrate', $tags)) ? Catalog::check_int((int) $tags['video_bitrate'], 4294967294, 0) : $info['video_bitrate'];
$info['audio_codec'] = (!$info['audio_codec'] && array_key_exists('audio_codec', $tags)) ? trim((string)$tags['audio_codec']) : $info['audio_codec'];
$info['video_codec'] = (!$info['video_codec'] && array_key_exists('video_codec', $tags)) ? trim((string)$tags['video_codec']) : $info['video_codec'];
$info['description'] = (!$info['description'] && array_key_exists('description', $tags)) ? trim((string)$tags['description']) : $info['description'];
$info['tvshow'] = (!$info['tvshow'] && array_key_exists('tvshow', $tags)) ? trim((string)$tags['tvshow']) : $info['tvshow'];
$info['tvshow_year'] = (!$info['tvshow_year'] && array_key_exists('tvshow_year', $tags)) ? trim((string)$tags['tvshow_year']) : $info['tvshow_year'];
$info['tvshow_season'] = (!$info['tvshow_season'] && array_key_exists('tvshow_season', $tags)) ? trim((string)$tags['tvshow_season']) : $info['tvshow_season'];
$info['tvshow_episode'] = (!$info['tvshow_episode'] && array_key_exists('tvshow_episode', $tags)) ? trim((string)$tags['tvshow_episode']) : $info['tvshow_episode'];
$info['release_date'] = (!$info['release_date'] && array_key_exists('release_date', $tags)) ? trim((string)$tags['release_date']) : $info['release_date'];
$info['summary'] = (!$info['summary'] && array_key_exists('summary', $tags)) ? trim((string)$tags['summary']) : $info['summary'];
$info['tvshow_summary'] = (!$info['tvshow_summary'] && array_key_exists('tvshow_summary', $tags)) ? trim((string)$tags['tvshow_summary']) : $info['tvshow_summary'];
$info['tvshow_art'] = (!$info['tvshow_art'] && array_key_exists('tvshow_art', $tags)) ? trim((string)$tags['tvshow_art']) : $info['tvshow_art'];
$info['tvshow_season_art'] = (!$info['tvshow_season_art'] && array_key_exists('tvshow_season_art', $tags)) ? trim((string)$tags['tvshow_season_art']) : $info['tvshow_season_art'];
$info['art'] = (!$info['art'] && array_key_exists('art', $tags)) ? trim((string)$tags['art']) : $info['art'];
if (static::getConfigContainer()->get(ConfigurationKeyEnum::ENABLE_CUSTOM_METADATA) && is_array($tags)) {
// Add rest of the tags without typecast to the array
foreach ($tags as $tag => $value) {
if (!array_key_exists($tag, $info) && !is_array($value)) {
$info[$tag] = trim((string)$value);
}
}
}
}
// Determine the correct file size, do not get fooled by the size which may be returned by id3v2!
$size = $results['general']['size'] ?? Core::get_filesize(Core::conv_lc_file($filename));
$info['size'] = $info['size'] ?? $size;
return $info;
}
/**
* get_mbid_array
* @param string $mbid
* @return array
*/
public static function get_mbid_array($mbid): array
{
if (preg_match(self::MBID_REGEX, $mbid, $matches)) {
return $matches;
}
return array($mbid);
}
/**
* parse_mbid
* Get the first valid mbid. (if it's valid)
* @param string|array $mbid
*/
public static function parse_mbid($mbid): ?string
{
if (empty($mbid)) {
return null;
}
if (is_array($mbid)) {
$mbid = implode(";", $mbid);
}
if (preg_match(self::MBID_REGEX, $mbid, $matches)) {
return (string)$matches[0];
}
return null;
}
/**
* parse_mbid_array
* Return only valid mbid data
* @param string|array $mbid
* @return array
*/
public static function parse_mbid_array($mbid): array
{
if (empty($mbid)) {
return array();
}
if (is_array($mbid)) {
$mbid = implode(";", $mbid);
}
if (preg_match_all(self::MBID_REGEX, $mbid, $matches)) {
return $matches[0];
}
return array();
}
/**
* is_mbid
* @param null|string $mbid
*/
public static function is_mbid($mbid): bool
{
if ($mbid === null) {
return false;
}
if (preg_match(self::MBID_REGEX, $mbid)) {
return true;
}
return false;
}
/**
* _get_type
*
* This function takes the raw information and figures out what type of file we are dealing with.
*/
private function _get_type(): ?string
{
// There are a few places that the file type can come from, in the end we trust the encoding type.
if (array_key_exists('video', $this->_raw) && array_key_exists('dataformat', $this->_raw['video'])) {
return $this->_clean_type($this->_raw['video']['dataformat']);
}
if (array_key_exists('audio', $this->_raw)) {
if (array_key_exists('streams', $this->_raw['audio']) && array_key_exists('0', $this->_raw['audio']['streams']) && array_key_exists('dataformat', $this->_raw['audio']['streams']['0'])) {
return $this->_clean_type($this->_raw['audio']['streams']['0']['dataformat']);
}
if (array_key_exists('dataformat', $this->_raw['audio'])) {
return $this->_clean_type($this->_raw['audio']['dataformat']);
}
}
if (array_key_exists('fileformat', $this->_raw)) {
return $this->_clean_type($this->_raw['fileformat']);
}
return null;
}
/**
* _get_tags
*
* This processes the raw getID3 output and bakes it.
* @return array
* @throws Exception
*/
private function _get_tags(): array
{
$results = array();
//$this->logger->debug('RAW TAGS ' . print_r($this->_raw, true), [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
// The tags can come in many different shapes and colors depending on the encoding.
if (array_key_exists('tags', $this->_raw) && is_array($this->_raw['tags'])) {
foreach ($this->_raw['tags'] as $key => $tag_array) {
switch ($key) {
case 'vorbiscomment':
//$this->logger->debug('Cleaning vorbis', [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
$parsed = $this->_cleanup_vorbiscomment($tag_array);
break;
case 'id3v2':
//$this->logger->debug('Cleaning id3v2', [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
$parsed = $this->_cleanup_id3v2($tag_array);
break;
case 'quicktime':
//$this->logger->debug('Cleaning quicktime', [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
$parsed = $this->_cleanup_quicktime($tag_array);
break;
case 'riff':
//$this->logger->debug('Cleaning riff', [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
$parsed = $this->_cleanup_riff($tag_array);
break;
case 'mpg':
case 'mpeg':
$key = 'mpeg';
//$this->logger->debug('Cleaning MPEG', [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
$parsed = $this->_cleanup_generic($tag_array);
break;
case 'asf':
case 'wmv':
case 'wma':
$key = 'asf';
//$this->logger->debug('Cleaning WMV/WMA/ASF', [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
$parsed = $this->_cleanup_asf($tag_array);
break;
case 'lyrics3':
//$this->logger->debug('Cleaning lyrics3', [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
$parsed = $this->_cleanup_lyrics($tag_array);
break;
case 'id3v1':
//$this->logger->debug('Cleaning id3v1', [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
$parsed = $this->_cleanup_id3v1($tag_array);
break;
case 'ape':
case 'avi':
case 'flv':
case 'matroska':
default:
//$this->logger->debug('Cleaning tag type ' . $key . ' for file ' . $this->filename, [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
$parsed = $this->_cleanup_generic($tag_array);
break;
}
$results[$key] = $parsed;
}
}
$results['general'] = $this->_parse_general($this->_raw);
$cleaned = self::clean_tag_info($results, self::get_tag_type($results, 'getid3_tag_order'), $this->filename);
$cleaned['raw'] = $results;
return $cleaned;
}
/**
* get_metadata_order_key
*/
private function get_metadata_order_key(): string
{
if (!in_array('music', $this->gatherTypes)) {
return 'metadata_order_video';
}
return 'metadata_order';
}
/**
* get_metadata_order
* @return array
*/
private function get_metadata_order(): array
{
$tagorderMap = [
'metadata_order' => static::getConfigContainer()->get(ConfigurationKeyEnum::METADATA_ORDER),
'metadata_order_video' => static::getConfigContainer()->get(ConfigurationKeyEnum::METADATA_ORDER_VIDEO),
'getid3_tag_order' => static::getConfigContainer()->get(ConfigurationKeyEnum::GETID3_TAG_ORDER)
];
// convert to lower case to be sure it matches plugin names in Ampache\Plugin\PluginEnum
return array_map('strtolower', $tagorderMap[$this->get_metadata_order_key()] ?? []);
}
/**
* _get_plugin_tags
*
* Get additional metadata from plugins
*/
private function _get_plugin_tags(): void
{
$tag_order = $this->get_metadata_order();
$plugin_names = Plugin::get_plugins('get_metadata');
/** @var User $user */
$user = (!empty(Core::get_global('user')))
? Core::get_global('user')
: new User(-1);
// don't loop over getid3 and filename
$tag_order = array_diff($tag_order, array('getid3', 'filename'));
foreach ($tag_order as $tag_source) {
if (in_array($tag_source, $plugin_names)) {
$plugin = new Plugin($tag_source);
$installed_version = Plugin::get_plugin_version($plugin->_plugin->name);
if ($installed_version > 0) {
if ($plugin->_plugin !== null && $plugin->load($user)) {
$this->tags[$tag_source] = $plugin->_plugin->get_metadata(
$this->gatherTypes,
self::clean_tag_info(
$this->tags,
self::get_tag_type($this->tags, $this->get_metadata_order_key()),
$this->filename
)
);
}
}
} elseif (!in_array($tag_source, array('filename', 'getid3'))) {
$this->logger->debug(
'_get_plugin_tags: ' . $tag_source . ' is not a valid metadata_order plugin',
[LegacyLogger::CONTEXT_TYPE => __CLASS__]
);
}
}
}
/**
* _parse_general
*
* Gather and return the general information about a file
* (vbr/cbr, sample rate, channels, etc.)
* @param $tags
* @return array
*/
private function _parse_general($tags): array
{
//$this->logger->debug('_parse_general: ' . print_r($tags, true), [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
$parsed = array();
if ((in_array('movie', $this->gatherTypes)) || (in_array('tvshow', $this->gatherTypes))) {
$parsed['title'] = $this->formatVideoName(urldecode($this->_pathinfo['filename']));
} else {
$parsed['title'] = urldecode($this->_pathinfo['filename']);
}
if (array_key_exists('audio', $tags)) {
$parsed['mode'] = $tags['audio']['bitrate_mode'] ?? 'vbr';
if ($parsed['mode'] == 'con') {
$parsed['mode'] = 'cbr';
}
$parsed['bitrate'] = $tags['audio']['bitrate'] ?? null;
$parsed['channels'] = (!empty($tags['audio']['channels'])) ? (int)$tags['audio']['channels'] : null;
$parsed['rate'] = (!empty($tags['audio']['sample_rate'])) ? (int)$tags['audio']['sample_rate'] : null;
$parsed['audio_codec'] = $tags['audio']['dataformat'] ?? null;
}
if (array_key_exists('video', $tags)) {
$parsed['video_codec'] = $tags['video']['dataformat'] ?? null;
$parsed['resolution_x'] = $tags['video']['resolution_x'] ?? null;
$parsed['resolution_y'] = $tags['video']['resolution_y'] ?? null;
$parsed['display_x'] = $tags['video']['display_x'] ?? null;
$parsed['display_y'] = $tags['video']['display_y'] ?? null;
$parsed['frame_rate'] = $tags['video']['frame_rate'] ?? null;
$parsed['video_bitrate'] = $tags['video']['bitrate'] ?? null;
}
$parsed['size'] = $this->_forcedSize ?? $tags['filesize'] ?? null;
$parsed['encoding'] = $tags['encoding'] ?? null;
$parsed['mime'] = $tags['mime_type'] ?? null;
if (($parsed['size'] && array_key_exists('avdataoffset', $tags) && array_key_exists('bitrate', $tags))) {
$parsed['time'] = (($parsed['size'] - $tags['avdataoffset']) * 8) / $tags['bitrate'];
} elseif (array_key_exists('playtime_seconds', $tags) && $tags['playtime_seconds'] > 0) {
$parsed['time'] = $tags['playtime_seconds'];
} else {
$this->logger->critical("UNABLE TO READ 'playtime_seconds'. This is probably a bad file " . $parsed['title'], [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
$parsed['time'] = 0;
}
if (isset($tags['ape'])) {
if (isset($tags['ape']['items'])) {
foreach ($tags['ape']['items'] as $key => $tag) {
switch (strtolower($key)) {
case 'replaygain_track_gain':
case 'replaygain_track_peak':
case 'replaygain_album_gain':
case 'replaygain_album_peak':
$parsed[$key] = (!is_null($tag['data'][0])) ? (float) $tag['data'][0] : null;
break;
case 'r128_track_gain':
case 'r128_album_gain':
$parsed[$key] = (!is_null($tag['data'][0])) ? (int) $tag['data'][0] : null;
break;
}
}
}
}
return $parsed;
}
/**
* @param string $string
*/
private function trimAscii($string): string
{
return preg_replace('/[\x00-\x1F\x80-\xFF]/', '', trim((string)$string));
}
/**
* _clean_type
* This standardizes the type that we are given into a recognized type.
* @param $type
*/
private function _clean_type($type): string
{
switch ($type) {
case 'mp2':
case 'mp3':
case 'mpeg3':
return 'mp3';
case 'ogg':
case 'opus':
case 'vorbis':
return 'ogg';
case 'asf':
case 'wma':
case 'wmv':
return 'asf';
case 'mp4':
case 'quicktime':
return 'quicktime';
case 'mpc':
return 'ape';
case 'avi':
case 'flac':
case 'flv':
case 'mpg':
case 'mpeg':
case 'wav':
return $type;
default:
/* Log the fact that we couldn't figure it out */
$this->logger->warning(
'Unable to determine file type from ' . $type . ' on file ' . $this->filename,
[LegacyLogger::CONTEXT_TYPE => __CLASS__]
);
return $type;
}
}
/**
* _cleanup_generic
*
* This does generic cleanup.
* @param $tags
* @return array
* @throws Exception
*/
private function _cleanup_generic($tags): array
{
$parsed = array();
foreach ($tags as $tagname => $data) {
//$this->logger->debug('generic tag: ' . strtolower($tagname) . ' value: ' . print_r($data ?? '', true), [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
switch (strtolower($tagname)) {
case 'artists':
$parsed['artists'] = $this->parseArtists($data);
break;
case 'genre':
// Pass the array through
$parsed['genre'] = $this->parseGenres($data);
break;
case 'discsubtitle':
$parsed['disksubtitle'] = $data[0];
break;
case 'partofset':
$elements = explode('/', $data[0]);
$parsed['disk'] = $elements[0];
$parsed['totaldisks'] = $elements[1] ?? null;
break;
case 'track_number':
case 'track':
$parsed['track'] = $data[0];
break;
case 'musicbrainz_artistid':
$parsed['mb_artistid'] = self::parse_mbid($data[0]);
$parsed['mb_artistid_array'] = (count($data) > 1) ? self::parse_mbid_array($data) : self::parse_mbid_array($data[0]);
break;
case 'musicbrainz_albumid':
$parsed['mb_albumid'] = self::parse_mbid($data[0]);
break;
case 'musicbrainz_albumartistid':
$parsed['mb_albumartistid'] = self::parse_mbid($data[0]);
$parsed['mb_albumartistid_array'] = (count($data) > 1) ? self::parse_mbid_array($data) : self::parse_mbid_array($data[0]);
break;
case 'musicbrainz_releasegroupid':
$parsed['mb_albumid_group'] = self::parse_mbid($data[0]);
break;
case 'musicbrainz_trackid':
$parsed['mb_trackid'] = self::parse_mbid($data[0]);
break;
case 'musicbrainz_albumtype':
$parsed['release_type'] = (is_array($data) && count($data) > 1)
? implode(", ", $data)
: implode(', ', array_diff(preg_split("/[^a-zA-Z0-9*]/", $data[0]), array('')));
break;
case 'musicbrainz_albumstatus':
$parsed['release_status'] = (is_array($data) && count($data) > 1)
? implode(", ", $data)
: implode(', ', array_diff(preg_split("/[^a-zA-Z0-9*]/", $data[0]), array('')));
break;
case 'originalyear':
case 'originalreleaseyear':
$parsed['original_year'] = $data[0];
break;
case 'label':
case 'publisher':
$parsed['publisher'] = $data[0];
break;
case 'version':
$parsed['version'] = $data[0];
break;
case 'music_cd_identifier':
// REMOVE_ME get rid of this annoying tag causing only problems with metadata
break;
default:
$parsed[$tagname] = $data[0];
break;
}
}
return $parsed;
}
/**
* _cleanup_lyrics
*
* This is supposed to handle lyrics3. FIXME: does it?
* @param $tags
* @return array
*/
private function _cleanup_lyrics($tags): array
{
$parsed = array();
foreach ($tags as $tag => $data) {
if ($tag == 'unsyncedlyrics' || $tag == 'unsynced lyrics' || $tag == 'unsynchronised lyric') {
$tag = 'lyrics';
}
$parsed[strtolower($tag)] = $data[0];
}
return $parsed;
}
/**
* _cleanup_vorbiscomment
*
* Standardizes tag names from vorbis.
* @param $tags
* @return array
* @throws Exception
*/
private function _cleanup_vorbiscomment($tags): array
{
$parsed = array();
foreach ($tags as $tag => $data) {
//$this->logger->debug('Vorbis tag: ' . $tag . ' value: ' . print_r($data ?? '', true), [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
switch (strtolower($tag)) {
case 'artists':
$parsed['artists'] = $this->parseArtists($data);
break;
case 'genre':
$parsed['genre'] = $this->parseGenres($data);
break;
case 'tracknumber':
case 'track_number':
$parsed['track'] = $data[0];
break;
case 'tracktotal':
$parsed['totaltracks'] = $data[0];
break;
case 'discnumber':
$parsed['disk'] = $data[0];
break;
case 'discsubtitle':
$parsed['disksubtitle'] = $data[0];
break;
case 'totaldiscs':
case 'disctotal':
$parsed['totaldisks'] = $data[0];
break;
case 'albumartist':
case 'album artist':
$parsed['albumartist'] = $data[0];
break;
case 'isrc':
$parsed['isrc'] = $data[0];
break;
case 'date':
$parsed['year'] = $data[0];
break;
case 'musicbrainz_artistid':
$parsed['mb_artistid'] = self::parse_mbid($data[0]);
$parsed['mb_artistid_array'] = (count($data) > 1) ? self::parse_mbid_array($data) : self::parse_mbid_array($data[0]);
break;
case 'musicbrainz_albumid':
$parsed['mb_albumid'] = self::parse_mbid($data[0]);
break;
case 'musicbrainz_albumartistid':
$parsed['mb_albumartistid'] = self::parse_mbid($data[0]);
$parsed['mb_albumartistid_array'] = (count($data) > 1) ? self::parse_mbid_array($data) : self::parse_mbid_array($data[0]);
break;
case 'musicbrainz_releasegroupid':
$parsed['mb_albumid_group'] = self::parse_mbid($data[0]);
break;
case 'musicbrainz_trackid':
$parsed['mb_trackid'] = self::parse_mbid($data[0]);
break;
case 'releasetype':
case 'musicbrainz_albumtype':
$parsed['release_type'] = (is_array($data) && count($data) > 1)
? implode(", ", $data)
: implode(', ', array_diff(preg_split("/[^a-zA-Z0-9*]/", $data[0]), array('')));
break;
case 'releasestatus':
case 'musicbrainz_albumstatus':
$parsed['release_status'] = (is_array($data) && count($data) > 1)
? implode(", ", $data)
: implode(', ', array_diff(preg_split("/[^a-zA-Z0-9*]/", $data[0]), array('')));
break;
case 'unsyncedlyrics':
case 'unsynced lyrics':
case 'lyrics':
$parsed['lyrics'] = $data[0];
break;
case 'originaldate':
$parsed['originaldate'] = strtotime(str_replace(" ", "", $data[0]));
if (strlen($data['0']) > 4) {
$data[0] = date('Y', $parsed['originaldate']);
}
$parsed['original_year'] = $parsed['original_year'] ?? $data[0];
break;
case 'originalyear':
$parsed['original_year'] = $data[0];
break;
case 'barcode':
$parsed['barcode'] = $data[0];
break;
case 'catalognumber':
$parsed['catalog_number'] = $data[0];
break;
case 'label':
case 'organization':
$parsed['publisher'] = $data[0];
break;
case 'version':
$parsed['version'] = $data[0];
break;
case 'rating':
$rating_user = -1;
if ($this->configContainer->get(ConfigurationKeyEnum::RATING_FILE_TAG_USER)) {
$rating_user = (int) $this->configContainer->get(ConfigurationKeyEnum::RATING_FILE_TAG_USER);
}
$parsed['rating'][$rating_user] = floor($data[0] * 5 / 100);
break;
default:
// look for set ratings using email address
foreach (preg_grep("/^rating:.*@.*/", array_keys($parsed)) as $user_rating) {
/**
* @todo check functionality; looks like an array is used for a string
*/
$rating_user = $this->userRepository->findByEmail(
array_map('trim', preg_split("/^rating:/", $user_rating))[1] ?? ''
);
if ($rating_user !== null) {
$parsed['rating'][$rating_user->id] = floor($data[0] * 5 / 100);
}
}
$parsed[strtolower($tag)] = $data[0];
break;
}
}
// Replaygain stored by getID3
if (isset($this->_raw['replay_gain'])) {
if (isset($this->_raw['replay_gain']['track']['adjustment'])) {
$parsed['replaygain_track_gain'] = (float) $this->_raw['replay_gain']['track']['adjustment'];
}
if (isset($this->_raw['replay_gain']['track']['peak'])) {
$parsed['replaygain_track_peak'] = (float) $this->_raw['replay_gain']['track']['peak'];
}
if (isset($this->_raw['replay_gain']['album']['adjustment'])) {
$parsed['replaygain_album_gain'] = (float) $this->_raw['replay_gain']['album']['adjustment'];
}
if (isset($this->_raw['replay_gain']['album']['peak'])) {
$parsed['replaygain_album_peak'] = (float) $this->_raw['replay_gain']['album']['peak'];
}
}
return $parsed;
}
/**
* _cleanup_id3v1
*
* Doesn't do much.
* @param $tags
* @return array
*/
private function _cleanup_id3v1($tags): array
{
$parsed = array();
foreach ($tags as $tag => $data) {
// This is our baseline for naming so everything's already right,
// we just need to shuffle off the array.
$parsed[strtolower($tag)] = $data[0];
}
return $parsed;
}
/**
* _cleanup_id3v2
*
* Whee, v2!
* @param $tags
* @return array
* @throws Exception
*/
private function _cleanup_id3v2($tags): array
{
$parsed = array();
foreach ($tags as $tag => $data) {
//$this->logger->debug('id3v2 tag: ' . strtolower($tag) . ' value: ' . print_r($data ?? '', true), [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
switch (strtolower($tag)) {
case 'artists':
$parsed['artists'] = $this->parseArtists($data);
break;
case 'genre':
$parsed['genre'] = $this->parseGenres($data);
break;
case 'discsubtitle':
$parsed['disksubtitle'] = $data[0];
break;
case 'part_of_a_set':
$elements = explode('/', $data[0]);
$parsed['disk'] = $elements[0];
$parsed['totaldisks'] = $elements[1] ?? null;
break;
case 'track_number':
$parsed['track'] = $data[0];
break;
case 'totaltracks':
$parsed['totaltracks'] = $data[0];
break;
case 'comment':
// First array key can be xFF\xFE in case of UTF-8, better to get it this way
$parsed['comment'] = reset($data);
break;
case 'band':
$parsed['albumartist'] = $data[0];
break;
case 'composer':
$BOM = chr(0xff) . chr(0xfe);
$parsed['composer'] = (strlen($data[0]) == 2 && $data[0] == $BOM)
? str_replace($BOM, '', $data[0])
: reset($data);
break;
case 'isrc':
$parsed['isrc'] = $data[0];
break;
case 'comments':
$parsed['comment'] = $data[0];
break;
case 'unsynchronised_lyric':
$parsed['lyrics'] = $data[0];
break;
case 'original_release_time':
case 'originaldate':
$parsed['originaldate'] = strtotime(str_replace(" ", "", $data[0]));
if (strlen($data['0']) > 4) {
$data[0] = date('Y', $parsed['originaldate']);
}
$parsed['original_year'] = (array_key_exists('original_year', $parsed)) ? ($parsed['original_year']) : $data[0];
break;
case 'originalyear':
$parsed['original_year'] = $data[0];
break;
case 'barcode':
$parsed['barcode'] = $data[0];
break;
case 'catalognumber':
$parsed['catalog_number'] = $data[0];
break;
case 'label':
case 'publisher':
$parsed['publisher'] = $data[0];
break;
case 'version':
$parsed['version'] = $data[0];
break;
case 'music_cd_identifier':
// REMOVE_ME get rid of this annoying tag causing only problems with metadata
break;
default:
if (array_key_exists(0, $data)) {
$parsed[strtolower($tag)] = $data[0];
}
break;
}
}
// getID3 doesn't do all the parsing we need, so grab the raw data
$id3v2 = $this->_raw['id3v2'];
if (!empty($id3v2['UFID'])) {
// Find the MBID for the track
foreach ($id3v2['UFID'] as $ufid) {
if ($ufid['ownerid'] == 'http://musicbrainz.org') {
$parsed['mb_trackid'] = $ufid['data'];
}
}
}
if (!empty($id3v2['TXXX'])) {
// Find the MBIDs for the album and artist
// Use trimAscii to remove noise (see #225 and #438 issues). Is this a GetID3 bug?
// not a bug those strings are UTF-16 encoded
// getID3 has copies of text properly converted to utf-8 encoding in comments/text
$enable_custom_metadata = $this->configContainer->get(ConfigurationKeyEnum::ENABLE_CUSTOM_METADATA);
foreach ($id3v2['TXXX'] as $txxx) {
//$this->logger->debug('id3v2 TXXX: ' . strtolower($this->trimAscii($txxx['description'] ?? '')) . ' value: ' . print_r($id3v2['comments']['text'][$txxx['description']] ?? '', true), [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
switch (strtolower($this->trimAscii($txxx['description']))) {
case 'artists':
$parsed['artists'] = $this->parseArtists($id3v2['comments']['text'][$txxx['description']]);
break;
case 'album artist':
$parsed['albumartist'] = $id3v2['comments']['text'][$txxx['description']];
break;
case 'musicbrainz artist id':
$parsed['mb_artistid'] = self::parse_mbid($id3v2['comments']['text'][$txxx['description']]);
$parsed['mb_artistid_array'] = self::parse_mbid_array($id3v2['comments']['text'][$txxx['description']]);
break;
case 'musicbrainz album artist id':
$parsed['mb_albumartistid'] = self::parse_mbid($id3v2['comments']['text'][$txxx['description']]);
$parsed['mb_albumartistid_array'] = self::parse_mbid_array($id3v2['comments']['text'][$txxx['description']]);
break;
case 'musicbrainz album id':
$parsed['mb_albumid'] = self::parse_mbid($id3v2['comments']['text'][$txxx['description']]);
break;
case 'musicbrainz release group id':
$parsed['mb_albumid_group'] = self::parse_mbid($id3v2['comments']['text'][$txxx['description']]);
break;
case 'musicbrainz album type':
$parsed['release_type'] = (is_array($id3v2['comments']['text'][$txxx['description']])) ? implode(", ", $id3v2['comments']['text'][$txxx['description']]) : implode(', ', array_diff(preg_split("/[^a-zA-Z0-9*]/", $id3v2['comments']['text'][$txxx['description']]), array('')));
break;
case 'musicbrainz album status':
$parsed['release_status'] = $id3v2['comments']['text'][$txxx['description']];
break;
case 'replaygain_track_gain':
// FIXME: shouldn't here $txxx['data'] be replaced by $id3v2['comments']['text'][$txxx['description']]
// all replaygain values aren't always correctly retrieved
$parsed['replaygain_track_gain'] = (float) $txxx['data'];
break;
case 'replaygain_track_peak':
$parsed['replaygain_track_peak'] = (float) $txxx['data'];
break;
case 'replaygain_album_gain':
$parsed['replaygain_album_gain'] = (float) $txxx['data'];
break;
case 'replaygain_album_peak':
$parsed['replaygain_album_peak'] = (float) $txxx['data'];
break;
case 'r128_track_gain':
$parsed['r128_track_gain'] = (int) $txxx['data'];
break;
case 'r128_album_gain':
$parsed['r128_album_gain'] = (int) $txxx['data'];
break;
case 'original_year':
$parsed['original_year'] = $id3v2['comments']['text'][$txxx['description']];
break;
case 'discsubtitle':
case 'setsubtitle':
$parsed['disksubtitle'] = $id3v2['comments']['text'][$txxx['description']];
break;
case 'barcode':
$parsed['barcode'] = $id3v2['comments']['text'][$txxx['description']];
break;
case 'catalognumber':
$parsed['catalog_number'] = $id3v2['comments']['text'][$txxx['description']];
break;
case 'label':
$parsed['publisher'] = $id3v2['comments']['text'][$txxx['description']];
break;
case 'version':
$parsed['version'] = $id3v2['comments']['text'][$txxx['description']];
break;
default:
$frame = strtolower($this->trimAscii($txxx['description']));
if ($enable_custom_metadata && !isset(self::DEFAULT_INFO[$frame]) && !in_array($frame, $parsed)) {
$parsed[strtolower($this->trimAscii($txxx['description']))] = $id3v2['comments']['text'][$txxx['description']];
}
break;
}
}
}
// Find the rating
if (array_key_exists('POPM', $id3v2) && is_array($id3v2['POPM'])) {
foreach ($id3v2['POPM'] as $popm) {
if (
array_key_exists('email', $popm) &&
$user = $this->userRepository->findByEmail($popm['email'])
) {
if ($user instanceof User) {
// Ratings are out of 255; scale it
$parsed['rating'][$user->id] = $popm['rating'] / 255 * 5;
}
continue;
}
// Rating made by an unknown user, adding it to super user (id=-1)
$rating_user = -1;
if ($this->configContainer->get(ConfigurationKeyEnum::RATING_FILE_TAG_USER)) {
$rating_user = (int) $this->configContainer->get(ConfigurationKeyEnum::RATING_FILE_TAG_USER);
}
$parsed['rating'][$rating_user] = $popm['rating'] / 255 * 5;
}
}
return $parsed;
}
/**
* _cleanup_riff
* @param $tags
* @return array
*/
private function _cleanup_riff($tags): array
{
$parsed = array();
foreach ($tags as $tag => $data) {
switch ($tag) {
case 'product':
$parsed['album'] = $data[0];
break;
default:
$parsed[strtolower($tag)] = $data[0];
break;
}
}
return $parsed;
}
/**
* _cleanup_quicktime
* @param $tags
* @return array
* @throws Exception
*/
private function _cleanup_quicktime($tags): array
{
$parsed = array();
foreach ($tags as $tag => $data) {
//$this->logger->debug('Quicktime tag: ' . strtolower($tag) . ' value: ' . print_r($data ?? '', true), [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
switch (strtolower($tag)) {
case 'artists':
$parsed['artists'] = $this->parseArtists($data);
break;
case 'genre':
$parsed['genre'] = $this->parseGenres($data);
break;
case 'creation_date':
$parsed['creation_date'] = strtotime(str_replace(" ", "", $data[0]));
if (strlen($data['0']) > 4) {
$data[0] = date('Y', $parsed['creation_date']);
}
$parsed['year'] = $data[0];
break;
case 'musicbrainz track id':
$parsed['mb_trackid'] = self::parse_mbid($data[0]);
break;
case 'musicbrainz album id':
$parsed['mb_albumid'] = self::parse_mbid($data[0]);
break;
case 'musicbrainz album artist id':
$parsed['mb_albumartistid'] = self::parse_mbid($data[0]);
$parsed['mb_albumartistid_array'] = self::parse_mbid_array($data);
break;
case 'musicbrainz release group id':
$parsed['mb_albumid_group'] = self::parse_mbid($data[0]);
break;
case 'musicbrainz artist id':
$parsed['mb_artistid'] = self::parse_mbid($data[0]);
$parsed['mb_artistid_array'] = self::parse_mbid_array($data);
break;
case 'musicbrainz album type':
$parsed['release_type'] = (is_array($data) && count($data) > 1)
? implode(", ", $data)
: implode(', ', array_diff(preg_split("/[^a-zA-Z0-9*]/", $data[0]), array('')));
break;
case 'musicbrainz album status':
$parsed['release_status'] = (is_array($data) && count($data) > 1)
? implode(", ", $data)
: implode(', ', array_diff(preg_split("/[^a-zA-Z0-9*]/", $data[0]), array('')));
break;
case 'track_number':
//$parsed['track'] = $data[0];
$elements = explode('/', $data[0]);
$parsed['track'] = $elements[0];
$parsed['totaltracks'] = $elements[1] ?? null;
break;
case 'disc_number':
$elements = explode('/', $data[0]);
$parsed['disk'] = $elements[0];
$parsed['totaldisks'] = $elements[1] ?? null;
break;
case 'discsubtitle':
$parsed['disksubtitle'] = $data[0];
break;
case 'isrc':
$parsed['isrc'] = $data[0];
break;
case 'album_artist':
$parsed['albumartist'] = $data[0];
break;
case 'originaldate':
$parsed['originaldate'] = strtotime(str_replace(" ", "", $data[0]));
if (strlen($data['0']) > 4) {
$data[0] = date('Y', $parsed['originaldate']);
}
$parsed['original_year'] = $parsed['original_year'] ?? $data[0];
break;
case 'originalyear':
$parsed['original_year'] = $data[0];
break;
case 'barcode':
$parsed['barcode'] = $data[0];
break;
case 'catalognumber':
$parsed['catalog_number'] = $data[0];
break;
case 'label':
$parsed['publisher'] = $data[0];
break;
case 'version':
$parsed['version'] = $data[0];
break;
case 'tv_episode':
$parsed['tvshow_episode'] = $data[0];
break;
case 'tv_season':
$parsed['tvshow_season'] = $data[0];
break;
case 'tv_show_name':
$parsed['tvshow'] = $data[0];
break;
default:
$parsed[strtolower($tag)] = $data[0];
break;
}
}
return $parsed;
}
/**
* _cleanup_asf
*
* This does WMA cleanup.
* @param $tags
* @return array
* @throws Exception
*/
private function _cleanup_asf($tags): array
{
$parsed = array();
foreach ($tags as $tagname => $data) {
//$this->logger->debug('asf tag: ' . strtolower($tagname) . ' value: ' . print_r($data ?? '', true), [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
switch (strtolower($tagname)) {
case 'artists':
$parsed['artists'] = $this->parseArtists($data);
break;
case 'genre':
$parsed['genre'] = $this->parseGenres($data);
break;
case 'partofset':
$elements = explode('/', $data[0]);
$parsed['disk'] = $elements[0];
$parsed['totaldisks'] = $elements[1] ?? null;
break;
case 'track_number':
case 'track':
$parsed['track'] = $data[0];
break;
case 'musicbrainz_artistid':
$parsed['mb_artistid'] = self::parse_mbid($data[0]);
$parsed['mb_artistid_array'] = (count($data) > 1) ? self::parse_mbid_array($data) : self::parse_mbid_array($data[0]);
break;
case 'musicbrainz_albumid':
$parsed['mb_albumid'] = self::parse_mbid($data[0]);
break;
case 'musicbrainz_albumartistid':
$parsed['mb_albumartistid'] = self::parse_mbid($data[0]);
$parsed['mb_albumartistid_array'] = (count($data) > 1) ? self::parse_mbid_array($data) : self::parse_mbid_array($data[0]);
break;
case 'musicbrainz_releasegroupid':
$parsed['mb_albumid_group'] = self::parse_mbid($data[0]);
break;
case 'musicbrainz_trackid':
$parsed['mb_trackid'] = self::parse_mbid($data[0]);
break;
case 'musicbrainz_albumtype':
$parsed['release_type'] = (is_array($data) && count($data) > 1)
? implode(", ", $data)
: implode(', ', array_diff(preg_split("/[^a-zA-Z0-9*]/", $data[0]), array('')));
break;
case 'musicbrainz_albumstatus':
$parsed['release_status'] = (is_array($data) && count($data) > 1)
? implode(", ", $data)
: implode(', ', array_diff(preg_split("/[^a-zA-Z0-9*]/", $data[0]), array('')));
break;
case 'releasecomment':
case 'version':
$parsed['version'] = $data[0];
break;
case 'originalreleaseyear':
$parsed['original_year'] = str_replace("\x00", '', $data[0]);
break;
case 'music_cd_identifier':
// REMOVE_ME get rid of this annoying tag causing only problems with metadata
break;
default:
$parsed[$tagname] = $data[0];
break;
}
}
// WMA isn't read very well so dig into the raw data
if (array_key_exists('asf', $this->_raw) && is_array($this->_raw['asf'])) {
$enable_custom_metadata = $this->configContainer->get(ConfigurationKeyEnum::ENABLE_CUSTOM_METADATA);
foreach ($this->_raw['asf']['extended_content_description_object']['content_descriptors'] as $wmaTag) {
$value = str_replace("\x00", '', $wmaTag['value']);
//$this->logger->debug('asf tag: ' . strtolower($wmaTag['name'] ?? '') . ' value: ' . print_r($value ?? '', true), [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
switch (strtolower($this->trimAscii($wmaTag['name']))) {
case 'wm/artists':
$parsed['artists'] = $this->parseArtists($value);
break;
case 'wm/albumartist':
$parsed['albumartist'] = $value;
break;
case 'wm/setsubtitle':
$parsed['disksubtitle'] = $value;
break;
case 'musicbrainz/artist id':
$parsed['mb_artistid'] = self::parse_mbid($value);
$parsed['mb_artistid_array'] = self::parse_mbid_array($value);
break;
case 'musicbrainz/album artist id':
$parsed['mb_albumartistid'] = self::parse_mbid($value);
$parsed['mb_albumartistid_array'] = self::parse_mbid_array($value);
break;
case 'musicbrainz/album id':
$parsed['mb_albumid'] = self::parse_mbid($value);
break;
case 'musicbrainz/release group id':
$parsed['mb_albumid_group'] = self::parse_mbid($value);
break;
case 'musicbrainz/album type':
$parsed['release_type'] = $value;
break;
case 'musicbrainz/album status':
$parsed['release_status'] = $value;
break;
case 'wm/originalreleaseyear':
$parsed['original_year'] = (int)$value;
break;
case 'wm/barcode':
$parsed['barcode'] = $value;
break;
case 'wm/catalogno':
$parsed['catalog_number'] = $value;
break;
case 'wm/publisher':
$parsed['publisher'] = $value;
break;
default:
$frame = strtolower($this->trimAscii($wmaTag['name']));
if ($enable_custom_metadata && !isset(self::DEFAULT_INFO[$frame]) && !in_array($frame, $parsed)) {
$parsed[strtolower($this->trimAscii($wmaTag['name']))] = $value;
}
break;
}
}
}
return $parsed;
}
/**
* _parse_filename
* This function uses the file and directory patterns to pull out extra tag
* information.
* parses TV show name variations:
* 1. title.[date].S#[#]E#[#].ext (Upper/lower case)
* 2. title.[date].#[#]X#[#].ext (both upper/lower case letters
* 3. title.[date].Season #[#] Episode #[#].ext
* 4. title.[date].###.ext (maximum of 9 seasons)
* parse directory path for name, season and episode numbers
* /TV shows/show name [(year)]/[season ]##/##.Episode.Title.ext
* parse movie names:
* title.[date].ext
* /movie title [(date)]/title.ext
* @param string $filepath
* @return array
*/
private function _parse_filename($filepath): array
{
$origin = $filepath;
$results = array();
$file = pathinfo($filepath, PATHINFO_FILENAME);
if (in_array('tvshow', $this->gatherTypes)) {
$season = array();
$episode = array();
$tvyear = array();
$temp = array();
preg_match("~(?<=\(\[\<\{)[1|2][0-9]{3}[^p]|[1|2][0-9]{3}[^p]~", $filepath, $tvyear);
$results['year'] = (!empty($tvyear)) ? (int) $tvyear[0] : null;
if (preg_match("~[Ss](\d+)[Ee](\d+)~", $file, $seasonEpisode)) {
$temp = preg_split("~(((\.|_|\s)[Ss]\d+(\.|_)*[Ee]\d+))~", $file, 2);
preg_match("~(?<=[Ss])\d+~", $file, $season);
preg_match("~(?<=[Ee])\d+~", $file, $episode);
} else {
if (preg_match("~[\_\-\.\s](\d{1,2})[xX](\d{1,2})~", $file, $seasonEpisode)) {
$temp = preg_split("~[\.\_\s\-\_]\d+[xX]\d{2}[\.\s\-\_]*|$~", $file);
preg_match("~\d+(?=[Xx])~", $file, $season);
preg_match("~(?<=[Xx])\d+~", $file, $episode);
} else {
if (preg_match("~[S|s]eason[\_\-\.\s](\d+)[\.\-\s\_]?\s?[e|E]pisode[\s\-\.\_]?(\d+)[\.\s\-\_]?~", $file, $seasonEpisode)) {
$temp = preg_split(
"~[\.\s\-\_][S|s]eason[\s\-\.\_](\d+)[\.\s\-\_]?\s?[e|E]pisode[\s\-\.\_](\d+)([\s\-\.\_])*~",
$file,
3
);
preg_match("~(?<=[Ss]eason[\.\s\-\_])\d+~", $file, $season);
preg_match("~(?<=[Ee]pisode[\.\s\-\_])\d+~", $file, $episode);
} else {
if (preg_match("~[\_\-\.\s](\d)(\d\d)[\_\-\.\s]*~", $file, $seasonEpisode)) {
$temp = preg_split("~[\.\s\-\_](\d)(\d\d)[\.\s\-\_]~", $file);
$season[0] = $seasonEpisode[1];
if (preg_match("~[\_\-\.\s](\d)(\d\d)[\_\-\.\s]~", $file, $seasonEpisode)) {
$temp = preg_split("~[\.\s\-\_](\d)(\d\d)[\.\s\-\_]~", $file);
$season[0] = $seasonEpisode[1];
$episode[0] = $seasonEpisode[2];
}
}
}
}
}
$results['tvshow_season'] = $season[0];
$results['tvshow_episode'] = $episode[0];
$results['tvshow'] = $this->formatVideoName($temp[0]);
$results['original_name'] = $this->formatVideoName($temp[1]);
// Try to identify the show information from parent folder
if (!$results['tvshow']) {
$folders = preg_split("~" . DIRECTORY_SEPARATOR . "~", $filepath, -1, PREG_SPLIT_NO_EMPTY);
if (array_key_exists('tvshow_season', $results) && array_key_exists('tvshow_episode', $results)) {
// We have season and episode, we assume parent folder is the tvshow name
$filetitle = end($folders);
$results['tvshow'] = $this->formatVideoName($filetitle);
} else {
// Or we assume each parent folder contains one missing information
if (preg_match('/[\/\\\\]([^\/\\\\]*)[\/\\\\]Season (\d{1,2})[\/\\\\]((E|Ep|Episode)\s?(\d{1,2})[\/\\\\])?/i', $filepath, $matches)) {
if ($matches != null) {
$results['tvshow'] = $this->formatVideoName($matches[1]);
$results['tvshow_season'] = $matches[2];
if (isset($matches[5])) {
$results['tvshow_episode'] = $matches[5];
} else {
// match pattern like 10.episode name.mp4
if (preg_match("~^(\d\d)[\_\-\.\s]?(.*)~", $file, $matches)) {
$results['tvshow_episode'] = $matches[1];
$results['original_name'] = $this->formatVideoName($matches[2]);
} else {
// Fallback to match any 3-digit Season/Episode that fails the standard pattern above.
preg_match("~(\d)(\d\d)[\_\-\.\s]?~", $file, $matches);
$results['tvshow_episode'] = $matches[2];
}
}
}
}
}
}
$results['title'] = $results['tvshow'];
}
if (in_array('movie', $this->gatherTypes)) {
$results['original_name'] = $results['title'] = $this->formatVideoName($file);
}
if (in_array('music', $this->gatherTypes) || in_array('clip', $this->gatherTypes)) {
$patres = VaInfo::parse_pattern($filepath, $this->_dir_pattern, $this->_file_pattern);
$results = array_merge($results, $patres);
if ($this->islocal) {
$results['size'] = Core::get_filesize(Core::conv_lc_file($origin));
}
}
return $results;
}
/**
* parse_pattern
* @param string $filepath
* @param string $dirPattern
* @param string $filePattern
* @return array
*/
public static function parse_pattern($filepath, $dirPattern, $filePattern): array
{
$logger = static::getLogger();
$results = array();
$slash_type_preg = DIRECTORY_SEPARATOR;
if ($slash_type_preg == '\\') {
$slash_type_preg .= DIRECTORY_SEPARATOR;
}
// Combine the patterns
$pattern = preg_quote($dirPattern) . $slash_type_preg . preg_quote($filePattern);
// Remove first left directories from filename to match pattern
$cntslash = substr_count($pattern, preg_quote(DIRECTORY_SEPARATOR)) + 1;
$filepart = explode(DIRECTORY_SEPARATOR, $filepath);
if (count($filepart) > $cntslash) {
$filepath = implode(DIRECTORY_SEPARATOR, array_slice($filepart, count($filepart) - $cntslash));
}
// Pull out the pattern codes into an array
preg_match_all('/\%\w/', $pattern, $elements);
// Mangle the pattern by turning the codes into regex captures
$pattern = preg_replace('/\%[d]/', '([0-9]?)', $pattern);
$pattern = preg_replace('/\%[TyY]/', '([0-9]+?)', $pattern);
$pattern = preg_replace('/\%\w/', '(.+?)', $pattern);
$pattern = str_replace('/', '\/', $pattern);
$pattern = str_replace(' ', '\s', $pattern);
$pattern = '/' . $pattern . '\..+$/';
// Pull out our actual matches
preg_match($pattern, $filepath, $matches);
//$logger->debug('Checking ' . $pattern . ' _ ' . print_r($matches, true) . ' on ' . $filepath, [LegacyLogger::CONTEXT_TYPE => __CLASS__]);
if ($matches != null) {
// The first element is the full match text
$matched = array_shift($matches);
$logger->debug(
$pattern . ' matched ' . $matched . ' on ' . $filepath,
[LegacyLogger::CONTEXT_TYPE => __CLASS__]
);
// Iterate over what we found
foreach ($matches as $key => $value) {
$new_key = self::translate_pattern_code($elements['0'][$key]);
if ($new_key !== false) {
$results[$new_key] = $value;
}
}
$results['title'] = $results['title'] ?? basename($filepath);
}
return $results;
}
/**
* removeCommonAbbreviations
* @param string $name
*/
private function removeCommonAbbreviations($name): string
{
$abbr = explode(",", $this->configContainer->get(ConfigurationKeyEnum::COMMON_ABBR));
$commonabbr = preg_replace("~\n~", '', $abbr);
$commonabbr[] = '[1|2][0-9]{3}'; //Remove release year
$abbr_count = count($commonabbr);
// scan for brackets, braces, etc and ignore case.
for ($count = 0; $count < $abbr_count; $count++) {
$commonabbr[$count] = "~\[*|\(*|\<*|\{*\b(?i)" . trim((string)$commonabbr[$count]) . "\b\]*|\)*|\>*|\}*~";
}
return preg_replace($commonabbr, '', $name);
}
/**
* formatVideoName
* @param string $name
*/
private function formatVideoName($name): string
{
return ucwords(trim(
(string)$this->removeCommonAbbreviations(str_replace(['.', '_', '-'], ' ', $name)),
"\s\t\n\r\0\x0B\.\_\-"
));
}
/**
* set_broken
*
* This fills all tag types with Unknown (Broken)
*
* @return array Return broken title, album, artist
*/
public function set_broken(): array
{
/* Pull In the config option */
$order = $this->configContainer->get(ConfigurationKeyEnum::TAG_ORDER);
if (!is_array($order)) {
$order = array($order);
}
$key = array_shift($order);
$broken = array();
$broken[$key] = array();
$broken[$key]['title'] = '**BROKEN** ' . $this->filename;
$broken[$key]['album'] = 'Unknown (Broken)';
$broken[$key]['artist'] = 'Unknown (Broken)';
return $broken;
}
/**
*
* @param array|string $data
* @return array
* @throws Exception
*/
private function parseGenres($data)
{
//debug_event(__CLASS__, "parseGenres: " . print_r($data, true), 5);
$result = null;
if (is_array($data)) {
$result = array();
foreach ($data as $row) {
if (!empty($row)) {
foreach (self::splitSlashedlist(str_replace("\x00", ';', str_replace('Folk, World, & Country', 'Folk World & Country', $row)), false) as $genre) {
$result[] = $genre;
}
}
}
}
if (is_string($data) && !empty($data)) {
$result = self::splitSlashedlist(str_replace("\x00", ';', str_replace('Folk, World, & Country', 'Folk World & Country', $data)), false);
}
return $result;
}
/**
*
* @param array|string $data
* @return array
*/
private function parseArtists($data): array
{
//debug_event(__CLASS__, "parseArtists: " . print_r($data, true), 5);
$result = null;
if (is_array($data)) {
$result = array();
foreach ($data as $row) {
if (!empty($row)) {
foreach (explode(';', str_replace("\x00", ';', $row)) as $artist) {
$result[] = trim($artist);
}
}
}
}
if (is_string($data) && !empty($data)) {
$result = explode(';', str_replace("\x00", ';', $data));
}
return $result;
}
/**
* splitSlashedlist
* Split items by configurable delimiter
* Return first item as string = default
* Return all items as array if doTrim = false passed as optional parameter
* @param string $data
* @param bool $doTrim
* @return string|array
* @throws Exception
*/
public function splitSlashedlist($data, $doTrim = true)
{
$delimiters = $this->configContainer->get(ConfigurationKeyEnum::ADDITIONAL_DELIMITERS);
if (!empty($data) && !empty($delimiters)) {
$pattern = '~[\s]?(' . $delimiters . ')[\s]?~';
$items = preg_split($pattern, $data);
$items = array_map('trim', $items);
if (empty($items)) {
throw new Exception('Pattern given in additional_genre_delimiters is not functional. Please ensure is it a valid regex (delimiter ~)');
}
$data = $items;
}
if (isset($data[0]) && $doTrim) {
return $data[0];
}
return $data;
}
/**
* translate_pattern_code
* This just contains a keyed array which it checks against to give you the
* 'tag' name that said pattern code corresponds to. It returns false if nothing
* is found.
* @return string|false
*/
private static function translate_pattern_code(string $code)
{
$code_array = array(
'%a' => 'artist',
'%A' => 'album',
'%b' => 'barcode',
'%c' => 'comment',
'%C' => 'catalog_number',
'%d' => 'disk',
'%g' => 'genre',
'%l' => 'label',
'%t' => 'title',
'%T' => 'track',
'%r' => 'release_type',
'%R' => 'release_status',
'%s' => 'subtitle',
'%y' => 'year',
'%Y' => 'original_year',
'%o' => 'zz_other'
);
if (isset($code_array[$code])) {
return $code_array[$code];
}
return false;
}
/**
* @deprecated inject by constructor
*/
private static function getConfigContainer(): ConfigContainerInterface
{
global $dic;
return $dic->get(ConfigContainerInterface::class);
}
/**
* @deprecated inject by constructor
*/
private static function getLogger(): LoggerInterface
{
global $dic;
return $dic->get(LoggerInterface::class);
}
}