m3u plus ile m3u: genişletilmiş bir çalma listesinin eklediği öznitelikler ve bunlar nasıl ayrıştırılır

M3U+ spesifikasyonu diye bir şey yok. Bir uzlaşım, bir korpus ve hem uzlaşımdan hem korpustan sağ çıkmak zorunda olan bir ayrıştırıcı var.

#EXTINF:-1 tvg-id="" tvg-logo="" group-title="News;General" tvg-chno="1",Example One
https://provider.example.com/stream/one.m3u8

İki satır. Bu satır kopyalanmadı, kuruldu: ayrıştırıcı işleri için tuttuğumuz iki çalma listesi gerçek yayıncıların adlarını taşıyor, dolayısıyla buradaki hiçbir şey onlardan harfi harfine alınmadı. İçindeki her şekil o dosyalarda gerçekten bulunan bir şekil ve değerlerin hiçbiri ayırt edici değil. 1.895 girdilik dosyada 107 satır 1 numaralı kanal olduğunu söylüyor, 20'si boş bir tvg-id, 42'si boş bir tvg-logo taşıyor. 308 girdilik dosyada 25 satır boş bir group-title taşıyor, 19'u boş bir tvg-logo taşıyor ve 10'u iki grup adını noktalı virgülle tek bir özniteliğe sıkıştırıyor.

İlk satırda yapılandırılmış veriye benzeyen hemen her şey ya yok, ya boş, ya da birbiriyle ilgisiz yüz kanalla paylaşılmış durumda. Hem güvenilir biçimde var olan hem de ayırt ediciliğe yaklaşan tek alan, ikinci satırdaki URL: küçük dosyada 308 girdide 308 farklı URL, büyüğünde 1.895 girdide 1.884 farklı URL.

Oynatıcı hiçbir kanal içermiyor ve size kanal sağlayamaz. İçinde ne görünüyorsa, gösterdiğiniz dosyadan geliyor ve kendi cihazınızda ayrıştırılıyor; yani o ayrıştırmanın kusurlu girdi karşısındaki davranışı, kanal listenizin sonunda ne olacağının büyük kısmını belirliyor.

Spesifikasyon yok, uzlaşım var

RFC 8216, HLS için #EXTINF'i bir süre, bir virgül ve satır sonuna kadar giden isteğe bağlı, insan tarafından okunabilir bir başlık olarak tanımlıyor. tvg-id, tvg-chno, group-title, tvg-logo, catchup-source ya da gerçek dosyaların süre ile virgül arasına tıkıştırdığı diğer belirteçlerin hiçbiri hakkında tek kelime etmiyor.

Bu belirteçler, üzerinde anlaşılmış bir adı olmayan, canlı TV çalma listeleri için daha sonra oluşmuş bir uzlaşımdan geliyor ve insanların uzandığı iki adın ikisi de yanlış. Extended M3U, #EXTM3U ve #EXTINF'in kendisini getiren Winamp dönemi uzlaşımı; RFC 8216 ondan 4.3.1.1 bölümünde adıyla söz ediyor ve içinde tvg- diye bir şey yok. M3U+ ise bir biçim bile değil: m3u_plus, sağlayıcı girişiyle bağlanılan sağlayıcı sunucularının bir çıktı modu; bir dışa aktarımı çıplak URL'lerden tvg-id, tvg-logo ve group-title taşıyan satırlara çeviren sorgu değeri. Bunlardan biri doksanlı yılların bir çalma listesi uzlaşımı, diğeri bir sorgu parametresi ve hiçbiri bir spesifikasyon değil. Uzlaşımı, üreticiler ne üretiyorsa o tanımlıyor.

Bu ayrım aşağıdaki her şeyin içinden geçiyor. Uzlaşımın izin verdikleri ile tek bir ayrıştırıcının kabul ettikleri iki ayrı liste ve bunları birbirine karıştırmak, bir ayrıştırıcının tek bir kaynağın alışkanlıklarını kuralmış gibi kodlamasına yol açıyor. Öyleyse: ayrıştırıcımızın uyguladığı dilbilgisi şu, fazlası değil.

line     := "#EXTINF:" WS* duration WS* attributes "," displayName
duration := "-"? DIGIT+

Önek eşleşmesi ordinal, yani #EXTINF: iki nokta üst üsteye kadar büyük küçük harfe duyarlı; ayrıştırıcı da öneği sekiz karakter olarak sabitleyip gerisini bir süre tarayıcısına veriyor. O tarayıcı isteğe bağlı tek bir eksi alıyor, sonra en az bir rakam istiyor, sonra rakam dizisini tüketiyor. Rakamların nerede bittiğini kaydediyor ve değeri çöpe atıyor. Süre, işlem hattının hiçbir yerinde okunmuyor. İki örnek çalma listemizdeki 2.203 #EXTINF satırının tamamı -1 taşıyor; bu, uzlaşımın "canlı, uzunluğu bilinmiyor" deme biçimi ve 7200 deselerdi hiçbir kod yolu umursamazdı.

