cpcdb.py

Automatiser l’inventaire des collections CPC avec Python
A l’aide du script CPC.PY , j’ai télécharger une collection impressionnante de plus de 4000 fichiers .dsk représentant des jeux et applications de cette mythique machine 8-bit . Ils proviennent des collections CPC_games et CPC_apps .
Reste à créer la db
Le défi : des noms de fichiers à décoder
Mes fichiers .dsk suivent une convention de nommage précise mais complexe :
Killer_Gorilla_1984_Micro_Power
Yie_Ar_Kung-Fu_1985_Imagine_Software
Killer_Ring_1987_Reaktor_fr_cr_NPS
Chaque nom contient des informations précieuses séparées par des underscores :
- Nom du jeu : la partie avant l’année
- Année de sortie : obligatoirement entre 1984-2025 ou format « 19xx »
- Éditeur : société ayant publié le logiciel
- Langue : fr, en, de, es, it…
- Type : cr (crack), t (trainer), demo…
La solution : un parseur Python intelligent
J’ai ‘ vibecodé ‘ un script Python qui automatise complètement l’extraction de ces métadonnées et leur stockage dans une base MySQL. Voici comment il fonctionne :
Architecture modulaire
class DSKParser:
def __init__(self, db_config):
self.language_patterns = ['fr', 'en', 'de', 'es', 'it']
self.type_patterns = ['cr', 't', 'demo', 'crack', 'trainer']
Le parseur utilise une approche orientée objet avec des patterns prédéfinis pour reconnaître les différents éléments.
Algorithme de parsing intelligent
Le cœur du système repose sur une logique de reconnaissance par étapes :
- Identification de l’année comme point de repère
# Trouver l'année (1984-2025 ou format xx) for i, part in enumerate(parts): if part.isdigit() and len(part) == 4: year = int(part) if 1984 <= year <= 2025: annee_index = i
- Reconstruction du nom complet avant l’année
# Tout avant l'année = nom du jeu nom_jeu = ' '.join(parts[:annee_index])
- Classification des suffixes après l’année
- Langues détectées automatiquement
- Types (crack/trainer) identifiés
- Noms de crackers filtrés
- Reste = éditeur
Base de données structurée
Le script crée automatiquement une table MySQL optimisée :
CREATE TABLE dsk_games (
id INT AUTO_INCREMENT PRIMARY KEY,
nom_fichier VARCHAR(255) NOT NULL,
emplacement VARCHAR(500) NOT NULL,
nom_jeu VARCHAR(255) NOT NULL,
type VARCHAR(100),
editeur VARCHAR(255),
langue VARCHAR(10),
annee_sortie INT,
date_ajout TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- Index pour optimiser les recherches
INDEX idx_nom_jeu (nom_jeu),
INDEX idx_annee (annee_sortie),
INDEX idx_editeur (editeur)
)
Fonctionnalités avancées
Mode test sans base de données
python dsk_parser.py --test /chemin/vers/dsk/
Permet de vérifier le parsing sur quelques fichiers avant le traitement complet.
Logging détaillé
Chaque opération est tracée pour faciliter le débogage :
logging.basicConfig(
level=logging.INFO,
handlers=[
logging.FileHandler('dsk_parser.log'),
logging.StreamHandler()
]
)
Gestion d’erreurs robuste
- Connexions MySQL protégées
- Parsing défensif pour les noms atypiques
- Transactions sécurisées
Résultats concrets
Avant le script
- 3805 fichiers dispersés dans des dossiers
- Recherche manuelle fastidieuse
- Aucune vue d’ensemble de la collection
Après le traitement
✅ Traitement terminé!
📁 Fichiers traités avec succès: 3805
📋 Base de données structurée et interrogeable
Exemples de transformation
Fichier : 007_A_View_to_a_Kill_1985_Domark
Résultat :
- Jeu: « 007 A View to a Kill »
- Année: 1985
- Éditeur: « Domark »
- Langue: None
- Type: None
Fichier : Killer_Ring_1987_Reaktor_fr_cr_NPS
Résultat :
- Jeu: « Killer Ring »
- Année: 1987
- Éditeur: « Reaktor »
- Langue: « fr »
- Type: « cr »
Possibilités d’exploitation
Avec cette base structurée, je peux maintenant :
- Rechercher par éditeur, année, langue
- Statistiques : répartition par décennie, éditeurs les plus représentés
- Interface web pour naviguer dans la collection
- Détection de doublons sophistiquée
- Export vers différents formats
Évolutions futures
Le script pourrait être étendu pour :
- Analyser le contenu des fichiers .dsk
- Détecter automatiquement les screenshots
- Intégration avec des bases de données existantes (MobyGames)
- Interface graphique pour la gestion
Code source et utilisation
Le script est modulaire et facilement adaptable à d’autres collections rétro. Les points clés pour l’adapter :
- Modifier les patterns de reconnaissance
- Ajuster la structure de base de données
- Configurer les paramètres de connexion
DB_CONFIG = {
'host': 'votre_serveur',
'user': 'username',
'password': 'password',
'database': 'nom_base'
}
code complet
/
#!/usr/bin/env python3
"""
Parseur de fichiers .dsk pour base de données MySQL
Analyse les noms de fichiers et extrait les métadonnées
"""
import os
import re
import logging
import mysql.connector
from pathlib import Path
from typing import Dict, Optional, List
# Configuration du logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('dsk_parser.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class DSKParser:
def __init__(self, db_config: Dict[str, str]):
self.db_config = db_config
self.connection = None
self.language_patterns = ['fr', 'en', 'de', 'es', 'it']
self.type_patterns = ['cr', 't', 'demo', 'crack', 'trainer']
def connect_db(self) -> bool:
try:
self.connection = mysql.connector.connect(**self.db_config)
logger.info("Connexion à MySQL établie")
return True
except mysql.connector.Error as e:
logger.error(f"Erreur connexion MySQL: {e}")
return False
def create_table(self) -> bool:
create_query = """
CREATE TABLE IF NOT EXISTS dsk_games (
id INT AUTO_INCREMENT PRIMARY KEY,
nom_fichier VARCHAR(255) NOT NULL,
emplacement VARCHAR(500) NOT NULL,
nom_jeu VARCHAR(255) NOT NULL,
type VARCHAR(100),
editeur VARCHAR(255),
langue VARCHAR(10),
annee_sortie INT,
date_ajout TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_nom_jeu (nom_jeu),
INDEX idx_annee (annee_sortie),
INDEX idx_editeur (editeur)
)
"""
try:
cursor = self.connection.cursor()
cursor.execute(create_query)
self.connection.commit()
cursor.close()
logger.info("Table 'dsk_games' créée/vérifiée")
return True
except mysql.connector.Error as e:
logger.error(f"Erreur création table: {e}")
return False
def parse_filename(self, filename: str) -> Dict[str, Optional[str]]:
parts = filename.split('_')
if len(parts) < 2:
return {'nom_jeu': filename, 'type': None, 'editeur': None, 'langue': None, 'annee_sortie': None}
annee_index, annee_sortie = -1, None
for i, part in enumerate(parts):
if part.isdigit() and len(part) == 4:
year = int(part)
if 1984 <= year <= 2025:
annee_index, annee_sortie = i, year
break
elif len(part) == 4 and part.endswith('xx'):
if part.startswith(('19', '20')):
annee_index, annee_sortie = i, int(part[:2]) * 100 + 50
break
if annee_index == -1:
return {'nom_jeu': filename.replace('_', ' '), 'type': None, 'editeur': None, 'langue': None, 'annee_sortie': None}
nom_jeu = ' '.join(parts[:annee_index])
editeur_parts, type_parts, langue = [], [], None
for part in parts[annee_index + 1:]:
pl = part.lower()
if pl in self.language_patterns:
langue = pl
continue
if pl in self.type_patterns:
type_parts.append(pl)
continue
if part.isdigit():
continue
if len(type_parts) > 0 and len(part) <= 10 and part.isupper():
continue
editeur_parts.append(part)
editeur = ' '.join(editeur_parts) if editeur_parts else None
type_jeu = '/'.join(type_parts) if type_parts else None
return {'nom_jeu': nom_jeu, 'type': type_jeu, 'editeur': editeur, 'langue': langue, 'annee_sortie': annee_sortie}
def insert_game(self, file_path: str, parsed_data: Dict) -> bool:
filename, directory = os.path.basename(file_path), os.path.dirname(file_path)
query = """
INSERT INTO dsk_games (nom_fichier, emplacement, nom_jeu, type, editeur, langue, annee_sortie)
VALUES (%s, %s, %s, %s, %s, %s, %s)
"""
values = (filename, directory, parsed_data['nom_jeu'], parsed_data['type'], parsed_data['editeur'], parsed_data['langue'], parsed_data['annee_sortie'])
try:
cursor = self.connection.cursor()
cursor.execute(query, values)
self.connection.commit()
cursor.close()
logger.info(f"Inséré: {filename}")
return True
except mysql.connector.Error as e:
logger.error(f"Erreur insertion {filename}: {e}")
return False
def scan_directory(self, directory_path: str) -> int:
if not os.path.exists(directory_path):
logger.error(f"Répertoire inexistant: {directory_path}")
return 0
dsk_files = [os.path.join(root, file) for root, _, files in os.walk(directory_path) for file in files if file.lower().endswith('.dsk')]
logger.info(f"Trouvé {len(dsk_files)} fichiers .dsk")
success = 0
for file_path in dsk_files:
parsed = self.parse_filename(Path(file_path).stem)
if self.insert_game(file_path, parsed):
success += 1
else:
logger.warning(f"Échec insertion: {file_path}")
logger.info(f"Traitement terminé: {success}/{len(dsk_files)} fichiers insérés")
return success
def close(self):
if self.connection:
self.connection.close()
logger.info("Connexion MySQL fermée")
def test_parsing_only(directory_path: str):
print("🧪 Mode test - Parsing seulement (sans DB)")
parser = DSKParser({})
if not os.path.exists(directory_path):
print(f"❌ Répertoire inexistant: {directory_path}")
return
dsk_files = [os.path.join(root, file) for root, _, files in os.walk(directory_path) if file.lower().endswith('.dsk')]
print(f"📁 Trouvé {len(dsk_files)} fichiers .dsk")
print("-" * 50)
for file_path in dsk_files[:10]:
parsed = parser.parse_filename(Path(file_path).stem)
print(f"🎮 Fichier: {Path(file_path).stem}")
print(f" Jeu: {parsed['nom_jeu']}")
print(f" Année: {parsed['annee_sortie']}")
print(f" Éditeur: {parsed['editeur']}")
print(f" Langue: {parsed['langue']}")
print(f" Type: {parsed['type']}")
print()
def main():
import sys
DB_CONFIG = {
'host': 'localhost',
'user': 'youruser',
'password': 'yourpassword',
'database': 'yourdb',
'connection_timeout': 5
}
DIRECTORY_TO_SCAN = '/home/stefan/python/cpc_games/'
if len(sys.argv) > 1:
if sys.argv[1] == '--test':
test_parsing_only(sys.argv[2] if len(sys.argv) > 2 else DIRECTORY_TO_SCAN)
return
elif sys.argv[1] == '--help':
print("Usage:")
print(" python parseur.py --test [directory] # Test parsing sans DB")
print(" python parseur.py # Mode complet avec DB")
return
parser = DSKParser(DB_CONFIG)
try:
if not parser.connect_db():
logger.error("Impossible de se connecter à la base")
return
if not parser.create_table():
logger.error("Impossible de créer la table")
return
success = parser.scan_directory(DIRECTORY_TO_SCAN)
print("\n✅ Traitement terminé!")
print(f"📁 Fichiers traités avec succès: {success}")
print("📋 Logs détaillés dans: dsk_parser.log")
except Exception as e:
logger.error(f"Erreur générale: {e}")
finally:
parser.close()
if __name__ == "__main__":
main()
Auteur/autrice
sdgadmin@tux.ovh
Publications similaires


parse_dsk.py
Un script Python robuste pour analyser des images de disquette .dsk (Amstrad CPC), détecter le répertoire, reconstituer la liste des fichiers et...
Lire la suite
Arnold
The Amstrad CPC supports the following file formats: DSK disk image files TAP tape image files SNA snapshot files BIN snapshot files...
Lire la suite
Blackjack
compter les cartes L’astuce : augmenter les mises quand le paquet est favorable, et miser peu quand il ne l’est pas.
Lire la suite
iamuz.py
Script python pour télécharger le contenu d’une archive en fonction de l’id sur Internet Archive,
Lire la suite