I think I know what might be happening luke
You might live somewhere that doesn't get Pluto TV.
I got the same error when I tried to get Pluto TV for a different region.
So I updated the script to take an optional argument for an IP address.
Pluto TV trusts the x-forwarded-for
header, so no need for a proxy or VPN.
Just pass a known static IP that is in the region for which channels you want.
For example, if I wanted to appear in the US, I could use Googles DNS IP (8.8.8.8)
#!/usr/bin/python
import requests
from datetime import datetime, timedelta, timezone
import xml.etree.ElementTree as ET
import socket
import io
import sys
# CONFIG #############################################################################################
SOCKET = "/config/epggrab/xmltv.sock" # Path to your UNIX socket
######################################################################################################
if len(sys.argv) > 1:
IP = sys.argv[1]
else:
IP = None
now = datetime.now(timezone.utc)
start_time = now - timedelta(
minutes=now.minute % 30, seconds=now.second, microseconds=now.microsecond
)
stop_time = start_time + timedelta(hours=8)
epg_begin = start_time.strftime("%Y-%m-%dT%H:%M:%S-00:00")
epg_end = stop_time.strftime("%Y-%m-%dT%H:%M:%S-00:00")
if IP != None:
headers={"X-Forwarded-For": IP}
else:
headers={}
data = requests.get(
f"https://api.pluto.tv/v2/channels?start={epg_begin}&stop={epg_end}", headers=headers
).json()
m3u = "#EXTM3U\n"
root = ET.Element("tv")
programmes = []
for channel in data:
channel_element = ET.SubElement(root, "channel", id="pluto-" + channel["_id"])
ET.SubElement(channel_element, "display-name").text = channel["name"]
ET.SubElement(channel_element, "channel-number").text = str(channel["number"])
ET.SubElement(channel_element, "icon").text = channel.get("colorLogoPNG", {}).get(
"path", ""
)
m3u += f'#EXTINF:-1 tvg-id="{"pluto-" + channel["_id"]}" tvg-chno="{channel["number"]}" tvg-name="{channel["name"]}" tvg-logo="{channel.get("colorLogoPNG", {}).get("path", "")}",{channel["name"]}\n'
if IP != None:
m3u += f"pipe://streamlink --http-header 'x-forwarded-for={IP}' 'https://pluto.tv/live-tv/{channel['_id']}' best --stdout\n"
else:
m3u += f"pipe://streamlink 'https://pluto.tv/live-tv/{channel['_id']}' best --stdout\n"
for programme in channel["timelines"]:
programme_data = {
"start": programme["start"],
"stop": programme["stop"],
"channel": "pluto-" + channel["_id"],
"title": programme["title"],
"episode": programme.get("episode", {}),
}
programmes.append(programme_data)
for programme in programmes:
start_time = datetime.fromisoformat(programme["start"]).strftime(
"%Y%m%d%H%M%S +0000"
)
stop_time = datetime.fromisoformat(programme["stop"]).strftime("%Y%m%d%H%M%S +0000")
programme_element = ET.SubElement(
root,
"programme",
start=start_time,
stop=stop_time,
channel=programme["channel"],
)
ET.SubElement(programme_element, "title").text = programme.get("title", "Untitled")
ET.SubElement(programme_element, "sub-title").text = programme["episode"].get(
"name", ""
)
ET.SubElement(programme_element, "desc").text = programme["episode"].get(
"description", ""
)
ET.SubElement(programme_element, "episode-num").text = (
f'S{programme["episode"].get("season", 0)}E{programme["episode"].get("number", 0)}'
)
ET.SubElement(programme_element, "category").text = programme["episode"].get(
"genre", ""
)
ET.SubElement(programme_element, "category").text = programme["episode"].get(
"subGenre", ""
)
ET.SubElement(programme_element, "category").text = programme.get("category", "")
tree = ET.ElementTree(root)
ET.indent(tree, space="\t", level=0)
xml_bytes = io.BytesIO()
tree.write(xml_bytes, encoding="UTF-8", xml_declaration=True)
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.connect(SOCKET)
xml_bytes.seek(0)
s.sendall(xml_bytes.read())
except:
pass
with open("pluto.xmltv", "wb") as file:
tree.write(file, encoding="utf-8", xml_declaration=True)
print(m3u)
Set the URL on the network like this pipe:///storage/downloads/plutotv.py 8.8.8.8
Adding no option uses your real IP pipe:///storage/downloads/plutotv.py