sfd
This commit is contained in:
parent
fb29c46f4f
commit
45e5a7aef4
4 changed files with 180 additions and 180 deletions
|
@ -1,58 +1,58 @@
|
||||||
from flask import Flask, request, render_template, send_from_directory
|
from flask import Flask, request, render_template, send_from_directory
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
M3U_URL = "https://iptv-org.github.io/iptv/index.m3u"
|
M3U_URL = "https://iptv-org.github.io/iptv/index.m3u"
|
||||||
PLAYLIST_DIR = "playlists"
|
PLAYLIST_DIR = "playlists"
|
||||||
|
|
||||||
if not os.path.exists(PLAYLIST_DIR):
|
if not os.path.exists(PLAYLIST_DIR):
|
||||||
os.makedirs(PLAYLIST_DIR)
|
os.makedirs(PLAYLIST_DIR)
|
||||||
|
|
||||||
def fetch_m3u():
|
def fetch_m3u():
|
||||||
response = requests.get(M3U_URL)
|
response = requests.get(M3U_URL)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.text
|
return response.text
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def parse_m3u(m3u_content):
|
def parse_m3u(m3u_content):
|
||||||
lines = m3u_content.split("\n")
|
lines = m3u_content.split("\n")
|
||||||
channels = []
|
channels = []
|
||||||
name, url = None, None
|
name, url = None, None
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if line.startswith("#EXTINF"):
|
if line.startswith("#EXTINF"):
|
||||||
parts = line.split(",")
|
parts = line.split(",")
|
||||||
name = parts[-1].strip() if len(parts) > 1 else "Unknown Channel"
|
name = parts[-1].strip() if len(parts) > 1 else "Unknown Channel"
|
||||||
elif line.startswith("http"):
|
elif line.startswith("http"):
|
||||||
url = line.strip()
|
url = line.strip()
|
||||||
if name and url:
|
if name and url:
|
||||||
channels.append((name, name, url))
|
channels.append((name, name, url))
|
||||||
name, url = None, None
|
name, url = None, None
|
||||||
return channels
|
return channels
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
m3u_content = fetch_m3u()
|
m3u_content = fetch_m3u()
|
||||||
channels = parse_m3u(m3u_content)
|
channels = parse_m3u(m3u_content)
|
||||||
return render_template("index.html", channels=channels)
|
return render_template("index.html", channels=channels)
|
||||||
|
|
||||||
@app.route("/generate", methods=["POST"])
|
@app.route("/generate", methods=["POST"])
|
||||||
def generate_playlist():
|
def generate_playlist():
|
||||||
filename = request.form.get("filename", "custom_playlist").strip()
|
filename = request.form.get("filename", "custom_playlist").strip()
|
||||||
selected_channels = request.form.getlist("channels")
|
selected_channels = request.form.getlist("channels")
|
||||||
if not filename.endswith(".m3u"):
|
if not filename.endswith(".m3u"):
|
||||||
filename += ".m3u"
|
filename += ".m3u"
|
||||||
filepath = os.path.join(PLAYLIST_DIR, filename)
|
filepath = os.path.join(PLAYLIST_DIR, filename)
|
||||||
with open(filepath, "w") as f:
|
with open(filepath, "w") as f:
|
||||||
f.write("#EXTM3U\n")
|
f.write("#EXTM3U\n")
|
||||||
for channel in selected_channels:
|
for channel in selected_channels:
|
||||||
info, url = channel.split("|", 1)
|
info, url = channel.split("|", 1)
|
||||||
f.write(f"#EXTINF:-1,{info}\n{url}\n")
|
f.write(f"#EXTINF:-1,{info}\n{url}\n")
|
||||||
return f"Playlist created: <a href='/playlists/{filename}'>{filename}</a>"
|
return f"Playlist created: <a href='/playlists/{filename}'>{filename}</a>"
|
||||||
|
|
||||||
@app.route("/playlists/<filename>")
|
@app.route("/playlists/<filename>")
|
||||||
def serve_playlist(filename):
|
def serve_playlist(filename):
|
||||||
return send_from_directory(PLAYLIST_DIR, filename)
|
return send_from_directory(PLAYLIST_DIR, filename)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
|
@ -1 +1 @@
|
||||||
placeholder
|
placeholder
|
|
@ -1,122 +1,122 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Create Playlist</title>
|
<title>Create Playlist</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: #121212;
|
background-color: #121212;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background-color: #1e1e1e;
|
background-color: #1e1e1e;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
|
box-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
input, button {
|
input, button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
border: 1px solid #555;
|
border: 1px solid #555;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: #0056b3;
|
background-color: #0056b3;
|
||||||
}
|
}
|
||||||
.channel-list {
|
.channel-list {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border: 1px solid #555;
|
border: 1px solid #555;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
.channel-item {
|
.channel-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-bottom: 1px solid #333;
|
border-bottom: 1px solid #333;
|
||||||
}
|
}
|
||||||
.channel-item:hover {
|
.channel-item:hover {
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
}
|
}
|
||||||
.channel-content {
|
.channel-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.channel-text {
|
.channel-text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
.channel-checkbox {
|
.channel-checkbox {
|
||||||
flex-basis: 50px;
|
flex-basis: 50px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
.channel-checkbox input {
|
.channel-checkbox input {
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
function filterChannels() {
|
function filterChannels() {
|
||||||
let input = document.getElementById("search").value.toLowerCase();
|
let input = document.getElementById("search").value.toLowerCase();
|
||||||
let items = document.querySelectorAll(".channel-item");
|
let items = document.querySelectorAll(".channel-item");
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
let text = item.querySelector(".channel-text").textContent.toLowerCase();
|
let text = item.querySelector(".channel-text").textContent.toLowerCase();
|
||||||
item.style.display = text.includes(input) ? "flex" : "none";
|
item.style.display = text.includes(input) ? "flex" : "none";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
document.querySelectorAll(".channel-item").forEach(item => {
|
document.querySelectorAll(".channel-item").forEach(item => {
|
||||||
item.addEventListener("click", (event) => {
|
item.addEventListener("click", (event) => {
|
||||||
if (event.target.tagName !== "INPUT") {
|
if (event.target.tagName !== "INPUT") {
|
||||||
let checkbox = item.querySelector("input");
|
let checkbox = item.querySelector("input");
|
||||||
checkbox.checked = !checkbox.checked;
|
checkbox.checked = !checkbox.checked;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Select Channels to Create a New Playlist</h1>
|
<h1>Select Channels to Create a New Playlist</h1>
|
||||||
<form action="/generate" method="post">
|
<form action="/generate" method="post">
|
||||||
<label for="filename">Playlist Name:</label>
|
<label for="filename">Playlist Name:</label>
|
||||||
<input type="text" name="filename" required>
|
<input type="text" name="filename" required>
|
||||||
<input type="text" id="search" onkeyup="filterChannels()" placeholder="Search channels...">
|
<input type="text" id="search" onkeyup="filterChannels()" placeholder="Search channels...">
|
||||||
<div class="channel-list">
|
<div class="channel-list">
|
||||||
{% for name, info, url in channels %}
|
{% for name, info, url in channels %}
|
||||||
<div class="channel-item">
|
<div class="channel-item">
|
||||||
<div class="channel-content">
|
<div class="channel-content">
|
||||||
<span class="channel-text">{{ name }}</span>
|
<span class="channel-text">{{ name }}</span>
|
||||||
<div class="channel-checkbox">
|
<div class="channel-checkbox">
|
||||||
<input type="checkbox" name="channels" value="{{ info }}|{{ url }}">
|
<input type="checkbox" name="channels" value="{{ info }}|{{ url }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<button type="submit">Generate Playlist</button>
|
<button type="submit">Generate Playlist</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Add table
Reference in a new issue