commit 6b48bd52b5e09b967508c8f3a4ebf981c7c7ed43 Author: Mustafa Yontar Date: Fri Apr 30 14:48:53 2021 +0300 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db62f6a --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.webm +*.mp4 +Dash/* +chunk/* +.venv +*.flv +*.jpg +*.mpd +*.m4s +*.pyd diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..8a932fd --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,26 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..cf36c21 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..3d44732 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/tryCanvas.iml b/.idea/tryCanvas.iml new file mode 100644 index 0000000..9655816 --- /dev/null +++ b/.idea/tryCanvas.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/StreamMerger.py b/StreamMerger.py new file mode 100644 index 0000000..2e9e80f --- /dev/null +++ b/StreamMerger.py @@ -0,0 +1,31 @@ +import time +from datetime import datetime +from threading import Thread + +from PIL import Image +from PIL import ImageDraw, ImageFont + +from VideoFile import VideoFile + + +class StreamMerger(Thread): + img = None + + def run(self) -> None: + video = VideoFile("yiv3.webm") + video2 = VideoFile("supernatural.webm") + video.open() + video2.open() + start_time = time.time() + while True: + img = Image.new('RGB', (1920, 1080), color='black') + + if video.frame is not None: + img.paste(video.frame) + if video2.frame is not None: + img.paste(video2.frame, (0, 600)) + fnt = ImageFont.truetype("antonio.ttf", 40) + d = ImageDraw.Draw(img) + d.text((0, 1000), str(int(time.time() - start_time)), font=fnt) + self.img = img + time.sleep(1.0 / 1000.0) diff --git a/VideoFile.py b/VideoFile.py new file mode 100644 index 0000000..eaa5804 --- /dev/null +++ b/VideoFile.py @@ -0,0 +1,43 @@ +import time +from threading import Thread + +import av +from PIL import Image + + +class VideoFile(Thread): + file: str = "" + frame: Image = None + running: bool = False + + def __init__(self, file: str): + self.file = file + super().__init__() + + def get_frame(self): + return self.frame + + def stop(self): + self.running = False + + def open(self): + self.running = True + self.start() + + def run(self) -> None: + container = av.open(self.file) + video = container.streams.video[0] + print(video.time_base) + + while self.running: + try: + for frame in container.decode(video): + + if self.running: + self.frame = frame.to_image() + else: + break + + time.sleep(1.0 / 60) + except: + self.running = False diff --git a/__pycache__/StreamMerger.cpython-38.pyc b/__pycache__/StreamMerger.cpython-38.pyc new file mode 100644 index 0000000..2b4bc6c Binary files /dev/null and b/__pycache__/StreamMerger.cpython-38.pyc differ diff --git a/__pycache__/VideoFile.cpython-38.pyc b/__pycache__/VideoFile.cpython-38.pyc new file mode 100644 index 0000000..c14ed9c Binary files /dev/null and b/__pycache__/VideoFile.cpython-38.pyc differ diff --git a/antonio.ttf b/antonio.ttf new file mode 100644 index 0000000..3463fbe Binary files /dev/null and b/antonio.ttf differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..d941ebe --- /dev/null +++ b/main.py @@ -0,0 +1,41 @@ +import time +from datetime import datetime + +import av +from PIL import Image +from av.frame import Fraction +from PIL import Image, ImageDraw, ImageFont + +from StreamMerger import StreamMerger +from VideoFile import VideoFile + +container = av.open('rtmp://localhost/live', mode='w', format='flv') +stream = container.add_stream('h264', framerate=60) # alibi frame rate +output_audio_stream = container.add_stream('aac', rate=44100) + +stream.width = 1920 +stream.height = 1080 +stream.pix_fmt = 'yuv420p' +# stream.bit_rate = 128000 +stream.time_base = Fraction(1, 60) + +st = StreamMerger() +st.start() +my_pts = 0 # [seconds] +frame_i = 0 +time_start = time.time() +while True: + if st.img is not None: + frame = av.VideoFrame.from_image(st.img) + framen = frame.reformat(format='yuv420p') + + ntime = time.time() - time_start + + frame.pts = ntime * 60 + frame.time_base = Fraction(1, 60) + # frame.key_frame = frame_i + frame_i += 1 + # print(dir(frame)) + for packet in stream.encode(framen): + container.mux(packet) + time.sleep(1.0 / 60.0) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..67321a8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +av +pillow +aiortc \ No newline at end of file diff --git a/stream.sh b/stream.sh new file mode 100644 index 0000000..4b74a38 --- /dev/null +++ b/stream.sh @@ -0,0 +1,37 @@ +#!/bin/bash +#HLS, Dash and fallback code from zazu.berlin 2020, Version 20200424 + +VIDEO_IN=rtmp://localhost/live +VIDEO_OUT=master +HLS_TIME=4 +FPS=25 +GOP_SIZE=100 +PRESET_P=veryslow +V_SIZE_1=960x540 +V_SIZE_2=416x234 +V_SIZE_3=640x360 +V_SIZE_4=768x432 +V_SIZE_5=1280x720 +V_SIZE_6=1920x1080 + + +# DASH +ffmpeg -i $VIDEO_IN -y \ + -preset $PRESET_P -keyint_min $GOP_SIZE -g $GOP_SIZE -sc_threshold 0 -r $FPS -c:v libx264 -pix_fmt yuv420p \ + -map v:0 -s:0 $V_SIZE_1 -b:v:0 2M -maxrate:0 2.14M -bufsize:0 3.5M \ + -map v:0 -s:1 $V_SIZE_2 -b:v:1 145k -maxrate:1 155k -bufsize:1 220k \ + -map v:0 -s:2 $V_SIZE_3 -b:v:2 365k -maxrate:2 390k -bufsize:2 640k \ + -map v:0 -s:3 $V_SIZE_4 -b:v:3 730k -maxrate:3 781k -bufsize:3 1278k \ + -map v:0 -s:4 $V_SIZE_4 -b:v:4 1.1M -maxrate:4 1.17M -bufsize:4 2M \ + -map v:0 -s:5 $V_SIZE_5 -b:v:5 3M -maxrate:5 3.21M -bufsize:5 5.5M \ + -map v:0 -s:6 $V_SIZE_5 -b:v:6 4.5M -maxrate:6 4.8M -bufsize:6 8M \ + -map v:0 -s:7 $V_SIZE_6 -b:v:7 6M -maxrate:7 6.42M -bufsize:7 11M \ + -map v:0 -s:8 $V_SIZE_6 -b:v:8 7.8M -maxrate:8 8.3M -bufsize:8 14M \ + -init_seg_name init\$RepresentationID\$.\$ext\$ -media_seg_name chunk\$RepresentationID\$-\$Number%05d\$.\$ext\$ \ + -use_template 1 -use_timeline 1 \ + -seg_duration 4 -adaptation_sets "id=0,streams=v id=1,streams=a" \ + -f dash Dash/dash.mpd + +# Fallback video file +ffmpeg -i $VIDEO_IN -y -c:v libx264 -pix_fmt yuv420p -r $FPS -s $V_SIZE_1 -b:v 1.8M -c:a aac -b:a 128k -ac 1 -ar 44100 fallback-video-$V_SIZE_1.mp4 + diff --git a/testsup.py b/testsup.py new file mode 100644 index 0000000..b7d57ac --- /dev/null +++ b/testsup.py @@ -0,0 +1,46 @@ +import av +from av.filter import Graph + + +def main(): + input_container = av.open('supernatural.webm', mode='r') + input_container2 = av.open('yiv3.webm', mode='r') + input_video_stream = input_container.streams.video[0] + + output_container = av.open("out.mp4", "w") + output_audio_stream = output_container.add_stream('aac', rate=input_container2.streams.audio[0].base_rate) + + graph = Graph() + + in_src = graph.add_abuffer(template=input_container.streams.audio[0]) + in_src2 = graph.add_abuffer(template=input_container2.streams.audio[0]) + pad = graph.add("amix", "inputs=2:duration=longest") + print(dir(pad)) + in_src.link_to(pad, 0, 0) + in_src2.link_to(pad, 0, 1) + sink = graph.add('abuffersink') + pad.link_to(sink) + graph.configure() + + for frame in input_container.decode(audio=0): + in_src.push(frame) + + for frame in input_container2.decode(audio=0): + in_src2.push(frame) + while True: + try: + filtered_frame = pad.pull() + output_container.mux(output_audio_stream.encode(filtered_frame)) + except: + break + + for packet in output_audio_stream.encode(): + output_container.mux(packet) + + input_container.close() + input_container2.close() + output_container.close() + + +if __name__ == '__main__': + main()