O tarayıcıdan doğrudan üç sonuç çıkıyor. +1 geri çevriliyor, çünkü artı ne eksi ne de rakam. .5 geri çevriliyor, çünkü noktadan önce bir rakam gelmek zorunda. Ve #EXTINF:120.5,Example gibi kesirli bir süre kazara doğru ayrıştırılıyor: tarayıcı noktada duruyor, geriye kalan .5 öznitelik bölgesinin içine düşüyor ve hiçbir öznitelik araması onunla eşleşmiyor. RFC 8216 o kesirli biçime izin veriyor ve ayrıştırıcımız onu kaldırıyor, ama kimse onu uygulamadı; bunu "ondalık destekliyoruz" başlığı altına yazmak yerine açıkça söylemekte fayda var.

Bu dilbilgisinin içindeki bir asimetriyi sahiplenmekte fayda var, ama ilk göründüğü yönde değil. Etiket öneki büyük küçük harfe duyarlı eşleştiriliyor; RFC 8216 4.1 bölümünün #EXT ile başlayan her etiketten istediği de bu. Öznitelik adları büyük küçük harfe duyarsız eşleştiriliyor, ki bunu hiçbir şey istemiyor. Yani küçük harfli #extinf: tanınmayan bir yönerge diye atlanırken, aynı satırdaki TVG-CHNO = "18" sorunsuz ayrıştırılıyor ve RFC'den sapan yarı, hoşgörülü olan yarı. Tutarsızlık daha eski, sunucu tarafındaki bir ayrıştırıcıdan yapılan taşımayla geldi ve kaynak dosya bunu hâlâ en üstte yazıyor. İki örnek çalma listesinin hiçbirinde küçük harfli etiket, büyük harfli öznitelik adı ya da eşittir işaretinin yanında boşluk yok; dolayısıyla korpusumuzda bu asimetrinin iki yarısı da hiç çalışmadı.

Ayırıcı olmayan virgül

Bir #EXTINF satırını ilk virgülünden bölün; iki dosyamızdan birindeki 308 girdinin 30'unu bozmuş olursunuz.

Sebebi şu: öznitelik değerleri tırnak içindeki dizeler ve tırnak içindeki dizeler virgül içerir. O 30 satırın her biri, tarayıcı UA dizesi taşıyan bir user-agent özniteliği taşıyor ve bu dizelerin 30'u da KHTML, like Gecko parçasını içeriyor. Yirmi sekizi onu parantez içine alıyor, ikisinde parantezler yukarıda bir yerde sıyrılmış; bu da virgül açısından hiçbir şeyi değiştirmiyor. Gerçek dosya üzerinde çalıştırılmış asıl karşılaştırma şu; kaynağın sunucu adları çıkarıldı, kanalın kendi adı değiştirildi:

naive first-comma split -> like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0",Example News (720p)
quote-aware split       -> Example News (720p)

O dosyanın yüzde dokuz virgül yedisi, görünen adı olarak birinin tarayıcı sürüm dizesini alıyor. Katalog yine de kuruluyor. Hiçbir şey hata vermiyor. Kanal listesi, çizim hatası gibi görünen ama olmayan bir biçimde yanlış.

Çözüm bir düzenli ifade değil, elle yazılmış bir tarayıcı: öznitelik bölgesinde karakter karakter yürüyün, her çift tırnakta bir inQuotes bayrağını çevirin ve o bayrak yanlışken görülen ilk virgülü döndürün. Solda kalan her şey öznitelik bölgesi, sağda kalan her şey görünen ad; ikisi de kırpılıyor. Dokuz satır kod ve ayrıştırıcıdaki en değerli dokuz satır.

O tarayıcı aynı zamanda bozuk bir satıra ne olacağına da karar veriyor ve karar katı. Öznitelik bölgesi tek sayıda çift tırnak taşıyorsa, örneğin kapanış tırnağının düştüğü group-title="Movies,Example Four durumunda, bayrak bir daha yanlışa dönmüyor, tırnak dışında hiçbir virgül bulunmuyor ve girdinin tamamı bir tanılamayla geri çevriliyor. İlk virgüle düşen bir yedek yol yok, tırnağın nerede olması gerektiğini tahmin etme girişimi de yok. Onarım yerine geri çevirmeyi seçtik, çünkü onarılmış bir satır sessizce yanlıştır, geri çevrilmiş bir satır ise gürültüyle eksiktir; gürültüyle eksik olan satır da okurun bildirebileceği satırdır.

Beş öznitelik okunuyor, bir dosya da yok saydığımız altıncısından 1.895 satır taşıyor

Ayrıştırıcımızın aradığı özniteliklerin tamamı beş yazımdan ibaret:

  • group-title, ilk noktalı virgüle kadar tutuluyor, boş olan yokluğa dönüşüyor
  • tvg-logo, kırpılıyor
  • logo, yalnızca tvg-logo tamamen yokken bakılıyor
  • tvg-id, ayrıştırma anında ham alınıyor, güven denetimi sonra yapılıyor
  • tvg-chno, hiçbir şey döndürmesine izin verilen bir sayı ayrıştırıcısına veriliyor

Listenin tamamı bu. Öznitelik adları, eşittir işaretinin iki yanında isteğe bağlı boşlukla ve büyük küçük harfe duyarsız eşleştiriliyor; yani TVG-CHNO = "18" ile tvg-chno="18" bizim için aynı. Değerler çift tırnaklı olmak zorunda; tırnaksız değer için bir yol yok ve tek tırnak hiçbir yerde tanınmıyor.

