diff --git a/web/app.py b/web/app.py index bfc3880..fcbb04b 100644 --- a/web/app.py +++ b/web/app.py @@ -158,6 +158,102 @@ def extract_channel(path: str) -> str: return 'Unknown' +def normalize_path(file_path: str) -> str: + """Normalize malformed file paths that have been corrupted with ://.""" + # Fix malformed paths that have been corrupted with :// + # Example: z://MP4KaraFun KaraokeKaraoke I m Not In Love - 10CC.mp4 + # Should be: z:\MP4\KaraFun Karaoke\Karaoke I'm Not In Love - 10CC.mp4 + + if '://' in file_path: + print(f"DEBUG: Detected malformed path, attempting to fix: {file_path}") + + # Extract drive letter and rest of path + import re + match = re.match(r'^([a-zA-Z])://(.+)$', file_path) + if match: + drive_letter = match.group(1) + rest_of_path = match.group(2) + + # Try to reconstruct the proper path structure + # Look for common patterns in the corrupted path + + # Pattern 1: Handle the specific case where "Karaoke" appears twice + # Example: "MP4KaraFun KaraokeKaraoke I m Not In Love - 10CC.mp4" + # Should become: "MP4\KaraFun Karaoke\Karaoke I'm Not In Love - 10CC.mp4" + karaoke_double_match = re.match(r'^MP4([A-Za-z\s]+)KaraokeKaraoke(.+)$', rest_of_path) + if karaoke_double_match: + channel_name = karaoke_double_match.group(1) + "Karaoke" + file_name = "Karaoke" + karaoke_double_match.group(2) + fixed_path = f"{drive_letter}:\\MP4\\{channel_name}\\{file_name}" + print(f"DEBUG: Fixed path (pattern 1 - double karaoke): {fixed_path}") + return fixed_path + + # Pattern 2: MP4 followed by channel name (e.g., MP4KaraFun Karaoke) + mp4_match = re.match(r'^MP4([A-Za-z\s]+Karaoke)(.+)$', rest_of_path) + if mp4_match: + channel_name = mp4_match.group(1) + file_name = mp4_match.group(2) + fixed_path = f"{drive_letter}:\\MP4\\{channel_name}\\{file_name}" + print(f"DEBUG: Fixed path (pattern 2): {fixed_path}") + return fixed_path + + # Pattern 3: Direct channel name followed by filename + channel_match = re.match(r'^([A-Za-z\s]+Karaoke)(.+)$', rest_of_path) + if channel_match: + channel_name = channel_match.group(1) + file_name = channel_match.group(2) + fixed_path = f"{drive_letter}:\\MP4\\{channel_name}\\{file_name}" + print(f"DEBUG: Fixed path (pattern 3): {fixed_path}") + return fixed_path + + # Pattern 4: Look for any known channel names + known_channels = ['Sing King Karaoke', 'KaraFun Karaoke', 'Stingray Karaoke'] + for channel in known_channels: + if channel.lower().replace(' ', '') in rest_of_path.lower().replace(' ', ''): + # Extract the part before and after the channel name + channel_lower = channel.lower().replace(' ', '') + rest_lower = rest_of_path.lower().replace(' ', '') + channel_index = rest_lower.find(channel_lower) + + if channel_index >= 0: + # Reconstruct the path + before_channel = rest_of_path[:channel_index] + after_channel = rest_of_path[channel_index + len(channel):] + + # If there's content before the channel, it might be a folder like "MP4" + if before_channel and before_channel.lower() in ['mp4']: + fixed_path = f"{drive_letter}:\\{before_channel}\\{channel}{after_channel}" + else: + fixed_path = f"{drive_letter}:\\MP4\\{channel}{after_channel}" + + print(f"DEBUG: Fixed path (pattern 4): {fixed_path}") + return fixed_path + + # Pattern 5: Try to split by common separators and reconstruct + # Look for patterns like "MP4KaraFunKaraoke" -> "MP4\KaraFun Karaoke" + if 'karaoke' in rest_of_path.lower(): + # Try to find where "karaoke" appears and reconstruct + karaoke_index = rest_of_path.lower().find('karaoke') + if karaoke_index > 0: + before_karaoke = rest_of_path[:karaoke_index] + after_karaoke = rest_of_path[karaoke_index + 7:] # length of "karaoke" + + # If before_karaoke starts with "MP4", extract the channel name + if before_karaoke.lower().startswith('mp4'): + channel_part = before_karaoke[3:] # Remove "MP4" + if channel_part: + fixed_path = f"{drive_letter}:\\MP4\\{channel_part} Karaoke{after_karaoke}" + print(f"DEBUG: Fixed path (pattern 5): {fixed_path}") + return fixed_path + + # Fallback: just replace :// with :\ and hope for the best + fallback_path = file_path.replace('://', ':\\') + print(f"DEBUG: Fallback path fix: {fallback_path}") + return fallback_path + + return file_path + + @app.route('/') def index(): """Main dashboard page.""" @@ -538,54 +634,83 @@ def serve_video(file_path): import urllib.parse decoded_path = urllib.parse.unquote(file_path) + # Normalize the path to fix any malformed paths + normalized_path = normalize_path(decoded_path) + # Debug logging print(f"DEBUG: Video request for path: {decoded_path}") + print(f"DEBUG: Normalized path: {normalized_path}") print(f"DEBUG: Current working directory: {os.getcwd()}") # Security check: ensure the path is within allowed directories # This prevents directory traversal attacks - if '..' in decoded_path: + if '..' in normalized_path: print(f"DEBUG: Security check failed - path contains '..'") return jsonify({'error': 'Invalid file path'}), 400 # On Windows, allow absolute paths with drive letters # On Unix-like systems, block absolute paths if os.name == 'nt': # Windows - if decoded_path.startswith('/') and not decoded_path[1:].startswith(':'): + if normalized_path.startswith('/') and not normalized_path[1:].startswith(':'): print(f"DEBUG: Security check failed - Unix-style absolute path on Windows") return jsonify({'error': 'Invalid file path'}), 400 else: # Unix-like systems - if decoded_path.startswith('/'): + if normalized_path.startswith('/'): print(f"DEBUG: Security check failed - absolute path on Unix") return jsonify({'error': 'Invalid file path'}), 400 # Check if file exists - if not os.path.exists(decoded_path): - print(f"DEBUG: File does not exist: {decoded_path}") + if not os.path.exists(normalized_path): + print(f"DEBUG: File does not exist: {normalized_path}") return jsonify({'error': 'Video file not found'}), 404 - # Check if it's a video file - if not decoded_path.lower().endswith(('.mp4', '.avi', '.mkv', '.mov', '.wmv')): - print(f"DEBUG: Invalid file type: {decoded_path}") + # Check if it's a video file and determine MIME type + file_extension = os.path.splitext(normalized_path)[1].lower() + mime_types = { + '.mp4': 'video/mp4', + '.avi': 'video/x-msvideo', + '.mkv': 'video/x-matroska', + '.mov': 'video/quicktime', + '.wmv': 'video/x-ms-wmv', + '.flv': 'video/x-flv', + '.webm': 'video/webm' + } + + if file_extension not in mime_types: + print(f"DEBUG: Invalid file type: {normalized_path}") return jsonify({'error': 'Invalid file type'}), 400 + mime_type = mime_types[file_extension] + # Get file info for debugging - file_size = os.path.getsize(decoded_path) + file_size = os.path.getsize(normalized_path) print(f"DEBUG: File exists, size: {file_size} bytes") + print(f"DEBUG: MIME type: {mime_type}") # Serve the video file - directory = os.path.dirname(decoded_path) - filename = os.path.basename(decoded_path) + directory = os.path.dirname(normalized_path) + filename = os.path.basename(normalized_path) print(f"DEBUG: Serving from directory: {directory}") print(f"DEBUG: Filename: {filename}") - return send_from_directory( + # Add headers for better video streaming + response = send_from_directory( directory, filename, - mimetype='video/mp4' # Adjust based on file type if needed + mimetype=mime_type ) + # Add CORS headers to allow cross-origin requests + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Methods'] = 'GET, HEAD, OPTIONS' + response.headers['Access-Control-Allow-Headers'] = 'Range' + + # Add cache control headers + response.headers['Cache-Control'] = 'public, max-age=3600' + + return response + except Exception as e: print(f"DEBUG: Exception in serve_video: {str(e)}") return jsonify({'error': f'Error serving video: {str(e)}'}), 500 diff --git a/web/templates/index.html b/web/templates/index.html index 36e6858..9809148 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -7,6 +7,22 @@ +