Skip to content

Instantly share code, notes, and snippets.

@krasi-georgiev
Last active April 18, 2025 14:14
Show Gist options
  • Save krasi-georgiev/5f891acb246a36d475f0c5c451e658e9 to your computer and use it in GitHub Desktop.
Save krasi-georgiev/5f891acb246a36d475f0c5c451e658e9 to your computer and use it in GitHub Desktop.
convert and merge files
#!/bin/bash
# ================================================ echo "1) Convert videos (default)"
#
# Run directly from this gist:
#
# bash <(curl -sL https://gist.githubusercontent.com/krasi-georgiev/5f891acb246a36d475f0c5c451e658e9/raw/convert.sh)
#
# ================================================
echo "1) Convert videos (default)"
echo "2) Merge converted videos"
read -p "Enter your choice (1 or 2): " action_choice
action_choice=${action_choice:-1}
if [[ "$action_choice" == "1" ]]; then
echo "1) Low Quality 576p (Fast, Small File Size) [default]"
echo "2) High Quality 1080p"
echo "3) High Quality 1080p 60fps"
read -p "Enter your choice (1, 2 or 3): " quality_choice
quality_choice=${quality_choice:-1}
fi
convert_videos() {
case $quality_choice in
1)
RESOLUTION="1024:576"
FPS=30
CRF=28
PRESET="ultrafast"
AUDIO_BITRATE="96k"
;;
2)
RESOLUTION="1920:1080"
FPS=30
CRF=20
PRESET="slow"
AUDIO_BITRATE="192k"
;;
3)
RESOLUTION="1920:1080"
FPS=60
CRF=20
PRESET="slow"
AUDIO_BITRATE="192k"
;;
*)
echo "Invalid selection!"
exit 1
;;
esac
echo "🎬 Converting to ${RESOLUTION} with ${FPS}fps"
mapfile -d '' files < <(find "$(pwd)" -maxdepth 1 -type f \( -iname '*.mp4' -o -iname '*.mov' \) -print0 | sort -z)
converted_folder="converted_${RESOLUTION}_${FPS}fps"
mkdir -p "$converted_folder"
for f in "${files[@]}"; do
output="$converted_folder/$(basename "${f%.*}").mp4"
if [[ -f "$output" ]]; then
echo "Skipping existing file: $output"
continue
fi
echo "🎬 Converting: $f -> $output"
ffmpeg -fflags +genpts -avoid_negative_ts make_zero -i "$f" \
-vf "fps=$FPS,scale=$RESOLUTION:force_original_aspect_ratio=decrease,pad=$RESOLUTION:(ow-iw)/2:(oh-ih)/2,setsar=1:1,format=yuv420p" \
-r "$FPS" -fps_mode cfr -vsync cfr -reset_timestamps 1 \
-af "aresample=async=1000:first_pts=0" -ar 48000 -ac 2 -c:a aac -b:a "$AUDIO_BITRATE" \
-c:v libx265 -preset "$PRESET" -crf "$CRF" -g 30 \
-movflags +faststart "$output"
done
merge_videos "$converted_folder"
}
merge_videos() {
folder="$(realpath "$1")"
echo "🎬 Merging the following files in folder '$folder':"
mapfile -d '' files < <(find "$folder" -maxdepth 1 -type f -iname '*.mp4' -print0 | sort -z)
if [[ ${#files[@]} -eq 0 ]]; then
echo "No files found to merge in $folder."
exit 1
fi
for f in "${files[@]}"; do
echo " $f"
done
concat_list=$(mktemp)
metadata_file=$(mktemp)
echo ";FFMETADATA1" > "$metadata_file"
start_time=0
for f in "${files[@]}"; do
abs_path=$(realpath "$f")
echo "file '$abs_path'" >> "$concat_list"
duration=$(ffprobe -v error -select_streams v:0 -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 "$abs_path" | awk '{printf "%d", $1 * 1000}')
end_time=$((start_time + duration))
filename=$(basename "$f")
echo -e "\n[CHAPTER]
TIMEBASE=1/1000
START=$start_time
END=$end_time
title=$filename" >> "$metadata_file"
start_time=$end_time
done
output_path="$folder/merged_$(basename "$folder").mp4"
ffmpeg -f concat -safe 0 -i "$concat_list" -i "$metadata_file" \
-map 0 -map_metadata 1 -c copy -movflags +faststart "$output_path"
echo "✅ Merged with chapters: $output_path"
rm "$concat_list" "$metadata_file"
}
case $action_choice in
1) convert_videos ;;
2)
if [[ -n "$converted_folder" ]]; then
merge_videos "$converted_folder"
else
echo "Select a folder to merge:"
folders=($(find "$(pwd)" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort -u))
if [[ ${#folders[@]} -eq 0 ]]; then
echo "No folders found."
exit 1
fi
for i in "${!folders[@]}"; do
printf "%d) %s\n" "$((i + 1))" "${folders[$i]}"
done
read -p "Enter your choice: " folder_index
folder_index=$((folder_index - 1))
if [[ "$folder_index" -ge 0 && "$folder_index" -lt "${#folders[@]}" ]]; then
selected_folder="${folders[$folder_index]}"
selected_folder="$(realpath "$selected_folder")"
merge_videos "$selected_folder"
else
echo "Invalid selection!"
exit 1
fi
fi
;;
*)
echo "Invalid selection!"
exit 1
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment