Last active 1 month ago

Created by ChatGPT, but not the first comment section.

Revision 540f1008866988c7c5be137e2cd780695da4dec3

gistfile1.txt Raw
1#!/usr/bin/env bash
2
3##########################################################################
4#
5# This bash script was written by ChatGPT, but not this comment section.
6# Everything works after testing, but not 2 functions:
7# - Adding metadata to compressed MP4 file.
8# - Checking if the file has already been compressed by this script.
9#
10##########################################################################
11
12set -u
13export LC_NUMERIC=C
14
15SCRIPT_NAME="$(basename "$0")"
16UNIQUE_PREFIX="r-"
17
18########################################
19# Encoding presets overview:
20#
21# none : balanced quality, AAC 96k
22# vlog : spoken voice / crowd, copy audio
23# tiny : max compression, AAC 64k
24#
25# Resolutions: 720p | 1080p
26########################################
27
28show_help() {
29 cat <<EOF
30Usage:
31 $SCRIPT_NAME <file>
32 $SCRIPT_NAME [none|vlog|tiny] [720p|1080p] <file>
33 $SCRIPT_NAME batch
34
35Batch:
36 Compresses all MP4 + MKV files using:
37 mode=none, resolution=1080p
38
39Defaults:
40 mode=none
41 resolution=720p
42
43EOF
44 exit 0
45}
46
47[[ "${1:-}" == "--help" ]] && show_help
48
49MODE="none"
50RES="720p"
51
52# --- Batch mode ---
53if [[ "${1:-}" == "batch" ]]; then
54 MODE="none"
55 RES="1080p"
56 shift || true
57
58 shopt -s nullglob
59 FILES=( *.mp4 *.MP4 *.mkv )
60 shopt -u nullglob
61
62 if [[ ${#FILES[@]} -eq 0 ]]; then
63 echo "No MP4 or MKV files found."
64 exit 0
65 fi
66
67 for f in "${FILES[@]}"; do
68 "$0" "$MODE" "$RES" "$f"
69 done
70 exit 0
71fi
72
73# --- Parse arguments ---
74if [[ $# -eq 1 ]]; then
75 INPUT="$1"
76elif [[ $# -eq 2 ]]; then
77 MODE="$1"
78 INPUT="$2"
79elif [[ $# -eq 3 ]]; then
80 MODE="$1"
81 RES="$2"
82 INPUT="$3"
83else
84 show_help
85fi
86
87[[ ! -f "$INPUT" ]] && { echo "File not found: $INPUT"; exit 1; }
88
89BASENAME="${INPUT%.*}"
90OUTPUT="${BASENAME}-r.mp4"
91SUMMARY="${BASENAME}.json"
92
93########################################
94# Resolution
95########################################
96case "$RES" in
97 720p) SCALE="scale=-2:720" ;;
98 1080p) SCALE="scale=-2:1080" ;;
99 *) echo "Invalid resolution"; exit 1 ;;
100esac
101
102########################################
103# Mode settings
104########################################
105CQ=23
106AUDIO_OPTS=("-c:a" "aac" "-b:a" "96k")
107
108case "$MODE" in
109 vlog)
110 AUDIO_OPTS=("-c:a" "copy")
111 CQ=23
112 ;;
113 tiny)
114 AUDIO_OPTS=("-c:a" "aac" "-b:a" "64k")
115 CQ=28
116 ;;
117 none) ;;
118 *)
119 echo "Invalid mode"
120 exit 1
121 ;;
122esac
123
124########################################
125# Metadata detection (robust)
126########################################
127EXISTING_ID="$(ffprobe -v error \
128 -show_entries format_tags=compressed_by \
129 -of default=nk=1:nw=1 "$INPUT" 2>/dev/null || true)"
130
131if [[ "$EXISTING_ID" =~ ^${UNIQUE_PREFIX}[A-Za-z0-9]+$ ]]; then
132 echo "Skipping already-compressed file: $INPUT"
133 exit 0
134fi
135
136########################################
137# Generate unique ID
138########################################
139COMPRESS_ID="${UNIQUE_PREFIX}$(tr -dc A-Za-z0-9 </dev/urandom | head -c 12)"
140
141########################################
142# Encode
143########################################
144echo
145echo "Compressing: $INPUT > $OUTPUT"
146
147DURATION=$(ffprobe -v error -show_entries format=duration \
148 -of default=nk=1:nw=1 "$INPUT")
149
150START_TIME=$(date +%s)
151
152ffmpeg -y -hide_banner -loglevel error \
153 -i "$INPUT" \
154 -map 0:v:0 -map 0:a? \
155 -vf "$SCALE" \
156 -c:v hevc_nvenc \
157 -preset p6 -tune hq \
158 -rc vbr -multipass fullres \
159 -profile:v main -pix_fmt yuv420p \
160 -spatial_aq 1 -temporal_aq 1 -aq-strength 8 \
161 -cq "$CQ" \
162 "${AUDIO_OPTS[@]}" \
163 -movflags +faststart \
164 -metadata compressed_by="$COMPRESS_ID" \
165 -progress pipe:1 \
166 "$OUTPUT" 2>/dev/null | while IFS='=' read -r key value; do
167
168 if [[ "$key" == "out_time_ms" ]]; then
169 CURRENT_SEC=$(awk "BEGIN { print $value / 1000000 }")
170
171 PERCENT=$(awk -v c="$CURRENT_SEC" -v d="$DURATION" \
172 'BEGIN { if (d>0) printf "%.1f", (c/d)*100; else print 0 }')
173
174 NOW=$(date +%s)
175 ELAPSED_SEC=$(( NOW - START_TIME ))
176
177 ETA_SEC=$(awk -v e="$ELAPSED_SEC" -v p="$PERCENT" '
178 BEGIN {
179 if (p>0) printf "%.0f", e*(100-p)/p;
180 else print 0
181 }')
182
183 printf "\rProgress: %5.1f%% | Elapsed: %02d:%02d | ETA: %02d:%02d" \
184 "$PERCENT" \
185 $((ELAPSED_SEC/60)) $((ELAPSED_SEC%60)) \
186 $((ETA_SEC/60)) $((ETA_SEC%60))
187 fi
188
189done
190
191########################################
192# Size check
193########################################
194ORIG_SIZE=$(stat -c%s "$INPUT")
195NEW_SIZE=$(stat -c%s "$OUTPUT")
196
197if (( NEW_SIZE >= ORIG_SIZE )); then
198 echo
199 echo "Result larger than original - skipping"
200 rm -f "$OUTPUT"
201 exit 0
202fi
203
204########################################
205# JSON summary
206########################################
207HUMAN_ORIG=$(numfmt --to=iec --suffix=B "$ORIG_SIZE")
208HUMAN_NEW=$(numfmt --to=iec --suffix=B "$NEW_SIZE")
209OCCURRED=$(date +"%Y-%m-%d %H:%M:%S")
210
211########################################
212# Safe reduction percent calculation
213########################################
214if [[ -z "$ORIG_SIZE" ]] || (( ORIG_SIZE == 0 )); then
215 REDUCTION_PERCENT=0
216else
217 REDUCTION_PERCENT=$(awk "BEGIN { pct=(1-$NEW_SIZE/$ORIG_SIZE)*100; if(pct<0) pct=0; printf \"%.2f\", pct }")
218fi
219
220cat > "$SUMMARY" <<EOF
221{
222 "input": "$(basename "$INPUT")",
223 "output": "$(basename "$OUTPUT")",
224
225 "original_bytes": $ORIG_SIZE,
226 "compressed_bytes": $NEW_SIZE,
227
228 "original": "$HUMAN_ORIG",
229 "compressed": "$HUMAN_NEW",
230
231 "reduction_percent": $REDUCTION_PERCENT,
232 "occurred": "$OCCURRED",
233
234 "compression_id": "$COMPRESS_ID",
235
236 "settings": {
237 "codec": "hevc_nvenc",
238 "resolution": "$RES",
239 "mode": "$MODE",
240 "cq": $CQ,
241 "scale_filter": "$SCALE",
242 "audio": "${AUDIO_OPTS[*]}",
243 "preset": "p6",
244 "rate_control": "vbr",
245 "multipass": "fullres",
246 "pixel_format": "yuv420p",
247 "aq": {
248 "spatial": 1,
249 "temporal": 1,
250 "strength": 8
251 }
252 }
253}
254EOF
255
256REDUCTION_HR=$(awk "BEGIN { printf \"%.2f\", (1 - $NEW_SIZE / $ORIG_SIZE) * 100 }")
257
258echo
259echo "Done. Final reduction: $REDUCTION_HR% ($HUMAN_ORIG > $HUMAN_NEW)"
260echo