Machins de dev

Purger massivement des MKV

J’ai écrit un petit script en Powershell afin de pouvoir traiter en masse des fichiers MKV en y enlevant toutes les pistes qui ne m’intéressaient pas.
Les MKV ont en effet la capacité d’embarquer plusieurs flux vidéos, audios, sous-titres et même des fichiers joints (des polices embarquées par exemple pour une utilisation avec les sous-titres), ce qui est très pratique.
Dans mon cas, il s’agissait de vidéos avec 3 flux audios, 3 sous-titres dont les 2/3 en russe (que je ne parle pas), le dernier tiers en anglais.

Afin d’alléger les fichiers et ainsi pouvoir les visionner sur mon téléphone, j’ai cherché le moyen de pouvoir effectuer cette purge, mais rapidement. Pour cela, les MKVToolnix, la boite à outil officielle du conteneur MKV vont être très utiles, ils contiennent :

Le script en question est donc surtout un « parser » pour mkvinfo, l’outil d’analyse, permettant ensuite de lancer tout seul la suite des opérations.

Voici la sorti type d’un mkvinfo

+ En-tête EBML
|+ Type de document : matroska
|+ Version du type de document : 2
|+ Version lue du type de document : 2
+ Segment, taille 2409578102
|+ En-tête de recherche (sous-entrées ignorées)
|+ EbmlVoid (taille : 4012)
|+ Informations sur le segment
| + Coefficient temporel : 1000000
| + Application de multiplexage : libebml v0.7.9 + libmatroska v0.8.1
| + Application d'écriture : mkvmerge v3.2.0 ('Beginnings') built on Feb 12 2010 16:46:17
| + Durée : 3479.872s (00:57:59.872)
| + Date : Mon Mar 01 18:00:07 2010 UTC
| + Titre : Alpha News 1x01
| + UID de segment : 0x9b 0xe4 0xc8 0x25 0x73 0x3d 0xb7 0xed 0x81 0xc7 0x25 0x6e 0xb9 0x36 0x9e 0x8e
|+ Pistes du segment
| + Une piste
| + Numéro de piste : 1 (identifiant de piste pour mkvmerge & mkvextract : 0)
| + UID de piste : 323836576
| + Type de piste : video
| + Activé : 1
| + Marque par défaut : 1
| + Marque de forçage : 0
| + Marque de laçage : 0
| + Cache minimum : 1
| + Coefficient temporel : 1
| + Identifiant d'ajout de bloc maximum : 0
| + Identifiant du codec : V_MPEG4/ISO/AVC
| + Codec de décodage intégral : 1
| + Données de codec privées, longueur 95 (profil h.264 : High @L3.1)
| + Durée par défaut : 41.708ms (23.976 images/champs par seconde pour une piste vidéo)
| + Langue : eng
| + Nom : "Pilot"
| + Piste vidéo
| + Largeur (pixels) : 1280
| + Hauteur (pixels) : 720
| + Entrelacé : 0
| + Largeur à l'écran : 1280
| + Hauteur à l'écran : 720
| + Une piste
| + Numéro de piste : 2 (identifiant de piste pour mkvmerge & mkvextract : 1)
| + UID de piste : 3094425017
| + Type de piste : audio
| + Activé : 1
| + Marque par défaut : 1
| + Marque de forçage : 0
| + Marque de laçage : 1
| + Cache minimum : 0
| + Coefficient temporel : 1
| + Identifiant d'ajout de bloc maximum : 0
| + Identifiant du codec : A_AC3
| + Codec de décodage intégral : 1
| + Durée par défaut : 32.000ms (31.250 images/champs par seconde pour une piste vidéo)
| + Langue : rus
| + Nom : Dolby Digital 5.1 448 kbps @ Lostfilm
| + Audio track
| + Fréquence d'échantillonnage : 48000
| + Canaux : 6
| + Une piste
| + Numéro de piste : 3 (identifiant de piste pour mkvmerge & mkvextract : 2)
| + UID de piste : 31873723
| + Type de piste : audio
| + Activé : 1
| + Marque par défaut : 0
| + Marque de forçage : 0
| + Marque de laçage : 1
| + Cache minimum : 0
| + Coefficient temporel : 1
| + Identifiant d'ajout de bloc maximum : 0
| + Identifiant du codec : A_AC3
| + Codec de décodage intégral : 1
| + Durée par défaut : 32.000ms (31.250 images/champs par seconde pour une piste vidéo)
| + Langue : rus
| + Nom : Dolby Digital 5.1 448 kbps @ foxlight
| + Audio track
| + Fréquence d'échantillonnage : 48000
| + Canaux : 6
| + Une piste
| + Numéro de piste : 4 (identifiant de piste pour mkvmerge & mkvextract : 3)
| + UID de piste : 3654252222
| + Type de piste : audio
| + Activé : 1
| + Marque par défaut : 0
| + Marque de forçage : 0
| + Marque de laçage : 1
| + Cache minimum : 0
| + Coefficient temporel : 1
| + Identifiant d'ajout de bloc maximum : 0
| + Identifiant du codec : A_AC3
| + Codec de décodage intégral : 1
| + Durée par défaut : 32.000ms (31.250 images/champs par seconde pour une piste vidéo)
| + Langue : eng
| + Nom : Dolby Digital 5.1 448 kbps
| + Audio track
| + Fréquence d'échantillonnage : 48000
| + Canaux : 6
| + Une piste
| + Numéro de piste : 5 (identifiant de piste pour mkvmerge & mkvextract : 4)
| + UID de piste : 152612246
| + Type de piste : subtitles
| + Activé : 1
| + Marque par défaut : 0
| + Marque de forçage : 0
| + Marque de laçage : 0
| + Cache minimum : 0
| + Coefficient temporel : 1
| + Identifiant d'ajout de bloc maximum : 0
| + Identifiant du codec : S_TEXT/ASS
| + Codec de décodage intégral : 1
| + Données de codec privées, longueur 1555
| + Langue : rus
| + Nom : Russian @ ASS @ Faker020285
| + Une piste
| + Numéro de piste : 6 (identifiant de piste pour mkvmerge & mkvextract : 5)
| + UID de piste : 2319477894
| + Type de piste : subtitles
| + Activé : 1
| + Marque par défaut : 0
| + Marque de forçage : 0
| + Marque de laçage : 0
| + Cache minimum : 0
| + Coefficient temporel : 1
| + Identifiant d'ajout de bloc maximum : 0
| + Identifiant du codec : S_TEXT/ASS
| + Codec de décodage intégral : 1
| + Données de codec privées, longueur 1166
| + Langue : eng
| + Nom : English @ ASS
|+ EbmlVoid (taille : 1024)
|+ Pièces jointes
| + Joints
| + Nom du fichier : corbel_0.ttf
| + Type MIME : application/x-truetype-font
| + Données du fichier, taille : 260648
| + UIF du fichier : 4097748661
| + Description du fichier : Imported font from Breaking Bad 1x01 Pilot HD720p.ass
| + Joints
| + Nom du fichier : GARA_0.ttf
| + Type MIME : application/x-truetype-font
| + Données du fichier, taille : 198069
| + UIF du fichier : 1642350759
| + Description du fichier : Imported font from Breaking Bad 1x01 Pilot HD720p.ass
| + Joints
| + Nom du fichier : Candara_0.ttf
| + Type MIME : application/x-truetype-font
| + Données du fichier, taille : 218504
| + UIF du fichier : 3442251435
| + Description du fichier : Imported font from Breaking Bad 1x01 Pilot HD720p.ass
| + Joints
| + Nom du fichier : HermesCTT Regular_0.ttf
| + Type MIME : application/x-truetype-font
| + Données du fichier, taille : 54693
| + UIF du fichier : 2891661193
| + Description du fichier : Imported font from Breaking Bad 1x01 Pilot HD720p.ass
| + Joints
| + Nom du fichier : GOTHIC_0.ttf
| + Type MIME : application/x-truetype-font
| + Données du fichier, taille : 137565
| + UIF du fichier : 61850035
| + Description du fichier : Imported font from Breaking Bad 1x01 Pilot HD720p.ass
|+ Cluster

