最終更新 18 hours ago

Created by ChatGPT, but not the first comment section.

airikr revised this gist 18 hours ago. Go to revision

1 file changed, 218 insertions

gistfile1.txt(file created)

@@ -0,0 +1,218 @@
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 +
12 + set -u
13 +
14 + SCRIPT_NAME="$(basename "$0")"
15 + UNIQUE_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 +
27 + show_help() {
28 + cat <<EOF
29 + Usage:
30 + $SCRIPT_NAME <file>
31 + $SCRIPT_NAME [none|vlog|tiny] [720p|1080p] <file>
32 + $SCRIPT_NAME batch
33 +
34 + Batch:
35 + Compresses all MP4 + MKV files using:
36 + mode=none, resolution=1080p
37 +
38 + Defaults:
39 + mode=none
40 + resolution=720p
41 +
42 + EOF
43 + exit 0
44 + }
45 +
46 + [[ "${1:-}" == "--help" ]] && show_help
47 +
48 + MODE="none"
49 + RES="720p"
50 +
51 + # --- Batch mode ---
52 + if [[ "${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
70 + fi
71 +
72 + # --- Parse arguments ---
73 + if [[ $# -eq 1 ]]; then
74 + INPUT="$1"
75 + elif [[ $# -eq 2 ]]; then
76 + MODE="$1"
77 + INPUT="$2"
78 + elif [[ $# -eq 3 ]]; then
79 + MODE="$1"
80 + RES="$2"
81 + INPUT="$3"
82 + else
83 + show_help
84 + fi
85 +
86 + [[ ! -f "$INPUT" ]] && { echo "File not found: $INPUT"; exit 1; }
87 +
88 + BASENAME="${INPUT%.*}"
89 + OUTPUT="${BASENAME}-r.mp4"
90 + SUMMARY="${BASENAME}-summary.json"
91 +
92 + ########################################
93 + # Resolution
94 + ########################################
95 + case "$RES" in
96 + 720p) SCALE="scale=-2:720" ;;
97 + 1080p) SCALE="scale=-2:1080" ;;
98 + *) echo "Invalid resolution"; exit 1 ;;
99 + esac
100 +
101 + ########################################
102 + # Mode settings
103 + ########################################
104 + CQ=23
105 + AUDIO_OPTS=("-c:a" "aac" "-b:a" "96k")
106 +
107 + case "$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 + ;;
121 + esac
122 +
123 + ########################################
124 + # Metadata detection (robust)
125 + ########################################
126 + EXISTING_ID="$(ffprobe -v error \
127 + -show_entries format_tags=compressed_by \
128 + -of default=nk=1:nw=1 "$INPUT" 2>/dev/null || true)"
129 +
130 + if [[ "$EXISTING_ID" =~ ^${UNIQUE_PREFIX}[A-Za-z0-9]+$ ]]; then
131 + echo "Skipping already-compressed file: $INPUT"
132 + exit 0
133 + fi
134 +
135 + ########################################
136 + # Generate unique ID
137 + ########################################
138 + COMPRESS_ID="${UNIQUE_PREFIX}$(tr -dc A-Za-z0-9 </dev/urandom | head -c 12)"
139 +
140 + ########################################
141 + # Encode
142 + ########################################
143 + echo "Compressing: $INPUT > $OUTPUT"
144 +
145 + if ! 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
161 + fi
162 +
163 + ########################################
164 + # Size check
165 + ########################################
166 + ORIG_SIZE=$(stat -c%s "$INPUT")
167 + NEW_SIZE=$(stat -c%s "$OUTPUT")
168 +
169 + if (( NEW_SIZE >= ORIG_SIZE )); then
170 + echo "Result larger than original → skipping"
171 + rm -f "$OUTPUT"
172 + exit 0
173 + fi
174 +
175 + ########################################
176 + # JSON summary
177 + ########################################
178 + HUMAN_ORIG=$(numfmt --to=iec --suffix=B "$ORIG_SIZE")
179 + HUMAN_NEW=$(numfmt --to=iec --suffix=B "$NEW_SIZE")
180 + OCCURRED=$(date +"%Y-%m-%d %H:%M:%S")
181 +
182 + cat > "$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 + }
216 + EOF
217 +
218 + echo "Done ✓"
Newer Older