Şimdi gözlemlenen tarafa gelelim, orası daha ilginç. 1.895 girdilik dosyada tvg-name 1.895 satırın hepsinde görünüyor ve hepsini yok sayıyoruz. tvg-country 1.642 satırda görünüyor, onları da yok sayıyoruz. Görünen ad her zaman virgülden sonraki metinden geliyor, hiçbir zaman tvg-name'den değil; bir kaynak yayımladığı her satırda ikisini birden verse bile. Bu bir gözden kaçırma değil, tasarım kararı: virgülden sonraki başlık, dilbilgisinin kendisinin garanti ettiği alan, tvg-name isteğe bağlı ve tek bir yetkili ad kaynağı, dosyadan dosyaya farklı davranan bir tercih sırasından iyidir.

Küçük dosya, bir girdinin iki satırı arasında duran yönerge katmanını gösteriyor. 30 user-agent özniteliği, 2 referrer özniteliği (o üretici iki r ile yazmış, ki HTTP başlığının yazımı bu değil) ve 32 ayrı #EXTVLCOPT yönerge satırı taşıyor; bunların 30'u #EXTINF satırının zaten belirttiği kullanıcı aracısını tekrar ediyor. Bunların hiçbirini okumuyoruz.

Başka oynatıcılar bizim okumadığımız öznitelikleri okuyor ve bu listelerin hiçbiri bir diğeriyle aynı değil. Kodi için IPTV Simple eklentisi catchup, catchup-source, catchup-days, tvg-shift, radio özniteliklerini ve bir #EXTGRP yönergesini belgeliyor. Akıllı televizyonlar için bir oynatıcı olan SS-IPTV, Kodi'nin belgelemediği aspect-ratio'yu belgeliyor. tvg-language iptv-org'dan geldi; iptv-org o zamandan beri onu tvg-country ile birlikte bıraktı. Bu belirteçlerin hiçbiri örneğimizin hiçbir yerinde görünmüyor ve hiçbirini okumuyoruz; bu da söylenmeye değer dürüst bir ürün kısıtı doğuruyor: tekrar izleme desteğimiz var, ama sağlayıcı girişi üzerine kurulu; dolayısıyla bir M3U kaynağı hangi öznitelikleri yazarsa yazsın bize tekrar izleme sunamaz.

Yedi başlık, üç kanal

Aşağıda, her yolu aynı anda çalıştırmak için kurulmuş bir parça var. İçindeki her sunucu adı ayrılmış bir örnek alan adı, her ad uydurma. Çalıştırdığı şekillerin çoğu korpusumuzda gerçekten görülüyor, büyük dosyadan alınmış gerçek bir değer olan BS4K101 dahil. Beşi ise korpusun hiçbir yerinde yok: bozuk süre, dengesiz tırnak, çıplak logo, eşittir işaretinin iki yanı boşluklu büyük harfli öznitelik adı ve ## ile başlayan ad. Elle kurulmuş bir parça, o beş yola ulaşmanın tek yolu; aşağıdaki adım adım incelemeyi gerçek dosyalar hakkında kanıt diye okumadan önce bunu bilmekte fayda var.

#EXTM3U x-tvg-url="https://provider.example.com/guide.xml"
#EXTINF:-1 tvg-id="one.example" TVG-CHNO = "22.1" group-title="News;General" tvg-logo="",Example One
https://provider.example.com/stream/one.m3u8
#EXTINF:-1 tvg-id="two.example" user-agent="Mozilla/5.0 (KHTML, like Gecko)",Example Two
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (KHTML, like Gecko)
https://provider.example.com/stream/two.m3u8
#EXTINF:-1 tvg-chno="BS4K101" group-title="Local",## ===== LOCAL =====
https://provider.example.com/stream/divider
#EXTINF:x tvg-chno="7" group-title="Movies",Example Three
https://provider.example.com/stream/three.m3u8
#EXTINF:-1 group-title="Movies,Example Four
https://provider.example.com/stream/four.m3u8
#EXTINF:-1 tvg-chno="9",Example Five
rtmp://provider.example.com/stream/five
#EXTINF:-1 logo="https://provider.example.com/logos/six.png",Example Six
https://provider.example.com/stream/six.m3u8

İçeri yedi başlık giriyor. Ayrıştırıcıdan dört satır çıkıyor, bunların üçü kataloğa ulaşıyor ve beş tanılama dizesi üretiliyor.

Dosyanın 1. satırı atlanıyor. Ayrıştırıcımız #EXTM3U'yu hiç aramıyor; tanımadığı diğer her şeyle birlikte genel "kare işaretiyle başlıyor" atlamasından düşüyor ve üzerindeki x-tvg-url özniteliği okunmuyor. RFC 8216 o başlığı çalma listesinin ilk satırı olarak zorunlu tutuyor. Biz tutmuyoruz; doğrudan #EXTINF ile açılan başlıksız bir dosya da sorunsuz ayrıştırılıyor, çünkü ayrıştırmadan önce cihazda çalışan şekil denetimi, isteğe bağlı bir bayt sırası işaretinden ve baştaki boşluklardan sonra dosyanın ilk şeyi olarak iki etiketten birini kabul ediyor. Başka hiçbir şey önüne geçemez: bir yorum satırıyla ya da bir #EXTVLCOPT ile açılan dosya, ayrıştırıcı onu görmeden geri çevriliyor.

