1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
|
#!/usr/bin/env bash
# rmev - clean up videos that mpv integrity-check flagged as corrupt.
#
# The corrupt list comes from integrity_cache.tsv (current status). A file is
# only eligible for deletion when its on-disk mtime/size exactly match what the
# cache recorded. (If the file changed after the scan it is skipped as
# "changed" and a re-scan is suggested.)
#
# Default behaviour:
# * Show the target list and ask for y/N confirmation before deleting.
# * Use trash-put (trash) if available, otherwise fall back to rm.
# * After deletion, remove the entries from integrity_cache.tsv and corrupted.log.
set -euo pipefail
CONFIG_DIR="${MPV_CONFIG_DIR:-$HOME/.config/mpv}"
CACHE="$CONFIG_DIR/integrity_cache.tsv"
LOG="$CONFIG_DIR/corrupted.log"
DRY=0
FORCE=0
MODE=auto # MODE: auto|trash|rm
usage() {
cat <<'EOF'
Usage: rmev [options]
Delete video files that mpv integrity-check flagged as corrupt.
Options:
-n, --dry-run Show the targets without deleting anything
-f, --force Delete without the confirmation prompt
-t, --trash Move to trash (requires trash-put)
-r, --rm Permanently delete (rm) instead of trashing
-h, --help Show this help
Default: trash if trash-put exists, otherwise rm. Confirms the list first.
Deleted files are also removed from integrity_cache.tsv and corrupted.log.
EOF
}
while [ $# -gt 0 ]; do
case "$1" in
-n | --dry-run) DRY=1 ;;
-f | --force) FORCE=1 ;;
-t | --trash) MODE=trash ;;
-r | --rm) MODE=rm ;;
-h | --help)
usage
exit 0
;;
*)
printf 'Unknown option: %s\n\n' "$1" >&2
usage >&2
exit 2
;;
esac
shift
done
if [ ! -f "$CACHE" ]; then
printf 'Cache file not found: %s\n' "$CACHE" >&2
exit 1
fi
# Decide deletion method
if [ "$MODE" = auto ]; then
if command -v trash-put >/dev/null 2>&1; then MODE=trash; else MODE=rm; fi
fi
if [ "$MODE" = trash ] && ! command -v trash-put >/dev/null 2>&1; then
printf 'trash-put not found. Use --rm to delete permanently, or install trash-cli.\n' >&2
exit 1
fi
# Extract entries whose last cached status is corrupt (mtime, size, path).
# The cache is append-only, so a path may appear on several lines; last wins.
mapfile -t entries < <(
awk -F'\t' '
NF>=5 {
p=$5; for (i=6; i<=NF; i++) p = p "\t" $i
mt[p]=$1; sz[p]=$2; st[p]=$3
}
END { for (p in st) if (st[p]=="corrupt") printf "%s\t%s\t%s\n", mt[p], sz[p], p }
' "$CACHE"
)
if [ "${#entries[@]}" -eq 0 ]; then
printf 'No files flagged as corrupt.\n'
exit 0
fi
# Select real targets: must exist and match the cached mtime/size.
targets=()
skipped=()
for line in "${entries[@]}"; do
mt=${line%%$'\t'*}
rest=${line#*$'\t'}
sz=${rest%%$'\t'*}
path=${rest#*$'\t'}
if [ ! -e "$path" ]; then
skipped+=("missing | $path")
continue
fi
cur_mt=$(stat -c %Y -- "$path" 2>/dev/null || echo -1)
cur_sz=$(stat -c %s -- "$path" 2>/dev/null || echo -1)
if [ "$cur_mt" != "$mt" ] || [ "$cur_sz" != "$sz" ]; then
skipped+=("changed | $path (re-scan needed)")
continue
fi
targets+=("$path")
done
printf 'Corrupt files (%d):\n' "${#targets[@]}"
for p in "${targets[@]}"; do printf ' %s\n' "$p"; done
if [ "${#skipped[@]}" -gt 0 ]; then
printf '\nSkipped (%d):\n' "${#skipped[@]}"
for s in "${skipped[@]}"; do printf ' %s\n' "$s"; done
fi
if [ "${#targets[@]}" -eq 0 ]; then
exit 0
fi
if [ "$DRY" -eq 1 ]; then
printf '\n(dry-run) Nothing deleted.\n'
exit 0
fi
method_label=$([ "$MODE" = trash ] && echo 'move to trash' || echo 'permanently delete')
if [ "$FORCE" -ne 1 ]; then
printf '\n%s %d file(s)? [y/N] ' "$method_label" "${#targets[@]}"
read -r ans
case "$ans" in
y | Y | yes | YES) ;;
*)
printf 'Cancelled.\n'
exit 0
;;
esac
fi
deleted=()
for p in "${targets[@]}"; do
if [ "$MODE" = trash ]; then
if trash-put -- "$p"; then deleted+=("$p"); else printf 'Failed: %s\n' "$p" >&2; fi
else
if rm -f -- "$p"; then deleted+=("$p"); else printf 'Failed: %s\n' "$p" >&2; fi
fi
done
past_label=$([ "$MODE" = trash ] && echo 'moved to trash' || echo 'deleted')
printf '%d file(s) %s.\n' "${#deleted[@]}" "$past_label"
# Remove deleted entries from the cache and log
if [ "${#deleted[@]}" -gt 0 ]; then
tmpset=$(mktemp)
printf '%s\n' "${deleted[@]}" >"$tmpset"
prune() { # $1: file, $2: field number where the path starts
local f="$1" start="$2" tmp
[ -f "$f" ] || return 0
tmp=$(mktemp)
awk -F'\t' -v start="$start" '
NR==FNR { del[$0]=1; next }
{
p=$start; for (i=start+1; i<=NF; i++) p = p "\t" $i
if (!(p in del)) print
}
' "$tmpset" "$f" >"$tmp"
mv "$tmp" "$f"
}
prune "$CACHE" 5 # integrity_cache.tsv: mtime size status errors PATH
prune "$LOG" 2 # corrupted.log: timestamp PATH
rm -f "$tmpset"
fi
|