Compare commits

...

10 Commits

Author SHA1 Message Date
Nicolas Mowen
573a5ede62
Various Improvements (#22597)
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* Filter out cmdline items that we are not interested in

* Add endpoint for easily pulling review clip
2026-03-23 16:49:41 -05:00
Nicolas Mowen
a8b6ea5005
Various Fixes (#22594)
* Fix handling of preview

* Fix schema cleaning

* Cleanup
2026-03-23 11:22:52 -05:00
Josh Hawkins
a89c7d8819
Add verbose mode to Media Sync (#22592)
* add verbose mode to media sync

writes a report to /config/media_sync showing all of the orphaned paths by media type

* frontend

* docs
2026-03-23 10:05:38 -06:00
Nicolas Mowen
5d67ba76fd
Improve hwaccel UI config (#22590)
* Add proper labels for hwaccel presets

* Filter presets based on hardware
2026-03-23 08:37:40 -06:00
Nicolas Mowen
2c9a25e678
Slightly downgrade CUDA version to fix overaggressive BFC (#22589)
* Downgrade CUDA to support 50 series without errors

* Update speed
2026-03-23 08:13:32 -06:00
dependabot[bot]
d8f599c377
Bump ajv from 6.12.6 to 6.14.0 in /web (#22587)
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.6 to 6.14.0.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.6...v6.14.0)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 6.14.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 09:09:50 -05:00
dependabot[bot]
5bd74666a7
Bump rollup from 4.34.9 to 4.59.0 in /web (#22139)
Bumps [rollup](https://github.com/rollup/rollup) from 4.34.9 to 4.59.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.34.9...v4.59.0)

---
updated-dependencies:
- dependency-name: rollup
  dependency-version: 4.59.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 08:59:46 -05:00
dependabot[bot]
c2b50af170
Bump flatted from 3.3.1 to 3.4.2 in /web (#22540)
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.1 to 3.4.2.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.1...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 08:57:48 -05:00
dependabot[bot]
ca2242dac2
Bump minimatch in /web (#22141)
Bumps  and [minimatch](https://github.com/isaacs/minimatch). These dependencies needed to be updated together.

Updates `minimatch` from 3.1.2 to 3.1.5
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)

Updates `minimatch` from 9.0.4 to 9.0.9
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)

Updates `minimatch` from 9.0.5 to 9.0.9
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
- dependency-name: minimatch
  dependency-version: 9.0.9
  dependency-type: indirect
- dependency-name: minimatch
  dependency-version: 9.0.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 08:57:17 -05:00
dependabot[bot]
4cbe124b61
Bump immutable from 5.1.4 to 5.1.5 in /docs (#22264)
Bumps [immutable](https://github.com/immutable-js/immutable-js) from 5.1.4 to 5.1.5.
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v5.1.4...v5.1.5)

---
updated-dependencies:
- dependency-name: immutable
  dependency-version: 5.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 08:56:52 -05:00
20 changed files with 434 additions and 172 deletions

View File

@ -1,18 +1,18 @@
# Nvidia ONNX Runtime GPU Support # Nvidia ONNX Runtime GPU Support
--extra-index-url 'https://pypi.nvidia.com' --extra-index-url 'https://pypi.nvidia.com'
cython==3.0.*; platform_machine == 'x86_64' cython==3.0.*; platform_machine == 'x86_64'
nvidia-cuda-cupti-cu12==12.9.79; platform_machine == 'x86_64' nvidia-cuda-cupti-cu12==12.8.90; platform_machine == 'x86_64'
nvidia-cublas-cu12==12.9.1.*; platform_machine == 'x86_64' nvidia-cublas-cu12==12.8.4.1; platform_machine == 'x86_64'
nvidia-cudnn-cu12==9.19.0.*; platform_machine == 'x86_64' nvidia-cudnn-cu12==9.8.0.87; platform_machine == 'x86_64'
nvidia-cufft-cu12==11.4.1.*; platform_machine == 'x86_64' nvidia-cufft-cu12==11.3.3.83; platform_machine == 'x86_64'
nvidia-curand-cu12==10.3.10.*; platform_machine == 'x86_64' nvidia-curand-cu12==10.3.9.90; platform_machine == 'x86_64'
nvidia-cuda-nvcc-cu12==12.9.86; platform_machine == 'x86_64' nvidia-cuda-nvcc-cu12==12.8.93; platform_machine == 'x86_64'
nvidia-cuda-nvrtc-cu12==12.9.86; platform_machine == 'x86_64' nvidia-cuda-nvrtc-cu12==12.8.93; platform_machine == 'x86_64'
nvidia-cuda-runtime-cu12==12.9.79; platform_machine == 'x86_64' nvidia-cuda-runtime-cu12==12.8.90; platform_machine == 'x86_64'
nvidia-cusolver-cu12==11.7.5.*; platform_machine == 'x86_64' nvidia-cusolver-cu12==11.7.3.90; platform_machine == 'x86_64'
nvidia-cusparse-cu12==12.5.10.*; platform_machine == 'x86_64' nvidia-cusparse-cu12==12.5.8.93; platform_machine == 'x86_64'
nvidia-nccl-cu12==2.29.7; platform_machine == 'x86_64' nvidia-nccl-cu12==2.26.2.post1; platform_machine == 'x86_64'
nvidia-nvjitlink-cu12==12.9.86; platform_machine == 'x86_64' nvidia-nvjitlink-cu12==12.8.93; platform_machine == 'x86_64'
onnx==1.16.*; platform_machine == 'x86_64' onnx==1.16.*; platform_machine == 'x86_64'
onnxruntime-gpu==1.24.*; platform_machine == 'x86_64' onnxruntime-gpu==1.24.*; platform_machine == 'x86_64'
protobuf==3.20.3; platform_machine == 'x86_64' protobuf==3.20.3; platform_machine == 'x86_64'

View File

@ -162,6 +162,8 @@ Normal operation may leave small numbers of orphaned files until Frigate's sched
The Maintenance pane in the Frigate UI or an API endpoint `POST /api/media/sync` can be used to trigger a media sync. When using the API, a job ID is returned and the operation continues on the server. Status can be checked with the `/api/media/sync/status/{job_id}` endpoint. The Maintenance pane in the Frigate UI or an API endpoint `POST /api/media/sync` can be used to trigger a media sync. When using the API, a job ID is returned and the operation continues on the server. Status can be checked with the `/api/media/sync/status/{job_id}` endpoint.
Setting `verbose: true` writes a detailed report of every orphaned file and database entry to `/config/media_sync/<job_id>.txt`. For recordings, the report separates orphaned database entries (DB records whose files are missing from disk) from orphaned files (files on disk with no corresponding database record).
:::warning :::warning
This operation uses considerable CPU resources and includes a safety threshold that aborts if more than 50% of files would be deleted. Only run when necessary. If you set `force: true` the safety threshold will be bypassed; do not use `force` unless you are certain the deletions are intended. This operation uses considerable CPU resources and includes a safety threshold that aborts if more than 50% of files would be deleted. Only run when necessary. If you set `force: true` the safety threshold will be bypassed; do not use `force` unless you are certain the deletions are intended.

View File

@ -205,7 +205,7 @@ Inference is done with the `onnx` detector type. Speeds will vary greatly depend
| GTX 1070 | s-320: 16 ms | | 320: 14 ms | | GTX 1070 | s-320: 16 ms | | 320: 14 ms |
| RTX 3050 | t-320: 8 ms s-320: 10 ms s-640: 28 ms | Nano-320: ~ 12 ms | 320: ~ 10 ms 640: ~ 16 ms | | RTX 3050 | t-320: 8 ms s-320: 10 ms s-640: 28 ms | Nano-320: ~ 12 ms | 320: ~ 10 ms 640: ~ 16 ms |
| RTX 3070 | t-320: 6 ms s-320: 8 ms s-640: 25 ms | Nano-320: ~ 9 ms | 320: ~ 8 ms 640: ~ 14 ms | | RTX 3070 | t-320: 6 ms s-320: 8 ms s-640: 25 ms | Nano-320: ~ 9 ms | 320: ~ 8 ms 640: ~ 14 ms |
| RTX 5060 Ti | t-320: 5 ms s-320: 7 ms s-640: 22 ms | Nano-320: ~ 6 ms | | | RTX 5060 Ti | t-320: 5 ms s-320: 7 ms s-640: 22 ms | Nano-320: ~ 4 ms | |
| RTX A4000 | | | 320: ~ 15 ms | | RTX A4000 | | | 320: ~ 15 ms |
| Tesla P40 | | | 320: ~ 105 ms | | Tesla P40 | | | 320: ~ 105 ms |

View File

@ -12313,9 +12313,9 @@
} }
}, },
"node_modules/immutable": { "node_modules/immutable": {
"version": "5.1.4", "version": "5.1.5",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/import-fresh": { "node_modules/import-fresh": {

View File

@ -6891,6 +6891,11 @@ components:
title: Force title: Force
description: "If True, bypass safety threshold checks" description: "If True, bypass safety threshold checks"
default: false default: false
verbose:
type: boolean
title: Verbose
description: "If True, write full orphan file list to /config/media_sync/<job_id>.txt"
default: false
type: object type: object
title: MediaSyncBody title: MediaSyncBody
MotionSearchMetricsResponse: MotionSearchMetricsResponse:

View File

@ -5,6 +5,7 @@ import copy
import json import json
import logging import logging
import os import os
import platform
import traceback import traceback
import urllib import urllib
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -246,19 +247,26 @@ def get_active_profile(request: Request):
@router.get("/ffmpeg/presets", dependencies=[Depends(allow_any_authenticated())]) @router.get("/ffmpeg/presets", dependencies=[Depends(allow_any_authenticated())])
def ffmpeg_presets(): def ffmpeg_presets():
"""Return available ffmpeg preset keys for config UI usage.""" """Return available ffmpeg preset keys for config UI usage."""
machine = platform.machine().lower()
is_arm64 = machine in ("aarch64", "arm64", "armv8", "armv7l")
if is_arm64:
hwaccel_presets = [
"preset-rpi-64-h264",
"preset-rpi-64-h265",
"preset-jetson-h264",
"preset-jetson-h265",
"preset-rkmpp",
"preset-vaapi",
]
else:
hwaccel_presets = [
"preset-vaapi",
"preset-intel-qsv-h264",
"preset-intel-qsv-h265",
"preset-nvidia",
]
# Whitelist based on documented presets in ffmpeg_presets.md
hwaccel_presets = [
"preset-rpi-64-h264",
"preset-rpi-64-h265",
"preset-vaapi",
"preset-intel-qsv-h264",
"preset-intel-qsv-h265",
"preset-nvidia",
"preset-jetson-h264",
"preset-jetson-h265",
"preset-rkmpp",
]
input_presets = [ input_presets = [
"preset-http-jpeg-generic", "preset-http-jpeg-generic",
"preset-http-mjpeg-generic", "preset-http-mjpeg-generic",
@ -864,7 +872,10 @@ def sync_media(body: MediaSyncBody = Body(...)):
202 Accepted with job_id, or 409 Conflict if job already running. 202 Accepted with job_id, or 409 Conflict if job already running.
""" """
job_id = start_media_sync_job( job_id = start_media_sync_job(
dry_run=body.dry_run, media_types=body.media_types, force=body.force dry_run=body.dry_run,
media_types=body.media_types,
force=body.force,
verbose=body.verbose,
) )
if job_id is None: if job_id is None:

View File

@ -45,3 +45,7 @@ class MediaSyncBody(BaseModel):
force: bool = Field( force: bool = Field(
default=False, description="If True, bypass safety threshold checks" default=False, description="If True, bypass safety threshold checks"
) )
verbose: bool = Field(
default=False,
description="If True, write full orphan file list to disk",
)

View File

@ -1227,6 +1227,33 @@ async def event_clip(
) )
@router.get(
"/review/{review_id}/clip.mp4",
)
async def review_clip(
request: Request,
review_id: str,
padding: int = Query(0, description="Padding to apply to clip."),
):
try:
review: ReviewSegment = ReviewSegment.get(ReviewSegment.id == review_id)
except DoesNotExist:
return JSONResponse(
content={"success": False, "message": "Review not found"}, status_code=404
)
await require_camera_access(review.camera, request=request)
end_ts = (
datetime.now().timestamp()
if review.end_time is None
else review.end_time + padding
)
return await recording_clip(
request, review.camera, review.start_time - padding, end_ts
)
@router.get( @router.get(
"/events/{event_id}/preview.gif", "/events/{event_id}/preview.gif",
) )
@ -1337,9 +1364,9 @@ def preview_gif(
status_code=404, status_code=404,
) )
file_start = f"preview_{camera_name}" file_start = f"preview_{camera_name}-"
start_file = f"{file_start}-{start_ts}.{PREVIEW_FRAME_TYPE}" start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}"
end_file = f"{file_start}-{end_ts}.{PREVIEW_FRAME_TYPE}" end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}"
selected_previews = [] selected_previews = []
for file in sorted(os.listdir(preview_dir)): for file in sorted(os.listdir(preview_dir)):
@ -1519,9 +1546,9 @@ def preview_mp4(
status_code=404, status_code=404,
) )
file_start = f"preview_{camera_name}" file_start = f"preview_{camera_name}-"
start_file = f"{file_start}-{start_ts}.{PREVIEW_FRAME_TYPE}" start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}"
end_file = f"{file_start}-{end_ts}.{PREVIEW_FRAME_TYPE}" end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}"
selected_previews = [] selected_previews = []
for file in sorted(os.listdir(preview_dir)): for file in sorted(os.listdir(preview_dir)):

View File

@ -145,9 +145,9 @@ def preview_hour(
def get_preview_frames_from_cache(camera_name: str, start_ts: float, end_ts: float): def get_preview_frames_from_cache(camera_name: str, start_ts: float, end_ts: float):
"""Get list of cached preview frames""" """Get list of cached preview frames"""
preview_dir = os.path.join(CACHE_DIR, "preview_frames") preview_dir = os.path.join(CACHE_DIR, "preview_frames")
file_start = f"preview_{camera_name}" file_start = f"preview_{camera_name}-"
start_file = f"{file_start}-{start_ts}.{PREVIEW_FRAME_TYPE}" start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}"
end_file = f"{file_start}-{end_ts}.{PREVIEW_FRAME_TYPE}" end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}"
selected_previews = [] selected_previews = []
for file in sorted(os.listdir(preview_dir)): for file in sorted(os.listdir(preview_dir)):

View File

@ -324,9 +324,9 @@ class ReviewDescriptionProcessor(PostProcessorApi):
end_time: float, end_time: float,
) -> list[str]: ) -> list[str]:
preview_dir = os.path.join(CACHE_DIR, "preview_frames") preview_dir = os.path.join(CACHE_DIR, "preview_frames")
file_start = f"preview_{camera}" file_start = f"preview_{camera}-"
start_file = f"{file_start}-{start_time}.webp" start_file = f"{file_start}{start_time}.webp"
end_file = f"{file_start}-{end_time}.webp" end_file = f"{file_start}{end_time}.webp"
all_frames = [] all_frames = []
for file in sorted(os.listdir(preview_dir)): for file in sorted(os.listdir(preview_dir)):

