Hướng dẫn sử dụng

Cấu hình Camera Station & chạy test

1Cấu hình RTSP Templates

Các mẫu URL RTSP được định nghĩa trong Backend/appsettings.json, mục RtspConfig.Templates. Mỗi template có hai trường:

  • UrlTemplate – mẫu URL với placeholder {creds}, {host}, {track}, {start}, {end}
  • TimeFormat – định dạng thời gian theo chuẩn .NET (ví dụ: yyyyMMddTHHmmss, yyyy_MM_dd_HH_mm_ss)
Backend/appsettings.json
{
  "RtspConfig": {
    "Templates": {
      "[ARYA]": {
        "UrlTemplate": "rtsp://{creds}@{host}/cam/playback?channel={track}&subtype=0&starttime={start}&endtime={end}",
        "TimeFormat":  "yyyy_MM_dd_HH_mm_ss"
      },
      "[HIK_OLD]": {
        "UrlTemplate": "rtsp://{creds}@{host}/Streaming/tracks/{track}/?starttime={start}&endtime={end}&size=1064413088&rw_timeout=50000000",
        "TimeFormat":  "yyyyMMddTHHmmss"
      },
      "DEFAULT": {
        "UrlTemplate": "rtsp://{creds}@{host}/Streaming/tracks/{track}/?starttime={start}&endtime={end}&size=1064413088&rw_timeout=50000000",
        "TimeFormat":  "yyyyMMddTHHmmss"
      }
    }
  }
}
Để thêm loại đầu ghi mới, chỉ cần thêm một entry mới vào Templates với key là tag (ví dụ [DAHUA]), không cần sửa code.

2Nhập liệu Camera Station

Vào menu Camera Stations → Create hoặc chỉnh sửa station hiện có.

Trường Bắt buộc Ví dụ Mô tả
ComputerName dg-6bd Tên máy trạm, dùng để tìm kiếm khi tạo VideoClip
Track 101 Số kênh RTSP của đầu ghi
IpAndPort 192.168.60.115:554 IP và port của camera trực tiếp
NvrIpAndPort 192.168.60.3:554 IP và port của NVR (đầu ghi tập trung)
Description Không Camera kho 6 [ARYA] Chứa tag loại đầu ghi để chọn RTSP template phù hợp. Xem mục 3 để biết cú pháp.
IsActive Không Chỉ station đang Active mới được sử dụng để tạo VideoClip

3Quy tắc trường Description

Hệ thống tự động chọn RTSP template và token dựa vào nội dung trường Description. Có hai cú pháp hỗ trợ:

Cú pháp 1 — Chỉ chọn template (không override token)

Đặt tag là key trong appsettings.json vào bất kỳ vị trí nào trong Description:

Đúng cú pháp
DescriptionKết quả
Camera dg-6bd [ARYA][ARYA] + token mặc định
Đầu ghi cũ [HIK_OLD][HIK_OLD] + token mặc định
[ARYA] kho 3 tầng 1[ARYA] + token mặc định
(để trống)DEFAULT + token mặc định
Sai cú pháp – sẽ dùng DEFAULT
DescriptionLý do sai
[[Format:ARYA]]Không phải cú pháp hợp lệ
ARYAThiếu dấu ngoặc vuông
[arya]Key phân biệt chữ hoa thường
Cú pháp 2 — Chọn template nhúng token riêng

Dùng khi camera station cần token khác với token mặc định. Cú pháp: [[TYPE:token]]

Ví dụ
Camera kho 6 [[ARYA:dXNlcjpwYXNz]]
                 ^^^^^^^^^^^^
                 └─ token riêng của camera này (Base64 hoặc plaintext)

[[HIK_OLD:admin:abc123]] kho cũ tầng 2

# Kết quả sau khi parse:
#   Template được chọn : [ARYA] hoặc [HIK_OLD]
#   Token gửi lên backend: dXNlcjpwYXNz / admin:abc123
#   Token mặc định bị BỎ QUA
DescriptionTemplateToken dùng
Camera [[ARYA:dXNlcjpwYXNz]][ARYA]dXNlcjpwYXNz (từ Description)
[[HIK_OLD:tok123]] kho cũ[HIK_OLD]tok123 (từ Description)
Camera [ARYA][ARYA]Token mặc định của VideoClip
(để trống)DEFAULTToken mặc định của VideoClip
Ưu tiên token:
  1. Token nhúng trong Description: [[TYPE:token]]
  2. Token lưu trên VideoClip (trường Token)
  3. Default token từ cấu hình (VideoClip:DefaultToken)
Lưu ý: Key trong [[TYPE:token]] phải là CHỮ HOA và chỉ chứa A-Z, 0-9, _. Ví dụ hợp lệ: ARYA, HIK_OLD, DAHUA2. Ví dụ KHÔNG hợp lệ: arya, Arya.
Luồng xử lý
  1. Khi tạo / trích xuất VideoClip, hệ thống đọc Description của CameraStation.
  2. Frontend parse Description (DescriptionParser):
    • Nếu tìm thấy [[TYPE:token]] → chuẩn hoá thành [TYPE] trong Description, lấy token override.
    • Ngược lại → giữ nguyên Description, token = token mặc định của VideoClip.
  3. Payload gửi Backend chứa Description (đã chuẩn hoá) và Token (đã chọn đúng).
  4. Backend dùng RtspTemplateHelper: duyệt keys, key nào Contains trong Description → chọn template đó.
  5. Nếu không có key nào khớp → dùng template DEFAULT.
  6. TimeFormat của template được áp dụng cho {start}{end}.

4Kiểm tra kết quả trích xuất

