How IPTV Stream URLs Are Parsed from Markdown Tables in Free-TV/IPTV
The Free-TV/IPTV repository extracts IPTV stream URLs from markdown tables by scanning for [>] markers in the fourth column, parsing the embedded hyperlink syntax to isolate the URL, and converting valid rows into M3U8 playlist entries using the Channel class in make_playlist.py.
The Free-TV/IPTV project generates playable IPTV playlists by processing human-readable markdown tables stored in the lists/ directory. According to the repository source code, the make_playlist.py script orchestrates the entire transformation, converting markdown rows into standardized M3U8 entries that any compatible media player can consume.
File Discovery and Group Extraction
The parsing process begins with file system traversal and group identification.
Scanning the lists Directory
The script locates all markdown files in the lists/ folder, excluding README.md:
lists_dir = os.path.join(base_dir, "lists")
...
for filename in sorted(os.listdir(lists_dir)):
if filename == "README.md" or not filename.endswith(".md"):
continue
markup_path = os.path.join(lists_dir, filename)
This loop ensures only country-specific markdown tables are processed, with each file representing a distinct geographic region.
Extracting Group Titles from H1 Tags
When the parser encounters an <h1> HTML tag within the markdown, it updates the current group title:
if "<h1>" in line.lower() and "</h1>" in line.lower():
group = re.sub('<[^<>]+>', '', line.strip())
This extracted group name becomes the group-title attribute in the final M3U8 output, organizing channels by country or category.
Filtering Valid Stream Rows
Not every row in the markdown tables represents a valid stream. The parser uses a strict filter to identify active channels:
if "[>]" not in line:
continue
Only rows containing the [>] marker—specifically in the fourth column—are processed further. This convention allows maintainers to mark broken or inactive streams with [x] while excluding them automatically from generated playlists.
Parsing Markdown Table Rows with the Channel Class
Once a valid row is identified, the Channel class constructor in make_playlist.py performs the actual parsing. The constructor accepts three parameters: group, line, and country_code.
Splitting the Markdown Line
The raw markdown row is split into components using the pipe delimiter:
parts = md_line.split("|")
This creates a list where index positions correspond to specific metadata fields. For example, a typical row splits into:
['', ' 1 ', ' ABC ', ' [>](http://example.com/abc.m3u8) ', ' <img src="https://i.imgur.com/abc.png" width="24"> ', ' ']
Extracting the Stream URL
The URL resides in the fourth column (index 3) as a markdown hyperlink formatted like [>](http://example.com/stream). The extraction logic isolates the content between parentheses:
self.url = self.url[self.url.find("(")+1:self.url.rfind(")")]
This approach extracts the raw HTTP URL regardless of the protocol used, discarding the [>] marker wrapper.
Extracting the Channel Logo
The logo URL is embedded in an HTML <img> tag within the fifth column (index 4). The parser extracts the src attribute value:
self.logo = self.logo[self.logo.find('src="')+5:self.logo.rfind('"')]
This pulls the image URL from markup like <img src="https://i.imgur.com/abc.png" width="24">.
Optional EPG Metadata
If a sixth column exists (index 5), it is stored as the Electronic Program Guide identifier:
if len(parts) > 5:
self.epg = parts[5].strip()
else:
self.epg = None
This optional field maps to the tvg-id attribute in the final M3U8 output.
Generating M3U8 Output
The Channel.to_m3u_line() method formats the parsed data into valid M3U8 syntax:
return f'#EXTINF:-1 tvg-name="{self.name}" tvg-logo="{self.logo}"{chno}{country} group-title="{self.group}",{self.name}\n{self.url}'
When an EPG ID is present, the method includes tvg-id="{self.epg}" in the output. The resulting string is written to both the global playlist.m3u8 and the country-specific file located at playlists/playlist_<country>.m3u8.
Summary
- File location: The
make_playlist.pyscript processes all.mdfiles in thelists/directory, excludingREADME.md. - Validity marker: Only rows containing
[>]in the fourth column are converted to streams; rows marked[x]are ignored. - URL extraction: Stream URLs are parsed from markdown hyperlink syntax by extracting text between the first
(and last)characters. - Logo parsing: Channel logos are extracted from the
srcattribute of HTML<img>tags in the fifth column. - Output format: Parsed data generates standard M3U8 playlists with
tvg-name,tvg-logo,group-title, and optionaltvg-idattributes.
Frequently Asked Questions
What does the [>] marker mean in Free-TV/IPTV markdown tables?
The [>] marker indicates an active, working stream URL that should be included in the generated playlists. Rows containing this marker in the fourth column are processed by the Channel class, while rows marked with [x] are filtered out as invalid or offline streams.
How does the script handle invalid or dead stream URLs?
The script relies on the [>] marker as a manual curation signal. If a stream is dead, maintainers change the marker from [>] to [x] in the markdown table, and the make_playlist.py script automatically skips these rows during the filtering phase, preventing broken URLs from entering the final M3U8 files.
Where are the parsed IPTV playlists saved?
After processing, the script generates two outputs: a consolidated playlist.m3u8 in the repository root, and individual country-specific files located in the playlists/ directory following the naming convention playlist_<country>.m3u8, where <country> corresponds to the source markdown filename.
Can I add custom EPG data to a channel entry?
Yes, by including a sixth column in the markdown table row. The Channel class constructor checks for this optional field and assigns it to self.epg, which then appears as the tvg-id attribute in the M3U8 output, enabling electronic program guide integration for that specific channel.
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 →