add logging and a few more error handling

This commit is contained in:
2025-01-26 18:37:29 -03:00
parent 60e57b1667
commit 49d066c9e0

116
run.py
View File

@@ -1,4 +1,5 @@
import json import json
import logging
import socket import socket
import urllib.parse import urllib.parse
@@ -8,6 +9,9 @@ from fake_useragent import UserAgent
from flask import Flask, Response, request from flask import Flask, Response, request
from requests.exceptions import SSLError from requests.exceptions import SSLError
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = Flask(__name__) app = Flask(__name__)
def resolve_dns(hostname): def resolve_dns(hostname):
@@ -92,30 +96,60 @@ def proxy_image(image_url):
try: try:
# Decode the URL # Decode the URL
original_url = urllib.parse.unquote(image_url) original_url = urllib.parse.unquote(image_url)
logger.info(f"Image proxy request for: {original_url}")
# Make request with stream=True to handle large images # Make request with stream=True and timeout
response = requests.get(original_url, stream=True) response = requests.get(original_url, stream=True, timeout=10)
response.raise_for_status() response.raise_for_status()
# Get content type from response, default to image/jpeg # Get content type from response
content_type = response.headers.get('Content-Type', 'image/jpeg') content_type = response.headers.get('Content-Type', '')
logger.info(f"Image response headers: {dict(response.headers)}")
if not content_type.startswith('image/'):
logger.error(f"Invalid content type for image: {content_type}")
return Response('Invalid image type', status=415)
def generate(): def generate():
for chunk in response.iter_content(chunk_size=8192): try:
yield chunk bytes_sent = 0
for chunk in response.iter_content(chunk_size=8192):
if chunk:
bytes_sent += len(chunk)
yield chunk
logger.info(f"Image completed, sent {bytes_sent} bytes")
except Exception as e:
logger.error(f"Image streaming error in generator: {str(e)}")
raise
headers = {
'Cache-Control': 'public, max-age=31536000',
'Access-Control-Allow-Origin': '*',
}
# Only add Content-Length if we have it and it's not chunked transfer
if ('Content-Length' in response.headers and
'Transfer-Encoding' not in response.headers):
headers['Content-Length'] = response.headers['Content-Length']
else:
headers['Transfer-Encoding'] = 'chunked'
logger.info(f"Sending image response with headers: {headers}")
return Response( return Response(
generate(), generate(),
mimetype=content_type, mimetype=content_type,
headers={ headers=headers
'Cache-Control': 'public, max-age=31536000',
'Access-Control-Allow-Origin': '*',
'Content-Length': response.headers.get('Content-Length')
}
) )
except requests.Timeout:
logger.error(f"Timeout fetching image: {original_url}")
return Response('Image fetch timeout', status=504)
except requests.HTTPError as e:
logger.error(f"HTTP error fetching image: {str(e)}")
return Response(f'Failed to fetch image: {str(e)}', status=e.response.status_code)
except Exception as e: except Exception as e:
print(f"Image proxy error: {str(e)}") logger.error(f"Image proxy error: {str(e)}")
return Response('', mimetype='image/jpeg') return Response('Failed to process image', status=500)
@app.route('/xmltv', methods=['GET']) @app.route('/xmltv', methods=['GET'])
def generate_xmltv(): def generate_xmltv():
@@ -231,45 +265,75 @@ def proxy_stream(stream_url):
try: try:
# Decode the URL # Decode the URL
original_url = urllib.parse.unquote(stream_url) original_url = urllib.parse.unquote(stream_url)
logger.info(f"Stream proxy request for: {original_url}")
# Make request with proper headers # Make request with proper headers
headers = { headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
} }
response = requests.get(original_url, stream=True, headers=headers) # Add timeout to prevent hanging
response = requests.get(original_url, stream=True, headers=headers, timeout=10)
response.raise_for_status() response.raise_for_status()
logger.info(f"Stream response headers: {dict(response.headers)}")
# Get content type from response
content_type = response.headers.get('Content-Type')
if not content_type:
# Try to determine content type from URL
if original_url.endswith('.ts'):
content_type = 'video/MP2T'
elif original_url.endswith('.m3u8'):
content_type = 'application/vnd.apple.mpegurl'
else:
content_type = 'application/octet-stream'
logger.info(f"Using content type: {content_type}")
def generate(): def generate():
try: try:
for chunk in response.iter_content(chunk_size=64*1024): # Increased chunk size bytes_sent = 0
for chunk in response.iter_content(chunk_size=64*1024):
if chunk: if chunk:
bytes_sent += len(chunk)
yield chunk yield chunk
logger.info(f"Stream completed, sent {bytes_sent} bytes")
except Exception as e: except Exception as e:
print(f"Streaming error: {str(e)}") logger.error(f"Streaming error in generator: {str(e)}")
raise
# Copy important headers from original response
response_headers = { response_headers = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Content-Type': response.headers.get('Content-Type', 'video/MP2T'), 'Content-Type': content_type,
'Accept-Ranges': 'bytes', 'Accept-Ranges': 'bytes',
'Cache-Control': 'no-cache', 'Cache-Control': 'no-cache',
'Connection': 'keep-alive', 'Connection': 'keep-alive'
'Transfer-Encoding': 'chunked'
} }
# Add Content-Length if available # Only add Content-Length if we have it and it's not chunked transfer
if 'Content-Length' in response.headers: if ('Content-Length' in response.headers and
'Transfer-Encoding' not in response.headers):
response_headers['Content-Length'] = response.headers['Content-Length'] response_headers['Content-Length'] = response.headers['Content-Length']
else:
response_headers['Transfer-Encoding'] = 'chunked'
logger.info(f"Sending response with headers: {response_headers}")
return Response( return Response(
generate(), generate(),
headers=response_headers, headers=response_headers,
direct_passthrough=True # More efficient streaming direct_passthrough=True
) )
except requests.Timeout:
logger.error(f"Timeout fetching stream: {original_url}")
return Response('Stream timeout', status=504)
except requests.HTTPError as e:
logger.error(f"HTTP error fetching stream: {str(e)}")
return Response(f'Failed to fetch stream: {str(e)}', status=e.response.status_code)
except Exception as e: except Exception as e:
print(f"Stream proxy error: {str(e)}") logger.error(f"Stream proxy error: {str(e)}")
return Response('', mimetype='video/MP2T') return Response('Failed to process stream', status=500)
@app.route('/m3u', methods=['GET']) @app.route('/m3u', methods=['GET'])
def generate_m3u(): def generate_m3u():
@@ -381,4 +445,4 @@ def generate_m3u():
return Response(m3u_playlist, mimetype='audio/x-scpls', headers={"Content-Disposition": "attachment; filename=LiveStream.m3u"}) return Response(m3u_playlist, mimetype='audio/x-scpls', headers={"Content-Disposition": "attachment; filename=LiveStream.m3u"})
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=True) app.run(debug=True, host='0.0.0.0')