View File

@ -54,12 +54,16 @@ class OllamaClient(GenAIClient):
return None return None
@staticmethod @staticmethod
def _clean_schema_for_ollama(schema: dict) -> dict: def _clean_schema_for_ollama(schema: dict, *, _is_properties: bool = False) -> dict:
"""Strip Pydantic metadata from a JSON schema for Ollama compatibility. """Strip Pydantic metadata from a JSON schema for Ollama compatibility.
Ollama's grammar-based constrained generation works best with minimal Ollama's grammar-based constrained generation works best with minimal
schemas. Pydantic adds title/description/constraint fields that can schemas. Pydantic adds title/description/constraint fields that can
cause the grammar generator to silently skip required fields. cause the grammar generator to silently skip required fields.
Keys inside a ``properties`` dict are actual field names and must never
be stripped, even if they collide with a metadata key name (e.g. a
model field called ``title``).
""" """
STRIP_KEYS = { STRIP_KEYS = {
"title", "title",
@ -71,10 +75,12 @@ class OllamaClient(GenAIClient):
} }
result = {} result = {}
for key, value in schema.items(): for key, value in schema.items():
if key in STRIP_KEYS: if not _is_properties and key in STRIP_KEYS:
continue continue
if isinstance(value, dict): if isinstance(value, dict):
result[key] = OllamaClient._clean_schema_for_ollama(value) result[key] = OllamaClient._clean_schema_for_ollama(
value, _is_properties=(key == "properties")
)
elif isinstance(value, list): elif isinstance(value, list):
result[key] = [ result[key] = [
OllamaClient._clean_schema_for_ollama(item) OllamaClient._clean_schema_for_ollama(item)

View File

@ -1,13 +1,14 @@
"""Media sync job management with background execution.""" """Media sync job management with background execution."""
import logging import logging
import os
import threading import threading
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
from frigate.comms.inter_process import InterProcessRequestor from frigate.comms.inter_process import InterProcessRequestor
from frigate.const import UPDATE_JOB_STATE from frigate.const import CONFIG_DIR, UPDATE_JOB_STATE
from frigate.jobs.job import Job from frigate.jobs.job import Job
from frigate.jobs.manager import ( from frigate.jobs.manager import (
get_current_job, get_current_job,
@ -16,7 +17,7 @@ from frigate.jobs.manager import (
set_current_job, set_current_job,
) )
from frigate.types import JobStatusTypesEnum from frigate.types import JobStatusTypesEnum
from frigate.util.media import sync_all_media from frigate.util.media import sync_all_media, write_orphan_report
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -29,6 +30,7 @@ class MediaSyncJob(Job):
dry_run: bool = False dry_run: bool = False
media_types: list[str] = field(default_factory=lambda: ["all"]) media_types: list[str] = field(default_factory=lambda: ["all"])
force: bool = False force: bool = False
verbose: bool = False
class MediaSyncRunner(threading.Thread): class MediaSyncRunner(threading.Thread):
@ -61,6 +63,21 @@ class MediaSyncRunner(threading.Thread):
force=self.job.force, force=self.job.force,
) )
# Write verbose report if requested
if self.job.verbose:
report_dir = os.path.join(CONFIG_DIR, "media_sync")
os.makedirs(report_dir, exist_ok=True)
report_path = os.path.join(report_dir, f"{self.job.id}.txt")
write_orphan_report(
results,
report_path,
job_id=self.job.id,
dry_run=self.job.dry_run,
)
logger.info(
"Media sync verbose orphan report written to %s", report_path
)
# Store results and mark as complete # Store results and mark as complete
self.job.results = results.to_dict() self.job.results = results.to_dict()
self.job.status = JobStatusTypesEnum.success self.job.status = JobStatusTypesEnum.success
@ -95,6 +112,7 @@ def start_media_sync_job(
dry_run: bool = False, dry_run: bool = False,
media_types: Optional[list[str]] = None, media_types: Optional[list[str]] = None,
force: bool = False, force: bool = False,
verbose: bool = False,
) -> Optional[str]: ) -> Optional[str]:
"""Start a new media sync job if none is currently running. """Start a new media sync job if none is currently running.
@ -113,6 +131,7 @@ def start_media_sync_job(
dry_run=dry_run, dry_run=dry_run,
media_types=media_types or ["all"], media_types=media_types or ["all"],
force=force, force=force,
verbose=verbose,
) )
logger.debug(f"Creating new media sync job: {job.id}") logger.debug(f"Creating new media sync job: {job.id}")