Cela va être très pratique à parser, les informations d’un même élément étant étalés sur plusieurs lignes.
L’idée va être de rassembler toutes les informations intéressantes dans un tableau avec :
* en première colonne, le numéro de piste pour mkvextract
* en seconde colonne, le type de la piste
* en troisième colonne, la langue de la piste

A coup de regex, cela donne quelque chose dans le style :

Piste Type Lang File 
----- ---- ---- ---- 
0 
video 
eng 
1 
audio 
rus 
2 
audio 
rus 
3 
audio 
eng 
4 
subtitles 
rus 
5 
subtitles 
eng 
corbel_0.ttf 
GARA_0.ttf 
Candara_0.ttf 
HermesCTT Regula...
GOTHIC_0.ttf

On retrouve bien le fait que l’information est éparpillée : pour chaque piste, l’information doit être lu sur 3 colonnes, mais à partir de ces informations, cela est suffisant pour lancer la commande de purge, on a donc tous les éléments en main pour écrire le script, ci-dessous.

function TransformMKV ($file) {
	$mkvinfo = 'C:\Program Files\MKVToolNix\mkvinfo.exe'
	$mkvextract = 'C:\Program Files\MKVToolNix\mkvextract.exe'
	$mkvmerge = 'C:\Program Files\MKVToolNix\mkvmerge.exe'
	$workdir = 'D:\temps'

	$rawOutput = & $mkvinfo $file
	$output = $rawOutput | Select-String -Pattern 'mkvextract : (?<piste>[0-9]+)', `
		'Type de piste : (?<type>.+)', `
		'Langue : (?<lang>.+)', `
		'Nom du fichier : (?<file>.+)'

	$data = $output | Select-Object @{"Label"="Piste";"Expression"={$_.Matches[0].Groups['piste'].Value}}, `
		@{"Label"="Type";"Expression"={$_.Matches[0].Groups['type'].Value}}, `
		@{"Label"="Lang";"Expression"={$_.Matches[0].Groups['lang'].Value}}, `
		@{"Label"="File";"Expression"={$_.Matches[0].Groups['file'].Value}}

	$i = 0
	$audioTracks = @()
	$subTracks = @()

	if ((Test-Path $workdir -PathType Container) -ne $true) {
		New-Item $workdir -ItemType folder
	}

	while ($i -lt $data.Count) {
		$line = $data[$i]
		$piste = $line.piste

		if ($piste -ne $null -and $piste -ne '') {
			$type = $data[++$i].type
			$lang = $data[++$i].lang

			if ($lang -eq 'rus') { continue }
			if ($type -eq 'subtitles') { $subTracks += $piste }
			elseif ($type -eq 'audio') { $audioTracks += $piste }
		}

		$i++
	}

	& $mkvmerge -q -o ("{0}\{1}" -f $workdir, [System.IO.Path]::GetFileName($file)) --default-language eng -a ($audioTracks -join ',') -s ($subTracks -join ',') "$file"
}

