I've seen a XUMO M3U kicking around but I can't find a functional XML EPG for it.
Yes I found that one as-well. I know the people on this community are super smart I’m hoping somebody can figure it out!
Has anyone figured this out yet? If not, are there any third party EPG's available for XUMO or others?
Any developments on Xumo? I really like the channels--and it has Fox Weather. Would be nice to integrate it in Channels...
FWIW you can already add Fox Weather, follow instructions in this thread: Fox Weather - #85 by jilted
Thanks! Works great.
docker solution xumo m3u + epg
$ docker logs vlc-bridge-xumo
ERROR in app: Exception on /xumo/playlist.m3u [GET]
Traceback (most recent call last):
server.py", line 46, in playlist
KeyError: 'url'
To fix:
$ git diff
diff --git a/server.py b/server.py
index 3ebc5fe..91389c2 100644
--- a/server.py
+++ b/server.py
@@ -43,7 +43,7 @@ def playlist(provider):
if genre := s.get('genre'):
m3u += f" group-title=\"{';'.join([x.strip().title() for x in genre.split(',')])}\""
m3u += f",{s.get('name') or s.get('id')}\n"
- m3u += f"{s['url']}\n\n"
+ m3u += f"http://{host}/{provider}/watch/{s['watchId']}\n\n"
return m3u
@app.get("/<provider>/watch/<id>")
Some streams are in MPD format. There was a feature request for channels to support MPD files but somehow disappeared. For now this can be used
fix it bug
which channel problem?
These channels are not watchable anyway
$ streamlink http://127.0.0.1:7777/xumo/watch/99991352
[cli][info] Found matching plugin dash for URL http://127.0.0.1:7777/xumo/watch/99991352
error: https://live-content.xumo.com/drm/2565/DASH/XM07AHJBB52U80/1676657780123/mpds/manifest.mpd is protected by DRM
It might be better to pull the non-DRM streams from elsewhere.
You should remove nuitka, no use for home user. Plus docker compose takes forever. I just run the script directly instead through docker/nuitka.
(vlc-bridge-xumo) $ pip install -r requirements.txt
(vlc-bridge-xumo) $ export PROVIDER=xumo
(vlc-bridge-xumo) $ python server.py
⇨ http server started on [::]:7777
Docker aficionados, please get familiar with venv — Creation of virtual environments — Python 3.11.3 documentation before making comments.
I installed the XUMO vlc bridge in docker.
It runs and produces the playlist.m3u and epg.xml
I added source to channels, and it added the xumo channels.
However, it never populates the channel guide with the epg.xml data.
I can play the stations, but need guide data.
The channels log shows this for the EPG update:
2023/07/27 11:11:04.165026 [DVR] Fetched guide data for XMLTV-VLCXUMO in 6s
2023/07/27 11:11:04.779100 [DVR] Indexed 1 airings into XMLTV-VLCXUMO (309 channels over 24h0m0s) + 2693 skipped [0s index]
2023/07/27 11:11:05.044868 [IDX] Pruned 0 expired groups from XMLTV-VLCXUMO in 320.625µs.
Any clues as to why it might not be populating the guide data?
Leave it running for a few hours. It will eventually do the right thing.
I think the problem is DVR does not like EPG timestamps produced by the VLC bridge and thinks they happened in the past.
Could we report in the log the highest and lowest timestamps from the EPG? Or the reason for skipping like happened in the past, too far in the future, etc .....
Thank you @sdust , I will wait to see how it goes.
Looking at the raw epg data for one station, I see first and last entries of:
<programme start="20230727000000 +0000" stop="20230727010000 +0000" channel="xumo-99991247" clumpidx="0/1">
<title>Stay Tuned NOW With Gadi Schwartz</title>
<desc>A fresh look at the day's news, from the latest breaking news to what's on the horizon.</desc>
<icon src="https://image.xumo.com/v1/channels/channel/xumo-99991247/600x337.png?type=channelTile"/>
<episode-num system="dd_progid">EP046743610098</episode-num>
</programme>
...
<programme start="20230727050000 +0000" stop="20230727060000 +0000" channel="xumo-99991247" clumpidx="0/1">
<title>NBC Nightly News With Lester Holt</title>
<desc>The latest news, going beyond the headlines to see how lives are affected by the world around them.</desc>
<icon src="https://image.xumo.com/v1/channels/channel/xumo-99991247/600x337.png?type=channelTile"/>
<episode-num system="dd_progid">EP023034512799</episode-num>
</programme>
So these do seem to be "in the past".
In case it makes any difference, I setup docker via:
docker run -d -p 7778:7777 --name vlc-bridge-xumo registry.gitlab.com/miibeez/vlc-bridge-xumo
(I had to use different port, as 7777 is used by the PBS version of the bridge (but of course using EPG from a local EPG provider, which is probably why it loaded guide right after setup).
FYI, now that it has reached midnight UTC (few minutes ago), I forced a re download of the EPG and now guide is populated.
So it looks like some fine tuning of the EPG generation is needed.
Also, it looks like XUMO offers EPG for at least a couple days (did not try to scroll to end on their website), so it might be possible to modify to check several future dates to be able to give an extended EPG.
I have not seen activity by @mibeez here or in their repository for a while, so I might just play with the EPG code myself to see if I can make these changes.
I don't have ability to setup a docker, so I simply made a py script to pull in and save the epg xml file that I run on a cronjob.
It pulls in 7 days worth of data (maybe able to get more, I simply am starting at 7).
The change was to the xumo.py file in the github mentioned above for the function "epg" the modified function is :
def epg(self):
self.load_list()
epg = xmltv.Tv(
source_info_name="xumo",
generator_info_name="vlc-bridge"
)
current_date = datetime.now(timezone.utc)
day_count = 0 # counter for the number of days processed
while day_count < 7: # run the loop for 7 days
today = current_date.strftime('%Y%m%d')
offset = 0
while True:
url = "https://android-tv-mds.xumo.com/v2/epg/10032/"+today+"/0.json?limit=50&offset="+str(offset)+"&f=asset.title&f=asset.descriptions"
print(f"Making a GET request to: {url}")
data = requests.get(url).json()
if "channels" not in data or len(data["channels"]) == 0:
break
offset += 50
for ch in data["channels"]:
id = str(ch["channelId"])
if self.list.get(id) is None:
continue
gid = "xumo-"+id
epg.channel.append(xmltv.Channel(
id=gid,
display_name=[self.list[id]["title"].strip()]
))
for s in ch["schedule"]:
assetId = s["assetId"]
asset = data["assets"][assetId]
d = asset["descriptions"]
desc = d.get("large") or d.get("medium") or d.get("small") or d.get("tiny") or ""
p = xmltv.Programme(
channel=gid,
start=datetime.strptime(s["start"], '%Y-%m-%dT%H:%M:%S%z').strftime('%Y%m%d%H%M%S %z'),
stop=datetime.strptime(s["end"], '%Y-%m-%dT%H:%M:%S%z').strftime('%Y%m%d%H%M%S %z'),
title=asset["title"],
sub_title=asset.get("episodeTitle"),
desc=desc,
)
if assetId.startswith("EP"):
p.episode_num = xmltv.EpisodeNum(
system="dd_progid",
content=[assetId],
)
else:
p.episode_num = xmltv.EpisodeNum(
system="dd_seriesid",
content=[assetId],
)
p.icon = xmltv.Icon(
src="https://image.xumo.com/v1/channels/channel/"+gid+"/600x337.png?type=channelTile",
)
epg.programme.append(p)
current_date += timedelta(days=1) # move to the next day
day_count += 1 # increment the counter
offset = 0 # reset the offset
serializer = XmlSerializer(config=SerializerConfig(
pretty_print=True,
encoding="UTF-8",
xml_version="1.1",
xml_declaration=False,
no_namespace_schema_location=None
))
return serializer.render(epg)
In case anyone wants a standalone py script to build/updte the epg, it is (note hard coded path to save file near end, in my case running on an ubuntu machine Apache server):
import requests
import re
import time
import uuid
from datetime import datetime, timezone, timedelta
from xmltv.models import xmltv
from xsdata.formats.dataclass.serializers import XmlSerializer
from xsdata.formats.dataclass.serializers.config import SerializerConfig
class Client:
def __init__(self):
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'okhttp/4.9.3'
})
self.list = None
def load_list(self):
if self.list is None:
self.list = {}
url = "https://android-tv-mds.xumo.com/v2/channels/list/10032.json?f=genreId&sort=hybrid&geoId=unknown"
print(f"Making a GET request to: {url}")
data = self.session.get(url).json()
for ch in data["channel"]["item"]:
if ch["callsign"].endswith("-DRM") or ch["callsign"].endswith("DRM-CMS"):
continue
if ch["properties"].get("is_live") != "true":
continue
id = ch["guid"]["value"]
self.list[id] = ch
def epg(self):
self.load_list()
epg = xmltv.Tv(
source_info_name="xumo",
generator_info_name="vlc-bridge"
)
current_date = datetime.now(timezone.utc)
day_count = 0 # counter for the number of days processed
while day_count < 7: # run the loop for 7 days
today = current_date.strftime('%Y%m%d')
offset = 0
while True:
url = "https://android-tv-mds.xumo.com/v2/epg/10032/"+today+"/0.json?limit=50&offset="+str(offset)+"&f=asset.title&f=asset.descriptions"
print(f"Making a GET request to: {url}")
data = requests.get(url).json()
if "channels" not in data or len(data["channels"]) == 0:
break
offset += 50
for ch in data["channels"]:
id = str(ch["channelId"])
if self.list.get(id) is None:
continue
gid = "xumo-"+id
epg.channel.append(xmltv.Channel(
id=gid,
display_name=[self.list[id]["title"].strip()]
))
for s in ch["schedule"]:
assetId = s["assetId"]
asset = data["assets"][assetId]
d = asset["descriptions"]
desc = d.get("large") or d.get("medium") or d.get("small") or d.get("tiny") or ""
p = xmltv.Programme(
channel=gid,
start=datetime.strptime(s["start"], '%Y-%m-%dT%H:%M:%S%z').strftime('%Y%m%d%H%M%S %z'),
stop=datetime.strptime(s["end"], '%Y-%m-%dT%H:%M:%S%z').strftime('%Y%m%d%H%M%S %z'),
title=asset["title"],
sub_title=asset.get("episodeTitle"),
desc=desc,
)
if assetId.startswith("EP"):
p.episode_num = xmltv.EpisodeNum(
system="dd_progid",
content=[assetId],
)
else:
p.episode_num = xmltv.EpisodeNum(
system="dd_seriesid",
content=[assetId],
)
p.icon = xmltv.Icon(
src="https://image.xumo.com/v1/channels/channel/"+gid+"/600x337.png?type=channelTile",
)
epg.programme.append(p)
current_date += timedelta(days=1) # move to the next day
day_count += 1 # increment the counter
offset = 0 # reset the offset
serializer = XmlSerializer(config=SerializerConfig(
pretty_print=True,
encoding="UTF-8",
xml_version="1.1",
xml_declaration=False,
no_namespace_schema_location=None
))
return serializer.render(epg)
if __name__ == '__main__':
client = Client()
epg_data = client.epg()
with open('/var/www/html/xumo_epg.xml', 'w') as f:
f.write(epg_data)
This is a very nice start, but I think there are a few issues to clean up.
You need to change this to with open('/var/www/html/xumo_epg.xml', 'w', encoding='utf-8') as f:
otherwise you'll get this error (or something like it):
UnicodeEncodeError: 'charmap' codec can't encode character '\u02bc' in position 645982: character maps to <undefined>
I am wondering if these characters are causing issues, though, and require something more robust to clean up. You'll see why below...
For anyone running this for the for the first time, you need to do pip install -r https://gitlab.com/miibeez/vlc-bridge-xumo/-/raw/main/requirements.txt
to get all the required components installed first.
As you noted, you have to edit line 103 to change the path where you want the xml file to end up. If you are using Windows, be sure to do something like C:\\path\\to\\where\\you\\want\\it\\xumo_epg.xml
, where the double slashes are required!
That all said, I'm still seeing huge holes in the schedule and some stations where the only thing scheduled is their names. Here's some examples of both:
So yeah, I think what you've got going on here is the right path, but is probably going to require a deeper dive than you may want to get into!