# How IPTV Stream URLs Are Parsed from Markdown Tables in Free-TV/IPTV

> Learn how the Free-TV/IPTV repository parses IPTV stream URLs from markdown tables using [>] markers and hyperlink syntax within the fourth column to create M3U8 playlist entries.

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

---

**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`](https://github.com/Free-TV/IPTV/blob/main/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`](https://github.com/Free-TV/IPTV/blob/main/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`](https://github.com/Free-TV/IPTV/blob/main/README.md):

```python
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:

```python
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:

```python
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`](https://github.com/Free-TV/IPTV/blob/main/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:

```python
parts = md_line.split("|")

```

This creates a list where index positions correspond to specific metadata fields. For example, a typical row splits into:

```python
['', ' 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:

```python
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:

```python
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:

```python
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:

```python
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.py`](https://github.com/Free-TV/IPTV/blob/main/make_playlist.py) script processes all `.md` files in the `lists/` directory, excluding [`README.md`](https://github.com/Free-TV/IPTV/blob/main/README.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 `src` attribute of HTML `<img>` tags in the fifth column.
- **Output format**: Parsed data generates standard M3U8 playlists with `tvg-name`, `tvg-logo`, `group-title`, and optional `tvg-id` attributes.

## 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`](https://github.com/Free-TV/IPTV/blob/main/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.