1
0
Fork 0
mirror of https://github.com/Yetangitu/ampache synced 2025-10-03 01:39:28 +02:00
ampache/lib/class/vainfo.class.php
Phyks (Lucas Verney) 2bb142eeb8 Fix issue #1260
Fix coding guidelines incoherences. Code should match PSR1/2 now, and
php-cs is set to check it on each commit.

Also fixed the Git hook to take into account only added, modified,
copied and renamed files (preventing errors when trying to check deleted
files).

Closes #1260.
2016-08-01 21:55:14 +02:00

1252 lines
47 KiB
PHP

<?php
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
/**
*
* LICENSE: GNU Affero General Public License, version 3 (AGPLv3)
* Copyright 2001 - 2015 Ampache.org
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
/**
*
* This class takes the information pulled from getID3 and returns it in a
* Ampache-friendly way.
*
*/
class vainfo
{
public $encoding = '';
public $encoding_id3v1 = '';
public $encoding_id3v2 = '';
public $filename = '';
public $type = '';
public $tags = array();
public $islocal;
public $gather_types = array();
protected $_raw = array();
protected $_getID3 = null;
protected $_forcedSize = 0;
protected $_file_encoding = '';
protected $_file_pattern = '';
protected $_dir_pattern = '';
private $_pathinfo;
private $_broken = false;
/**
* Constructor
*
* This function just sets up the class, it doesn't pull the information.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct($file, $gather_types = array(), $encoding = null, $encoding_id3v1 = null, $encoding_id3v2 = null, $dir_pattern = '', $file_pattern ='', $islocal = true)
{
$this->islocal = $islocal;
$this->filename = $file;
$this->gather_types = $gather_types;
$this->encoding = $encoding ?: AmpConfig::get('site_charset');
/* These are needed for the filename mojo */
$this->_file_pattern = $file_pattern;
$this->_dir_pattern = $dir_pattern;
// 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']);
$enabled_sources = (array) $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;
$this->_getID3->encoding = $this->encoding;
// 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) {
debug_event('getID3', "Broken file detected: $file: " . $error->getMessage(), 1);
$this->_broken = true;
return false;
}
if (AmpConfig::get('mb_detect_order')) {
$mb_order = AmpConfig::get('mb_detect_order');
} elseif (function_exists('mb_detect_order')) {
$mb_order = implode(", ", mb_detect_order());
} else {
$mb_order = "auto";
}
$test_tags = array('artist', 'album', 'genre', 'title');
if ($encoding_id3v1) {
$this->encoding_id3v1 = $encoding_id3v1;
} else {
$tags = array();
foreach ($test_tags as $tag) {
if ($value = $this->_raw['id3v1'][$tag]) {
$tags[$tag] = $value;
}
}
$this->encoding_id3v1 = self::_detect_encoding($tags, $mb_order);
}
if (AmpConfig::get('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 ($value = $this->_raw['id3v2']['comments'][$tag]) {
$tags[$tag] = $value;
}
}
$this->encoding_id3v2 = self::_detect_encoding($tags, $mb_order);
$this->_getID3->encoding = $this->encoding_id3v2;
}
$this->_getID3->encoding_id3v1 = $this->encoding_id3v1;
}
}
public function forceSize($size)
{
$this->_forcedSize = $size;
}
/**
* _detect_encoding
*
* Takes an array of tags and attempts to automatically detect their
* encoding.
*/
private static function _detect_encoding($tags, $mb_order)
{
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) {
$encodings[$enc]++;
}
}
} else {
$enc = mb_detect_encoding($tags, $mb_order, true);
if ($enc != false) {
$encodings[$enc]++;
}
}
//!!debug_event('vainfo', '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
*/
public function get_info()
{
// 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 true;
}
$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) {
debug_event('getID2', 'Unable to catalog file: ' . $error->getMessage(), 1);
}
}
/* 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();
} // get_info
/*
* write_id3
* This function runs the various steps to gathering the metadata
*/
public function write_id3($data)
{
// Get the Raw file information
$this->read_id3();
if (isset($this->_raw['tags']['id3v2'])) {
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH . 'write.php', __FILE__, true);
$tagWriter = new getid3_writetags();
$tagWriter->filename = $this->filename;
//'id3v2.4' doesn't saves the year;
$tagWriter->tagformats = array('id3v1', 'id3v2.3');
$tagWriter->overwrite_tags = true;
$tagWriter->remove_other_tags = true;
$tagWriter->tag_encoding = 'UTF-8';
$TagData = $this->_raw['tags']['id3v2'];
// Foreach what we've got
foreach ($data as $key=>$value) {
if ($key != 'APIC') {
$TagData[$key][0] = $value;
}
}
if (isset($data['APIC'])) {
$TagData['attached_picture'][0]['data'] = $data['APIC']['data'];
$TagData['attached_picture'][0]['picturetypeid'] = '3';
$TagData['attached_picture'][0]['description'] = 'Cover';
$TagData['attached_picture'][0]['mime'] = $data['APIC']['mime'];
}
$tagWriter->tag_data = $TagData;
if ($tagWriter->WriteTags()) {
if (!empty($tagWriter->warnings)) {
debug_event('vainfo', 'FWarnings ' . implode("\n", $tagWriter->warnings), 5);
}
} else {
debug_event('vainfo', 'Failed to write tags! ' . implode("\n", $tagWriter->errors), 5);
}
}
} // write_id3
/**
* read_id3
* This function runs the various steps to gathering the metadata
*/
public function read_id3()
{
// Get the Raw file information
try {
$this->_raw = $this->_getID3->analyze($this->filename);
return $this->_raw;
} catch (Exception $e) {
debug_event('vainfo', "Unable to read file:" . $e->getMessage(), '1');
}
} // read_id3
/**
* 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.
*/
public static function get_tag_type($results, $config_key = 'metadata_order')
{
$order = (array) AmpConfig::get($config_key);
// Iterate through the defined key order adding them to an ordered array.
$returned_keys = array();
foreach ($order as $key) {
if ($results[$key]) {
$returned_keys[] = $key;
}
}
// If we didn't find anything then default to everything.
if (!isset($returned_keys)) {
$returned_keys = array_keys($results);
$returned_keys = sort($returned_keys);
}
// Unless they explicitly set it, add bitrate/mode/mime/etc.
if (is_array($returned_keys)) {
if (!in_array('general', $returned_keys)) {
$returned_keys[] = 'general';
}
}
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
*/
public static function clean_tag_info($results, $keys, $filename = null)
{
$info = array();
//debug_event('vainfo', 'Clean tag info: ' . print_r($results, true), '5');
$info['file'] = $filename;
// Iteration!
foreach ($keys as $key) {
$tags = $results[$key];
$info['file'] = $info['file'] ?: $tags['file'];
$info['bitrate'] = $info['bitrate'] ?: intval($tags['bitrate']);
$info['rate'] = $info['rate'] ?: intval($tags['rate']);
$info['mode'] = $info['mode'] ?: $tags['mode'];
// size will be added later, because of conflicts between real file size and getID3 reported filesize
$info['mime'] = $info['mime'] ?: $tags['mime'];
$info['encoding'] = $info['encoding'] ?: $tags['encoding'];
$info['rating'] = $info['rating'] ?: $tags['rating'];
$info['time'] = $info['time'] ?: intval($tags['time']);
$info['channels'] = $info['channels'] ?: $tags['channels'];
// This because video title are almost always bad...
$info['original_name'] = $info['original_name'] ?: stripslashes(trim($tags['original_name']));
$info['title'] = $info['title'] ?: stripslashes(trim($tags['title']));
$info['year'] = $info['year'] ?: intval($tags['year']);
$info['disk'] = $info['disk'] ?: intval($tags['disk']);
$info['totaldisks'] = $info['totaldisks'] ?: intval($tags['totaldisks']);
$info['artist'] = $info['artist'] ?: trim($tags['artist']);
$info['albumartist'] = $info['albumartist'] ?: trim($tags['albumartist']);
$info['album'] = $info['album'] ?: trim($tags['album']);
$info['band'] = $info['band'] ?: trim($tags['band']);
$info['composer'] = $info['composer'] ?: trim($tags['composer']);
$info['publisher'] = $info['publisher'] ?: trim($tags['publisher']);
$info['genre'] = self::clean_array_tag('genre', $info, $tags);
$info['mb_trackid'] = $info['mb_trackid'] ?: trim($tags['mb_trackid']);
$info['mb_albumid'] = $info['mb_albumid'] ?: trim($tags['mb_albumid']);
$info['mb_albumid_group'] = $info['mb_albumid_group'] ?: trim($tags['mb_albumid_group']);
$info['mb_artistid'] = $info['mb_artistid'] ?: trim($tags['mb_artistid']);
$info['mb_albumartistid'] = $info['mb_albumartistid'] ?: trim($tags['mb_albumartistid']);
$info['release_type'] = $info['release_type'] ?: trim($tags['release_type']);
$info['language'] = $info['language'] ?: trim($tags['language']);
$info['comment'] = $info['comment'] ?: trim($tags['comment']);
$info['lyrics'] = $info['lyrics']
?: strip_tags(nl2br($tags['lyrics']), "<br>");
$info['replaygain_track_gain'] = $info['replaygain_track_gain'] ?: floatval($tags['replaygain_track_gain']);
$info['replaygain_track_peak'] = $info['replaygain_track_peak'] ?: floatval($tags['replaygain_track_peak']);
$info['replaygain_album_gain'] = $info['replaygain_album_gain'] ?: floatval($tags['replaygain_album_gain']);
$info['replaygain_album_peak'] = $info['replaygain_album_peak'] ?: floatval($tags['replaygain_album_peak']);
$info['track'] = $info['track'] ?: intval($tags['track']);
$info['resolution_x'] = $info['resolution_x'] ?: intval($tags['resolution_x']);
$info['resolution_y'] = $info['resolution_y'] ?: intval($tags['resolution_y']);
$info['display_x'] = $info['display_x'] ?: intval($tags['display_x']);
$info['display_y'] = $info['display_y'] ?: intval($tags['display_y']);
$info['frame_rate'] = $info['frame_rate'] ?: floatval($tags['frame_rate']);
$info['video_bitrate'] = $info['video_bitrate'] ?: intval($tags['video_bitrate']);
$info['audio_codec'] = $info['audio_codec'] ?: trim($tags['audio_codec']);
$info['video_codec'] = $info['video_codec'] ?: trim($tags['video_codec']);
$info['description'] = $info['description'] ?: trim($tags['description']);
$info['tvshow'] = $info['tvshow'] ?: trim($tags['tvshow']);
$info['tvshow_year'] = $info['tvshow_year'] ?: trim($tags['tvshow_year']);
$info['tvshow_season'] = $info['tvshow_season'] ?: trim($tags['tvshow_season']);
$info['tvshow_episode'] = $info['tvshow_episode'] ?: trim($tags['tvshow_episode']);
$info['release_date'] = $info['release_date'] ?: trim($tags['release_date']);
$info['summary'] = $info['summary'] ?: trim($tags['summary']);
$info['tvshow_summary'] = $info['tvshow_summary'] ?: trim($tags['tvshow_summary']);
$info['tvshow_art'] = $info['tvshow_art'] ?: trim($tags['tvshow_art']);
$info['tvshow_season_art'] = $info['tvshow_season_art'] ?: trim($tags['tvshow_season_art']);
$info['art'] = $info['art'] ?: trim($tags['art']);
if (AmpConfig::get('enable_custom_metadata') && is_array($tags)) {
// Add rest of the tags without typecast to the array
foreach ($tags as $tag => $value) {
if (!isset($info[$tag]) && !is_array($value)) {
$info[$tag] = (!is_array($value)) ? trim($value) : $value;
}
}
}
}
// Some things set the disk number even though there aren't multiple
if ($info['totaldisks'] == 1 && $info['disk'] == 1) {
unset($info['disk']);
unset($info['totaldisks']);
}
// Determine the correct file size, do not get fooled by the size which may be returned by id3v2!
if (isset($results['general']['size'])) {
$size = $results['general']['size'];
} else {
$size = Core::get_filesize(Core::conv_lc_file($filename));
}
$info['size'] = $info['size'] ?: $size;
return $info;
}
private static function clean_array_tag($field, $info, $tags)
{
$arr = array();
if ((!$info[$field] || count($info[$field]) == 0) && $tags[$field]) {
if (!is_array($tags[$field])) {
// not all tag formats will return an array, but we need one
$arr[] = trim($tags[$field]);
} else {
foreach ($tags[$field] as $genre) {
$arr[] = trim($genre);
}
}
} else {
$arr = $info[$field];
}
return $arr;
}
/**
* _get_type
*
* This function takes the raw information and figures out what type of
* file we are dealing with.
*/
private function _get_type()
{
// There are a few places that the file type can come from, in the end
// we trust the encoding type.
if ($type = $this->_raw['video']['dataformat']) {
return $this->_clean_type($type);
}
if ($type = $this->_raw['audio']['streams']['0']['dataformat']) {
return $this->_clean_type($type);
}
if ($type = $this->_raw['audio']['dataformat']) {
return $this->_clean_type($type);
}
if ($type = $this->_raw['fileformat']) {
return $this->_clean_type($type);
}
return false;
}
/**
* _get_tags
*
* This processes the raw getID3 output and bakes it.
*/
private function _get_tags()
{
$results = array();
// The tags can come in many different shapes and colors
// depending on the encoding time of day and phase of the moon.
if (is_array($this->_raw['tags'])) {
foreach ($this->_raw['tags'] as $key => $tag_array) {
switch ($key) {
case 'ape':
case 'avi':
case 'flv':
case 'matroska':
debug_event('vainfo', 'Cleaning ' . $key, 5);
$parsed = $this->_cleanup_generic($tag_array);
break;
case 'vorbiscomment':
debug_event('vainfo', 'Cleaning vorbis', 5);
$parsed = $this->_cleanup_vorbiscomment($tag_array);
break;
case 'id3v1':
debug_event('vainfo', 'Cleaning id3v1', 5);
$parsed = $this->_cleanup_id3v1($tag_array);
break;
case 'id3v2':
debug_event('vainfo', 'Cleaning id3v2', 5);
$parsed = $this->_cleanup_id3v2($tag_array);
break;
case 'quicktime':
debug_event('vainfo', 'Cleaning quicktime', 5);
$parsed = $this->_cleanup_quicktime($tag_array);
break;
case 'riff':
debug_event('vainfo', 'Cleaning riff', 5);
$parsed = $this->_cleanup_riff($tag_array);
break;
case 'mpg':
case 'mpeg':
$key = 'mpeg';
debug_event('vainfo', 'Cleaning MPEG', 5);
$parsed = $this->_cleanup_generic($tag_array);
break;
case 'asf':
case 'wmv':
$key = 'asf';
debug_event('vainfo', 'Cleaning WMV/WMA/ASF', 5);
$parsed = $this->_cleanup_generic($tag_array);
break;
case 'lyrics3':
debug_event('vainfo', 'Cleaning lyrics3', 5);
$parsed = $this->_cleanup_lyrics($tag_array);
break;
default:
debug_event('vainfo', 'Cleaning unrecognised tag type ' . $key . ' for file ' . $this->filename, 5);
$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;
}
private function get_metadata_order_key()
{
if (!in_array('music', $this->gather_types)) {
return 'metadata_order_video';
}
return 'metadata_order';
}
private function get_metadata_order()
{
return (array) AmpConfig::get($this->get_metadata_order_key());
}
/**
* _get_plugin_tags
*
* Get additional metadata from plugins
*/
private function _get_plugin_tags()
{
$tag_order = $this->get_metadata_order();
if (!is_array($tag_order)) {
$tag_order = array($tag_order);
}
$plugin_names = Plugin::get_plugins('get_metadata');
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) {
if ($plugin->load($GLOBALS['user'])) {
$this->tags[$tag_source] = $plugin->_plugin->get_metadata($this->gather_types, self::clean_tag_info($this->tags, self::get_tag_type($this->tags, $this->get_metadata_order_key()), $this->filename));
}
}
}
}
}
/**
* _parse_general
*
* Gather and return the general information about a file (vbr/cbr,
* sample rate, channels, etc.)
*/
private function _parse_general($tags)
{
$parsed = array();
if ((in_array('movie', $this->gather_types)) || (in_array('tvshow', $this->gather_types))) {
$parsed['title'] = $this->formatVideoName(urldecode($this->_pathinfo['filename']));
} else {
$parsed['title'] = urldecode($this->_pathinfo['filename']);
}
$parsed['mode'] = $tags['audio']['bitrate_mode'];
if ($parsed['mode'] == 'con') {
$parsed['mode'] = 'cbr';
}
$parsed['bitrate'] = $tags['audio']['bitrate'];
$parsed['channels'] = intval($tags['audio']['channels']);
$parsed['rate'] = intval($tags['audio']['sample_rate']);
$parsed['size'] = $this->_forcedSize ?: $tags['filesize'];
$parsed['encoding'] = $tags['encoding'];
$parsed['mime'] = $tags['mime_type'];
$parsed['time'] = ($this->_forcedSize ? ((($this->_forcedSize - $tags['avdataoffset']) * 8) / $tags['bitrate']) : $tags['playtime_seconds']);
$parsed['audio_codec'] = $tags['audio']['dataformat'];
$parsed['video_codec'] = $tags['video']['dataformat'];
$parsed['resolution_x'] = $tags['video']['resolution_x'];
$parsed['resolution_y'] = $tags['video']['resolution_y'];
$parsed['display_x'] = $tags['video']['display_x'];
$parsed['display_y'] = $tags['video']['display_y'];
$parsed['frame_rate'] = $tags['video']['frame_rate'];
$parsed['video_bitrate'] = $tags['video']['bitrate'];
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] = floatval($tag['data'][0]);
break;
}
}
}
}
return $parsed;
}
private function trimAscii($string)
{
return preg_replace('/[\x00-\x1F\x80-\xFF]/', '', trim($string));
}
/**
* _clean_type
* This standardizes the type that we are given into a recognized type.
*/
private function _clean_type($type)
{
switch ($type) {
case 'mp3':
case 'mp2':
case 'mpeg3':
return 'mp3';
case 'vorbis':
return 'ogg';
case 'flac':
case 'flv':
case 'mpg':
case 'mpeg':
case 'asf':
case 'wmv':
case 'avi':
case 'quicktime':
return $type;
default:
/* Log the fact that we couldn't figure it out */
debug_event('vainfo', 'Unable to determine file type from ' . $type . ' on file ' . $this->filename, '5');
return $type;
}
}
/**
* _cleanup_generic
*
* This does generic cleanup.
*/
private function _cleanup_generic($tags)
{
$parsed = array();
foreach ($tags as $tagname => $data) {
switch (strtolower($tagname)) {
case 'genre':
// Pass the array through
$parsed[$tagname] = $this->parseGenres($data);
break;
case 'musicbrainz_artistid':
$parsed['mb_artistid'] = $data[0];
break;
case 'musicbrainz_albumid':
$parsed['mb_albumid'] = $data[0];
break;
case 'musicbrainz_albumartistid':
$parsed['mb_albumartistid'] = $data[0];
break;
case 'musicbrainz_releasegroupid':
$parsed['mb_albumid_group'] = $data[0];
break;
case 'musicbrainz_trackid':
$parsed['mb_trackid'] = $data[0];
break;
case 'musicbrainz_albumtype':
$parsed['release_type'] = $data[0];
break;
default:
$parsed[$tagname] = $data[0];
break;
}
}
return $parsed;
}
/**
* _cleanup_lyrics
*
* This is supposed to handle lyrics3. FIXME: does it?
*/
private function _cleanup_lyrics($tags)
{
$parsed = array();
foreach ($tags as $tag => $data) {
if ($tag == 'unsyncedlyrics' || $tag == 'unsynced lyrics' || $tag == 'unsynchronised lyric') {
$tag = 'lyrics';
}
$parsed[$tag] = $data[0];
}
return $parsed;
}
/**
* _cleanup_vorbiscomment
*
* Standardises tag names from vorbis.
*/
private function _cleanup_vorbiscomment($tags)
{
$parsed = array();
foreach ($tags as $tag => $data) {
switch (strtolower($tag)) {
case 'genre':
// Pass the array through
$parsed[$tag] = $this->parseGenres($data);
break;
case 'tracknumber':
$parsed['track'] = $data[0];
break;
case 'discnumber':
$elements = explode('/', $data[0]);
$parsed['disk'] = $elements[0];
$parsed['totaldisks'] = $elements[1];
break;
case 'date':
$parsed['year'] = $data[0];
break;
case 'musicbrainz_artistid':
$parsed['mb_artistid'] = $data[0];
break;
case 'musicbrainz_albumid':
$parsed['mb_albumid'] = $data[0];
break;
case 'musicbrainz_albumartistid':
$parsed['mb_albumartistid'] = $data[0];
break;
case 'musicbrainz_releasegroupid':
$parsed['mb_albumid_group'] = $data[0];
break;
case 'musicbrainz_trackid':
$parsed['mb_trackid'] = $data[0];
break;
case 'musicbrainz_albumtype':
$parsed['release_type'] = $data[0];
break;
case 'unsyncedlyrics':
case 'unsynced lyrics':
case 'lyrics':
$parsed['lyrics'] = $data[0];
break;
default:
$parsed[$tag] = $data[0];
break;
}
}
return $parsed;
}
/**
* _cleanup_id3v1
*
* Doesn't do much.
*/
private function _cleanup_id3v1($tags)
{
$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[$tag] = $data[0];
}
return $parsed;
}
/**
* _cleanup_id3v2
*
* Whee, v2!
*/
private function _cleanup_id3v2($tags)
{
$parsed = array();
foreach ($tags as $tag => $data) {
switch ($tag) {
case 'genre':
$parsed['genre'] = $this->parseGenres($data);
break;
case 'part_of_a_set':
$elements = explode('/', $data[0]);
$parsed['disk'] = $elements[0];
$parsed['totaldisks'] = $elements[1];
break;
case 'track_number':
$parsed['track'] = $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 'comments':
$parsed['comment'] = $data[0];
break;
case 'unsynchronised_lyric':
$parsed['lyrics'] = $data[0];
break;
default:
$parsed[$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?
foreach ($id3v2['TXXX'] as $txxx) {
switch (strtolower($this->trimAscii($txxx['description']))) {
case 'musicbrainz album id':
$parsed['mb_albumid'] = $this->trimAscii($txxx['data']);
break;
case 'musicbrainz release group id':
$parsed['mb_albumid_group'] = $this->trimAscii($txxx['data']);
break;
case 'musicbrainz artist id':
$parsed['mb_artistid'] = $this->trimAscii($txxx['data']);
break;
case 'musicbrainz album artist id':
$parsed['mb_albumartistid'] = $this->trimAscii($txxx['data']);
break;
case 'musicbrainz album type':
$parsed['release_type'] = $this->trimAscii($txxx['data']);
break;
case 'catalognumber':
$parsed['catalog_number'] = $this->trimAscii($txxx['data']);
break;
case 'replaygain_track_gain':
$parsed['replaygain_track_gain'] = floatval($txxx['data']);
break;
case 'replaygain_track_peak':
$parsed['replaygain_track_peak'] = floatval($txxx['data']);
break;
case 'replaygain_album_gain':
$parsed['replaygain_album_gain'] = floatval($txxx['data']);
break;
case 'replaygain_album_peak':
$parsed['replaygain_album_peak'] = floatval($txxx['data']);
break;
}
}
}
}
// Find the rating
if (is_array($id3v2['POPM'])) {
foreach ($id3v2['POPM'] as $popm) {
if (array_key_exists('email', $popm) &&
$user = User::get_from_email($popm['email'])) {
if ($user) {
// Ratings are out of 255; scale it
$parsed['rating'][$user->id] = $popm['rating'] / 255 * 5;
}
}
// Rating made by an unknow user, adding it to super user (id=-1)
else {
$parsed['rating'][-1] = $popm['rating'] / 255 * 5;
}
}
}
return $parsed;
}
/**
* _cleanup_riff
*/
private function _cleanup_riff($tags)
{
$parsed = array();
foreach ($tags as $tag => $data) {
switch ($tag) {
case 'product':
$parsed['album'] = $data[0];
break;
default:
$parsed[$tag] = $data[0];
break;
}
}
return $parsed;
}
/**
* _cleanup_quicktime
*/
private function _cleanup_quicktime($tags)
{
$parsed = array();
foreach ($tags as $tag => $data) {
switch ($tag) {
case 'creation_date':
$parsed['release_date'] = strtotime(str_replace(" ", "", $data[0]));
if (strlen($data['0']) > 4) {
$data[0] = date('Y', $parsed['release_date']);
}
$parsed['year'] = $data[0];
break;
case 'MusicBrainz Track Id':
$parsed['mb_trackid'] = $data[0];
break;
case 'MusicBrainz Album Id':
$parsed['mb_albumid'] = $data[0];
break;
case 'MusicBrainz Album Artist Id':
$parsed['mb_albumartistid'] = $data[0];
break;
case 'MusicBrainz Release Group Id':
$parsed['mb_albumid_group'] = $data[0];
break;
case 'MusicBrainz Artist Id':
$parsed['mb_artistid'] = $data[0];
break;
case 'MusicBrainz Album Type':
$parsed['release_type'] = $data[0];
break;
case 'track_number':
$parsed['track'] = $data[0];
break;
case 'disc_number':
$parsed['disk'] = $data[0];
break;
case 'album_artist':
$parsed['albumartist'] = $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[$tag] = $data[0];
break;
}
}
return $parsed;
}
/**
* 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
*/
private function _parse_filename($filepath)
{
$origin = $filepath;
$results = array();
$file = pathinfo($filepath, PATHINFO_FILENAME);
if (in_array('tvshow', $this->gather_types)) {
$season = array();
$episode = array();
$tvyear = array();
$temp = array();
preg_match("~(?<=\(\[\<\{)[1|2][0-9]{3}|[1|2][0-9]{3}~", $filepath, $tvyear);
$results['year'] = !empty($tvyear) ? intval($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 ($results['tvshow_season'] && $results['tvshow_episode']) {
// 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->gather_types)) {
$results['original_name'] = $results['title'] = $this->formatVideoName($file);
}
if (in_array('music', $this->gather_types) || in_array('clip', $this->gather_types)) {
$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;
}
public static function parse_pattern($filepath, $dir_pattern, $file_pattern)
{
$results = array();
$slash_type_preg = DIRECTORY_SEPARATOR;
if ($slash_type_preg == '\\') {
$slash_type_preg .= DIRECTORY_SEPARATOR;
}
// Combine the patterns
$pattern = preg_quote($dir_pattern) . $slash_type_preg . preg_quote($file_pattern);
// 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('/\%[Ty]/', '([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);
if ($matches != null) {
// The first element is the full match text
$matched = array_shift($matches);
debug_event('vainfo', $pattern . ' matched ' . $matched . ' on ' . $filepath, 5);
// Iterate over what we found
foreach ($matches as $key => $value) {
$new_key = translate_pattern_code($elements['0'][$key]);
if ($new_key) {
$results[$new_key] = $value;
}
}
$results['title'] = $results['title'] ?: basename($filepath);
}
return $results;
}
private function removeCommonAbbreviations($name)
{
$abbr = explode(",", AmpConfig::get('common_abbr'));
$commonabbr = preg_replace("~\n~", '', $abbr);
$commonabbr[] = '[1|2][0-9]{3}'; //Remove release year
//scan for brackets, braces, etc and ignore case.
for ($i=0; $i< count($commonabbr);$i++) {
$commonabbr[$i] = "~\[*|\(*|\<*|\{*\b(?i)" . trim($commonabbr[$i]) . "\b\]*|\)*|\>*|\}*~";
}
$string = preg_replace($commonabbr, '', $name);
return $string;
}
private function formatVideoName($name)
{
return ucwords(trim($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()
{
/* Pull In the config option */
$order = AmpConfig::get('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;
}
// set_broken
/**
*
* @param array $data
* @return array
* @throws Exception
*/
private function parseGenres($data)
{
// read additional id3v2 delimiters from config
$delimiters = AmpConfig::get('additional_genre_delimiters');
if (isset($data) && is_array($data) && count($data) === 1 && isset($delimiters)) {
$pattern = '~[\s]?(' . $delimiters . ')[\s]?~';
$genres = preg_split($pattern, reset($data));
if ($genres === false) {
throw new Exception('Pattern given in additional_genre_delimiters is not functional. Please ensure is it a valid regex (delimiter ~).');
}
$data = $genres;
}
return $data;
}
} // end class vainfo