|
| 1 | +#!/usr/bin/python3 |
| 2 | + |
| 3 | +import subprocess |
| 4 | +import json |
| 5 | + |
| 6 | +RENDITIONS_SAMPLE = ( |
| 7 | + # resolution bitrate(kbps) audio-rate(kbps) |
| 8 | + [3840, 2160, 14000000, 192000], |
| 9 | + [2560, 1440, 10000000, 192000], |
| 10 | + [1920, 1080, 5000000, 192000], |
| 11 | + [1280, 720, 2800000, 192000], |
| 12 | + [842, 480, 1400000, 128000], |
| 13 | + [640, 360, 800000, 128000] |
| 14 | +) |
| 15 | + |
| 16 | +def to_kps(bitrate): |
| 17 | + return str(int(bitrate/1000))+"k" |
| 18 | + |
| 19 | +def GetABRCommand(in_file, target, streaming_type, renditions=RENDITIONS_SAMPLE, duration=2, |
| 20 | + segment_num=0,loop=0): |
| 21 | + ffprobe_cmd = ["ffprobe", "-v", "quiet", "-print_format", "json", |
| 22 | + "-show_streams", in_file] |
| 23 | + |
| 24 | + process_id = subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE) |
| 25 | + # the `multiprocessing.Process` process will wait until |
| 26 | + # the call to the `subprocess.Popen` object is completed |
| 27 | + process_id.wait() |
| 28 | + clip_info = json.loads(process_id.stdout.read().decode("utf-8")) |
| 29 | + |
| 30 | + keyframe_interval = 0 |
| 31 | + frame_height = 0 |
| 32 | + clip_v_duration = 0 |
| 33 | + clip_a_duration = 0 |
| 34 | + |
| 35 | + segment_target_duration = duration # try to create a new segment every X seconds |
| 36 | + max_bitrate_ratio = 1.07 # maximum accepted bitrate fluctuations |
| 37 | + rate_monitor_buffer_ratio = 1.5 # maximum buffer size between bitrate conformance checks |
| 38 | + |
| 39 | + for item in clip_info["streams"]: |
| 40 | + if item["codec_type"] == "video": |
| 41 | + keyframe_interval = int(eval(item["avg_frame_rate"])+0.5) |
| 42 | + frame_height = item["height"] |
| 43 | + clip_v_duration = eval(item["duration"]) |
| 44 | + if item["codec_type"] == "audio": |
| 45 | + clip_a_duration = eval(item["duration"]) |
| 46 | + |
| 47 | + if segment_num != 0: |
| 48 | + segment_duration = (int)((clip_v_duration+2.0)/segment_num) |
| 49 | + if segment_duration < segment_target_duration: |
| 50 | + segment_target_duration = segment_duration |
| 51 | + |
| 52 | + cmd = [] |
| 53 | + cmd_abr = [] |
| 54 | + if loop: |
| 55 | + cmd_base = ["ffmpeg", "-hide_banner", "-y", "-stream_loop", "0", "-i", in_file] |
| 56 | + else: |
| 57 | + cmd_base = ["ffmpeg", "-hide_banner", "-y", "-i", in_file] |
| 58 | + |
| 59 | + cmd_static = ["-c:v", "libx264", "-profile:v", "main", "-sc_threshold", "0", "-strict", "-2"] |
| 60 | + cmd_static += ["-g", str(keyframe_interval), "-keyint_min", str(keyframe_interval)] |
| 61 | + cmd_dash = ["-use_timeline", "1", "-use_template", "1", "-seg_duration", |
| 62 | + str(segment_target_duration), "-adaptation_sets", "id=0,streams=v"] |
| 63 | + cmd_hls = ["-hls_time", str(segment_target_duration), "-hls_list_size", "0"] |
| 64 | + cmd_fade_in_out = ["-an"] |
| 65 | + |
| 66 | + master_playlist = "#EXTM3U" + "\n" + "#EXT-X-VERSION:3" +"\n" + "#" + "\n" |
| 67 | + |
| 68 | + count = 0 |
| 69 | + default_threshold = 4 |
| 70 | + |
| 71 | + for item in renditions: |
| 72 | + width = item[0] |
| 73 | + height = item[1] |
| 74 | + v_bitrate = to_kps(item[2]) |
| 75 | + a_bitrate = to_kps(item[3]) |
| 76 | + maxrate = to_kps(item[2] * max_bitrate_ratio) |
| 77 | + bufsize = to_kps(item[2] * rate_monitor_buffer_ratio) |
| 78 | + name = str(height) + "p" |
| 79 | + |
| 80 | + if frame_height < height: |
| 81 | + continue |
| 82 | + |
| 83 | + cmd_1 = [] |
| 84 | + cmd_2 = [] |
| 85 | + cmd_3 = [] |
| 86 | + cmd_4 = [] |
| 87 | + |
| 88 | + if streaming_type == "hls": |
| 89 | + cmd_1 = ["-vf", "scale=w="+str(width)+":"+"h="+str(height)+":"+"force_original_aspect_ratio=decrease" |
| 90 | + +","+ "pad=w="+str(width)+":"+"h="+str(height)+":"+"x=(ow-iw)/2"+":"+"y=(oh-ih)/2"] |
| 91 | + cmd_2 = ["-b:v", v_bitrate, "-maxrate", maxrate, "-bufsize", bufsize] |
| 92 | + cmd_3 = ["-f", streaming_type] |
| 93 | + cmd_4 = ["-hls_segment_filename", target+"/"+name+"_"+"%03d.ts", target+"/"+name+".m3u8"] |
| 94 | + master_playlist += "#EXT-X-STREAM-INF:BANDWIDTH="+str(item[2])+","+"RESOLUTION="+str(width)+"x"+str(height)+"\n"+name+".m3u8"+"\n" |
| 95 | + cmd_abr += cmd_static + cmd_1 + cmd_2 + cmd_fade_in_out + cmd_3 + cmd_hls + cmd_4 |
| 96 | + |
| 97 | + if streaming_type == "dash": |
| 98 | + cmd_1 = ["-map", "0:v", "-b:v"+":"+str(count), v_bitrate, "-s:v"+":"+str(count), str(width)+"x"+str(height), |
| 99 | + "-maxrate"+":"+str(count), maxrate, "-bufsize"+":"+str(count), bufsize] |
| 100 | + cmd_2 = ["-an"] |
| 101 | + cmd_3 = ["-f", streaming_type] |
| 102 | + cmd_4 = ["-init_seg_name", name+"-init-stream$RepresentationID$.m4s", "-media_seg_name", |
| 103 | + name+"-chunk-stream$RepresentationID$-$Number%05d$.m4s", "-y", target+"/"+name+".mpd"] |
| 104 | + if clip_a_duration == 0: |
| 105 | + cmd_1 = ["-map", "0:v", "-b:v"+":"+str(count), v_bitrate, "-s:v"+":"+str(count), str(width)+"x"+str(height), |
| 106 | + "-maxrate"+":"+str(count), maxrate, "-bufsize"+":"+str(count), bufsize] |
| 107 | + cmd_2 = [] |
| 108 | + cmd_abr += cmd_1 + cmd_2 |
| 109 | + |
| 110 | + count += 1 |
| 111 | + if default_threshold < count: |
| 112 | + break |
| 113 | + |
| 114 | + if streaming_type == "hls": |
| 115 | + cmd = cmd_base + cmd_abr |
| 116 | + elif streaming_type == "dash": |
| 117 | + cmd = cmd_base + cmd_static + cmd_abr +["-f", "dash"] + cmd_dash + ["-y", target+"/"+"index.mpd"] |
| 118 | + |
| 119 | + #generate master m3u8 file |
| 120 | + if streaming_type == "hls": |
| 121 | + with open(target+"/"+"index.m3u8", "w", encoding='utf-8') as f: |
| 122 | + f.write(master_playlist) |
| 123 | + |
| 124 | + return cmd |
0 commit comments