AllDrives = json_decode(
file_get_contents(implode(DIRECTORY_SEPARATOR, [__DIR__, 'resources', 'drives.json']))
);
}
public function getLog(): string
{
return $this->log;
}
/**
* @param string $LogPath path to log file on local filesystem
*/
public function newFile(string $LogPath): void
{
$this->reset();
$this->logPath = $LogPath;
$this->log = file_get_contents($this->logPath);
}
private function reset(): void
{
$this->logPath = null;
$this->logs = array();
$this->Tracks = array();
$this->checksumStatus = Check\Checksum::CHECKSUM_OK;
$this->Score = 100;
$this->Details = array();
$this->Offsets = array();
$this->DriveFound = false;
$this->Drives = array();
$this->SecureMode = true;
$this->NonSecureMode = null;
$this->BadTrack = array();
$this->DecreaseScoreTrack = 0;
$this->ripper = null;
$this->ripperVersion = null;
$this->TrackNumber = null;
$this->ARTracks = array();
$this->Combined = null;
$this->CurrLog = null;
$this->Range = null;
$this->ARSummary = null;
$this->XLDSecureRipper = false;
}
public function validateChecksum(bool $Bool): void
{
$this->ValidateChecksum = $Bool;
}
public function parse(): void
{
try {
$this->log = Util::decodeEncoding($this->log, $this->logPath);
} catch (\Exception $exc) {
$this->Score = 0;
$this->account('Could not detect log encoding, log is corrupt.');
}
$this->ripper = Ripper::getRipper($this->log);
if ($this->ripper === Ripper::WHIPPER) {
$this->whipperParse();
} else {
$this->legacyParse();
}
}
private function whipperParse(): void
{
if (preg_match('/whipper ([0-9]+\.[0-9]+\.[0-9])/', $this->log, $matches)) {
if (version_compare('0.7.3', $matches[1]) === 1) {
$this->account('Logs must be produced by whipper 0.7.3+.', 100);
return;
}
}
// Whipper 0.7.x has an issue where it can produce invalid YAML
// as it hand writes out the values without dealing properly
// with the escaping the output, so we fix that here
$log = preg_replace_callback('/ (Release|Album): (.+)/', function ($match) {
return " {$match[1]}: " . Yaml::dump($match[2]);
}, $this->log);
// symfony/yaml will attempt to parse CRCs that start with 0 as octals
$log = preg_replace_callback('/CRC: ([A-Z0-9]+)/', function ($match) {
return "CRC: \"{$match[1]}\"";
}, $log);
try {
$Yaml = Yaml::parse($log);
} catch (ParseException $exception) {
$this->account('Could not parse whipper log.', 100);
return;
}
$this->ripperVersion = explode(" ", $Yaml['Log created by'])[1];
// Releases before this used octal numbers for tracks in the log which
// gets messed up in parsing and we lose track data (e.g. tracks 08 and
// 09 get merged into one entry).
if (empty($this->ripperVersion) || version_compare('0.7.3', $this->ripperVersion) === 1) {
$this->account('Logs must be produced by whipper 0.7.3+', 100);
return;
}
$Yaml['Log created by'] = preg_replace(
'/^(whipper) ([^\s]+)/',
"$1 $2",
$Yaml['Log created by']
);
if (empty($Yaml['SHA-256 hash'])) {
$this->checksumStatus = Check\Checksum::CHECKSUM_MISSING;
} else {
$this->checksumStatus = Check\Checksum::validate($this->logPath, $this->ripper);
$Class = $this->checksumStatus === Check\Checksum::CHECKSUM_OK ? 'good' : 'bad';
$Yaml['SHA-256 hash'] = "{$Yaml['SHA-256 hash']}";
}
$Key = 'Ripping phase information';
$Drive = $Yaml[$Key]['Drive'];
$Offset = $Yaml[$Key]['Read offset correction'];
if (in_array(trim($Drive), $this->FakeDrives)) {
$this->account('Virtual drive used: ' . $Drive, 20, false, false, false);
$Yaml[$Key]['Drive'] = "{$Drive}";
} else {
$this->getDrives($Drive);
$DriveClass = 'badish';
if (count($this->Drives) > 0) {
$DriveClass = 'good';
if (in_array((string) $Offset, $this->Offsets)) {
$OffsetClass = 'good';
} else {
$OffsetClass = 'bad';
$this->account(
'Incorrect read offset for drive. Correct offsets are: ' .
implode(', ', $this->Offsets) . ' (Checked against the following drive(s): ' .
implode(', ', $this->Drives) . ')',
5,
false,
false,
false
);
}
} else {
$Drive .= ' (not found in database)';
$OffsetClass = 'badish';
if ($Offset === '0') {
$OffsetClass = 'bad';
$this->account(
'The drive was not found in the database, so we cannot determine the correct read ' .
'offset. However, the read offset in this case was 0, which is almost never correct. ' .
'As such, we are assuming that the offset is incorrect',
5,
false,
false,
false
);
}
}
$Yaml[$Key]['Drive'] = "{$Drive}";
$Offset = ($Offset > 0) ? '+' . (string) $Offset : (string) $Offset;
$Yaml[$Key]['Read offset correction'] = "{$Offset}";
}
$DefeatCache = $Yaml[$Key]['Defeat audio cache'];
if (is_string($DefeatCache)) {
$Value = (strtolower($DefeatCache) === 'yes') ? 'Yes' : 'No';
$Class = (strtolower($DefeatCache) === 'yes') ? 'good' : 'bad';
} else {
$Value = ($DefeatCache === true) ? 'true' : 'false';
$Class = ($DefeatCache === true) ? 'good' : 'bad';
}
if ($Class === 'bad') {
$this->account('"Defeat audio cache" should be Yes/true', 10);
}
$Yaml[$Key]['Defeat audio cache'] = "{$Value}";
$Yaml[$Key]['Overread into lead-out'] = "{$Yaml[$Key]['Overread into lead-out']}";
// CD Metadata
$Key = 'CD metadata';
$ReleaseKey = isset($Yaml[$Key]['Release']) ? 'Release' : 'Album';
if (is_string($Yaml[$Key][$ReleaseKey])) {
$Yaml[$Key][$ReleaseKey] = "{$Yaml[$Key][$ReleaseKey]}";
} else {
$Yaml[$Key][$ReleaseKey]['Artist'] = "{$Yaml[$Key][$ReleaseKey]['Artist']}";
$Yaml[$Key][$ReleaseKey]['Title'] = "{$Yaml[$Key][$ReleaseKey]['Title']}";
}
// TOC
foreach ($Yaml['TOC'] as &$Track) {
foreach (['Start', 'Length', 'Start sector', 'End sector'] as $Key) {
$Track[$Key] = "{$Track[$Key]}";
}
}
// Tracks
foreach ($Yaml['Tracks'] as &$Track) {
$Track['Peak level'] = sprintf('%.6f', $Track['Peak level']);
$Class = 'good';
if ($Track['Test CRC'] !== $Track['Copy CRC']) {
$Class = 'bad';
$this->account("CRC mismatch: {$Track['Test CRC']} and {$Track['Copy CRC']}", 30);
}
$Track['Test CRC'] = "{$Track['Test CRC']}";
$Track['Copy CRC'] = "{$Track['Copy CRC']}";
$Class = ($Track['Status'] === 'Copy OK') ? 'good' : 'bad';
$Track['Status'] = "{$Track['Status']}";
foreach (['Filename', 'Pre-gap length', 'Peak level', 'Extraction speed', 'Extraction quality'] as $Key) {
if (!isset($Track[$Key])) {
continue;
}
$Track[$Key] = "{$Track[$Key]}";
}
foreach (['v1', 'v2'] as $Version) {
$Key = 'AccurateRip ' . $Version;
if (isset($Track[$Key])) {
$Class = $Track[$Key]['Result'] === 'Found, exact match' ? 'good' : 'badish';
$Track[$Key]['Result'] = "{$Track[$Key]['Result']}";
if (isset($Track[$Key]['Local CRC']) && isset($Track[$Key]['Remote CRC'])) {
$Class = ($Track[$Key]['Local CRC'] === $Track[$Key]['Remote CRC']) ? 'goodish' : 'badish';
$Track[$Key]['Local CRC'] = "{$Track[$Key]['Local CRC']}";
$Track[$Key]['Remote CRC'] = "{$Track[$Key]['Remote CRC']}";
}
}
}
}
// Conclusive status report
$Key = 'Conclusive status report';
$Class = $Yaml[$Key]['AccurateRip summary'] === 'All tracks accurately ripped' ? 'good' : 'badish';
$Yaml[$Key]['AccurateRip summary'] = "{$Yaml[$Key]['AccurateRip summary']}";
$Class = $Yaml[$Key]['Health status'] === 'No errors occurred' ? 'good' : 'bad';
$Yaml[$Key]['Health status'] = "{$Yaml[$Key]['Health status']}";
$CreationDate = gmdate("Y-m-d\TH:i:s\Z", $Yaml['Log creation date']);
$this->log = "Log created by: {$Yaml['Log created by']}\nLog creation date: {$CreationDate}\n\n";
$this->log .= "Ripping phase information:\n";
foreach ($Yaml['Ripping phase information'] as $Key => $Value) {
if (is_bool($Value)) {
$Value = ($Value) ? 'true' : 'false';
}
$this->log .= " {$Key}: {$Value}\n";
}
$this->log .= "\n";
$this->log .= "CD metadata:\n";
foreach ($Yaml['CD metadata'] as $Key => $Value) {
if (is_bool($Value)) {
$Value = ($Value) ? 'true' : 'false';
}
$this->log .= " {$Key}: {$Value}\n";
}
$this->log .= "\n";
$this->log .= "TOC:\n";
foreach ($Yaml['TOC'] as $Key => $Track) {
$this->log .= " {$Key}:\n";
foreach ($Track as $KKey => $Value) {
$this->log .= " {$KKey}: {$Value}\n";
}
$this->log .= "\n";
}
$this->log .= "Tracks:\n";
foreach ($Yaml['Tracks'] as $Key => $Track) {
$this->log .= " {$Key}:\n";
foreach ($Track as $KKey => $Value) {
if (is_array($Value)) {
$this->log .= " {$KKey}:\n";
foreach ($Value as $KKKey => $VValue) {
$this->log .= " {$KKKey}: {$VValue}\n";
}
} else {
if (is_bool($Value)) {
$Value = ($Value) ? 'Yes' : 'No';
}
$this->log .= " {$KKey}: {$Value}\n";
}
}
$this->log .= "\n";
}
$this->log .= "Conclusive status report:\n";
foreach ($Yaml['Conclusive status report'] as $Key => $Value) {
$this->log .= " {$Key}: {$Value}\n";
}
$this->log .= "\n";
if (isset($Yaml['SHA-256 hash'])) {
$this->log .= "SHA-256 hash: {$Yaml['SHA-256 hash']}\n";
}
}
private function legacyParse()
{
if ($this->ripper === Ripper::EAC) {
$translator = new Translator();
$lang = $translator->getLanguage($this->log);
if ($lang['code'] !== 'en') {
$this->language = $lang['code'];
$this->account(
"Translated log from {$lang['name']} ({$lang['name_english']}) to English.",
false,
false,
false,
true
);
$this->log = $translator->translate($this->log, $lang['code']);
}
}
$this->log = str_replace(array("\r\n", "\r"), array("\n", ""), $this->log);
// Split the log apart
if (preg_match("/[\=]+\s+Log checksum/i", $this->log)) { // eac checksum
$this->logs = preg_split("/(\n\=+\s+Log checksum.*)/i", $this->log, -1, PREG_SPLIT_DELIM_CAPTURE);
} elseif (
preg_match(
"/[\-]+BEGIN XLD SIGNATURE[\S\n\-]+END XLD SIGNATURE[\-]+/i",
$this->log
)
) { // xld checksum (plugin)
$this->logs = preg_split(
"/(\n[\-]+BEGIN XLD SIGNATURE[\S\n\-]+END XLD SIGNATURE[\-]+)/i",
$this->log,
-1,
PREG_SPLIT_DELIM_CAPTURE
);
} else { //no checksum
$this->checksumStatus = Check\Checksum::CHECKSUM_MISSING;
$this->logs = preg_split("/(\nEnd of status report)/i", $this->log, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach ($this->logs as $Key => $Value) {
if (preg_match("/---- CUETools DB Plugin V.+/i", $Value)) {
unset($this->logs[$Key]);
}
}
}
foreach ($this->logs as $Key => $Log) {
$Log = trim($Log);
if ($Log === "" || preg_match('/^\-+$/i', $Log)) {
unset($this->logs[$Key]);
} elseif (
$this->checksumStatus !== Check\Checksum::CHECKSUM_OK
&& preg_match("/End of status report/i", $Log)
) {
//strip empty
//append stat msgs
$this->logs[$Key - 1] .= $Log;
unset($this->logs[$Key]);
} elseif (
$this->checksumStatus === Check\Checksum::CHECKSUM_OK
&& preg_match("/[\=]+\s+Log checksum/i", $Log)
) {
$this->logs[$Key - 1] .= $Log;
unset($this->logs[$Key]);
} elseif (
$this->checksumStatus === Check\Checksum::CHECKSUM_OK
&& preg_match("/[\-]+BEGIN XLD SIGNATURE/i", $Log)
) {
$this->logs[$Key - 1] .= $Log;
unset($this->logs[$Key]);
}
}
$this->logs = array_values($this->logs); //rebuild index
if (count($this->logs) > 1) {
$this->Combined = count($this->logs);
} //is_combined
foreach ($this->logs as $LogArrayKey => $Log) {
$this->CurrLog = $LogArrayKey + 1;
if (preg_match('/Exact Audio Copy (.+) from/i', $Log, $Matches)) { //eac v1 & checksum
if ($Matches[1]) {
$this->ripperVersion = ltrim($Matches[1], 'V');
$versionCheck = explode(" ", $this->ripperVersion)[0];
if (version_compare($versionCheck, "1.0") < 0) {
$this->checksumStatus = Check\Checksum::CHECKSUM_MISSING;
if ($this->ripperVersion <= 0.95) {
# EAC 0.95 and before was missing a handful of stuff for full log validation
# that 0.99 included (-30 points)
$this->account("EAC version older than 0.99", 30);
}
} elseif (!preg_match('/(\=+\s+Log checksum.*)/i', $Log)) {
// Above version 1 and no checksum
$this->checksumStatus = Check\Checksum::CHECKSUM_MISSING;
}
} else {
$this->checksumStatus = Check\Checksum::CHECKSUM_MISSING;
$this->account("EAC version older than 0.99", 30);
}
} elseif (preg_match('/EAC extraction logfile from/i', $Log)) {
$this->checksumStatus = Check\Checksum::CHECKSUM_MISSING;
$this->account("EAC version older than 0.99", 30);
}
if (preg_match('/X Lossless Decoder version (\d+) \((.+)\)/i', $Log, $Matches)) { //xld version & checksum
$this->ripperVersion = $Matches[1];
if (
version_compare($this->ripperVersion, "20121222") >= 0
&& !preg_match('/([\-]+BEGIN XLD SIGNATURE[\S\n\-]+END XLD SIGNATURE[\-]+)/i', $Log)
) {
$this->checksumStatus = Check\Checksum::CHECKSUM_MISSING;
//$this->account('No checksum with XLD 20121222 or newer', 15);
}
}
$Log = preg_replace(
'/Exact Audio Copy (.+) from (.+)/i',
'Exact Audio Copy $1 from $2',
$Log,
1,
$Count
);
$Log = preg_replace(
"/EAC extraction logfile from (.+)\n+(.+)/i",
"EAC extraction logfile from $1\n\n" .
'$2',
$Log,
1,
$EAC
);
$Log = preg_replace(
"/X Lossless Decoder version (.+) \((.+)\)/i",
"X Lossless Decoder version $1 ($2)",
$Log,
1,
$Count
);
$Log = preg_replace(
"/XLD extraction logfile from (.+)\n+(.+)/i",
"XLD extraction logfile from $1\n\n" .
'$2',
$Log,
1,
$XLD
);
if (!$EAC && !$XLD) {
if ($this->Combined) {
unset($this->Details);
$this->Details[] = "Combined Log (" . $this->Combined . ")";
$this->Details[] = "Unrecognized log file (" . $this->CurrLog . ")! " .
"Feel free to report for manual review.";
} else {
$this->Details[] = "Unrecognized log file! Feel free to report for manual review.";
}
$this->Score = 0;
return;
}
if (
$this->ValidateChecksum
&& $this->checksumStatus === Check\Checksum::CHECKSUM_OK
&& !empty($this->logPath)
) {
if (Check\Checksum::logcheckerExists($this->ripper)) {
$this->checksumStatus = Check\Checksum::validate($this->logPath, $this->ripper);
} else {
$this->account(
"Could not find {$this->ripper} logchecker, checksum not validated.",
false,
false,
false,
true
);
}
}
$Class = $this->checksumStatus === Check\Checksum::CHECKSUM_OK ? 'good' : 'bad';
$Log = preg_replace('/(\=+\s+Log checksum.*)/i', "$1", $Log, 1, $Count);
$Log = preg_replace(
'/([\-]+BEGIN XLD SIGNATURE[\S\n\-]+END XLD SIGNATURE[\-]+)/i',
"$1",
$Log,
1,
$Count
);
$Log = preg_replace_callback("/Used drive( *): (.+)/i", array(
$this,
'drive'
), $Log, 1, $Count);
if (!$Count) {
$this->account('Could not verify used drive', 1);
}
$Log = preg_replace_callback("/Media type( *): (.+)/i", array(
$this,
'mediaTypeXld'
), $Log, 1, $Count);
if ($XLD && $this->ripperVersion && $this->ripperVersion >= 20130127 && !$Count) {
$this->account('Could not verify media type', 1);
}
$Log = preg_replace_callback('/Read mode( *): ([a-z]+)(.*)?/i', array(
$this,
'readMode'
), $Log, 1, $Count);
if (!$Count && $EAC) {
$this->account('Could not verify read mode', 1);
}
$Log = preg_replace_callback('/Ripper mode( *): (.*)/i', array(
$this,
'ripperModeXld'
), $Log, 1, $XLDRipperMode);
$Log = preg_replace_callback('/Use cdparanoia mode( *): (.*)/i', array(
$this,
'cdparanoiaModeXld'
), $Log, 1, $XLDCDParanoiaMode);
if (!$XLDRipperMode && !$XLDCDParanoiaMode && $XLD) {
$this->account('Could not verify read mode', 1);
}
$Log = preg_replace_callback('/Max retry count( *): (\d+)/i', array(
$this,
'maxRetryCount'
), $Log, 1, $Count);
if (!$Count && $XLD) {
$this->account('Could not verify max retry count');
}
$Log = preg_replace_callback('/Utilize accurate stream( *): (Yes|No)/i', array(
$this,
'accurateStream'
), $Log, 1, $EAC_ac_stream);
$Log = preg_replace_callback('/, (|NO )accurate stream/i', array(
$this,
'accurateStreamEacPre9'
), $Log, 1, $EAC_ac_stream_pre99);
if (!$EAC_ac_stream && !$EAC_ac_stream_pre99 && !$this->NonSecureMode && $EAC) {
$this->account('Could not verify accurate stream', 20);
}
$Log = preg_replace_callback('/Defeat audio cache( *): (Yes|No)/i', array(
$this,
'defeatAudioCache'
), $Log, 1, $EAC_defeat_cache);
$Log = preg_replace_callback('/ (|NO )disable cache/i', array(
$this,
'defeatAudioCacheEacPre99'
), $Log, 1, $EAC_defeat_cache_pre99);
if (!$EAC_defeat_cache && !$EAC_defeat_cache_pre99 && !$this->NonSecureMode && $EAC) {
$this->account('Could not verify defeat audio cache', 1);
}
$Log = preg_replace_callback('/Disable audio cache( *): (.*)/i', array(
$this,
'defeatAudioCacheXld'
), $Log, 1, $Count);
if (!$Count && $XLD) {
$this->account('Could not verify defeat audio cache', 1);
}
$Log = preg_replace_callback('/Make use of C2 pointers( *): (Yes|No)/i', array(
$this,
'c2Pointers'
), $Log, 1, $C2);
$Log = preg_replace_callback('/with (|NO )C2/i', array(
$this,
'c2PointersEacPre99'
), $Log, 1, $C2_EACpre99);
if (!$C2 && !$C2_EACpre99 && !$this->NonSecureMode) {
$this->account('Could not verify C2 pointers', 1);
}
$Log = preg_replace_callback('/Read offset correction( *): ([+-]?[0-9]+)/i', array(
$this,
'readOffset'
), $Log, 1, $Count);
if (!$Count) {
$this->account('Could not verify read offset', 1);
}
$Log = preg_replace(
"/(Combined read\/write offset correction\s*:\s+\d+)/i",
"$1",
$Log,
1,
$Count
);
if ($Count) {
$this->account('Combined read/write offset cannot be verified', 4, false, false, false);
}
//xld alternate offset table
$Log = preg_replace(
"/(List of \w+ offset correction values) *(\n+)(( *.*confidence .*\) ?\n)+)/i",
"$1$2$3\n",
$Log,
1,
$Count
);
$Log = preg_replace(
"/(List of \w+ offset correction values) *\n( *\# +\| +Absolute +\| +Relative +\| +Confidence) *\n" .
"( *\-+) *\n(( *\d+ +\| +\-?\+?\d+ +\| +\-?\+?\d+ +\| +\d+ *\n)+)/i",
"$1\n$2\n$3\n$4\n",
$Log,
1,
$Count
);
$Log = preg_replace(
'/Overread into Lead-In and Lead-Out( +): (Yes|No)/i',
'Overread into Lead-In and Lead-Out$1: $2',
$Log,
1,
$Count
);
$Log = preg_replace_callback('/Fill up missing offset samples with silence( +): (Yes|No)/i', array(
$this,
'fillOffsetSamples'
), $Log, 1, $Count);
if (!$Count && $EAC) {
$this->account('Could not verify missing offset samples', 1);
}
$Log = preg_replace_callback('/Delete leading and trailing silent blocks([ \w]*)( +): (Yes|No)/i', array(
$this,
'deleteSilentBlocks'
), $Log, 1, $Count);
if (!$Count && $EAC) {
$this->account('Could not verify silent blocks', 1);
}
$Log = preg_replace_callback('/Null samples used in CRC calculations( +): (Yes|No)/i', array(
$this,
'nullSamples'
), $Log, 1, $Count);
if (!$Count && $EAC) {
$this->account('Could not verify null samples');
}
$Log = preg_replace(
'/Used interface( +): ([^\n]+)/i',
'Used interface$1: $2',
$Log,
1,
$Count
);
$Log = preg_replace_callback('/Gap handling( +): ([^\n]+)/i', array(
$this,
'gapHandling'
), $Log, 1, $Count);
if (!$Count && $EAC) {
$this->account('Could not verify gap handling', 10);
}
$Log = preg_replace_callback('/Gap status( +): (.*)/i', array(
$this,
'gapHandlingXld'
), $Log, 1, $Count);
if (!$Count && $XLD) {
$this->account('Could not verify gap status', 10);
}
$Log = preg_replace(
'/Used output format( *): ([^\n]+)/i',
'Used output format$1: $2',
$Log,
1,
$Count
);
$Log = preg_replace(
'/Sample format( +): ([^\n]+)/i',
'Sample format$1: $2',
$Log,
1,
$Count
);
$Log = preg_replace(
'/Selected bitrate( +): ([^\n]+)/i',
'Selected bitrate$1: $2',
$Log,
1,
$Count
);
$Log = preg_replace(
'/( +)(\d+ kBit\/s)/i',
'$1$2',
$Log,
1,
$Count
);
$Log = preg_replace(
'/Quality( +): ([^\n]+)/i',
'Quality$1: $2',
$Log,
1,
$Count
);
$Log = preg_replace_callback('/Add ID3 tag( +): (Yes|No)/i', array(
$this,
'addId3Tag'
), $Log, 1, $Count);
if (!$Count && $EAC) {
$this->account('Could not verify id3 tag setting', 1);
}
$Log = preg_replace(
"/(Use compression offset\s+:\s+\d+)/i",
"$1",
$Log,
1,
$Count
);
if ($Count) {
$this->account('Ripped with compression offset', false, 0);
}
$Log = preg_replace(
'/Command line compressor( +): ([^\n]+)/i',
'Command line compressor$1: $2',
$Log,
1,
$Count
);
$Log = preg_replace(
"/Additional command line options([^\n]{70,110} )/",
"Additional command line options$1
",
$Log
);
$Log = preg_replace(
'/( *)Additional command line options( +): (.+)\n/i',
'Additional command line options$2: $3' . "\n",
$Log,
1,
$Count
);
// xld album gain
$Log = preg_replace(
"/All Tracks\s*\n(\s*Album gain\s+:) (.*)?\n(\s*Peak\s+:) (.*)?/i",
"All Tracks\n$1 $2\n" .
"$3 $4",
$Log,
1,
$Count
);
if (!$Count && $XLD) {
$this->account('Could not verify album gain');
}
// pre-0.99
$Log = preg_replace(
'/Other options( +):/i',
'Other options$1:',
$Log,
1,
$Count
);
$Log = preg_replace(
'/\n( *)Native Win32 interface(.+)/i',
"\n$1Native Win32 interface$2",
$Log,
1,
$Count
);
// 0.99
$Log = str_replace(
'TOC of the extracted CD',
'TOC of the extracted CD',
$Log
);
$Log = preg_replace(
'/( +)Track( +)\|( +)Start( +)\|( +)Length( +)\|( +)Start sector( +)\|( +)End sector( ?)/i',
'$0',
$Log
);
$Log = preg_replace('/-{10,100}/', '$0', $Log);
$Log = preg_replace_callback(
'/( +)([0-9]{1,3})( +)\|( +)(([0-9]{1,3}:)?[0-9]{2}[\.:][0-9]{2})( +)\|' .
'( +)(([0-9]{1,3}:)?[0-9]{2}[\.:][0-9]{2})( +)\|( +)([0-9]{1,10})( +)\|' .
'( +)([0-9]{1,10})( +)\n/i',
[
$this,
'toc'
],
$Log
);
$Log = str_replace(
'None of the tracks are present in the AccurateRip database',
'None of the tracks are present in the AccurateRip database',
$Log
);
$Log = str_replace(
'Disc not found in AccurateRip DB.',
'Disc not found in AccurateRip DB.',
$Log
);
$Log = preg_replace('/No errors occurr?ed/i', 'No errors occurred', $Log);
$Log = preg_replace("/(There were errors) ?\n/i", "$1\n", $Log);
$Log = preg_replace("/(Some inconsistencies found) ?\n/i", "$1\n", $Log);
$Log = preg_replace('/End of status report/i', 'End of status report', $Log);
$Log = preg_replace(
'/Track(\s*)Ripping Status(\s*)\[Disc ID: ([0-9a-f]{8}-[0-9a-f]{8})\]/i',
'Track$1Ripping Status$2Disc ID: ' .
'$3',
$Log
);
$Log = preg_replace('/(All Tracks Accurately Ripped\.?)/i', '$1', $Log);
$Log = preg_replace("/\d+ track.* +accurately ripped\.? *\n/i", '$0', $Log);
$Log = preg_replace(
"/\d+ track.* +not present in the AccurateRip database\.? *\n/i",
'$0',
$Log
);
$Log = preg_replace("/\d+ track.* +canceled\.? *\n/i", '$0', $Log);
$Log = preg_replace(
"/\d+ track.* +could not be verified as accurate\.? *\n/i",
'$0',
$Log
);
$Log = preg_replace(
"/Some tracks could not be verified as accurate\.? *\n/i",
'$0',
$Log
);
$Log = preg_replace(
"/No tracks could be verified as accurate\.? *\n/i",
'$0',
$Log
);
$Log = preg_replace("/You may have a different pressing.*\n/i", '$0', $Log);
//xld accurip summary
$Log = preg_replace_callback("/(Track +\d+ +: +)(OK +)\(A?R?\d?,? ?confidence +(\d+).*?\)(.*)\n/i", array(
$this,
'arSummaryConfXld'
), $Log);
$Log = preg_replace_callback("/(Track +\d+ +: +)(NG|Not Found).*?\n/i", array(
$this,
'arSummaryConfXld'
), $Log);
$Log = preg_replace( //Status line
"/( *.{2} ?)(\d+ track\(s\).*)\n/i",
"$1$2\n",
$Log,
1
);
//(..) may need additional entries
//accurip summary (range)
$Log = preg_replace(
"/\n( *AccurateRip summary\.?)/i",
"\n$1",
$Log
);
$Log = preg_replace_callback(
"/(Track +\d+ +.*?accurately ripped\.? *)(\(confidence +)(\d+)\)(.*)\n/i",
[
$this,
'arSummaryConf'
],
$Log
);
$Log = preg_replace(
"/(Track +\d+ +.*?in database *)\n/i",
"$1\n",
$Log,
-1,
$Count
);
if ($Count) {
$this->ARSummary['bad'] = $Count;
}
$Log = preg_replace(
"/(Track +\d+ +.*?(could not|cannot) be verified as accurate.*)\n/i",
"$1\n",
$Log,
-1,
$Count
);
if ($Count) {
$this->ARSummary['bad'] = $Count;
} //don't mind the actual count
//range rip
$Log = preg_replace("/\n( *Selected range)/i", "\n$1", $Log, 1, $Range1);
$Log = preg_replace(
'/\n( *Range status and errors)/i',
"\n$1",
$Log,
1,
$Range2
);
if ($Range1 || $Range2) {
$this->Range = 1;
$this->account('Range rip detected', 30);
}
$FormattedTrackListing = '';
//------ Handle individual tracks ------//
if (!$this->Range) {
preg_match('/\nTrack( +)([0-9]{1,3})([^<]+)/i', $Log, $Matches);
$TrackListing = $Matches[0];
$FullTracks = preg_split('/\nTrack( +)([0-9]{1,3})/i', $TrackListing, -1, PREG_SPLIT_DELIM_CAPTURE);
array_shift($FullTracks);
$TrackBodies = preg_split('/\nTrack( +)([0-9]{1,3})/i', $TrackListing, -1);
array_shift($TrackBodies);
//------ Range rip ------//
} else {
preg_match('/\n( +)Filename +(.*)([^<]+)/i', $Log, $Matches);
$TrackListing = $Matches[0];
$FullTracks = preg_split('/\n( +)Filename +(.*)/i', $TrackListing, -1, PREG_SPLIT_DELIM_CAPTURE);
array_shift($FullTracks);
$TrackBodies = preg_split('/\n( +)Filename +(.*)/i', $TrackListing, -1);
array_shift($TrackBodies);
}
$Tracks = array();
foreach ($TrackBodies as $Key => $TrackBody) {
// The number of spaces between 'Track' and the number, to keep formatting intact
$Spaces = $FullTracks[($Key * 3)];
// Track number
$TrackNumber = $FullTracks[($Key * 3) + 1];
$this->TrackNumber = $TrackNumber;
// How much to decrease the overall score by, if this track fails and
// no attempt at recovery is made later on
$this->DecreaseScoreTrack = 0;
// List of things that went wrong to add to $this->Bad if this track fails and
// no attempt at recovery is made later on
$this->BadTrack = array();
// The track number is stripped in the preg_split, let's bring it back, eh?
if (!$this->Range) {
$TrackBody = 'Track' . $Spaces . '' .
$TrackNumber . '' . $TrackBody;
} else {
$TrackBody = $Spaces . 'Filename ' .
$TrackNumber . '' . $TrackBody;
}
/* match newline for xld multifile encodes */
$TrackBody = preg_replace(
'/Filename ((.+)?\.(wav|flac|ape))\n/is',
"Filename $1\n",
$TrackBody,
-1,
$Count
);
if (!$Count && !$this->Range) {
$this->accountTrack('Could not verify filename', 1);
}
// xld track gain
$TrackBody = preg_replace(
"/( *Track gain\s+:) (.*)?\n(\s*Peak\s+:) (.*)?/i",
"$1 $2\n$3 $4",
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
'/( +)(Statistics *)\n/i',
"$1$2\n",
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace_callback('/(Read error)( +:) (\d+)/i', array(
$this,
'xldStat'
), $TrackBody, -1, $Count);
if (!$Count && $XLD) {
$this->accountTrack('Could not verify read errors');
}
$TrackBody = preg_replace_callback('/(Skipped \(treated as error\))( +:) (\d+)/i', array(
$this,
'xldStat'
), $TrackBody, -1, $Count);
if (!$Count && $XLD && !$this->XLDSecureRipper) {
$this->accountTrack('Could not verify skipped errors');
}
$TrackBody = preg_replace_callback('/(Edge jitter error \(maybe fixed\))( +:) (\d+)/i', array(
$this,
'xldStat'
), $TrackBody, -1, $Count);
if (!$Count && $XLD && !$this->XLDSecureRipper) {
$this->accountTrack('Could not verify edge jitter errors');
}
$TrackBody = preg_replace_callback('/(Atom jitter error \(maybe fixed\))( +:) (\d+)/i', array(
$this,
'xldStat'
), $TrackBody, -1, $Count);
if (!$Count && $XLD && !$this->XLDSecureRipper) {
$this->accountTrack('Could not verify atom jitter errors');
}
$TrackBody = preg_replace_callback( //xld secure ripper
'/(Jitter error \(maybe fixed\))( +:) (\d+)/i',
array(
$this,
'xldStat'
),
$TrackBody,
-1,
$Count
);
if (!$Count && $XLD && $this->XLDSecureRipper) {
$this->accountTrack('Could not verify jitter errors');
}
$TrackBody = preg_replace_callback( //xld secure ripper
'/(Retry sector count)( +:) (\d+)/i',
array(
$this,
'xldStat'
),
$TrackBody,
-1,
$Count
);
if (!$Count && $XLD && $this->XLDSecureRipper) {
$this->accountTrack('Could not verify retry sector count');
}
$TrackBody = preg_replace_callback( //xld secure ripper
'/(Damaged sector count)( +:) (\d+)/i',
array(
$this,
'xldStat'
),
$TrackBody,
-1,
$Count
);
if (!$Count && $XLD && $this->XLDSecureRipper) {
$this->accountTrack('Could not verify damaged sector count');
}
$TrackBody = preg_replace_callback('/(Drift error \(maybe fixed\))( +:) (\d+)/i', array(
$this,
'xldStat'
), $TrackBody, -1, $Count);
if (!$Count && $XLD && !$this->XLDSecureRipper) {
$this->accountTrack('Could not verify drift errors');
}
$TrackBody = preg_replace_callback('/(Dropped bytes error \(maybe fixed\))( +:) (\d+)/i', array(
$this,
'xldStat'
), $TrackBody, -1, $Count);
if (!$Count && $XLD && !$this->XLDSecureRipper) {
$this->accountTrack('Could not verify dropped bytes errors');
}
$TrackBody = preg_replace_callback(
'/(Duplicated bytes error \(maybe fixed\))( +:) (\d+)/i',
[
$this,
'xldStat'
],
$TrackBody,
-1,
$Count
);
if (!$Count && $XLD && !$this->XLDSecureRipper) {
$this->accountTrack('Could not verify duplicated bytes errors');
}
$TrackBody = preg_replace_callback('/(Inconsistency in error sectors)( +:) (\d+)/i', array(
$this,
'xldStat'
), $TrackBody, -1, $Count);
if (!$Count && $XLD && !$this->XLDSecureRipper) {
$this->accountTrack('Could not verify inconsistent error sectors');
}
$TrackBody = preg_replace(
"/(List of suspicious positions +)(: *\n?)(( *.* +\d{2}:\d{2}:\d{2} *\n)+)/i",
'$1$2$3',
$TrackBody,
-1,
$Count
);
if ($Count) {
$this->accountTrack('Suspicious position(s) found', 20);
}
$TrackBody = preg_replace(
'/Suspicious position( +)([0-9]:[0-9]{2}:[0-9]{2})/i',
'Suspicious position$1$2',
$TrackBody,
-1,
$Count
);
if ($Count) {
$this->accountTrack('Suspicious position(s) found', 20);
}
$TrackBody = preg_replace(
'/Timing problem( +)([0-9]:[0-9]{2}:[0-9]{2})/i',
'Timing problem$1$2',
$TrackBody,
-1,
$Count
);
if ($Count) {
$this->accountTrack('Timing problem(s) found', 20);
}
$TrackBody = preg_replace(
'/Missing samples/i',
'Missing samples',
$TrackBody,
-1,
$Count
);
if ($Count) {
$this->accountTrack('Missing sample(s) found', 20);
}
$TrackBody = preg_replace(
'/Copy aborted/i',
'Copy aborted',
$TrackBody,
-1,
$Count
);
if ($Count) {
$Aborted = true;
$this->accountTrack('Copy aborted', 100);
} else {
$Aborted = false;
}
$TrackBody = preg_replace(
'/Pre-gap length( +|\s+:\s+)([0-9]{1,2}:[0-9]{2}:[0-9]{2}.?[0-9]{0,2})/i',
'Pre-gap length$1$2',
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
'/Peak level ([0-9]{1,3}\.[0-9] %)/i',
'Peak level $1',
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
'/Extraction speed ([0-9]{1,3}\.[0-9]{1,} X)/i',
'Extraction speed $1',
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
'/Track quality ([0-9]{1,3}\.[0-9] %)/i',
'Track quality $1',
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
'/Range quality\s+([0-9]{1,3}\.[0-9] %)/i',
'Range quality $1',
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
'/CRC32 hash \(skip zero\)(\s*:) ([0-9A-F]{8})/i',
'CRC32 hash (skip zero)$1 $2',
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace_callback(
'/Test CRC ([0-9A-F]{8})\n(\s*)Copy CRC ([0-9A-F]{8})/i',
[
$this,
'testCopy'
],
$TrackBody,
-1,
$EACTC
);
$TrackBody = preg_replace_callback(
'/CRC32 hash \(test run\)(\s*:) ([0-9A-F]{8})\n(\s*)CRC32 hash(\s+:) ([0-9A-F]{8})/i',
[
$this,
'testCopy'
],
$TrackBody,
-1,
$XLDTC
);
if (!$EACTC && !$XLDTC && !$Aborted) {
$this->account('Test and copy was not used', 10);
if (!$this->SecureMode) {
if ($EAC) {
$Msg = 'Rip was not done in Secure mode, and T+C was not used - as a result, ' .
'we cannot verify the authenticity of the rip (-40 points)';
} else {
$Msg = 'Rip was not done with Secure Ripper / in CDParanoia mode, and T+C was not used ' .
'- as a result, we cannot verify the authenticity of the rip (-40 points)';
}
if (!in_array($Msg, $this->Details)) {
$this->Score -= 40;
$this->Details[] = $Msg;
}
}
}
$TrackBody = preg_replace(
'/Copy CRC ([0-9A-F]{8})/i',
'Copy CRC $1',
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
'/CRC32 hash(\s*:) ([0-9A-F]{8})/i',
'CRC32 hash$1 $2',
$TrackBody,
-1,
$Count
);
$TrackBody = str_replace(
'Track not present in AccurateRip database',
'Track not present in AccurateRip database',
$TrackBody
);
$TrackBody = preg_replace(
'/Accurately ripped( +)\(confidence ([0-9]+)\)( +)(\[[0-9A-F]{8}\])/i',
'Accurately ripped$1(confidence $2)$3$4',
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
"/Cannot be verified as accurate +\(.*/i",
'$0',
$TrackBody,
-1,
$Count
);
//xld ar
$TrackBody = preg_replace_callback(
'/AccurateRip signature( +): ([0-9A-F]{8})\n(.*?)(Accurately ripped\!?)' .
'( +\(A?R?\d?,? ?confidence )([0-9]+\))/i',
[
$this,
'arXld'
],
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
'/AccurateRip signature( +): ([0-9A-F]{8})\n(.*?)(Rip may not be accurate\.?)(.*?)/i',
"AccurateRip signature$1: $2\n" .
"$3$4$5",
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
'/(Rip may not be accurate\.?)(.*?)/i',
"$1$2",
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
'/AccurateRip signature( +): ([0-9A-F]{8})\n(.*?)' .
'(Track not present in AccurateRip database\.?)(.*?)/i',
"AccurateRip signature$1: $2\n" .
"$3$4$5",
$TrackBody,
-1,
$Count
);
$TrackBody = preg_replace(
"/\(matched[ \w]+;\n *calculated[ \w]+;\n[ \w]+signature[ \w:]+\)/i",
"$0",
$TrackBody,
-1,
$Count
);
//ar track + conf
preg_match('/Accurately ripped\!? +\(A?R?\d?,? ?confidence ([0-9]+)\)/i', $TrackBody, $matches);
if ($matches) {
$this->ARTracks[$TrackNumber] = $matches[1];
} else {
$this->ARTracks[$TrackNumber] = 0;
} //no match - no boost
$TrackBody = str_replace('Copy finished', 'Copy finished', $TrackBody);
$TrackBody = preg_replace('/Copy OK/i', 'Copy OK', $TrackBody, -1, $Count);
$Tracks[$TrackNumber] = array(
'number' => $TrackNumber,
'spaces' => $Spaces,
'text' => $TrackBody,
'decreasescore' => $this->DecreaseScoreTrack,
'bad' => $this->BadTrack
);
$FormattedTrackListing .= "\n" . $TrackBody;
$this->Tracks[$LogArrayKey][$TrackNumber] = $Tracks[$TrackNumber];
}
unset($Tracks);
$Log = str_replace($TrackListing, $FormattedTrackListing, $Log);
$Log = str_replace('
', "\n", $Log);
//xld all tracks statistics
$Log = preg_replace('/( +)?(All tracks *)\n/i', "$1$2\n", $Log, 1);
$Log = preg_replace('/( +)(Statistics *)\n/i', "$1$2\n", $Log, 1);
$Log = preg_replace_callback('/(Read error)( +:) (\d+)/i', array(
$this,
'xldAllStat'
), $Log, 1);
$Log = preg_replace_callback('/(Skipped \(treated as error\))( +:) (\d+)/i', array(
$this,
'xldAllStat'
), $Log, 1);
$Log = preg_replace_callback('/(Jitter error \(maybe fixed\))( +:) (\d+)/i', array(
$this,
'xldAllStat'
), $Log, 1);
$Log = preg_replace_callback('/(Edge jitter error \(maybe fixed\))( +:) (\d+)/i', array(
$this,
'xldAllStat'
), $Log, 1);
$Log = preg_replace_callback('/(Atom jitter error \(maybe fixed\))( +:) (\d+)/i', array(
$this,
'xldAllStat'
), $Log, 1);
$Log = preg_replace_callback('/(Drift error \(maybe fixed\))( +:) (\d+)/i', array(
$this,
'xldAllStat'
), $Log, 1);
$Log = preg_replace_callback('/(Dropped bytes error \(maybe fixed\))( +:) (\d+)/i', array(
$this,
'xldAllStat'
), $Log, 1);
$Log = preg_replace_callback('/(Duplicated bytes error \(maybe fixed\))( +:) (\d+)/i', array(
$this,
'xldAllStat'
), $Log, 1);
$Log = preg_replace_callback('/(Retry sector count)( +:) (\d+)/i', array(
$this,
'xldAllStat'
), $Log, 1);
$Log = preg_replace_callback('/(Damaged sector count)( +:) (\d+)/i', array(
$this,
'xldAllStat'
), $Log, 1);
//end xld all tracks statistics
$this->logs[$LogArrayKey] = $Log;
$this->checkTracks($LogArrayKey);
if ($this->NonSecureMode) { #non-secure mode
$this->account($this->NonSecureMode . ' mode was used', 20);
}
$this->ARTracks = array();
$this->ARSummary = array();
$this->SecureMode = true;
$this->NonSecureMode = null;
} //end log loop
$FinalTracks = [];
foreach ($this->Tracks as $LogArrayKey => $Tracks) {
foreach ($Tracks as $Number => $Track) {
$FinalTracks[$Number] = $Track;
}
}
foreach ($FinalTracks as $Track) {
if ($Track['decreasescore']) {
$this->Score -= $Track['decreasescore'];
}
if (count($Track['bad']) > 0) {
$this->Details = array_merge($this->Details, $Track['bad']);
}
}
unset($FinalTracks);
unset($this->Tracks);
$this->log = implode($this->logs);
if (strlen($this->log) === 0) {
$this->Score = 0;
$this->account('Unrecognized log file! Feel free to report for manual review.');
}
if ($this->Combined) {
array_unshift($this->Details, "Combined Log (" . $this->Combined . ")");
} //combined log msg
}
// Callback functions
private function drive($Matches)
{
if (in_array(trim($Matches[2]), $this->FakeDrives)) {
$this->account('Virtual drive used: ' . $Matches[2], 20, false, false, false);
return "Used Drive$Matches[1]: $Matches[2]";
}
$DriveName = $Matches[2];
$this->getDrives($DriveName);
if (count($this->Drives) > 0) {
$Class = 'good';
$this->DriveFound = true;
} else {
$Class = 'badish';
$Matches[2] .= ' (not found in database)';
}
return "Used Drive$Matches[1]: $Matches[2]";
}
private function getDrives($DriveName)
{
// Necessary transformations to get what the drives report themselves to match up into
// what is from the AccurateRIP DB
$DriveName = str_replace('JLMS', 'Lite-ON', $DriveName);
$DriveName = preg_replace('/TSSTcorp(BD|CD|DVD)/', 'TSSTcorp \1', $DriveName);
$DriveName = preg_replace('/HL-DT-ST(BD|CD|DVD)/', 'HL-DT-ST \1', $DriveName);
$DriveName = str_replace('HL-DT-ST', 'LG Electronics', $DriveName);
$DriveName = str_replace(array('Matshita', 'MATSHITA'), 'Panasonic', $DriveName);
$DriveName = preg_replace('/\s+-\s/', ' ', $DriveName);
$DriveName = preg_replace('/\s+/', ' ', $DriveName);
$DriveName = preg_replace('/\(revision [a-zA-Z0-9\.\,\-]*\)/', '', $DriveName);
$DriveName = preg_replace('/ Adapter.*$/', '', $DriveName);
$DriveName = strtolower($DriveName);
$MatchedDrives = [];
for ($i = 0; $i < 5; $i++) {
$MatchedDrives[$i] = ['drives' => [], 'offsets' => []];
}
foreach ($this->AllDrives as [$Drive, $Offset]) {
$Distance = levenshtein($Drive, $DriveName);
if ($Distance < 5) {
$MatchedDrives[$Distance]['drives'][] = $Drive;
$MatchedDrives[$Distance]['offsets'][] = preg_replace('/[^0-9]/s', '', (string) $Offset);
}
}
foreach ($MatchedDrives as $Match) {
if (count($Match['drives']) > 0) {
$this->Drives = $Match['drives'];
$this->Offsets = $Match['offsets'];
break;
}
}
}
private function mediaTypeXld($Matches)
{
// Pressed CD
if (trim($Matches[2]) == "Pressed CD") {
$Class = 'good';
} else { // CD-R etc.; not necessarily "bad" (e.g. commercial CD-R)
$Class = 'badish';
$this->account('Not a pressed cd', false, false, true, true);
}
return "Media type$Matches[1]: $Matches[2]";
}
private function readMode($Matches)
{
if ($Matches[2] == 'Secure') {
$Class = 'good';
} else {
$this->SecureMode = false;
$this->NonSecureMode = $Matches[2];
$Class = 'bad';
}
$Str = 'Read mode' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
if ($Matches[3]) {
$Str .= '' . $Matches[3] . '';
}
return $Str;
}
private function cdparanoiaModeXld($Matches)
{
if (substr($Matches[2], 0, 3) == 'YES') {
$Class = 'good';
} else {
$this->SecureMode = false;
$Class = 'bad';
}
return 'Use cdparanoia mode' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
}
private function ripperModeXld($Matches)
{
if (substr($Matches[2], 0, 10) == 'CDParanoia') {
$Class = 'good';
} elseif ($Matches[2] == "XLD Secure Ripper") {
$Class = 'good';
$this->XLDSecureRipper = true;
} else {
$this->SecureMode = false;
$Class = 'bad';
}
return 'Ripper mode' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
}
private function arXld($Matches)
{
if (strpos(strtolower($Matches[4]), 'accurately ripped') != -1) {
$conf = substr($Matches[6], 0, -1);
if ((int) $conf < 2) {
$Class = 'goodish';
} else {
$Class = 'good';
}
} else {
$Class = 'badish';
}
return "AccurateRip signature$Matches[1]: " .
"$Matches[2]\n" .
"$Matches[3]$Matches[4]$Matches[5]$Matches[6]";
}
private function arSummaryConfXld($Matches)
{
if (strtolower(trim($Matches[2])) == 'ok') {
if ($Matches[3] < 2) {
$Class = 'goodish';
} else {
$Class = 'good';
}
} else {
$Class = 'badish';
}
return "$Matches[1]" . substr($Matches[0], strlen($Matches[1])) . "";
}
private function arSummaryConf($Matches)
{
if ($Matches[3] < 2) {
$Class = 'goodish';
$this->ARSummary['goodish'][] = $Matches[3];
} else {
$Class = 'good';
$this->ARSummary['good'][] = $Matches[3];
}
return "$Matches[0]";
}
private function maxRetryCount($Matches)
{
if ($Matches[2] >= 10) {
$Class = 'goodish';
} else {
$Class = 'badish';
$this->account('Low "max retry count" (potentially bad setting)');
}
return 'Max retry count' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
}
private function accurateStream($Matches)
{
if ($Matches[2] == 'Yes') {
$Class = 'good';
} else {
$Class = 'bad';
$this->account('"Utilize accurate stream" should be yes', 20);
}
return 'Utilize accurate stream' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
}
private function accurateStreamEacPre9($Matches)
{
if (strtolower($Matches[1]) != 'no ') {
$Class = 'good';
} else {
$Class = 'bad';
$this->account('"accurate stream" should be yes', 20);
}
return ', ' . $Matches[1] . 'accurate stream';
}
private function defeatAudioCache($Matches)
{
if ($Matches[2] == 'Yes') {
$Class = 'good';
} else {
$Class = 'bad';
$this->account('"Defeat audio cache" should be yes', 10);
}
return 'Defeat audio cache' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
}
private function defeatAudioCacheEacPre99($Matches)
{
if (strtolower($Matches[1]) != 'no ') {
$Class = 'good';
} else {
$Class = 'bad';
$this->account('Audio cache not disabled', 10);
}
return ' ' . $Matches[1] . 'disable cache';
}
private function defeatAudioCacheXld($Matches)
{
if (substr($Matches[2], 0, 2) == 'OK' || substr($Matches[2], 0, 3) == 'YES') {
$Class = 'good';
} else {
$Class = 'bad';
$this->account('"Disable audio cache" should be yes/ok', 10);
}
return 'Disable audio cache' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
}
private function c2Pointers($Matches)
{
if (strtolower($Matches[2]) == 'yes') {
$Class = 'bad';
$this->account('C2 pointers were used', 10);
} else {
$Class = 'good';
}
return 'Make use of C2 pointers' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
}
private function c2PointersEacPre99($Matches)
{
if (strtolower($Matches[1]) == 'no ') {
$Class = 'good';
} else {
$Class = 'bad';
$this->account('C2 pointers were used', 10);
}
return 'with ' . $Matches[1] . 'C2';
}
private function readOffset($Matches)
{
if ($this->DriveFound == true) {
if (in_array($Matches[2], $this->Offsets)) {
$Class = 'good';
} else {
$Class = 'bad';
$Msg = 'Incorrect read offset for drive. Correct offsets are: ' . implode(', ', $this->Offsets) .
' (Checked against the following drive(s): ' . implode(', ', $this->Drives) . ')';
$this->account($Msg, 5, false, false, false);
}
} else {
if ($Matches[2] == 0) {
$Class = 'bad';
$Msg = 'The drive was not found in the database, so we cannot determine the correct read offset. ' .
'However, the read offset in this case was 0, which is almost never correct. As such, we are ' .
'assuming that the offset is incorrect';
$this->account($Msg, 5, false, false, false);
} else {
$Class = 'badish';
}
}
return 'Read offset correction' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
}
private function fillOffsetSamples($Matches)
{
if ($Matches[2] == 'Yes') {
$Class = 'good';
} else {
$Class = 'bad';
$this->account('Does not fill up missing offset samples with silence', 5, false, false, false);
}
return 'Fill up missing offset samples with silence' . $Matches[1] .
': ' . $Matches[2] . '';
}
private function deleteSilentBlocks($Matches)
{
if ($Matches[2] == 'Yes') {
$Class = 'bad';
$this->account('Deletes leading and trailing silent blocks', 5, false, false, false);
} else {
$Class = 'good';
}
return 'Delete leading and trailing silent blocks' .
$Matches[1] . $Matches[2] . ': ' . $Matches[3] . '';
}
private function nullSamples($Matches)
{
if ($Matches[2] == 'Yes') {
$Class = 'good';
} else {
$Class = 'bad';
$this->account('Null samples should be used in CRC calculations', 5);
}
return 'Null samples used in CRC calculations' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
}
private function gapHandling($Matches)
{
if (strpos($Matches[2], 'Not detected') !== false) {
$Class = 'bad';
$this->account('Gap handling was not detected', 10);
} elseif (strpos($Matches[2], 'Appended to next track') !== false) {
$Class = 'bad';
$this->account('Gap handling should be appended to previous track');
} elseif (strpos($Matches[2], 'Appended to previous track') !== false) {
$Class = 'good';
} else {
$Class = 'goodish';
}
return 'Gap handling' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
}
private function gapHandlingXld($Matches)
{
if (strpos(strtolower($Matches[2]), 'not') !== false) { //?
$Class = 'bad';
$this->account('Incorrect gap handling', 10, false, false, false);
} elseif (
strpos(strtolower($Matches[2]), 'analyzed') !== false
&& strpos(strtolower($Matches[2]), 'appended') !== false
) {
$Class = 'good';
} else {
$Class = 'badish';
$this->account('Incomplete gap handling', 10, false, false, false);
}
return 'Gap status' . $Matches[1] . ': ' .
'' . $Matches[2] . '';
}
private function addId3Tag($Matches)
{
if ($Matches[2] == 'Yes') {
$Class = 'badish';
$this->account(
'ID3 tags should not be added to FLAC files - they are mainly for MP3 files. ' .
'FLACs should have vorbis comments for tags instead.',
1
);
} else {
$Class = 'good';
}
return 'Add ID3 tag' . $Matches[1] . ': ' . $Matches[2] . '';
}
private function testCopy($Matches)
{
if ($this->ripper == "EAC") {
if ($Matches[1] == $Matches[3]) {
$Class = 'good';
} else {
$Class = 'bad';
$this->accountTrack("CRC mismatch: $Matches[1] and $Matches[3]", 30);
if (!$this->SecureMode) {
$this->DecreaseScoreTrack += 20;
$this->BadTrack[] = 'Rip ' . (($this->Combined) ? " (" . $this->CurrLog . ") " : '') . 'was not ' .
'done in Secure mode, and experienced CRC mismatches (-20 points)';
$this->SecureMode = true;
}
}
return "Test CRC $Matches[1]\n" .
"$Matches[2]Copy CRC $Matches[3]";
} elseif ($this->ripper == "XLD") {
if ($Matches[2] == $Matches[5]) {
$Class = 'good';
} else {
$Class = 'bad';
$this->accountTrack("CRC mismatch: $Matches[2] and $Matches[5]", 30);
if (!$this->SecureMode) {
$this->DecreaseScoreTrack += 20;
$this->BadTrack[] = 'Rip ' . (($this->Combined) ? " (" . $this->CurrLog . ") " : '') . 'was not ' .
'done with Secure Ripper / in CDParanoia mode, and experienced CRC mismatches (-20 points)';
$this->SecureMode = true;
}
}
return "CRC32 hash (test run)$Matches[1] " .
"$Matches[2]\n$Matches[3] " .
"CRC32 hash$Matches[4] $Matches[5]";
}
}
private function xldAllStat($Matches)
{
$Text = $Matches[1] . $Matches[2];
if (
strtolower($Matches[1]) == 'read error'
|| strtolower($Matches[1]) == 'skipped (treated as error)'
|| strtolower($Matches[1]) == 'inconsistency in error sectors'
|| strtolower($Matches[1]) == 'damaged sector count'
) {
if ($Matches[3] == 0) {
$Class = 'good';
} else {
$Class = 'bad';
}
return '' . $Text . ' ' . $Matches[3] . '';
}
if (
strtolower($Matches[1]) == 'retry sector count'
|| strtolower($Matches[1]) == 'jitter error (maybe fixed)'
|| strtolower($Matches[1]) == 'edge jitter error (maybe fixed)'
|| strtolower($Matches[1]) == 'atom jitter error (maybe fixed)'
|| strtolower($Matches[1]) == 'drift error (maybe fixed)'
|| strtolower($Matches[1]) == 'dropped bytes error (maybe fixed)'
|| strtolower($Matches[1]) == 'duplicated bytes error (maybe fixed)'
) {
if ($Matches[3] == 0) {
$Class = 'goodish';
} else {
$Class = 'badish';
}
return '' . $Text . ' ' . $Matches[3] . '';
}
}
private function xldStat($Matches)
{
$Text = $Matches[1] . $Matches[2];
if (strtolower($Matches[1]) == 'read error') {
if ($Matches[3] == 0) {
$Class = 'good';
} else {
$Class = 'bad';
$err = ($Matches[3] > 10) ? 10 : $Matches[3]; //max.
$this->accountTrack('Read error' . ($Matches[3] == 1 ? '' : 's') . ' detected', $err);
}
return '' . $Text . ' ' . $Matches[3] . '';
}
if (strtolower($Matches[1]) == 'skipped (treated as error)') {
if ($Matches[3] == 0) {
$Class = 'good';
} else {
$Class = 'bad';
$err = ($Matches[3] > 10) ? 10 : $Matches[3]; //max.
$this->accountTrack('Skipped error' . ($Matches[3] == 1 ? '' : 's') . ' detected', $err);
}
return '' . $Text . ' ' . $Matches[3] . '';
}
if (strtolower($Matches[1]) == 'inconsistency in error sectors') {
if ($Matches[3] == 0) {
$Class = 'good';
} else {
$Class = 'bad';
$err = ($Matches[3] > 10) ? 10 : $Matches[3]; //max.
$this->accountTrack(
'Inconsistenc' . (($Matches[3] == 1) ? 'y' : 'ies') . ' in error sectors detected',
$err
);
}
return '' . $Text . ' ' . $Matches[3] . '';
}
if (strtolower($Matches[1]) == 'damaged sector count') { //xld secure ripper
if ($Matches[3] == 0) {
$Class = 'good';
} else {
$Class = 'bad';
$err = ($Matches[3] > 10) ? 10 : $Matches[3]; //max.
$this->accountTrack('Damaged sector count of ' . ($Matches[3]), $err);
}
return '' . $Text . ' ' . $Matches[3] . '';
}
if (
strtolower($Matches[1]) == 'retry sector count'
|| strtolower($Matches[1]) == 'jitter error (maybe fixed)'
|| strtolower($Matches[1]) == 'edge jitter error (maybe fixed)'
|| strtolower($Matches[1]) == 'atom jitter error (maybe fixed)'
|| strtolower($Matches[1]) == 'drift error (maybe fixed)'
|| strtolower($Matches[1]) == 'dropped bytes error (maybe fixed)'
|| strtolower($Matches[1]) == 'duplicated bytes error (maybe fixed)'
) {
if ($Matches[3] == 0) {
$Class = 'goodish';
} else {
$Class = 'badish';
}
return '' . $Text . ' ' . $Matches[3] . '';
}
}
private function toc($Matches)
{
return "$Matches[1]$Matches[2]$Matches[3]|" .
"$Matches[4]$Matches[5]$Matches[7]|" .
"$Matches[8]$Matches[9]$Matches[11]|" .
"$Matches[12]$Matches[13]$Matches[14]|" .
"$Matches[15]$Matches[16]$Matches[17]" . "\n";
}
private function checkTracks($LogArrayKey)
{
if (count($this->Tracks[$LogArrayKey]) === 0) { //no tracks
unset($this->Details);
if ($this->Combined) {
$this->Details[] = "Combined Log (" . $this->Combined . ")";
$this->Details[] = "Invalid log (" . $this->CurrLog . "), no tracks!";
} else {
$this->Details[] = "Invalid log, no tracks!";
}
$this->Score = 0;
}
}
private function account(
$Msg,
$Decrease = false,
$Score = false,
$InclCombined = false,
$Notice = false
) {
$DecreaseScore = $SetScore = false;
$Append2 = '';
$Append1 = ($InclCombined) ? (($this->Combined) ? " (" . $this->CurrLog . ")" : '') : '';
$Prepend = ($Notice) ? '[Notice] ' : '';
if ($Decrease) {
$DecreaseScore = true;
$Append2 = ($Decrease > 0) ? ' (-' . $Decrease . ' point' . ($Decrease == 1 ? '' : 's') . ')' : '';
} elseif ($Score || $Score === 0) {
$SetScore = true;
$Decrease = 100 - $Score;
$Append2 = ($Decrease > 0) ? ' (-' . $Decrease . ' point' . ($Decrease == 1 ? '' : 's') . ')' : '';
}
if (!in_array($Prepend . $Msg . $Append1 . $Append2, $this->Details)) {
$this->Details[] = $Prepend . $Msg . $Append1 . $Append2;
if ($DecreaseScore) {
$this->Score -= $Decrease;
}
if ($SetScore) {
$this->Score = $Score;
}
}
}
private function accountTrack($Msg, $Decrease = false)
{
$tn = (intval($this->TrackNumber) < 10) ? '0' . intval($this->TrackNumber) : $this->TrackNumber;
$Append = '';
if ($Decrease) {
$this->DecreaseScoreTrack += $Decrease;
$Append = ' (-' . $Decrease . ' point' . ($Decrease == 1 ? '' : 's') . ')';
}
$Prepend = 'Track ' . $tn . (($this->Combined) ? " (" . $this->CurrLog . ")" : '') . ': ';
$this->BadTrack[] = $Prepend . $Msg . $Append;
}
public function getRipper()
{
return $this->ripper;
}
public function getRipperVersion()
{
return $this->ripperVersion;
}
public function getScore(): int
{
return $this->Score;
}
public function getDetails(): array
{
return $this->Details;
}
public function getChecksumState(): string
{
return $this->checksumStatus;
}
public function getLanguage(): string
{
return $this->language;
}
public static function getAcceptValues(): string
{
return ".txt,.TXT,.log,.LOG";
}
public static function getLogcheckerVersion(): string
{
$composer = json_decode(
file_get_contents(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', 'composer.json'])),
true
);
return $composer['version'];
}
}