| @@ -27,12 +27,12 @@ before_install: | |||||
| install: | install: | ||||
| - composer install --ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress | - composer install --ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress | ||||
| - pip3 install --user chardet xld_logchecker eac_logchecker | - pip3 install --user chardet xld_logchecker eac_logchecker | ||||
| script: | script: | ||||
| - bin/logchecker --version | - bin/logchecker --version | ||||
| - bin/logchecker --help | - bin/logchecker --help | ||||
| - bin/logchecker tests/logs/wgdbcm.log | |||||
| - bin/logchecker tests/logs/xld_perfect.log | |||||
| - bin/logchecker analyze tests/logs/wgdbcm.log | |||||
| - bin/logchecker analyze tests/logs/xld_perfect.log | |||||
| before_deploy: | before_deploy: | ||||
| - php -d phar.readonly=0 bin/compile | - php -d phar.readonly=0 bin/compile | ||||
| @@ -46,4 +46,4 @@ deploy: | |||||
| on: | on: | ||||
| tags: true | tags: true | ||||
| repo: OPSnet/Logchecker | repo: OPSnet/Logchecker | ||||
| php: '7.2' | |||||
| php: '7.2' | |||||
| @@ -1,7 +1,7 @@ | |||||
| { | { | ||||
| "name": "orpheusnet/logchecker", | "name": "orpheusnet/logchecker", | ||||
| "description": "Logchecker for validating logs generated from supported ripping programs (like EAC and XLD)", | "description": "Logchecker for validating logs generated from supported ripping programs (like EAC and XLD)", | ||||
| "version": "0.8.6", | |||||
| "version": "0.9.0", | |||||
| "license": "Unlicense", | "license": "Unlicense", | ||||
| "type": "library", | "type": "library", | ||||
| "authors": [ | "authors": [ | ||||
| @@ -3,12 +3,11 @@ | |||||
| namespace OrpheusNET\Logchecker; | namespace OrpheusNET\Logchecker; | ||||
| use OrpheusNET\Logchecker\Exception\FileNotFoundException; | use OrpheusNET\Logchecker\Exception\FileNotFoundException; | ||||
| use Symfony\Component\Process\Exception\ProcessFailedException; | |||||
| use Symfony\Component\Process\Process; | use Symfony\Component\Process\Process; | ||||
| class Chardet | class Chardet | ||||
| { | { | ||||
| private $executable = null; | |||||
| private static $executable = null; | |||||
| private $executables = [ | private $executables = [ | ||||
| 'chardet', | 'chardet', | ||||
| 'chardetect' | 'chardetect' | ||||
| @@ -16,15 +15,17 @@ class Chardet | |||||
| public function __construct() | public function __construct() | ||||
| { | { | ||||
| foreach ($this->executables as $executable) { | |||||
| if (Util::commandExists($executable)) { | |||||
| $this->executable = $executable; | |||||
| break; | |||||
| if (static::$executable === null) { | |||||
| foreach ($this->executables as $executable) { | |||||
| if (Util::commandExists($executable)) { | |||||
| static::$executable = $executable; | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| } | |||||
| if ($this->executable === null) { | |||||
| throw new \RuntimeException('chardet not installed'); | |||||
| if (static::$executable === null) { | |||||
| throw new \RuntimeException('chardet not installed'); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -35,7 +36,7 @@ class Chardet | |||||
| throw new FileNotFoundException($filename); | throw new FileNotFoundException($filename); | ||||
| } | } | ||||
| $process = new Process([$this->executable, $filename]); | |||||
| $process = new Process([static::$executable, $filename]); | |||||
| $process->run(); | $process->run(); | ||||
| // Following regex: | // Following regex: | ||||
| @@ -1,6 +1,6 @@ | |||||
| <?php | <?php | ||||
| namespace OrpheusNET\Logchecker\Checks; | |||||
| namespace OrpheusNET\Logchecker\Check; | |||||
| use OrpheusNET\Logchecker\Util; | use OrpheusNET\Logchecker\Util; | ||||
| use Symfony\Component\Process\Process; | use Symfony\Component\Process\Process; | ||||
| @@ -1,6 +1,6 @@ | |||||
| <?php | <?php | ||||
| namespace OrpheusNET\Logchecker\Checks; | |||||
| namespace OrpheusNET\Logchecker\Check; | |||||
| class ChecksumStates | class ChecksumStates | ||||
| { | { | ||||
| @@ -2,7 +2,7 @@ | |||||
| declare(strict_types=1); | declare(strict_types=1); | ||||
| namespace OrpheusNET\Logchecker\Checks; | |||||
| namespace OrpheusNET\Logchecker\Check; | |||||
| use OrpheusNET\Logchecker\Exception\UnknownRipperException; | use OrpheusNET\Logchecker\Exception\UnknownRipperException; | ||||
| @@ -21,10 +21,13 @@ class Ripper | |||||
| return Ripper::XLD; | return Ripper::XLD; | ||||
| } elseif (strpos($log, "Exact Audio Copy") !== false) { | } elseif (strpos($log, "Exact Audio Copy") !== false) { | ||||
| return Ripper::EAC; | return Ripper::EAC; | ||||
| } else if (strpos($log, "EAC") === 0) { | |||||
| return Ripper::EAC; | |||||
| } else { | } else { | ||||
| throw new UnknownRipperException("Could not determine ripper"); | |||||
| $firstLine = strstr($log, "\n", true); | |||||
| if ($firstLine !== false && strpos($firstLine, "EAC") !== false) { | |||||
| return Ripper::EAC; | |||||
| } else { | |||||
| throw new UnknownRipperException("Could not determine ripper"); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,51 @@ | |||||
| <?php | |||||
| namespace OrpheusNET\Logchecker\Command; | |||||
| use OrpheusNET\Logchecker\Util; | |||||
| use Symfony\Component\Console\Command\Command; | |||||
| use Symfony\Component\Console\Input\InputArgument; | |||||
| use Symfony\Component\Console\Input\InputInterface; | |||||
| use Symfony\Component\Console\Output\OutputInterface; | |||||
| class DecodeCommand extends Command | |||||
| { | |||||
| protected function configure() | |||||
| { | |||||
| $this | |||||
| ->setName('decode') | |||||
| ->setDescription('Decodes log from whatever encoding into UTF-8') | |||||
| ->setHelp(<<<HELP | |||||
| This command decodes a log from whatever encoding into UTF-8. | |||||
| XLD and Whipper generates logs that are in UTF-8, while EAC uses UTF-16. However, older | |||||
| EAC logs will often be in a smattering of different encoding (most popular is CP-1251, which | |||||
| is a Cyrillic code page), which are in-compatible with UTF-8 based analysis, and so require | |||||
| decoding first. Due to the difficulty of this problem, we use chardet (if installed) to give | |||||
| us the encoding if we cannot detect it via a BOM. | |||||
| If no [out_file] is specified, the decoded log will be printed to stdout. | |||||
| HELP | |||||
| ) | |||||
| ->addArgument('file', InputArgument::REQUIRED, 'Log file to decode') | |||||
| ->addArgument('out_file', InputArgument::OPTIONAL, 'File to write decoded log file to'); | |||||
| } | |||||
| protected function execute(InputInterface $input, OutputInterface $output) | |||||
| { | |||||
| $filename = $input->getArgument('file'); | |||||
| if (!file_exists($filename)) { | |||||
| $output->writeln("Invalid file"); | |||||
| return 1; | |||||
| } | |||||
| $log = file_get_contents($filename); | |||||
| $log = Util::decodeEncoding($log, $filename); | |||||
| if ($input->getArgument('out_file')) { | |||||
| file_put_contents($input->getArgument('out_file'), $log); | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,53 @@ | |||||
| <?php | |||||
| namespace OrpheusNET\Logchecker\Command; | |||||
| use OrpheusNET\Logchecker\Parser\EAC\Translator; | |||||
| use Symfony\Component\Console\Command\Command; | |||||
| use Symfony\Component\Console\Input\InputArgument; | |||||
| use Symfony\Component\Console\Input\InputInterface; | |||||
| use Symfony\Component\Console\Input\InputOption; | |||||
| use Symfony\Component\Console\Output\OutputInterface; | |||||
| class TranslateCommand extends Command | |||||
| { | |||||
| protected function configure() | |||||
| { | |||||
| $this | |||||
| ->setName('translate') | |||||
| ->setDescription('Translates a log into english') | |||||
| ->setHelp("Translates a log into english") | |||||
| ->addOption('language', 'l', InputOption::VALUE_OPTIONAL, 'Force language to use') | |||||
| ->addArgument('file', InputArgument::REQUIRED, 'Log file to decode') | |||||
| ->addArgument('out_file', InputArgument::OPTIONAL, 'File to write decoded log file to'); | |||||
| } | |||||
| protected function execute(InputInterface $input, OutputInterface $output) | |||||
| { | |||||
| $filename = $input->getArgument('file'); | |||||
| if (!file_exists($filename)) { | |||||
| $output->writeln("Invalid file"); | |||||
| return 1; | |||||
| } | |||||
| $log = file_get_contents($filename); | |||||
| if ($input->getOption('language')) { | |||||
| $code = $input->getOption('language'); | |||||
| $output->writeln("Translating from {$code} to English"); | |||||
| } else { | |||||
| $language = Translator::getLanguage($log); | |||||
| $code = $language['code']; | |||||
| $output->writeln("Translating from {$language['name']} ({$language['name_english']}) to English"); | |||||
| } | |||||
| $log = Translator::translate($log, $language['code']); | |||||
| if ($input->getArgument('out_file')) { | |||||
| file_put_contents($input->getArgument('out_file'), $log); | |||||
| } else { | |||||
| $output->write($log); | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| } | |||||
| @@ -2,7 +2,7 @@ | |||||
| namespace OrpheusNET\Logchecker; | namespace OrpheusNET\Logchecker; | ||||
| use OrpheusNET\Logchecker\Checks\Ripper; | |||||
| use OrpheusNET\Logchecker\Check\Ripper; | |||||
| use OrpheusNET\Logchecker\Parser\EAC\Translator; | use OrpheusNET\Logchecker\Parser\EAC\Translator; | ||||
| use Symfony\Component\Yaml\Yaml; | use Symfony\Component\Yaml\Yaml; | ||||
| use Symfony\Component\Yaml\Exception\ParseException; | use Symfony\Component\Yaml\Exception\ParseException; | ||||
| @@ -17,7 +17,7 @@ class Logchecker | |||||
| private $logPath = null; | private $logPath = null; | ||||
| private $logs = array(); | private $logs = array(); | ||||
| private $Tracks = array(); | private $Tracks = array(); | ||||
| private $checksumStatus = Checks\ChecksumStates::CHECKSUM_OK; | |||||
| private $checksumStatus = Check\ChecksumStates::CHECKSUM_OK; | |||||
| private $Score = 100; | private $Score = 100; | ||||
| private $Details = array(); | private $Details = array(); | ||||
| private $Offsets = array(); | private $Offsets = array(); | ||||
| @@ -37,7 +37,6 @@ class Logchecker | |||||
| private $Range = null; | private $Range = null; | ||||
| private $ARSummary = null; | private $ARSummary = null; | ||||
| private $XLDSecureRipper = false; | private $XLDSecureRipper = false; | ||||
| private $Chardet = null; | |||||
| private $FakeDrives = [ | private $FakeDrives = [ | ||||
| 'Generic DVD-ROM SCSI CdRom Device' | 'Generic DVD-ROM SCSI CdRom Device' | ||||
| ]; | ]; | ||||
| @@ -46,13 +45,6 @@ class Logchecker | |||||
| public function __construct() | public function __construct() | ||||
| { | { | ||||
| try { | |||||
| $this->Chardet = new Chardet(); | |||||
| } catch (\Exception $exc) { | |||||
| // Could not find chardet | |||||
| $this->Chardet = null; | |||||
| } | |||||
| $this->AllDrives = array_map(function ($elem) { | $this->AllDrives = array_map(function ($elem) { | ||||
| return explode(',', $elem); | return explode(',', $elem); | ||||
| }, file(__DIR__ . '/offsets.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)); | }, file(__DIR__ . '/offsets.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)); | ||||
| @@ -78,7 +70,7 @@ class Logchecker | |||||
| $this->logPath = null; | $this->logPath = null; | ||||
| $this->logs = array(); | $this->logs = array(); | ||||
| $this->Tracks = array(); | $this->Tracks = array(); | ||||
| $this->checksumStatus = Checks\ChecksumStates::CHECKSUM_OK; | |||||
| $this->checksumStatus = Check\ChecksumStates::CHECKSUM_OK; | |||||
| $this->Score = 100; | $this->Score = 100; | ||||
| $this->Details = array(); | $this->Details = array(); | ||||
| $this->Offsets = array(); | $this->Offsets = array(); | ||||
| @@ -104,31 +96,6 @@ class Logchecker | |||||
| $this->ValidateChecksum = $Bool; | $this->ValidateChecksum = $Bool; | ||||
| } | } | ||||
| private function convertEncoding() | |||||
| { | |||||
| // Whipper uses UTF-8 so we don't need to bother checking, especially as it's | |||||
| // possible a log may be falsely detected as a different encoding by chardet | |||||
| if (strpos($this->log, "Log created by: whipper") !== false) { | |||||
| return; | |||||
| } | |||||
| // To parse the log, we want to deal with the log in UTF-8. EAC by default should | |||||
| // always output to UTF-16 and XLD to UTF-8, but sometimes people view the log and | |||||
| // re-encode them to something else (like Windows-1251), and we need to use chardet | |||||
| // to detect this so we can then convert it to UTF-8. | |||||
| if (ord($this->log[0]) . ord($this->log[1]) == 0xFF . 0xFE) { | |||||
| $this->log = mb_convert_encoding(substr($this->log, 2), 'UTF-8', 'UTF-16LE'); | |||||
| } elseif (ord($this->log[0]) . ord($this->log[1]) == 0xFE . 0xFF) { | |||||
| $this->log = mb_convert_encoding(substr($this->log, 2), 'UTF-8', 'UTF-16BE'); | |||||
| } elseif (ord($this->log[0]) == 0xEF && ord($this->log[1]) == 0xBB && ord($this->log[2]) == 0xBF) { | |||||
| $this->log = substr($this->log, 3); | |||||
| } elseif ($this->Chardet !== null) { | |||||
| $Results = $this->Chardet->analyze($this->logPath); | |||||
| if ($Results['charset'] !== 'utf-8' && $Results['confidence'] > 0.7) { | |||||
| $this->log = mb_convert_encoding($this->log, 'UTF-8', $Results['charset']); | |||||
| } | |||||
| } | |||||
| } | |||||
| /** | /** | ||||
| * @return array Returns an array that contains [Score, Details, Checksum, Log] | * @return array Returns an array that contains [Score, Details, Checksum, Log] | ||||
| */ | */ | ||||
| @@ -136,7 +103,7 @@ class Logchecker | |||||
| { | { | ||||
| try { | try { | ||||
| $this->convertEncoding(); | |||||
| $this->log = Util::decodeEncoding($this->log, $this->logPath); | |||||
| } catch (\Exception $exc) { | } catch (\Exception $exc) { | ||||
| $this->Score = 0; | $this->Score = 0; | ||||
| $this->account('Could not detect log encoding, log is corrupt.'); | $this->account('Could not detect log encoding, log is corrupt.'); | ||||
| @@ -186,7 +153,7 @@ class Logchecker | |||||
| $Class = $this->checksumStatus ? 'good' : 'bad'; | $Class = $this->checksumStatus ? 'good' : 'bad'; | ||||
| $Yaml['SHA-256 hash'] = "<span class='{$Class}'>{$Hash}</span>"; | $Yaml['SHA-256 hash'] = "<span class='{$Class}'>{$Hash}</span>"; | ||||
| } else { | } else { | ||||
| $this->checksumStatus = Checks\ChecksumStates::CHECKSUM_MISSING; | |||||
| $this->checksumStatus = Check\ChecksumStates::CHECKSUM_MISSING; | |||||
| } | } | ||||
| $RippingKey = 'Ripping phase information'; | $RippingKey = 'Ripping phase information'; | ||||
| @@ -356,7 +323,7 @@ class Logchecker | |||||
| PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_DELIM_CAPTURE | ||||
| ); | ); | ||||
| } else { //no checksum | } else { //no checksum | ||||
| $this->checksumStatus = Checks\ChecksumStates::CHECKSUM_MISSING; | |||||
| $this->checksumStatus = Check\ChecksumStates::CHECKSUM_MISSING; | |||||
| $this->logs = preg_split("/(\nEnd of status report)/i", $this->log, -1, PREG_SPLIT_DELIM_CAPTURE); | $this->logs = preg_split("/(\nEnd of status report)/i", $this->log, -1, PREG_SPLIT_DELIM_CAPTURE); | ||||
| foreach ($this->logs as $Key => $Value) { | foreach ($this->logs as $Key => $Value) { | ||||
| if (preg_match("/---- CUETools DB Plugin V.+/i", $Value)) { | if (preg_match("/---- CUETools DB Plugin V.+/i", $Value)) { | ||||
| @@ -370,7 +337,7 @@ class Logchecker | |||||
| if ($Log === "" || preg_match('/^\-+$/i', $Log)) { | if ($Log === "" || preg_match('/^\-+$/i', $Log)) { | ||||
| unset($this->logs[$Key]); | unset($this->logs[$Key]); | ||||
| } elseif ( | } elseif ( | ||||
| $this->checksumStatus !== Checks\ChecksumStates::CHECKSUM_OK | |||||
| $this->checksumStatus !== Check\ChecksumStates::CHECKSUM_OK | |||||
| && preg_match("/End of status report/i", $Log) | && preg_match("/End of status report/i", $Log) | ||||
| ) { | ) { | ||||
| //strip empty | //strip empty | ||||
| @@ -378,13 +345,13 @@ class Logchecker | |||||
| $this->logs[$Key - 1] .= $Log; | $this->logs[$Key - 1] .= $Log; | ||||
| unset($this->logs[$Key]); | unset($this->logs[$Key]); | ||||
| } elseif ( | } elseif ( | ||||
| $this->checksumStatus === Checks\ChecksumStates::CHECKSUM_OK | |||||
| $this->checksumStatus === Check\ChecksumStates::CHECKSUM_OK | |||||
| && preg_match("/[\=]+\s+Log checksum/i", $Log) | && preg_match("/[\=]+\s+Log checksum/i", $Log) | ||||
| ) { | ) { | ||||
| $this->logs[$Key - 1] .= $Log; | $this->logs[$Key - 1] .= $Log; | ||||
| unset($this->logs[$Key]); | unset($this->logs[$Key]); | ||||
| } elseif ( | } elseif ( | ||||
| $this->checksumStatus === Checks\ChecksumStates::CHECKSUM_OK | |||||
| $this->checksumStatus === Check\ChecksumStates::CHECKSUM_OK | |||||
| && preg_match("/[\-]+BEGIN XLD SIGNATURE/i", $Log) | && preg_match("/[\-]+BEGIN XLD SIGNATURE/i", $Log) | ||||
| ) { | ) { | ||||
| $this->logs[$Key - 1] .= $Log; | $this->logs[$Key - 1] .= $Log; | ||||
| @@ -404,7 +371,7 @@ class Logchecker | |||||
| if ($Matches[1]) { | if ($Matches[1]) { | ||||
| $this->version = floatval(explode(" ", substr($Matches[1], 1))[0]); | $this->version = floatval(explode(" ", substr($Matches[1], 1))[0]); | ||||
| if ($this->version < 1) { | if ($this->version < 1) { | ||||
| $this->checksumStatus = Checks\ChecksumStates::CHECKSUM_MISSING; | |||||
| $this->checksumStatus = Check\ChecksumStates::CHECKSUM_MISSING; | |||||
| if ($this->version <= 0.95) { | if ($this->version <= 0.95) { | ||||
| # EAC 0.95 and before was missing a handful of stuff for full log validation | # EAC 0.95 and before was missing a handful of stuff for full log validation | ||||
| # that 0.99 included (-30 points) | # that 0.99 included (-30 points) | ||||
| @@ -412,14 +379,14 @@ class Logchecker | |||||
| } | } | ||||
| } else { | } else { | ||||
| // Above version 1 and no checksum | // Above version 1 and no checksum | ||||
| $this->checksumStatus = Checks\ChecksumStates::CHECKSUM_MISSING; | |||||
| $this->checksumStatus = Check\ChecksumStates::CHECKSUM_MISSING; | |||||
| } | } | ||||
| } else { | } else { | ||||
| $this->checksumStatus = Checks\ChecksumStates::CHECKSUM_MISSING; | |||||
| $this->checksumStatus = Check\ChecksumStates::CHECKSUM_MISSING; | |||||
| $this->account("EAC version older than 0.99", 30); | $this->account("EAC version older than 0.99", 30); | ||||
| } | } | ||||
| } elseif (preg_match('/EAC extraction logfile from/i', $Log)) { | } elseif (preg_match('/EAC extraction logfile from/i', $Log)) { | ||||
| $this->checksumStatus = Checks\ChecksumStates::CHECKSUM_MISSING; | |||||
| $this->checksumStatus = Check\ChecksumStates::CHECKSUM_MISSING; | |||||
| $this->account("EAC version older than 0.99", 30); | $this->account("EAC version older than 0.99", 30); | ||||
| } | } | ||||
| @@ -433,7 +400,7 @@ class Logchecker | |||||
| if (preg_match('/X Lossless Decoder version (\d+) \((.+)\)/i', $Log, $Matches)) { //xld version & checksum | if (preg_match('/X Lossless Decoder version (\d+) \((.+)\)/i', $Log, $Matches)) { //xld version & checksum | ||||
| $this->version = $Matches[1]; | $this->version = $Matches[1]; | ||||
| if ($this->version >= 20121222 && !$Count) { | if ($this->version >= 20121222 && !$Count) { | ||||
| $this->checksumStatus = Checks\ChecksumStates::CHECKSUM_MISSING; | |||||
| $this->checksumStatus = Check\ChecksumStates::CHECKSUM_MISSING; | |||||
| //$this->account('No checksum with XLD 20121222 or newer', 15); | //$this->account('No checksum with XLD 20121222 or newer', 15); | ||||
| } | } | ||||
| } | } | ||||
| @@ -482,11 +449,11 @@ class Logchecker | |||||
| if ( | if ( | ||||
| $this->ValidateChecksum | $this->ValidateChecksum | ||||
| && $this->checksumStatus == Checks\ChecksumStates::CHECKSUM_OK | |||||
| && $this->checksumStatus == Check\ChecksumStates::CHECKSUM_OK | |||||
| && !empty($this->logPath) | && !empty($this->logPath) | ||||
| ) { | ) { | ||||
| if (Checks\Checksum::logcheckerExists($EAC)) { | |||||
| $this->checksumStatus = Checks\Checksum::validate($this->logPath, $EAC); | |||||
| if (Check\Checksum::logcheckerExists($EAC)) { | |||||
| $this->checksumStatus = Check\Checksum::validate($this->logPath, $EAC); | |||||
| } else { | } else { | ||||
| $this->account( | $this->account( | ||||
| "Could not find {$this->ripper} logchecker, checksum not validated.", | "Could not find {$this->ripper} logchecker, checksum not validated.", | ||||
| @@ -16,9 +16,11 @@ class LogcheckerConsole extends Application | |||||
| $analyze_command = new Command\AnalyzeCommand(); | $analyze_command = new Command\AnalyzeCommand(); | ||||
| $this->addCommands([ | $this->addCommands([ | ||||
| $analyze_command | |||||
| $analyze_command, | |||||
| new Command\DecodeCommand(), | |||||
| new Command\TranslateCommand() | |||||
| ]); | ]); | ||||
| $this->setDefaultCommand($analyze_command->getName(), true); | |||||
| //$this->setDefaultCommand($analyze_command->getName(), false); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,171 @@ | |||||
| { | |||||
| "31": "Filename will be ignored", | |||||
| "50": "Value out of range !", | |||||
| "51": "Invalid characters !", | |||||
| "52": "Invalid filename !", | |||||
| "1200": "Status and Error Messages", | |||||
| "2501": "Track status and errors", | |||||
| "1203": "Possible Errors", | |||||
| "1204": "Create Log", | |||||
| "1210": "Stato intervallo ed errori", | |||||
| "1211": "Intervallo selezionato", | |||||
| "1212": " Timing problem ", | |||||
| "1213": " Suspicious position ", | |||||
| "1214": " Missing samples", | |||||
| "1215": " Too many samples", | |||||
| "1216": " File write error", | |||||
| "1217": " Livello di picco ", | |||||
| "1299": " Extraction speed ", | |||||
| "1218": " Qualità intervallo ", | |||||
| "1219": " CRC ", | |||||
| "1220": " Copia corretta", | |||||
| "1221": " Copy finished", | |||||
| "1227": " Track quality ", | |||||
| "1228": " Copy aborted", | |||||
| "1269": " Nome file ", | |||||
| "1270": " Pre-gap length ", | |||||
| "1271": " Test CRC ", | |||||
| "1272": " Copy CRC ", | |||||
| "1273": " Compressing", | |||||
| "1280": " Track not fully ripped for AccurateRip lookup", | |||||
| "1281": " Accurately ripped (confidence ", | |||||
| "1282": " Not accurately ripped (confidence ", | |||||
| "1283": " Track not present in AccurateRip database", | |||||
| "1330": " Cannot be verified as accurate", | |||||
| "1337": "cannot be verified as accurate", | |||||
| "1331": ", AccurateRip returned", | |||||
| "1332": "(confidence ", | |||||
| "1333": "No tracks could be verified as accurate", | |||||
| "1334": "You may have a different pressing from the one(s) in the database", | |||||
| "1335": "Some tracks could not be verified as accurate", | |||||
| "1336": "All tracks accurately ripped", | |||||
| "1338": "Null samples used in CRC calculations", | |||||
| "1339": "track(s) not present in the AccurateRip database", | |||||
| "1340": "track(s) accurately ripped", | |||||
| "1341": "track(s) could not be verified as accurate", | |||||
| "1342": "track(s) not fully ripped for AccurateRip lookup", | |||||
| "1343": "track(s) canceled", | |||||
| "1344": "None of the tracks are present in the AccurateRip database", | |||||
| "1284": "Not all tracks ripped accurately", | |||||
| "1222": "Non soNo stati riscontrati errori", | |||||
| "1223": "Review Range", | |||||
| "1224": "There were errors", | |||||
| "1225": "Fine del resoconto di stato", | |||||
| "1321": "Not detected, thus appended to previous track", | |||||
| "1322": "Appended to previous track", | |||||
| "1323": "Appended to next track", | |||||
| "1325": "Log checksum", | |||||
| "1226": "Track", | |||||
| "1230": "Index", | |||||
| "1229": "Review Tracks", | |||||
| "1274": "Estrazione file di log EAC da ", | |||||
| "1240": "January", | |||||
| "1241": "February", | |||||
| "1242": "March", | |||||
| "1243": "April", | |||||
| "1244": "May", | |||||
| "1245": "June", | |||||
| "1246": "July", | |||||
| "1247": "August", | |||||
| "1248": "September", | |||||
| "1249": "October", | |||||
| "1250": "November", | |||||
| "1251": "December", | |||||
| "1232": "EAC extraction log file", | |||||
| "1233": "Unità predefinita: ", | |||||
| "1234": "Modalità di lettura", | |||||
| "1235": "Burst", | |||||
| "1236": "Fast", | |||||
| "1237": "Paranoid", | |||||
| "1238": "Secure with NO C2, accurate stream, NO disable cache", | |||||
| "1239": "Secure with NO C2, NO accurate stream, disable cache", | |||||
| "1252": "Secure with C2, accurate stream, NO disable cache", | |||||
| "1253": "Secure with C2, accurate stream, disable cache", | |||||
| "1254": "Secure with NO C2, accurate stream, disable cache", | |||||
| "1255": "Combined read/write offset correction", | |||||
| "1256": "Correzione offset di lettura", | |||||
| "1257": "Sovrascrivi anche nel Lead-In e Lead-Out", | |||||
| "1258": "Formato di destinazione scelto", | |||||
| "1259": "Additional command line options", | |||||
| "1260": "Routine interne WAV", | |||||
| "1261": "44.100 Hz; 16 Bit; Stereo", | |||||
| "1262": "Use compression offset", | |||||
| "1263": "Altre opzioni : ", | |||||
| "1264": "Riempi sample offset mancanti con silenzio", | |||||
| "1265": "Rimuovi blocchi di silezio ad inizio e fine", | |||||
| "1266": "Normalize to", | |||||
| "13303": "Native Win32 interface for XP/Vista/Win 7", | |||||
| "1267": "Native Win32 interface for Win NT & 2000", | |||||
| "1268": "Interfaccia esterna ASPI (installata)", | |||||
| "1275": "AccurateRip summary", | |||||
| "1276": "not ripped completely", | |||||
| "1277": "accurately ripped (confidence ", | |||||
| "1278": "not ripped accurately (confidence ", | |||||
| "1279": "not present in database", | |||||
| "1285": ", but should be", | |||||
| "1286": "Performing a test extraction only", | |||||
| "1287": "Sectors", | |||||
| "1288": "Exact Audio Copy", | |||||
| "1289": "TOC of the extracted CD", | |||||
| "1290": "Track", | |||||
| "1291": "Start", | |||||
| "1292": "Length", | |||||
| "1293": "Start sector", | |||||
| "1294": "End sector", | |||||
| "1295": "Secure", | |||||
| "1296": "Make use of C2 pointers", | |||||
| "1297": "Utilize accurate stream", | |||||
| "1298": "Defeat audio cache", | |||||
| "1305": "Used interface", | |||||
| "1306": "Command line compressor", | |||||
| "1307": "Selected bitrate", | |||||
| "1308": "Quality", | |||||
| "1328": "High", | |||||
| "1329": "Low", | |||||
| "1309": "Add ID3 tag", | |||||
| "1310": "Sample format", | |||||
| "1320": "Gap handling", | |||||
| "1324": "Left out", | |||||
| "81700": "L3Enc MP3 Encoder & Compatible", | |||||
| "81701": "Fraunhofer MP3Enc MP3 Encoder", | |||||
| "81702": "Xing X3Enc MP3 Encoder", | |||||
| "81703": "Xing ToMPG MP3 Encoder", | |||||
| "81704": "LAME MP3 Encoder", | |||||
| "81705": "GOGO MP3 Encoder", | |||||
| "81706": "MPC Encoder", | |||||
| "81707": "Ogg Vorbis Encoder", | |||||
| "81708": "Microsoft WMA9 Encoder", | |||||
| "81709": "FAAC AAC Encoder", | |||||
| "81710": "Homeboy AAC Encoder", | |||||
| "81711": "Quartex AAC Encoder", | |||||
| "81712": "PsyTEL AAC Encoder", | |||||
| "81713": "MBSoft AAC Encoder", | |||||
| "81714": "Yamaha VQF Encoder", | |||||
| "81715": "Real Audio Encoder", | |||||
| "81716": "Monkey's Audio Lossless Encoder", | |||||
| "81717": "Shorten Lossless Encoder", | |||||
| "81718": "RKAU Lossless Encoder", | |||||
| "81719": "LPAC Lossless Encoder", | |||||
| "81720": "User Defined Encoder", | |||||
| "1000000": "per CD", | |||||
| "4270": "Low", | |||||
| "4271": "Medium", | |||||
| "4272": "High", | |||||
| "1": "Corsu", | |||||
| "2": "Corsican", | |||||
| "5": "Error Message", | |||||
| "6": "Warning", | |||||
| "7": "Success", | |||||
| "8": "Information", | |||||
| "10": "OK", | |||||
| "11": "Cancel", | |||||
| "12": "Apply", | |||||
| "15": "Yes", | |||||
| "16": "No" | |||||
| } | |||||
| @@ -6,6 +6,13 @@ | |||||
| "name": "Български", | "name": "Български", | ||||
| "name_english": "Bulgarian" | "name_english": "Bulgarian" | ||||
| }, | }, | ||||
| "co": { | |||||
| "eac_strings": [ | |||||
| "Estrazione file di log EAC da" | |||||
| ], | |||||
| "name": "Corsu", | |||||
| "name_english": "Corsican" | |||||
| }, | |||||
| "cs": { | "cs": { | ||||
| "eac_strings": [ | "eac_strings": [ | ||||
| "Protokol extrakce EAC z " | "Protokol extrakce EAC z " | ||||
| @@ -11,4 +11,36 @@ class Util | |||||
| exec("{$where} {$cmd} 2>/dev/null", $output, $return_var); | exec("{$where} {$cmd} 2>/dev/null", $output, $return_var); | ||||
| return $return_var === 0; | return $return_var === 0; | ||||
| } | } | ||||
| public static function decodeEncoding(string $log, string $logPath): string | |||||
| { | |||||
| try { | |||||
| $chardet = new Chardet(); | |||||
| } catch (\RuntimeException $exc) { | |||||
| $chardet = null; | |||||
| } | |||||
| // Whipper uses UTF-8 so we don't need to bother checking, especially as it's | |||||
| // possible a log may be falsely detected as a different encoding by chardet | |||||
| if (strpos($log, "Log created by: whipper") !== false) { | |||||
| return $log; | |||||
| } | |||||
| // To parse the log, we want to deal with the log in UTF-8. EAC by default should | |||||
| // always output to UTF-16 and XLD to UTF-8, but sometimes people view the log and | |||||
| // re-encode them to something else (like Windows-1251), and we need to use chardet | |||||
| // to detect this so we can then convert it to UTF-8. | |||||
| if (ord($log[0]) . ord($log[1]) == 0xFF . 0xFE) { | |||||
| $log = mb_convert_encoding(substr($log, 2), 'UTF-8', 'UTF-16LE'); | |||||
| } elseif (ord($log[0]) . ord($log[1]) == 0xFE . 0xFF) { | |||||
| $log = mb_convert_encoding(substr($log, 2), 'UTF-8', 'UTF-16BE'); | |||||
| } elseif (ord($log[0]) == 0xEF && ord($log[1]) == 0xBB && ord($log[2]) == 0xBF) { | |||||
| $log = substr($log, 3); | |||||
| } elseif ($chardet !== null) { | |||||
| $Results = $chardet->analyze($logPath); | |||||
| if ($Results['charset'] !== 'utf-8' && $Results['confidence'] > 0.7) { | |||||
| $log = mb_convert_encoding($log, 'UTF-8', $Results['charset']); | |||||
| } | |||||
| } | |||||
| return $log; | |||||
| } | |||||
| } | } | ||||
| @@ -2,10 +2,10 @@ | |||||
| declare(strict_types=1); | declare(strict_types=1); | ||||
| namespace OrpheusNET\Logchecker\Checks; | |||||
| namespace OrpheusNET\Logchecker\Check; | |||||
| use PHPUnit\Framework\TestCase; | use PHPUnit\Framework\TestCase; | ||||
| use OrpheusNET\Logchecker\Checks\Ripper; | |||||
| use OrpheusNET\Logchecker\Check\Ripper; | |||||
| use OrpheusNET\Logchecker\Exception\UnknownRipperException; | use OrpheusNET\Logchecker\Exception\UnknownRipperException; | ||||
| class RipperTest extends TestCase | class RipperTest extends TestCase | ||||
| @@ -18,7 +18,12 @@ class RipperTest extends TestCase | |||||
| Ripper::EAC | Ripper::EAC | ||||
| ], | ], | ||||
| [ | [ | ||||
| "EAC 展開 ログファイル 日付: 24. 12月 2005, 18:37 for CD", | |||||
| "EAC 展開 ログファイル 日付: 24. 12月 2005, 18:37 for CD\nTest", | |||||
| Ripper::EAC | |||||
| ], | |||||
| [ | |||||
| "Отчёт EAC об извлечении, выполненном 15. января 2010, 16:06 для диска:\n" . | |||||
| "Girls Against Boys / Cruise Yourself", | |||||
| Ripper::EAC | Ripper::EAC | ||||
| ], | ], | ||||
| [ | [ | ||||
| @@ -13,7 +13,7 @@ class TranslatorTest extends TestCase | |||||
| public function foreignLogDataProvider() | public function foreignLogDataProvider() | ||||
| { | { | ||||
| $logs = []; | $logs = []; | ||||
| $logPath = implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..', 'logs', 'transcoded_foreign_logs']); | |||||
| $logPath = implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..', 'logs', 'eac', 'transcoded_logs']); | |||||
| foreach (new FilesystemIterator($logPath, FilesystemIterator::SKIP_DOTS) as $dir) { | foreach (new FilesystemIterator($logPath, FilesystemIterator::SKIP_DOTS) as $dir) { | ||||
| if ($dir->isFile()) { | if ($dir->isFile()) { | ||||
| continue; | continue; | ||||
| @@ -0,0 +1,29 @@ | |||||
| Estrazione file di log EAC da 13. Giugno 2006, 12:34 per CD | |||||
| Cousteau / Cousteau | |||||
| Unità predefinita: HL-DT-STCD-RW GCE-8480B Adapter: 2 ID: 1 | |||||
| Modalità di lettura: Sicuro con NO C2, Lettura Accurata, Disattiva Cache | |||||
| Correzione offset di lettura:6 | |||||
| Sovrascrivi anche nel Lead-In e Lead-Out : No | |||||
| Formato di destinazione scelto: Routine interne WAV | |||||
| 44.100 Hz; 16 Bit; Stereo | |||||
| Altre opzioni : | |||||
| Riempi sample offset mancanti con silenzio : Sì | |||||
| Rimuovi blocchi di silezio ad inizio e fine : No | |||||
| Interfaccia esterna ASPI (installata) | |||||
| Stato intervallo ed errori | |||||
| Intervallo selezionato | |||||
| Nome file C:\Documents and Settings\maurizio\Desktop\Cousteau - Cousteau.wav | |||||
| Livello di picco 99.9 % | |||||
| Qualità intervallo 100.0 % | |||||
| CRC 5B545F17 | |||||
| Copia corretta | |||||
| Non sono stati riscontrati errori | |||||
| Fine del resoconto di stato | |||||
| @@ -0,0 +1,29 @@ | |||||
| EAC extraction logfile from 13. GiugNo 2006, 12:34 for disc | |||||
| Cousteau / Cousteau | |||||
| Used drive : HL-DT-STCD-RW GCE-8480B Adapter: 2 ID: 1 | |||||
| Read mode: Sicuro con No C2, Lettura Accurata, Disattiva Cache | |||||
| Read offset correction:6 | |||||
| Overread into Lead-In and Lead-Out : No | |||||
| Used output format: Internal WAV Routines | |||||
| 44.100 Hz; 16 Bit; Stereo | |||||
| Other options : | |||||
| Fill up missing offset samples with silence : Sì | |||||
| Delete leading and trailing silent blocks : No | |||||
| Installed external ASPI interface | |||||
| Range status and errors | |||||
| Selected range | |||||
| Filename C:\Documents and Settings\maurizio\Desktop\Cousteau - Cousteau.wav | |||||
| Peak level 99.9 % | |||||
| Range Quality 100.0 % | |||||
| CRC 5B545F17 | |||||
| Copy OK | |||||
| No errors occurred | |||||
| End of status report | |||||