Example One, öznitelik adı yanlış harf durumundayken ve eşittir işaretinin iki yanında boşluk varken ayrıştırılıyor. Grubu News oluyor, çünkü yalnızca ilk noktalı virgülden önceki parça tutuluyor. Numarası tam sayı olarak 22 değil, ondalık olarak 22.1 oluyor; bu aşağıda önemli olacak. Logosu, hiçbir şey olmamasını bekleyeceğiniz yerde boş dize oluyor; bu da aşağıda önemli olacak.

Example Two, tırnak içindeki virgül durumu; ondan sonraki #EXTVLCOPT satırı da genel kare işareti atlamasının bekleyen başlığı temizlememesi gerektiğinin sebebi. Yönergeler bir #EXTINF ile URL'si arasında meşru biçimde durur. Tanımadığı her # satırında durumu sıfırlayan bir ayrıştırıcı bu kanalı ve buna benzeyen her kanalı düşürürdü; küçük örnek dosyada bu 308 girdinin 30'u demek.

Ayırıcı satır temiz ayrıştırılıyor ve dilbilgisi açısından bir kanal. tvg-chno değeri BS4K101, sayı ayrıştırıcısı tarafından reddediliyor ve yokluğa dönüşüyor; numara gidiyor ama satır kalıyor. Onu listeden çıkaran şey, adının ## ile başlaması ve bu daha sonra, ayrıştırma anında değil ad normalleştirmesi sırasında oluyor. İki aşamayı ayrı tutmak, ayrıştırıcının bir kanal adının ne anlama geldiği konusunda hiçbir görüşü olmaması demek; o sınır için doğru yer de burası.

Example Three süre üzerinde başarısız oluyor: x ne eksi ne de rakam, dolayısıyla başlık bir tanılamayla geri çevriliyor ve bekleyen beş alanın hepsi temizleniyor. Ardından URL'si bekleyen bir başlık olmadan geliyor ve ikinci bir tanılama üretiyor. Bozuk tek bir başlık iki satıra mal oluyor ve iki log satırı üretiyor; bu doğru ve gerçek bir içe aktarmanın tanılamalarını okurken bilmekte fayda var.

Example Four dengesiz tırnak durumu ve tıpatıp aynı davranıyor: iki tanılama, hiçbir çıktı yok.

Example Five kusursuz geçerli bir başlığa ve bir rtmp URL'sine sahip. Bu mutlak bir URI, dolayısıyla öyle ayrıştırılıyor, ama şeması ne http ne https ve onu geri çeviriyoruz. Parçadaki en tehlikeli satır bu, çünkü geri çevirme yolu bekleyen durumu temizlemeseydi Example Six 9 numaralı kanalı sessizce miras alırdı. Bu yol varsayımsal değil. Büyük örnek dosya 1.895 girdisinin 6'sını bir rtmp ya da rtsp URL'siyle bitiriyor, beşi ve biri; o 6 satır da 2.203 girdilik korpusun tamamında ayrıştırıcımızın geri çevirdiği tek satırlar.

Example Six, hiç tvg-logo ve hiç group-title olmadan çıplak bir logo özniteliği taşıyor; bu yüzden logo yedeği devreye giriyor ve kanal, kaynağın adını taşıyan öntanımlı bir gruba düşüyor.

Her atlama yolu başlığı temizlemek zorunda

Bir M3U girdisi, değişebilir durumu paylaşan iki satırdır ve biçimin bütün zorluğu buradadır. Ayrıştırıcı, bir başlığı okumakla bir URL'yi okumak arasında bekleyen beş alan tutuyor: ad, grup, logo, kimlik, numara. Döngünün içinden yedi ayrı yol geçiyor ve ayrıştırıcının doğruluğu tamamen her birinin o beş alana ne yaptığı meselesi.

Boş satırlar temizlemeden atlanıyor ve bu doğru, çünkü bir başlıkla URL'si arasındaki boş satır girdiyi yok etmemeli. Tanınmayan # yönergeleri de temizlemeden atlanıyor; sebebini yukarıdaki parça gösteriyor. Bozuk bir başlık beşini de temizliyor. Geri çevrilen bir URL beşini de temizliyor. Başarılı bir çıktı beşini de temizliyor.

Yedinci yol, satır çifti ayrıştırıcılarının çoğunun yanlış yaptığı yol ve ilginç tarafı, bir sıfırlamayla hiç ele alınmıyor olması. Herhangi bir URL gelmeden ikinci bir #EXTINF geldiğinde, ayrıştırıcı beş alanın hepsini koşulsuz atıyor; ne bir if var ne de null birleştirmeli bir atama. Hiçbir şey bulamayan bir öznitelik araması hiçbir şey döndürüyor, hiçbir şey verilen sayı ayrıştırıcısı da hiçbir şey döndürüyor; yani tvg-chno taşımayan bir başlık, önceki başlığın numarasını yerinde bırakmak yerine üzerine boşluk yazıyor. K girdisinin N alanının K+1 girdisine sağ çıkabileceği bir yol yok. Bu, "sıfırlamayı unutmuyoruz" demekten daha güçlü bir özellik ve bir ayrıştırıcıyı bilerek böyle kurmakta fayda var, çünkü sıfırlamalı sürüm ancak gelecekteki her düzenleme onu genişletmeyi hatırladığı sürece doğru kalır.

