# How make_playlist.py Parses Markdown Channel Data into M3U8 Format

> Discover how make_playlist.py transforms Markdown channel data into M3U8 playlists by parsing pipe-delimited tables and serializing EXTINF entries with TVG metadata. Learn more now.

- Repository: [Free TV/IPTV](https://github.com/Free-TV/IPTV)
- Tags: how-to-guide
- Published: 2026-06-16

---

**The [`make_playlist.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py) script in the Free-TV/IPTV repository converts pipe-delimited Markdown tables into standards-compliant M3U8 playlists by scanning for rows marked with `[>]`, parsing them into `Channel` objects, and serializing them into EXTINF entries with TVG metadata.**

The Free-TV/IPTV repository uses a unique Markdown-based workflow to maintain IPTV channel lists. At the center of this pipeline sits [`make_playlist.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py), which transforms human-readable Markdown tables into machine-ready M3U8 playlists. Understanding how this script parses markdown channel data into M3U8 format reveals a lightweight but robust approach to IPTV playlist generation.

## Stage 1: Collecting Markdown Source Files

The script begins by discovering source files in the `lists/` directory. According to the source code in [`make_playlist.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py) (lines 38-44), it iterates through every `*.md` file while explicitly excluding [`README.md`](https://github.com/Free-TV/IPTV/blob/main/README.md) from processing.

This collection phase establishes the group-title context for each channel based on the filename or an `<h1>` heading found within the Markdown file.

## Stage 2: Parsing Channel Rows with the Channel Class

Once files are collected, the script processes each line individually to identify valid channel entries.

### Identifying Valid Channel Rows

The parser filters for rows containing the specific token `[>]`. As implemented in lines 52-55 of [`make_playlist.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py), only lines beginning with this marker are considered valid channel data:

```python

# From make_playlist.py

if line.startswith("[>]"):
    # Process as channel row

```

### Extracting Fields from Pipe-Delimited Data

When a valid row is found, it is passed to the `Channel` class constructor (lines 94-106). The `Channel.__init__` method splits the line on the pipe character (`|`) and trims whitespace from each column:

```python
class Channel:
    def __init__(self, group, md_line, country_code=""):
        # Split the pipe-separated line

        parts = md_line.strip().split("|")
        self.number = parts[1].strip()
        self.name = parts[2].strip()

```

### Handling URL and Logo Extraction

The script handles two special formatting requirements in the Markdown source:

1. **URL extraction**: The URL column is wrapped in parentheses in the Markdown table. The constructor strips these delimiters to isolate the raw stream URL (lines 103-104):

```python
self.url = parts[3].strip()
self.url = self.url[self.url.find("(")+1:self.url.rfind(")")]

```

2. **Logo extraction**: The logo column contains an HTML `<img>` tag. The constructor extracts the value of the `src` attribute (lines 104-106):

```python
self.logo = parts[4].strip()
self.logo = self.logo[self.logo.find('src="')+5:self.logo.rfind('"')]

```

3. **Optional EPG identifier**: If a sixth column exists, it is stored as the optional EPG identifier (lines 108-112):

```python
self.epg = parts[5].strip() if len(parts) > 6 else None

```

## Stage 3: Generating M3U8 Output

After parsing, the script serializes each `Channel` object into M3U8 format.

### Constructing EXTINF Metadata

The `Channel.to_m3u_line()` method (lines 114-121) builds a single EXTINF line following the M3U8 specification:

- Always adds `tvg-name`, `tvg-logo`, and `group-title`
- Appends `tvg-country` if a country code was supplied via the `COUNTRY_CODES` map
- Includes `tvg-chno` if a channel number is present
- Adds `tvg-id` only when an EPG identifier exists

The resulting output is a two-line block: the EXTINF metadata line followed by the raw stream URL.

### Writing Country-Specific and Master Playlists

The script aggregates channels into two destinations:

1. **`playlist.m3u8`** – A unified playlist containing every channel from all source files
2. **`playlists/playlist_<country>.m3u8`** – Individual country-specific playlists

Both playlist types begin with a header containing EPG URLs sourced from [`epglist.txt`](https://github.com/Free-TV/IPTV/blob/main/epglist.txt) (lines 31-35), ensuring IPTV clients can retrieve program guide data.

## Summary

- **[`make_playlist.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py)** scans the `lists/` directory for Markdown files (excluding [`README.md`](https://github.com/Free-TV/IPTV/blob/main/README.md)) to find channel source data
- Valid channel rows are identified by the `[>]` marker and parsed by the `Channel` class constructor
- The constructor splits pipe-delimited lines, extracts URLs from parentheses, parses logo `src` attributes from HTML tags, and handles optional EPG identifiers
- The `to_m3u_line()` method generates standards-compliant EXTINF entries with TVG metadata attributes
- Output is written to both a master `playlist.m3u8` and per-country playlists in the `playlists/` directory, each prefixed with EPG headers from [`epglist.txt`](https://github.com/Free-TV/IPTV/blob/main/epglist.txt)

## Frequently Asked Questions

### What format does the Markdown source data use?

The source uses pipe-delimited tables where each channel occupies one row marked with `[>]`. A typical row contains: marker, channel number, name, URL in parentheses, logo image tag, and an optional EPG ID. This format balances human readability with structured data extraction.

### How does the script handle optional EPG identifiers?

The `Channel` class checks if a sixth column exists after splitting the pipe-delimited line. If present, the value is stored in `self.epg` and later rendered as the `tvg-id` attribute in the M3U8 output. If absent, the attribute is omitted from the EXTINF line entirely.

### Where does the group-title attribute come from?

The `group-title` attribute derives from the source Markdown filename or an `<h1>` heading within the file. This value is passed to the `Channel` constructor and included in every M3U8 entry, enabling IPTV clients to organize channels by country or category.

### Can I run make_playlist.py standalone?

Yes. Executing `python3 make_playlist.py` from the repository root processes all Markdown files in `lists/` and generates both the master playlist and country-specific variants. The script requires no external dependencies beyond standard Python libraries.