|
<?php |
|
|
|
/* |
|
* https://www.gnu.org/software/ccd2cue/manual/html_node/INDEX-_0028CUE-Command_0029.html |
|
*/ |
|
|
|
final class Time |
|
{ |
|
private $seconds; |
|
private $frames; |
|
public function __construct(int $seconds, int $frames = 0) |
|
{ |
|
$this->seconds = $seconds; |
|
$this->frames = $frames; |
|
} |
|
|
|
public function seconds(): int { return $this->seconds; } |
|
public function frames(): int { return $this->frames; } |
|
|
|
public function add(Time $time): Time |
|
{ |
|
$frames = $this->frames() + $time->frames(); |
|
|
|
return new Time( |
|
$this->seconds() + $time->seconds() + floor($frames / 75), |
|
$frames % 75 |
|
); |
|
} |
|
|
|
public function toCueNotation(): string |
|
{ |
|
return sprintf( |
|
'%02d:%02d:%02d', |
|
floor($this->seconds / 60), |
|
$this->seconds % 60, |
|
$this->frames |
|
); |
|
} |
|
|
|
public static function fromTimeNotation(string $duration): Time |
|
{ |
|
$parts = array_reverse(explode(':', $duration)); |
|
$seconds = |
|
$parts[0] + |
|
intval($parts[1] ?? '0') * 60 + |
|
intval($parts[2] ?? '0') * 3600; |
|
|
|
return new Time($seconds); |
|
} |
|
|
|
public static function fromCueNotation(string $duration): Time |
|
{ |
|
$parts = array_reverse(explode(':', $duration)); |
|
|
|
return new Time( |
|
intval($parts[1] ?? '0') + intval($parts[2] ?? '0') * 60, |
|
intval($parts[0] ?? '0') |
|
); |
|
} |
|
} |
|
|
|
final class Track |
|
{ |
|
private $raw; |
|
private $number; |
|
private $indices; |
|
|
|
public function __construct(string $raw, int $number, string ...$indices) |
|
{ |
|
$this->raw = $raw; |
|
$this->number = $number; |
|
$this->indices = $indices; |
|
} |
|
|
|
public function raw(): string { return $this->raw; } |
|
public function number(): int { return $this->number; } |
|
public function indices(): array { return $this->indices; } |
|
} |
|
|
|
function extract_tracks(string $contents): array |
|
{ |
|
preg_match_all('/(?P<track>\s*TRACK (?P<number>\d+) AUDIO.+?(\s*INDEX \d{2} \d+(:\d+)*)+)/s', $contents, $matches); |
|
|
|
$tracks = []; |
|
for($i = 0; $i < count($matches['track']); $i++) { |
|
preg_match_all('/INDEX \d+ (?P<time>\d+(:\d+)*)/s', $matches['track'][$i], $indexMatches); |
|
|
|
$tracks[] = new Track( |
|
$matches['track'][$i], |
|
intval($matches['number'][$i]), |
|
...$indexMatches['time'] |
|
); |
|
} |
|
|
|
return $tracks; |
|
} |
|
|
|
function debug(string $line): void |
|
{ |
|
print $line . PHP_EOL; |
|
} |
|
|
|
if (count($argv) < 3) { |
|
$filename = basename(__FILE__); |
|
print "Usage php -f ${filename} <destination> [<source>:<duration>]" . PHP_EOL; |
|
exit(0); |
|
} |
|
|
|
$destination = $argv[1]; |
|
$sources = array_slice($argv, 2); |
|
|
|
$trackNumber = 0; |
|
$totalDuration = new Time(0); |
|
|
|
foreach ($sources as $index => $source) { |
|
if (preg_match('/^(?P<filename>.+?):(?P<duration>(\d+:)*\d+)$/si', $source, $matches) !== 1) { |
|
print "Unable to split filename and duration for ${source}" . PHP_EOL; |
|
exit(1); |
|
} |
|
|
|
$filename = $matches['filename']; |
|
$time = Time::fromTimeNotation($matches['duration']); |
|
|
|
$contents = utf8_encode(file_get_contents($filename)); |
|
$tracks = extract_tracks($contents); |
|
|
|
if ($index === 0) { |
|
// Copy first file as is |
|
file_put_contents($destination, trim($contents)); |
|
} else { |
|
foreach ($tracks as $track) { |
|
$raw = str_replace( // update track number |
|
[sprintf('TRACK %d', $track->number()), sprintf('TRACK %02d', $track->number())], |
|
sprintf('TRACK %02d', $trackNumber + $track->number()), |
|
$track->raw() |
|
); |
|
|
|
foreach ($track->indices() as $index) { |
|
$newIndex = Time::fromCueNotation($index)->add($totalDuration); |
|
|
|
$raw = str_replace( |
|
$index, |
|
$newIndex->toCueNotation(), |
|
$raw |
|
); |
|
} |
|
|
|
file_put_contents($destination, $raw, FILE_APPEND); |
|
} |
|
} |
|
|
|
$trackNumber += count($tracks); // let's assume sequential track numbers |
|
$totalDuration = $totalDuration->add($time); |
|
} |
|
|
|
// Leave a nice line break at the end of the file |
|
file_put_contents($destination, PHP_EOL, FILE_APPEND); |