Bir yol not düşmeyi hak ediyor, çünkü hata gibi görünüyor ama değil. "URL bekleyen başlık olmadan geldi" yolu tanılamasını üretiyor ve hiçbir şeyi temizlemiyor. Bu şans eseri değil, yapı gereği güvenli; bu değişmez de atamalar okunarak denetlenebiliyor: diğer dört alanın atandığı tek yer başlık yolu ve o yol aynı geçişte adı her zaman boş olmayan bir değere ayarlıyor, başlık boşsa Unnamed channel düz dizesine düşerek. Yani null bir ad, diğer dördünün zaten null olduğu anlamına geliyor ve eksik sıfırlama, erişilemeyen bir iş.

Bu yolların her birinin kendi regresyon testine ihtiyacı var ve elimizdeki dokuz test sızma yollarını gerçekten kapsıyor: biri numaralı bir girdinin numarasını bir sonrakine sızdırmadığını, bir diğeri bozuk bir başlığın da sızdırmadığını doğruluyor. Hiç testi olmayan şey, bu yazının ayrıştırıcıdaki en değerli dokuz satır dediği tırnak içindeki virgül tarayıcısı; yanında dengesiz tırnak geri çevirmesi, iki satır arasına giren #EXTVLCOPT durumu ve çıplak logo yedeği. Bu boşluğun hiçbir yeri istisna fırlatmıyor. Katalog kuruluyor. Bozuk tek bir satırın ardındaki yüz kanal yanlış grubu taşıyor.

Tanımlayıcıya benzeyen iki alan

tvg-chno görüntüleme verisi ve bunu kesinleştiren ölçüm bir satırlık kabuk işi. 1.895 girdilik dosyada 1.355 girdi bu özniteliği taşıyor. O 1.355 numara 263 farklı değeri kapsıyor. 107 girdi 1 numarayı iddia ediyor. Yineleme, grupları birleştirmenin yan ürünü değil, çünkü gruplamadan sağ çıkıyor: o dosyadaki bir grupta hepsi 1 numaralı 7 kanal var ve herhangi bir numara taşıyan 85 grubun 33'ünün içinde en az bir yineleme var. O dosyada toplam 92 grup var, yani 7'si hiç numara taşımıyor. Diğer dosyada, 308 girdide, hiçbir yerde tvg-chno yok. Bu değer üzerinden hiçbir şey anahtarlanamaz, hiçbir şey benzersiz olduğunu varsayamaz ve ona göre sıralayan her tüketicinin deterministik bir eşitlik bozucuya ihtiyacı var.

Onu ayrıştırmak, akıllılık yapmayı reddetme konusunda küçük bir alıştırma. Değer, tam sayı olarak değil, sabit kültür (invariant culture) altında kayan noktalı sayı olarak okunuyor ve hiçbir zaman cihazın yerel ayarı altında okunmuyor. 1.355'in 56'sı doğrudan reddediliyor. O 56'nın hiçbiri sayısal değil:

  • 49'u yayın sistemi kodu: 7'si D011 ile D081 arasında, 19'u bir BS101 ile BS4K101 ailesinde, 22'si bir CS055 ile CS363 dizisinde ve bir kablo operatörünün önekini taşıyan tek bir kod
  • 4'ü düz bir #, anlaşılan üreticinin hiç doldurmadığı bir yer tutucu
  • 3'ü içine bir sayı gömülmüş serbest metin; ikisi CH 106 ve CH 108, biri de 527 ile biten Japonca bir servis adı

Ayrıştırıcı ayrıca 99999'luk bir tavan taşıyor; bunun üzerindeki bir değer çöp sayılıyor. Bu korpusta hiç devreye girmedi. Dosyadaki en büyük sayı 4999; bu da bir varsayım için yazılmış bir korumanın, o varsayımın gerçek olduğunun kanıtı olmadığına dair işe yarar bir hatırlatma.

Ondalık ayrıştırma bir üslup tercihi değil. O dosyadaki 25 girdi ATSC tarzı alt kanal numaralandırması kullanıyor; 22.1'den 36.2'ye giden 24 farklı değer var ve tam sayı ayrıştırması 22.1, 22.2 ve 22.3'ü hepsi 22 numaralı üç kanala kaynaştırıyor. Sabit kültür de bir üslup tercihi değil: cihazın yerel ayarına uymak, aynı dosyanın ondalık ayırıcısı virgül olacak şekilde ayarlanmış bir makinede farklı numaralar üretmesine yol açardı; bu da yalnızca başkasının bilgisayarında yeniden üretilebilen bir hata sınıfı.

tvg-id daha kötü, çünkü hataları boş değil. 1.895 girdilik dosyada öznitelik 483 satırda hiç yok, 20 satırda daha boş ve 24 satırda daha var ama şekil kuralımızı geçemiyor; yani 527 satır, dosyanın yüzde 27.8'i, kullanılabilir bir kimlik olmadan bitiyor. Rahatsız edici olanlar o 24 satır. On yedisi tıpatıp gerçek kimliklere benziyor (örneğin ülke soneki taşıyan kısa bir ad) ve tek bir sebeple, altı karakterden kısa oldukları için geri çevriliyor. Diğer 7'sinde nokta yok. En az uzunluk kaba bir kural, bize o 17'ye mal oluyor ve sayı bir gün büyürse eşiği yeniden ele alırız.

