Xumo m3u

I currently have Pluto setup on docker and it works great. Just curious if anyone has been able to figure out how to do the same thing with Xumo? And I know they are similar. But Xumo has a History channel that I’m looking at. Thanks

3 Likes

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?

1 Like

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

1 Like

Thanks! Works great.

docker solution xumo m3u + epg

Millie / vlc-bridge-xumo · GitLab

$ 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

1 Like

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.

1 Like

not work vlc. i remove it

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 .....

1 Like

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).

that's definitely in the past, @miibeez should fix it

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)