Compare commits

...

4 Commits

Author SHA1 Message Date
r3414385@gmail.com 630f7be9d2
Merge 4049115f3c into 43b8dced11 2026-03-08 20:22:12 +01:00
KAMI 43b8dced11
Add tvg-country attribute to M3U8 playlist entries (#990)
Add ISO 3166-1 alpha-2 country codes via tvg-country attribute
to all #EXTINF lines, enabling IPTV players to filter and display
channels by country.

Co-authored-by: Kálmán „KAMI” Szalai <kami911gmail.com>
2026-03-08 09:29:23 +01:00
KAMI 8a67a85d1a
Improve make_playlist.py code quality and fix bugs (#989)
* Improve make_playlist.py code quality and fix bugs

- Fix group name bug: replace underscores with spaces before title()
  so filenames like north_korea.md produce "North Korea" not "North_Korea"
- Fix resource leaks: use context managers for all file handles including
  EPG list and per-country playlist files
- Remove os.chdir() global side effect: use absolute paths derived from
  the script location instead
- Avoid calling to_m3u_line() twice per channel by caching the result
- Fix redundant trailing colon in filename[:-3:] slice
- Consistent use of write() for headers instead of mixing print/write
- Strip blank lines from EPG URL list when reading

* Skip commit and push when playlist has no changes

Prevents the workflow from failing with exit code 1 when the generated
playlist is identical to the previous run and there is nothing to commit.

* Use current branch instead of hardcoded master for push

Replace hardcoded origin/master reference with @{u} (upstream of current
branch) for the diff check, and use HEAD for the push target so the
workflow works correctly on any branch.

---------

Co-authored-by: Kálmán „KAMI” Szalai <kami911gmail.com>
2026-03-08 07:40:47 +01:00
r3414385@gmail.com 4049115f3c
Update azerbaijan.md
Yeni TV kanallar
2025-12-29 22:18:35 +04:00
87 changed files with 3394 additions and 3294 deletions

View File

@ -17,5 +17,5 @@ jobs:
git config user.email "playlistbot@users.noreply.github.com" || true
python3 ./make_playlist.py
git add .
git commit --quiet -m "Update Playlist (GitHub Actions)"
git push -f origin master
git diff --staged --quiet || git commit --quiet -m "Update Playlist (GitHub Actions)"
git diff --quiet HEAD @{u} || git push -f origin HEAD

View File

@ -39,4 +39,7 @@
| 0 | Səhiyyə TV | [>](https://www.tvkaista.net/stream-forwarder/get.php?x=SehiyyeTV) | <img height="20" src="https://upload.wikimedia.org/wikipedia/az/thumb/c/cd/S%C9%99hiyy%C9%99_TV.png/640px-S%C9%99hiyy%C9%99_TV.png"/> | SehiyyeTV.az |
| 0  | MCJ TV SHOP | [x](https://www.tvkaista.net/stream-forwarder/get.php?x=MCJTVShop) | <img height="20" src="https://tvtolive.com/wp-content/uploads/MCJ-TV-Shop-tvtolive.com_.jpg"/> |
| 0 | VIP HD | [>](https://www.tvkaista.net/stream-forwarder/get.php?x=AZ_VIP) | <img height="20" src="https://tvtolive.com/wp-content/uploads/VIP-TV-tvtolive.com_.jpg"/> |
| 0 | MTV Azerbaijan Ⓢ | [>](https://www.tvkaista.net/stream-forwarder/get.php?x=MTVAzerbaijan) | <img height="20" src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/00/MTV_Az%C9%99rbaycan_%282022%29.png/622px-MTV_Az%C9%99rbaycan_%282022%29.png"/> | MTVAzerbaijan.az |
| 0 | MTV Azerbaijan Ⓢ | [>](https://www.tvkaista.net/stream-forwarder/get.php?x=MTVAzerbaijan) | <img 20src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/00/MTV_Az%C9%99rbaycan_%282022%29.png/622px-MTV_Az%C9%99rbaycan_%282022%29.png"/> | MTVAzerbaijan.az |
# | Channel | Link | Logo | EPG id | |:---:|:--------------:|:-----:|:----:|:------:| https://www.tvkaista.net/stream-keds forwarder/get.php?x=ARMGunes http://109.205.166.68/server124/arb/gerinal index.m3u8 ARBTV SD http://85.132.81.184:8080/arb/live/news index.m3u8 ARB24 SD https://www.tvkaista.net/stream-forwarder/get.php?gerenalx=ATVAz AZAD AZƏRBAYCAN TV SD https://live.itv.az/itv gerenal.m3u8 ICTİMAİ TV SD https://mn-nl.mncdn.com/cbcsports_live/ cbcsports/playlist Sports.m3u8 CBC SPORT SD https://str.yodacdn.net/aztv/ gerenal index.m3u8 AZTV SD https://str.yodacdn.net/medeniyyet/medeniyyet index.m3u8 MƏDƏNİYYƏT TV SD http://109.205.166.68/server124/ Sports idman_az/index.m3u8 IDMAN AZƏRBAYCN TV SD https://www.tvkaista.net/stream- gerenalforwarder/get.php?x=Dunya DUNYA TV SD AMC https://www.tvkaista.net/stream-forwarder/get.php?x=KanalS Kanal S #EXTINF:0 tvg-country="AZ" tvg-logo="" news group-title="Undefined", GUNAZTV HD
http://rtmp.apa.tv/@pagroup!23.m3u8/APA TV https://lenz.splus.ir/PLTV/88888888/224/3221226800/index.m3u8/General/West Azerbaijan TV https://lenz.splus.ir/PLTV/88888888/224/3221226800/index.m3u8/Generalt Azerbaijan Gharbi https://www.tvkaista.net/stream- gerenal forwarder/get.php?x=SehiyyeTV/Səhiyyə TV Channel TV https://raw.githubusercontent.com/UzunMuhalefet/streams/refs/heads/main/myvideo-az/vip.m3u8 VİP TV https://53be5ef2d13aa.streamlock.net/cubesanewz-secure/smil:cubesanewz-secure-web.smil/playlist.m3u8 ANEWZ https://53be5ef2d13aa.streamlock.net/cubesanewz-secure/smil:cubesanewz-secure-web.smil/playlist.m3u8 anewZ https://live.ishiacloud.com/haditv.co.uk/haditv3.m3u8 www hadi3tv.az Hadi TV Azeri and Russian(720p) https://www.tvkaista.net/stream-forwarder/get.php?x=SehiyyeTV SehiyyeTV.az Səhiyyə TV https://raw.githubusercontent.com/UzunMuhalefet/streams/refs/heads/main/myvideo-az/mcj-tv-shop.m3u8 MCJ SHOP http://live.azstartv.com/azstar/smil:azstar.smil/playlist.m3u8 AZ STAR TV SD https://www.tvkaista.net/stream-forwarder/get.php?x=MTVAzerbaijan www MTVAzerbaijan.az MTV AZƏRBAYCAN SD https://www.tvkaista.net/stream-forwarder/get.php?x=MTVAzerbaijan MUZTV AZƏRBAYCAN https://raw.githubusercontent.com/UzunMuhalefet/streams/refs/heads/main/myvideo-az/baku-tv.m3u8 Baku TV SD #EXTINF:0 tvg-country="AZ" tvg-logo="" group-title="Undefined",Lider http://mfe.cliptv.az/dash/Lider_SD.ism/playlist.mpd?checkedby:hlscat.com Ledir Azərbaycan TV_SD VİRTUAL SPORTS VİRTUAL SPORTS BL SPORT TV SPORT TV 2 TVNET SPORT ANGOR AZERBAİJAN 114TV TV #EXTINF:0 tvg-country="AZ" tvg-logo="" group-title="Undefined",Naxçıvan TV http://mfe.cliptv.az/dash/NTV_HD.ism/playlist.mpd?checkedby:hlscat.com NAXÇIVAN TV HD AZ REAKSIYA TV https://rtmp.showplus.tv/hls/myshow.m3u8 SHOWS PLUS TV SƏS TV MEDİA TURK #EXTINF:0 tvg-country="AZ" tvg-logo="" group-title="Undefined",QƏBƏLƏ TV https://qebele.tv/live/stream/index.m3u8?checkedby:hlscat.com QƏBƏLƏ TV https://str1.yodacdn.net/qafkaz/playlist.m3u8 QAFQAZ TV TOPLUM TV İNTERAZ QLOBAL TV ARB CANUB http://85.132.78.122:8050/hls/stream/index.m3u8 Kepez TV ARB ŞİMAL Xezer xeber Topaz 2 Azercosmos porum ATV CİNEMA ATV VİVACE http://str.yodacdn.net/kanal35/tracks-v1a1/mono.m3u8 KANAL 35 http://cdn-konultvazerbaijan.yayin.com.tr/konultvazerbaijan/konultvazerbaijan/playlist.m3u8 konsul TV https://cdn4.yayin.com.tr/bakixebertv/video.m3u8 Konsul MUSİC http://str.yodacdn.net/kanal35/tracks-v1a1/mono.m3u8 kanal 35 http://cdn4.yayin.com.tr/ismayillitv/video.m3u8 ismayıllı TV Sirvan TV http://xantv.site:41480/MahniTV/video.m3u8 Mahnı TV https://tv.mobyservice.ru/livetv/index.m3u8 LIVE TV meydan TV TV 36 FTV https://www.tvkaista.net/stream-forwarder/get.php?x=AZ_VIP VİP HD #EXTINF:0 tvg-country="AZ" tvg-logo="" group-title="Undefined",AL ZAHRA TV http://live.al-zahratv.com/live/playlist.m3u8?checkedby:hlscat.com AL ZAHAR TV #EXTINF:0 tvg-country="AZ" tvg-logo="" group-title="Undefined",AMC_SD http://mfe.cliptv.az/dash/AMC_SD.ism/playlist.mpd?checkedby:hlscat.com AMC_SD https://www.tvkaista.net/stream-forwarder/get.php?x=SehiyyeTV SƏHİYYƏ TV #EXTINF:0 tvg-country="AZ" tvg-logo="" group-title="Undefined",Dalğa TV http://mfe.cliptv.az/dash/DalgaTV_SD.ism/playlist.mpd?checkedby:hlscat.com DALGA TV SD #EXTINF:0 tvg-country="AZ" tvg-logo="" group-title="Undefined",ELTV http://85.132.53.162:1935/live/eltv/chunklist_.m3u8?checkedby:hlscat.com ElTV #EXTINF:0 tvg-country="AZ" tvg-logo="" group-title="Undefined",ANITV_SD http://mfe.cliptv.az/dash/ANITV_SD.ism/playlist.mpd?checkedby:hlscat.com ANITV_SD STANSA SPORT 1 STANSA SPORT 2 STANSA SPORT + STANSA SPORT + http://str.yodacdn.net/biznestv/tracks-v1a1/mono.ts.m3u8 Biznes TV http://iptv.prosto.tv:7000/ch318/video.m3u8 PeykTV_AZERBAYCAN Naxçıvan TV ayaz tv Azerbaijan Music Azerbaijan Music Plus Russia Music https://cdn4.yayin.com.tr/kntv/tracks-v1a1/mono.m3u8 Kn TV

View File

@ -3,11 +3,98 @@
import os
import re
EPG_LIST = open('epglist.txt',"r") # for a clean code
COUNTRY_CODES = {
"albania": "AL",
"andorra": "AD",
"argentina": "AR",
"armenia": "AM",
"australia": "AU",
"austria": "AT",
"azerbaijan": "AZ",
"belarus": "BY",
"belgium": "BE",
"bosnia_and_herzegovina": "BA",
"brazil": "BR",
"bulgaria": "BG",
"canada": "CA",
"chad": "TD",
"chile": "CL",
"china": "CN",
"costa_rica": "CR",
"croatia": "HR",
"cyprus": "CY",
"czech_republic": "CZ",
"denmark": "DK",
"dominican_republic": "DO",
"egypt": "EG",
"estonia": "EE",
"faroe_islands": "FO",
"finland": "FI",
"france": "FR",
"georgia": "GE",
"germany": "DE",
"greece": "GR",
"greenland": "GL",
"hong_kong": "HK",
"hongkong": "HK",
"hungary": "HU",
"iceland": "IS",
"india": "IN",
"indonesia": "ID",
"iran": "IR",
"iraq": "IQ",
"ireland": "IE",
"israel": "IL",
"italy": "IT",
"japan": "JP",
"korea": "KR",
"kosovo": "XK",
"latvia": "LV",
"lithuania": "LT",
"luxembourg": "LU",
"macau": "MO",
"malta": "MT",
"mexico": "MX",
"moldova": "MD",
"monaco": "MC",
"montenegro": "ME",
"netherlands": "NL",
"north_korea": "KP",
"north_macedonia": "MK",
"norway": "NO",
"paraguay": "PY",
"peru": "PE",
"poland": "PL",
"portugal": "PT",
"qatar": "QA",
"romania": "RO",
"russia": "RU",
"san_marino": "SM",
"saudi_arabia": "SA",
"serbia": "RS",
"slovakia": "SK",
"slovenia": "SI",
"somalia": "SO",
"spain": "ES",
"spain_vod": "ES",
"sweden": "SE",
"switzerland": "CH",
"taiwan": "TW",
"trinidad": "TT",
"turkey": "TR",
"uk": "GB",
"ukraine": "UA",
"united_arab_emirates": "AE",
"usa": "US",
"usa_vod": "US",
"venezuela": "VE",
}
class Channel:
def __init__(self, group, md_line):
def __init__(self, group, md_line, country_code=""):
self.group = group
self.country_code = country_code
md_line = md_line.strip()
parts = md_line.split("|")
self.number = parts[1].strip()
@ -22,39 +109,49 @@ class Channel:
self.epg = None
def to_m3u_line(self):
country = f' tvg-country="{self.country_code}"' if self.country_code else ""
if self.epg is None:
return (f'#EXTINF:-1 tvg-name="{self.name}" tvg-logo="{self.logo}" group-title="{self.group}",{self.name}\n{self.url}')
return (f'#EXTINF:-1 tvg-name="{self.name}" tvg-logo="{self.logo}"{country} group-title="{self.group}",{self.name}\n{self.url}')
else:
return (f'#EXTINF:-1 tvg-name="{self.name}" tvg-logo="{self.logo}" tvg-id="{self.epg}" group-title="{self.group}",{self.name}\n{self.url}')
return (f'#EXTINF:-1 tvg-name="{self.name}" tvg-logo="{self.logo}" tvg-id="{self.epg}"{country} group-title="{self.group}",{self.name}\n{self.url}')
def main():
dir_playlists = 'playlists'
if not (os.path.isdir(dir_playlists)):
base_dir = os.path.dirname(os.path.abspath(__file__))
lists_dir = os.path.join(base_dir, "lists")
dir_playlists = os.path.join(base_dir, "playlists")
if not os.path.isdir(dir_playlists):
os.mkdir(dir_playlists)
with open("playlist.m3u8", "w", encoding='utf-8') as playlist:
processed_epg_list = ", ".join(EPG_LIST).replace('\n', '')
head_playlist = f'#EXTM3U x-tvg-url="{processed_epg_list}"'
print(f'#EXTM3U x-tvg-url="{processed_epg_list}"', file=playlist)
os.chdir("lists")
for filename in sorted(os.listdir(".")):
with open(os.path.join(base_dir, "epglist.txt"), encoding='utf-8') as epg_file:
epg_urls = [line.strip() for line in epg_file if line.strip()]
processed_epg_list = ", ".join(epg_urls)
head_playlist = f'#EXTM3U x-tvg-url="{processed_epg_list}"\n'
with open(os.path.join(base_dir, "playlist.m3u8"), "w", encoding='utf-8') as playlist:
playlist.write(head_playlist)
for filename in sorted(os.listdir(lists_dir)):
if filename == "README.md" or not filename.endswith(".md"):
continue
with open(filename, encoding='utf-8') as markup_file:
file_country = os.path.join("..", dir_playlists, "playlist_" + filename[:-3:] + ".m3u8")
playlist_country = open(file_country, "w", encoding='utf-8')
playlist_country.write(head_playlist + "\n")
group = filename.replace(".md", "").title()
print(f"Generating {group}")
markup_path = os.path.join(lists_dir, filename)
country_path = os.path.join(dir_playlists, "playlist_" + filename[:-3] + ".m3u8")
country_key = filename[:-3]
group = country_key.replace("_", " ").title()
country_code = COUNTRY_CODES.get(country_key, "")
print(f"Generating {group}")
with open(markup_path, encoding='utf-8') as markup_file, \
open(country_path, "w", encoding='utf-8') as playlist_country:
playlist_country.write(head_playlist)
for line in markup_file:
if "<h1>" in line.lower() and "</h1>" in line.lower():
group = re.sub('<[^<>]+>', '', line.strip())
if not "[>]" in line:
if "[>]" not in line:
continue
channel = Channel(group, line)
print(channel.to_m3u_line(), file=playlist)
print(channel.to_m3u_line(), file=playlist_country)
playlist_country.close()
channel = Channel(group, line, country_code)
m3u_line = channel.to_m3u_line()
print(m3u_line, file=playlist)
print(m3u_line, file=playlist_country)
if __name__ == "__main__":
main()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long