Source Layout Probe avatar

Source Layout Probe

Pricing

Pay per usage

Go to Apify Store
Source Layout Probe

Source Layout Probe

Panels, borders, gutters, logos and layout changes of composite video — pure geometry on grayscale samples (ffmpeg, no ML).

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Andrew Babo

Andrew Babo

Maintained by Community

Actor stats

0

Bookmarked

24

Total users

24

Monthly active users

43 minutes ago

Last modified

Categories

Share

source-layout — bố cục bên trong khung hình nguồn

Actor Apify thuần hình học (Node + ffmpeg, không mô hình ML): tìm các ô hình trong một nguồn ghép — podcast hai ô, lưới Zoom, chia sẻ màn hình + webcam, letterbox/pillarbox, khung thương hiệu, hình-trong-hình — rồi trả về:

  • states[]: từng khoảng thời gian có cùng bố cục, mỗi state gồm các panels (toạ độ chuẩn hoá 0..1, loại camera | screen | static, cờ pip, cạnh nào thật sự có biên), decor (viền, gutter, logo tĩnh), signature, confidence;
  • changes[]: mốc đổi bố cục, hard: true khi trùng điểm cắt cảnh;
  • stats: số khung lấy mẫu, số cảnh, thời gian tính.

Cùng một đầu vào ⇒ cùng một kết quả từng byte (không có ngẫu nhiên, không có đồng hồ trong thuật toán).

Cách làm (một đoạn)

Lấy mẫu 2 fps ở 480 px rộng, chỉ kênh xám. Mỗi cảnh (hoặc mỗi lát ≤ 12 s của cảnh dài) được gom thành ảnh trung bình và ảnh phương sai. Vùng động (phương sai cao) là chỗ có người/nội dung; đường thẳng tĩnh của ảnh trung bình là khung ô, mép gutter, mép letterbox. Mỗi khối động nở ra tới đường thẳng gần nhất ở bốn phía; một đường chỉ được nhận là biên khi phần bên kia nó "chết" (không chuyển động, hoặc là một ô khác, hoặc là dải phẳng như letterbox). Luật góc kín (đường cạnh chạy xuyên qua góc ⇒ đó là vạch chia trong một màn hình) và luật ô tĩnh (người ngồi im bên cạnh vẫn là một ô) hoàn thiện bức tranh. Với khung người + màn hình (slide, mã, biểu đồ) bước mặt (src/layout/surfaces.js) chạy trước: đo "ít mức xám" theo cột/hàng để chia khung thành mặt màn hình và mặt người, mặt màn hình là một ô trọn (trong đó chỉ tìm webcam có khung), mặt người đi đường nở khối cũ nhưng bị rào trong mặt. Mọi ngưỡng nằm trong một bộ LAYOUT_POLICY (src/layout/panels.js; túi surfaces = SURFACE_POLICY).

Đầu vào

{
"op": "layout", // hoặc "capabilities"
"source": "https://… | kv:<storeId>/<key>",
"options": { "start_sec": 0, "duration_sec": null, "sample_fps": 2, "analysis_width": 480 },
"output": { "signed_upload_url": null },
"cleanup": "on_success",
"callback": { "url": "…", "secret_header": { "name": "x-…", "value": "…" } }
}

Đầu ra

Artifact layout (layout.json trong KV store của run, hoặc đẩy thẳng tới signed_upload_url):

{
"version": 1,
"probe": { "name": "source-layout", "version": "1.5.0", "sampleFps": 2, "analysisWidth": 480, "analysisHeight": 270 },
"source": { "w": 1920, "h": 1080, "durationSec": 3120.4 },
"states": [
{
"id": "s0", "startSec": 0, "endSec": 7, "kind": "two-up-h", "confidence": 1,
"panels": [
{ "id": "p0", "rect": { "x": 0.0167, "y": 0.1519, "w": 0.4625, "h": 0.7704 }, "kind": "camera",
"confidence": 1, "motion": 0.51, "bounded": { "left": true, "top": true, "right": true, "bottom": true } },
{ "id": "p1", "rect": { "x": 0.5167, "y": 0.1519, "w": 0.4646, "h": 0.763 }, "kind": "camera", "…": "…" }
],
"decor": [ { "kind": "border", "rect": { "…": "…" } }, { "kind": "gutter", "rect": { "…": "…" } }, { "kind": "logo", "rect": { "…": "…" }, "panelId": "p1" } ],
"signature": "2|1,8,24,46;26,8,49,46",
"notes": []
}
],
"changes": [ { "atSec": 7, "fromStateId": "s0", "toStateId": "s1", "hard": true } ],
"stats": { "frames": 120, "shots": 5, "slices": 7, "computeMs": 278, "totalMs": 577 }
}

