Última actividad 1 month ago

Created by ChatGPT, but not the first comment section.

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