diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8ab6294 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +flask \ No newline at end of file diff --git a/templates/camera_view.html b/templates/camera_view.html new file mode 100644 index 0000000..3633165 --- /dev/null +++ b/templates/camera_view.html @@ -0,0 +1,790 @@ + + + + + + {{ camera_view_name }} Camera - TeslaCam + + + + +
+
+ + + + + All Cameras + +
+ + TeslaCam +
+
+ +
+ + + +
+
+ + +
+
+

{{ camera_view_name }} Camera

+ {{ videos | length }} Clips +
+ + +
+ + +
+ +
+
+ +
+ + + + +
Select a video clip from the queue to play
+
+
+ + +
+ + + +
+ + + + + + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..6da8288 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,1051 @@ + + + + + + TeslaCam Video Player + + + + +
+
+ +
+ TeslaCam Player + Dashcam & Sentry Archive +
+
+ +
+ + + +
+
+ + +
+ +
+
+ +
+ Front +
+
+ + + + +
Select a clip from the list to begin playback
+
+
+ + +
+ Camera Angles — Same Timestamp +
+ Select a video to unlock synchronized camera switching. +
+
+ + +
+ + + +
+ + + + + + + diff --git a/videoplayer.py b/videoplayer.py new file mode 100644 index 0000000..8f0c922 --- /dev/null +++ b/videoplayer.py @@ -0,0 +1,135 @@ +""" +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 = r'W:\2026\USA2026\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__': + app.run(debug=True)