kind của state: single | two-up-h | two-up-v | grid | pip | screen+cam | unknown. Một nguồn một máy quay cho đúng một state single với một ô phủ cả khung và bounded toàn false — máy dựng khung phía sau không cần phân nhánh.

Chi phí

~0,3 s tính toán cho mỗi phút video (2 fps, 480 px) cộng thời gian giải mã của ffmpeg; bộ nhớ dưới 300 MB. Chạy trên proxy 480p của video-audio-toolkit là rẻ nhất.

Kiểm thử

npm test # 20 cảnh giả lập vẽ bằng số: hai ô, người ngồi im, pillarbox + vạch chia,
# letterbox, mép tường trong phòng, PiP góc, lưới 2×2, cắt cảnh, state dài,
# khe hẹp có viền nhấp nháy…

Không có ngẫu nhiên nên kết quả thử là tất định; mọi lỗi tìm thấy trên video thật phải được tái hiện bằng một cảnh giả lập mới trong src/layout/layout.test.js trước khi sửa.

Lịch sử máy dò (probe.version)

Đổi số hiệu khi LUẬT đọc bố cục đổi — sổ bố cục ghi theo (project, probe_version) nên bản mới không đè bản cũ.

  • 1.7.0 (09/10/2026): băng chữ vắt ngang toàn khung là một vật thật, đo MỘT nơi ở bước mặt (frameBand, D-SS-14). Human QA Greencap 3254 s: băng câu hỏi hai tông (thân xanh + dòng tên trắng) đè đáy cả người lẫn slide trong 13,5 s — sharedStrip (1.6.4) chỉ bắt được khi đường nở của ô người đã dừng đúng mép băng; băng có chữ thì dải "không phải nền chết" nên ô người bị thả xuống tận đáy (ôm băng), và khi băng trượt vào thì ảnh trung bình để ô người dừng lưng chừng trong băng. Nay băng được nhận trực tiếp: vạch cứng chạy suốt TỪNG mặt ở cùng độ cao, dải tới mép khung ≤ sharedStripShare, ít mức xám (≥ sharedStripMode), và hồ sơ HÀNG hai bên trùng nền (màu trội từng hàng chênh ≤ sharedStripTone; trung bình hàng chỉ làm trần sharedStripMeanTone) — người | slide lệch ~100, băng thật 11. detectSurfaces trả band; chưa chia được (băng che phần dưới vạch ghép dọc, độ phủ tụt dưới snapCoverage) thì so hai nửa trái/phải, bỏ băng ra khỏi khung nội dung rồi chia lại. surfaceCandidates RÀO mọi mặt tại vạch (ô người nở trong rào dừng ở đó, ô màn hình cắt ở đó) ⇒ băng thành border dưới hợp các ô; completeScreenSiblings đo "chạm hai mép" theo rào. Bộ Framing v2 12 ca và 32 ca Bài giảng & Màn hình không đổi một chỉ số.
  • 1.6.4 (08/10/2026): băng vắt ngang đáy/đỉnh CẢ người lẫn màn hình là viền chung. Human QA Greencap: băng bình luận xanh 15 % khung chạy dưới cả hai ô — ô người đã dừng ở mép băng (đường nở dừng ở vạch) nhưng ô màn hình ôm luôn băng (nền slide + băng đều ít mức xám) ⇒ khung "Chỉ màn hình" mang theo băng. Nay sharedStrip (panels.js): mặt màn hình có vạch cứng chạy suốt đúng ở mép ô người, dải ngoài ≤ sharedStripShare, hai bên cùng tông (≤ sharedStripTone) và cùng màu trội (≥ sharedStripMode) ⇒ cắt ô màn hình ở vạch; băng thành border dưới hợp các ô. Phản chứng: vạch kẻ trong slide tình cờ ngang mép ô người không cắt (khác tông). Bộ Framing v2 12 ca và bộ 32 ca Bài giảng & Màn hình không đổi một chỉ số.
  • 1.6.3 (08/10/2026): dải trống chừa vai vạch; đảo mép gộp SAU đảo trong; ffmpeg ghim 2 luồng. Tìm ra gốc của "cửa sổ 2689 s sai trên mây, đúng tại chỗ": tái hiện được bằng ffmpeg 6 (bản trong hộp) so với 8 (tại chỗ) trên CÙNG tệp — bộ co ảnh đặt vạch cứng lệch một điểm (240 ↔ 239) nên cột vai sát vạch lọt vào dải đo, nét 0,031 > 0,03 ⇒ không kéo vạch ⇒ hồ sơ ngang thắng ("hai màn hình chồng"). Nay dải đo chừa 1 + insetPx điểm phía vạch (bandEmptyBetween, có bài kiểm). Đảo mép 1.6.2 cũng không nổ: hàng xóm 553 s bị một đảo trong ở giây 48 chẻ làm "36 s" nên chưa "dài" lúc xét ⇒ nay gộp đảo trong trước, đảo mép sau (lượt riêng). ffmpeg -threads 2 (xem docs/ops/perf/screen-analysis.md).
  • 1.6.2 (08/10/2026): đảo ở mép mảnh: cửa sổ đầu/cuối của một mảnh (không có hàng xóm phía kia) đo lệch ≤ 2 cửa sổ, không có cú cắt ngăn với hàng xóm DÀI ≥ 4 cửa sổ ⇒ nhập vào hàng xóm — cùng lý với đảo ngắn (hình không nhảy thì bố cục không đổi). Lượt chạy thật 1.6.1 trên bài giảng 69 phút: cửa sổ đầu mảnh 2689 s ra "hai màn hình chồng 18/82 %" trên đám mây (cùng cửa sổ chạy tại chỗ ra đúng — khác biệt giải mã/scale giữa hai máy làm vạch ghép mờ dưới ngưỡng ở đúng một cửa sổ), rồi 553 s chia đôi liền sau không cú cắt. Hàng xóm cũng ngắn ⇒ không ai nuốt ai; có cú cắt ⇒ cảnh mở đầu thật, giữ.
  • 1.6.1 (08/10/2026): độ sống dọc đứng trước mức xám ngang; dải trống đo bằng vệt kẻ; mặt 50 % không là banner; dòng tên không là máy quay (lượt chạy thật 1.6.0 trên bài giảng 69 phút: 64 → 19 trạng thái, còn hai cửa sổ sai). (1) Thứ tự cửa: dọc mức xám → dọc độ sống → ngang mức xám → ngang độ sống — cặp người | slide mà mức xám không chia được thì hồ sơ NGANG vẫn gãy ở thanh tiêu đề slide và cắt qua cả hai ("hai màn hình chồng 18/82 %"). (2) Dải trống khi kéo vạch: nét ≤ 0,03, HOẶC ≤ 0,08 mà không có vệt kẻ chạy suốt (nguồn nén nét hơn bản xem: hạt tường thành cạnh, chữ slide thì xếp hàng). (3) Mặt màn hình theo mức xám không chứa ô đường cũ chỉ là "dải trang trí" khi MỎNG ≤ 35 % trục chia; mặt theo độ sống không hỏi đường cũ (băng bình luận vắt ngang đáy hai ô làm đường cũ nở lệch, slide 50 % từng bị bỏ ⇒ cả khung thành màn hình + "camera" 9 % đáy). (4) Ô nở trong mặt người có cạnh ngắn ≤ 12 % khung và dẹt ≥ 4:1 (dòng tên + ảnh đại diện dưới băng) ⇒ bỏ, không phải máy quay thứ hai. Bài kiểm: lectureSplitCaptionBand. Vai của dải nằm (băng 100 % × 18–23 %) là việc của panelRoles.ts:stripShape ⇒ trang trí.
  • 1.6.0 (08/10/2026): vạch ghép cứng thắng điểm gãy mức xám; cặp theo độ sống; lưới phải là lưới; đảo ngắn gộp (chạy thật bài giảng chia đôi 69 phút, 08/10/2026 — một bố cục giữ 47 phút mà máy dò ra 64 trạng thái): (1) điểm gãy mức xám rơi ở MÉP NGƯỜI (tường tối sau lưng cũng "ít mức xám" như slide) ⇒ chia ở 36 % khung với tin cậy 0,9 trong khi vạch ghép ở 50 %. Nay không có vạch sát điểm gãy thì kéo vạch chia qua dải trống (nét ≤ 0,03, đo thật 0,007–0,018) tới vạch cứng gần nhất về phía màn hình (≤ 30 % bề mặt); dải có nội dung thì không kéo — vạch bên trong slide không cướp mép. (2) Slide mang ảnh/biểu đồ (không "ít mức xám") + tường tối: mức xám không chia được và cửa "khung chết ≥ 55 %" gọi cả khung là màn hình ⇒ cặp theo độ sống (splitByLiveness): tại vạch cứng chạy suốt, bên chết ≥ 0,55 có nét là màn hình, bên chết ≤ 0,7 có khối đặc SỐNG cỡ ≥ một webcam là người (đo thật: slide 0,92–0,99 chết, nửa người 0,03–0,30). (3) Giả thuyết lưới 2×2 "ranh 0,83" bịa trên người + slide: ranh nay phải nổi hơn làn đối chứng cách 6 px ≥ 0,2 (vân đều của ảnh cho cạnh ở mọi làn) và không bị khối động mạnh vắt qua (> 15 % ranh có động hai bên ⇒ không phải khe giữa hai luồng hình). (4) buildStates: đảo ngắn ≤ 2 cửa sổ kẹp giữa hai trạng thái cùng hình học, KHÔNG có cú cắt ở hai đầu ⇒ gộp (hình không nhảy thì bố cục không đổi); có cú cắt ⇒ giữ. Năm cửa sổ lỗi thật (two-up-v, pip, lưới, chia 0,36) đều về đúng camera 0–237 | screen 241–479; bộ Framing v2 không đổi; bộ Bài giảng & Màn hình 32 ca không đổi (ô IoU 0,968). Bài kiểm: lectureSplitDarkWall, lectureSplitDeadDoor, đảo ngắn trong buildStates. Chạy
    LAYOUT_DEBUG=1 node --test …
    để in ô + ghi chú chẩn đoán của từng cảnh.
  • 1.5.0 (08/10/2026): màn hình toàn khung + webcam không viền (chạy thật trên video chia sẻ màn hình, 08/10/2026): đường cũ chia màn hình thành nhiều "ô" là cửa sổ con của ứng dụng và bỏ sót webcam nhỏ góc dưới. Nay cả khung là mặt số khi đặc trưng ít mức xám theo làn (trung bình theo cột và theo hàng ≥ 0,8) + nét + ít động, HOẶC khi ≥ 55 % khung chết (độ sống < 0,12 — trang web nhiều màu làm biểu đồ xám loãng như ảnh người nhưng điểm ảnh số không lăn tăn; đo: màn hình chia sẻ 0,63–0,93 chết, camera toàn khung 0,00–0,30) — và khi ấy mặt thắng, đường cũ chỉ còn quyền khi ra bố cục ghép kín, tin cậy cao VÀ có người (tường phẳng của podcast sáng). Khối đặc lớn chỉ là "người toàn khung" khi nó SỐNG; hộp màu trang web đổi vài lần rồi đứng im là nội dung số. Trên mặt màn hình, ô chèn (webcam) đo bằng độ sống từng điểm ảnh (phần cặp khung liên tiếp mà điểm ảnh đổi — FrameAccumulator.liveness): ảnh máy quay không bao giờ đứng im (nhiễu cảm biến; tường phẳng bị nén còn 0,1–0,3), mặt số chết hẳn (≤ 0,06), một lần đổi slide chỉ lật một cặp. Lấy LÕI là khối động mạnh và sống lớn nhất (hộp màu đổi vài lần cũng "mạnh" nhưng không sống — tính vào lõi thì webcam sát bên nhập chung thành dải dài), gọt mép chết, rồi NỞ từng dải 3 px chừng nào dải còn sống ≥ 0,12; dừng sớm ở vạch mép (bậc ≥ 24 mức xám dọc ≥ 60 % cạnh) khi 8–16 px bên kia đã chết — nén video làm độ sống tràn ra ngoài mép chừng một khối 16 px nên dải sát mép vẫn "sống". Hộp nở sai dáng (lan sang chữ cuộn kề bên) ⇒ nở lại NGHIÊM, dừng ở mọi vạch mép. Sau đó xét cỡ (≤ pipMaxArea), dáng (≤ 2,2:1, cao ≥ 10 % khung), ít mức xám < modeBox trừ khi cả ô sống ≥ 0,3, có khối người; đồ hoạ động (cột biểu đồ chạy: ≥ 15 % hộp lệch chuẩn

    60 mà ảnh trung bình không có vân) bị loại — webcam hiện/ẩn giữa cửa sổ cũng đổi mạnh nhưng vẫn giữ vân. Hộp webcam có vạch khung chỉ là CẬN TRÊN: gọt theo độ sống; bên trong không phải ảnh máy quay thì bỏ, phần sống chỉ chiếm góc nhỏ thì khung là ô giao diện (hai ô lưới có viền ôm webcam) và lấy phần sống. Mặt "màn hình" không chứa ô nào của đường cũ (banner tên kênh phẳng có chữ trên hai camera: 20VC) là dải trang trí — bỏ phép chia, giữ hai ô như 1.3.1. Bộ kiểm đơn vị vẽ phòng có nhiễu cảm biến ±2 (ảnh thật không bao giờ đứng im tuyệt đối). Bộ Framing v2 không đổi; bộ Bài giảng & Màn hình 32 ca (nhãn webcam trôi = hộp quét): ô IoU 0,95 → 0,967, vai F1 0,976 → 0,983, kiểu 0,87 → 0,893, sai nguồn 0,046 → 0,033, webcam đè IoU 0,891. Video chia sẻ màn hình thật 18,6 phút: 24 trạng thái lộn xộn → 9, mọi trạng thái có webcam đúng góc (sai mép ≤ 7 px ở 480 px), còn sót một cửa sổ người ngồi im 12 s (không có khối động ⇒ chỉ màn hình).

  • 1.4.0 (07/10/2026): mặt màn hình (surfaces.js, Bài giảng & Màn hình): trước khi nở khối, hỏi khung có mấy MẶT. Đặc trưng "ít mức xám" theo cột/hàng (5 bin đông nhất) tách slide/mã/biểu đồ (0,8–1,0) khỏi ảnh người (0,55–0,65); chia bằng điểm gãy bình phương tối thiểu, chỉ nhận cặp người + màn hình (màn hình ít động hơn người); cả khung là màn hình khi ít mức xám + nét + ít động và không có khối người đặc (phần khung ngoài khối vẫn là mặt số ⇒ webcam đè). Trong mặt màn hình chỉ tìm webcam có khung (bốn đường quanh mảng động, cho phép sát mép, chẻ khối dính hoạt hình kề; loại mảng mặt số/ít động). Đường cũ giữ quyền khi nó ra bố cục ghép tin cậy hoặc ô có người đặc — bộ Framing v2 và podcast không đổi. Bộ tổng hợp 32 ca: ô IoU 0,62 → 0,95, kiểu đúng 0,53 → 0,83, sai nguồn 0,145 → 0,049. Lề cùng màu nền trội là mặt, không phải viền (slide trắng không mất lề trắng).
  • 1.3.1 (07/10/2026): khe trang trí chỉ giữa hai ô KỀ NHAU — hai ô cách nhau một ô (hàng ba cột, hàng dưới căn giữa) từng sinh "khe" đè trọn ô giữa, bộ lập kế hoạch khoét mất ô ấy (decor-in-frame ở mọi khung cột giữa Zoom 3×3).
  • 1.3.0 (07/10/2026): lưới hội họp (Zoom/Meet/Teams) đi cửa riêng gallery.js (D-F2-20): giả thuyết lưới cột×hàng từ hộp sáng trên nền tối, kiểm bằng chứng ở ranh lưới dự đoán, cắt từng hàng theo dải sáng (hàng cuối căn giữa), gọt pillarbox trong ô; ô lưới luôn là người (camera/static), tin cậy theo bằng chứng ranh. Zoom 8 người thật: trước là MỘT "màn hình" hai hàng, nay 3×3 → 8 ô đúng, tin cậy 1,00.
  • 1.2.0 (07/10/2026): mép ô dừng trước vạch khung (D-F2-19): lùi qua hết dải vạch khi vầng động liếm vào sườn trong; dải viền chuyển sắc/vạch mảnh là nền chết (làn phẳng), không phải nội dung; dải mỏng toàn làn phẳng kề banner có logo là vạch, ô dừng ở đường. 20VC: hộp hai ô nằm trọn trong vạch trắng, tin cậy 0,60 → 1,00.
  • 1.1.0 (07/10/2026): độ chết của khe giữa hai ô đo trên làn nền (các làn phẳng cùng độ sáng), không đo cả hộp khe. Viền sáng kề nội dung tối nhấp nháy theo nén video (20VC: cột viền động 0,51, nền động 0) từng làm hai camera bị gọi là một ô rộng suốt 20 giây.
  • 1.0.0: bản đầu.