How make_playlist.py Parses Markdown to Create M3U8 Playlists in Free-TV/IPTV
The make_playlist.py script converts Markdown tables containing TV channel data into standards-compliant M3U8 playlists by scanning for [>] markers, parsing pipe-delimited rows into Channel objects, and serializing them into EXTINF entries with metadata attributes.
The Free-TV/IPTV repository maintains human-readable channel lists in Markdown format that require conversion into machine-readable M3U8 playlists for IPTV clients. The make_playlist.py utility automates this transformation by parsing structured Markdown tables and extracting stream URLs, logos, and metadata to generate playable playlists.
Overview of the Markdown-to-M3U8 Pipeline
The parsing process operates through three distinct stages defined in make_playlist.py. First, the script discovers source files in the repository's lists/ directory. Next, it extracts channel data from Markdown table rows marked with specific tokens. Finally, it serializes this data into the M3U8 format using the Channel class's conversion methods.
Stage 1: Collecting Source Markdown Files
According to the source code in make_playlist.py (lines 38-44), the script begins by scanning the lists/ directory for all *.md files while explicitly excluding README.md. Each discovered file represents a country or regional group containing channel definitions in Markdown table format.
Stage 2: Extracting Channel Data from Markdown Tables
Identifying Channel Rows with the [>] Marker
The parser identifies valid channel entries by searching for lines containing the [>] token (lines 52-55 in make_playlist.py). This marker distinguishes actual channel data rows from header rows or descriptive text within the Markdown tables.
Parsing the Pipe-Delimited Format
Once identified, each channel row is passed to the Channel class constructor defined in make_playlist.py (lines 94-106). The constructor splits the line on the pipe character (|) and trims whitespace from each column:
class Channel:
def __init__(self, group, md_line, country_code=""):
parts = md_line.strip().split("|")
self.number = parts[1].strip()
self.name = parts[2].strip()
Extracting URLs and Logos
The URL column contains the stream address wrapped in parentheses, so the constructor strips these delimiters to isolate the raw URL (lines 103-104). For the logo column, which contains an HTML <img> tag, the script extracts the src attribute value:
# Strip surrounding parentheses from URL
self.url = parts[3].strip()
self.url = self.url[self.url.find("(")+1:self.url.rfind(")")]
# Extract src attribute from img tag
self.logo = parts[4].strip()
self.logo = self.logo[self.logo.find('src="')+5:self.logo.rfind('"')]
If a sixth column exists, the parser stores it as an optional EPG identifier (lines 108-112).
Stage 3: Generating M3U8 Entries
The Channel.to_m3u_line() method (lines 114-121) constructs standards-compliant M3U8 entries. Each entry begins with an #EXTINF line containing metadata attributes followed by the raw stream URL.
The method always includes tvg-name, tvg-logo, and group-title attributes. It conditionally appends tvg-country (when a country code exists in the COUNTRY_CODES map), tvg-chno (for channel numbers), and tvg-id (when EPG data is present).
A complete M3U8 entry output by the script follows this structure:
#EXTINF:-1 tvg-name="NBC" tvg-logo="https://example.com/nbc.png" tvg-id="nbcn" tvg-chno="101" tvg-country="US" group-title="Usa",NBC
http://example.com/nbc.m3u8
Aggregating Playlists and Writing Output
After processing all channels, make_playlist.py writes the results to two locations. The master playlist.m3u8 file contains every channel discovered across all source files. Additionally, the script generates country-specific playlists in the playlists/ directory using the naming convention playlist_<country>.m3u8.
Both output files begin with a header section that includes EPG URLs sourced from epglist.txt (lines 31-35), enabling electronic program guide functionality for compatible IPTV clients.
Summary
make_playlist.pyscans thelists/directory for Markdown files, excludingREADME.md.- Channel rows are identified by the
[>]marker and parsed using theChannelclass constructor. - The parser extracts stream URLs from parentheses, logos from
<img>tags, and optional EPG identifiers from the sixth column. - The
to_m3u_line()method generates#EXTINFentries with metadata attributes includingtvg-name,tvg-logo, andgroup-title. - Output is written to both a master
playlist.m3u8and country-specific files inplaylists/, prefixed with EPG URLs fromepglist.txt.
Frequently Asked Questions
What format does the markdown channel data use?
The markdown files in lists/ use pipe-delimited tables where each channel occupies one row. Valid rows begin with the [>] marker followed by columns for channel number, name, URL (in parentheses), logo (as an HTML <img> tag), and an optional EPG identifier.
How does make_playlist.py handle the channel logo URLs?
Inside Channel.__init__ in make_playlist.py, the logo column containing an HTML <img> tag is processed by extracting the value of the src attribute using string slicing operations. This isolates the raw image URL from the surrounding HTML markup.
What is the purpose of the [>] marker in the markdown files?
The [>] token serves as a row identifier that distinguishes actual channel data from header rows or comments in the Markdown tables. The parser specifically checks for this substring (lines 52-55) to determine which lines to process as channel entries.
Where does make_playlist.py output the generated playlists?
The script generates two types of output files: a master playlist.m3u8 in the repository root containing all channels, and individual country-specific files named playlists/playlist_<country>.m3u8 that contain only channels from that specific region.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →