Skip to content

Commit

Permalink
Dashcam Viewer: Screen Recordings support and interface refresh (comm…
Browse files Browse the repository at this point in the history
…aai#160)

* init bootstrap 5.3

* bootstrap done

* watch screen records

* formatting

* syntax

* import

* specific path

* pathing

* text

* not needed

* sunnypilot home

* better name

* Revert "syntax"

This reverts commit 45c54a54ef978ca317d60a8d4b5581619151e452.

* path

* fix miss "/" at end of path and show current screenrecord video

* Created the About page

* Display errors in a more communicative way

* enable <br> at error messages

* display title on top of the page

* link to download screenrecord

* Update README.md

* Update styles

* Navbar on top, don't block content

* Don't center and format

* Update CHANGELOGS.md

* Update About page

---------

Co-authored-by: Jason Wen <[email protected]>
  • Loading branch information
AlexandreSato and sunnyhaibin authored Jun 2, 2023
1 parent a004024 commit 1846814
Show file tree
Hide file tree
Showing 12 changed files with 258 additions and 153 deletions.
1 change: 1 addition & 0 deletions CHANGELOGS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
sunnypilot - 0.9.2.x (2023-02-22)
========================
* Dashcam Viewer (native & screen recordings) via Browser support thanks to actuallylemoncurd, AlexandreSato, ntegan1, and royjr!
* Honda Clarity 2018-22 support thanks to mcallbosco, vanillagorillaa and wirelessnet2!

sunnypilot - Version Latest (2023-02-22)
Expand Down
4 changes: 2 additions & 2 deletions system/dashcamviewer/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Dashcam Viewer

View dashcam footage by connecting to the comma device via the same network, with your mobile device or PC. Big thanks to [actuallylemoncurd](https://github.com/actuallylemoncurd), [AlexandreSato](https://github.com/alexandreSato), [ntegan1](https://github.com/ntegan1), and [royjr](https://github.com/royjr).
View dashcam footage and screen recordings by connecting to the comma device via the same network, with your mobile device or PC. Big thanks to [actuallylemoncurd](https://github.com/actuallylemoncurd), [AlexandreSato](https://github.com/alexandreSato), [ntegan1](https://github.com/ntegan1), and [royjr](https://github.com/royjr).

The network can be set up by Wi-Fi, mobile hotspot, or tethering on the comma device. Navigate to http://tici:5000/ OR ipAddress:5000 to access the dashcam footage.
The network can be set up by Wi-Fi, mobile hotspot, or tethering on the comma device. Navigate to http://tici:5050/ OR http://ipAddress:5050 to access the dashcam footage.
126 changes: 45 additions & 81 deletions system/dashcamviewer/dashcam_viewer.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env python3
from flask import Flask, render_template, Response, request
from system.dashcamviewer.helpers import *
import os
import system.dashcamviewer.helpers as dashcam
from flask import Flask, render_template, Response, request, send_from_directory
from system.loggerd.config import ROOT as REALDATA

app = Flask(__name__)

Expand All @@ -14,28 +16,27 @@ def index_page():
def full(cameratype, route):
chunk_size = 1024 * 512 # 5KiB
file_name = cameratype + (".ts" if cameratype == "qcamera" else ".hevc")
vidlist = "|".join(ROOT + "/" + segment + "/" + file_name for segment in segments_in_route(route))
vidlist = "|".join(REALDATA + "/" + segment + "/" + file_name for segment in dashcam.segments_in_route(route))

def generate_buffered_stream():
with ffmpeg_mp4_concat_wrap_process_builder(vidlist, cameratype, chunk_size) as process:
with dashcam.ffmpeg_mp4_concat_wrap_process_builder(vidlist, cameratype, chunk_size) as process:
for chunk in iter(lambda: process.stdout.read(chunk_size), b""):
yield bytes(chunk)
return Response(generate_buffered_stream(), status=200, mimetype='video/mp4')


@app.route("/footage/<cameratype>/<segment>")
def fcamera(cameratype, segment):
if not is_valid_segment(segment):
return "invalid segment"
file_name = ROOT + "/" + segment + "/" + cameratype + (".ts" if cameratype == "qcamera" else ".hevc")

return Response(ffmpeg_mp4_wrap_process_builder(file_name).stdout.read(), status=200, mimetype='video/mp4')
if not dashcam.is_valid_segment(segment):
return render_template("error.html", error="invalid segment")
file_name = REALDATA + "/" + segment + "/" + cameratype + (".ts" if cameratype == "qcamera" else ".hevc")
return Response(dashcam.ffmpeg_mp4_wrap_process_builder(file_name).stdout.read(), status=200, mimetype='video/mp4')


@app.route("/footage/<route>")
def route(route):
if len(route) != 20:
return "route not found"
return render_template("error.html", error="route not found")

if str(request.query_string) == "b''":
query_segment = str("0")
Expand All @@ -46,85 +47,48 @@ def route(route):

links = ""
segments = ""
for segment in segments_in_route(route):
for segment in dashcam.segments_in_route(route):
links += "<a href='"+route+"?"+segment.split("--")[2]+","+query_type+"'>"+segment+"</a><br>"
segments += "'"+segment+"',"
return """<html>
<head>
<meta name="viewport" content="initial-scale=1, width=device-width"/>
<link href="/static/favicon.ico" rel="icon">
<title>Dashcam Footage</title>
</head>
<body>
<center>
<video id="video" width="320" height="240" controls autoplay="autoplay" style="background:black">
</video>
<br><br>
current segment: <span id="currentsegment"></span>
<br>
current view: <span id="currentview"></span>
<br>
<a download=\""""+route+"-"+ query_type + ".mp4" + """\" href=\"/footage/full/"""+query_type+"""/"""+route+"""\">download full route """ + query_type + """</a>
<br><br>
<a href="/footage">back to routes</a>
<br><br>
<a href=\""""+route+"""?0,qcamera\">qcamera</a> -
<a href=\""""+route+"""?0,fcamera\">fcamera</a> -
<a href=\""""+route+"""?0,dcamera\">dcamera</a> -
<a href=\""""+route+"""?0,ecamera\">ecamera</a>
<br><br>
"""+links+"""
</center>
</body>
<script>
var video = document.getElementById('video');
var tracks = {
list: ["""+segments+"""],
index: """+query_segment+""",
next: function() {
if (this.index == this.list.length - 1) this.index = 0;
else {
this.index += 1;
}
},
play: function() {
return ( \""""+query_type+"""/" + this.list[this.index] );
}
}
video.addEventListener('ended', function(e) {
tracks.next();
video.src = tracks.play();
document.getElementById("currentsegment").textContent=video.src.split("/")[5];
document.getElementById("currentview").textContent=video.src.split("/")[4];
video.load();
video.play();
});
video.src = tracks.play();
document.getElementById("currentsegment").textContent=video.src.split("/")[5];
document.getElementById("currentview").textContent=video.src.split("/")[4];
</script>
</html>
"""
return render_template("route.html", route=route, query_type=query_type, links=links, segments=segments, query_segment=query_segment)


@app.route("/footage")
def index():
result = """
<html>
<head>
<meta name="viewport" content="initial-scale=1, width=device-width"/>
<link href="/static/favicon.ico" rel="icon">
<title>Dashcam Footage</title>
</head>
<body><center><br><a href='\\'>Back to landing page</a><br>"""
for route in all_routes():
result += "<a href='footage/"+route+"'>"+route+"</a><br>"
result += """</center></body></html>"""
return result
def footage():
return render_template("footage.html", rows=dashcam.all_routes())


@app.route("/screenrecords")
def screenrecords():
rows = dashcam.all_screenrecords()
if not rows:
return render_template("error.html", error="no screenrecords found at:<br><br>" + dashcam.SCREENRECORD_PATH)
return render_template("screenrecords.html", rows=rows, clip=rows[0])


@app.route("/screenrecords/<clip>")
def screenrecord(clip):
return render_template("screenrecords.html", rows=dashcam.all_screenrecords(), clip=clip)


@app.route("/screenrecords/play/pipe/<file>")
def videoscreenrecord(file):
file_name = dashcam.SCREENRECORD_PATH + file
return Response(dashcam.ffplay_mp4_wrap_process_builder(file_name).stdout.read(), status=200, mimetype='video/mp4')


@app.route("/screenrecords/download/<clip>")
def download_file(clip):
return send_from_directory(dashcam.SCREENRECORD_PATH, clip, as_attachment=True)


@app.route("/about")
def about():
return render_template("about.html")


def main():
app.run(host="0.0.0.0")
app.run(host="0.0.0.0", port=5050)


if __name__ == '__main__':
Expand Down
34 changes: 30 additions & 4 deletions system/dashcamviewer/helpers.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import os
import subprocess
from system.loggerd.config import ROOT
from pathlib import Path
from system.hardware import PC
from system.loggerd.config import ROOT as REALDATA
from system.loggerd.uploader import listdir_by_creation
from tools.lib.route import SegmentName

# path to sunnypilot screen recordings
if PC:
SCREENRECORD_PATH = os.path.join(str(Path.home()), ".comma", "media", "0", "videos", "")
else:
SCREENRECORD_PATH = "/data/media/0/videos/"



def all_screenrecords():
return sorted(listdir_by_creation(SCREENRECORD_PATH), reverse=True)


def is_valid_segment(segment):
try:
segment_to_segment_name(ROOT, segment)
segment_to_segment_name(REALDATA, segment)
return True
except AssertionError:
return False
Expand All @@ -20,9 +33,9 @@ def segment_to_segment_name(data_dir, segment):

def all_segment_names():
segments = []
for segment in listdir_by_creation(ROOT):
for segment in listdir_by_creation(REALDATA):
try:
segments.append(segment_to_segment_name(ROOT, segment))
segments.append(segment_to_segment_name(REALDATA, segment))
except AssertionError:
pass
return segments
Expand Down Expand Up @@ -89,3 +102,16 @@ def ffmpeg_mp4_wrap_process_builder(filename):
return subprocess.Popen(
command_line, stdout=subprocess.PIPE
)


def ffplay_mp4_wrap_process_builder(file_name):
command_line = ["ffmpeg"]
command_line += ["-i", file_name]
command_line += ["-c", "copy"]
command_line += ["-map", "0"]
command_line += ["-f", "mp4"]
command_line += ["-movflags", "empty_moov"]
command_line += ["-"]
return subprocess.Popen(
command_line, stdout=subprocess.PIPE
)
1 change: 0 additions & 1 deletion system/dashcamviewer/static/favicon.ico

This file was deleted.

1 change: 1 addition & 0 deletions system/dashcamviewer/static/favicon.ico
18 changes: 18 additions & 0 deletions system/dashcamviewer/templates/about.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{% extends "layout.html" %}

{% block title %}
About
{% endblock %}

{% block main %}
<br>
<b>About</b>
<br><br>
<footer class="small text-center text-muted" style="word-wrap: break-word;">
Special thanks to:<br><br>
ntegan1<br>
royjr<br>
AlexandreSato<br>
actuallylemoncurd
</footer>
{% endblock %}
11 changes: 11 additions & 0 deletions system/dashcamviewer/templates/error.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% extends "layout.html" %}

{% block title %}
About
{% endblock %}

{% block main %}
<br> Oopss
<br><br><br><br>
{{ error | safe }}
{% endblock %}
14 changes: 14 additions & 0 deletions system/dashcamviewer/templates/footage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends "layout.html" %}

{% block title %}
Dashcam Routes
{% endblock %}

{% block main %}
<br>
<b>Dashcam Routes</b>
<br><br>
{% for row in rows %}
<a href="footage/{{ row }}">{{ row }}</a><br>
{% endfor %}
{% endblock %}
78 changes: 13 additions & 65 deletions system/dashcamviewer/templates/index.html
Original file line number Diff line number Diff line change
@@ -1,65 +1,13 @@
<html>
<head>
<meta name="viewport" content="initial-scale=1, width=device-width"/>
<link href="/static/favicon.ico" rel="icon">
<title>remote door locker</title>
<style>
.button {
border: none;
color: white;
padding: 16px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
transition-duration: 0.4s;
cursor: pointer;
}

.button1 {
background-color: white;
color: black;
border: 2px solid #4CAF50;
}

.button1:hover {
background-color: #4CAF50;
color: white;
}

.button2 {
background-color: white;
color: black;
border: 2px solid #008CBA;
}

.button2:hover {
background-color: #008CBA;
color: white;
}

.container {
height: 200px;
position: relative;
border: 3px solid green;
}

.center {
margin: 0;
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}

</style>

</head>
<body>
<center>
<br><a href='footage'>Viem DashCam Footage</a><br>
</center>
</body>
</html>
{% extends "layout.html" %}

{% block title %}
Home
{% endblock %}

{% block main %}
<br>
<b>Dashcam Viewer</b>
<br><br>
<a href='/footage'>View Dashcam Footage</a><br>
<br><a href='/screenrecords'>View Screen Recordings</a><br>
{% endblock %}
Loading

0 comments on commit 1846814

Please sign in to comment.