airikr a révisé ce gist 18 hours ago. Aller à la révision
1 file changed, 218 insertions
gistfile1.txt(fichier créé)
| @@ -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 ✓" | |
Plus récent
Plus ancien