initial commit
This commit is contained in:
commit
9b5769747a
2 changed files with 223 additions and 0 deletions
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
|||
currently only supports a ``--debug``` flag
|
222
stream.py
Normal file
222
stream.py
Normal file
|
@ -0,0 +1,222 @@
|
|||
from flask import Flask, Response, request
|
||||
import subprocess
|
||||
import os
|
||||
import argparse
|
||||
import time
|
||||
import threading
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
BASE_STORAGE_PATH = "/vPath" # Root directory for MKV files
|
||||
DEBUG_MODE = True # Default: Debugging on
|
||||
|
||||
|
||||
@app.route('/convert/vpath/<path:filepath>.webm')
|
||||
def convert(filepath):
|
||||
# Log the incoming request
|
||||
if DEBUG_MODE:
|
||||
print(f"[DEBUG] Received request: {request.url}")
|
||||
print(f"[DEBUG] Mapped file path: {os.path.join(BASE_STORAGE_PATH, filepath + '.mkv')}")
|
||||
|
||||
mkv_file = os.path.join(BASE_STORAGE_PATH, filepath + ".mkv")
|
||||
|
||||
# Check if the file exists before proceeding
|
||||
if not os.path.isfile(mkv_file):
|
||||
if DEBUG_MODE:
|
||||
print(f"[ERROR] File not found: {mkv_file}")
|
||||
return "File not found", 404
|
||||
|
||||
def generate():
|
||||
# Use ffprobe to get the audio codec info
|
||||
probe_cmd = ["ffprobe", "-v", "error", "-select_streams", "a:0", "-show_entries", "stream=codec_name", "-of", "default=nw=1:nk=1", mkv_file]
|
||||
try:
|
||||
audio_codec_info = subprocess.check_output(probe_cmd, stderr=subprocess.PIPE).decode('utf-8').strip()
|
||||
if DEBUG_MODE:
|
||||
print(f"[DEBUG] Audio codec: {audio_codec_info}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
if DEBUG_MODE:
|
||||
print(f"[ERROR] Audio probe failed: {e}")
|
||||
audio_codec_info = "unknown"
|
||||
|
||||
# Use ffprobe to get the video information
|
||||
probe_cmd = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=codec_name", "-of", "default=nw=1:nk=1", mkv_file]
|
||||
try:
|
||||
video_codec_info = subprocess.check_output(probe_cmd, stderr=subprocess.PIPE).decode('utf-8').strip()
|
||||
if DEBUG_MODE:
|
||||
print(f"[DEBUG] Video codec: {video_codec_info}")
|
||||
except subprocess.CalledProcessError as e:
|
||||
if DEBUG_MODE:
|
||||
print(f"[ERROR] Video probe failed: {e}")
|
||||
video_codec_info = "unknown"
|
||||
|
||||
# Get available encoders in this FFmpeg installation
|
||||
available_encoders = []
|
||||
try:
|
||||
encoders_output = subprocess.check_output(["ffmpeg", "-encoders"], stderr=subprocess.PIPE).decode('utf-8')
|
||||
if DEBUG_MODE:
|
||||
print("[DEBUG] Checking available encoders")
|
||||
for line in encoders_output.split('\n'):
|
||||
if ' V' in line or ' A' in line: # Video or Audio encoder
|
||||
parts = line.split()
|
||||
if len(parts) > 2:
|
||||
available_encoders.append(parts[1])
|
||||
if DEBUG_MODE:
|
||||
print(f"[DEBUG] Found encoders: {', '.join(available_encoders)}")
|
||||
except Exception as e:
|
||||
if DEBUG_MODE:
|
||||
print(f"[ERROR] Failed to get encoder list: {str(e)}")
|
||||
|
||||
# Build FFmpeg command with fallback options
|
||||
ffmpeg_cmd = ["ffmpeg", "-i", mkv_file]
|
||||
|
||||
# Add video options based on codec and available encoders
|
||||
if video_codec_info in ["vp8", "vp9"]:
|
||||
ffmpeg_cmd.extend(["-c:v", "copy"])
|
||||
elif "libvpx" in available_encoders:
|
||||
ffmpeg_cmd.extend(["-c:v", "libvpx", "-b:v", "1M"])
|
||||
elif "vp8" in available_encoders:
|
||||
ffmpeg_cmd.extend(["-c:v", "vp8", "-b:v", "1M"])
|
||||
else:
|
||||
# Last resort fallback
|
||||
ffmpeg_cmd.extend(["-c:v", "libx264", "-b:v", "1M"])
|
||||
|
||||
# Add audio options based on codec and available encoders
|
||||
if audio_codec_info in ["opus", "vorbis"]:
|
||||
ffmpeg_cmd.extend(["-c:a", "copy"])
|
||||
elif "libvorbis" in available_encoders:
|
||||
ffmpeg_cmd.extend(["-c:a", "libvorbis", "-b:a", "128k"])
|
||||
elif "vorbis" in available_encoders:
|
||||
ffmpeg_cmd.extend(["-c:a", "vorbis", "-b:a", "128k"])
|
||||
elif "aac" in available_encoders:
|
||||
ffmpeg_cmd.extend(["-c:a", "aac", "-b:a", "128k"])
|
||||
else:
|
||||
# Try to copy audio as a last resort
|
||||
ffmpeg_cmd.extend(["-c:a", "copy"])
|
||||
|
||||
# Add output format and destination - simplified with just essential options
|
||||
ffmpeg_cmd.extend([
|
||||
"-f", "webm",
|
||||
"-y", # Overwrite output without asking
|
||||
"pipe:1"
|
||||
])
|
||||
|
||||
if DEBUG_MODE:
|
||||
print(f"[DEBUG] Executing command: {' '.join(ffmpeg_cmd)}")
|
||||
|
||||
# Start FFmpeg process
|
||||
process = subprocess.Popen(
|
||||
ffmpeg_cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
bufsize=1024*1024 # 1MB buffer
|
||||
)
|
||||
|
||||
# Create a flag to track if we're actually getting video data
|
||||
received_data = False
|
||||
error_output = []
|
||||
|
||||
# Create a thread to capture and log stderr
|
||||
def log_stderr():
|
||||
for line in iter(process.stderr.readline, b''):
|
||||
line_text = line.decode('utf-8', errors='replace').strip()
|
||||
error_output.append(line_text)
|
||||
if DEBUG_MODE:
|
||||
print(f"[FFMPEG] {line_text}")
|
||||
|
||||
stderr_thread = threading.Thread(target=log_stderr)
|
||||
stderr_thread.daemon = True
|
||||
stderr_thread.start()
|
||||
|
||||
try:
|
||||
# Stream output to client
|
||||
start_time = time.time()
|
||||
byte_count = 0
|
||||
|
||||
while True:
|
||||
chunk = process.stdout.read(65536) # 64KB chunk size
|
||||
|
||||
# Check if we got any data
|
||||
if chunk:
|
||||
byte_count += len(chunk)
|
||||
received_data = True
|
||||
if byte_count == len(chunk): # First chunk
|
||||
if DEBUG_MODE:
|
||||
print(f"[DEBUG] First chunk received after {time.time() - start_time:.2f} seconds, size: {len(chunk)} bytes")
|
||||
yield chunk
|
||||
else:
|
||||
# Check if process has ended
|
||||
if process.poll() is not None:
|
||||
if DEBUG_MODE:
|
||||
print(f"[DEBUG] FFmpeg process completed with code: {process.returncode}")
|
||||
print(f"[DEBUG] Total bytes streamed: {byte_count}")
|
||||
|
||||
# If we never got any data and process exited with error
|
||||
if not received_data and process.returncode != 0:
|
||||
if DEBUG_MODE:
|
||||
print("[ERROR] FFmpeg failed to produce any output")
|
||||
if error_output:
|
||||
print(f"[ERROR] Last errors: {error_output[-10:]}")
|
||||
break
|
||||
|
||||
# Short wait before trying again
|
||||
time.sleep(0.1)
|
||||
|
||||
# Safety timeout - if no data after 30 seconds, abort
|
||||
if not received_data and (time.time() - start_time) > 30:
|
||||
if DEBUG_MODE:
|
||||
print("[ERROR] Timed out waiting for FFmpeg output")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
if DEBUG_MODE:
|
||||
print(f"[ERROR] Streaming exception: {str(e)}")
|
||||
|
||||
finally:
|
||||
try:
|
||||
if process.poll() is None:
|
||||
process.terminate()
|
||||
time.sleep(0.5)
|
||||
if process.poll() is None:
|
||||
process.kill()
|
||||
|
||||
# Wait for stderr thread to finish
|
||||
stderr_thread.join(timeout=1.0)
|
||||
|
||||
except Exception as e:
|
||||
if DEBUG_MODE:
|
||||
print(f"[ERROR] Process cleanup error: {str(e)}")
|
||||
|
||||
if DEBUG_MODE:
|
||||
print(f"[DEBUG] Streaming ended for: {mkv_file}")
|
||||
|
||||
# Set proper headers for streaming
|
||||
headers = {
|
||||
'Content-Type': 'video/webm',
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'X-Content-Type-Options': 'nosniff'
|
||||
}
|
||||
|
||||
return Response(
|
||||
generate(),
|
||||
headers=headers,
|
||||
direct_passthrough=True
|
||||
)
|
||||
|
||||
|
||||
@app.route('/test')
|
||||
def test():
|
||||
"""Simple test endpoint to verify the server is running"""
|
||||
return "Server is running"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Run Flask video streaming server")
|
||||
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.debug:
|
||||
DEBUG_MODE = True
|
||||
print("[DEBUG] Debug mode enabled")
|
||||
|
||||
app.run(host="0.0.0.0", port=8080, debug=DEBUG_MODE, threaded=True)
|
Loading…
Add table
Reference in a new issue