summaryrefslogtreecommitdiff
path: root/ar/.config/mpv/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'ar/.config/mpv/scripts')
-rw-r--r--ar/.config/mpv/scripts/integrity-check.lua256
1 files changed, 256 insertions, 0 deletions
diff --git a/ar/.config/mpv/scripts/integrity-check.lua b/ar/.config/mpv/scripts/integrity-check.lua
new file mode 100644
index 0000000..8db71fa
--- /dev/null
+++ b/ar/.config/mpv/scripts/integrity-check.lua
@@ -0,0 +1,256 @@
+-- integrity-check.lua
+-- 재생 중인 동영상이 손상(끊김)되었는지 파일 로드 시 백그라운드로 검사한다.
+-- 손상일 때만 화면 우상단에 표시등을 띄우고, 정상 파일은 아무 표시도 하지 않는다.
+--
+-- 동작:
+-- * file-loaded 시 ffmpeg 으로 demux 검사를 백그라운드 실행(재생은 그대로).
+-- * 손상 구간을 만나면 -xerror 로 즉시 중단 → 손상 파일은 빠르게 판정.
+-- * 결과는 경로+수정시각으로 캐시 → 다음에 같은 파일은 즉시 표시.
+-- * 손상 파일 경로는 corrupted.log 에 기록.
+--
+-- 한계: -c copy(디코딩 안 함) 기반이라 컨테이너/끊김/잘림은 잡지만,
+-- 컨테이너는 멀쩡하고 화면만 미세하게 깨지는 결함은 못 잡는다.
+-- 그런 정밀 검사는 deep_scan=yes 로 바꾸면 되지만 훨씬 느리다.
+
+local mp = require 'mp'
+local utils = require 'mp.utils'
+local msg = require 'mp.msg'
+
+----------------------------------------------------------------------
+-- 옵션 (script-opts/integrity-check.conf 로 덮어쓰기 가능)
+----------------------------------------------------------------------
+local opts = {
+ enabled = true, -- 기능 on/off
+ scan_on_load = true, -- 파일 열 때 자동 검사
+ show_scanning = false, -- 검사 중 표시 여부 (기본: 표시 안 함)
+ deep_scan = false, -- true 면 디코딩까지 검사(정밀하지만 느림)
+ use_cache = true, -- 검사 결과 캐시 사용
+ ffmpeg = "ffmpeg",
+ font_size = 22,
+}
+require('mp.options').read_options(opts, "integrity-check")
+
+----------------------------------------------------------------------
+-- 경로
+----------------------------------------------------------------------
+local HOME = os.getenv("HOME") or ""
+local CONFIG_DIR = HOME .. "/.config/mpv"
+local CACHE_FILE = CONFIG_DIR .. "/integrity_cache.tsv"
+local LOG_FILE = CONFIG_DIR .. "/corrupted.log"
+
+----------------------------------------------------------------------
+-- 상태
+----------------------------------------------------------------------
+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 function load_cache()
+ local f = io.open(CACHE_FILE, "r")
+ if not f then return end
+ for line in f:lines() do
+ local mtime, size, status, errors, path =
+ line:match("^(%d+)\t(%d+)\t(%S+)\t(%d+)\t(.+)$")
+ if path then
+ cache[path] = {
+ mtime = tonumber(mtime),
+ size = tonumber(size),
+ status = status,
+ errors = tonumber(errors),
+ }
+ end
+ end
+ f:close()
+end
+
+-- 한 항목만 덧붙인다(append). 여러 mpv 인스턴스가 동시에 캐시를 써도
+-- 서로의 결과를 덮어쓰지 않게 하기 위함. load 시 같은 경로는 마지막 줄이 이긴다.
+local function append_cache(path, e)
+ if not opts.use_cache then return end
+ local f = io.open(CACHE_FILE, "a")
+ if not f then return end
+ f:write(string.format("%d\t%d\t%s\t%d\t%s\n",
+ e.mtime or 0, e.size or 0, e.status, e.errors or 0, path))
+ f:close()
+end
+
+-- 시작 시 중복 줄을 정리(최신 상태만 남기고 재작성). 세션 중에는 append만 사용.
+local function compact_cache()
+ if not opts.use_cache then return end
+ local f = io.open(CACHE_FILE, "w")
+ if not f then return end
+ for path, e in pairs(cache) do
+ f:write(string.format("%d\t%d\t%s\t%d\t%s\n",
+ e.mtime or 0, e.size or 0, e.status, e.errors or 0, path))
+ end
+ f:close()
+end
+
+local function log_corrupted(path)
+ local f = io.open(LOG_FILE, "a")
+ if not f then return end
+ f:write(string.format("%s\t%s\n", os.date("%Y-%m-%d %H:%M:%S"), path))
+ f:close()
+end
+
+----------------------------------------------------------------------
+-- 배지(표시등) — 손상일 때만 표시
+----------------------------------------------------------------------
+local function hide_badge()
+ overlay:remove()
+end
+
+local function show_corrupt()
+ overlay.res_x = 1280
+ overlay.res_y = 720
+ overlay.data = string.format(
+ "{\\an9\\pos(1268,10)\\fs%d\\bord2\\shad1\\1c&H0000E0&\\3c&H000000&}%s",
+ opts.font_size, "■ 손상됨")
+ overlay:update()
+end
+
+local function show_scanning()
+ if not opts.show_scanning then hide_badge(); return end
+ overlay.res_x = 1280
+ overlay.res_y = 720
+ overlay.data = string.format(
+ "{\\an9\\pos(1268,10)\\fs%d\\bord2\\shad1\\1c&H00D7FF&\\3c&H000000&}%s",
+ opts.font_size, "● 무결성 검사 중…")
+ overlay:update()
+end
+
+----------------------------------------------------------------------
+-- 유틸
+----------------------------------------------------------------------
+local function scannable(path)
+ if not path then return false end
+ if path:find("^%a[%w%+%-%.]*://") then return false end -- 네트워크/스트림 제외
+ return true
+end
+
+local function file_sig(path)
+ local info = utils.file_info(path)
+ if not info or not info.is_file then return nil end
+ return math.floor(info.mtime), info.size
+end
+
+----------------------------------------------------------------------
+-- 검사
+----------------------------------------------------------------------
+local function build_args(path)
+ if opts.deep_scan then
+ -- 디코딩까지: 정밀하지만 느림
+ return { opts.ffmpeg, "-hide_banner", "-v", "error", "-xerror",
+ "-i", path, "-map", "0", "-f", "null", "-" }
+ end
+ -- demux 만: 빠름. 컨테이너/끊김/잘림 검출
+ return { opts.ffmpeg, "-hide_banner", "-v", "error", "-xerror",
+ "-i", path, "-c", "copy", "-map", "0", "-f", "null", "-" }
+end
+
+local function apply_result(path, corrupt, from_cache)
+ local mtime, size = file_sig(path)
+ cache[path] = {
+ mtime = mtime or 0,
+ size = size or 0,
+ status = corrupt and "corrupt" or "ok",
+ errors = 0,
+ }
+ if corrupt then
+ show_corrupt()
+ if not from_cache then
+ log_corrupted(path)
+ msg.warn("손상됨: " .. path)
+ end
+ else
+ -- 정상 파일은 아무 표시도 하지 않는다
+ hide_badge()
+ end
+ if not from_cache then append_cache(path, cache[path]) end
+end
+
+local function start_scan(path)
+ scan_token = scan_token + 1
+ local token = scan_token
+ show_scanning()
+ mp.command_native_async({
+ name = "subprocess",
+ args = build_args(path),
+ playback_only = false,
+ capture_stdout = true,
+ capture_stderr = true,
+ }, function(success, result, err)
+ if token ~= scan_token then return end -- 다른 파일로 넘어감 → 무시
+ 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 확인 필요")
+ hide_badge()
+ return
+ end
+ local stderr = result.stderr or ""
+ local status = result.status or 0
+ local corrupt = (stderr:gsub("%s+", "") ~= "") or (status ~= 0)
+ apply_result(path, corrupt, false)
+ end)
+end
+
+----------------------------------------------------------------------
+-- 이벤트
+----------------------------------------------------------------------
+local function on_file_loaded()
+ hide_badge()
+ 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 return end
+
+ -- 캐시 적중(수정시각·크기 동일)이면 즉시 표시
+ if opts.use_cache and cache[current_path] then
+ local mtime, size = file_sig(current_path)
+ local e = cache[current_path]
+ if mtime and e.mtime == mtime and e.size == size then
+ apply_result(current_path, e.status == "corrupt", true)
+ return
+ end
+ end
+ start_scan(current_path)
+end
+
+----------------------------------------------------------------------
+-- 키 바인딩 / 메시지
+----------------------------------------------------------------------
+local function rescan()
+ if current_path and scannable(current_path) then
+ cache[current_path] = nil
+ start_scan(current_path)
+ mp.osd_message("무결성: 다시 검사 중…")
+ else
+ mp.osd_message("무결성: 검사할 수 없는 파일")
+ end
+end
+
+local function toggle()
+ opts.enabled = not opts.enabled
+ if not opts.enabled then
+ hide_badge()
+ else
+ on_file_loaded()
+ end
+ mp.osd_message("무결성 검사: " .. (opts.enabled and "켜짐" or "꺼짐"))
+end
+
+mp.add_key_binding(nil, "rescan", rescan)
+mp.add_key_binding(nil, "toggle", toggle)
+mp.register_script_message("integrity-rescan", rescan)
+mp.register_script_message("integrity-toggle", toggle)
+
+----------------------------------------------------------------------
+-- 초기화
+----------------------------------------------------------------------
+load_cache()
+compact_cache()
+mp.register_event("file-loaded", on_file_loaded)