wpms commited on
Commit
5f88e7f
·
verified ·
1 Parent(s): 26afe26

Rename index.js to app.py

Browse files
Files changed (2) hide show
  1. app.py +198 -0
  2. index.js +0 -46
app.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uuid
3
+ import asyncio
4
+ import subprocess
5
+ import shutil
6
+ from fastapi import FastAPI, Query, HTTPException
7
+ from fastapi.responses import FileResponse
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ import logging
10
+
11
+ logging.basicConfig(level=logging.INFO)
12
+ logger = logging.getLogger(__name__)
13
+
14
+ app = FastAPI(title="M3U8 to MP4 Converter", version="1.0.0")
15
+
16
+ app.add_middleware(
17
+ CORSMiddleware,
18
+ allow_origins=["*"],
19
+ allow_credentials=True,
20
+ allow_methods=["*"],
21
+ allow_headers=["*"],
22
+ )
23
+
24
+ TEMP_DIR = "/tmp/m3u8_conversions"
25
+ os.makedirs(TEMP_DIR, exist_ok=True)
26
+
27
+ active_conversions = {}
28
+
29
+
30
+ def cleanup_files(file_paths):
31
+ for path in file_paths:
32
+ try:
33
+ if os.path.exists(path):
34
+ if os.path.isdir(path):
35
+ shutil.rmtree(path)
36
+ else:
37
+ os.remove(path)
38
+ logger.info(f"Cleaned up: {path}")
39
+ except Exception as e:
40
+ logger.error(f"Cleanup error for {path}: {e}")
41
+
42
+
43
+ def get_ffmpeg_path():
44
+ ffmpeg_path = shutil.which("ffmpeg")
45
+ if ffmpeg_path:
46
+ return ffmpeg_path
47
+ for path in ["/usr/bin/ffmpeg", "/usr/local/bin/ffmpeg", "/app/ffmpeg", "./ffmpeg"]:
48
+ if os.path.exists(path):
49
+ return path
50
+ return "ffmpeg"
51
+
52
+
53
+ async def convert_m3u8_to_mp4(m3u8_url: str, output_path: str, task_id: str):
54
+ ffmpeg = get_ffmpeg_path()
55
+
56
+ cmd = [
57
+ ffmpeg,
58
+ "-i", m3u8_url,
59
+ "-c", "copy",
60
+ "-bsf:a", "aac_adtstoasc",
61
+ "-movflags", "+faststart",
62
+ "-y",
63
+ output_path
64
+ ]
65
+
66
+ cmd_fallback = [
67
+ ffmpeg,
68
+ "-i", m3u8_url,
69
+ "-c:v", "libx264",
70
+ "-preset", "fast",
71
+ "-crf", "23",
72
+ "-c:a", "aac",
73
+ "-b:a", "128k",
74
+ "-movflags", "+faststart",
75
+ "-y",
76
+ output_path
77
+ ]
78
+
79
+ logger.info(f"Starting conversion: {task_id}")
80
+ logger.info(f"Command: {' '.join(cmd)}")
81
+
82
+ active_conversions[task_id] = {"status": "converting", "progress": 0}
83
+
84
+ try:
85
+ process = await asyncio.create_subprocess_exec(
86
+ *cmd,
87
+ stdout=asyncio.subprocess.PIPE,
88
+ stderr=asyncio.subprocess.PIPE
89
+ )
90
+
91
+ stdout, stderr = await process.communicate()
92
+
93
+ if process.returncode != 0:
94
+ logger.warning(f"Copy mode failed, trying re-encode mode: {stderr.decode()[:500]}")
95
+ active_conversions[task_id]["status"] = "re-encoding"
96
+
97
+ process = await asyncio.create_subprocess_exec(
98
+ *cmd_fallback,
99
+ stdout=asyncio.subprocess.PIPE,
100
+ stderr=asyncio.subprocess.PIPE
101
+ )
102
+ stdout, stderr = await process.communicate()
103
+
104
+ if process.returncode != 0:
105
+ raise Exception(f"Conversion failed: {stderr.decode()[:1000]}")
106
+
107
+ active_conversions[task_id]["status"] = "completed"
108
+ logger.info(f"Conversion completed: {task_id}")
109
+
110
+ except Exception as e:
111
+ active_conversions[task_id]["status"] = "failed"
112
+ active_conversions[task_id]["error"] = str(e)
113
+ logger.error(f"Conversion error: {e}")
114
+ raise
115
+
116
+
117
+ @app.get("/")
118
+ def root():
119
+ return {
120
+ "message": "M3U8 to MP4 Converter API",
121
+ "usage": "/video?url=<m3u8-url>&format=mp4",
122
+ "endpoints": {
123
+ "convert": "/video?url=<m3u8-url>&format=mp4",
124
+ "status": "/status/<task_id>"
125
+ }
126
+ }
127
+
128
+
129
+ @app.get("/video")
130
+ async def convert_video(
131
+ url: str = Query(..., description="M3U8 URL to convert"),
132
+ format: str = Query("mp4", description="Output format (mp4 only)")
133
+ ):
134
+ if not url:
135
+ raise HTTPException(status_code=400, detail="URL parameter is required")
136
+
137
+ if not url.startswith(("http://", "https://")):
138
+ raise HTTPException(status_code=400, detail="Invalid URL format")
139
+
140
+ task_id = str(uuid.uuid4())
141
+ output_filename = f"video_{task_id}.mp4"
142
+ output_path = os.path.join(TEMP_DIR, output_filename)
143
+
144
+ try:
145
+ await convert_m3u8_to_mp4(url, output_path, task_id)
146
+
147
+ if not os.path.exists(output_path):
148
+ raise HTTPException(status_code=500, detail="Conversion failed - output file not created")
149
+
150
+ file_size = os.path.getsize(output_path)
151
+ logger.info(f"File size: {file_size} bytes")
152
+
153
+ response = FileResponse(
154
+ path=output_path,
155
+ media_type="video/mp4",
156
+ filename=f"converted_video_{task_id}.mp4",
157
+ headers={
158
+ "Content-Disposition": f'attachment; filename="converted_video_{task_id}.mp4"'
159
+ }
160
+ )
161
+
162
+ @response.background
163
+ def cleanup():
164
+ cleanup_files([output_path])
165
+
166
+ return response
167
+
168
+ except HTTPException:
169
+ cleanup_files([output_path])
170
+ raise
171
+ except Exception as e:
172
+ cleanup_files([output_path])
173
+ logger.error(f"Error: {e}")
174
+ raise HTTPException(status_code=500, detail=f"Conversion error: {str(e)}")
175
+
176
+
177
+ @app.get("/status/{task_id}")
178
+ def get_status(task_id: str):
179
+ if task_id not in active_conversions:
180
+ return {"status": "not_found"}
181
+ return active_conversions[task_id]
182
+
183
+
184
+ @app.get("/health")
185
+ def health_check():
186
+ ffmpeg = get_ffmpeg_path()
187
+ ffmpeg_available = os.path.exists(ffmpeg) or shutil.which("ffmpeg") is not None
188
+ return {
189
+ "status": "healthy",
190
+ "ffmpeg_available": ffmpeg_available,
191
+ "ffmpeg_path": ffmpeg
192
+ }
193
+
194
+
195
+ if __name__ == "__main__":
196
+ import uvicorn
197
+ port = int(os.environ.get("PORT", 7860))
198
+ uvicorn.run(app, host="0.0.0.0", port=port)
index.js DELETED
@@ -1,46 +0,0 @@
1
- const express = require("express");
2
- const { Client, GatewayIntentBits } = require("discord.js");
3
-
4
- const app = express();
5
- const PORT = process.env.PORT || 7860;
6
-
7
- app.get("/", (req, res) => {
8
- res.send("Bot is alive");
9
- });
10
-
11
- app.listen(PORT, () => {
12
- console.log("Web server running on port", PORT);
13
- });
14
-
15
- const client = new Client({
16
- intents: [
17
- GatewayIntentBits.Guilds,
18
- GatewayIntentBits.GuildMessages,
19
- GatewayIntentBits.MessageContent
20
- ]
21
- });
22
-
23
- // ✅ prevent crash
24
- process.on("uncaughtException", console.error);
25
- process.on("unhandledRejection", console.error);
26
-
27
- client.once("ready", () => {
28
- console.log(`Logged in as ${client.user.tag}`);
29
- });
30
-
31
- // safer message handler
32
- client.on("messageCreate", async (message) => {
33
- try {
34
- if (message.author.bot) return;
35
-
36
- if (message.content === "!ping") {
37
- await message.reply("Pong!");
38
- }
39
- } catch (err) {
40
- console.error("Message error:", err);
41
- }
42
- });
43
-
44
- client.login(process.env.DISCORD_TOKEN).catch((err) => {
45
- console.error("Login failed:", err);
46
- });