app.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import os
  2. import sys
  3. sys.path.append(os.path.dirname(sys.path[0]))
  4. from flask import Flask, send_from_directory, make_response, jsonify, redirect
  5. from utils.tools import get_result_file_content, get_ip_address, resource_path, join_url, add_port_to_url, \
  6. get_url_without_scheme
  7. from utils.config import config
  8. import utils.constants as constants
  9. from utils.db import get_db_connection, return_db_connection
  10. import subprocess
  11. import atexit
  12. from collections import OrderedDict
  13. import threading
  14. import json
  15. app = Flask(__name__)
  16. nginx_dir = resource_path(os.path.join('utils', 'nginx-rtmp-win32'))
  17. nginx_path = resource_path(os.path.join(nginx_dir, 'nginx.exe'))
  18. stop_path = resource_path(os.path.join(nginx_dir, 'stop.bat'))
  19. hls_temp_path = resource_path(os.path.join(nginx_dir, 'temp/hls')) if sys.platform == "win32" else '/tmp/hls'
  20. live_running_streams = OrderedDict()
  21. hls_running_streams = OrderedDict()
  22. MAX_STREAMS = 10
  23. rtmp_hls_file_url = join_url(add_port_to_url(config.app_host, 8080), 'hls/')
  24. app_rtmp_url = get_url_without_scheme(add_port_to_url(config.app_host, 1935))
  25. rtmp_hls_id_url = f"rtmp://{join_url(app_rtmp_url, 'hls/')}"
  26. rtmp_live_id_url = f"rtmp://{join_url(app_rtmp_url, 'live/')}"
  27. @app.route("/")
  28. def show_index():
  29. return get_result_file_content(
  30. path=constants.live_result_path if config.open_rtmp else config.final_file,
  31. file_type="m3u" if config.open_m3u_result else "txt"
  32. )
  33. @app.route("/favicon.ico")
  34. def favicon():
  35. return send_from_directory(resource_path('static/images'), 'favicon.ico',
  36. mimetype='image/vnd.microsoft.icon')
  37. @app.route("/txt")
  38. def show_txt():
  39. return get_result_file_content(path=config.final_file, file_type="txt")
  40. @app.route("/ipv4/txt")
  41. def show_ipv4_txt():
  42. return get_result_file_content(path=constants.ipv4_result_path, file_type="txt")
  43. @app.route("/ipv6/txt")
  44. def show_ipv6_txt():
  45. return get_result_file_content(path=constants.ipv6_result_path, file_type="txt")
  46. @app.route("/live")
  47. def show_live():
  48. return get_result_file_content(path=constants.live_result_path,
  49. file_type="m3u" if config.open_m3u_result else "txt")
  50. @app.route("/live/txt")
  51. def show_live_txt():
  52. return get_result_file_content(path=constants.live_result_path, file_type="txt")
  53. @app.route("/live/ipv4/txt")
  54. def show_live_ipv4_txt():
  55. return get_result_file_content(path=constants.live_ipv4_result_path, file_type="txt")
  56. @app.route("/live/ipv6/txt")
  57. def show_live_ipv6_txt():
  58. return get_result_file_content(path=constants.live_ipv6_result_path, file_type="txt")
  59. @app.route("/hls")
  60. def show_hls():
  61. return get_result_file_content(path=constants.hls_result_path,
  62. file_type="m3u" if config.open_m3u_result else "txt")
  63. @app.route("/hls/txt")
  64. def show_hls_txt():
  65. return get_result_file_content(path=constants.hls_result_path, file_type="txt")
  66. @app.route("/hls/ipv4/txt")
  67. def show_hls_ipv4_txt():
  68. return get_result_file_content(path=constants.hls_ipv4_result_path, file_type="txt")
  69. @app.route("/hls/ipv6/txt")
  70. def show_hls_ipv6_txt():
  71. return get_result_file_content(path=constants.hls_ipv6_result_path, file_type="txt")
  72. @app.route("/m3u")
  73. def show_m3u():
  74. return get_result_file_content(path=config.final_file, file_type="m3u")
  75. @app.route("/live/m3u")
  76. def show_live_m3u():
  77. return get_result_file_content(path=constants.live_result_path, file_type="m3u")
  78. @app.route("/hls/m3u")
  79. def show_hls_m3u():
  80. return get_result_file_content(path=constants.hls_result_path, file_type="m3u")
  81. @app.route("/ipv4/m3u")
  82. def show_ipv4_m3u():
  83. return get_result_file_content(path=constants.ipv4_result_path, file_type="m3u")
  84. @app.route("/ipv4")
  85. def show_ipv4_result():
  86. return get_result_file_content(
  87. path=constants.live_ipv4_result_path if config.open_rtmp else constants.ipv4_result_path,
  88. file_type="m3u" if config.open_m3u_result else "txt"
  89. )
  90. @app.route("/ipv6/m3u")
  91. def show_ipv6_m3u():
  92. return get_result_file_content(path=constants.ipv6_result_path, file_type="m3u")
  93. @app.route("/ipv6")
  94. def show_ipv6_result():
  95. return get_result_file_content(
  96. path=constants.live_ipv6_result_path if config.open_rtmp else constants.ipv6_result_path,
  97. file_type="m3u" if config.open_m3u_result else "txt"
  98. )
  99. @app.route("/live/ipv4/m3u")
  100. def show_live_ipv4_m3u():
  101. return get_result_file_content(path=constants.live_ipv4_result_path, file_type="m3u")
  102. @app.route("/live/ipv6/m3u")
  103. def show_live_ipv6_m3u():
  104. return get_result_file_content(path=constants.live_ipv6_result_path, file_type="m3u")
  105. @app.route("/hls/ipv4/m3u")
  106. def show_hls_ipv4_m3u():
  107. return get_result_file_content(path=constants.hls_ipv4_result_path, file_type="m3u")
  108. @app.route("/hls/ipv6/m3u")
  109. def show_hls_ipv6_m3u():
  110. return get_result_file_content(path=constants.hls_ipv6_result_path, file_type="m3u")
  111. @app.route("/content")
  112. def show_content():
  113. return get_result_file_content(
  114. path=constants.live_result_path if config.open_rtmp else config.final_file,
  115. file_type="m3u" if config.open_m3u_result else "txt",
  116. show_content=True
  117. )
  118. @app.route("/epg/epg.xml")
  119. def show_epg():
  120. return get_result_file_content(path=constants.epg_result_path, file_type="xml", show_content=False)
  121. @app.route("/epg/epg.gz")
  122. def show_epg_gz():
  123. return get_result_file_content(path=constants.epg_gz_result_path, file_type="gz", show_content=False)
  124. @app.route("/log")
  125. def show_log():
  126. if os.path.exists(constants.result_log_path):
  127. with open(constants.result_log_path, "r", encoding="utf-8") as file:
  128. content = file.read()
  129. else:
  130. content = constants.waiting_tip
  131. response = make_response(content)
  132. response.mimetype = "text/plain"
  133. return response
  134. def get_channel_data(channel_id):
  135. conn = get_db_connection(constants.rtmp_data_path)
  136. channel_data = {}
  137. try:
  138. cursor = conn.cursor()
  139. cursor.execute("SELECT url, headers FROM result_data WHERE id=?", (channel_id,))
  140. data = cursor.fetchone()
  141. if data:
  142. channel_data = {
  143. 'url': data[0],
  144. 'headers': json.loads(data[1]) if data[1] else None
  145. }
  146. except Exception as e:
  147. print(f"❌ Error retrieving channel data: {e}")
  148. finally:
  149. return_db_connection(constants.rtmp_data_path, conn)
  150. return channel_data
  151. def monitor_stream_process(streams, process, channel_id):
  152. process.wait()
  153. if channel_id in streams:
  154. del streams[channel_id]
  155. def cleanup_streams(streams):
  156. to_delete = []
  157. for channel_id, process in streams.items():
  158. if process.poll() is not None:
  159. to_delete.append(channel_id)
  160. for channel_id in to_delete:
  161. del streams[channel_id]
  162. while len(streams) > MAX_STREAMS:
  163. streams.popitem(last=False)
  164. @app.route('/live/<channel_id>', methods=['GET'])
  165. def run_live(channel_id):
  166. if not channel_id:
  167. return jsonify({'Error': 'Channel ID is required'}), 400
  168. data = get_channel_data(channel_id)
  169. url = data.get("url", "")
  170. if not url:
  171. return jsonify({'Error': 'Url not found'}), 400
  172. headers = data.get("headers", None)
  173. channel_rtmp_url = join_url(rtmp_live_id_url, channel_id)
  174. if channel_id in live_running_streams:
  175. process = live_running_streams[channel_id]
  176. if process.poll() is None:
  177. return redirect(channel_rtmp_url)
  178. else:
  179. del live_running_streams[channel_id]
  180. cleanup_streams(live_running_streams)
  181. cmd = [
  182. 'ffmpeg',
  183. '-loglevel', 'error',
  184. '-re',
  185. '-headers', ''.join(f'{k}: {v}\r\n' for k, v in headers.items()) if headers else '',
  186. '-i', url.partition('$')[0],
  187. '-c:v', 'copy',
  188. '-c:a', 'copy',
  189. '-f', 'flv',
  190. '-flvflags', 'no_duration_filesize',
  191. channel_rtmp_url
  192. ]
  193. try:
  194. process = subprocess.Popen(
  195. cmd,
  196. stdout=subprocess.PIPE,
  197. stderr=subprocess.PIPE,
  198. stdin=subprocess.PIPE
  199. )
  200. threading.Thread(
  201. target=monitor_stream_process,
  202. args=(live_running_streams, process, channel_id),
  203. daemon=True
  204. ).start()
  205. live_running_streams[channel_id] = process
  206. return redirect(channel_rtmp_url)
  207. except Exception as e:
  208. return jsonify({'Error': str(e)}), 500
  209. @app.route('/hls/<channel_id>', methods=['GET'])
  210. def run_hls(channel_id):
  211. if not channel_id:
  212. return jsonify({'Error': 'Channel ID is required'}), 400
  213. data = get_channel_data(channel_id)
  214. url = data.get("url", "")
  215. if not url:
  216. return jsonify({'Error': 'Url not found'}), 400
  217. headers = data.get("headers", None)
  218. channel_file = f'{channel_id}.m3u8'
  219. m3u8_path = os.path.join(hls_temp_path, channel_file)
  220. if channel_id in hls_running_streams:
  221. process = hls_running_streams[channel_id]
  222. if process.poll() is None:
  223. if os.path.exists(m3u8_path):
  224. return redirect(f'{join_url(rtmp_hls_file_url, channel_file)}')
  225. else:
  226. return jsonify({'status': 'pending', 'message': 'Stream is starting'}), 202
  227. else:
  228. del hls_running_streams[channel_id]
  229. cleanup_streams(hls_running_streams)
  230. cmd = [
  231. 'ffmpeg',
  232. '-loglevel', 'error',
  233. '-re',
  234. '-headers', ''.join(f'{k}: {v}\r\n' for k, v in headers.items()) if headers else '',
  235. '-stream_loop', '-1',
  236. '-i', url.partition('$')[0],
  237. '-c:v', 'copy',
  238. '-c:a', 'copy',
  239. '-f', 'flv',
  240. '-flvflags', 'no_duration_filesize',
  241. join_url(rtmp_hls_id_url, channel_id)
  242. ]
  243. try:
  244. process = subprocess.Popen(
  245. cmd,
  246. stdout=subprocess.PIPE,
  247. stderr=subprocess.PIPE,
  248. stdin=subprocess.PIPE
  249. )
  250. threading.Thread(
  251. target=monitor_stream_process,
  252. args=(hls_running_streams, process, channel_id),
  253. daemon=True
  254. ).start()
  255. hls_running_streams[channel_id] = process
  256. return jsonify({
  257. 'status': 'starting',
  258. 'message': 'Stream is being prepared'
  259. }), 202
  260. except Exception as e:
  261. return jsonify({'Error': str(e)}), 500
  262. def stop_rtmp_service():
  263. if sys.platform == "win32":
  264. try:
  265. os.chdir(nginx_dir)
  266. subprocess.Popen([stop_path], shell=True)
  267. except Exception as e:
  268. print(f"❌ Rtmp service stop failed: {e}")
  269. def run_service():
  270. try:
  271. if not os.getenv("GITHUB_ACTIONS"):
  272. if config.open_rtmp and sys.platform == "win32":
  273. original_dir = os.getcwd()
  274. try:
  275. os.chdir(nginx_dir)
  276. subprocess.Popen([nginx_path], shell=True)
  277. except Exception as e:
  278. print(f"❌ Rtmp service start failed: {e}")
  279. finally:
  280. os.chdir(original_dir)
  281. ip_address = get_ip_address()
  282. print(f"📄 Speed test log: {ip_address}/log")
  283. if config.open_rtmp:
  284. print(f"🚀 Live api: {ip_address}/live")
  285. print(f"🚀 HLS api: {ip_address}/hls")
  286. print(f"🚀 IPv4 api: {ip_address}/ipv4")
  287. print(f"🚀 IPv6 api: {ip_address}/ipv6")
  288. print(f"✅ You can use this url to watch IPTV 📺: {ip_address}")
  289. app.run(host="0.0.0.0", port=config.app_port)
  290. except Exception as e:
  291. print(f"❌ Service start failed: {e}")
  292. if __name__ == "__main__":
  293. if config.open_rtmp:
  294. atexit.register(stop_rtmp_service)
  295. run_service()