O kuralın var olma sebebi başka bir korpus; ayrıştırıcı işleri için tuttuğumuz, yukarıdaki iki derlenmiş dosyadan değil sağlayıcı dışa aktarımlarından oluşan bir korpus. Onun üç kaynağının ikisinde en sık görülen tek tvg-id değeri düz TS dizesiydi: birinde 446 satır, diğerinde 274. TS, aktarım akışı kapsayıcı biçimi; bir çıktı biçimi alanından sızıp kimlik sütununa düşmüş. Birbiriyle ilgisiz o 720 kanalın arasında, tek bir tanımlayıcı altında yan yana duran bir İngiliz eğlence kanalı ile Arapça bir radyo kanalı vardı. Kanal anahtarı kimlikten türetildiği için 720'sinin tamamı tek bir mantıksal kanala çöktü. Sonucu şu: kimsenin işaretlemediği kanallarda beliren bir favori, aynı anda 720 şeyi kilitleyen bir ebeveyn kilidi ve hepsinin yerine geçen tek bir çoklu görünüm yuvası.

Onarım, tek bir yeri olan bir güven kuralıydı; aşağıdaki hiçbir şeyin yeniden denetlemeyi hatırlamak zorunda kalmaması için içe alma sınırında uygulanıyor. Bir kimlik ancak 6 ile 128 karakter arasındaysa, nokta içeriyorsa ve TS değilse kullanılabilir; sağ çıkan da sabit kültür altında küçük harfe çevriliyor, çünkü kaynaklar aynı kanalın kimliğinin harf durumu konusunda gerçekten anlaşamıyor. Başka her şey yokluğa normalleştiriliyor ve "kimlik yok" durumunu her tüketici zaten karşılıyor, çünkü çoğu kanalın gerçekten kimliği yok. Kural gerekli ama yeterli değil ve bunu biliyoruz: şekil denetimini geçen bir kimliğin birbiriyle ilgisiz 74 kanala bağlı olduğu ortaya çıktı; bu yüzden kaynakların birleştirildiği her yerde bunun üstünde derleme anında bir kardinalite denetimi çalışıyor. Örneğimizdeki iki derlenmiş dosyanın hiçbirinde tek bir TS kimliği bile yok; bütün bunların dürüst çekincesi de bu. Temiz bir korpus bu arızayı size hiçbir zaman göstermez. Bunu bir testten değil, bir hata bildiriminden öğrendik.

Çoğu dosyada kullanılabilir kimlik olmadığı için bir tane türetmek gerekiyor ve rahatsız edici ödünleşme de bu türetmede. Yayın akışı URL'si, tek bir kaynağın içinde her zaman var olan ve benzersizliğe en çok yaklaşan tek alan; bu yüzden sentetik kimlik, o URL'nin SHA-1'i: ilk dört bayt little-endian okunup pozitif 32 bitlik bir tam sayıya katlanıyor. Geriye kabaca 2^31 anahtar uzayı kalıyor ve doğum günü sınırına göre 50.000 girdilik bir çalma listesi, en az bir iç çakışma için yaklaşık yüzde 44 ihtimal taşıyor. Bu sayıyı, aritmetiği önümüzde dururken kabul ettik; gerekçemiz, bir çakışmanın tek bir kaynağın içindeki iki kanalı birleştirmesi, ki bir kaynak aynı kanalı iki kez listelediğinde zaten olan şey bu. Kendi korpusumuz bunu 9 URL üzerinde, büyük dosyadaki 1.895 satırın 20'sini kapsayacak şekilde yapıyor, 308 girdilik dosyada ise hiç yapmıyor; yani gerçek ama rutin olmayan bir olay ve bu ölçüm savı desteklemekten çok zayıflatıyor. Soruyu asıl kesinleştiren şey şu: temeli tvg-id'yi de kapsayacak şekilde genişletmek ya da daha geniş bir hash'e geçmek sürüme çıkarılamaz; türetilen kimlik, başka cihazların geri okuduğu kayıtlı verinin içine yazılıyor, dolayısıyla türetmeyi değiştirmek hepsini bir anda geçersiz kılar. Çakışma oranı, donmuş bir veri biçiminin bedeli.

Boş olmak yok olmak değildir, bir alan hariç

Üç öznitelik, boş dize için üç ayrı politika ve ölçene kadar fark etmedik.

group-title="" yokluğa dönüşüyor, çünkü grup normalleştiricisi boşluğu yokluk sayıyor ve küçük dosyadaki 25 satır bu yoldan gidiyor. tvg-id="" yokluğa dönüşüyor, çünkü güven kuralı onu geri çeviriyor. tvg-logo="" dönüşmüyor: boş dize olarak geliyor ve katalog satırının simge alanına kadar gidiyor. Bir örnek dosyada 19, diğerinde 42 satır boş bir logo özniteliği taşıyor; yani 2.203 satırlık bir korpusta 61 satır, diğer her alanın hiçbir şey tutacağı yerde boş dize tutuyor. Logonun tuhaf olan taraf olmasının, üç dönüşümün yazılma sırasından başka bir sebebi yok.

Bunun bedeli, okumadığımız bir yola bağlı. Görsel yükleyici boş dize ile null'ı aynı şekilde ele alıyorsa, bu bir pürüzden ibaret. Boş dize başarısız olan bir istek başlatıyorsa, 61 satır her çizimde gereksiz iş yapıyor demektir ve pürüz küçük bir hataya dönüşür. Bu sayfa ikisinden birini iddia etmeden önce birinin o yolu okuması gerekiyor.

Ölçmediklerimiz

