Skip to content

Instantly share code, notes, and snippets.

@sinkers
Last active August 28, 2018 18:50
Show Gist options
  • Save sinkers/4e35c1b9922b8bd85452 to your computer and use it in GitHub Desktop.
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
#!/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)
@sinkers
Copy link
Author

sinkers commented Aug 20, 2014

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

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