summaryrefslogtreecommitdiff
path: root/ar
diff options
context:
space:
mode:
Diffstat (limited to 'ar')
-rw-r--r--ar/.config/mpv/scripts/integrity-check.lua170
1 files changed, 92 insertions, 78 deletions
diff --git a/ar/.config/mpv/scripts/integrity-check.lua b/ar/.config/mpv/scripts/integrity-check.lua
index fce3d1d..f6b7c27 100644
--- a/ar/.config/mpv/scripts/integrity-check.lua
+++ b/ar/.config/mpv/scripts/integrity-check.lua
@@ -1,44 +1,44 @@
-- integrity-check.lua
--- 재생 중인 동영상이 손상(끊김)되었는지 파일 로드 시 백그라운드로 검사한다.
--- 손상일 때만 화면 우상단에 표시등을 띄우고, 정상 파일은 아무 표시도 하지 않는다.
+-- Background-checks whether the playing video is corrupt (truncated) when a file loads.
+-- Shows a badge in the top-right ONLY when corrupt; healthy files show nothing.
--
--- 동작:
--- * file-loaded 시 ffmpeg 으로 demux 검사를 백그라운드 실행(재생은 그대로).
--- * 손상 구간을 만나면 -xerror 로 즉시 중단 → 손상 파일은 빠르게 판정.
--- * 결과는 경로+수정시각으로 캐시 → 다음에 같은 파일은 즉시 표시.
--- * 손상 파일 경로는 corrupted.log 에 기록.
--- * 재생목록이 있으면(scan_playlist) 현재 파일 검사가 끝난 뒤,
--- 뒤따르는 항목들을 한 번에 하나씩 백그라운드로 미리 검사한다.
--- (배지는 현재 재생 파일에만 표시하고, 미리 검사는 캐시·로그만 갱신)
--- * 재생목록 미리 검사가 끝나면 OSD 로 요약 알림(notify_done).
--- script-message integrity-status 로 진행 현황을 즉시 확인할 수 있다.
+-- Behaviour:
+-- * On file-loaded, run an ffmpeg demux check in the background (playback continues).
+-- * Abort on the first error with -xerror -> corrupt files are judged quickly.
+-- * Results are cached by path + mtime -> the same file shows instantly next time.
+-- * Corrupt file paths are recorded in corrupted.log.
+-- * If a playlist exists (scan_playlist), after the current file is checked,
+-- the following entries are pre-checked one at a time in the background.
+-- (The badge is only for the current file; pre-checks update cache/log only.)
+-- * When the playlist pre-check finishes, an OSD summary is shown (notify_done).
+-- script-message integrity-status shows the current progress at any time.
--
--- 한계: -c copy(디코딩 안 함) 기반이라 컨테이너/끊김/잘림은 잡지만,
--- 컨테이너는 멀쩡하고 화면만 미세하게 깨지는 결함은 못 잡는다.
--- 그런 정밀 검사는 deep_scan=yes 로 바꾸면 되지만 훨씬 느리다.
+-- Limitation: based on -c copy (no decoding), so it catches container/truncation/cutoff
+-- problems, but not cases where the container is fine and only the picture is
+-- slightly broken. For that, set deep_scan=yes (much slower).
local mp = require 'mp'
local utils = require 'mp.utils'
local msg = require 'mp.msg'
----------------------------------------------------------------------
--- 옵션 (script-opts/integrity-check.conf 로 덮어쓰기 가능)
+-- Options (override via script-opts/integrity-check.conf)
----------------------------------------------------------------------
local opts = {
- enabled = true, -- 기능 on/off
- scan_on_load = true, -- 파일 열 때 자동 검사
- scan_playlist = true, -- 현재 파일 검사 후 뒤따르는 재생목록 항목도 백그라운드 검사
- notify_done = true, -- 재생목록 백그라운드 검사가 끝나면 OSD 로 요약 표시
- show_scanning = false, -- 검사 중 표시 여부 (기본: 표시 안 함)
- deep_scan = false, -- true 면 디코딩까지 검사(정밀하지만 느림)
- use_cache = true, -- 검사 결과 캐시 사용
+ enabled = true, -- feature on/off
+ scan_on_load = true, -- auto-check when a file opens
+ scan_playlist = true, -- after the current file, also background-check following playlist entries
+ notify_done = true, -- show an OSD summary when the playlist background check finishes
+ show_scanning = false, -- show a "scanning" badge (default: off)
+ deep_scan = false, -- if true, decode while checking (precise but slow)
+ use_cache = true, -- use the result cache
ffmpeg = "ffmpeg",
font_size = 22,
}
require('mp.options').read_options(opts, "integrity-check")
----------------------------------------------------------------------
--- 경로
+-- Paths
----------------------------------------------------------------------
local HOME = os.getenv("HOME") or ""
local CONFIG_DIR = HOME .. "/.config/mpv"
@@ -46,16 +46,16 @@ local CACHE_FILE = CONFIG_DIR .. "/integrity_cache.tsv"
local LOG_FILE = CONFIG_DIR .. "/corrupted.log"
----------------------------------------------------------------------
--- 상태
+-- State
----------------------------------------------------------------------
local cache = {} -- path -> {mtime=, size=, status=, errors=}
local overlay = mp.create_osd_overlay("ass-events")
local current_path = nil
-local scan_token = 0 -- 오래된(stale) 콜백 무시용
-local bg_token = 0 -- 재생목록 백그라운드 검사 무효화용
+local scan_token = 0 -- to ignore stale callbacks
+local bg_token = 0 -- to invalidate the playlist background scan
----------------------------------------------------------------------
--- 캐시 입출력
+-- Cache I/O
----------------------------------------------------------------------
local function load_cache()
local f = io.open(CACHE_FILE, "r")
@@ -75,8 +75,8 @@ local function load_cache()
f:close()
end
--- 한 항목만 덧붙인다(append). 여러 mpv 인스턴스가 동시에 캐시를 써도
--- 서로의 결과를 덮어쓰지 않게 하기 위함. load 시 같은 경로는 마지막 줄이 이긴다.
+-- Append a single entry. So that concurrent mpv instances writing the cache
+-- don't overwrite each other's results. On load, the last line for a path wins.
local function append_cache(path, e)
if not opts.use_cache then return end
local f = io.open(CACHE_FILE, "a")
@@ -86,7 +86,8 @@ local function append_cache(path, e)
f:close()
end
--- 시작 시 중복 줄을 정리(최신 상태만 남기고 재작성). 세션 중에는 append만 사용.
+-- On startup, compact duplicate lines (keep only the latest, rewrite).
+-- During a session only append is used.
local function compact_cache()
if not opts.use_cache then return end
local f = io.open(CACHE_FILE, "w")
@@ -106,7 +107,7 @@ local function log_corrupted(path)
end
----------------------------------------------------------------------
--- 배지(표시등) — 손상일 때만 표시
+-- Badge (indicator) - shown only when corrupt
----------------------------------------------------------------------
local function hide_badge()
overlay:remove()
@@ -117,7 +118,7 @@ local function show_corrupt()
overlay.res_y = 720
overlay.data = string.format(
"{\\an9\\pos(1268,10)\\fs%d\\bord2\\shad1\\1c&H0000E0&\\3c&H000000&}%s",
- opts.font_size, "■ 손상됨")
+ opts.font_size, "■ Corrupt")
overlay:update()
end
@@ -127,16 +128,16 @@ local function show_scanning()
overlay.res_y = 720
overlay.data = string.format(
"{\\an9\\pos(1268,10)\\fs%d\\bord2\\shad1\\1c&H00D7FF&\\3c&H000000&}%s",
- opts.font_size, "● 무결성 검사 중…")
+ opts.font_size, "● Checking...")
overlay:update()
end
----------------------------------------------------------------------
--- 유틸
+-- Utils
----------------------------------------------------------------------
local function scannable(path)
if not path then return false end
- if path:find("^%a[%w%+%-%.]*://") then return false end -- 네트워크/스트림 제외
+ if path:find("^%a[%w%+%-%.]*://") then return false end -- exclude network/stream
return true
end
@@ -147,15 +148,15 @@ local function file_sig(path)
end
----------------------------------------------------------------------
--- 검사
+-- Check
----------------------------------------------------------------------
local function build_args(path)
if opts.deep_scan then
- -- 디코딩까지: 정밀하지만 느림
+ -- with decoding: precise but slow
return { opts.ffmpeg, "-hide_banner", "-v", "error", "-xerror",
"-i", path, "-map", "0", "-f", "null", "-" }
end
- -- demux 만: 빠름. 컨테이너/끊김/잘림 검출
+ -- demux only: fast; detects container/truncation/cutoff
return { opts.ffmpeg, "-hide_banner", "-v", "error", "-xerror",
"-i", path, "-c", "copy", "-map", "0", "-f", "null", "-" }
end
@@ -172,23 +173,23 @@ local function apply_result(path, corrupt, from_cache)
show_corrupt()
if not from_cache then
log_corrupted(path)
- msg.warn("손상됨: " .. path)
+ msg.warn("Corrupt: " .. path)
end
else
- -- 정상 파일은 아무 표시도 하지 않는다
+ -- healthy files show nothing
hide_badge()
end
if not from_cache then append_cache(path, cache[path]) end
end
--- subprocess 결과를 손상 여부로 환산
+-- Convert the subprocess result into a corrupt/ok verdict
local function determine_corrupt(result)
local stderr = result.stderr or ""
local status = result.status or 0
return (stderr:gsub("%s+", "") ~= "") or (status ~= 0)
end
--- 캐시가 현재 파일 상태(수정시각·크기)와 일치하면 그 항목을 반환
+-- Return the cache entry if it matches the file's current mtime/size
local function cached_fresh(path)
if not (opts.use_cache and cache[path]) then return nil end
local mtime, size = file_sig(path)
@@ -210,11 +211,11 @@ local function start_scan(path, on_done)
capture_stdout = true,
capture_stderr = true,
}, function(success, result, err)
- if token ~= scan_token then return end -- 다른 파일로 넘어감 → 무시
+ if token ~= scan_token then return end -- moved to another file -> ignore
if not result then hide_badge(); return end
if result.killed_by_us then return end
if result.error_string == "init" then
- msg.error("ffmpeg 실행 실패 — PATH 확인 필요")
+ msg.error("ffmpeg failed to run - check PATH")
hide_badge()
return
end
@@ -224,24 +225,24 @@ local function start_scan(path, on_done)
end
----------------------------------------------------------------------
--- 재생목록 백그라운드 검사 (현재 파일 검사 완료 후 뒤따르는 항목들)
+-- Playlist background check (entries after the current file, once it's checked)
----------------------------------------------------------------------
--- 진행 중인 재생목록 검사를 무효화(파일 전환·토글 시)
+-- Invalidate an in-progress playlist scan (on file change / toggle)
local function cancel_bg()
bg_token = bg_token + 1
end
--- 재생목록 항목의 filename 을 실제 검사 가능한 경로로 변환
+-- Convert a playlist entry's filename into a checkable path
local function resolve_playlist_path(filename)
if not filename then return nil end
- if filename:find("^%a[%w%+%-%.]*://") then return filename end -- URL 은 그대로
- if filename:find("^/") then return filename end -- 절대경로
+ if filename:find("^%a[%w%+%-%.]*://") then return filename end -- keep URLs as-is
+ if filename:find("^/") then return filename end -- absolute path
local wd = mp.get_property("working-directory")
if wd then return utils.join_path(wd, filename) end
return filename
end
--- 배지 없이 캐시·로그만 갱신 (백그라운드 항목용)
+-- Update cache/log only, no badge (for background entries)
local function record_result_quiet(path, corrupt)
local mtime, size = file_sig(path)
cache[path] = {
@@ -252,12 +253,12 @@ local function record_result_quiet(path, corrupt)
}
if corrupt then
log_corrupted(path)
- msg.warn("손상됨(재생목록): " .. path)
+ msg.warn("Corrupt (playlist): " .. path)
end
append_cache(path, cache[path])
end
--- 재생목록 전체의 검사 현황을 캐시 기준으로 집계
+-- Aggregate the playlist's check status from the cache
local function playlist_stats()
local count = mp.get_property_number("playlist-count", 0)
local s = { total = 0, ok = 0, corrupt = 0, pending = 0 }
@@ -275,32 +276,33 @@ local function playlist_stats()
return s
end
--- start_index 부터 재생목록 끝까지 한 번에 하나씩 순차 검사
+-- Scan sequentially, one at a time, from start_index to the end of the playlist
local function scan_playlist_from(start_index)
cancel_bg()
local token = bg_token
local count = mp.get_property_number("playlist-count", 0)
- local did_scan = false -- 이번 검사에서 실제로 새로 검사한 항목이 있었는지
+ local did_scan = false -- whether this run actually checked any new entry
- -- 끝까지 도달했을 때 요약 알림(새로 검사한 게 있을 때만 — 자동재생 중복 방지)
+ -- Summary notification when reaching the end (only if something new was
+ -- scanned - avoids duplicate notifications during autoplay)
local function finish()
if not (opts.notify_done and did_scan) then return end
local s = playlist_stats()
local txt = (s.corrupt > 0)
- and string.format("재생목록 검사 완료 · 손상 %d개 (총 %d)", s.corrupt, s.total)
- or string.format("재생목록 검사 완료 · 이상 없음 (총 %d)", s.total)
+ and string.format("Playlist check done | %d corrupt (total %d)", s.corrupt, s.total)
+ or string.format("Playlist check done | no issues (total %d)", s.total)
mp.osd_message(txt, 4)
msg.info(txt)
end
local function step(i)
- if token ~= bg_token then return end -- 파일 전환/토글 → 중단
+ if token ~= bg_token then return end -- file change / toggle -> stop
if i >= count then finish(); return end
local path = resolve_playlist_path(
mp.get_property("playlist/" .. i .. "/filename"))
if not scannable(path) then return step(i + 1) end
- if path == current_path then return step(i + 1) end -- 현재 파일은 이미 검사됨
- if cached_fresh(path) then return step(i + 1) end -- 이미 검사된 파일은 건너뜀
+ if path == current_path then return step(i + 1) end -- current file already checked
+ if cached_fresh(path) then return step(i + 1) end -- already checked, skip
did_scan = true
mp.command_native_async({
@@ -310,7 +312,7 @@ local function scan_playlist_from(start_index)
capture_stdout = true,
capture_stderr = true,
}, function(success, result, err)
- if token ~= bg_token then return end -- 중간에 무효화됨 → 무시
+ if token ~= bg_token then return end -- invalidated midway -> ignore
if result and not result.killed_by_us
and result.error_string ~= "init" then
record_result_quiet(path, determine_corrupt(result))
@@ -322,48 +324,48 @@ local function scan_playlist_from(start_index)
step(start_index)
end
--- 재생목록이 있으면 현재 위치 다음 항목부터 백그라운드 검사 시작
+-- If a playlist exists, start the background scan from the entry after the current position
local function maybe_scan_playlist()
if not (opts.enabled and opts.scan_playlist) then return end
local count = mp.get_property_number("playlist-count", 0)
- if count <= 1 then return end -- 재생목록 없음
+ if count <= 1 then return end -- no playlist
local pos = mp.get_property_number("playlist-pos", 0)
scan_playlist_from(pos + 1)
end
----------------------------------------------------------------------
--- 이벤트
+-- Events
----------------------------------------------------------------------
local function on_file_loaded()
hide_badge()
- cancel_bg() -- 이전 재생목록 검사 중단
+ cancel_bg() -- stop the previous playlist scan
current_path = mp.get_property("path")
if not opts.enabled or not opts.scan_on_load then return end
if not scannable(current_path) then
- maybe_scan_playlist() -- 현재가 스트림이어도 목록은 검사
+ maybe_scan_playlist() -- even if current is a stream, still scan the list
return
end
- -- 캐시 적중(수정시각·크기 동일)이면 즉시 표시
+ -- On a cache hit (same mtime/size), show immediately
local e = cached_fresh(current_path)
if e then
apply_result(current_path, e.status == "corrupt", true)
maybe_scan_playlist()
return
end
- start_scan(current_path, maybe_scan_playlist) -- 현재 검사 완료 후 목록 검사
+ start_scan(current_path, maybe_scan_playlist) -- after the current check, scan the list
end
----------------------------------------------------------------------
--- 키 바인딩 / 메시지
+-- Key bindings / messages
----------------------------------------------------------------------
local function rescan()
if current_path and scannable(current_path) then
cache[current_path] = nil
start_scan(current_path)
- mp.osd_message("무결성: 다시 검사 중…")
+ mp.osd_message("Integrity: re-checking...")
else
- mp.osd_message("무결성: 검사할 수 없는 파일")
+ mp.osd_message("Integrity: file cannot be checked")
end
end
@@ -375,20 +377,32 @@ local function toggle()
else
on_file_loaded()
end
- mp.osd_message("무결성 검사: " .. (opts.enabled and "켜짐" or "꺼짐"))
+ mp.osd_message("Integrity check: " .. (opts.enabled and "on" or "off"))
end
--- 재생목록 검사 현황을 즉시 표시 (끝났는지 / 얼마나 남았는지 확인용)
+-- Current file's status, independent of the use_cache option (checks freshness)
+local function current_status_text()
+ if not current_path then return "Integrity: no file" end
+ if not scannable(current_path) then return "Integrity: not checkable (stream)" end
+ local e = cache[current_path]
+ local mtime, size = file_sig(current_path)
+ if e and mtime and e.mtime == mtime and e.size == size then
+ return (e.status == "corrupt") and "Integrity: corrupt" or "Integrity: OK"
+ end
+ return "Integrity: checking..."
+end
+
+-- Show the check status immediately (single file, or whole playlist)
local function status()
local count = mp.get_property_number("playlist-count", 0)
if count <= 1 then
- mp.osd_message("무결성: 재생목록 없음")
+ mp.osd_message(current_status_text(), 4)
return
end
local s = playlist_stats()
- local state = (s.pending == 0) and "검사 완료" or ("검사 중(남음 " .. s.pending .. ")")
+ local state = (s.pending == 0) and "done" or ("scanning (" .. s.pending .. " left)")
mp.osd_message(string.format(
- "무결성 %s · 정상 %d · 손상 %d · 미검사 %d (총 %d)",
+ "Integrity %s | ok %d | corrupt %d | pending %d (total %d)",
state, s.ok, s.corrupt, s.pending, s.total), 4)
end
@@ -400,7 +414,7 @@ mp.register_script_message("integrity-toggle", toggle)
mp.register_script_message("integrity-status", status)
----------------------------------------------------------------------
--- 초기화
+-- Initialization
----------------------------------------------------------------------
load_cache()
compact_cache()