Serve VoD as .strm Files #139
Replies: 14 comments 11 replies
-
If I could get some examples, or other projects that are doing this I'd be happy to take a look and see if it's something I could add. |
Beta Was this translation helpful? Give feedback.
-
What examples do you need? I've made a Python Script for my testing purposes to see if .strm files are suitable for Jellyfin. |
Beta Was this translation helpful? Give feedback.
-
Perfect! That's what I meant, an example, just like that 😉
let me review and see what I can come up with...
…On Thu, May 1, 2025, 12:03 PM martenumberto ***@***.***> wrote:
*martenumberto* left a comment (sparkison/m3u-editor#124)
<#124 (comment)>
What examples do you need? I've made a Python Script for my testing
purposes to see if .strm files are suitable for Jellyfin.
`import requests
import json
import re
import os
import time
server = "http://my-provider.com"
username = "userxyz"
password = "passxyz"
DIR_SERIES = "Series"
DIR_MOVIES = "Movies"
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'}
def main():
print("Xtream2STRM helper")
print("Connecting to: " + server)
authJSON = json.loads(authenticate())
userJSON = authJSON["user_info"]
if (userJSON["auth"]):
print("Successfully Authenticated. Getting Series Categorys...")
menu = input("Type S(eries) or M(ovies): ")
if(menu == "S" or menu == "s"):
menuSeriesCat()
if(menu == "M" or menu == "m"):
menuMoviesCat()
else:
print("Authentication faled")
quit()
def menuMoviesCat():
moviesCat = json.loads(getVodCat())
for cat in moviesCat:
print("Category Name: " + cat["category_name"] + " Category ID: " +
cat["category_id"])
moviesCatID = input("Enter Category ID: ")
moviesJSON = json.loads(getVodStreams(moviesCatID))
for movie in moviesJSON:
print("Movie: " + movie["name"] + " ID: " + str(movie["stream_id"]))
movieID = input("Enter Movie ID or type All: ")
if (movieID == "All"):
for cat in moviesCat:
if(cat["category_id"] == moviesCatID):
movieWholeCat(moviesJSON, cat["category_name"])
elif (check_user_input(movieID) == "int"):
singleMovie(moviesJSON, movieID, cat["category_name"])
else:
quit()
def movieWholeCat(moviesJSON, catName):
catFolder = catName.replace(" | ", " - ")
catFolder = catFolder.rstrip()
if(input("Do you want to generate .strm files for all movies in this
category? (y/N): ") == "y" or "Y"):
for movie in moviesJSON:
movieName = movie["name"]
movieName = movieName.replace("/", "")
#movieRelease = movie["releaseDate"]
print("Generating .strm files for: " + movieName)
id = str(movie["stream_id"])
ext = movie["container_extension"]
filename = movieName + ".strm"
path = os.path.join(os.path.dirname(os.path.abspath(*file*)),
"xtream2strm", DIR_MOVIES, catFolder)
createFile(buildMovieURL(id, ext), path, filename, movieName)
main()
else:
main()
def singleMovie(moviesJSON, streamID, catName):
catFolder = catName.replace(" | ", " - ")
catFolder = catFolder.rstrip()
if(input("Do you want to generate .strm files for this Movie?? (y/N): ")
== "y" or "Y"):
for movie in moviesJSON:
if(movie["stream_id"] == streamID):
movieName = movie["name"]
movieName = movieName.replace("/", "")
#movieRelease = movie["releaseDate"]
print("Generating .strm files for: " + movieName)
id = str(movie["stream_id"])
ext = movie["container_extension"]
filename = movieName + ".strm"
path = os.path.join(os.path.dirname(os.path.abspath(*file*)),
"xtream2strm", DIR_MOVIES, catFolder)
createFile(buildMovieURL(id, ext), path, filename, movieName)
main()
else:
main()
def menuSeriesCat():
seriesCatJSON = json.loads(getSeriesCat())
for scat in seriesCatJSON:
print("Category Name: " + scat["category_name"] + " Category ID: " +
scat["category_id"])
seriesCatID = input("Enter Category ID: ")
seriesJSON = json.loads(getSeries(seriesCatID))
for series in seriesJSON:
print("Serie: " + series["name"] + " ID: " + str(series["series_id"]))
seriesID = input("Enter Series ID or type All: ")
if (seriesID == "All"):
for scat in seriesCatJSON:
if(scat["category_id"] == seriesCatID):
menuWholeCat(seriesJSON, scat["category_name"])
elif (check_user_input(seriesID) == "int"):
for scat in seriesCatJSON:
if(scat["category_id"] == seriesCatID):
menuSeries(seriesID, scat["category_name"])
else:
quit()
def menuWholeCat(seriesJSON, catName):
catFolder = catName.replace(" | ", " - ")
catFolder = catFolder.rstrip()
if(input("Do you want to generate .strm files for all series in this
category? (y/N): ") == "y" or "Y"):
for series in seriesJSON:
seriesDetailJSON = json.loads(getSeasons(series["series_id"]))
seriesInfo = seriesDetailJSON["info"]
seriesName = seriesInfo["name"]
#seriesRelease = seriesInfo["releaseDate"]
seasons = seriesDetailJSON["episodes"]
print("Generating .strm files for: " + seriesName)
for season, season_episodes in seasons.items():
for episode in season_episodes:
id = episode["id"]
eNum = str(episode["episode_num"])
rawName = episode["title"]
ext = episode["container_extension"]
match = re.match(r"^(.
*?) - S\d{2}E\d{2} - ", rawName) if match: seriesName = match.group(1)
seriesFolder = seriesName seriesFolder = seriesFolder.replace("DE", "")
seriesFolder = seriesFolder.rstrip() seasonFolder = "Season " +
season.zfill(2) friendlyNameFind = re.findall(r"S\d{2}E\d{2} - (.*)",
rawName)
if(len(friendlyNameFind) == 0):
friendlyName = "Unbekannt"
else:
friendlyName = friendlyNameFind[0]
fileprefix = "S" + season.zfill(2) + "E" + eNum.zfill(2)
filename = fileprefix + " - " + friendlyName + ".strm"
path = os.path.join(os.path.dirname(os.path.abspath(*file*)),
"xtream2strm", DIR_SERIES, catFolder, seriesFolder, seasonFolder)
createFile(buildFileURL(id, ext), path, filename, fileprefix)
main()
else:
main()
def menuSeries(seriesID, catName):
if(not seriesID):
seriesID = input("Enter Series ID: ")
catFolder = catName.replace(" | ", " - ")
catFolder = catFolder.rstrip()
seriesDetailJSON = json.loads(getSeasons(seriesID))
seriesInfo = seriesDetailJSON["info"]
seriesName = seriesInfo["name"]
#seriesRelease = seriesInfo["releaseDate"]
print(seriesName)
menu = input("Generate .strm files? (y/N): ")
if(menu == "y" or menu == "Y"):
seasons = seriesDetailJSON["episodes"]
for season, season_episodes in seasons.items():
for episode in season_episodes:
id = episode["id"]
eNum = str(episode["episode_num"])
rawName = episode["title"]
ext = episode["container_extension"]
match = re.match(r"^(.*?) - S\d{2}E\d{2} - ", rawName)
if match:
seriesName = match.group(1)
seriesFolder = seriesName
seriesFolder = seriesFolder.replace("DE", "")
seriesFolder = seriesFolder.rstrip()
seasonFolder = "Season " + season.zfill(2)
friendlyNameFind = re.findall(r"S\d{2}E\d{2} - (.*)", rawName)
if(len(friendlyNameFind) == 0):
friendlyName = "Unbekannt"
else:
friendlyName = friendlyNameFind[0]
fileprefix = "S" + season.zfill(2) + "E" + eNum.zfill(2)
filename = fileprefix + " - " + friendlyName + ".strm"
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "xtream2strm", DIR_SERIES, catFolder, seriesFolder, seasonFolder)
createFile(buildFileURL(id, ext), path, filename, fileprefix)
main()
else:
main()
def createFile(content, path, filename, prefix):
if not os.path.exists(path):
os.makedirs(path)
try:
with open(path + "/" + filename, "w") as f:
f.write(content)
f.close
except OSError as e:
with open(path + "/" + prefix, "w") as f:
f.write(content)
f.close
def buildFileURL(streamID, extension):
return server + "/series/" + username +"/" + password + "/" + streamID +
"." + extension
def buildMovieURL(streamID, extension):
return server + "/movie/" + username +"/" + password + "/" + streamID +
"." + extension
def check_user_input(input):
try:
val = int(input)
return "int"
except ValueError:
return "string"
def getSeasons(seriesid):
counter = 0
try:
r = requests.get(get_series_info_URL_by_ID(seriesid), headers=headers,
timeout=10)
except:
while counter < 5:
time.sleep(1)
r = requests.get(get_series_info_URL_by_ID(seriesid), headers=headers,
timeout=10)
if r.status_code == 200:
break
counter += 1
return r.text
def getSeries(catid):
counter = 0
try:
r = requests.get(get_series_URL_by_category(catid), headers=headers,
timeout=10)
except:
while counter < 5:
time.sleep(1)
r = requests.get(get_series_URL_by_category(catid), headers=headers,
timeout=10)
if r.status_code == 200:
break
counter += 1
return r.text
def getSeriesCat():
r = requests.get(get_series_cat_URL(), headers=headers, timeout=10)
return r.text
def authenticate():
r = requests.get(get_authenticate_URL(), headers=headers, timeout=10)
return r.text
def getVodCat():
r = requests.get(get_vod_cat_URL(), headers=headers, timeout=10)
return r.text
def getVodStreams(catID):
counter = 0
try:
r = requests.get(get_vod_streams_URL_by_category(catID), headers=headers,
timeout=10)
except:
while counter < 5:
time.sleep(1)
r = requests.get(get_vod_streams_URL_by_category(catID), headers=headers,
timeout=10)
if r.status_code == 200:
break
counter += 1
return r.text
def get_authenticate_URL():
URL = server + '/player_api.php?username=' + username +'&password=' +
password
return URL
def get_live_categories_URL():
URL = '%s/player_api.php?username=%s&password=%s&action=%s' % (server,
username, password, 'get_live_categories')
return URL
def get_live_streams_URL():
URL = '%s/player_api.php?username=%s&password=%s&action=%s' % (server,
username, password, 'get_live_streams')
return URL
def get_live_streams_URL_by_category(category_id):
URL = '%s/player_api.php?username=%s&password=%s&action=%s&category_id=%s'
% (server, username, password, 'get_live_streams', category_id)
return URL
def get_vod_cat_URL():
URL = '%s/player_api.php?username=%s&password=%s&action=%s' % (server,
username, password, 'get_vod_categories')
return URL
def get_vod_streams_URL():
URL = '%s/player_api.php?username=%s&password=%s&action=%s' % (server,
username, password, 'get_vod_streams')
return URL
def get_vod_streams_URL_by_category(category_id):
URL = '%s/player_api.php?username=%s&password=%s&action=%s&category_id=%s'
% (server, username, password, 'get_vod_streams', category_id)
return URL
def get_series_cat_URL():
URL = '%s/player_api.php?username=%s&password=%s&action=%s' % (server,
username, password, 'get_series_categories')
return URL
def get_series_URL():
URL = '%s/player_api.php?username=%s&password=%s&action=%s' % (server,
username, password, 'get_series')
return URL
def get_series_URL_by_category(category_id):
URL = '%s/player_api.php?username=%s&password=%s&action=%s&category_id=%s'
% (server, username, password, 'get_series', category_id)
return URL
def get_series_info_URL_by_ID(series_id):
URL = '%s/player_api.php?username=%s&password=%s&action=%s&series_id=%s' %
(server, username, password, 'get_series_info', series_id)
return URL
def get_VOD_info_URL_by_ID(vod_id):
URL = '%s/player_api.php?username=%s&password=%s&action=%s&vod_id=%s' %
(server, username, password, 'get_vod_info', vod_id)
return URL
def get_live_epg_URL_by_stream(stream_id):
URL = '%s/player_api.php?username=%s&password=%s&action=%s&stream_id=%s' %
(server, username, password, 'get_short_epg', stream_id)
return URL
def get_live_epg_URL_by_stream_and_limit(stream_id, limit):
URL =
'%s/player_api.php?username=%s&password=%s&action=%s&stream_id=%s&limit=%s'
% (server, username, password, 'get_short_epg', stream_id, limit)
return URL
def get_all_live_epg_URL_by_stream(stream_id):
URL = '%s/player_api.php?username=%s&password=%s&action=%s&stream_id=%s' %
(server, username, password, 'get_simple_data_table', stream_id)
return URL
def get_all_epg_URL():
URL = '%s/xmltv.php?username=%s&password=%s' % (server, username, password)
return URL
if *name* == '*main*':
main()`
—
Reply to this email directly, view it on GitHub
<#124 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABBSH2FDGG2X4W5SZBN7M2T24JOXZAVCNFSM6AAAAAB4ATVMKSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDQNBVGM3TQNJVGQ>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Sounds good. If I can do anything let me know! |
Beta Was this translation helpful? Give feedback.
-
thanks again for the example, that helped a lot! I have a test version up in the to use it, update to the latest |
Beta Was this translation helpful? Give feedback.
-
I added some additional environment variables too:
the files will be stored in you can see this in the screenshot in my previous post too |
Beta Was this translation helpful? Give feedback.
-
Thx a lot! |
Beta Was this translation helpful? Give feedback.
-
Hey! It would be nice if we could choose multiple categories at the same time :) |
Beta Was this translation helpful? Give feedback.
-
On the first look it works pretty nice but i got some/a lot of Unknown/Unnamed Series. Maybe we could dive deeper into it in the next weeks. What im missing is the "caching" function. Is it possible to replaces the .strm with the original File once viewed/pulled? |
Beta Was this translation helpful? Give feedback.
-
Channels DVR also supports strm files. This would be a great feature. https://getchannels.com/docs/channels-dvr-server/how-to/stream-files/ |
Beta Was this translation helpful? Give feedback.
-
implemented in latest it's a couple step process so we don't import a million episodes, lol! 😂 For Xtream API playlist, enable "Series" import, and then the dropdown will populate with your providers series categories. Select the categories you'd like to sync, or select all: After this update, sync your playlist to populate the series. You will need to enable and process the series you want enabled to have them sync automatically going forward. This prevents importing every single series in the categories, which require individual calls your provider to fetch series details and episodes. You can also setup stream location file (.strm) generation in the Series edit screen: Once this is all setup, the sync will happen automatically going forward! |
Beta Was this translation helpful? Give feedback.
-
I've updated this a bit since it was a little buggy using the Playlist edit form. I've implemented a flow that is more inline with this thread, and how it works in the CLI. Going forward, to add series, use the "Add Series" button in the Series section to use the form wizard: And the Xtream import options are updated to remove Series and add Live, so you can import either Live or VOD, or none if you don't want any |
Beta Was this translation helpful? Give feedback.
-
An add all option has been added to allow importing all series in a category 🎉 USE WITH CAUTION: this will make a lot of requests to your provider in a fairly short amount of time (depending on the number of series in a category). You have been warned 😜 |
Beta Was this translation helpful? Give feedback.
-
Hello, |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello!
I want to make a Feature-Request for this nice Project.
Since a lot of Xtream Providers only allow 1-2 connections at once it would be nice to take care of them using M3U-Editor. I think about it in varius ways:
Beta Was this translation helpful? Give feedback.
All reactions