最終更新 17 hours ago

Created by ChatGPT, but not the first comment section.

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