Bu ayrıştırıcıdaki bazı sınırların örneğimizde tanığı yok ve bunu söylemek, test edilmiş gibi davranmaktan daha faydalı. Tırnaksız değer için bir yol yok: tırnaksız tvg-chno=18 bizim için görünmez. Elimizdeki 2.203 #EXTINF satırının sıfırı tırnaksız değer kullanıyor, dolayısıyla bu bizi hiç ısırmadı ve şeklin doğada ne kadar yaygın olduğunu bilmiyoruz. Çıplak logo yedeğinde aynı sorun tersinden var: satırlarımızın sıfırı onu kullanıyor, yani sürüme çıkardığımız bir yedeğin gerçek girdiye karşı hiç testi yok.

Bu sayfayı yazmak, sürüme çıkarmış olduğumuz bir hatayı buldu; yaptığı en faydalı şey de bu oldu. Öznitelik eşleştiricisini yayımlanacak kadar açık biçimde ortaya koymak, desenin sol sınırı olmadığını apaçık gösterdi: logo deseni, logo ile biten herhangi bir öznitelik adının içinde eşleşiyordu. Buna, Kodi için IPTV Simple eklentisinin belgelediği gerçek bir öznitelik olan provider-logo da dahil. provider-logo taşıyan ve tvg-logo taşımayan bir çalma listesi yanlış görseli alıyordu ve kısa devre bizi kurtarmadı, çünkü o yalnızca tvg-logo varken devreye giriyor. Aynı delik, tvg-id'nin x-tvg-id içinde, group-title ile tvg-chno'nun da bunlarla biten herhangi bir şeyin içinde eşleşmesine izin veriyordu.

Bariz çözümün yanlış olması bizi şaşırttı. Burada sözcük sınırı işe yaramıyor: bu uzlaşımdaki her öznitelik adı tireyle birleşik ve - bir sözcük karakteri değil, dolayısıyla tire ile ad arasında zaten bir sınır var ve \btvg-logo yine de x-tvg-logo içinde eşleşiyor. Asıl istediğiniz koşul, adın öznitelik bölgesini başlatması ya da bir boşluktan sonra gelmesi. .NET'te bu, (?<=^|\s) şeklinde bir geriye bakış; sandığınızdan daha az motorda taşınabilir: Python bunu derlemeyi reddediyor, çünkü çoğu motor sabit genişlikli bir geriye bakış istiyor, bu ise seçenekli. Dört özniteliğin dördü de artık o sınırı taşıyor ve yukarıdaki altı durum testlerle sabitlenmiş.

Oynatıcımız bütün bunları sizin cihazınızda, sizin verdiğiniz çalma listesi üzerinde çalıştırıyor ve her geri çevirme istisna yerine bir tanılama dizesi üretiyor; çünkü 3.000 satırın 40'ı bozuk olan bir dosya, diğer 2.960 satırı içe aktarmak ve size neyin eksildiğini söylemek zorunda. Windows ve Xbox uygulamaları bu işi yerelde yapıyor ve siz de birini zaten elinizde olan bir kaynağa yöneltiyorsunuz.

Kendi ayrıştırıcınızı yazıyorsanız, yukarıdaki ölçümler size biçim belgelerinden daha çok yarar sağlar; sebebi de büyük ölçüde ortada pek biçim belgesi olmaması. Girdilerinizi sayın. Her özniteliğin ne sıklıkta geçtiğini sayın, kaç farklı değeri olduğunu sayın ve o alan üzerinden bir şey anahtarlamadan önce bu iki sayıyı karşılaştırın. Bu, zaten elinizde olan bir korpusla bir öğleden sonra sürer ve test ettiğiniz dosyada çalışan bir ayrıştırıcı ile size gönderilen dosyada çalışan bir ayrıştırıcı arasındaki fark budur.

