Last active
August 28, 2018 18:50
-
-
Save sinkers/4e35c1b9922b8bd85452 to your computer and use it in GitHub Desktop.
Encoding HLS and DASH in a simple script, lot's of tweaks to go
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 | |
# Script to encode HLS and DASH from a multi TS mezzanine file | |
# Places encoded files in a folder of the same name as the source input | |
# Steps | |
# 1. Check file with mediainfo (optional) | |
# 2. Encode to resolutions as mp4 | |
# 3. Split into TS segments and create playlists | |
# 4. Create variant playlist | |
import subprocess | |
import sys | |
import os | |
#from threading import Thread | |
# String holding the variant playlist | |
variantPlaylist = "#EXTM3U\n" | |
FFMPEG = "/usr/local/bin/ffmpeg" | |
#Gpax | |
MP4BOX = "/usr/local/bin/MP4Box" | |
#Bento | |
MP4FRAGMENT="/Users/andrew/Desktop/workspace/hbbtv/Bento4-SDK-1-3-5-541.universal-apple-macosx/bin/Release/mp4fragment" | |
MP4DASH="/Users/andrew/Desktop/workspace/hbbtv/Bento4-SDK-1-3-5-541.universal-apple-macosx/utils/mp4-dash.py" | |
hls = { | |
"profile": { | |
"type":"hls", | |
"video_codec":"libx264", #ffmpeg compatible | |
"audio_codec":"libfdk_aac", #ffmpeg compatible | |
"audio_rate":44100, #hz | |
"keyframes":2, # every x seconds | |
"fps":25, | |
"pix_fmt": "yuv420p", | |
# change this to fast or medium, this is for testing | |
"preset":"ultrafast", | |
"max_rate_factor": 1.5, | |
"bufsize_factor": 2, | |
"rates": | |
[ | |
{ | |
"video_bitrate":2500, #kbps | |
"width":1280, | |
"height":720, | |
"audio_bitrate":128, #kbps | |
"profile":"main" | |
}, | |
{ | |
"video_bitrate":1500, #kbps | |
"width":1024, | |
"height":576, | |
"audio_bitrate":96, #kbps | |
"profile":"main" | |
}, | |
{ | |
"video_bitrate":1200, #kbps | |
"width":960, | |
"height":540, | |
"audio_bitrate":128, #kbps | |
"profile":"main" | |
}, | |
{ | |
"video_bitrate":800, #kbps | |
"width":640, | |
"height":360, | |
"audio_bitrate":64, #kbps | |
"profile":"main" | |
} | |
] | |
} | |
} | |
def encodeFile(file, width, height, bitrate, vprofile="main", output_dir=".", profile=hls): | |
fileNoExt = os.path.splitext(file)[0] | |
# Creates a new file in the format filename_widthxheight.mp4 | |
new_file = ("%(filenameNoExt)s_%(bitrate)s_%(width)sx%(height)s.mp4" | |
% {"bitrate":bitrate, "height":height,"width":width, "filenameNoExt":fileNoExt}) | |
g = int(hls["profile"]["fps"]) * 4 | |
maxrate = int(bitrate * hls["profile"]["max_rate_factor"]) | |
bufsize = int(bitrate * hls["profile"]["bufsize_factor"]) | |
output_file = os.path.join(output_dir, new_file) | |
# -movflags frag_keyframe+empty_moov | |
ffmpegCmd= ("%(ffmpeg)s -y -i %(filename)s -c:v %(video_codec)s -r %(fps)s -g %(g)s " \ | |
"-b:v %(bitrate)sk -force_key_frames 'expr:gte(t,n_forced*2)' -maxrate %(maxrate)s " | |
" -bufsize %(bufsize)sk -s %(width)sx%(height)s -profile:v %(profile)s " \ | |
" -ss 00:00:00 -t 30 " \ | |
"-c:a %(audio_codec)s -ar %(audio_rate)s -b:a 64k -preset %(preset)s %(newFile)s" \ | |
% { | |
"ffmpeg":FFMPEG, | |
"filename":file, | |
"bitrate":bitrate, | |
"height":height, | |
"width":width, | |
"newFile":output_file, | |
"video_codec":hls["profile"]["video_codec"], | |
"audio_codec":hls["profile"]["audio_codec"], | |
"audio_rate":hls["profile"]["audio_rate"], | |
"fps":hls["profile"]["fps"], | |
"g":g, | |
"pix_fmt":hls["profile"]["pix_fmt"], | |
"profile":vprofile, | |
"preset":hls["profile"]["preset"], | |
"maxrate":maxrate, | |
"bufsize":bufsize | |
}) | |
print ffmpegCmd | |
process = subprocess.Popen(ffmpegCmd,shell=True, stdout=subprocess.PIPE) | |
print process.communicate() | |
return output_file | |
def encodeFileAsHLS(file, bitrate): | |
fileNoExt = os.path.splitext(file)[0] | |
ffmpegCmd= ("%(ffmpeg)s -y -i %(filename)s -codec copy -map 0 -f segment -segment_list index_%(bitrate)s" \ | |
".m3u8 -segment_time 10 -bsf h264_mp4toannexb -segment_list_type m3u8 %(filenameNoExt)s_%(count)s.ts" \ | |
% { | |
"filename":file, | |
"filenameNoExt":fileNoExt, | |
"bitrate":bitrate, | |
"count":"%03d", | |
"ffmpeg":FFMPEG | |
}) | |
print ffmpegCmd | |
appendVariantPlaylist(bitrate) | |
process = subprocess.Popen(ffmpegCmd,shell=True, stdout=subprocess.PIPE) | |
print process.communicate() | |
return | |
def appendVariantPlaylist(bitrate): | |
global variantPlaylist | |
extBitrate = bitrate * 1000 | |
variantPlaylist += ("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%(bitrate)s\n" % {"bitrate":extBitrate}) | |
variantPlaylist += ("index_%(bitrate)s.m3u8\n" % {"bitrate":bitrate}) | |
return | |
def createVariantPlaylist(output_dir="."): | |
print variantPlaylist | |
f = open("index.m3u8","w") | |
f.write(variantPlaylist) | |
f.close() | |
return | |
def createDASH(dash_files): | |
dashed_files = [] | |
for item in dash_files: | |
file_no_ext = os.path.splitext(item)[0] | |
dashed_file = file_no_ext + "_dash.mp4" | |
fragcmd = "%(mp4fragment)s %(input)s %(dashed_file)s" % ({ | |
"mp4fragment":MP4FRAGMENT, | |
"input":item, | |
"dashed_file":dashed_file | |
}) | |
print fragcmd | |
process = subprocess.Popen(fragcmd,shell=True, stdout=subprocess.PIPE) | |
print process.communicate() | |
dashed_files.append(dashed_file) | |
dashcmd = "%(mp4dash)s -f --use-segment-timeline %(dash_files)s -o dash -m manifest.mpd --use-segment-list" % \ | |
({ | |
"mp4dash":MP4DASH, | |
"dash_files":" ".join(dashed_files) | |
}) | |
print dashcmd | |
process = subprocess.Popen(dashcmd,shell=True, stdout=subprocess.PIPE) | |
print process.communicate() | |
def createDASHGpac(dash_files, output_dir="./dash", manifest="manifest.mpd"): | |
output = os.path.join(output_dir, manifest) | |
gpaccmd = "%(mp4box)s -profile main -dash 2000 -frag 6000 -segment-timeline -out %(manifest)s %(dash_files)s" % \ | |
({ | |
"mp4box":MP4BOX, | |
"dash_files":" ".join(dash_files), | |
"manifest":manifest | |
}) | |
print gpaccmd | |
process = subprocess.Popen(gpaccmd,shell=True, stdout=subprocess.PIPE) | |
print process.communicate() | |
def createDir (file): | |
return | |
dash_files = [] | |
file = sys.argv[1] | |
for item in hls["profile"]["rates"]: | |
encoded_file = encodeFile(file , item["width"], item["height"], item["video_bitrate"]) | |
dash_files.append(encoded_file) | |
encodeFileAsHLS(encoded_file, item["video_bitrate"]) | |
createVariantPlaylist() | |
#createDASHGpac(dash_files) | |
createDASH(dash_files) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that this is a testing config and as such only produces 30s samples at present, east to remove the -ss and -t bits, though these can later be used for split and stitch