├── server ├── conf │ └── extra.conf ├── wsgi-scripts │ ├── wc_process.py │ ├── wc_input_source.py │ ├── wc_codecs.py │ ├── wc_utils.py │ ├── wc_stopencoder.py │ ├── wc_store_load_input_cfg.py │ ├── wc_restore_old_config.py │ ├── wc_encoder_status.py │ ├── wc_configdb.py │ ├── wc_config_handler.py │ ├── wc_capture.py │ ├── wc_ffmpeg_args.py │ └── wc_startencoder.py └── html │ └── index.html ├── install_scripts ├── requirements.txt ├── install.sh └── startServer.sh ├── test_scripts ├── utils │ ├── text.txt │ └── OpenSans-Bold.ttf ├── playlists │ └── playlist_url.txt ├── launchEncoderTestPattern.sh ├── launchEncoderPlaylist.sh └── launchEncoderTestPattern_withAudio.sh ├── Dockerfile └── readme.md /server/conf/extra.conf: -------------------------------------------------------------------------------- 1 | Header set Access-Control-Allow-Origin "*" 2 | -------------------------------------------------------------------------------- /install_scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | mod-wsgi==4.6.5 2 | psutil==5.4.8 3 | webapp2==2.5.2 4 | WebOb==1.8.4 5 | -------------------------------------------------------------------------------- /test_scripts/utils/text.txt: -------------------------------------------------------------------------------- 1 | Encoder time 2 | %{localtime:%M}:%{localtime:%S }:%{eif:1M*t-1K*trunc(t*1K):d} 3 | -------------------------------------------------------------------------------- /test_scripts/utils/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michael-riha/cmafBroadcaster/HEAD/test_scripts/utils/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /install_scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-64bit-static.tar.xz 3 | 4 | mkdir ffmpeg-64bit-static-build 5 | 6 | tar xf ffmpeg-git-64bit-static.tar.xz -C ffmpeg-64bit-static-build --strip-components 1 7 | 8 | mv ffmpeg-64bit-static-build/ ffmpeg/ 9 | 10 | rm ffmpeg-git-64bit-static.tar.xz 11 | 12 | pip install -r /tmp/requirements.txt 13 | -------------------------------------------------------------------------------- /test_scripts/playlists/playlist_url.txt: -------------------------------------------------------------------------------- 1 | #https://github.com/FFmpeg/FFmpeg/blob/master/tests/extended.ffconcat 2 | 3 | file 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' 4 | duration 5.0 5 | # results in an error! 6 | #inpoint 00:00.00 7 | #outpoint 00:04.00 8 | 9 | file 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4' 10 | duration 5.0 11 | 12 | file 'playlist_url.txt' 13 | -------------------------------------------------------------------------------- /test_scripts/launchEncoderTestPattern.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Encoding settings for x264 (CPU based encoder) 3 | 4 | x264enc='libx264 -tune zerolatency -profile:v high -preset veryfast -bf 0 -refs 3 -sc_threshold 0' 5 | 6 | /ffmpeg/ffmpeg \ 7 | -hide_banner \ 8 | -re \ 9 | -f lavfi \ 10 | -i "testsrc2=size=1920x1080:rate=30" \ 11 | -pix_fmt yuv420p \ 12 | -map 0:v \ 13 | -c:v ${x264enc} \ 14 | -g 150 \ 15 | -keyint_min 150 \ 16 | -b:v 4000k \ 17 | -vf "fps=30,drawtext=fontfile=/tmp/utils/OpenSans-Bold.ttf:box=1:fontcolor=black:boxcolor=white:fontsize=100':x=40:y=400:textfile=/tmp/utils/text.txt" \ 18 | -seg_duration 5 \ 19 | -streaming 1 \ 20 | -utc_timing_url "https://time.akamai.com/?iso" \ 21 | -index_correction 1 \ 22 | -use_timeline 0 \ 23 | -media_seg_name 'chunk-stream-$RepresentationID$-$Number%05d$.m4s' \ 24 | -init_seg_name 'init-stream1-$RepresentationID$.m4s' \ 25 | -window_size 5 \ 26 | -extra_window_size 10 \ 27 | -remove_at_exit 1 \ 28 | -adaptation_sets "id=0,streams=v" \ 29 | -f dash \ 30 | $OUTPUT_FOLDER/manifest.mpd 31 | #>/dev/null 2>logs/encode.log & 32 | -------------------------------------------------------------------------------- /test_scripts/launchEncoderPlaylist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Encoding settings for x264 (CPU based encoder) 3 | 4 | x264enc='libx264 -tune zerolatency -profile:v high -preset veryfast -bf 0 -refs 3 -sc_threshold 0' 5 | 6 | /ffmpeg/ffmpeg \ 7 | -hide_banner \ 8 | -re \ 9 | -f concat \ 10 | -safe 0 \ 11 | -protocol_whitelist file,http,https,tcp,tls \ 12 | -i /tmp/playlists/playlist_url.txt \ 13 | -pix_fmt yuv420p \ 14 | -map 0:v \ 15 | -c:v ${x264enc} \ 16 | -g 150 \ 17 | -keyint_min 150 \ 18 | -b:v 4000k \ 19 | -vf "fps=30,drawtext=fontfile=/tmp/utils/OpenSans-Bold.ttf:box=1:fontcolor=black:boxcolor=white:fontsize=100':x=40:y=400:textfile=/tmp/utils/text.txt" \ 20 | -seg_duration 5 \ 21 | -streaming 1 \ 22 | -utc_timing_url "https://time.akamai.com/?iso" \ 23 | -index_correction 1 \ 24 | -use_timeline 0 \ 25 | -media_seg_name 'chunk-stream-$RepresentationID$-$Number%05d$.m4s' \ 26 | -init_seg_name 'init-stream1-$RepresentationID$.m4s' \ 27 | -window_size 5 \ 28 | -extra_window_size 10 \ 29 | -remove_at_exit 1 \ 30 | -adaptation_sets "id=0,streams=v" \ 31 | -f dash \ 32 | $OUTPUT_FOLDER/manifest.mpd 33 | #>/dev/null 2>logs/encode.log & 34 | -------------------------------------------------------------------------------- /server/wsgi-scripts/wc_process.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, Akamai Technologies 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import psutil 16 | import time 17 | import subprocess 18 | 19 | def is_process_active(pid): 20 | try: 21 | psproc = psutil.Process(pid) 22 | proc_status = psproc.status() 23 | if proc_status.lower() != 'zombie': 24 | return True 25 | else: 26 | return False 27 | except: 28 | return False 29 | 30 | def wait_for_process(pid, timeout=None): 31 | start_time = time.time() 32 | while True == is_process_active(pid): 33 | if timeout is not None and (time.time() - start_time) >= timeout: 34 | raise psutil.TimeoutExpired("Process is still alive") 35 | time.sleep(0.1) -------------------------------------------------------------------------------- /test_scripts/launchEncoderTestPattern_withAudio.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Encoding settings for x264 (CPU based encoder) 3 | 4 | x264enc='libx264 -tune zerolatency -profile:v high -preset veryfast -bf 0 -refs 3 -sc_threshold 0' 5 | 6 | /ffmpeg/ffmpeg \ 7 | -hide_banner \ 8 | -re \ 9 | -f lavfi \ 10 | -i "testsrc2=size=1920x1080:rate=30" \ 11 | -f lavfi \ 12 | -i "sine=frequency=800:sample_rate=48000" \ 13 | -preset:v superfast \ 14 | -pix_fmt yuv420p \ 15 | -map 0:v \ 16 | -map 1:a \ 17 | -c:v ${x264enc} \ 18 | -g 150 \ 19 | -keyint_min 150 \ 20 | -b:v 4000k \ 21 | -vf "fps=30,drawtext=fontfile=/tmp/utils/OpenSans-Bold.ttf:box=1:fontcolor=black:boxcolor=white:fontsize=100':x=40:y=400:textfile=/tmp/utils/text.txt" \ 22 | -c:a aac -ar 48000 -ac 2 \ 23 | -seg_duration 5 \ 24 | -streaming 1 \ 25 | -utc_timing_url "https://time.akamai.com/?iso" \ 26 | -index_correction 1 \ 27 | -use_timeline 0 \ 28 | -media_seg_name 'chunk-stream-$RepresentationID$-$Number%05d$.m4s' \ 29 | -init_seg_name 'init-stream1-$RepresentationID$.m4s' \ 30 | -window_size 5 \ 31 | -extra_window_size 10 \ 32 | -remove_at_exit 1 \ 33 | -adaptation_sets "id=0,streams=v id=1,streams=a" \ 34 | -f dash \ 35 | $OUTPUT_FOLDER/manifest.mpd 36 | #>/dev/null 2>logs/encode.log & 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | LABEL Maintainer="Michael Riha (rihabitmovin/cmafbroadcaster)" 3 | 4 | RUN apt-get update && apt-get install -y \ 5 | sudo \ 6 | python-minimal \ 7 | python-pip \ 8 | wget \ 9 | apache2 \ 10 | apache2-dev 11 | #apache2-utils 12 | #&& mkdir /tmp 13 | 14 | #https://vsupalov.com/docker-arg-env-variable-guide/ 15 | ENV OUTPUT_FOLDER="/out" 16 | ENV STREAM="with_audio" 17 | 18 | 19 | COPY install_scripts /tmp 20 | COPY test_scripts /tmp 21 | COPY server /srv 22 | 23 | # https://stackoverflow.com/questions/27701930/add-user-to-docker-container wsgi can not run as root! 24 | RUN useradd -ms /bin/bash wsgi \ 25 | && sudo adduser wsgi sudo \ 26 | && chown wsgi:wsgi -R tmp \ 27 | && chmod -R +x tmp \ 28 | && chown wsgi:wsgi -R /srv \ 29 | && passwd wsgi -d \ 30 | #even create the output 31 | && sudo mkdir $OUTPUT_FOLDER \ 32 | && sudo chown wsgi:wsgi -R $OUTPUT_FOLDER \ 33 | && sudo chmod g+rw -R $OUTPUT_FOLDER \ 34 | && ls -la $OUTPUT_FOLDER 35 | 36 | VOLUME ["/out"] 37 | VOLUME ["/tmp/test_scripts/playlists"] 38 | 39 | RUN sh /tmp/install.sh # install ffmpeg prebuild and python stuff 40 | 41 | #https://stackoverflow.com/questions/25845538/how-to-use-sudo-inside-a-docker-container 42 | USER wsgi 43 | 44 | # Make port 8000 available to the world outside this container 45 | EXPOSE 8000 46 | 47 | ENTRYPOINT ["/tmp/startServer.sh"] 48 | -------------------------------------------------------------------------------- /install_scripts/startServer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "starting the server now! ($STREAM)" 3 | echo "OUTPUT @-> $OUTPUT_FOLDER" 4 | 5 | # https://stackoverflow.com/questions/59838/check-if-a-directory-exists-in-a-shell-script 6 | if [ ! -d "$OUTPUT_FOLDER" ]; then 7 | # Control will enter here if $DIRECTORY exists. 8 | echo "have to create ouput folder!" 9 | sudo mkdir $OUTPUT_FOLDER 10 | sudo chown wsgi:wsgi -R $OUTPUT_FOLDER 11 | sudo chmod g+rw -R $OUTPUT_FOLDER 12 | ls -la $OUTPUT_FOLDER 13 | fi 14 | 15 | #start a teststream https://unix.stackexchange.com/questions/222847/how-to-stream-with-ffmpeg-in-a-separate-process 16 | #./launchEncoderTestPattern.sh > /out/output.log 2>&1 < /dev/null & 17 | #/tmp/launchEncoderTestPattern.sh > /out/output.log 2>&1 < /dev/null & 18 | #/tmp/launchEncoderTestPattern_withAudio.sh > /out/output.log 2>&1 < /dev/null & 19 | 20 | case "$STREAM" in 21 | "with_audio") 22 | echo "starting stream with audio" 23 | /tmp/launchEncoderTestPattern_withAudio.sh > /out/output.log 2>&1 < /dev/null & 24 | ;; 25 | "playlist") 26 | echo "starting stream from a playlist" 27 | /tmp/launchEncoderPlaylist.sh > /out/output.log 2>&1 < /dev/null & 28 | ;; 29 | *) 30 | echo "starting stream without audio" 31 | /tmp/launchEncoderTestPattern.sh > /out/output.log 2>&1 < /dev/null & 32 | ;; 33 | esac 34 | 35 | mod_wsgi-express start-server --chunked-request --url-alias /static $OUTPUT_FOLDER --include-file srv/conf/extra.conf /srv/wsgi-scripts/wc_config_handler.py 36 | -------------------------------------------------------------------------------- /server/wsgi-scripts/wc_input_source.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, Akamai Technologies 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import sys 17 | import urlparse 18 | 19 | working_dir = os.path.dirname(os.path.realpath(__file__)) 20 | sys.path.append(working_dir) 21 | 22 | import wc_capture as wc_capture 23 | 24 | def add_input(input_config): 25 | #TODO (rpatagar) add validation for input_config, test case also 26 | parsed_url = urlparse.urlparse(input_config['input']['input_url']) 27 | if False == (bool(parsed_url.scheme)): 28 | msg = ' Invalid input_url ' + str(input_config['input']['input_url']) 29 | return 400, msg 30 | 31 | input_config['input']['input_interface'] = wc_capture.INPUT_INTERFACE_URL 32 | statuscode, msg = wc_capture.add_input_source(input_config) 33 | return statuscode, msg 34 | 35 | def remove_input(input_src_id=None): 36 | ret_status, ret_reason = wc_capture.remove_input_source(input_src_id) 37 | return ret_status, ret_reason 38 | -------------------------------------------------------------------------------- /server/wsgi-scripts/wc_codecs.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, Akamai Technologies 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import subprocess 16 | import re 17 | import os 18 | import sys 19 | 20 | working_dir = os.path.dirname(os.path.realpath(__file__)) 21 | sys.path.append(working_dir) 22 | import wc_configdb as configdb 23 | 24 | def refresh_codecs(): 25 | supported_codecs_list = ['libx264', 'h264_videotoolbox', 'libvpx-vp9', 'libx265'] 26 | device_name_query = "ffmpeg -encoders" 27 | proc = subprocess.Popen(device_name_query, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 28 | out, err = proc.communicate() 29 | configdb.delete_config('Codecs') 30 | for codec in supported_codecs_list: 31 | if out.find(codec) != -1: 32 | print codec 33 | codec_cfg = {'Name' : codec} 34 | configdb.insert_config(codec_cfg, 'Codecs') 35 | 36 | def get_codecs(): 37 | codec_list = [] 38 | codec_list_db = configdb.get_config('Codecs') 39 | if len(codec_list_db) == 0: 40 | refresh_codecs() 41 | codec_list_db = configdb.get_config('Codecs') 42 | for codec in codec_list_db: 43 | codec_list.append(codec['Name']) 44 | return codec_list 45 | 46 | if __name__ == "__main__": 47 | refresh_codecs() -------------------------------------------------------------------------------- /server/wsgi-scripts/wc_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, Akamai Technologies 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import urlparse 16 | import time 17 | import os 18 | import sys 19 | import re 20 | import datetime 21 | import json 22 | import socket 23 | 24 | working_dir = os.path.dirname(os.path.realpath(__file__)) 25 | sys.path.append(working_dir) 26 | import wc_capture as capture 27 | import wc_configdb as configdb 28 | import wc_stopencoder as stopencoder 29 | import wc_store_load_input_cfg as store_load_input_cfg 30 | 31 | 32 | def to_int(n_str): 33 | try: 34 | n = int(n_str) 35 | except: 36 | n = -1 37 | return n 38 | 39 | def clamp(n, smallest, largest): 40 | return min(largest, max(n, smallest)) 41 | 42 | def map_vid_width_height(vid_res): 43 | try: 44 | vid_res_map = { 45 | '1080p': [1920, 1080], 46 | '720p': [1280, 720], 47 | '480p': [720, 480], 48 | '360p': [640, 360], 49 | '240p': [426, 240] 50 | } 51 | return vid_res_map[vid_res][0], vid_res_map[vid_res][1] 52 | except: 53 | return -1, -1 54 | 55 | def map_fps_num_den(fps): 56 | try: 57 | fps_num_den = { 58 | '23.98': ['24000', '1001'], 59 | '24': ['24', '1'], 60 | '25': ['25', '1'], 61 | '29.97': ['30000', '1001'], 62 | '30': ['30', '1'], 63 | '50': ['50', '1'], 64 | '59.94': ['60000', '1001'], 65 | '60': ['60', '1'] 66 | } 67 | return fps_num_den[fps][0], fps_num_den[fps][1] 68 | except: 69 | return fps, '1' -------------------------------------------------------------------------------- /server/wsgi-scripts/wc_stopencoder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, Akamai Technologies 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import sqlite3 16 | import os 17 | import sys 18 | import urlparse 19 | import psutil 20 | import time 21 | import signal 22 | import json 23 | import urllib 24 | 25 | working_dir = os.path.dirname(os.path.realpath(__file__)) 26 | sys.path.append(working_dir) 27 | import wc_configdb as configdb 28 | import wc_capture as capture 29 | import wc_process as process 30 | import wc_store_load_input_cfg as store_load_input_cfg 31 | 32 | def stop_encoder(input_id=None): 33 | 34 | if input_id == None: 35 | inp_src = configdb.get_config('CapInputNames') 36 | else: 37 | inp_src = configdb.get_config('CapInputNames', {'InputId': input_id}) 38 | if len(inp_src) == 0: 39 | return 400, 'Bad Request: Invalid Id' 40 | 41 | for i in range(0, len(inp_src)): 42 | input_id = inp_src[i]['InputId'] 43 | cfg = configdb.get_config('StreamConfig', {'InputID': str(input_id)}) 44 | if cfg: 45 | pid = int(cfg[0]['ProcessID']) 46 | try: 47 | if True == process.is_process_active(pid): 48 | psproc = psutil.Process(pid) 49 | #Terminate signal doesn't reach WSGI process. Using SIGUSR2 as a workaround. 50 | #psproc.send_signal(signal.SIGTERM); 51 | psproc.send_signal(signal.SIGUSR2); 52 | process.wait_for_process(pid, timeout=7) 53 | except psutil.AccessDenied as err: 54 | print ("Access Denied err ", err.msg) 55 | except psutil.TimeoutExpired as err: 56 | print ("Time out expired ", err.msg) 57 | psproc.kill() 58 | except: 59 | print ("No such process ") 60 | 61 | configdb.delete_config('StreamConfig', {'InputID': input_id}) 62 | 63 | store_load_input_cfg.delete_json_cfg(input_id) 64 | 65 | return 200, 'OK' 66 | -------------------------------------------------------------------------------- /server/wsgi-scripts/wc_store_load_input_cfg.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018, Akamai Technologies 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import json 16 | import os 17 | import sys 18 | import re 19 | 20 | working_dir = os.path.dirname(os.path.realpath(__file__)) 21 | sys.path.append(working_dir) 22 | import wc_configdb 23 | 24 | def get_json_cfg_filename(input_id): 25 | json_dir = wc_configdb.get_config_path() 26 | return json_dir + 'enc_%s.json'%(input_id) 27 | 28 | def get_input_json_cfg_filename(input_id): 29 | json_dir = wc_configdb.get_config_path() 30 | return json_dir + 'input_%s.json'%(input_id) 31 | 32 | def get_all_json_cfgs(regexp): 33 | file_list = [] 34 | try: 35 | json_dir = wc_configdb.get_config_path() 36 | for dirpath, dirnames, filenames in os.walk(json_dir): 37 | for filename in filenames: 38 | match = re.match(regexp, filename) 39 | if match != None: 40 | file_list.append(read_json_cfg_from_file(os.path.join(json_dir, filename))) 41 | except: 42 | pass 43 | 44 | return file_list 45 | 46 | def get_all_enc_json_cfgs(): 47 | file_list = get_all_json_cfgs('enc\_\d+\.json') 48 | return file_list 49 | 50 | def get_all_input_json_cfgs(): 51 | file_list = get_all_json_cfgs('input\_\d+\.json') 52 | return file_list 53 | 54 | def store_json_cfg(enc_params): 55 | try: 56 | filename = get_json_cfg_filename(enc_params['input_id']) 57 | fp = open(filename, 'w') 58 | json.dump(enc_params, fp, indent=4) 59 | fp.close() 60 | except Exception as e: 61 | print e 62 | pass 63 | 64 | def store_input_json_cfg(input_config): 65 | try: 66 | filename = get_input_json_cfg_filename(input_config['input_id']) 67 | fp = open(filename, 'w') 68 | json.dump(input_config, fp, indent=4) 69 | fp.close() 70 | except Exception as e: 71 | print 'error', e 72 | pass 73 | 74 | def read_json_cfg_from_file(filename): 75 | try: 76 | fp = open(filename, 'r') 77 | enc_params = json.load(fp) 78 | fp.close() 79 | return enc_params 80 | except Exception as e: 81 | print e 82 | return {} 83 | 84 | def get_json_cfg(input_id): 85 | filename = get_json_cfg_filename(input_id) 86 | enc_params = read_json_cfg_from_file(filename) 87 | return enc_params 88 | 89 | def get_input_json_cfg(input_id): 90 | filename = get_json_cfg_filename(get_input_json_cfg_filename(input_id)) 91 | input_config = read_json_cfg_from_file(filename) 92 | return input_config 93 | 94 | def delete_json_cfg(input_id): 95 | try: 96 | os.remove(get_json_cfg_filename(input_id)) 97 | except OSError: 98 | pass 99 | 100 | def delete_input_json_cfg(input_id): 101 | try: 102 | os.remove(get_input_json_cfg_filename(input_id)) 103 | except OSError: 104 | pass 105 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | ## CMAF Broadcaster a remix of two Projects in a Container 3 | 4 | pretty much everything is copied from two sources, so all the credits to them. 5 | 6 | - https://github.com/streamlinevideo/low-latency-preview ( @colleenkhenry ) 7 | - https://github.com/jkarthic-akamai/ABR-Broadcaster/ ( @jkarthic-akamai ) 8 | 9 | This project is just a tiny feasibility study! 10 | Is is very limited at the moment, but I will add stuff. ;-) 11 | 12 | ### installation 13 | _The project is very rudimentary for the moment._ 14 | 15 | build the docker container with 16 | `docker build -t=