Sau khi tạo và chạy VideoClip, kiểm tra log của Backend container để xác nhận template đã được chọn đúng:

Xem log container
# Xem realtime log
docker logs -f <backend-container-name>

# Tìm dòng log template
docker logs <backend-container-name> 2>&1 | grep "RTSP template key matched"

Dòng log mẫu khi chọn đúng template [ARYA]:

info: Backend.Controllers.VideoCaptureController[0]
      RTSP template key matched: [ARYA], TimeFormat: yyyy_MM_dd_HH_mm_ss

5Chạy Unit Test

Toàn bộ test chạy trong Docker container video-extraction-dev (image mcr.microsoft.com/dotnet/sdk:8.0). Workspace được mount tại /app.

Backend Tests – RtspTemplateHelperTests

Kiểm tra logic chọn template và định dạng thời gian (14 test cases, không cần DB, không cần FFmpeg).

Lệnh chạy
# Khởi động container (nếu chưa chạy)
docker start video-extraction-dev

# Chạy backend tests
docker exec video-extraction-dev \
  dotnet test /app/Backend.Tests/Backend.Tests.csproj \
  --logger "console;verbosity=normal"
TestMục đích kiểm tra
SelectsAryaTemplate_WhenDescriptionContainsAryaTagDescription chứa [ARYA] → chọn ARYA URL
SelectsHikOldTemplate_WhenDescriptionContainsHikOldTagDescription chứa [HIK_OLD] → chọn HIK_OLD URL
FallsBackToDefault_WhenDescriptionIsNullDescription null → dùng DEFAULT
FallsBackToDefault_WhenDescriptionIsEmptyDescription rỗng → dùng DEFAULT
FallsBackToDefault_WhenDescriptionHasNoKnownTagDescription không có tag → dùng DEFAULT
AryaTemplate_UsesUnderscoreSeparatedTimeFormatARYA template dùng format yyyy_MM_dd_HH_mm_ss
DefaultTemplate_UsesHikVisionTimeFormat_NoZSuffixDEFAULT template dùng format yyyyMMddTHHmmss
CustomTimeFormat_IsApplied_WhenConfiguredPerTemplateTimeFormat bất kỳ từ config được áp dụng đúng
ThrowsInvalidOperation_WhenNoDefaultTemplateThiếu entry DEFAULT → ném exception
PicksFirstMatchingTag_WhenDescriptionContainsMultipleTagsNhiều tag → lấy tag đầu tiên khớp
ReplacesAllPlaceholders_* (×2)Tất cả placeholder được thay thế đúng
TrackIsConvertedToString_NotLeft_AsIntTrack int được convert sang string
HikOldTemplate_UsesHikVisionTimeFormat_NoZSuffixHIK_OLD dùng format không có Z
Frontend Tests – StationDescriptionTests & DescriptionParserTests

Kiểm tra logic tìm kiếm Description từ DB, parse [[TYPE:token]] và đóng gói extract request (15 + 19 test cases).

Lệnh chạy
# Chỉ chạy StationDescriptionTests
docker exec video-extraction-dev \
  dotnet test /app/Frontend/UnitTest/UnitTest.csproj \
  --filter "FullyQualifiedName~StationDescriptionTests" \
  --logger "console;verbosity=normal"

# Chỉ chạy DescriptionParserTests (parser [[TYPE:token]])
docker exec video-extraction-dev \
  dotnet test /app/Frontend/UnitTest/UnitTest.csproj \
  --filter "FullyQualifiedName~DescriptionParserTests" \
  --logger "console;verbosity=normal"

# Chạy toàn bộ frontend tests
docker exec video-extraction-dev \
  dotnet test /app/Frontend/UnitTest/UnitTest.csproj \
  --logger "console;verbosity=normal"
Kết quả mong đợi: 14 backend tests passed, 15 StationDescriptionTests passed, 19 DescriptionParserTests passed.

6Câu hỏi thường gặp

Kiểm tra lại: (1) Tag trong Description phải khớp chính xác với key trong appsettings.json — bao gồm dấu ngoặc vuông và chữ hoa/thường. (2) Tìm dòng log RTSP template key matched để xem backend đã đọc được key nào. (3) Sau khi sửa appsettings.json, cần restart backend container để load lại config.

  1. Mở Backend/appsettings.json, thêm entry mới vào RtspConfig.Templates. Ví dụ: "[DAHUA]": {{ "UrlTemplate": "rtsp://...", "TimeFormat": "yyyyMMddTHHmmss" }}
  2. Restart backend container.
  3. Trên trang Camera Stations, sửa Description của station đó để chứa tag mới (ví dụ: Camera kho [DAHUA]).

Bất kỳ chuỗi định dạng .NET DateTime.ToString(format) đều được hỗ trợ. Một số ví dụ:
yyyyMMddTHHmmss – Hikvision tiêu chuẩn
yyyy_MM_dd_HH_mm_ss – ARYA (dấu gạch dưới phân cách)
yyyy-MM-ddTHH:mm:ss – ISO 8601
yyyyMMdd_HHmmss – Định dạng tùy chỉnh với dấu gạch dưới

Test chạy bên trong Docker container video-extraction-dev, không phải trên host. Đảm bảo container đang chạy (docker start video-extraction-dev) rồi dùng docker exec như ví dụ ở mục 5.

Dùng cú pháp [[TYPE:token]] trong trường Description của CameraStation. Ví dụ: Camera kho 6 [[ARYA:dXNlcjpwYXNz]]. Token này sẽ được dùng thay cho token mặc định khi trích xuất clip từ station đó. Token nên là giá trị Base64 của username:password.