
This guide shows how to capture OBS YouTube HLS stream output and save m3u8/ts files. Sharing the code and usage of PushCap, a Python-based HLS stream receiving server for capturing and debugging HLS PUSH streaming software
PushCap
One of the challenges the writer faced while developing the YouTube HLS PUSH module for Wowza Streaming Engine was validating the module’s output.
The Wowza Streaming Engine’s logs showed that the module was working as expected and that network traffic was active. However, YouTube could not recognize the stream input. This led the writer to realize the need to compare the module’s output data with the data from OBS (Open Broadcast Software).
To solve this propose, the writer built a HLS data debug capture server with Python to save and check the incoming HLS PUSH data. And, named it PushCap, inspired by the memorable song shown below.
Running the Server
Preparing the Environment
- Install Python
PushCap
is a program based on Python. Before proceeding, ensure Python is installed on the PC that will act as the server. If needed, consult a guide on how to install Python. - Install required packages
Install
flask
package andtornado
package to execute the script using the following command.py -m pip install flask
py -m pip install tornado
- Create an HTTPS Certificate
Modern HLS protocols use HTTPS, which requires a certificate. Readers should generate one for this purpose. For detailed instructions, refer to the guide on Applying a Private HTTPS Certificate to Wowza Streaming Engine.
- Create the Program File
Create a new directory. Then, copy the code provided below and save it in a file named
PushCap.py
.
import os
import ssl
import threading
import asyncio
from flask import Flask, request, send_from_directory
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
app = Flask(__name__)
UPLOAD_DIR = os.path.join(os.path.dirname(__file__), 'uploads')
playlist_counters = {}
counter_lock = threading.Lock()
@app.route('/http_upload_hls', methods=['PUT'])
def hls_ingest():
try:
stream_key = request.args.get('cid')
file_name = request.args.get('file')
if not stream_key or not file_name:
return 'No file name or stream ID.', 400
stream_dir = os.path.join(UPLOAD_DIR, stream_key)
os.makedirs(stream_dir, exist_ok=True)
file_path = os.path.join(stream_dir, file_name)
request_body = request.environ['wsgi.input'].read()
with open(file_path, 'wb') as f:
f.write(request_body)
file_size = len(request_body)
if file_size == 0:
print(f'Warning: 0-byte file: {stream_key}/{file_name}')
else:
print(f'저장: {stream_key}/{file_name} ({file_size} bytes)')
// print(f'saved: {stream_key}/{file_name} ({file_size} bytes)')
if file_name.endswith('.m3u8'):
with counter_lock:
playlist_counters.setdefault(stream_key, {})
current_counter = playlist_counters[stream_key].get(file_name, 0)
playlist_counters[stream_key][file_name] = current_counter + 1
base_name, extension = os.path.splitext(file_name)
new_file_name = f'{base_name}.{current_counter}{extension}'
new_file_path = os.path.join(stream_dir, new_file_name)
with open(new_file_path, 'wb') as f:
f.write(request_body)
print(f'-> M3U8 순차 저장 : {stream_key}/{new_file_name}')
//print(f'-> M3U8 Saved with serial number : {stream_key}/{new_file_name}')
return 'OK', 200
except Exception as e:
print(f"HLS ingest error: {e}")
return 'Internal Server Error', 500
@app.route('/streams//', methods=['GET'])
def hls_playback(stream_key, file_name):
stream_dir = os.path.join(UPLOAD_DIR, stream_key)
return send_from_directory(stream_dir, file_name)
if __name__ == '__main__':
async def main():
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_ctx.load_cert_chain(certfile='cert.pem', keyfile='key.pem')
http_server = HTTPServer(
WSGIContainer(app),
ssl_options=ssl_ctx
)
http_server.listen(443, address='0.0.0.0')
print("Starting PushCap HTTPS server on port 443...")
print("스트림을 쏘세요!")
//print("Ready To receive!")
await asyncio.Event().wait()
asyncio.run(main())
Execute the PushCap
In a console window, navigate to the directory containing PushCap.py
and run it. If a message saying 스트림을 쏘세요!(Ready to receive!) is displayed, it means the program is running and ready to receive the HLS stream.
Microsoft Windows [Version 10.0.20348.4052]
(c) Microsoft Corporation. All rights reserved.
D:\>cd HLSWEB
D:\HLSWEB>py PushCap.py
Starting PushCap HTTPS server on port 443...
스트림을 쏘세요!
Capturing OBS’s YouTube HLS Output
Modifying the hosts
OBS uses a hardcoded address for the default YouTube HLS ingest server. As a result, it always sends data directly to YouTube. To capture this traffic, readers must redirect this address to point to the server where PushCap
is running.
YouTube uses a.upload.youtube.com
and b.upload.youtube.com
as its HLS data upload addresses. When OBS tries to access them, the operating system looks up the corresponding IP address. By modifying the hosts
file, which the OS checks first in this process, readers can redirect the connection away from the real YouTube servers.
The hosts
file is a plain text file with no extension, and it uses the same name on both Windows and Linux. The default paths for this file are as follows:
- Windows
C:\Windows\System32\drivers\etc\hosts
- Linux
/etc/hosts
Notepad
or nano
, readers should change the YouTube upload addresses to point to the IP address of the host running PushCap
. The following is an example of a modified hosts
file on Windows
.
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host
127.0.0.1 a.upload.youtube.com
# 127.0.0.1 b.upload.youtube.com
# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost
In the example above, the main upload address is pointed to the local host where PushCap
is running. This implies that OBS
and PushCap
are running on the one same server. The backup address line is prefixed with a #
, which marks it as a comment, leaving it unchanged. The hosts
file on Linux can be modified in the exact same way.
Capture the OBS HLS PUSH output stream
PushCap
is now ready to receive the stream. When readers start a YouTube HLS stream from OBS
, PushCap
will intercept and receive the stream data.
- Select HLS PUSH
In the
OBS
settings window, navigate to the Stream. Then, selectYouTube-HLS
from the service list. - Set the Stream Key
Select the
Use Stream Key
option. In the Stream Key field, enter any random value. The specific format or length does not matter. The key shown in the example screenshot is a random value created by the writer. - Start Streaming
Click the
Start Streaming
to start streaming. 을 선택해, After starting the stream, readers can confirm that a connection is made toPushCap
and that stream data is being uploaded.
Checking the Captured HLS Data
After stopp the streaming in OBS
, readers can check the directory containing the PushCap
file. A new subdirectory named upload
will have been created. Inside this directory, there will be another folder named after the stream key used, which contains the captured HLS data files from the OBS
.

