AI 生成: 一个生成 M3U 播放列表文件的项目,包含多个可订阅的 M3U 文件和一个简单的 Flask Web 界面用于预览和下载。
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# M3U Playlist Generator
|
||||
|
||||
一个简单实用的 M3U 播放列表生成器,提供多个可订阅的 M3U 文件,并附带一个基于 Flask 的 Web 界面用于浏览和下载。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 提供多个分类的 M3U 播放列表文件(新闻、音乐、电影、体育等)
|
||||
- 支持通过 HTTP 订阅播放列表
|
||||
- 提供 Flask Web 界面,方便浏览和下载
|
||||
- 使用相对路径,便于部署
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
m3u-playlist-generator/
|
||||
├── README.md
|
||||
├── app.py # Flask 应用入口
|
||||
├── requirements.txt # Python 依赖
|
||||
├── playlists/ # M3U 播放列表文件目录
|
||||
│ ├── news.m3u
|
||||
│ ├── music.m3u
|
||||
│ ├── movies.m3u
|
||||
│ └── sports.m3u
|
||||
├── templates/ # HTML 模板
|
||||
│ └── index.html
|
||||
└── static/
|
||||
└── style.css
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. 启动服务
|
||||
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
### 3. 访问
|
||||
|
||||
打开浏览器访问 http://127.0.0.1:5000
|
||||
|
||||
### 4. 订阅播放列表
|
||||
|
||||
在播放器(如 VLC、PotPlayer、Kodi 等)中添加以下订阅地址:
|
||||
|
||||
- http://127.0.0.1:5000/playlists/news.m3u
|
||||
- http://127.0.0.1:5000/playlists/music.m3u
|
||||
- http://127.0.0.1:5000/playlists/movies.m3u
|
||||
- http://127.0.0.1:5000/playlists/sports.m3u
|
||||
|
||||
## 自定义
|
||||
|
||||
直接编辑 `playlists/` 目录下的 `.m3u` 文件即可添加或修改频道。
|
||||
|
||||
M3U 文件格式示例:
|
||||
|
||||
```
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="channel1" tvg-logo="https://example.com/logo.png" group-title="News",Channel 1
|
||||
https://example.com/stream1.m3u8
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
M3U Playlist Generator - Flask 应用入口
|
||||
提供 M3U 播放列表的浏览、下载和订阅服务。
|
||||
"""
|
||||
|
||||
import os
|
||||
from flask import Flask, render_template, send_from_directory, abort
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# 播放列表目录(相对路径)
|
||||
PLAYLIST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'playlists')
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""首页:列出所有可用的播放列表。"""
|
||||
playlists = []
|
||||
if os.path.isdir(PLAYLIST_DIR):
|
||||
for filename in sorted(os.listdir(PLAYLIST_DIR)):
|
||||
if filename.endswith('.m3u'):
|
||||
filepath = os.path.join(PLAYLIST_DIR, filename)
|
||||
size = os.path.getsize(filepath)
|
||||
# 统计频道数量(以 #EXTINF 开头的行)
|
||||
channel_count = 0
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if line.strip().startswith('#EXTINF'):
|
||||
channel_count += 1
|
||||
except Exception:
|
||||
pass
|
||||
playlists.append({
|
||||
'name': filename,
|
||||
'size': size,
|
||||
'channels': channel_count,
|
||||
'url': f'playlists/{filename}'
|
||||
})
|
||||
return render_template('index.html', playlists=playlists)
|
||||
|
||||
|
||||
@app.route('/playlists/<path:filename>')
|
||||
def serve_playlist(filename):
|
||||
"""提供播放列表文件下载/订阅。"""
|
||||
# 安全检查:防止路径穿越
|
||||
if '..' in filename or filename.startswith('/'):
|
||||
abort(400)
|
||||
if not filename.endswith('.m3u'):
|
||||
abort(404)
|
||||
filepath = os.path.join(PLAYLIST_DIR, filename)
|
||||
if not os.path.isfile(filepath):
|
||||
abort(404)
|
||||
return send_from_directory(
|
||||
PLAYLIST_DIR,
|
||||
filename,
|
||||
mimetype='audio/x-mpegurl',
|
||||
as_attachment=False
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 监听所有网卡,方便局域网内其他设备订阅
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
@@ -0,0 +1,11 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="hbo" tvg-logo="https://example.com/hbo.png" group-title="电影",HBO
|
||||
https://example.com/streams/hbo.m3u8
|
||||
#EXTINF:-1 tvg-id="cinemax" tvg-logo="https://example.com/cinemax.png" group-title="电影",Cinemax
|
||||
https://example.com/streams/cinemax.m3u8
|
||||
#EXTINF:-1 tvg-id="starz" tvg-logo="https://example.com/starz.png" group-title="电影",Starz
|
||||
https://example.com/streams/starz.m3u8
|
||||
#EXTINF:-1 tvg-id="showtime" tvg-logo="https://example.com/showtime.png" group-title="电影",Showtime
|
||||
https://example.com/streams/showtime.m3u8
|
||||
#EXTINF:-1 tvg-id="tcm" tvg-logo="https://example.com/tcm.png" group-title="电影",Turner Classic Movies
|
||||
https://example.com/streams/tcm.m3u8
|
||||
@@ -0,0 +1,11 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="mtv" tvg-logo="https://example.com/mtv.png" group-title="音乐",MTV Live
|
||||
https://example.com/streams/mtv.m3u8
|
||||
#EXTINF:-1 tvg-id="vh1" tvg-logo="https://example.com/vh1.png" group-title="音乐",VH1
|
||||
https://example.com/streams/vh1.m3u8
|
||||
#EXTINF:-1 tvg-id="billboard" tvg-logo="https://example.com/billboard.png" group-title="音乐",Billboard Music
|
||||
https://example.com/streams/billboard.m3u8
|
||||
#EXTINF:-1 tvg-id="kiss-fm" tvg-logo="https://example.com/kiss.png" group-title="音乐",Kiss FM
|
||||
https://example.com/streams/kiss-fm.m3u8
|
||||
#EXTINF:-1 tvg-id="classic-fm" tvg-logo="https://example.com/classic.png" group-title="音乐",Classic FM
|
||||
https://example.com/streams/classic-fm.m3u8
|
||||
@@ -0,0 +1,11 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="cctv1" tvg-logo="https://example.com/cctv1.png" group-title="新闻",CCTV-1 综合
|
||||
https://example.com/streams/cctv1.m3u8
|
||||
#EXTINF:-1 tvg-id="cctv13" tvg-logo="https://example.com/cctv13.png" group-title="新闻",CCTV-13 新闻
|
||||
https://example.com/streams/cctv13.m3u8
|
||||
#EXTINF:-1 tvg-id="bbc-world" tvg-logo="https://example.com/bbc.png" group-title="新闻",BBC World News
|
||||
https://example.com/streams/bbc-world.m3u8
|
||||
#EXTINF:-1 tvg-id="cnn" tvg-logo="https://example.com/cnn.png" group-title="新闻",CNN International
|
||||
https://example.com/streams/cnn.m3u8
|
||||
#EXTINF:-1 tvg-id="aljazeera" tvg-logo="https://example.com/aljazeera.png" group-title="新闻",Al Jazeera English
|
||||
https://example.com/streams/aljazeera.m3u8
|
||||
@@ -0,0 +1,11 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="espn" tvg-logo="https://example.com/espn.png" group-title="体育",ESPN
|
||||
https://example.com/streams/espn.m3u8
|
||||
#EXTINF:-1 tvg-id="fox-sports" tvg-logo="https://example.com/foxsports.png" group-title="体育",Fox Sports
|
||||
https://example.com/streams/fox-sports.m3u8
|
||||
#EXTINF:-1 tvg-id="sky-sports" tvg-logo="https://example.com/skysports.png" group-title="体育",Sky Sports
|
||||
https://example.com/streams/sky-sports.m3u8
|
||||
#EXTINF:-1 tvg-id="nba-tv" tvg-logo="https://example.com/nba.png" group-title="体育",NBA TV
|
||||
https://example.com/streams/nba-tv.m3u8
|
||||
#EXTINF:-1 tvg-id="eurosport" tvg-logo="https://example.com/eurosport.png" group-title="体育",Eurosport
|
||||
https://example.com/streams/eurosport.m3u8
|
||||
@@ -0,0 +1 @@
|
||||
Flask==3.0.0
|
||||
@@ -0,0 +1,139 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background: #f5f7fa;
|
||||
color: #2c3e50;
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.2rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
header p {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
max-width: 1100px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.playlist-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.playlist-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.playlist-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.playlist-card h2 {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 12px;
|
||||
color: #667eea;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.85rem;
|
||||
color: #7f8c8d;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #667eea;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #5568d3;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #eef0f5;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #dfe3ec;
|
||||
}
|
||||
|
||||
.subscribe-hint {
|
||||
font-size: 0.75rem;
|
||||
color: #95a5a6;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.subscribe-hint code {
|
||||
background: #f0f2f7;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: #95a5a6;
|
||||
padding: 60px 20px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #95a5a6;
|
||||
font-size: 0.85rem;
|
||||
border-top: 1px solid #e8ecf1;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>M3U 播放列表</title>
|
||||
<link rel="stylesheet" href="static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>🎵 M3U 播放列表</h1>
|
||||
<p>选择下方任意播放列表进行订阅或下载</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{% if playlists %}
|
||||
<div class="playlist-grid">
|
||||
{% for pl in playlists %}
|
||||
<div class="playlist-card">
|
||||
<h2>{{ pl.name }}</h2>
|
||||
<p class="meta">
|
||||
<span>频道数:{{ pl.channels }}</span>
|
||||
<span>大小:{{ pl.size }} 字节</span>
|
||||
</p>
|
||||
<div class="actions">
|
||||
<a class="btn btn-primary" href="{{ pl.url }}" target="_blank">查看 / 订阅</a>
|
||||
<a class="btn btn-secondary" href="{{ pl.url }}" download>下载</a>
|
||||
</div>
|
||||
<p class="subscribe-hint">
|
||||
订阅地址:<code>{{ request.host_url }}{{ pl.url }}</code>
|
||||
</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="empty">暂无播放列表,请在 playlists/ 目录下添加 .m3u 文件。</p>
|
||||
{% endif %}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>M3U Playlist Generator © 2024</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user