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 @@
+