Files with the .ts
extension are the video segments, saved exactly as uploaded by OBS
. The .m3u8
playlist files are saved with a sequential number each time when the OBS
uploads a new version. The unnumbered .m3u8
file is a copy of the last playlist uploaded by OBS
Verifying the Data
Now, readers can verify that the captured data files were generated correctly. In the case of YouTube, readers can refer to the YouTube HLS Ingestion specifications to verify that the captured data conforms to the required HLS format.
Conclusion
The best way to confirm that a program is working correctly is to verify that its output is as expected. The PushCap
allows readers to directly inspect the HLS stream output from their streaming programs. The writer hopes this article has provided a helpful hint for readers who need to verify HLS stream data.
FAQ
- What does HLS streaming data consist of?
- HLS streaming data consists of
.ts
files, which contain the actual video data, and.m3u8
files, which are playlists that define the playback order of the video files. - What information is included in an .m3u8 file?
- It includes the m3u8 version, the duration of each video segment, playback sequence numbers, and the names of the video files for each sequence. Additionally, it can contain data for SCTE-35 digital ad markers and information for adaptive bitrate streaming, such as different video files for various bitrates.
- What is the chunk size for OBS?
- For YouTube HLS streaming, OBS creates 2-second chunks. YouTube’s recommended chunk size between 2~4 seconds.
- What is the difference between YouTube’s HLS ingest and a general HLS server?
- YouTube’s HLS PUSH URL includes parameters like
cid
and a filename. In contrast, general HLS servers typically require files to be uploaded directly to some fixed path. Because of this difference, some streaming software that supports HLS PUSH may still be incompatible with YouTube if it cannot handle this specific URL format.