Skip to content

Instantly share code, notes, and snippets.

@stanionascu
Last active May 9, 2022 15:21
Show Gist options
  • Save stanionascu/aa25d96f23da0a0fed2cdb406697818b to your computer and use it in GitHub Desktop.
Save stanionascu/aa25d96f23da0a0fed2cdb406697818b to your computer and use it in GitHub Desktop.
MKV to DolbyVision MP4
#!/usr/bin/env python
import argparse
import subprocess
import json
from pathlib import Path
CODEC_TO_EXT = {
'MPEG-H/HEVC/h.265': 'h265',
'VC-1': 'vc1',
'AC-3': 'ac3',
'TrueHD Atmos': 'atmos',
'HDMV PGS': 'pgs',
'DTS-HD Master Audio': 'dts'
}
def track_name(track):
t_id = track['id']
t_lang = track['properties']['language']
t_codec = track['codec']
return '{0}_{1}.{2}'.format(t_id, t_lang, CODEC_TO_EXT[t_codec])
def make_extract_tracks_cmd(in_file, tracks):
cmd = ['mkvextract', in_file, 'chapters', 'chapters.xml', 'tracks']
for track in tracks:
t_id = track['id']
cmd += ['{}:{}'.format(t_id, track_name(track))]
return cmd
def make_mux_cmd(out_file, tracks):
cmd = ['mp4muxer', '--dv-profile', '7', '--output-file', out_file]
# video tracks are first, sort them by size?
for track in filter(is_video, tracks):
cmd += ['--input-file', track_name(track)]
# then all ac3 tracks
for track in filter(is_audio, tracks):
t_lang = track['properties']['language']
cmd += ['--input-file', track_name(track),
'--media-lang', t_lang]
return cmd
def is_video(track):
return track['type'] == 'video'
def is_audio(track):
return track['type'] == 'audio'
def is_lang(track, languages):
return ('properties' in track and
track['properties']['language'] in ['und'] + languages)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('input', nargs=1, type=str)
parser.add_argument('--keep-audio-lang', nargs='+', default=None)
cmd = parser.parse_args()
for file in cmd.input:
info = subprocess.run(['mkvmerge', '-J', file], capture_output=True)
if info.returncode == 0:
info = json.loads(info.stdout.decode('utf-8'))
tracks = info['tracks']
# must have 2 video tracks
if len(list(filter(is_video, tracks))) < 2:
raise Exception('2 video tracks are needed for Dolby Vision mux')
# filter audio audio tracks
tracks = list(filter(lambda t: is_lang(t, cmd.keep_audio_lang) or not is_audio(t),
tracks))
extract_cmd = make_extract_tracks_cmd(file, tracks)
print(extract_cmd)
out_file = Path(file).stem + '.mp4'
remux_cmd = make_mux_cmd(out_file, tracks)
print(remux_cmd)
subprocess.run(extract_cmd)
subprocess.run(remux_cmd)
print('cleaning up...')
for track in tracks:
t_file = Path(track_name(track))
if t_file.exists():
Path(track_name(track)).unlink()
else:
print('{} is already gone'.format(t_file.as_posix()))
print('done: {}'.format(out_file))
if __name__ == "__main__":
main()
@Hydrag1
Copy link

Hydrag1 commented Apr 3, 2022

Hi,

The topic looks interesting but I can't seem to get it to run. Do you have to edit that python file based on the title of the .mkv somehow?

@stanionascu
Copy link
Author

Hi,

The topic looks interesting but I can't seem to get it to run. Do you have to edit that python file based on the title of the .mkv somehow?

You need to have the mkvtoolnix and dlb_mp4base from dolby labs in the path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment