diff --git a/docker/Dockerfile b/docker/Dockerfile index 5d142567..ba46acf5 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 fc84a514..51aeee00 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 b86c7e42..dddb949b 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 07269116..58785e1b 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 1a2d69b6..fd50ae71 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 12847842..0c88b07b 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 d742f362..31fec64d 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 a61a09a5..aa348b7e 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 ab01b3e8..bc9885fc 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 c60c8fb9..00d368de 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 726c1a88..b0b171cd 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 00000000..a4b9429a --- /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 470c8565..846c18d6 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 3bec0954..258dd0bc 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 19197bff..1c9b36c6 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 d446eb85..f91e89b6 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 e285e702..ea292f8c 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)