""" TeslaCam Video Player This web app displays TeslaCam videos and allows users to change the camera view. It scans the TeslaCam folder for all videos and stores a list sorted by filename and categorized by camera view (format YYYY-MM-DD_HH-MM-SS-${camera_view}.mp4). The app then displays the videos in a web interface, allowing users to select and play videos from different camera views. """ import os import re from flask import Flask, render_template, send_from_directory, abort app = Flask(__name__) # Root TeslaCam directory containing RecentClips, SavedClips, SentryClips, etc. TESLACAM_DIRECTORY = '/mnt/TeslaCam' # Regular expression matching TeslaCam filenames: YYYY-MM-DD_HH-MM-SS-${camera_view}.mp4 VIDEO_FILENAME_REGEX = re.compile( r'^(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})-(\w+)\.mp4$' ) # Friendly display names for camera views CAMERA_VIEW_MAPPING = { 'front': 'Front', 'left_repeater': 'Left Repeater', 'right_repeater': 'Right Repeater', 'rear': 'Rear', 'left_pillar': 'Left Pillar', 'right_pillar': 'Right Pillar' } def scan_teslacam_videos(): """ Recursively scans the TeslaCam directory tree for video files and categorizes them by camera view. Returns: dict: Keys are raw camera view identifiers (e.g. 'front'), values are lists of dictionaries containing file details sorted chronologically. """ videos_by_camera = {} if not os.path.exists(TESLACAM_DIRECTORY): return videos_by_camera # Recursive directory walk to catch nested folders in SavedClips/SentryClips for root, _, files in os.walk(TESLACAM_DIRECTORY): for filename in files: match = VIDEO_FILENAME_REGEX.match(filename) if match: timestamp, camera_view = match.groups() # Get path relative to TESLACAM_DIRECTORY for safe web serving full_path = os.path.join(root, filename) rel_path = os.path.relpath( full_path, TESLACAM_DIRECTORY).replace('\\', '/') video_entry = { 'filename': filename, 'timestamp': timestamp, 'rel_path': rel_path, 'url': f'/video/{rel_path}' } if camera_view not in videos_by_camera: videos_by_camera[camera_view] = [] videos_by_camera[camera_view].append(video_entry) # Sort each camera list chronologically by filename/timestamp for camera_view in videos_by_camera: videos_by_camera[camera_view].sort(key=lambda item: item['filename']) return videos_by_camera def get_camera_display_name(camera_view): """Returns human-readable title for a camera view key.""" return CAMERA_VIEW_MAPPING.get(camera_view, camera_view.replace('_', ' ').title()) @app.route('/') def index(): """ Main page listing all videos categorized by human-readable camera view. """ all_videos = scan_teslacam_videos() # Map raw camera keys to display names for rendering categorized_videos = {} for camera_view, video_list in all_videos.items(): display_name = get_camera_display_name(camera_view) categorized_videos[display_name] = { 'raw_key': camera_view, 'videos': video_list } return render_template('index.html', categorized_videos=categorized_videos) @app.route('/camera/') def camera_view(camera_view): """ Displays all available videos for a single camera view. """ all_videos = scan_teslacam_videos() videos = all_videos.get(camera_view, []) display_name = get_camera_display_name(camera_view) return render_template( 'camera_view.html', camera_view_key=camera_view, camera_view_name=display_name, videos=videos ) @app.route('/video/') def serve_video(filepath): """ Safely streams video files from the TeslaCam directory structure. """ full_path = os.path.join(TESLACAM_DIRECTORY, filepath) if not os.path.exists(full_path): abort(404, description="Video file not found") return send_from_directory(TESLACAM_DIRECTORY, filepath) if __name__ == '__main__': host = '0.0.0.0' app.run(debug=True, host=host, port=5000)