Files
ohmj2/api/lib/ScoreScanner.php
2026-02-18 10:08:48 +01:00

505 lines
16 KiB
PHP

<?php
class ScoreScanner {
private $scoresPath;
public function __construct($path = '../legacy/Scores/') {
$this->scoresPath = $path;
}
public function getAllScores() {
$scores = [];
$directories = scandir($this->scoresPath);
foreach ($directories as $dir) {
if ($dir === '.' || $dir === '..') continue;
// Ignorer les fichiers cachés et non-numériques
if (strpos($dir, '.') === 0) continue;
if (!is_numeric($dir)) continue;
$scorePath = $this->scoresPath . $dir;
if (!is_dir($scorePath)) continue;
$score = $this->getScoreInfo($dir);
if ($score) {
$scores[] = $score;
}
}
// Trier par ID
usort($scores, function($a, $b) {
return intval($a['id']) - intval($b['id']);
});
return $scores;
}
public function getScoreInfo($id) {
$scoreDir = $this->scoresPath . $id;
$iniFile = $scoreDir . '/score.ini';
if (!file_exists($iniFile)) {
return null;
}
$ini = @parse_ini_file($iniFile, true);
if ($ini === false) {
return null;
}
return [
'id' => $id,
'name' => $ini['info']['name'] ?? 'Inconnu',
'compositor' => $ini['info']['compositor'] ?? 'Inconnu',
'ressource' => $ini['info']['ressource'] ?? null
];
}
public function getScoreDetail($id) {
$scoreDir = $this->scoresPath . $id;
if (!is_dir($scoreDir)) {
return null;
}
$basicInfo = $this->getScoreInfo($id);
if (!$basicInfo) {
return null;
}
$instruments = [];
// New structure: NUM/PIECE/INSTRUMENT/VERSION/
// First get all piece directories
$pieceDirs = scandir($scoreDir);
foreach ($pieceDirs as $pieceDir) {
if ($pieceDir === '.' || $pieceDir === '..' || $pieceDir === 'score.ini') continue;
if (strpos($pieceDir, '.') === 0) continue;
if (!is_dir($scoreDir . '/' . $pieceDir)) continue;
// Then get instrument directories
$instrumentDirs = scandir($scoreDir . '/' . $pieceDir);
foreach ($instrumentDirs as $instrumentId) {
if ($instrumentId === '.' || $instrumentId === '..') continue;
if (strpos($instrumentId, '.') === 0) continue;
if (!is_dir($scoreDir . '/' . $pieceDir . '/' . $instrumentId)) continue;
$instrument = $this->getInstrumentInfo($id, $instrumentId, $pieceDir);
if ($instrument) {
$instruments[] = $instrument;
}
}
}
// Trier instruments par nom
usort($instruments, function($a, $b) {
return strcmp($a['title'], $b['title']);
});
return array_merge($basicInfo, ['instruments' => $instruments]);
}
private function getInstrumentInfo($scoreId, $instrumentId, $pieceId = null) {
// New structure: NUM/PIECE/INSTRUMENT/VERSION/
if ($pieceId !== null) {
$instrumentPath = $this->scoresPath . $scoreId . '/' . $pieceId . '/' . $instrumentId;
} else {
$instrumentPath = $this->scoresPath . $scoreId . '/' . $instrumentId;
}
if (!is_dir($instrumentPath)) {
return null;
}
$title = $this->getInstrumentName($instrumentId);
$parts = [];
$entries = scandir($instrumentPath);
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') continue;
if (strpos($entry, '.') === 0) continue;
$partPath = $instrumentPath . '/' . $entry;
if (!is_dir($partPath)) continue;
$part = $this->getPartInfo($scoreId, $instrumentId, $entry, $pieceId);
if ($part) {
$parts[] = $part;
}
}
// Trier les parties par numéro
usort($parts, function($a, $b) {
return intval($a['id']) - intval($b['id']);
});
if (empty($parts)) {
return null;
}
return [
'id' => $instrumentId,
'title' => $title,
'piece' => $pieceId,
'parts' => $parts
];
}
private function getPartInfo($scoreId, $instrumentId, $partId, $pieceId = null) {
// New structure: NUM/PIECE/INSTRUMENT/VERSION/
if ($pieceId !== null) {
$partPath = $this->scoresPath . $scoreId . '/' . $pieceId . '/' . $instrumentId . '/' . $partId;
} else {
$partPath = $this->scoresPath . $scoreId . '/' . $instrumentId . '/' . $partId;
}
if (!is_dir($partPath)) {
return null;
}
$files = [];
$entries = scandir($partPath);
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') continue;
if (strpos($entry, '.') === 0) continue;
$filePath = $partPath . '/' . $entry;
if (!is_file($filePath)) continue;
// Vérifier que c'est un PDF
if (strtolower(pathinfo($entry, PATHINFO_EXTENSION)) !== 'pdf') continue;
$relativePath = $pieceId !== null
? "$scoreId/$pieceId/$instrumentId/$partId/$entry"
: "$scoreId/$instrumentId/$partId/$entry";
$parsed = $this->parseFilename(pathinfo($entry, PATHINFO_FILENAME));
$files[] = [
'name' => pathinfo($entry, PATHINFO_FILENAME),
'filename' => $entry,
'path' => $relativePath,
'part' => $parsed['part'],
'key' => $parsed['key'],
'clef' => $parsed['clef'],
'variant' => $parsed['variant']
];
}
if (empty($files)) {
return null;
}
// Trier les fichiers par nom
usort($files, function($a, $b) {
return strcmp($a['name'], $b['name']);
});
return [
'id' => $partId,
'files' => $files
];
}
private function getInstrumentName($code) {
$names = [
'cla' => 'Clarinette',
'flu' => 'Flûte',
'trb' => 'Trombone',
'pic' => 'Piccolo',
'per' => 'Percussions',
'htb' => 'Hautbois',
'trp' => 'Trompette',
'dir' => 'Direction',
'sax' => 'Sax Alto',
'sat' => 'Sax Ténor',
'sab' => 'Sax Baryton',
'cor' => 'Cor',
'eup' => 'Euphonium',
'bas' => 'Basson',
'cba' => 'Contrebasse',
'crn' => 'Cornet',
'coa' => 'Cor Anglais',
'clb' => 'Clarinette Basse',
'har' => 'Harpe',
'pia' => 'Piano',
'tub' => 'Tuba',
'sup' => 'Parties supplémentaires',
'par' => 'Parties'
];
return $names[$code] ?? $code;
}
private function parseFilename(string $filename): array {
$result = [
'part' => null,
'key' => null,
'clef' => null,
'variant' => null
];
// Normalize: replace - by _ and lowercase
$name = strtolower(str_replace('-', '_', $filename));
// Extract clef (clesol, clefa)
if (preg_match('/(clesol|clefa)$/', $name, $m)) {
$result['clef'] = $m[1];
$name = preg_replace('/_(clesol|clefa)$/', '', $name);
}
// Extract variant (solo, etc.)
$variants = ['solo', 'default'];
foreach ($variants as $variant) {
if (strpos($name, $variant) !== false) {
$result['variant'] = $variant;
$name = str_replace('_' . $variant, '', $name);
break;
}
}
// Extract key (tonalités: sib, mib, fa, do, etc.)
$keys = ['sib', 'mib', 'reb', 'dob', 'solb', 'lab', 'mibemol', 'sibemol'];
foreach ($keys as $key) {
if (preg_match('/_' . $key . '/', $name)) {
$result['key'] = $key;
$name = preg_replace('/_' . $key . '/', '', $name);
break;
}
}
// If no compound keys found, try single keys
if ($result['key'] === null) {
$singleKeys = ['fa', 'do', 'ut', 'sol', 're', 'mi', 'si'];
foreach ($singleKeys as $key) {
if (preg_match('/_' . $key . '(?:_|$)/', $name)) {
$result['key'] = $key;
$name = preg_replace('/_' . $key . '/', '', $name);
break;
}
}
}
// Extract part number (1, 2, 1_et_2, etc.) - should be last
// Handle formats: 1, 2, 1_et_2
if (preg_match('/_(\d+(?:_\w+)*)$/', $name, $m)) {
$result['part'] = $m[1];
$name = preg_replace('/_\d+(?:_\w+)*$/', '', $name);
}
return $result;
}
// API Methods
public function listScores(): array {
return $this->getAllScores();
}
public function getScore(string $id): ?array {
return $this->getScoreDetail($id);
}
public function getPieces(string $scoreId): array {
$scoreDir = $this->scoresPath . $scoreId;
$iniFile = $scoreDir . '/score.ini';
if (!file_exists($iniFile)) {
return [];
}
$ini = @parse_ini_file($iniFile, true);
if ($ini === false) {
return [];
}
$pieces = [];
// Check if [pieces] section exists
if (isset($ini['pieces'])) {
$piecesSection = $ini['pieces'];
$count = intval($piecesSection['count'] ?? 1);
for ($i = 1; $i <= $count; $i++) {
$pieces[] = [
'id' => $i,
'name' => $piecesSection[$i] ?? "Pièce $i"
];
}
}
return $pieces;
}
public function getInstruments(string $scoreId, ?string $pieceId = null): array {
$scoreDir = $this->scoresPath . $scoreId;
if (!is_dir($scoreDir)) {
return [];
}
$instruments = [];
// New structure: NUM/PIECE/INSTRUMENT/VERSION/
$pieceDirs = scandir($scoreDir);
foreach ($pieceDirs as $pieceDir) {
if ($pieceDir === '.' || $pieceDir === '..' || $pieceDir === 'score.ini') continue;
if (strpos($pieceDir, '.') === 0) continue;
if (!is_dir($scoreDir . '/' . $pieceDir)) continue;
// Filter by piece if specified
if ($pieceId !== null && $pieceDir !== $pieceId) continue;
$instrumentDirs = scandir($scoreDir . '/' . $pieceDir);
foreach ($instrumentDirs as $instrumentId) {
if ($instrumentId === '.' || $instrumentId === '..') continue;
if (strpos($instrumentId, '.') === 0) continue;
if (!is_dir($scoreDir . '/' . $pieceDir . '/' . $instrumentId)) continue;
$instrument = $this->getInstrumentInfo($scoreId, $instrumentId, $pieceDir);
if ($instrument) {
$instruments[] = $instrument;
}
}
}
// Sort by title
usort($instruments, function($a, $b) {
return strcmp($a['title'], $b['title']);
});
return $instruments;
}
public function createScore(string $id, string $name, string $compositor): array {
$scoreDir = $this->scoresPath . $id;
if (is_dir($scoreDir)) {
return ['success' => false, 'error' => 'Score already exists'];
}
if (!mkdir($scoreDir, 0755, true)) {
return ['success' => false, 'error' => 'Failed to create directory'];
}
$iniContent = "[info]\nname = $name\ncompositor = $compositor\n\n[pieces]\ncount = 1\n";
if (file_put_contents($scoreDir . '/score.ini', $iniContent) === false) {
return ['success' => false, 'error' => 'Failed to create score.ini'];
}
return [
'success' => true,
'score' => [
'id' => $id,
'name' => $name,
'compositor' => $compositor
]
];
}
public function updateScore(string $scoreId, ?string $name, ?string $compositor): array {
$scoreDir = $this->scoresPath . $scoreId;
$iniFile = $scoreDir . '/score.ini';
if (!file_exists($iniFile)) {
return ['success' => false, 'error' => 'Score not found'];
}
$ini = @parse_ini_file($iniFile, true);
if ($ini === false) {
return ['success' => false, 'error' => 'Failed to parse score.ini'];
}
// Update values
if ($name !== null) {
$ini['info']['name'] = $name;
}
if ($compositor !== null) {
$ini['info']['compositor'] = $compositor;
}
// Rebuild ini content
$content = "[info]\n";
$content .= "name = " . ($ini['info']['name'] ?? '') . "\n";
$content .= "compositor = " . ($ini['info']['compositor'] ?? '') . "\n\n";
$content .= "[pieces]\n";
if (isset($ini['pieces'])) {
foreach ($ini['pieces'] as $key => $value) {
$content .= "$key = $value\n";
}
}
if (file_put_contents($iniFile, $content) === false) {
return ['success' => false, 'error' => 'Failed to write score.ini'];
}
return ['success' => true];
}
public function deleteScore(string $scoreId): array {
$scoreDir = $this->scoresPath . $scoreId;
if (!is_dir($scoreDir)) {
return ['success' => false, 'error' => 'Score not found'];
}
// Recursive delete
$this->deleteDirectory($scoreDir);
return ['success' => true];
}
private function deleteDirectory(string $dir): void {
if (!is_dir($dir)) return;
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = $dir . '/' . $item;
if (is_dir($path)) {
$this->deleteDirectory($path);
} else {
unlink($path);
}
}
rmdir($dir);
}
public function uploadPdf(string $scoreId, array $file, string $piece, string $instrument, string $version, string $filename): array {
$scoreDir = $this->scoresPath . $scoreId;
if (!is_dir($scoreDir)) {
return ['success' => false, 'error' => 'Score not found'];
}
// Create directory structure: scoreId/piece/instrument/version
$targetDir = $scoreDir . '/' . $piece . '/' . $instrument . '/' . $version;
if (!mkdir($targetDir, 0755, true)) {
return ['success' => false, 'error' => 'Failed to create directory'];
}
// Determine filename
if (empty($filename)) {
$filename = $file['name'];
}
$targetPath = $targetDir . '/' . $filename;
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
return ['success' => true, 'path' => "$scoreId/$piece/$instrument/$version/$filename"];
}
return ['success' => false, 'error' => 'Failed to move uploaded file'];
}
}