diff --git a/docker/Dockerfile b/docker/Dockerfile
index 5d1425678..ba46acf5a 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -63,7 +63,8 @@ ENV \
DEBIAN_FRONTEND=noninteractive \
S6_KEEP_ENV=1 \
S6_SERVICES_GRACETIME=30000 \
- S6_KILL_GRACETIME=1000 \
+ S6_KILL_GRACETIME=30000 \
+ S6_KILL_FINISH_MAXTIME=30000 \
PATH=$PATH:/home/abc/bin \
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib \
PYTHONPATH=$PYTHONPATH:/usr/local/lib/python3.8/site-packages \
diff --git a/frontend/src/components/events/CameraPickerDialog.tsx b/frontend/src/components/events/CameraPickerDialog.tsx
index fc84a5146..51aeee007 100644
--- a/frontend/src/components/events/CameraPickerDialog.tsx
+++ b/frontend/src/components/events/CameraPickerDialog.tsx
@@ -33,6 +33,7 @@ export const CameraPickerDialog = ({
Cameras
;
+ playerCardRef: React.RefObject;
};
export const PlayerCard = ({
@@ -126,35 +127,33 @@ export const PlayerCard = ({
requestedTimestamp,
selectedTab,
hlsRef,
-}: PlayerCardProps) => {
- const ref = useRef(null);
- return (
-
- {camera && }
-
- {eventSource && selectedTab === "events" ? (
-
- ) : camera && requestedTimestamp && selectedTab === "timeline" ? (
-
- ) : (
-
- )}
-
-
- );
-};
+ playerCardRef,
+}: PlayerCardProps) => (
+ ({
+ marginBottom: theme.margin,
+ position: "relative",
+ })}
+ >
+ {camera && }
+
+ {eventSource && selectedTab === "events" ? (
+
+ ) : camera && requestedTimestamp && selectedTab === "timeline" ? (
+
+ ) : (
+
+ )}
+
+
+);
diff --git a/frontend/src/components/events/EventTable.tsx b/frontend/src/components/events/EventTable.tsx
index b86c7e423..dddb949b4 100644
--- a/frontend/src/components/events/EventTable.tsx
+++ b/frontend/src/components/events/EventTable.tsx
@@ -3,16 +3,35 @@ import Divider from "@mui/material/Divider";
import Grid from "@mui/material/Grid";
import Typography from "@mui/material/Typography";
import dayjs, { Dayjs } from "dayjs";
-import { memo } from "react";
+import { memo, useEffect } from "react";
+import { forceCheck } from "react-lazyload";
import ServerDown from "svg/undraw/server_down.svg?react";
import { ErrorMessage } from "components/error/ErrorMessage";
import { EventTableItem } from "components/events/EventTableItem";
import { Loading } from "components/loading/Loading";
import { useRecordings } from "lib/api/recordings";
+import { throttle } from "lib/helpers";
import * as types from "lib/types";
+const useOnScroll = (parentRef: React.RefObject) => {
+ useEffect(() => {
+ const container = parentRef.current;
+ if (!container) return () => {};
+
+ const throttleForceCheck = throttle(() => {
+ forceCheck();
+ }, 100);
+ container.addEventListener("scroll", throttleForceCheck);
+
+ return () => {
+ container.removeEventListener("scroll", throttleForceCheck);
+ };
+ });
+};
+
type EventTableProps = {
+ parentRef: React.RefObject;
camera: types.Camera | types.FailedCamera;
date: Dayjs | null;
selectedRecording: types.Recording | null;
@@ -21,6 +40,7 @@ type EventTableProps = {
export const EventTable = memo(
({
+ parentRef,
camera,
date,
selectedRecording,
@@ -34,6 +54,8 @@ export const EventTable = memo(
configOptions: { enabled: !!date },
});
+ useOnScroll(parentRef);
+
if (recordingsQuery.isError) {
return (
,
+ playerCardRef: React.RefObject | undefined,
+) => {
+ const theme = useTheme();
+ const resizeObserver = useRef();
+
+ useEffect(() => {
+ if (!divRef.current || !playerCardRef || !playerCardRef.current) {
+ return () => {};
+ }
+
+ resizeObserver.current = new ResizeObserver(() => {
+ if (!divRef.current || !playerCardRef.current) {
+ return;
+ }
+
+ divRef.current.style.maxHeight = `calc(${COLUMN_HEIGHT} - ${theme.headerHeight}px - ${theme.margin} - ${playerCardRef.current.offsetHeight}px)`;
+ });
+
+ resizeObserver.current.observe(playerCardRef.current);
+
+ return () => {
+ if (resizeObserver.current) {
+ resizeObserver.current.disconnect();
+ }
+ };
+ }, [divRef, playerCardRef, theme.headerHeight, theme.margin]);
+};
+
+type CameraGridProps = {
cameras: types.CamerasOrFailedCameras;
changeSelectedCamera: (
ev: React.MouseEvent,
@@ -13,14 +47,15 @@ type EventsCameraGridProps = {
) => void;
selectedCamera: types.Camera | types.FailedCamera | null;
};
-export function EventsCameraGrid({
+function CameraGrid({
cameras,
changeSelectedCamera,
selectedCamera,
-}: EventsCameraGridProps) {
+}: CameraGridProps) {
const theme = useTheme();
+
return (
-
+
{cameras
? Object.keys(cameras)
.sort()
@@ -54,3 +89,70 @@ export function EventsCameraGrid({
);
}
+
+type EventsCameraGridPropsCard = {
+ variant: "card";
+ playerCardRef: React.RefObject;
+ cameras: types.CamerasOrFailedCameras;
+ changeSelectedCamera: (
+ ev: React.MouseEvent,
+ camera: types.Camera | types.FailedCamera,
+ ) => void;
+ selectedCamera: types.Camera | types.FailedCamera | null;
+};
+type EventsCameraGridPropsGrid = {
+ variant?: "grid";
+ cameras: types.CamerasOrFailedCameras;
+ changeSelectedCamera: (
+ ev: React.MouseEvent,
+ camera: types.Camera | types.FailedCamera,
+ ) => void;
+ selectedCamera: types.Camera | types.FailedCamera | null;
+};
+type EventsCameraGridProps =
+ | EventsCameraGridPropsCard
+ | EventsCameraGridPropsGrid;
+export function EventsCameraGrid(props: EventsCameraGridProps) {
+ const {
+ variant = "card",
+ cameras,
+ changeSelectedCamera,
+ selectedCamera,
+ } = props;
+
+ const playerCardRef =
+ variant === "card"
+ ? (props as EventsCameraGridPropsCard).playerCardRef
+ : undefined;
+
+ const ref = useRef(null);
+ useResizeObserver(ref, playerCardRef);
+
+ if (variant === "grid") {
+ return (
+
+ );
+ }
+ return (
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/events/Layouts.tsx b/frontend/src/components/events/Layouts.tsx
index 072691169..58785e1bc 100644
--- a/frontend/src/components/events/Layouts.tsx
+++ b/frontend/src/components/events/Layouts.tsx
@@ -23,6 +23,7 @@ import { PlayerCard } from "components/events/EventPlayerCard";
import { EventTable } from "components/events/EventTable";
import { EventsCameraGrid } from "components/events/EventsCameraGrid";
import { TimelineTable } from "components/events/timeline/TimelineTable";
+import { COLUMN_HEIGHT } from "components/events/timeline/utils";
import { insertURLParameter } from "lib/helpers";
import * as types from "lib/types";
@@ -162,6 +163,7 @@ const Tabs = ({
{selectedCamera ? (
{
+ const theme = useTheme();
const parentRef = useRef(null);
const hlsRef = useRef(null);
+ const playerCardRef = useRef(null);
return (
({
+ sx={{
width: "650px",
- height: `calc(98dvh - ${theme.headerHeight}px)`,
+ height: `calc(${COLUMN_HEIGHT} - ${theme.headerHeight}px)`,
overflow: "auto",
overflowX: "hidden",
- })}
+ }}
>
(null);
const hlsRef = useRef(null);
+ const playerCardRef = useRef(null);
// Observe div height to calculate the height of the EventTable
const [height, setHeight] = useState();
@@ -331,6 +338,7 @@ export const LayoutSmall = memo(
requestedTimestamp={requestedTimestamp}
selectedTab={selectedTab}
hlsRef={hlsRef}
+ playerCardRef={playerCardRef}
/>
,
+ hlsClientIdRef: React.MutableRefObject,
requestedTimestamp: number,
camera: types.Camera | types.FailedCamera,
) => {
@@ -24,6 +25,7 @@ const loadSource = (
return;
}
const source = `/api/v1/hls/${camera.identifier}/index.m3u8?start_timestamp=${requestedTimestamp}&daily=true`;
+ hlsClientIdRef.current = uuidv4();
hlsRef.current.loadSource(source);
};
@@ -98,6 +100,7 @@ const onMediaAttached = (
const initializePlayer = (
hlsRef: React.MutableRefObject,
+ hlsClientIdRef: React.MutableRefObject,
videoRef: React.RefObject,
initialProgramDateTime: React.MutableRefObject,
auth: types.AuthEnabledResponse,
@@ -111,7 +114,6 @@ const initializePlayer = (
}
// Create a new hls instance
- const hlsClientId = uuidv4();
hlsRef.current = new Hls({
maxBufferLength: 30, // 30 seconds of forward buffer
backBufferLength: 15, // 15 seconds of back buffer
@@ -125,7 +127,7 @@ const initializePlayer = (
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.setRequestHeader("Authorization", `Bearer ${token}`);
}
- xhr.setRequestHeader("Hls-Client-Id", hlsClientId);
+ xhr.setRequestHeader("Hls-Client-Id", hlsClientIdRef.current);
}
},
});
@@ -135,7 +137,7 @@ const initializePlayer = (
}
// Load the source and start the hls instance
- loadSource(hlsRef, requestedTimestampRef.current, camera);
+ loadSource(hlsRef, hlsClientIdRef, requestedTimestampRef.current, camera);
// Handle MEDIA_ATTACHED event
hlsRef.current.on(Hls.Events.MEDIA_ATTACHED, () => {
@@ -160,6 +162,7 @@ const initializePlayer = (
default:
initializePlayer(
hlsRef,
+ hlsClientIdRef,
videoRef,
initialProgramDateTime,
auth,
@@ -174,6 +177,7 @@ const initializePlayer = (
const useInitializePlayer = (
hlsRef: React.MutableRefObject,
+ hlsClientIdRef: React.MutableRefObject,
videoRef: React.RefObject,
initialProgramDateTime: React.MutableRefObject,
requestedTimestampRef: React.MutableRefObject,
@@ -185,6 +189,7 @@ const useInitializePlayer = (
if (Hls.isSupported()) {
initializePlayer(
hlsRef,
+ hlsClientIdRef,
videoRef,
initialProgramDateTime,
auth,
@@ -207,6 +212,7 @@ const useInitializePlayer = (
// Seek to the requestedTimestamp if it is within the seekable range
const useSeekToTimestamp = (
hlsRef: React.MutableRefObject,
+ hlsClientIdRef: React.MutableRefObject,
videoRef: React.RefObject,
initialProgramDateTime: React.MutableRefObject,
requestedTimestamp: number,
@@ -251,10 +257,17 @@ const useSeekToTimestamp = (
}
if (!seeked) {
- loadSource(hlsRef, requestedTimestamp, camera);
+ loadSource(hlsRef, hlsClientIdRef, requestedTimestamp, camera);
}
videoRef.current.play();
- }, [camera, hlsRef, initialProgramDateTime, requestedTimestamp, videoRef]);
+ }, [
+ camera,
+ hlsClientIdRef,
+ hlsRef,
+ initialProgramDateTime,
+ requestedTimestamp,
+ videoRef,
+ ]);
};
const useResizeObserver = (
@@ -268,7 +281,8 @@ const useResizeObserver = (
if (containerRef.current) {
resizeObserver.current = new ResizeObserver(() => {
videoRef.current!.style.height = `${calculateHeight(
- camera,
+ camera.width,
+ camera.height,
containerRef.current!.offsetWidth,
)}px`;
});
@@ -295,11 +309,13 @@ export const TimelinePlayer: React.FC = ({
requestedTimestamp,
}) => {
const videoRef = useRef(null);
+ const hlsClientIdRef = useRef(uuidv4());
const initialProgramDateTime = useRef(null);
const requestedTimestampRef = useRef(requestedTimestamp);
requestedTimestampRef.current = requestedTimestamp;
useInitializePlayer(
hlsRef,
+ hlsClientIdRef,
videoRef,
initialProgramDateTime,
requestedTimestampRef,
@@ -307,6 +323,7 @@ export const TimelinePlayer: React.FC = ({
);
useSeekToTimestamp(
hlsRef,
+ hlsClientIdRef,
videoRef,
initialProgramDateTime,
requestedTimestamp,
@@ -321,7 +338,11 @@ export const TimelinePlayer: React.FC = ({
width: "100%",
verticalAlign: "top",
height: containerRef.current
- ? calculateHeight(camera, containerRef.current.offsetWidth)
+ ? calculateHeight(
+ camera.width,
+ camera.height,
+ containerRef.current.offsetWidth,
+ )
: undefined,
}}
controls
diff --git a/frontend/src/components/events/timeline/utils.tsx b/frontend/src/components/events/timeline/utils.tsx
index 1a2d69b6e..fd50ae71b 100644
--- a/frontend/src/components/events/timeline/utils.tsx
+++ b/frontend/src/components/events/timeline/utils.tsx
@@ -7,6 +7,7 @@ import * as types from "lib/types";
export const TICK_HEIGHT = 8;
export const SCALE = 60;
export const EXTRA_TICKS = 10;
+export const COLUMN_HEIGHT = "99dvh";
export const DEFAULT_ITEM: TimelineItem = {
time: 0,
timedEvent: null,
@@ -261,6 +262,7 @@ export const findFragmentByTimestamp = (
// Calculate the height of the camera while maintaining aspect ratio
export const calculateHeight = (
- camera: types.Camera | types.FailedCamera,
+ cameraWidth: number,
+ cameraHeight: number,
width: number,
-): number => (width * camera.height) / camera.width;
+): number => (width * cameraHeight) / cameraWidth;
diff --git a/frontend/src/components/header/Breadcrumbs.tsx b/frontend/src/components/header/Breadcrumbs.tsx
index 12847842b..0c88b07b3 100644
--- a/frontend/src/components/header/Breadcrumbs.tsx
+++ b/frontend/src/components/header/Breadcrumbs.tsx
@@ -2,6 +2,8 @@ import NavigateNextIcon from "@mui/icons-material/NavigateNext";
import MuiBreadcrumbs from "@mui/material/Breadcrumbs";
import Link from "@mui/material/Link";
import Typography from "@mui/material/Typography";
+import { useTheme } from "@mui/material/styles";
+import useMediaQuery from "@mui/material/useMediaQuery";
import { Link as RouterLink, useLocation } from "react-router-dom";
import queryClient from "lib/api/client";
@@ -9,10 +11,20 @@ import { toTitleCase } from "lib/helpers";
import * as types from "lib/types";
export default function Breadcrumbs() {
+ const theme = useTheme();
+ const mediaQueryMedium = useMediaQuery(theme.breakpoints.up("sm"));
const location = useLocation();
const pathnames = location.pathname.split("/").filter((x) => x);
if (pathnames.length === 0) {
- return null;
+ pathnames.push("cameras");
+ }
+
+ if (!mediaQueryMedium) {
+ return (
+
+ {toTitleCase(pathnames[0])}
+
+ );
}
return (
diff --git a/frontend/src/components/header/Header.tsx b/frontend/src/components/header/Header.tsx
index d742f362b..31fec64df 100644
--- a/frontend/src/components/header/Header.tsx
+++ b/frontend/src/components/header/Header.tsx
@@ -1,6 +1,5 @@
import Brightness4Icon from "@mui/icons-material/Brightness4";
import Brightness7Icon from "@mui/icons-material/Brightness7";
-import GitHubIcon from "@mui/icons-material/GitHub";
import LogoutIcon from "@mui/icons-material/Logout";
import MenuIcon from "@mui/icons-material/Menu";
import SettingsIcon from "@mui/icons-material/Settings";
@@ -11,7 +10,6 @@ import Stack from "@mui/material/Stack";
import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import { alpha, styled, useTheme } from "@mui/material/styles";
-import useMediaQuery from "@mui/material/useMediaQuery";
import { useContext, useRef, useState } from "react";
import { Link as RouterLink, useNavigate } from "react-router-dom";
import ViseronLogo from "svg/viseron-logo.svg?react";
@@ -48,7 +46,7 @@ const Header = styled("header", {
theme.palette.mode === "dark"
? alpha(theme.palette.background.default, 0.72)
: "rgba(255,255,255,0.72)",
- marginBottom: "10px",
+ marginBottom: theme.margin,
transform: showHeader ? "translateY(0)" : "translateY(-100%)",
transition: "transform 300ms ease-in",
}));
@@ -56,7 +54,6 @@ const Header = styled("header", {
export default function AppHeader({ setDrawerOpen }: AppHeaderProps) {
const colorMode = useContext(ColorModeContext);
const theme = useTheme();
- const mediaQueryMedium = useMediaQuery(theme.breakpoints.up("md"));
const [showHeader, setShowHeader] = useState(true);
const lastTogglePos = useRef(0);
const { auth } = useAuthContext();
@@ -101,39 +98,47 @@ export default function AppHeader({ setDrawerOpen }: AppHeaderProps) {
minHeight: theme.headerHeight,
}}
>
-
- {
- setDrawerOpen(true);
- }}
- >
-
-
-
-
-
-
-
-
- {mediaQueryMedium && }
-
-
-
+
+
{
+ setDrawerOpen(true);
+ }}
>
-
+
+
+
+
+
+
+
+
+
+
+
void, timeFrame: number) {
+ let lastTime = 0;
+ return () => {
+ const now = new Date().getTime();
+ if (now - lastTime >= timeFrame) {
+ func();
+ lastTime = now;
+ }
+ };
+}
diff --git a/frontend/src/pages/Cameras.tsx b/frontend/src/pages/Cameras.tsx
index a61a09a52..aa348b7ec 100644
--- a/frontend/src/pages/Cameras.tsx
+++ b/frontend/src/pages/Cameras.tsx
@@ -1,7 +1,6 @@
import Container from "@mui/material/Container";
import Grid from "@mui/material/Grid";
import Grow from "@mui/material/Grow";
-import Typography from "@mui/material/Typography";
import { CameraCard } from "components/camera/CameraCard";
import { FailedCameraCard } from "components/camera/FailedCameraCard";
@@ -30,10 +29,7 @@ const Cameras = () => {
return (
-
- Cameras
-
-
+
{failedCameras.data
? Object.keys(failedCameras.data)
.sort()
diff --git a/frontend/src/pages/recordings/CameraRecordings.tsx b/frontend/src/pages/recordings/CameraRecordings.tsx
index ab01b3e82..bc9885fcb 100644
--- a/frontend/src/pages/recordings/CameraRecordings.tsx
+++ b/frontend/src/pages/recordings/CameraRecordings.tsx
@@ -72,7 +72,7 @@ const CameraRecordings = () => {
{cameraQuery.data.name}
-
+
{Object.keys(recordingsQuery.data)
.sort()
.reverse()
diff --git a/frontend/src/pages/recordings/CameraRecordingsDaily.tsx b/frontend/src/pages/recordings/CameraRecordingsDaily.tsx
index c60c8fb9a..00d368dea 100644
--- a/frontend/src/pages/recordings/CameraRecordingsDaily.tsx
+++ b/frontend/src/pages/recordings/CameraRecordingsDaily.tsx
@@ -71,7 +71,7 @@ const CameraRecordingsDaily = () => {
{`${cameraQuery.data.name} - ${date}`}
-
+
{Object.keys(recordingsQuery.data[date])
.reverse()
.map((recording) => (
diff --git a/frontend/src/pages/recordings/Recordings.tsx b/frontend/src/pages/recordings/Recordings.tsx
index 726c1a885..b0b171cd8 100644
--- a/frontend/src/pages/recordings/Recordings.tsx
+++ b/frontend/src/pages/recordings/Recordings.tsx
@@ -1,7 +1,6 @@
import Container from "@mui/material/Container";
import Grid from "@mui/material/Grid";
import Grow from "@mui/material/Grow";
-import Typography from "@mui/material/Typography";
import { ScrollToTopOnMount } from "components/ScrollToTop";
import { Loading } from "components/loading/Loading";
@@ -49,10 +48,7 @@ const Recordings = () => {
return (
-
- Recordings
-
-
+
{failedCameras.data
? Object.keys(failedCameras.data)
.sort()
diff --git a/rootfs/etc/cont-finish.d/10-postgres b/rootfs/etc/cont-finish.d/10-postgres
new file mode 100644
index 000000000..a4b9429a7
--- /dev/null
+++ b/rootfs/etc/cont-finish.d/10-postgres
@@ -0,0 +1,23 @@
+#!/usr/bin/with-contenv bash
+#
+source /helpers/logger.sh
+
+log_info "Wait for Viseron to stop..."
+PID=$(pgrep -f ^viseron)
+while ps -p $PID > /dev/null; do
+ sleep 1
+done
+log_info "Viseron has stopped!"
+
+log_info "Stopping PostgreSQL..."
+s6-setuidgid postgres /usr/lib/postgresql/12/bin/pg_ctl -D /config/postgresql -l /config/postgresql/logfile stop
+
+# Wait until PostgreSQL has stopped
+log_info "Waiting for PostgreSQL Server to stop..."
+while s6-setuidgid abc pg_isready -d viseron; do
+ sleep 1
+done
+log_info "PostgreSQL Server has stopped!"
+
+# Restore permissions
+chown -R abc:abc /config/postgresql
diff --git a/rootfs/etc/cont-init.d/80-postgres b/rootfs/etc/cont-init.d/80-postgres
index 470c8565b..846c18d67 100644
--- a/rootfs/etc/cont-init.d/80-postgres
+++ b/rootfs/etc/cont-init.d/80-postgres
@@ -5,26 +5,25 @@ source /helpers/logger.sh
log_info "***************** Preparing PostgreSQL *******************"
mkdir -p /config/postgresql
mkdir -p /var/run/postgresql
-chown abc:abc /var/run/postgresql
-chown -R abc:abc /config/postgresql
+chown postgres:abc /var/run/postgresql
+chown -R postgres:abc /config/postgresql
chmod 0700 /config/postgresql
if [ -e /config/postgresql/postgresql.conf ]; then
log_info "Database has already been initialized."
else
log_info "Database has not been initialized. Initializing..."
- s6-setuidgid abc /usr/lib/postgresql/12/bin/initdb
+ s6-setuidgid postgres /usr/lib/postgresql/12/bin/initdb
log_info "Starting PostgreSQL..."
- s6-setuidgid abc /usr/lib/postgresql/12/bin/pg_ctl -D /config/postgresql -l /config/postgresql/logfile start
- if s6-setuidgid abc psql -q -d viseron -tc "SELECT 1 FROM pg_database WHERE datname = 'viseron';" ; then
+ s6-setuidgid postgres /usr/lib/postgresql/12/bin/pg_ctl -D /config/postgresql -l /config/postgresql/logfile start
+ if s6-setuidgid postgres psql -q -d viseron -tc "SELECT 1 FROM pg_database WHERE datname = 'viseron';" ; then
log_info "Database has already been created."
else
log_info "Database has not been created. Creating..."
- s6-setuidgid abc createuser viseron
- s6-setuidgid abc createdb -U abc -O viseron viseron
+ s6-setuidgid postgres createdb -U postgres -O postgres viseron
fi
log_info "Stopping PostgreSQL..."
- s6-setuidgid abc /usr/lib/postgresql/12/bin/pg_ctl -D /config/postgresql -l /config/postgresql/logfile stop
+ s6-setuidgid postgres /usr/lib/postgresql/12/bin/pg_ctl -D /config/postgresql -l /config/postgresql/logfile stop
fi
log_info "*********************** Done *****************************"
diff --git a/rootfs/etc/services.d/postgres/run b/rootfs/etc/services.d/postgres/run
index 3bec09543..258dd0bcf 100644
--- a/rootfs/etc/services.d/postgres/run
+++ b/rootfs/etc/services.d/postgres/run
@@ -1,4 +1,4 @@
#!/usr/bin/with-contenv bash
echo "Starting PostgreSQL Server..."
-s6-setuidgid abc /usr/lib/postgresql/12/bin/postgres -D /config/postgresql
+s6-setuidgid postgres /usr/lib/postgresql/12/bin/postgres -D /config/postgresql
diff --git a/rootfs/etc/services.d/viseron/run b/rootfs/etc/services.d/viseron/run
index 19197bffe..1c9b36c60 100644
--- a/rootfs/etc/services.d/viseron/run
+++ b/rootfs/etc/services.d/viseron/run
@@ -7,7 +7,7 @@ pkill -f ffmpeg_
pkill -f gstreamer_
# Wait until PostgreSQL is ready
-until s6-setuidgid abc pg_isready -d viseron; do
+until s6-setuidgid postgres pg_isready -d viseron; do
log_info "Waiting for PostgreSQL Server to start..."
sleep 1
done
diff --git a/viseron/components/storage/const.py b/viseron/components/storage/const.py
index d446eb851..f91e89b67 100644
--- a/viseron/components/storage/const.py
+++ b/viseron/components/storage/const.py
@@ -5,7 +5,7 @@
COMPONENT = "storage"
-DATABASE_URL = "postgresql://localhost/viseron"
+DATABASE_URL = "postgresql://postgres@localhost/viseron"
MOVE_FILES_THROTTLE_SECONDS = 10
diff --git a/viseron/domains/camera/fragmenter.py b/viseron/domains/camera/fragmenter.py
index e285e702f..ea292f8c3 100644
--- a/viseron/domains/camera/fragmenter.py
+++ b/viseron/domains/camera/fragmenter.py
@@ -19,7 +19,7 @@
from viseron.components.storage.const import COMPONENT as STORAGE_COMPONENT
from viseron.components.storage.models import FilesMeta
-from viseron.const import TEMP_DIR, VISERON_SIGNAL_SHUTDOWN
+from viseron.const import CAMERA_SEGMENT_DURATION, TEMP_DIR, VISERON_SIGNAL_SHUTDOWN
from viseron.helpers.logs import LogPipe
if TYPE_CHECKING:
@@ -145,18 +145,8 @@ def _move_to_segments_folder(self, file: str):
except FileNotFoundError:
self._logger.debug(f"{file} not found")
- def _write_files_metadata(self, file: str):
+ def _write_files_metadata(self, file: str, extinf: float):
"""Write metadata about the fragmented mp4 to the database."""
- extinf = _extract_extinf_number(
- open(
- os.path.join(
- self._camera.temp_segments_folder,
- file.split(".")[0],
- "master_1.m3u8",
- ),
- encoding="utf-8",
- ).read()
- )
with self._storage.get_session() as session:
orig_ctime = datetime.datetime.fromtimestamp(
int(file.split(".")[0]), tz=None
@@ -172,6 +162,16 @@ def _write_files_metadata(self, file: str):
session.execute(stmt)
session.commit()
+ def _read_m3u8(self, file: str) -> str:
+ return open(
+ os.path.join(
+ self._camera.temp_segments_folder,
+ file.split(".")[0],
+ "master_1.m3u8",
+ ),
+ encoding="utf-8",
+ ).read()
+
def _create_fragmented_mp4(self):
"""Create fragmented mp4 from mp4 using MP4Box."""
self._logger.debug("Checking for new segments to fragment")
@@ -180,7 +180,10 @@ def _create_fragmented_mp4(self):
self._logger.debug(f"Processing {mp4}")
try:
if self._mp4box_command(mp4):
- self._write_files_metadata(mp4)
+ extinf = _extract_extinf_number(self._read_m3u8(mp4))
+ self._write_files_metadata(
+ mp4, extinf if extinf else float(CAMERA_SEGMENT_DURATION)
+ )
self._move_to_segments_folder(mp4)
except Exception as err: # pylint: disable=broad-except
self._logger.error(f"Failed to fragment {mp4}", exc_info=err)