Ripper et encoder ses CD Audio
Sommaire
L'article arrive 20 ans après la bataille et vous avez déjà peut être tous jeté vos CD Audio et pris des abonnements Spotify ou Deezer. Cependant pour les autres qui ont encore des étagères de CD Audio qui prennent la poussière, il serait temps d'archiver proprement tout ce fonds.
Sachez qu'écouter ses propres acquisitions, sans abonnement internet, sans abonnement à un service de musique, anonymement en mode hors ligne et en n'utilisant que des outils libres, c'est pas ringuard et ça peut être ça la vraie liberté :) En plus vous n'emcombrerez pas le réseau #écologie. Voici quelques lignes de commande utiles pour réaliser ce projet.
Le CD Audio
Le CD Audio suit la norme Red Book. Les données audio ont les caractéristiques suivantes :
- pas de compression, un flux linéaire dit PCM
- 2 canaux (stéreo)
- une fréquence d'échantillonnage (sampling rate) à 44100Hz
- une quantification (quantization ou bit bepth) sur 16 bits LPCM, signé, format petit-boutiste
L'acquisition
On ne va pas "numériser" le CD Audio car il l'est déjà mais plutôt extraire bit à bit son contenu.
L'outil cdparanoia
permet le rip (l'extraction) des données d'un CD Audio, sans les dénaturer. On aura une copie exacte à l'entête prêt, exportée au format .wav
Sous MacOS avec homebrew
ou sous Debian/Ubuntu avec apt
:
1brew install cdparanoia
2apt install cdparanoira
Note: Sous MacOs Ventura, cdparanoia a un bug, la version macport a été patchée mais pas la version homebrew. Voir mon article sur l'installation de MacPorts.
Note: Depuis début 2024, homebrew a retiré cdparanoia de ses formules, car plus maintenu, et il ne se laisser pas compiler facilemement. Le package est toujours proposé sous Debian 12, c'est peut être le plus simple.
Mettre un CD dans son lecteur puis lancer la commande suivante, comptez 15min d'extraction pour un CD de 60min.
1cdparanoia -XB
Va créer dans le répertoire courant des fichiers track01.cdda.wav
, track02.cdda.wav
, etc ...
L'encodage
On va utiliser la trousse à outil magique ffmpeg
.
Sous MacOS avec homebrew
ou sous Debian/Ubuntu avec apt
:
1brew install ffmpeg
2apt install ffmpeg
Pour encoder en MP3 :
1ffmpeg -i track01.cdda.wav -c:a libmp3lame -b:a 320k track01.mp3
Pour encoder en AAC :
1ffmpeg -i track01.cdda.wav -c:a aac -b:a 160k track01.m4a
Pour encoder en FLAC (sans perte) :
1ffmpeg -i track01.cdda.wav -c:a flac track01.flac
J'ai aussi écrit un article sur le FLAC, un format audio vraiment sans perte ?
Récupération des métadonnées
On voudrait récupérer des métadonnées (titre de la piste) pour nommer nos fichiers de façon plus lisible. Une grande base de données libre existe, elle s'appelle gnudb
.
Chaque CD Audio est identifié de façon non officielle par une empreinte, calculée en fonction du nombre de pistes et de la durée de chacune d'elle (plus précisement du décalage à l'origine, en frames, du début de chaque piste). Cet identifiant est le DiscId. Un outil cd-discid
permet de le calculer.
Sous MacOS avec homebrew
ou sous Debian/Ubuntu avec apt
:
1brew install cd-discid
2apt install cd-discid
Sur mon MacOS, mon lecteur de CD est identifié par /dev/rdisk3
(trouvable avec la commande diskutil list
), j'invoque donc la commande cd-discid
avec le chemin du lecteur :
1$ ./cd-discid /dev/rdisk3
26a09de08 8 150 49017 76397 94357 112290 137397 154280 164597 2528
Notre disque a comme discid
6a09de08
, il possède 8
pistes. La 1ère piste commence à la frame 150
(= 2 secondes de blanc initial), la 2nde piste à la frame 49017
et ainsi de suite.
On peut faire un appel http sur l'api de gnudb à l'url suivante https://gnudb.org/gnudb/6a09de08
:
1# xmcd
2#
3# Track frame offsets:
4# 150
5# 49017
6# 76397
7# 94357
8# 112290
9# 137397
10# 154280
11# 164597
12#
13# Disc length: 2528
14#
15# Revision: 11
16# Processed by: cddbd v1.5.2PL0 Copyright (c) Steve Scherf et al.
17# Submitted via: fre:ac v1.0.31
18#
19DISCID=6a09de08
20DTITLE=Jean- Michel Jarre / Chronologie
21DYEAR=1993
22DGENRE=Electronica
23TTITLE0=Chronologie 1
24TTITLE1=Chronologie 2
25TTITLE2=Chronologie 3
26TTITLE3=Chronologie 4
27TTITLE4=Chronologie 5
28TTITLE5=Chronologie 6
29TTITLE6=Chronologie 7
30TTITLE7=Chronologie 8
31EXTD= YEAR: 1993
32EXTT0=
33EXTT1=
34EXTT2=
35EXTT3=
36EXTT4=
37EXTT5=
38EXTT6=
39EXTT7=
40PLAYORDER=
C'est un résultat texte au format .ini
facilement parsable.
Voilà, on a toutes les briques en place. On peut maintenant automatiser tout le processus en le scriptant.
Le script complet
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4#######################
5# ripenc.py
6#######################
7# - Rip d'un CD Audio
8# - Récupération Titre/Artiste
9# - Encodage AAC, MP3, FLAC
10# - Génération de la playlist
11# - Ménage
12#######################
13# Compatibilité :
14# - MacOS
15# - Linux
16#######################
17
18import subprocess # commandes externes
19import urllib.request # requête http
20import configparser # lecture .ini
21import re # regexp
22import os # renommage fichiers
23import glob # recherche de fichiers
24
25# Dépendances binaires externes devant se trouver dans le path
26DEPENDENCIES = [
27 'cdparanoia',
28 'cd-discid',
29 'ffmpeg'
30]
31
32# Ripper le CD Audio ?
33CD_RIP = True
34
35# Chemin du lecteur de CD (à adapter)
36CD_PATH = "/dev/rdisk3"
37
38# Récupérer les titres sur gnudb et renommer les fichiers extraits
39# Le CD Audio doit toujours être dans le lecteur
40GNUDB_REQUEST = True
41
42# type d'encodage à effectuer : aac|flac|mp3|False
43ENCODE_FORMAT = 'aac'
44
45# effacer le .wav d'origine à la fin de l'encodage ?
46CLEAN_WAV = True
47
48# générer la liste de lecture
49PLAYLIST_CREATE = True
50
51# nom de la liste de lecture
52PLAYLIST_NAME = "playlist.m3u"
53
54def check_dependencies():
55 print("- Vérification des dépendances")
56 for cmd in DEPENDENCIES:
57 subprocess.run(['command', '-v', cmd], check=True, universal_newlines=True)
58 print("OK")
59
60def cd_rip():
61 print("- RIP du CD audio")
62 subprocess.run(['cdparanoia', '-XB'], check=True, universal_newlines=True)
63
64def get_discid():
65 print("- Récupération du discid")
66 cp = subprocess.run(['cd-discid', CD_PATH], check=True, universal_newlines=True, stdout=subprocess.PIPE)
67 # ex: 6a09de08 8 150 49017 76397 94357 112290 137397 154280 164597 2528
68 ret = cp.stdout.split()
69 return ret[0]
70
71def get_gnudb(discid):
72 print('- Récupération des metadonnées sur gnudb.org')
73 url = 'https://gnudb.org/gnudb/' + discid
74 str = urllib.request.urlopen(url).read().decode("utf-8")
75 cfg = configparser.ConfigParser()
76 cfg.read_string('[root]\n' + str) # section root factice
77 dict = {}
78 for option in cfg.options('root'):
79 dict[option] = cfg.get('root', option) # les options ont été lowercasées
80 return dict
81
82def rename_tracks(dict):
83 print('- Renommage des fichiers')
84 for key in dict:
85 m = re.search('ttitle([0-9]{1,2})', key)
86 if m:
87 track_number = format(int(m.group(1)) + 1, '02d')
88 orig_file = 'track' + track_number + '.cdda.wav'
89 dest_file = track_number + ' - ' + dict[key] + '.wav'
90 print('key: ' + key + ' value= ' + dict[key] + ' track_number = ', track_number)
91 print('rename ' + orig_file + ' -> ' + dest_file)
92 if os.path.isfile(orig_file):
93 os.rename(orig_file, dest_file)
94 print('OK')
95 else:
96 print('fichier ' + orig_file + ' non trouvé')
97
98def encode_wavs():
99 files = glob.glob('*.wav')
100 files.sort() # glob ne fait aucun tri
101 for file in files:
102 print('- Traitement ' + file)
103
104 if ENCODE_FORMAT == 'aac':
105 encode_aac(file)
106 elif ENCODE_FORMAT == 'flac':
107 encode_flac(file)
108 elif ENCODE_FORMAT == 'mp3':
109 encode_mp3(file)
110
111 if CLEAN_WAV:
112 clean_wav(file)
113
114def clean_wav(file):
115 print("- Suppression " + file)
116 os.remove(file)
117
118def encode_aac(input):
119 output = input.replace('.cdda.wav', '').replace('.wav', '') + '.m4a'
120 print('- Encodage AAC : ' + input + ' -> ' + output)
121 subprocess.run(['ffmpeg', '-y', '-hide_banner', '-i', input, '-c:a', 'aac', '-b:a', '160k', output], check=True, universal_newlines=True)
122 if PLAYLIST_CREATE:
123 playlist_append(output)
124
125def encode_flac(input):
126 output = input.replace('.cdda.wav', '').replace('.wav', '') + '.flac'
127 print('- Encodage FLAC : ' + input + ' -> ' + output)
128 subprocess.run(['ffmpeg', '-y', '-hide_banner', '-i', input, '-c:a', 'flac', output], check=True, universal_newlines=True)
129 if PLAYLIST_CREATE:
130 playlist_append(output)
131
132def encode_mp3(input):
133 output = input.replace('.cdda.wav', '').replace('.wav', '') + '.mp3'
134 print('- Encodage MP3 : ' + input + ' -> ' + output)
135 subprocess.run(['ffmpeg', '-y', '-hide_banner', '-i', input, '-c:a', 'libmp3lame', '-b:a', '320k', output], check=True, universal_newlines=True)
136 if PLAYLIST_CREATE:
137 playlist_append(output)
138
139def playlist_append(file):
140 print('- Ajout ' + file + ' à la liste de lecture')
141 f = open(PLAYLIST_NAME, 'a')
142 f.write(file + "\n")
143 f.close()
144
145if __name__ == '__main__':
146 check_dependencies()
147 if CD_RIP:
148 cd_rip()
149 if GNUDB_REQUEST:
150 discid = get_discid()
151 print('discid: ' + discid)
152
153 info = get_gnudb(discid)
154 if info:
155 rename_tracks(info)
156
157 encode_wavs()
Améliorations possibles
- Tagger les fichiers en extrayant DTITLE, DYEAR, DGENRE (quel outil ?)
- Récupérer la pochette (quel outil ? via quel site ?)
- Débugguer
cdparanoia
sous MacOS Ventura