Created
January 22, 2011 01:27
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*.pyc |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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!