#!/usr/bin/env bash set -euo pipefail GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' log() { echo -e "${GREEN}[INFO]${NC} $1" } fail() { echo -e "${RED}[ERROR]${NC} $1" exit 1 } # Check if required Python modules are installed if ! python3 -c "import ffpb" &>/dev/null; then fail "Python module 'ffpb' is not installed. Please install it and try again." fi if ! python3 -c "import google_speech" &>/dev/null; then fail "Python module 'google_speech' is not installed. Please install it and try again." fi if [[ -d "$HOME/.local/bin" ]]; then export PATH="$HOME/.local/bin:$PATH" fi # Install required packages if command -v apt-get >/dev/null 2>&1; then apt_packages=("bc" "ffmpegthumbnailer" "libffmpegthumbnailer-dev" "libsox-fmt-all" "python3-pip" "sox") missing_apt_pkgs=() for pkg in "${apt_packages[@]}"; do if ! dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "ok installed"; then missing_apt_pkgs+=("$pkg") fi done if [[ ${#missing_apt_pkgs[@]} -gt 0 ]]; then log "Installing: ${missing_apt_pkgs[*]}" echo sudo apt -y install "${missing_apt_pkgs[@]}" || fail "Failed to install required packages." fi elif command -v pacman >/dev/null 2>&1; then pacman_packages=("bc" "ffmpegthumbnailer" "python-pip" "sox") missing_pacman_pkgs=() for pkg in "${pacman_packages[@]}"; do if ! pacman -Qi "$pkg" >/dev/null 2>&1; then missing_pacman_pkgs+=("$pkg") fi done if [[ ${#missing_pacman_pkgs[@]} -gt 0 ]]; then log "Installing: ${missing_pacman_pkgs[*]}" echo sudo pacman -Sy --noconfirm --needed "${missing_pacman_pkgs[@]}" || fail "Failed to install required packages." fi else fail "Unsupported package manager. Please install the required packages manually." fi # Check for input videos shopt -s nullglob vid_files=(*.mp4 *.mkv) shopt -u nullglob if [[ ${#vid_files[@]} -eq 0 ]]; then google_speech "No input videos were located." 2>/dev/null || true fail "No input videos were located." fi random_dir=$(mktemp -d) trap 'rm -rf "$random_dir"' EXIT for vid in "${vid_files[@]}"; do [[ -f "$vid" ]] || continue # Get video metadata; skip file if ffprobe fails aspect_ratio=$(ffprobe -hide_banner -select_streams v:0 -show_entries stream=display_aspect_ratio -of default=nk=1:nw=1 -pretty "$vid" 2>/dev/null) || { echo "Warning: Skipping $vid (ffprobe failed)"; continue; } file_length=$(ffprobe -hide_banner -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$vid" 2>/dev/null) || continue max_rate=$(ffprobe -hide_banner -show_entries format=bit_rate -of default=nk=1:nw=1 -pretty "$vid" 2>/dev/null) || continue file_height=$(ffprobe -hide_banner -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 -pretty "$vid" 2>/dev/null) || continue file_width=$(ffprobe -hide_banner -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 -pretty "$vid" 2>/dev/null) || continue # Validate that we got numeric-ish values if [[ -z "$file_length" || -z "$max_rate" ]]; then echo "Warning: Could not read metadata for $vid, skipping." continue fi file_in="$vid" fext="${file_in##*.}" file_out="$random_dir/${file_in%.*} (x265).$fext" # Extract numeric bitrate (strip trailing units like " kbit/s") trim="${max_rate%% *}" trim="${trim%%[!0-9.]*}" if [[ -z "$trim" ]]; then echo "Warning: Could not parse bitrate for $vid, skipping." continue fi # Calculate encoding parameters trim=$(bc <<< "scale=2; $trim * 1000") br=$(bc <<< "scale=2; $trim / 2") bitrate="${br%.*}" maxrate=$(( bitrate * 2 )) bufsize=$(( bitrate * 2 )) length_raw="${file_length%.*}" length=$(( length_raw / 60 )) clear cat </dev/null || true mv "$file_out" "$PWD/${file_in%.*} (x265).$fext" else google_speech "Video conversion failed for $file_in." 2>/dev/null || true echo -e "${RED}[ERROR]${NC} Video conversion failed for $file_in." fi clear done