Skip to content

Instantly share code, notes, and snippets.

@docblades
Created January 22, 2011 01:27
Show Gist options
  • Save docblades/790748 to your computer and use it in GitHub Desktop.
Save docblades/790748 to your computer and use it in GitHub Desktop.
Transcode, instead of completely convert, an mkv into an mp4 so that the PS3 will play it. Keeps video intact, but changes audio from to aac. Requires mkvtoolnix, python-argparse, ffmpeg, libfaac and gpac.
#!/usr/bin/python
# Created by Christian Blades <christian dot blades at docblades dot com>
from xml.etree.cElementTree import Element, ElementTree
from urllib import quote_plus as quote
import re
def _getData(line):
name, text = None, None
div = line.find(':')
if div == -1:
name = quote(line.strip())
else:
name = quote(line[:div].strip())
text = line[div + 1:].strip()
return name, text
def parse(theFile):
elStack = []
root = []
for line in theFile:
start = line.find("+ ")
if (start == -1):
continue
else:
name, text = _getData(line[start + 2:])
myEl = Element(name)
myEl.text = text
if start == 0:
elStack = [[0, myEl]]
root.append(myEl)
else:
if start > elStack[-1][0]:
elStack[-1][1].append(myEl)
elStack.append([start, myEl])
else:
while(elStack[-1][0] > start):
elStack.pop()
elStack.pop()
elStack[-1][1].append(myEl)
elStack.append([start, myEl])
rootNode = Element("root")
for el in root:
rootNode.append(el)
return ElementTree(rootNode)
class TrackNotFoundException(Exception): pass
class InvalidTrackException(Exception): pass
def get_tracks(elTree):
"""Returns a list of tracks"""
tracks = elTree.findall("//A+track")
if tracks is None:
raise TrackNotFoundException("No tracks found")
return tracks
def get_track_by_type(elTree, trackType):
"""Returns the first found track of the specified type"""
tracks = get_tracks(elTree)
theTrack = None
for track in tracks:
if track.find("Track+type").text == trackType:
theTrack = track
break
if theTrack is None:
raise TrackNotFoundException("No {0} track found".format(trackType))
return theTrack
def get_fps(trackEl):
"""Given a track element, extracts and returns the FPS"""
dur = trackEl.find("Default+duration")
if dur is None:
raise InvalidTrackException("No 'Default duration' element found in this track")
results = re.findall("\(([0-9\.]{6}) fps", dur.text)
if len(results) == 0:
raise InvalidTrackException("No FPS data found in this track")
return results[0]
def get_audio_fps(elTree):
audioTrack = get_track_by_type(elTree, "audio")
return get_fps(audioTrack)
def get_vid_fps(elTree):
videoTrack = get_track_by_type(elTree, "video")
return get_fps(videoTrack)
#!/usr/bin/python
# Created By: Christian Blades <christian DOT blades AT docblades DOT com>
# Requires mkvtoolnix, python-argparse, ffmpeg, libfaac and gpac
import os, uuid, argparse, mkvinfo_parser, re, subprocess
MKVEXTRACT = 'mkvextract'
MKVINFO = 'mkvinfo-text'
FFMPEG = 'ffmpeg'
FFPROBE = 'ffprobe'
AAC = 'libfaac'
MP4BOX = 'MP4Box'
def sanitize_in(inStr):
clean = re.sub("[\n;&]", " ", inStr).strip()
return clean
def get_audio_rate(path):
""" Uses ffprobe and grabs the kb/s from the bitrate line"""
#pout, perr = os.popen4("{0} {1}".format(FFPROBE, path))
p = subprocess.Popen("{0} {1}".format(FFPROBE, path),
shell=True, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
close_fds=True)
perr = p.stdout
bitrate = None
reBitrate = re.compile("bitrate: ([0-9]+) kb/s")
for line in perr:
match = reBitrate.findall(line)
if len(match) > 0:
bitrate = match[0]
break
if bitrate is None:
raise Exception("ffprobe did not return a bitrate for {0}".format(path))
perr.close()
return bitrate
def get_video_fps(path):
""" Parse MKVInfo into a tree, then extract the video FPS """
cmdStr = "{0} {1}".format(MKVINFO, path)
myFile = os.popen(cmdStr)
myTree = mkvinfo_parser.parse(myFile)
myFile.close()
fps = mkvinfo_parser.get_vid_fps(myTree)
return fps
def existing_file(path):
""" Argparser type """
path = sanitize_in(path)
if not os.path.isfile(path):
raise argparse.ArgumentTypeError("{0} does not exist".format(path))
return path
def new_file(path):
""" Argparser type """
path = sanitize_in(path)
try:
aFile = open(path, 'w')
aFile.close()
except IOError:
raise argparse.ArgumentTypeError("{0} is a bad path, or you do not have write permissions")
return path
def existing_dir(path):
""" Argparser type """
path = sanitize_in(path)
if not os.path.isdir(path):
raise argparse.ArgumentTypeError("{0} is not a valid path".format(path))
return path
parser = argparse.ArgumentParser(
description="Transcodes the audio from a MKV into AAC and then repacks into an avi")
parser.add_argument('inFile', help='Input Filename',
type=existing_file, metavar='FILE')
parser.add_argument('--out', help='Output Filename',
type=new_file)
parser.add_argument('--brate', help='Audio Bitrate (0 = same as source)',
type=int, default=0, nargs=1)
parser.add_argument('--cleanup', help='Clean up temporary files',
default=False, type=bool, nargs=1, metavar='True|False')
parser.add_argument('--tempdir', help='Where to put the temp files. Default is current directory."',
default=os.path.curdir, type=existing_dir)
parser.add_argument('--reverse', help='Audio and Video tracks are in opposite order',
default=False, type=bool, nargs=1, metavar='True|False')
theVars = parser.parse_args()
theuuid = str(uuid.uuid1())
tmpPath = os.path.join(theVars.tempdir, theuuid)
if theVars.out is None:
theVars.out = "{0}.mp4".format(os.path.splitext(theVars.inFile)[0])
print "===Extracting audio and video tracks"
if theVars.reverse:
cmdStr = "{0} tracks {1} 1:{2}.dts 2:{2}.264"
else:
cmdStr = "{0} tracks {1} 1:{2}.264 2:{2}.dts"
cmdStr = cmdStr.format(MKVEXTRACT, theVars.inFile, tmpPath)
os.system(cmdStr)
print "===Converting audio"
brate = theVars.brate
if brate == 0:
brate = get_audio_rate("{0}.dts".format(tmpPath))
cmdStr = "{0} -i {1}.dts -acodec libfaac -ab {2}k {1}.aac"
cmdStr = cmdStr.format(FFMPEG, tmpPath, brate)
os.system(cmdStr)
try:
os.remove("{0}.dts".format(tmpPath))
except OSError:
print "Failed to remove temp file {0}.dts".format(tmpPath)
print "===Repacking as MP4"
fps = get_video_fps(theVars.inFile)
cmdStr = "{0} -new {1} -add {2}.264 -add {2}.aac -fps {3}"
cmdStr = cmdStr.format(MP4BOX, theVars.out, tmpPath, fps)
os.system(cmdStr)
try:
os.remove("{0}.264".format(tmpPath))
os.remove("{0}.aac".format(tmpPath))
except OSError:
print "There was an error while cleaning up temporary files. Sorry."
@docblades
Copy link
Author

Okay, so not perfect. While the script works, sometimes, the output files don't play on the ps3. Looking into the issue.

Edit: found the solution here http://www.shroomery.org/forums/showflat.php/Number/9038616

Extended the mkvinfo parser and added a short parser for ffprobe to get some information I needed to automate the process.

It works!

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