Get-Item -Path 'W:\source\*.mkv' | ForEach-Object { TransformMKV($_) }

 

C’est écrit à l’arrache, mais pour ce qui me concerne, il a été suffisant et reste simple à modifier.
On commence par indiquer en dur où se trouve les différents binaires, ainsi qu’un dossier de travail qui sera crée automatiquement s’il n’existe pas.
On balance un mkvinfo sur le fichier source qu’on transforme en object avec la cmdlet Select-Object . Il ne faut surtout pas utiliser Format-Table, lequel ne sert qu’à faire de l’affichage, et ne peut donc être pipé que dans la Console.

Durant la boucle, on rassemble les morceaux et on liste l’ensemble des pistes à garder (j’ai posé en hypothèse que je n’ai qu’une seule piste vidéo, et que donc je garde toutes les pistes vidéos lors de la purge).

Enfin on exécute mkvmerge, lequel a la capacité de créer des mkv à partir de flux nu, mais également à partir d’autres container, comme un mkv. La première solution étant plus difficile (car alors il faut également gérer l’existence et l’extraction des tags, chapitres, fichiers embarqués), je me suis tourné var la seconde solution, où il suffit d’indiquer ce que l’on souhaite garder et le reste est conservé.
La ligne de commande indique donc de garder tous les flux vidéos, tous les tags, chapitres et fichier embarqués, et de ne conserver que les flux audios et sous-titres spécifiés.

Enjoy !