Files
tesla-cam-view/videoplayer.py
T
2026-07-24 11:32:32 +02:00

137 lines
4.3 KiB
Python

"""
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/<camera_view>')
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/<path:filepath>')
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)