View File

@ -266,8 +266,8 @@ class PreviewRecorder:
.timestamp() .timestamp()
) )
file_start = f"preview_{config.name}" file_start = f"preview_{config.name}-"
start_file = f"{file_start}-{start_ts}.webp" start_file = f"{file_start}{start_ts}.webp"
for file in sorted(os.listdir(os.path.join(CACHE_DIR, FOLDER_PREVIEW_FRAMES))): for file in sorted(os.listdir(os.path.join(CACHE_DIR, FOLDER_PREVIEW_FRAMES))):
if not file.startswith(file_start): if not file.startswith(file_start):

View File

@ -156,9 +156,9 @@ class RecordingExporter(threading.Thread):
else: else:
# need to generate from existing images # need to generate from existing images
preview_dir = os.path.join(CACHE_DIR, "preview_frames") preview_dir = os.path.join(CACHE_DIR, "preview_frames")
file_start = f"preview_{self.camera}" file_start = f"preview_{self.camera}-"
start_file = f"{file_start}-{self.start_time}.{PREVIEW_FRAME_TYPE}" start_file = f"{file_start}{self.start_time}.{PREVIEW_FRAME_TYPE}"
end_file = f"{file_start}-{self.end_time}.{PREVIEW_FRAME_TYPE}" end_file = f"{file_start}{self.end_time}.{PREVIEW_FRAME_TYPE}"
selected_preview = None selected_preview = None
for file in sorted(os.listdir(preview_dir)): for file in sorted(os.listdir(preview_dir)):
@ -264,9 +264,9 @@ class RecordingExporter(threading.Thread):
if is_current_hour(self.start_time): if is_current_hour(self.start_time):
# get list of current preview frames # get list of current preview frames
preview_dir = os.path.join(CACHE_DIR, "preview_frames") preview_dir = os.path.join(CACHE_DIR, "preview_frames")
file_start = f"preview_{self.camera}" file_start = f"preview_{self.camera}-"
start_file = f"{file_start}-{self.start_time}.{PREVIEW_FRAME_TYPE}" start_file = f"{file_start}{self.start_time}.{PREVIEW_FRAME_TYPE}"
end_file = f"{file_start}-{self.end_time}.{PREVIEW_FRAME_TYPE}" end_file = f"{file_start}{self.end_time}.{PREVIEW_FRAME_TYPE}"
for file in sorted(os.listdir(preview_dir)): for file in sorted(os.listdir(preview_dir)):
if not file.startswith(file_start): if not file.startswith(file_start):

