Skip to content
This repository was archived by the owner on Nov 11, 2024. It is now read-only.

Commit fe48322

Browse files
author
Pengfei Qu
committed
add common image
1 parent d6ad4ab commit fe48322

File tree

7 files changed

+281
-0
lines changed

7 files changed

+281
-0
lines changed

Diff for: common/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
set(service "ovc_cdn_common")
2+
include("${CMAKE_SOURCE_DIR}/script/service.cmake")

Diff for: common/Dockerfile

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
FROM centos:7.6.1810
3+
4+
RUN yum install -y -q epel-release && yum install -y -q python36-requests python36-ply python36-psutil && rm -rf /var/cache/yum/*
5+
6+
COPY *.py /home/
7+
ENV PYTHONIOENCODING=UTF-8
8+
9+
####
10+
ARG USER=docker
11+
ARG GROUP=docker
12+
ARG UID
13+
ARG GID
14+
## must use ; here to ignore user exist status code
15+
RUN [ ${GID} -gt 0 ] && groupadd -f -g ${GID} ${GROUP}; \
16+
[ ${UID} -gt 0 ] && useradd -d /home -M -g ${GID} -K UID_MAX=${UID} -K UID_MIN=${UID} ${USER}; \
17+
chown -R ${UID}:${GID} /home
18+
####
19+

Diff for: common/abr_hls_dash.py

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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

Diff for: common/build.sh

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash -e
2+
3+
IMAGE="ovc_cdn_common"
4+
DIR=$(dirname $(readlink -f "$0"))
5+
. "$DIR/../script/build.sh"

Diff for: common/messaging.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/python3
2+
3+
import socket
4+
from kafka import KafkaProducer, KafkaConsumer, TopicPartition
5+
import traceback
6+
import socket
7+
import time
8+
9+
KAFKA_HOSTS = ["kafka-service:9092"]
10+
11+
class Producer(object):
12+
def __init__(self):
13+
super(Producer, self).__init__()
14+
self._client_id = socket.gethostname()
15+
self._producer = None
16+
17+
def send(self, topic, message):
18+
if not self._producer:
19+
try:
20+
self._producer = KafkaProducer(bootstrap_servers=KAFKA_HOSTS,
21+
client_id=self._client_id,
22+
api_version=(0, 10), acks=0)
23+
except:
24+
print(traceback.format_exc(), flush=True)
25+
self._producer = None
26+
27+
try:
28+
self._producer.send(topic, message.encode('utf-8'))
29+
except:
30+
print(traceback.format_exc(), flush=True)
31+
32+
def flush(self):
33+
if self._producer:
34+
self._producer.flush()
35+
36+
def close(self):
37+
if self._producer:
38+
self._producer.close()
39+
self._producer=None
40+
41+
class Consumer(object):
42+
def __init__(self, group=None):
43+
super(Consumer, self).__init__()
44+
self._client_id = socket.gethostname()
45+
self._group = group
46+
47+
def messages(self, topic, timeout=None):
48+
c = KafkaConsumer(topic, bootstrap_servers=KAFKA_HOSTS, client_id=self._client_id,
49+
group_id=self._group, auto_offset_reset="earliest", api_version=(0, 10))
50+
51+
for msg in c:
52+
yield msg.value.decode('utf-8')
53+
c.close()
54+
55+
def debug(self, topic):
56+
c = KafkaConsumer(bootstrap_servers=KAFKA_HOSTS, client_id=self._client_id,
57+
group_id=None, api_version=(0, 10))
58+
59+
# assign/subscribe topic
60+
partitions = c.partitions_for_topic(topic)
61+
if not partitions:
62+
raise Exception("Topic "+topic+" not exist")
63+
c.assign([TopicPartition(topic, p) for p in partitions])
64+
65+
# seek to beginning if needed
66+
c.seek_to_beginning()
67+
68+
# fetch messages
69+
while True:
70+
partitions = c.poll(100)
71+
if partitions:
72+
for p in partitions:
73+
for msg in partitions[p]:
74+
yield msg.value.decode('utf-8')
75+
yield ""
76+
77+
c.close()

Diff for: common/shell.sh

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash -e
2+
3+
IMAGE="ovc_cdn_common"
4+
DIR=$(dirname $(readlink -f "$0"))
5+
6+
. "$DIR/../script/shell.sh"

Diff for: common/zkstate.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/python3
2+
3+
from kazoo.client import KazooClient
4+
from kazoo.exceptions import NoNodeError, NodeExistsError
5+
from kazoo.protocol.states import KazooState
6+
import traceback
7+
import time
8+
9+
ZK_HOSTS = 'zookeeper-service:2181'
10+
11+
class ZKState(object):
12+
def __init__(self, path, name=None):
13+
super(ZKState, self).__init__()
14+
options={"max_tries":-1, "max_delay":5, "ignore_expire":True}
15+
self._zk = KazooClient(hosts=ZK_HOSTS, connection_retry=options)
16+
try:
17+
self._zk.start(timeout=3600)
18+
except:
19+
print(traceback.format_exc(), flush=True)
20+
self._path = path
21+
self._name="" if name is None else name+"."
22+
self._zk.ensure_path(path)
23+
24+
def processed(self):
25+
return self._zk.exists(self._path+"/"+self._name+"complete")
26+
27+
def process_start(self):
28+
if self.processed():
29+
return False
30+
try:
31+
self._zk.create(self._path+"/"+self._name+"processing", ephemeral=True)
32+
return True
33+
except NodeExistsError: # another process wins
34+
return False
35+
36+
def process_end(self):
37+
try:
38+
self._zk.create(self._path+"/"+self._name+"complete")
39+
except NodeExistsError:
40+
pass
41+
42+
def process_abort(self):
43+
# the ephemeral node will be deleted upon close
44+
pass
45+
46+
def close(self):
47+
self._zk.stop()
48+
self._zk.close()

0 commit comments

Comments
 (0)