diff options
Diffstat (limited to 'ar/.config')
| -rw-r--r-- | ar/.config/mpv/scripts/integrity-check.lua | 170 |
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() |