View File

@ -49,6 +49,7 @@ class SyncResult:
orphans_found: int = 0 orphans_found: int = 0
orphans_deleted: int = 0 orphans_deleted: int = 0
orphan_paths: list[str] = field(default_factory=list) orphan_paths: list[str] = field(default_factory=list)
orphan_db_paths: list[str] = field(default_factory=list)
aborted: bool = False aborted: bool = False
error: str | None = None error: str | None = None
@ -132,7 +133,7 @@ def sync_recordings(
) )
result.orphans_found += len(recordings_to_delete) result.orphans_found += len(recordings_to_delete)
result.orphan_paths.extend( result.orphan_db_paths.extend(
[ [
recording["path"] recording["path"]
for recording in recordings_to_delete for recording in recordings_to_delete
@ -773,6 +774,61 @@ class MediaSyncResults:
return results return results
def write_orphan_report(
results: "MediaSyncResults",
path: str,
job_id: str = "",
dry_run: bool = False,
) -> None:
"""Write a verbose orphan report file listing all orphan paths by media type.
Args:
results: The completed MediaSyncResults.
path: File path to write the report to.
job_id: Job ID for the report header.
dry_run: Whether the sync was a dry run, for the report header.
"""
try:
with open(path, "w") as f:
f.write("# Media Sync Orphan Report\n")
f.write(f"# Job: {job_id}\n")
f.write(
f"# Date: {datetime.datetime.now().astimezone(datetime.timezone.utc).isoformat()}\n"
)
f.write(f"# Mode: dry_run={dry_run}\n\n")
for name, result in [
("recordings", results.recordings),
("event_snapshots", results.event_snapshots),
("event_thumbnails", results.event_thumbnails),
("review_thumbnails", results.review_thumbnails),
("previews", results.previews),
("exports", results.exports),
]:
if result is None:
continue
if result.orphan_db_paths:
f.write(
f"## {name} - orphaned db entries ({len(result.orphan_db_paths)})\n"
)
for orphan_path in result.orphan_db_paths:
f.write(f"{orphan_path}\n")
f.write("\n")
if result.orphan_paths:
f.write(
f"## {name} - orphaned files ({len(result.orphan_paths)})\n"
)
for orphan_path in result.orphan_paths:
f.write(f"{orphan_path}\n")
f.write("\n")
logger.debug("Wrote verbose orphan report to %s", path)
except OSError as e:
logger.error("Failed to write orphan report to %s: %s", path, e)
def sync_all_media( def sync_all_media(
dry_run: bool = False, media_types: list[str] = ["all"], force: bool = False dry_run: bool = False, media_types: list[str] = ["all"], force: bool = False
) -> MediaSyncResults: ) -> MediaSyncResults:

View File

@ -117,12 +117,16 @@ def get_cpu_stats() -> dict[str, dict]:
"mem": str(system_mem.percent), "mem": str(system_mem.percent),
} }
keywords = ["ffmpeg", "go2rtc", "frigate.", "python3"]
for process in psutil.process_iter(["pid", "name", "cpu_percent", "cmdline"]): for process in psutil.process_iter(["pid", "name", "cpu_percent", "cmdline"]):
pid = str(process.info["pid"]) pid = str(process.info["pid"])
try: try:
cpu_percent = process.info["cpu_percent"] cpu_percent = process.info["cpu_percent"]
cmdline = " ".join(process.info["cmdline"]).rstrip() cmdline = " ".join(process.info["cmdline"]).rstrip()
if not any(keyword in cmdline for keyword in keywords):
continue
with open(f"/proc/{pid}/stat", "r") as f: with open(f"/proc/{pid}/stat", "r") as f:
stats = f.readline().split() stats = f.readline().split()
utime = int(stats[13]) utime = int(stats[13])

319
web/package-lock.json generated
View File

@ -4776,9 +4776,9 @@
} }
}, },
"node_modules/@rjsf/validator-ajv8/node_modules/ajv": { "node_modules/@rjsf/validator-ajv8/node_modules/ajv": {
"version": "8.17.1", "version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@ -4798,9 +4798,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz",
"integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==", "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -4812,9 +4812,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz",
"integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==", "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4826,9 +4826,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz",
"integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4840,9 +4840,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz",
"integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4854,9 +4854,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz",
"integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==", "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4868,9 +4868,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz",
"integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==", "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4882,9 +4882,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz",
"integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==", "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -4896,9 +4896,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz",
"integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==", "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -4910,9 +4910,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz",
"integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4924,9 +4924,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz",
"integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4937,10 +4937,10 @@
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-loongarch64-gnu": { "node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz",
"integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==", "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@ -4951,10 +4951,38 @@
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz",
"integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==", "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz",
"integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz",
"integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -4966,9 +4994,23 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz",
"integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==", "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz",
"integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -4980,9 +5022,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz",
"integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==", "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -4994,9 +5036,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz",
"integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -5008,9 +5050,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz",
"integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -5021,10 +5063,38 @@
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz",
"integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz",
"integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz",
"integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -5036,9 +5106,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz",
"integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==", "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -5049,10 +5119,24 @@
"win32" "win32"
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz",
"integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz",
"integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -5387,9 +5471,9 @@
} }
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.6", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/estree-jsx": { "node_modules/@types/estree-jsx": {
@ -5677,9 +5761,9 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -5687,13 +5771,13 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.4", "version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.2"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
@ -5928,9 +6012,9 @@
} }
}, },
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -5962,9 +6046,9 @@
} }
}, },
"node_modules/ajv-formats/node_modules/ajv": { "node_modules/ajv-formats/node_modules/ajv": {
"version": "8.17.1", "version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@ -7779,9 +7863,9 @@
} }
}, },
"node_modules/flatted": { "node_modules/flatted": {
"version": "3.3.1", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@ -10410,9 +10494,10 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
}, },
@ -12222,13 +12307,13 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.34.9", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz",
"integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==", "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/estree": "1.0.6" "@types/estree": "1.0.8"
}, },
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
@ -12238,25 +12323,31 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.34.9", "@rollup/rollup-android-arm-eabi": "4.60.0",
"@rollup/rollup-android-arm64": "4.34.9", "@rollup/rollup-android-arm64": "4.60.0",
"@rollup/rollup-darwin-arm64": "4.34.9", "@rollup/rollup-darwin-arm64": "4.60.0",
"@rollup/rollup-darwin-x64": "4.34.9", "@rollup/rollup-darwin-x64": "4.60.0",
"@rollup/rollup-freebsd-arm64": "4.34.9", "@rollup/rollup-freebsd-arm64": "4.60.0",
"@rollup/rollup-freebsd-x64": "4.34.9", "@rollup/rollup-freebsd-x64": "4.60.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.34.9", "@rollup/rollup-linux-arm-gnueabihf": "4.60.0",
"@rollup/rollup-linux-arm-musleabihf": "4.34.9", "@rollup/rollup-linux-arm-musleabihf": "4.60.0",
"@rollup/rollup-linux-arm64-gnu": "4.34.9", "@rollup/rollup-linux-arm64-gnu": "4.60.0",
"@rollup/rollup-linux-arm64-musl": "4.34.9", "@rollup/rollup-linux-arm64-musl": "4.60.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.34.9", "@rollup/rollup-linux-loong64-gnu": "4.60.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.9", "@rollup/rollup-linux-loong64-musl": "4.60.0",
"@rollup/rollup-linux-riscv64-gnu": "4.34.9", "@rollup/rollup-linux-ppc64-gnu": "4.60.0",
"@rollup/rollup-linux-s390x-gnu": "4.34.9", "@rollup/rollup-linux-ppc64-musl": "4.60.0",
"@rollup/rollup-linux-x64-gnu": "4.34.9", "@rollup/rollup-linux-riscv64-gnu": "4.60.0",
"@rollup/rollup-linux-x64-musl": "4.34.9", "@rollup/rollup-linux-riscv64-musl": "4.60.0",
"@rollup/rollup-win32-arm64-msvc": "4.34.9", "@rollup/rollup-linux-s390x-gnu": "4.60.0",
"@rollup/rollup-win32-ia32-msvc": "4.34.9", "@rollup/rollup-linux-x64-gnu": "4.60.0",
"@rollup/rollup-win32-x64-msvc": "4.34.9", "@rollup/rollup-linux-x64-musl": "4.60.0",
"@rollup/rollup-openbsd-x64": "4.60.0",
"@rollup/rollup-openharmony-arm64": "4.60.0",
"@rollup/rollup-win32-arm64-msvc": "4.60.0",
"@rollup/rollup-win32-ia32-msvc": "4.60.0",
"@rollup/rollup-win32-x64-gnu": "4.60.0",
"@rollup/rollup-win32-x64-msvc": "4.60.0",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@ -13033,9 +13124,9 @@
} }
}, },
"node_modules/test-exclude/node_modules/brace-expansion": { "node_modules/test-exclude/node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -13064,13 +13155,13 @@
} }
}, },
"node_modules/test-exclude/node_modules/minimatch": { "node_modules/test-exclude/node_modules/minimatch": {
"version": "9.0.5", "version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.2"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"

View File

@ -1253,6 +1253,8 @@
"dryRunDisabled": "Files will be deleted", "dryRunDisabled": "Files will be deleted",
"force": "Force", "force": "Force",
"forceDesc": "Bypass safety threshold and complete sync even if more than 50% of the files would be deleted.", "forceDesc": "Bypass safety threshold and complete sync even if more than 50% of the files would be deleted.",
"verbose": "Verbose",
"verboseDesc": "Write a full list of orphaned files to disk for review.",
"running": "Sync Running...", "running": "Sync Running...",
"start": "Start Sync", "start": "Start Sync",
"inProgress": "Sync is in progress. This page is disabled.", "inProgress": "Sync is in progress. This page is disabled.",
@ -1330,7 +1332,18 @@
"none": "None", "none": "None",
"useGlobalSetting": "Inherit from global setting", "useGlobalSetting": "Inherit from global setting",
"selectPreset": "Select preset", "selectPreset": "Select preset",
"manualPlaceholder": "Enter FFmpeg arguments" "manualPlaceholder": "Enter FFmpeg arguments",
"presetLabels": {
"preset-rpi-64-h264": "Raspberry Pi (H.264)",
"preset-rpi-64-h265": "Raspberry Pi (H.265)",
"preset-vaapi": "VAAPI (Intel/AMD GPU)",
"preset-intel-qsv-h264": "Intel QuickSync (H.264)",
"preset-intel-qsv-h265": "Intel QuickSync (H.265)",
"preset-nvidia": "NVIDIA GPU",
"preset-jetson-h264": "NVIDIA Jetson (H.264)",
"preset-jetson-h265": "NVIDIA Jetson (H.265)",
"preset-rkmpp": "Rockchip RKMPP"
}
}, },
"cameraInputs": { "cameraInputs": {
"itemTitle": "Stream {{index}}" "itemTitle": "Stream {{index}}"

View File

@ -405,7 +405,10 @@ export function FfmpegArgsWidget(props: WidgetProps) {
<SelectContent> <SelectContent>
{presetOptions.map((preset) => ( {presetOptions.map((preset) => (
<SelectItem key={preset} value={preset}> <SelectItem key={preset} value={preset}>
{preset} {t(`configForm.ffmpegArgs.presetLabels.${preset}`, {
ns: "views/settings",
defaultValue: preset,
})}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>

View File

@ -22,6 +22,7 @@ export default function MediaSyncSettingsView() {
]); ]);
const [dryRun, setDryRun] = useState(true); const [dryRun, setDryRun] = useState(true);
const [force, setForce] = useState(false); const [force, setForce] = useState(false);
const [verbose, setVerbose] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const MEDIA_TYPES = [ const MEDIA_TYPES = [
@ -67,6 +68,7 @@ export default function MediaSyncSettingsView() {
dry_run: dryRun, dry_run: dryRun,
media_types: selectedMediaTypes, media_types: selectedMediaTypes,
force: force, force: force,
verbose: verbose,
}, },
{ {
headers: { headers: {
@ -94,7 +96,7 @@ export default function MediaSyncSettingsView() {
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }
}, [selectedMediaTypes, dryRun, force, t]); }, [selectedMediaTypes, dryRun, force, verbose, t]);
return ( return (
<> <>
@ -205,6 +207,25 @@ export default function MediaSyncSettingsView() {
/> />
</div> </div>
</div> </div>
<div className="flex flex-col">
<div className="flex flex-row items-center justify-between gap-4">
<div className="space-y-0.5">
<Label htmlFor="verbose" className="cursor-pointer">
{t("maintenance.sync.verbose")}
</Label>
<p className="text-xs text-muted-foreground">
{t("maintenance.sync.verboseDesc")}
</p>
</div>
<Switch
id="verbose"
checked={verbose}
onCheckedChange={setVerbose}
disabled={isJobRunning}
/>
</div>
</div>
</div> </div>
{/* Action Buttons */} {/* Action Buttons */}