Last active
April 18, 2025 14:14
-
-
Save krasi-georgiev/5f891acb246a36d475f0c5c451e658e9 to your computer and use it in GitHub Desktop.
convert and merge files
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/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