#!/bin/bash # Choose which Extensions are acceptable extensions=("mp4" "mkv" "avi" "mov" "wmv" "flv" "webm" "lrv" "MP4" "MKV" "AVI" "MOV" "WMV" "FLV" "WEBM" "LRV" "GIF" "gif") # Language code mapping declare -A LANG_CODES=( ["eng"]="English" ["en"]="English" ["spa"]="Spanish" ["es"]="Spanish" ["fra"]="French" ["fr"]="French" ["deu"]="German" ["ger"]="German" ["de"]="German" ["jpn"]="Japanese" ["ja"]="Japanese" ["ita"]="Italian" ["it"]="Italian" ["por"]="Portuguese" ["pt"]="Portuguese" ["rus"]="Russian" ["ru"]="Russian" ["chi"]="Chinese" ["zh"]="Chinese" ["kor"]="Korean" ["ko"]="Korean" ["dut"]="Dutch" ["nl"]="Dutch" ["swe"]="Swedish" ["sv"]="Swedish" ["fin"]="Finnish" ["fi"]="Finnish" ["pol"]="Polish" ["pl"]="Polish" ["ara"]="Arabic" ["ar"]="Arabic" ["hin"]="Hindi" ["hi"]="Hindi" ["tur"]="Turkish" ["tr"]="Turkish" ["und"]="Undefined" [" "]="Undefined" ["ab"]="Abkhazian" ["abk"]="Abkhazian" ["aa"]="Afar" ["aar"]="Afar" ["af"]="Afrikaans" ["afr"]="Afrikaans" ["ak"]="Akan" ["aka"]="Akan" ["twi"]="Twi" ["fat"]="Fanti" ["sq"]="Albanian" ["sqi"]="Albanian" ["alb"]="Albanian" ["am"]="Amharic" ["amh"]="Amharic" ["arb"]="Arabic" ["an"]="Aragonese" ["arg"]="Aragonese" ["hy"]="Armenian" ["hye"]="Armenian" ["arm"]="Armenian" ["as"]="Assamese" ["asm"]="Assamese" ["av"]="Avaric" ["ava"]="Avaric" ["ae"]="Avestan" ["ave"]="Avestan" ["ay"]="Aymara" ["aym"]="Aymara" ["az"]="Azerbaijani" ["aze"]="Azerbaijani" ["bm"]="Bambara" ["bam"]="Bambara" ["ba"]="Bashkir" ["bak"]="Bashkir" ["eu"]="Basque" ["eus"]="Basque" ["baq"]="Basque" ["be"]="Belarusian" ["bel"]="Belarusian" ["bn"]="Bengali" ["ben"]="Bengali" ["bi"]="Bislama" ["bis"]="Bislama" ["bs"]="Bosnian" ["bos"]="Bosnian" ["br"]="Breton" ["bre"]="Breton" ["bg"]="Bulgarian" ["bul"]="Bulgarian" ["my"]="Burmese" ["mya"]="Burmese" ["ca"]="Catalan" ["cat"]="Catalan" ["ch"]="Chamorro" ["cha"]="Chamorro" ["ce"]="Chechen" ["che"]="Chechen" ["ny"]="Chichewa" ["nya"]="Chichewa" ["zho"]="Chinese" ["cu"]="Church Slavonic" ["chu"]="Church Slavonic" ["cv"]="Chuvash" ["chv"]="Chuvash" ["kw"]="Cornish" ["cor"]="Cornish" ["co"]="Corsican" ["cos"]="Corsican" ["cr"]="Cree" ["cre"]="Cree" ["hr"]="Croatian" ["hrv"]="Croatian" ["cs"]="Czech" ["ces"]="Czech" ["cze"]="Czech" ["da"]="Danish" ["dan"]="Danish" ["dv"]="Divehi" ["div"]="Divehi" ["dz"]="Dzongkha" ["dzo"]="Dzongkha" ["eo"]="Esperanto" ["epo"]="Esperanto" ["et"]="Estonian" ["est"]="Estonian" ["ee"]="Ewe" ["ewe"]="Ewe" ["fo"]="Faroese" ["fao"]="Faroese" ["fj"]="Fijian" ["fij"]="Fijian" ["fre"]="French" ["fy"]="Western Frisian" ["fry"]="Western Frisian" ["ff"]="Fulah" ["ful"]="Fulah" ["gd"]="Gaelic, Scottish Gaelic" ["gla"]="Gaelic" ["gl"]="Galician" ["glg"]="Galician" ["lg"]="Ganda" ["lug"]="Ganda" ["ka"]="Georgian" ["kat"]="Georgian" ["geo"]="Georgian" ["el"]="Greek" ["ell"]="Greek" ["gre"]="Greek" ["kl"]="Kalaallisut" ["kal"]="Kalaallisut" ["gn"]="Guarani" ["grn"]="Guarani" ["gu"]="Gujarati" ["guj"]="Gujarati" ["ht"]="Haitian Creole" ["hat"]="Haitian Creole" ["ha"]="Hausa" ["hau"]="Hausa" ["he"]="Hebrew" ["heb"]="Hebrew" ["hz"]="Herero" ["her"]="Herero" ["ho"]="Hiri Motu" ["hmo"]="Hiri Motu" ["hu"]="Hungarian" ["hun"]="Hungarian" ["is"]="Icelandic" ["isl"]="Icelandic" ["ice"]="Icelandic" ["io"]="Ido" ["ido"]="Ido" ["ig"]="Igbo" ["ibo"]="Igbo" ["id"]="Indonesian" ["ind"]="Indonesian" ["ia"]="Interlingua" ["ina"]="Interlingua" ["ie"]="Interlingue" ["ile"]="Interlingue" ["iu"]="Inuktitut" ["iku"]="Inuktitut" ["ik"]="Inupiaq" ["ipk"]="Inupiaq" ["ga"]="Irish" ["gle"]="Irish" ["jv"]="Javanese" ["jav"]="Javanese" ["kn"]="Kannada" ["kan"]="Kannada" ["kr"]="Kanuri" ["kau"]="Kanuri" ["ks"]="Kashmiri" ["kas"]="Kashmiri" ["kk"]="Kazakh" ["kaz"]="Kazakh" ["km"]="Central Khmer" ["khm"]="Central Khmer" ["ki"]="Kikuyu" ["kik"]="Kikuyu" ["rw"]="Kinyarwanda" ["kin"]="Kinyarwanda" ["ky"]="Kyrgyz" ["kir"]="Kyrgyz" ["kv"]="Komi" ["kom"]="Komi" ["kg"]="Kongo" ["kon"]="Kongo" ["kj"]="Kuanyama" ["kua"]="Kuanyama" ["ku"]="Kurdish" ["kur"]="Kurdish" ["lo"]="Lao" ["lao"]="Lao" ["la"]="Latin" ["lat"]="Latin" ["lv"]="Latvian" ["lav"]="Latvian" ["li"]="Limburgan" ["lim"]="Limburgan" ["ln"]="Lingala" ["lin"]="Lingala" ["lt"]="Lithuanian" ["lit"]="Lithuanian" ["lu"]="Luba-Katanga" ["lub"]="Luba-Katanga" ["lb"]="Luxembourgish" ["ltz"]="Luxembourgish" ["mk"]="Macedonian" ["mkd"]="Macedonian" ["mac"]="Macedonian" ["mg"]="Malagasy" ["mlg"]="Malagasy" ["ms"]="Malay" ["msa"]="Malay" ["ml"]="Malayalam" ["mal"]="Malayalam" ["mt"]="Maltese" ["mlt"]="Maltese" ["gv"]="Manx" ["glv"]="Manx" ["mi"]="Maori" ["mri"]="Maori" ["mao"]="Maori" ["mr"]="Marathi" ["mar"]="Marathi" ["mh"]="Marshallese" ["mah"]="Marshallese" ["mn"]="Mongolian" ["mon"]="Mongolian" ["na"]="Nauru" ["nau"]="Nauru" ["nv"]="Navajo" ["nav"]="Navajo" ["nd"]="North Ndebele" ["nde"]="North Ndebele" ["nr"]="South Ndebele" ["nbl"]="South Ndebele" ["ng"]="Ndonga" ["ndo"]="Ndonga" ["ne"]="Nepali" ["nep"]="Nepali" ["no"]="Norwegian" ["nor"]="Norwegian" ["nb"]="Norwegian Bokmål" ["nob"]="Norwegian Bokmål" ["nn"]="Norwegian Nynorsk" ["nno"]="Norwegian Nynorsk" ["oc"]="Occitan" ["oci"]="Occitan" ["oj"]="Ojibwa" ["oji"]="Ojibwa" ["or"]="Oriya" ["ori"]="Oriya" ["om"]="Oromo" ["orm"]="Oromo" ["os"]="Ossetian" ["oss"]="Ossetian" ["pi"]="Pali" ["pli"]="Pali" ["ps"]="Pashto" ["pus"]="Pashto" ["fa"]="Persian" ["fas"]="Persian" ["per"]="Persian" ["pa"]="Punjabi" ["pan"]="Punjabi" ["qu"]="Quechua" ["que"]="Quechua" ["ro"]="Romanian" ["ron"]="Romanian" ["rum"]="Romanian" ["rm"]="Romansh" ["roh"]="Romansh" ["rn"]="Rundi" ["run"]="Rundi" ["se"]="Northern Sami" ["sme"]="Northern Sami" ["sm"]="Samoan" ["smo"]="Samoan" ["sg"]="Sango" ["sag"]="Sango" ["sa"]="Sanskrit" ["san"]="Sanskrit" ["sc"]="Sardinian" ["srd"]="Sardinian" ["sr"]="Serbian" ["srp"]="Serbian" ["sn"]="Shona" ["sna"]="Shona" ["sd"]="Sindhi" ["snd"]="Sindhi" ["si"]="Sinhala" ["sin"]="Sinhala" ["sk"]="Slovak" ["slk"]="Slovak" ["slo"]="Slovak" ["sl"]="Slovenian" ["slv"]="Slovenian" ["so"]="Somali" ["som"]="Somali" ["st"]="Southern Sotho" ["sot"]="Southern Sotho" ["su"]="Sundanese" ["sun"]="Sundanese" ["sw"]="Swahil" ["swa"]="Swahili" ["ss"]="Swati" ["ssw"]="Swati" ["tl"]="Tagalog" ["tgl"]="Tagalog" ["ty"]="Tahitian" ["tah"]="Tahitian" ["tg"]="Tajik" ["tgk"]="Tajik" ["ta"]="Tamil" ["tam"]="Tamil" ["tt"]="Tatar" ["tat"]="Tatar" ["te"]="Telugu" ["tel"]="Telugu" ["th"]="Thai" ["tha"]="Thai" ["bo"]="Tibetan" ["bod"]="Tibetan" ["tib"]="Tibetan" ["ti"]="Tigrinya" ["tir"]="Tigrinya" ["to"]="Tongan" ["ton"]="Tongan" ["ts"]="Tsonga" ["tso"]="Tsonga" ["tn"]="Tswana" ["tsn"]="Tswana" ["tk"]="Turkmen" ["tuk"]="Turkmen" ["ug"]="Uighur" ["uig"]="Uighur" ["uk"]="Ukrainian" ["ukr"]="Ukrainian" ["ur"]="Urdu" ["urd"]="Urdu" ["uz"]="Uzbek" ["uzb"]="Uzbek" ["ve"]="Venda" ["ven"]="Venda" ["vi"]="Vietnamese" ["vie"]="Vietnamese" ["vo"]="Volapük" ["vol"]="Volapük" ["wa"]="Walloon" ["wln"]="Walloon" ["cy"]="Welsh" ["cym"]="Welsh" ["wel"]="Welsh" ["wo"]="Wolof" ["wol"]="Wolof" ["xh"]="Xhosa" ["xho"]="Xhosa" ["ii"]="Sichuan Yi" ["iii"]="Sichuan Yi" ["yi"]="Yiddish" ["yid"]="Yiddish" ["yo"]="Yoruba" ["yor"]="Yoruba" ["za"]="Zhuang" ["zha"]="Zhuang" ["zu"]="Zulu" ["zul"]="Zulu" ) declare -A CONTAINER_NAMES=( ["mov"]="QuickTime" ["mp4"]="MP4" ["mkv"]="Matroska" ["webm"]="WebM" ["avi"]="AVI" ["flv"]="FLV" ["wmv"]="WMV" ) # Function to translate language codes translate_language() { local code="$1" # Sanitize input: remove spaces and convert to lowercase code=$(echo "$code" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]') # Check for valid subscript and handle fallback if [[ -z "$code" || ! ${LANG_CODES[$code]+_} ]]; then echo "Undefined" else echo "${LANG_CODES[$code]}" fi } # Function to format file size in human-readable form format_file_size() { local size_bytes="$1" if [ "$size_bytes" -lt 1024 ]; then echo "${size_bytes}B" elif [ "$size_bytes" -lt 1048576 ]; then echo "$((size_bytes / 1024))KB" elif [ "$size_bytes" -lt 1073741824 ]; then echo "$((size_bytes / 1048576))MB" else echo "$((size_bytes / 1073741824))GB" fi } # Function to format duration into HH:MM:SS format_duration() { local duration="$1" printf "%02d:%02d:%02d" $((duration/3600)) $(((duration%3600)/60)) $((duration%60)) } # Function to extract video, audio, and subtitle details for a single file process_file() { local file="$1" local file_size file_size=$(stat --printf="%s" "$file") formatted_size=$(format_file_size "$file_size") # Extract video duration in seconds duration_seconds=$(ffprobe -v error -select_streams v:0 -show_entries format=duration -of csv=p=0 "$file" | cut -d'.' -f1) duration_formatted=$(format_duration "$duration_seconds") # Extract video codec, width, height, and pixel format (like yuv420p(tv, bt709)) video_info=$(ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name,width,height,pix_fmt,color_space \ -of default=noprint_wrappers=1:nokey=1 "$file") codec=$(echo "$video_info" | sed -n '1p') width=$(echo "$video_info" | sed -n '2p') height=$(echo "$video_info" | sed -n '3p') framerate=$(ffprobe -v 0 -select_streams v:0 -show_entries stream=r_frame_rate -of csv=p=0 "$file" | awk -F/ '{printf "%.3f", $1/$2}') dar=$(ffprobe -v error -select_streams v:0 -show_entries stream=display_aspect_ratio -of default=noprint_wrappers=1:nokey=1 "$file") pix_fmt=$(echo "$video_info" | sed -n '4p') color_space=$(echo "$video_info" | sed -n '5p') # Detect interlacing using ffmpeg idet filter (analyzing a short portion to keep it fast) interlace_result=$(ffmpeg -filter:v idet -frames:v 100 -an -f rawvideo -y /dev/null -i "$file" 2>&1) tff_count=$(echo "$interlace_result" | grep 'TFF:' | tail -n1 | grep -o 'TFF:[ ]*[0-9]*' | grep -o '[0-9]*') bff_count=$(echo "$interlace_result" | grep 'BFF:' | tail -n1 | grep -o 'BFF:[ ]*[0-9]*' | grep -o '[0-9]*') progressive_count=$(echo "$interlace_result" | grep 'Progressive:' | tail -n1 | grep -o 'Progressive:[ ]*[0-9]*' | grep -o '[0-9]*') if [[ "$progressive_count" -gt "$tff_count" && "$progressive_count" -gt "$bff_count" ]]; then interlace_status="Progressive" elif [[ "$tff_count" -gt "$bff_count" ]]; then interlace_status="Interlaced (TFF)" elif [[ "$bff_count" -gt "$tff_count" ]]; then interlace_status="Interlaced (BFF)" else interlace_status="Unknown" fi # Extract audio information (language, codec, and channel layout) audio_info=$(ffprobe -v quiet -select_streams a -show_entries stream=codec_name,channel_layout:stream_tags=language \ -of default=noprint_wrappers=1:nokey=1 "$file" | paste -sd "," -) # Extract subtitle information (language) subtitle_info=$(ffprobe -v quiet -select_streams s -show_entries stream_tags=language \ -of default=noprint_wrappers=1:nokey=1 "$file" | paste -sd "," -) # Format video output video_output="Video: ${codec} (${width}x${height}@${framerate}) [${dar}], ${pix_fmt} (${color_space}), ${interlace_status}" # Format audio output audio_output="Audio: " languages="" details="" IFS=',' read -r -a audio_streams <<< "$audio_info" for (( i=0; i<${#audio_streams[@]}; i+=3 )); do audio_codec="${audio_streams[i]}" channel_layout="${audio_streams[i+1]}" lang_code="${audio_streams[i+2]}" lang=$(translate_language "${lang_code:-" "}") # Collect languages languages+="$lang" # Collect detailed information details+="$lang $audio_codec $channel_layout" # Add comma separator if not the last item if (( i+3 < ${#audio_streams[@]} )); then languages+=", " details+=", " fi done # Format output with languages first, followed by detailed info in brackets audio_output+="$languages [$details]" # Format subtitle output subtitle_output="Subtitles: " if [ -n "$subtitle_info" ]; then IFS=',' read -r -a subtitle_streams <<< "$subtitle_info" for lang in "${subtitle_streams[@]}"; do subtitle_output+=$(translate_language "$lang") subtitle_output+=", " done subtitle_output=${subtitle_output%, } else subtitle_output="Subtitles: None" fi # Output the result for the file echo "$file ($formatted_size, Duration: $duration_formatted):" echo -e "\t$video_output" echo -e "\t$audio_output" echo -e "\t$subtitle_output" } # Main script logic if [ -z "$1" ]; then for ext in "${extensions[@]}"; do for file in *."$ext"; do [ -e "$file" ] || continue process_file "$file" done done elif [ -d "$1" ]; then for file in "$1"/*.{mp4,mkv,avi,mov}; do [ -e "$file" ] || continue process_file "$file" done elif [ -f "$1" ]; then process_file "$1" else echo "Error: $1 is not a valid file or directory." exit 1 fi # #!/bin/bash # # Choose which Extensions are acceptable # extensions=("mp4" "mkv" "avi" "mov" "m4v" "flv" "lrv") # # Language code translation associative array # declare -A LANG_CODES=( # ["eng"]="English" # ["en"]="English" # ["spa"]="Spanish" # ["es"]="Spanish" # ["fra"]="French" # ["fr"]="French" # ["deu"]="German" # ["ger"]="German" # ["de"]="German" # ["jpn"]="Japanese" # ["ja"]="Japanese" # ["ita"]="Italian" # ["it"]="Italian" # ["por"]="Portuguese" # ["pt"]="Portuguese" # ["rus"]="Russian" # ["ru"]="Russian" # ["chi"]="Chinese" # ["zh"]="Chinese" # ["kor"]="Korean" # ["ko"]="Korean" # ["dut"]="Dutch" # ["nl"]="Dutch" # ["swe"]="Swedish" # ["sv"]="Swedish" # ["fin"]="Finnish" # ["fi"]="Finnish" # ["pol"]="Polish" # ["pl"]="Polish" # ["ara"]="Arabic" # ["ar"]="Arabic" # ["hin"]="Hindi" # ["hi"]="Hindi" # ["tur"]="Turkish" # ["tr"]="Turkish" # ["und"]="Undefined" # [" "]="Undefined" # ) # # Function to translate language codes # translate_language() { # local code="$1" # # Sanitize input: remove spaces and convert to lowercase # code=$(echo "$code" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]') # # Debugging output # # echo "DEBUG: Translating code: '$code'" >&2 # # Check for valid subscript and handle fallback # if [[ -z "$code" || ! ${LANG_CODES[$code]+_} ]]; then # echo "Undefined" # else # echo "${LANG_CODES[$code]}" # fi # } # # Function to format file size in human-readable form # format_file_size() { # local size_bytes="$1" # if [ "$size_bytes" -lt 1024 ]; then # echo "${size_bytes}B" # elif [ "$size_bytes" -lt 1048576 ]; then # echo "$((size_bytes / 1024))KB" # elif [ "$size_bytes" -lt 1073741824 ]; then # echo "$((size_bytes / 1048576))MB" # else # echo "$((size_bytes / 1073741824))GB" # fi # } # # Function to extract video, audio, and subtitle details for a single file # process_file() { # local file="$1" # local file_size # file_size=$(stat --printf="%s" "$file") # formatted_size=$(format_file_size "$file_size") # # Extract video codec, width, height, and pixel format (like yuv420p(tv, bt709)) # video_info=$(ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name,width,height,pix_fmt,color_space \ # -of default=noprint_wrappers=1:nokey=1 "$file") # codec=$(echo "$video_info" | sed -n '1p') # width=$(echo "$video_info" | sed -n '2p') # height=$(echo "$video_info" | sed -n '3p') # pix_fmt=$(echo "$video_info" | sed -n '4p') # color_space=$(echo "$video_info" | sed -n '5p') # # Extract audio information (language, codec, and channel layout) # audio_info=$(ffprobe -v quiet -select_streams a -show_entries stream=codec_name,channel_layout:stream_tags=language \ # -of default=noprint_wrappers=1:nokey=1 "$file" | paste -sd "," -) # # Extract subtitle information (language) # subtitle_info=$(ffprobe -v quiet -select_streams s -show_entries stream_tags=language \ # -of default=noprint_wrappers=1:nokey=1 "$file" | paste -sd "," -) # # Format video output # video_output="Video: ${codec} (${width}x${height}), ${pix_fmt} (${color_space})" # # Format audio output # audio_output="Audio: " # IFS=',' read -r -a audio_streams <<< "$audio_info" # for (( i=0; i<${#audio_streams[@]}; i+=3 )); do # audio_codec="${audio_streams[i]}" # channel_layout="${audio_streams[i+1]}" # lang_code="${audio_streams[i+2]}" # lang=$(translate_language "${lang_code:-" "}") # audio_output+="${lang} ${audio_codec} ${channel_layout}" # if (( i+3 < ${#audio_streams[@]} )); then # audio_output+=", " # fi # done # # Format subtitle output # subtitle_output="Subtitles: " # if [ -n "$subtitle_info" ]; then # IFS=',' read -r -a subtitle_streams <<< "$subtitle_info" # for lang in "${subtitle_streams[@]}"; do # subtitle_output+=$(translate_language "$lang") # subtitle_output+=", " # done # # Remove the last comma # subtitle_output=${subtitle_output%, } # else # subtitle_output="Subtitles: None" # fi # # Output the result for the file # echo "$file ($formatted_size):" # echo -e "\t$video_output" # echo -e "\t$audio_output" # echo -e "\t$subtitle_output" # } # # Main script logic # if [ -z "$1" ]; then # for ext in "${extensions[@]}"; do # # No argument provided, process all video files in the current directory # for file in *."$ext"; do # [ -e "$file" ] || continue # Skip if no matching files # process_file "$file" # done # done # elif [ -d "$1" ]; then # # Argument is a directory, process all video files in that directory # for file in "$1"/*.{mp4,mkv,avi,mov}; do # [ -e "$file" ] || continue # Skip if no matching files # process_file "$file" # done # elif [ -f "$1" ]; then # # Argument is a single file, process only that file # process_file "$1" # else # echo "Error: $1 is not a valid file or directory." # exit 1 # fi