How h1 and h2 HTML Tags Define Channel Group Hierarchy in Free-TV IPTV Markdown
In the Free-TV/IPTV repository, <h1> tags define top-level channel groups (countries) and <h2> tags define sub-groups (broadcast standards), which make_playlist.py combines into hierarchical #EXTGRP: entries in the final M3U8 playlist.
The Free-TV/IPTV project organizes thousands of free television channels using a lightweight Markdown catalog system. Rather than relying on complex databases or YAML front-matter, the repository leverages standard HTML heading tags within Markdown files to establish a strict two-level hierarchy that drives automated playlist generation.
The Two-Level Hierarchy: h1 as Groups and h2 as Sub-Groups
The channel organization system relies on exactly two HTML heading levels embedded in plain-text Markdown. According to the repository's README, the <h1> tag serves as the group title—typically representing a country or major geographic region that becomes a discrete group in the playlist.
When make_playlist.py processes a Markdown file from the lists/ directory, it extracts the first <h1> element to create the base group name. The <h2> tag defines sub-groups within that main category, such as broadcast standards (ATSC, DVB-S) or service types (Pluto). When the parser encounters an <h2> line, it generates a combined #EXTGRP: entry that joins the h1 and h2 values with an en-dash separator.
For example, lists/usa.md begins with <h1>USA</h1> on line 1. Lines 3, 14, 28, and 35 contain <h2> tags defining sub-groups like "ATSC", "DVB-S", and "Pluto". This structure produces playlist entries such as:
#EXTGRP:USA – ATSC
#EXTGRP:USA – DVB-S
#EXTGRP:USA – Pluto
How make_playlist.py Parses the Markdown Headings
The conversion logic in make_playlist.py uses regular expressions to extract hierarchy information without requiring a full Markdown parser. The script reads each .md file in the lists/ directory and applies pattern matching to identify the heading structure.
The extraction follows this precise workflow:
- Capture the top-level group using
re.search(r'<h1>(.*?)</h1>', text).group(1) - Iterate through all sub-groups using
re.finditer(r'<h2>(.*?)</h2>', text) - Combine the values as
f'{group} – {subgroup}'for each#EXTGRP:entry
This regex-based approach allows the generator to process plain-text Markdown files efficiently while ignoring the table-based channel listings that follow the headings.
Real-World Implementation in lists/usa.md
The lists/usa.md file demonstrates this hierarchy in production usage. The file structure follows a strict pattern where the <h1> establishes the country context and subsequent <h2> tags partition channels by technical standard or source.
As implemented in Free-TV/IPTV, the parser treats each <h2> boundary as a new sub-group container. Channels listed in the Markdown tables beneath an <h2> heading inherit that sub-group classification until the next <h2> tag appears. This creates a clean separation between different broadcast types (terrestrial ATSC versus satellite DVB-S) while maintaining the top-level "USA" grouping.
Creating a New Channel List with Proper Heading Structure
To add a new country catalog, create a Markdown file in the lists/ directory following this exact pattern:
<h1>Canada</h1>
<h2>ATSC</h2>
| # | Channel | Link | Logo | EPG id |
|---|---------|------|------|--------|
| 1 | CBC | [>](https://example.com/cbc.m3u8) | <img src="..." /> | CBC.ca |
<h2>DVB-S</h2>
| # | Channel | Link | Logo | EPG id |
|---|---------|------|------|--------|
| 1 | Global TV | [>](https://example.com/global.m3u8) | <img src="..." /> | Global.ca |
The <h1> tag creates the "Canada" group, while each <h2> tag generates a distinct sub-group (Canada – ATSC, Canada – DVB-S). No additional metadata, YAML front-matter, or indentation is required to establish this relationship.
Summary
<h1>tags in Markdown files define top-level channel groups (typically countries) that serve as the base name inmake_playlist.py<h2>tags create sub-groups that combine with the h1 value using the format "Group – SubGroup" for#EXTGRP:entries- The parser extracts headings using regex patterns
r'<h1>(.*?)</h1>'andr'<h2>(.*?)</h2>'rather than a Markdown parser - Channel lists reside in the
lists/directory and require no special configuration beyond these two HTML heading levels
Frequently Asked Questions
Can I use standard Markdown # headings instead of HTML <h1> tags?
No. The make_playlist.py script specifically searches for HTML <h1> and <h2> tags using regex patterns. Standard Markdown ATX headings (# Heading) will not be recognized as group delimiters during playlist generation, and the channels will not be properly categorized in the output.
What happens if a Markdown file lacks an <h1> tag?
The parsing logic expects an <h1> element to establish the base group name. Without it, the script cannot properly prefix sub-groups, potentially causing incomplete or malformed #EXTGRP: entries in the generated M3U8 playlist. Every channel list file must contain exactly one <h1> tag at the beginning.
Is there a limit to how many <h2> sub-groups can exist under one <h1>?
There is no enforced limit in the parsing logic. The re.finditer() function processes all <h2> occurrences sequentially, allowing unlimited sub-groups (ATSC, DVB-S, Cable, Pluto, etc.) beneath a single country heading. Each sub-group simply generates a new combined entry in the playlist.
Does the hierarchy support levels deeper than h2 (such as h3)?
No. The current implementation in make_playlist.py only recognizes <h1> and <h2> tags for playlist organization. Any <h3> or deeper headings would be treated as standard content and ignored during group extraction, making them unsuitable for defining additional hierarchy levels.
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 →