Bu makale neyi ölçtü?26 iddiaların her biri arkasında deliller barındırıyor
TalepKanıtsayıldı
RFC 8216, #EXTINF'i bir süre, bir virgül ve satır sonuna kadar giden isteğe bağlı bir başlık olarak tanımlıyor.RFC 8216 section 4.3.2.1ŞartnameGeçerli değil
RFC 8216, kesirli bir #EXTINF süresine izin veriyor.RFC 8216 section 4.3.2.1ŞartnameGeçerli değil
RFC 8216, #EXTM3U'yu çalma listesinin ilk satırı olarak zorunlu tutuyor.RFC 8216 section 4.3.1.1ŞartnameGeçerli değil
RFC 8216, etiketlerin #EXT ile başladığını ve büyük küçük harfe duyarlı olduğunu söylüyor; dolayısıyla ordinal bir önek eşleşmesi kurallara uygun davranış.RFC 8216 section 4.1ŞartnameGeçerli değil
Extended M3U, #EXTM3U ve #EXTINF'i getiren Winamp dönemi uzlaşımıdır ve RFC 8216 ondan adıyla söz eder. tvg- belirteçlerinin hiçbirini içermez.RFC 8216 section 4.3.1.1, which reads: the EXTM3U tag indicates that the file is an Extended M3U [M3U] Playlist fileŞartnameGeçerli değil
M3U+ bir biçim adı değil. m3u_plus, sağlayıcı girişiyle bağlanılan sağlayıcı sunucularının bir çıktı modu; bir dışa aktarımı çıplak URL'lerden tvg-id, tvg-logo ve group-title taşıyan satırlara çeviren sorgu değeri.Provider login API, playlist export parameter type=m3u_plusŞartnameGeçerli değil
Kodi için IPTV Simple eklentisi catchup, catchup-source, catchup-days, tvg-shift, radio, provider-logo özniteliklerini ve bir #EXTGRP yönergesini belgeliyor.kodi-pvr/pvr.iptvsimple README, playlist attribute referenceŞartnameGeçerli değil
aspect-ratio'yu Kodi değil, SS-IPTV belgeliyor.SS-IPTV playlist attribute documentationŞartnameGeçerli değil
tvg-language iptv-org'dan geldi; iptv-org o zamandan beri onu tvg-country ile birlikte bıraktı.iptv-org/iptv repository, playlist field historyŞartnameGeçerli değil
~2^31 büyüklüğünde bir anahtar uzayında 50.000 girdilik bir çalma listesi, en az bir iç çakışma için yaklaşık yüzde 44 ihtimal taşıyor.Birthday bound, 1 - exp(-n(n-1)/2N) with N = 2^31 and n = 50,000, which evaluates to 0.4412ŞartnameGeçerli değil
Korpustaki her #EXTINF satırı -1 süresi taşıyor.n = 220322 Ağu 2026
İlk virgülden bölmek, küçük dosyadaki 308 girdinin 30'unu bozuyor; 30'unun da user-agent değeri KHTML, like Gecko parçasını içeriyor.n = 30822 Ağu 2026
1.895 girdinin 1.355'i tvg-chno taşıyor, 263 farklı değere yayılmış hâlde; 107 satır 1 numarayı iddia ediyor ve bir grupta hepsi 1 numaralı 7 kanal var.n = 189522 Ağu 2026
Büyük dosyada 92 grup var; bunların 85'i en az bir numara taşıyor ve o 85'in 33'ünde yinelenen bir numara var.n = 189522 Ağu 2026
1.355 tvg-chno değerinin 56'sı reddediliyor ve hiçbiri sayısal değil: 49 yayın sistemi kodu, 4 tane düz # yer tutucusu, 3 serbest metin değeri. Ayrıştırıcının 99999 tavanı hiç devreye girmedi; dosyadaki en büyük sayı 4999.n = 135522 Ağu 2026
25 satır ATSC tarzı alt kanal numaralandırması kullanıyor, 22.1'den 36.2'ye giden 24 farklı değere yayılmış hâlde.n = 189522 Ağu 2026
Büyük dosyada tvg-id 483 satırda hiç yok, 20 satırda daha boş ve 24 satırda daha var ama şekil kuralını geçemiyor; yani 527 satır (yüzde 27.8) kullanılabilir bir kimlik olmadan bitiyor.n = 189522 Ağu 2026
O 24 şekil hatasının 17'si yalnızca altı karakterden kısa olduğu için geri çevriliyor ve gerçek kimliklere benziyor; örneğin kısa bir ad ve ardından bir ülke soneki. Diğer 7'sinde nokta yok.n = 2422 Ağu 2026
Sağlayıcı dışa aktarımlarından oluşan ayrı bir korpusta TS, üç kaynağın ikisinde en sık görülen tvg-id değeriydi; birinde 446 satır, diğerinde 274 satır. Birbiriyle ilgisiz o 720 kanal tek bir kanal anahtarına çöktü. Şekil denetimini geçen bir kimlik, birbiriyle ilgisiz 74 kanala bağlıydı.n = 720Daha önceki külliyat
Yinelenen yayın akışı URL'leri gerçek ama seyrek: büyük dosyada 1.895 satırın 20'sini kapsayan 9 yinelenmiş URL değeri, küçük dosyada 308'de 0.n = 220322 Ağu 2026
Büyük dosyada 6 satır bir rtmp ya da rtsp URL'sini gösteriyor (5 ve 1) ve bunlar korpusun tamamında ayrıştırıcının geri çevirdiği tek satırlar.n = 220322 Ağu 2026
61 satır boş bir tvg-logo taşıyor (küçük dosyada 19, büyük dosyada 42) ve boş dize katalog satırına kadar sağ çıkıyor; oysa boş bir group-title (25 satır) ve boş bir tvg-id yokluğa dönüşüyor.n = 220322 Ağu 2026
tvg-name büyük dosyanın 1.895 satırının hepsinde, tvg-country 1.642 satırında görünüyor; küçük dosya 30 user-agent özniteliği, 2 referrer özniteliği ve 32 #EXTVLCOPT yönerge satırı taşıyor.n = 220322 Ağu 2026
Korpusta tırnaksız öznitelik değeri sıfır, küçük harfli #extinf etiketi sıfır, büyük harfli öznitelik adı sıfır, eşittir işaretinin iki yanında boşluk sıfır, dengesiz tırnaklı satır sıfır ve çıplak logo özniteliği sıfır.n = 220322 Ağu 2026
Bu yazıdaki elle kurulmuş parça 4 ayrıştırılmış satır, 3 katalog satırı ve 5 tanılama veriyor.n = 722 Ağu 2026
Ayrıştırıcının 9 birim testi var; ikisi alanların girdiler arasında sızmadığını doğruluyor. Tırnak içindeki virgülü bulan tarayıcının, dengesiz tırnak geri çevirmesinin, iki satır arasına giren #EXTVLCOPT durumunun ve çıplak logo yedeğinin hiç testi yok.n = 922 Ağu 2026