Created
December 11, 2025 12:23
-
-
Save Plasmoxy/a07c1963c6ced09f682c138292799fc2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| # VidMod v2 clip editor script | |
| # ASCII intro | |
| cat << "EOF" | |
| β¦ β¦β¬ββ¬βββ¦ββββββ¬β | |
| βββββ ββββββ β ββ | |
| ββ β΄ββ΄ββ© β©βββββ΄β | |
| πΈ v2 by Plasmoxy πΈ | |
| EOF | |
| echo "" | |
| # Discord mode constants | |
| MAX_DISCORD_FILE_SIZE_MB=7.7 # Safety margin (Discord limit is 8MB for free users) | |
| DISCORD_AUDIO_BITRATE_K=128 # Assumed audio bitrate in kbps | |
| DISCORD_MIN_VIDEO_BITRATE_K=100 # Minimum video bitrate | |
| DISCORD_HD_THRESHOLD=1080 # Resolution threshold for auto-desize | |
| DISCORD_TARGET_HEIGHT=720 # Target height for Discord videos (HD 720p) | |
| # Initialize variables | |
| start_time="" | |
| end_time="" | |
| do_desize=false | |
| scale_filter="" | |
| bitrate="" | |
| srt_track="" | |
| discord_mode=false | |
| input_file="" | |
| output_file="" | |
| # Parse arguments | |
| while [ $# -gt 0 ]; do | |
| case $1 in | |
| -s) | |
| start_time="$2" | |
| shift 2 | |
| ;; | |
| -e) | |
| end_time="$2" | |
| shift 2 | |
| ;; | |
| -d) | |
| do_desize=true | |
| shift | |
| ;; | |
| -b) | |
| bitrate="$2" | |
| shift 2 | |
| ;; | |
| -discord) | |
| discord_mode=true | |
| shift | |
| ;; | |
| -srt) | |
| srt_track="$2" | |
| shift 2 | |
| ;; | |
| -*) | |
| echo "β Unknown option: $1" | |
| exit 1 | |
| ;; | |
| *) | |
| if [ -z "$input_file" ]; then | |
| input_file="$1" | |
| elif [ -z "$output_file" ]; then | |
| output_file="$1" | |
| else | |
| echo "β Too many arguments" | |
| exit 1 | |
| fi | |
| shift | |
| ;; | |
| esac | |
| done | |
| # Check if input file is provided | |
| if [ -z "$input_file" ]; then | |
| echo "π Usage: vidmod [options] input_file [output_file]" | |
| echo "" | |
| echo "Options:" | |
| echo " -s TIME β±οΈ Start time (e.g., 0:15)" | |
| echo " -e TIME β±οΈ End time (e.g., 0:30)" | |
| echo " -d π Downscale by half (desize)" | |
| echo " -b BITRATE πΎ Set bitrate (e.g., 2M, 64k)" | |
| echo " -discord π¬ Optimize for Discord (auto-scale to ~${DISCORD_TARGET_HEIGHT}p, limit to ${MAX_DISCORD_FILE_SIZE_MB}MB)" | |
| echo " -srt TRACK π Extract subtitle track (standalone command)" | |
| exit 1 | |
| fi | |
| echo "π Input: $input_file" | |
| # Handle SRT extraction (standalone command) | |
| if [ -n "$srt_track" ]; then | |
| if [ -z "$output_file" ]; then | |
| output_file="${input_file%.*}.${srt_track}.srt" | |
| fi | |
| echo "π Extracting subtitle track $srt_track" | |
| echo "πΎ Output: $output_file" | |
| echo "" | |
| ffmpeg -i "$input_file" -map "0:s:$srt_track" "$output_file" -hide_banner -loglevel error -stats | |
| if [ $? -eq 0 ]; then | |
| echo "" | |
| echo "β Subtitle extraction complete!" | |
| else | |
| echo "" | |
| echo "β Subtitle extraction failed!" | |
| fi | |
| exit 0 | |
| fi | |
| # Handle Discord mode | |
| if [ "$discord_mode" = true ]; then | |
| echo "π¬ Discord mode enabled" | |
| # Get video resolution and duration using ffprobe | |
| echo "π Analyzing video..." | |
| probe_output=$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height,duration -of csv=p=0 "$input_file" 2>/dev/null) | |
| width=$(echo "$probe_output" | cut -d',' -f1) | |
| height=$(echo "$probe_output" | cut -d',' -f2) | |
| duration=$(echo "$probe_output" | cut -d',' -f3) | |
| # If duration is not in video stream, try format duration | |
| if [ -z "$duration" ] || [ "$duration" = "N/A" ]; then | |
| duration=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$input_file" 2>/dev/null) | |
| fi | |
| echo "πΊ Resolution: ${width}x${height}" | |
| # Adjust duration based on time cuts | |
| if [ -n "$start_time" ] && [ -n "$end_time" ]; then | |
| # Convert times to seconds for calculation | |
| start_sec=$(echo "$start_time" | awk -F: '{ if (NF==3) print ($1 * 3600) + ($2 * 60) + $3; else if (NF==2) print ($1 * 60) + $2; else print $1 }') | |
| end_sec=$(echo "$end_time" | awk -F: '{ if (NF==3) print ($1 * 3600) + ($2 * 60) + $3; else if (NF==2) print ($1 * 60) + $2; else print $1 }') | |
| duration=$(echo "$end_sec - $start_sec" | bc) | |
| elif [ -n "$start_time" ]; then | |
| start_sec=$(echo "$start_time" | awk -F: '{ if (NF==3) print ($1 * 3600) + ($2 * 60) + $3; else if (NF==2) print ($1 * 60) + $2; else print $1 }') | |
| duration=$(echo "$duration - $start_sec" | bc) | |
| elif [ -n "$end_time" ]; then | |
| end_sec=$(echo "$end_time" | awk -F: '{ if (NF==3) print ($1 * 3600) + ($2 * 60) + $3; else if (NF==2) print ($1 * 60) + $2; else print $1 }') | |
| duration=$end_sec | |
| fi | |
| # Determine if we need to scale based on resolution | |
| # Check both width and height to handle both landscape and portrait videos | |
| if [ "$width" -ge 1920 ] || [ "$height" -ge "$DISCORD_HD_THRESHOLD" ]; then | |
| do_desize=true | |
| # Determine orientation and calculate target dimensions | |
| if [ "$width" -gt "$height" ]; then | |
| # Landscape video - scale based on height | |
| target_height=$DISCORD_TARGET_HEIGHT | |
| # Calculate width maintaining aspect ratio: width * (target_height / height) | |
| # Use -2 to ensure even number (required by some codecs) | |
| scale_filter="scale=-2:${target_height}" | |
| target_width=$(echo "scale=0; $width * $target_height / $height" | bc) | |
| target_width=$(( (target_width + 1) / 2 * 2 )) # Round to even | |
| else | |
| # Portrait video - scale based on width | |
| target_width=$DISCORD_TARGET_HEIGHT | |
| # Calculate height maintaining aspect ratio | |
| scale_filter="scale=${target_width}:-2" | |
| target_height=$(echo "scale=0; $height * $target_width / $width" | bc) | |
| target_height=$(( (target_height + 1) / 2 * 2 )) # Round to even | |
| fi | |
| echo "π Auto-scale enabled: ${width}x${height} β ${target_width}x${target_height}" | |
| echo " βΉοΈ Targeting ~${DISCORD_TARGET_HEIGHT}p while preserving aspect ratio" | |
| fi | |
| # Calculate target bitrate to stay under MAX_DISCORD_FILE_SIZE_MB | |
| # Formula: (MAX_SIZE_MB * 8 * 1024 / duration_sec) - AUDIO_BITRATE_K | |
| if [ -n "$duration" ] && [ "$(echo "$duration > 0" | bc)" -eq 1 ]; then | |
| duration_rounded=$(printf "%.1f" "$duration") | |
| echo "β±οΈ Duration: ${duration_rounded}s" | |
| target_bitrate=$(echo "scale=0; ($MAX_DISCORD_FILE_SIZE_MB * 8 * 1024 / $duration) - $DISCORD_AUDIO_BITRATE_K" | bc) | |
| # Ensure bitrate is at least minimum | |
| if [ "$(echo "$target_bitrate < $DISCORD_MIN_VIDEO_BITRATE_K" | bc)" -eq 1 ]; then | |
| target_bitrate=$DISCORD_MIN_VIDEO_BITRATE_K | |
| echo "β οΈ Using minimum video bitrate: ${DISCORD_MIN_VIDEO_BITRATE_K}k" | |
| else | |
| echo "π Calculated video bitrate: ${target_bitrate}k" | |
| fi | |
| bitrate="${target_bitrate}k" | |
| else | |
| # Fallback if we can't determine duration | |
| bitrate="1M" | |
| echo "β οΈ Couldn't determine duration, using fallback bitrate: ${bitrate}" | |
| fi | |
| fi | |
| # Build output filename if not provided | |
| if [ -z "$output_file" ]; then | |
| base="${input_file%.*}" | |
| suffix="" | |
| if [ "$discord_mode" = true ]; then | |
| suffix="${suffix}_discord" | |
| fi | |
| if [ -n "$start_time" ] || [ -n "$end_time" ]; then | |
| suffix="${suffix}_${start_time:-0}-${end_time:-end}" | |
| suffix=$(echo "$suffix" | tr ':' '-') | |
| fi | |
| if [ "$do_desize" = true ] && [ "$discord_mode" = false ]; then | |
| suffix="${suffix}_d" | |
| fi | |
| if [ -n "$bitrate" ] && [ "$discord_mode" = false ]; then | |
| suffix="${suffix}_b${bitrate}" | |
| fi | |
| output_file="${base}${suffix}.mp4" | |
| else | |
| # Add .mp4 extension if no extension provided | |
| case "$output_file" in | |
| *.*) | |
| # Has extension, keep as is | |
| ;; | |
| *) | |
| # No extension, add .mp4 | |
| output_file="${output_file}.mp4" | |
| ;; | |
| esac | |
| fi | |
| echo "πΎ Output: $output_file" | |
| echo "" | |
| # Show active operations | |
| echo "π¬ Active operations:" | |
| if [ -n "$start_time" ]; then | |
| echo " β±οΈ Start time: $start_time" | |
| fi | |
| if [ -n "$end_time" ]; then | |
| echo " β±οΈ End time: $end_time" | |
| fi | |
| if [ "$do_desize" = true ]; then | |
| if [ "$discord_mode" = true ]; then | |
| echo " π Scale to ~${DISCORD_TARGET_HEIGHT}p (aspect ratio preserved)" | |
| else | |
| echo " π Desize (scale Γ·2)" | |
| fi | |
| fi | |
| if [ -n "$bitrate" ]; then | |
| echo " πΎ Bitrate: $bitrate" | |
| fi | |
| echo "" | |
| # Build ffmpeg command | |
| ffmpeg_cmd="ffmpeg -i \"$input_file\"" | |
| # Add time parameters | |
| if [ -n "$start_time" ]; then | |
| ffmpeg_cmd="$ffmpeg_cmd -ss \"$start_time\"" | |
| fi | |
| if [ -n "$end_time" ]; then | |
| ffmpeg_cmd="$ffmpeg_cmd -to \"$end_time\"" | |
| fi | |
| # Build filter chain | |
| filter="" | |
| if [ "$do_desize" = true ]; then | |
| if [ -n "$scale_filter" ]; then | |
| # Discord mode with custom scale filter | |
| filter="$scale_filter" | |
| else | |
| # Regular desize mode (Γ·2) | |
| filter="scale=iw/2:ih/2" | |
| fi | |
| fi | |
| if [ -n "$filter" ]; then | |
| ffmpeg_cmd="$ffmpeg_cmd -vf \"$filter\"" | |
| fi | |
| # Add bitrate | |
| if [ -n "$bitrate" ]; then | |
| ffmpeg_cmd="$ffmpeg_cmd -b:v $bitrate" | |
| fi | |
| # Add async for time cuts | |
| if [ -n "$start_time" ] || [ -n "$end_time" ]; then | |
| ffmpeg_cmd="$ffmpeg_cmd -async 1" | |
| fi | |
| # Add output file with ffmpeg logging options | |
| ffmpeg_cmd="$ffmpeg_cmd \"$output_file\" -hide_banner -loglevel error -stats" | |
| # Execute | |
| echo "βοΈ Processing..." | |
| eval $ffmpeg_cmd | |
| # Check if ffmpeg succeeded | |
| if [ $? -ne 0 ]; then | |
| echo "" | |
| echo "β Processing failed!" | |
| exit 1 | |
| fi | |
| echo "" | |
| echo "β Processing complete!" | |
| # Verify file size in Discord mode | |
| if [ "$discord_mode" = true ]; then | |
| file_size=$(stat -f%z "$output_file" 2>/dev/null || stat -c%s "$output_file" 2>/dev/null) | |
| file_size_mb=$(echo "scale=2; $file_size / 1048576" | bc) | |
| echo "π Final file size: ${file_size_mb} MB (limit: ${MAX_DISCORD_FILE_SIZE_MB} MB)" | |
| if [ "$(echo "$file_size_mb > $MAX_DISCORD_FILE_SIZE_MB" | bc)" -eq 1 ]; then | |
| echo "β οΈ Warning: File size exceeds ${MAX_DISCORD_FILE_SIZE_MB} MB! Consider cutting the video shorter." | |
| else | |
| echo "β File is within Discord size limit!" | |
| fi | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment