implement caching for TeslaCam directory scan and add refresh endpoint
This commit is contained in:
1 parent
a4202afe30
commit
829c28ca69
3 files changed
+57
-6
No files matched your search
@@ -105,6 +105,7 @@ file / environment rather than relying on `.env`.
|
|||||||
| `/` | Main page with all videos categorized by camera view |
|
| `/` | Main page with all videos categorized by camera view |
|
||||||
| `/camera/<camera_view>` | All videos for a single camera view |
|
| `/camera/<camera_view>` | All videos for a single camera view |
|
||||||
| `/video/<path:filepath>` | Streams a video file from the TeslaCam directory |
|
| `/video/<path:filepath>` | Streams a video file from the TeslaCam directory |
|
||||||
|
| `/refresh` | Forces a re-scan of the TeslaCam directory |
|
||||||
|
|
||||||
## Camera Views
|
## Camera Views
|
||||||
|
|
||||||
@@ -119,6 +120,9 @@ file / environment rather than relying on `.env`.
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
- The directory scan is cached in memory for `SCAN_CACHE_TTL` seconds
|
||||||
|
(default 300) to avoid re-walking the tree on every request. Hit
|
||||||
|
`/refresh` to force a re-scan after copying new clips onto the drive.
|
||||||
- Debug mode is off by default (`FLASK_DEBUG=0`) and is only used by the local
|
- Debug mode is off by default (`FLASK_DEBUG=0`) and is only used by the local
|
||||||
dev server. It has no effect when running under a WSGI server like gunicorn.
|
dev server. It has no effect when running under a WSGI server like gunicorn.
|
||||||
- The TeslaCam directory must be accessible from the machine running the app (e.g., a mounted USB drive or network share).
|
- The TeslaCam directory must be accessible from the machine running the app (e.g., a mounted USB drive or network share).
|
||||||
|
|||||||
+18
-5
@@ -1029,18 +1029,31 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-play next clip when video finishes
|
// Auto-play next clip of the same camera when video finishes
|
||||||
player.addEventListener("ended", function () {
|
player.addEventListener("ended", function () {
|
||||||
const list = getFiltered();
|
if (!current) return;
|
||||||
const i = list.findIndex(function (c) {
|
// Build a list of clips for the SAME camera, sorted like the sidebar
|
||||||
|
const sameCam = clips
|
||||||
|
.filter(function (c) {
|
||||||
|
return c.cam === current.cam;
|
||||||
|
})
|
||||||
|
.sort(function (a, b) {
|
||||||
|
if (sortMode === "name")
|
||||||
|
return a.filename.localeCompare(b.filename);
|
||||||
|
const da = tsToDate(a.timestamp),
|
||||||
|
db = tsToDate(b.timestamp);
|
||||||
|
const ta = da ? da.getTime() : 0,
|
||||||
|
tb = db ? db.getTime() : 0;
|
||||||
|
return sortMode === "oldest" ? ta - tb : tb - ta;
|
||||||
|
});
|
||||||
|
const i = sameCam.findIndex(function (c) {
|
||||||
return (
|
return (
|
||||||
current &&
|
|
||||||
c.cam === current.cam &&
|
c.cam === current.cam &&
|
||||||
c.timestamp === current.timestamp &&
|
c.timestamp === current.timestamp &&
|
||||||
c.filename === current.filename
|
c.filename === current.filename
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (i > -1 && list[i + 1]) playClip(list[i + 1]);
|
if (i > -1 && sameCam[i + 1]) playClip(sameCam[i + 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
render();
|
render();
|
||||||
|
|||||||
+35
-1
@@ -11,6 +11,7 @@ play videos from different camera views.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from flask import Flask, render_template, send_from_directory, abort
|
from flask import Flask, render_template, send_from_directory, abort
|
||||||
@@ -39,8 +40,15 @@ CAMERA_VIEW_MAPPING = {
|
|||||||
'right_pillar': 'Right Pillar'
|
'right_pillar': 'Right Pillar'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# In-memory scan cache. Scanning a large TeslaCam tree on every request is
|
||||||
|
# expensive, so the result is cached and only refreshed after this many seconds
|
||||||
|
# (or when /refresh is hit). Override via the SCAN_CACHE_TTL env var.
|
||||||
|
SCAN_CACHE_TTL = int(os.environ.get('SCAN_CACHE_TTL', '3600'))
|
||||||
|
|
||||||
def scan_teslacam_videos():
|
_scan_cache = {'data': None, 'expires_at': 0.0}
|
||||||
|
|
||||||
|
|
||||||
|
def _scan_teslacam_videos_uncached():
|
||||||
"""
|
"""
|
||||||
Recursively scans the TeslaCam directory tree for video files and categorizes
|
Recursively scans the TeslaCam directory tree for video files and categorizes
|
||||||
them by camera view.
|
them by camera view.
|
||||||
@@ -84,6 +92,22 @@ def scan_teslacam_videos():
|
|||||||
return videos_by_camera
|
return videos_by_camera
|
||||||
|
|
||||||
|
|
||||||
|
def scan_teslacam_videos(force_refresh=False):
|
||||||
|
"""
|
||||||
|
Returns the cached TeslaCam video scan, refreshing it when the TTL expires
|
||||||
|
or when `force_refresh` is True. Safe to call on every request.
|
||||||
|
"""
|
||||||
|
now = time.time()
|
||||||
|
if (
|
||||||
|
_scan_cache['data'] is None
|
||||||
|
or now >= _scan_cache['expires_at']
|
||||||
|
or force_refresh
|
||||||
|
):
|
||||||
|
_scan_cache['data'] = _scan_teslacam_videos_uncached()
|
||||||
|
_scan_cache['expires_at'] = now + SCAN_CACHE_TTL
|
||||||
|
return _scan_cache['data']
|
||||||
|
|
||||||
|
|
||||||
def get_camera_display_name(camera_view):
|
def get_camera_display_name(camera_view):
|
||||||
"""Returns human-readable title for a camera view key."""
|
"""Returns human-readable title for a camera view key."""
|
||||||
return CAMERA_VIEW_MAPPING.get(camera_view, camera_view.replace('_', ' ').title())
|
return CAMERA_VIEW_MAPPING.get(camera_view, camera_view.replace('_', ' ').title())
|
||||||
@@ -137,6 +161,16 @@ def serve_video(filepath):
|
|||||||
return send_from_directory(TESLACAM_DIRECTORY, filepath)
|
return send_from_directory(TESLACAM_DIRECTORY, filepath)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/refresh')
|
||||||
|
def refresh():
|
||||||
|
"""
|
||||||
|
Forces a re-scan of the TeslaCam directory, bypassing the cache TTL.
|
||||||
|
Useful after copying new clips onto the drive.
|
||||||
|
"""
|
||||||
|
scan_teslacam_videos(force_refresh=True)
|
||||||
|
return 'Cache refreshed', 200
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Local dev server. For production, run behind a WSGI server instead, e.g.:
|
# Local dev server. For production, run behind a WSGI server instead, e.g.:
|
||||||
# gunicorn -w 4 -b 0.0.0.0:5000 'videoplayer:app'
|
# gunicorn -w 4 -b 0.0.0.0:5000 'videoplayer:app'
|
||||||
|
|||||||
Reference in new issue
Block a user