From af7ccb25b95b91eb2486dc298c3961785da21958 Mon Sep 17 00:00:00 2001 From: Hannes Date: Mon, 17 Nov 2025 00:41:24 +0100 Subject: [PATCH] Add All existing --- append | 64 +++++ create_folder_counting | 74 ++++++ create_folder_date | 40 +++ deappend | 95 ++++++++ deinterlacing_cpu.sh | 64 +++++ deinterlacing_gpu.sh | 32 +++ embed_audio | 78 ++++++ embed_subtitles | 49 ++++ embed_subtitles.py | 66 +++++ episode_numbers | 80 ++++++ extract_audio | 37 +++ extract_audio.py | 95 ++++++++ extract_subtitles | 37 +++ extract_subtitles.py | 88 +++++++ ffbitrate | 19 ++ ffsplit | 88 +++++++ fftesting | 36 +++ filter_extra | 49 ++++ generate-thumb | 120 +++++++++ grayjay | 12 + list_folder_empty | 51 ++++ nohup.out | 16 ++ rename_episodes | 27 +++ rename_filtered | 27 +++ rename_filtered.py | 64 +++++ rename_to_first4.py | 47 ++++ simple_ffprobe_script | 525 ++++++++++++++++++++++++++++++++++++++++ split_mkv_by_chapter | 137 +++++++++++ split_mkv_by_chapter.py | 122 ++++++++++ sub_dub | 282 +++++++++++++++++++++ sub_dub.py | 88 +++++++ sync_delete | 55 +++++ time_add.py | 23 ++ 33 files changed, 2687 insertions(+) create mode 100755 append create mode 100755 create_folder_counting create mode 100755 create_folder_date create mode 100755 deappend create mode 100755 deinterlacing_cpu.sh create mode 100755 deinterlacing_gpu.sh create mode 100755 embed_audio create mode 100755 embed_subtitles create mode 100644 embed_subtitles.py create mode 100755 episode_numbers create mode 100755 extract_audio create mode 100644 extract_audio.py create mode 100755 extract_subtitles create mode 100644 extract_subtitles.py create mode 100755 ffbitrate create mode 100755 ffsplit create mode 100755 fftesting create mode 100755 filter_extra create mode 100755 generate-thumb create mode 100755 grayjay create mode 100755 list_folder_empty create mode 100755 nohup.out create mode 100755 rename_episodes create mode 100755 rename_filtered create mode 100644 rename_filtered.py create mode 100755 rename_to_first4.py create mode 100755 simple_ffprobe_script create mode 100755 split_mkv_by_chapter create mode 100644 split_mkv_by_chapter.py create mode 100755 sub_dub create mode 100644 sub_dub.py create mode 100755 sync_delete create mode 100644 time_add.py diff --git a/append b/append new file mode 100755 index 0000000..53216b6 --- /dev/null +++ b/append @@ -0,0 +1,64 @@ +#!/bin/bash + +# Usage info +if [[ $# -lt 2 ]]; then + echo "Usage: $0 {front|back|ext|extension} [folder]" + exit 1 +fi + +mode=$1 +string=$2 +folder=${3:-$(pwd)} + +# Ensure the folder exists +if [[ ! -d "$folder" ]]; then + echo "Error: '$folder' is not a valid directory." + exit 1 +fi + +# Process files +for file in "$folder"/*; do + [[ -f "$file" ]] || continue # skip non-files + base="$(basename "$file")" + + # Split filename into name and extension (if any) + name="${base%.*}" + ext="${base##*.}" + + case "$mode" in + front) + # Add string to the front of the filename + newname="${string}${base}" + ;; + back) + # Add string before the extension (if present) + if [[ "$base" == "$ext" ]]; then + # no extension + newname="${name}${string}" + else + newname="${name}${string}.${ext}" + fi + ;; + ext|extension) + # Add string *to* the extension (after a dot) + if [[ "$base" == "$ext" ]]; then + # file has no extension + newname="${base}.${string}" + else + newname="${name}.${ext}${string}" + fi + ;; + *) + echo "Invalid mode: '$mode'. Use 'front', 'back', or 'ext/extension'." + exit 1 + ;; + esac + + # Avoid renaming collisions + if [[ "$newname" != "$base" ]]; then + echo "Renaming: $base -> $newname" + mv -n "$file" "$folder/$newname" + fi +done + +echo "✅ All applicable files in '$folder' have been renamed." diff --git a/create_folder_counting b/create_folder_counting new file mode 100755 index 0000000..1cfa1af --- /dev/null +++ b/create_folder_counting @@ -0,0 +1,74 @@ +#!/bin/bash + +# Check if the required arguments are provided +if [ "$#" -lt 4 ] || [ "$#" -gt 5 ]; then + echo "Usage: $0 {Folder} name1 {number1-number2 | letter1-letter2} name2 {symbol_between (optional)}" + exit 1 +fi + +# Set the target directory +if [ "$#" -eq 5 ]; then + TARGET_DIR="$1" + NAME1="$2" + RANGE="$3" + NAME2="$4" + SYMBOL="$5" +elif [ "$#" -eq 4 ]; then + # If 4 arguments are provided, the folder is assumed to be the current directory + TARGET_DIR="." + NAME1="$1" + RANGE="$2" + NAME2="$3" + SYMBOL="$4" +else + # If 3 arguments are provided, the folder is assumed to be the current directory + TARGET_DIR="." + NAME1="$1" + RANGE="$2" + NAME2="$3" + SYMBOL="_" # Default separator is "_" +fi + +# Ensure the target directory exists +mkdir -p "$TARGET_DIR" + +# Check if RANGE is numeric or alphabetic +if [[ "$RANGE" =~ ^[0-9]+-[0-9]+$ ]]; then + # Numeric range + START_NUM=$(echo "$RANGE" | cut -d'-' -f1) + END_NUM=$(echo "$RANGE" | cut -d'-' -f2) + + # Create folders for each number in the range + for ((i=START_NUM; i<=END_NUM; i++)); do + FOLDER_NAME="${NAME1}${SYMBOL}${i}${SYMBOL}${NAME2}" + mkdir -p "$TARGET_DIR/$FOLDER_NAME" + done + +elif [[ "$RANGE" =~ ^[a-zA-Z]-[a-zA-Z]$ ]]; then + # Alphabetic range + START_LETTER=$(echo "$RANGE" | cut -d'-' -f1) + END_LETTER=$(echo "$RANGE" | cut -d'-' -f2) + + # Convert letters to ASCII values for iteration + START_ASCII=$(printf "%d" "'$START_LETTER") + END_ASCII=$(printf "%d" "'$END_LETTER") + + # Ensure alphabetical order + if [ "$START_ASCII" -gt "$END_ASCII" ]; then + echo "Error: The alphabetical range should start with a letter that precedes the end letter." + exit 1 + fi + + # Create folders for each letter in the range + for ((i=START_ASCII; i<=END_ASCII; i++)); do + LETTER=$(printf "\x$(printf %x $i)") + FOLDER_NAME="${NAME1}${SYMBOL}${LETTER}${SYMBOL}${NAME2}" + mkdir -p "$TARGET_DIR/$FOLDER_NAME" + done + +else + echo "Error: RANGE should be in the format number1-number2 or letter1-letter2." + exit 1 +fi + +echo "Folders created in $TARGET_DIR." diff --git a/create_folder_date b/create_folder_date new file mode 100755 index 0000000..f11671d --- /dev/null +++ b/create_folder_date @@ -0,0 +1,40 @@ +#!/bin/bash + +# Check for arguments and set variables +if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then + echo "Usage: $0 {FOLDER} YYYY_MM_DD YYYY_MM_DD" + exit 1 +fi + +# Set folder path +if [ "$#" -eq 3 ]; then + TARGET_DIR="$1" + START_DATE="$2" + END_DATE="$3" +else + TARGET_DIR="." + START_DATE="$1" + END_DATE="$2" +fi + +# Ensure the target directory exists +mkdir -p "$TARGET_DIR" + +# Convert input dates to YYYY-MM-DD format for `date` compatibility +START_DATE=$(echo "$START_DATE" | sed 's/_/-/g') +END_DATE=$(echo "$END_DATE" | sed 's/_/-/g') + +# Loop through dates and create folders +current_date="$START_DATE" +while [[ "$current_date" < "$END_DATE" ]] || [[ "$current_date" == "$END_DATE" ]]; do + # Format date as YYYY_MM_DD + formatted_date=$(date -d "$current_date" +"%Y_%m_%d") + + # Create folder with the formatted date + mkdir -p "$TARGET_DIR/$formatted_date" + + # Increment date by one day + current_date=$(date -I -d "$current_date + 1 day") +done + +echo "Folders created from ${START_DATE//-/_} to ${END_DATE//-/_} in $TARGET_DIR." diff --git a/deappend b/deappend new file mode 100755 index 0000000..912ccad --- /dev/null +++ b/deappend @@ -0,0 +1,95 @@ +#!/usr/bin/python3 +import os +import sys + + +def remove_suffix_back(directory, suffix): + """Removes the specified suffix from the end of filenames before the extension.""" + for filename in os.listdir(directory): + filepath = os.path.join(directory, filename) + + # Skip directories + if not os.path.isfile(filepath): + continue + + # Separate base name and extension + base, ext = os.path.splitext(filename) + + # Remove suffix from the back of the base name + if base.endswith(suffix): + new_name = base[: -len(suffix)] + ext + os.rename(filepath, os.path.join(directory, new_name)) + print(f"Renamed: {filename} -> {new_name}") + + +def remove_prefix_front(directory, prefix): + """Removes the specified prefix from the start of filenames.""" + for filename in os.listdir(directory): + filepath = os.path.join(directory, filename) + + # Skip directories + if not os.path.isfile(filepath): + continue + + # Remove prefix if it exists + if filename.startswith(prefix): + new_name = filename[len(prefix):] + os.rename(filepath, os.path.join(directory, new_name)) + print(f"Renamed: {filename} -> {new_name}") + + +def remove_from_extension(directory, suffix): + """Removes the specified string from the file extensions.""" + for filename in os.listdir(directory): + filepath = os.path.join(directory, filename) + + # Skip directories + if not os.path.isfile(filepath): + continue + + # Separate base name and extension + base, ext = os.path.splitext(filename) + + # Remove suffix from the extension + if ext.endswith(suffix): + new_ext = ext[: -len(suffix)] + new_name = base + new_ext + os.rename(filepath, os.path.join(directory, new_name)) + print(f"Renamed: {filename} -> {new_name}") + + +def main(): + # Validate arguments + if len(sys.argv) < 3: + print("Usage: deappend.py [directory]") + sys.exit(1) + + # Get arguments + mode = sys.argv[1] # Mode: back, front, ext/extension + string = sys.argv[2] # String to remove + directory = sys.argv[3] if len(sys.argv) > 3 else os.getcwd() # Default: current directory + + # Check that the string is not empty + if string == "": + print("Error: The string to remove cannot be empty.") + sys.exit(1) + + # Validate directory + if not os.path.isdir(directory): + print(f"Error: Directory '{directory}' does not exist.") + sys.exit(1) + + # Process based on mode + if mode == "back": + remove_suffix_back(directory, string) + elif mode == "front": + remove_prefix_front(directory, string) + elif mode == "ext" or mode == "extension": + remove_from_extension(directory, string) + else: + print(f"Error: Unsupported mode '{mode}'. Supported modes: front, back, ext/extension.") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/deinterlacing_cpu.sh b/deinterlacing_cpu.sh new file mode 100755 index 0000000..792c696 --- /dev/null +++ b/deinterlacing_cpu.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +INPUT="$HOME/mount/Ripping/Lutz/noch zu rendern/Brücke nach Terabithia/A1_t00.mkv" +OUTPUT="${INPUT%.*}_deinterlaced.mp4" +LOGFILE=$(mktemp) + +if [ ! -f "$INPUT" ]; then + echo "Input file not found." + exit 1 +fi + +# Step 1: Try runtime detection with idet +ffmpeg -hide_banner -vstats -nostats \ + -i "$INPUT" \ + -filter:v "format=yuv420p,idet" \ + -frames:v 500 \ + -an -f rawvideo -y /dev/null \ + 2> "$LOGFILE" + +TFF=$(grep -o 'TFF:[0-9]*' "$LOGFILE" | head -1 | cut -d: -f2) +BFF=$(grep -o 'BFF:[0-9]*' "$LOGFILE" | head -1 | cut -d: -f2) +PROG=$(grep -o 'Progressive:[0-9]*' "$LOGFILE" | head -1 | cut -d: -f2) +rm "$LOGFILE" + +TFF=${TFF:-0} +BFF=${BFF:-0} +PROG=${PROG:-0} + +echo "Detected via idet - Progressive: $PROG, TFF: $TFF, BFF: $BFF" + +# Step 2: Fallback to metadata if no detection occurred +if [ "$((TFF + BFF + PROG))" -eq 0 ]; then + echo "idet returned 0 frames — falling back to metadata..." + FIELD_ORDER=$(ffprobe -v error -select_streams v:0 \ + -show_entries stream=field_order \ + -of default=noprint_wrappers=1:nokey=1 "$INPUT") + + case "$FIELD_ORDER" in + tt|bb) + echo "Field order is interlaced ($FIELD_ORDER)." + INTERLACED=true + ;; + progressive|unknown|"") + echo "Metadata says progressive or unknown." + INTERLACED=false + ;; + esac +else + INTERLACED=$((TFF + BFF > PROG)) +fi + +# Step 3: Skip or apply deinterlacing +if [ "$INTERLACED" = false ]; then + echo "Video is progressive. No deinterlacing needed." + exit 0 +fi + +echo "Video is interlaced. Proceeding with deinterlacing..." + +# Step 4: Apply yadif +ffmpeg -i "$INPUT" \ + -vf "yadif=mode=1:parity=auto" \ + -c:v libx264 -preset slow -crf 18 -c:a copy \ + "$OUTPUT" diff --git a/deinterlacing_gpu.sh b/deinterlacing_gpu.sh new file mode 100755 index 0000000..c6508c3 --- /dev/null +++ b/deinterlacing_gpu.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +INPUT="$HOME/mount/Ripping/Lutz/noch zu rendern/Brücke nach Terabithia/A1_t00.mkv" +OTPUT="${INPUT%.*}_deinterlaced_vaapi.mp4" +TEMP_STATS=$(mktemp) + +ffmpeg -filter:v idet -frames:v 5000 -an -f rawvideo -y /dev/null -i "$INPUT" 2> "$TEMP_STATS" + +SUMMARY_LINE=$(grep "Multi frame detection" "$TEMP_STATS") + +# Extract counts allowing for leading spaces +TFF_COUNT=$(echo "$SUMMARY_LINE" | grep -oP 'TFF:\s*\K[0-9]+' || echo 0) +BFF_COUNT=$(echo "$SUMMARY_LINE" | grep -oP 'BFF:\s*\K[0-9]+' || echo 0) +PROG_COUNT=$(echo "$SUMMARY_LINE" | grep -oP 'Progressive:\s*\K[0-9]+' || echo 0) + +rm "$TEMP_STATS" + +echo "Detected: TFF=$TFF_COUNT, BFF=$BFF_COUNT, Progressive=$PROG_COUNT" + +TOTAL_INTERLACED=$((TFF_COUNT + BFF_COUNT)) + +if [ "$TOTAL_INTERLACED" -lt 100 ]; then + echo "Video is mostly progressive. No deinterlacing needed." + exit 0 +fi + +echo "Deinterlacing required." + +ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi \ + -i "$INPUT" \ + -vf 'format=nv12,hwupload,deinterlace_vaapi' \U + -c:v h264_vaapi -b:v 5M -c:a copy "$OUTPUT" diff --git a/embed_audio b/embed_audio new file mode 100755 index 0000000..d7bfcff --- /dev/null +++ b/embed_audio @@ -0,0 +1,78 @@ +#!/usr/bin/python3 +import sys +import subprocess +import os +import glob + +def embed_audio(video_file, audio_file, output_file): + """ + Embeds an audio track into a video file as the first audio track. + + Parameters: + video_file (str): Path to the video file. + audio_file (str): Path to the audio file. + output_file (str): Path to save the output file. + """ + try: + result = subprocess.run( + [ + "ffmpeg", "-i", video_file, "-i", audio_file, + "-c:v", "copy", "-c:a", "aac", + "-map", "0:v:0", "-map", "1:a:0", + "-map", "0:a?", # Add any existing audio tracks as secondary tracks + "-shortest", output_file, "-y" + ], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True + ) + if result.returncode != 0: + print(f"Error embedding audio for {video_file}: {result.stderr}") + else: + print(f"Audio successfully embedded: {output_file}") + except Exception as e: + print(f"An error occurred for {video_file}: {e}") + +def main(): + # No arguments: auto-pairing mode + if len(sys.argv) == 1: + video_extensions = ["*.mp4", "*.mkv", "*.avi", "*.mov"] + files_processed = 0 + + for ext in video_extensions: + for video_file in glob.glob(ext): + base_name = os.path.splitext(video_file)[0] + audio_file = f"{base_name}.mp3" + if os.path.isfile(audio_file): + output_file = f"{base_name}_with_audio.mp4" + print(f"Processing: {video_file} with {audio_file} -> {output_file}") + embed_audio(video_file, audio_file, output_file) + files_processed += 1 + else: + print(f"No matching audio file found for: {video_file}") + + if files_processed == 0: + print("No video-audio pairs found in the current directory.") + sys.exit(1) + + # Explicit arguments mode + elif len(sys.argv) == 4: + video_file = sys.argv[1] + audio_file = sys.argv[2] + output_file = sys.argv[3] + + if not os.path.isfile(video_file): + print(f"Error: Video file '{video_file}' not found.") + sys.exit(1) + if not os.path.isfile(audio_file): + print(f"Error: Audio file '{audio_file}' not found.") + sys.exit(1) + + embed_audio(video_file, audio_file, output_file) + + else: + print("Usage:") + print(" Auto-pairing mode (no arguments): script will pair videos and matching audio in current directory") + print(" Explicit mode: ") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/embed_subtitles b/embed_subtitles new file mode 100755 index 0000000..268e268 --- /dev/null +++ b/embed_subtitles @@ -0,0 +1,49 @@ +#!/bin/bash + +# Paths to Python scripts +SUBTITLE_SCRIPT="$HOME/.bin/auto_embed_subtitles.py" + +# Check both Python scripts exist +if [ ! -f "$SUBTITLE_SCRIPT" ]; then + echo "Error: Subtitle script '$SUBTITLE_SCRIPT' not found." + exit 1 +fi + +# === Mode: Manual Audio Embed === +if [ "$#" -eq 3 ]; then + echo "Embedding subtitle: $1 + $2 -> $3" + python "$SUBTITLE_SCRIPT" "$1" "$2" "$3" + exit 0 + +# === Mode: Auto Detection === +elif [ "$#" -eq 0 ]; then + echo "No arguments provided. Attempting to embed audio and subtitles in the current directory." + + files_processed=0 + + # Process audio embeddings + for video_file in *.mp4 *.mkv *.avi *.mov; do + [ -e "$video_file" ] || continue + base_name="${video_file%.*}" + audio_file="${base_name}.mp3" + + if [ -f "$audio_file" ]; then + output_file="${base_name}_with_audio.mp4" + echo "Embedding subtitle: $video_file + $audio_file -> $output_file" + python "$SUBTITLE_SCRIPT" "$video_file" "$audio_file" "$output_file" + files_processed=1 + fi + done + + # Process subtitle embeddings + echo "Scanning for subtitles to embed..." + python "$SUBTITLE_SCRIPT" "." + + exit 0 + +else + echo "Usage:" + echo " $0 # Manual subtitel embed" + echo " $0 # Auto mode (pair audio + embed subtitles)" + exit 1 +fi diff --git a/embed_subtitles.py b/embed_subtitles.py new file mode 100644 index 0000000..b93c7fc --- /dev/null +++ b/embed_subtitles.py @@ -0,0 +1,66 @@ +import os +import sys +import subprocess +import re + +def find_matching_subtitles(video_path, search_dir): + base_name = os.path.splitext(os.path.basename(video_path))[0] + subtitle_files = [] + for file in os.listdir(search_dir): + if re.match(rf"^{re.escape(base_name)}__.*\.srt$", file): + subtitle_files.append(os.path.join(search_dir, file)) + return sorted(subtitle_files) + +def embed_subtitles(video_path, subtitle_paths, output_path): + try: + cmd = ["ffmpeg", "-i", video_path] + + for subtitle in subtitle_paths: + cmd += ["-i", subtitle] + + cmd += ["-c:v", "copy", "-c:a", "copy"] + + if output_path.lower().endswith(".mp4"): + cmd += ["-c:s", "mov_text"] + else: + cmd += ["-c:s", "copy"] + + cmd += ["-map", "0"] + for i in range(1, len(subtitle_paths) + 1): + cmd += ["-map", str(i)] + + cmd += ["-y", output_path] + + result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + if result.returncode != 0: + print(f"Error embedding subtitles into {video_path}:\n{result.stderr}") + else: + print(f"Embedded subtitles into: {output_path}") + + except Exception as e: + print(f"Failed to embed subtitles: {e}") + +def process_path(input_path): + if os.path.isfile(input_path): + directory = os.path.dirname(input_path) + subtitles = find_matching_subtitles(input_path, directory) + if subtitles: + output_file = os.path.splitext(input_path)[0] + "_subbed" + os.path.splitext(input_path)[1] + embed_subtitles(input_path, subtitles, output_file) + else: + print(f"No matching subtitles found for: {input_path}") + elif os.path.isdir(input_path): + for file_name in os.listdir(input_path): + full_path = os.path.join(input_path, file_name) + if os.path.isfile(full_path) and file_name.lower().endswith(('.mp4', '.mkv', '.avi', '.mov')): + process_path(full_path) + else: + print(f"Invalid input path: {input_path}") + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python auto_embed_subtitles.py ") + sys.exit(1) + + input_path = sys.argv[1] + process_path(input_path) diff --git a/episode_numbers b/episode_numbers new file mode 100755 index 0000000..32805a6 --- /dev/null +++ b/episode_numbers @@ -0,0 +1,80 @@ +#!/bin/bash + +LOG_DIR="/home/honney/.log" +LOG_FILE="${LOG_DIR}/rename.log" + +mkdir -p "$LOG_DIR" + +reverse_mode=false + +if [[ "$1" == "reverse" ]]; then + reverse_mode=true +elif [[ -z "$1" ]]; then + echo "Usage: $0 {start_number} | reverse" + exit 1 +fi + +if $reverse_mode; then + if [[ ! -f "$LOG_FILE" ]]; then + echo "No log file found. Nothing to reverse." + exit 1 + fi + + current_dir=$(realpath .) + match_start=$(grep -n "=== $current_dir ===" "$LOG_FILE" | cut -d: -f1) + + if [[ -z "$match_start" ]]; then + echo "No entries for this directory in the log." + exit 1 + fi + + sed -n "$((match_start + 1)),/^===/p" "$LOG_FILE" | grep -v "^===" | while IFS="|" read -r original renamed; do + if [[ -e "$renamed" ]]; then + mv -v "$renamed" "$original" + else + echo "Warning: '$renamed' not found. Skipping." + fi + done + + echo "Reversal complete." + exit 0 +fi + +# Start of normal renaming +start_number=$1 +counter=0 + +extensions=("mkv" "mp4" "avi" "mov" "jpg" "png") + +files=() +for ext in "${extensions[@]}"; do + while IFS= read -r -d '' file; do + files+=("$file") + done < <(find . -maxdepth 1 -type f -iname "*.${ext}" -print0) +done + +IFS=$'\n' sorted_files=($(printf '%s\n' "${files[@]}" | sort -rz | tr '\0' '\n')) + +# Log the current session +current_dir=$(realpath .) +echo "=== $current_dir ===" >> "$LOG_FILE" + +for file in "${sorted_files[@]}"; do + new_number=$(printf "%02d" $((start_number + counter))) + extension="${file##*.}" + base_name="E${new_number}" + new_name="${base_name}.${extension}" + suffix=1 + + # Avoid overwriting + while [ -e "$new_name" ]; do + new_name="${base_name}_$suffix.${extension}" + ((suffix++)) + done + + mv "$file" "$new_name" + echo "${file}|${new_name}" >> "$LOG_FILE" + ((counter++)) +done + +echo "Renaming complete! Changes logged to $LOG_FILE" diff --git a/extract_audio b/extract_audio new file mode 100755 index 0000000..b95c71b --- /dev/null +++ b/extract_audio @@ -0,0 +1,37 @@ +#!/bin/bash + +# Get the path to the Python script (adjust if needed) +PYTHON_SCRIPT="$HOME/.bin/extract_audio.py" + +# Check if the Python script exists +if [ ! -f "$PYTHON_SCRIPT" ]; then + echo "Error: Python script '$PYTHON_SCRIPT' not found." + exit 1 +fi + +# Check if arguments are provided +if [ "$#" -eq 0 ]; then + echo "No arguments provided. Attempting to process all video files in the current directory." + + # Initialize a flag to track if any files are processed + files_processed=0 + + # Find all video files in the current directory + for video_file in *.mp4 *.mkv *.avi *.mov; do + # Check if the file exists (in case no matches were found) + if [ -e "$video_file" ]; then + echo "Processing: $video_file" + python "$PYTHON_SCRIPT" "$video_file" + files_processed=1 + fi + done + + # If no files were processed, print a message + if [ "$files_processed" -eq 0 ]; then + echo "No video files found in the current directory." + exit 1 + fi +else + # Run the Python script with the provided arguments + python "$PYTHON_SCRIPT" "$@" +fi diff --git a/extract_audio.py b/extract_audio.py new file mode 100644 index 0000000..61abfc5 --- /dev/null +++ b/extract_audio.py @@ -0,0 +1,95 @@ +import os +import ffmpeg +import subprocess +import re +import json + + +def sanitize_filename(name): + """ + Removes invalid characters from the filename. + """ + return re.sub(r'[<>:"/\\|?*@]', '_', name) + + +def extract_audio_tracks(video_path): + """ + Extracts all audio tracks from a video file and saves them with their respective titles. + + Parameters: + video_path (str): Path to the video file. + """ + try: + # Get the base name and directory of the video file + base_name = os.path.splitext(os.path.basename(video_path))[0] + directory = os.path.dirname(video_path) + + # Use ffmpeg to get information about the video file + probe_cmd = ["ffprobe", "-v", "error", "-show_streams", "-of", "json", video_path] + probe_result = subprocess.run(probe_cmd, capture_output=True, text=True, check=True) + streams = json.loads(probe_result.stdout).get('streams', []) + + # Process each audio stream + audio_streams = [s for s in streams if s.get('codec_type') == 'audio'] + if not audio_streams: + print(f"No audio tracks found in {video_path}.") + return + + for i, stream in enumerate(audio_streams): + # Extract title or fallback to index + title = stream.get('tags', {}).get('title', f"track_{i + 1}") + language = stream.get('tags', {}).get('language', 'unknown') + sanitized_title = sanitize_filename(title) + sanitized_language = sanitize_filename(language) + output_name = f"{base_name}__{sanitized_title}__{sanitized_language}.mp3" + output_path = os.path.join(directory, output_name) + + # Extract the specific audio stream + print(f"Extracting audio: {sanitized_title} ({sanitized_language}) to {output_path}") + result = subprocess.run([ + "ffmpeg", "-i", video_path, + "-map", f"0:a:{i}", # Select the specific audio stream + "-c:a", "mp3", # Set the codec to mp3 + output_path, + "-y" # Overwrite if exists + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + + if result.returncode != 0: + print(f"ffmpeg error for stream {i}: {result.stderr}") + + print(f"Extraction completed for {video_path}.") + except Exception as e: + print(f"Error processing {video_path}: {e}") + + +def process_path(input_path): + """ + Processes a file or directory, extracting all audio tracks from video files. + + Parameters: + input_path (str): Path to the video file or directory. + """ + if os.path.isfile(input_path): + # Process a single file + extract_audio_tracks(input_path) + elif os.path.isdir(input_path): + # Process all video files in the directory + for file_name in os.listdir(input_path): + file_path = os.path.join(input_path, file_name) + if os.path.isfile(file_path) and file_name.lower().endswith(('.mp4', '.mkv', '.avi', '.mov')): + extract_audio_tracks(file_path) + else: + print(f"Invalid path: {input_path}") + +if __name__ == "__main__": + import argparse + + # Set up argument parser + parser = argparse.ArgumentParser(description="Extract all audio tracks from video files.") + parser.add_argument("input_path", help="Path to the video file or directory.") + + # Parse arguments + args = parser.parse_args() + + # Process the input path + process_path(args.input_path) diff --git a/extract_subtitles b/extract_subtitles new file mode 100755 index 0000000..e3b10b2 --- /dev/null +++ b/extract_subtitles @@ -0,0 +1,37 @@ +#!/bin/bash + +# Get the path to the Python script (adjust if needed) +PYTHON_SCRIPT="$HOME/.bin/extract_subtitles.py" + +# Check if the Python script exists +if [ ! -f "$PYTHON_SCRIPT" ]; then + echo "Error: Python script '$PYTHON_SCRIPT' not found." + exit 1 +fi + +# Check if arguments are provided +if [ "$#" -eq 0 ]; then + echo "No arguments provided. Attempting to process all video files in the current directory." + + # Initialize a flag to track if any files are processed + files_processed=0 + + # Find all video files in the current directory + for video_file in *.mp4 *.mkv *.avi *.mov; do + # Check if the file exists (in case no matches were found) + if [ -e "$video_file" ]; then + echo "Processing: $video_file" + python "$PYTHON_SCRIPT" "$video_file" + files_processed=1 + fi + done + + # If no files were processed, print a message + if [ "$files_processed" -eq 0 ]; then + echo "No video files found in the current directory." + exit 1 + fi +else + # Run the Python script with the provided arguments + python "$PYTHON_SCRIPT" "$@" +fi diff --git a/extract_subtitles.py b/extract_subtitles.py new file mode 100644 index 0000000..9c68433 --- /dev/null +++ b/extract_subtitles.py @@ -0,0 +1,88 @@ +import os +import subprocess +import re +import json + + +def sanitize_filename(name): + """ + Removes invalid characters from the filename. + """ + return re.sub(r'[<>:"/\\|?*@]', '_', name) + + +def extract_subtitle_tracks(video_path): + """ + Extracts all subtitle tracks from a video file and saves them with their respective titles. + + Parameters: + video_path (str): Path to the video file. + """ + try: + base_name = os.path.splitext(os.path.basename(video_path))[0] + directory = os.path.dirname(video_path) + + # Get stream info using ffprobe + probe_cmd = ["ffprobe", "-v", "error", "-show_streams", "-of", "json", video_path] + probe_result = subprocess.run(probe_cmd, capture_output=True, text=True, check=True) + streams = json.loads(probe_result.stdout).get('streams', []) + + # Filter subtitle streams + subtitle_streams = [s for s in streams if s.get('codec_type') == 'subtitle'] + if not subtitle_streams: + print(f"No subtitle tracks found in {video_path}.") + return + + for i, stream in enumerate(subtitle_streams): + title = stream.get('tags', {}).get('title', f"subtitle_{i + 1}") + language = stream.get('tags', {}).get('language', 'unknown') + sanitized_title = sanitize_filename(title) + sanitized_language = sanitize_filename(language) + output_name = f"{base_name}__{sanitized_title}__{sanitized_language}.srt" + output_path = os.path.join(directory, output_name) + + # Extract subtitle stream + print(f"Extracting subtitle: {sanitized_title} ({sanitized_language}) to {output_path}") + result = subprocess.run([ + "ffmpeg", "-i", video_path, + "-map", f"0:s:{i}", + "-c:s", "srt", + output_path, + "-y" + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + + if result.returncode != 0: + print(f"ffmpeg error for subtitle stream {i}: {result.stderr}") + + print(f"Subtitle extraction completed for {video_path}.") + except Exception as e: + print(f"Error processing {video_path}: {e}") + + +def process_path(input_path): + """ + Processes a file or directory, extracting all subtitle tracks from video files. + + Parameters: + input_path (str): Path to the video file or directory. + """ + if os.path.isfile(input_path): + extract_subtitle_tracks(input_path) + elif os.path.isdir(input_path): + for file_name in os.listdir(input_path): + file_path = os.path.join(input_path, file_name) + if os.path.isfile(file_path) and file_name.lower().endswith(('.mp4', '.mkv', '.avi', '.mov')): + extract_subtitle_tracks(file_path) + else: + print(f"Invalid path: {input_path}") + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="Extract all subtitle tracks from video files.") + parser.add_argument("input_path", help="Path to the video file or directory.") + args = parser.parse_args() + + process_path(args.input_path) + diff --git a/ffbitrate b/ffbitrate new file mode 100755 index 0000000..f3ca62a --- /dev/null +++ b/ffbitrate @@ -0,0 +1,19 @@ +#!/bin/bash + +# Check if the user provided a file as an argument +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +# Get the video file path from the argument +VIDEO_FILE="$1" + +# Use ffmpeg to extract the bitrate +BITRATE=$(ffmpeg -i "$VIDEO_FILE" 2>&1 | grep -oP 'bitrate:\s+\K[\d.]+') + +if [ -n "$BITRATE" ]; then + echo "Bitrate: ${BITRATE} kb/s" +else + echo "Unable to retrieve bitrate." +fi diff --git a/ffsplit b/ffsplit new file mode 100755 index 0000000..f8bd2c4 --- /dev/null +++ b/ffsplit @@ -0,0 +1,88 @@ +#!/bin/bash + +# Function to print usage instructions +usage() { + echo "Usage: $0 input_file HH:MM:SS_1 HH:MM:SS_2 ... HH:MM:SS_N" + exit 1 +} + +# Check if there are at least two arguments (input file and one time code) +if [ "$#" -lt 2 ]; then + usage +fi + +# Get the input file and validate its existence +input_file="$1" +if [ ! -f "$input_file" ]; then + echo "Error: File '$input_file' does not exist." + exit 1 +fi + +# Extract file extension and base name +extension="${input_file##*.}" +base_name="${input_file%.*}" + +# Get the duration of the input file using ffprobe +duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$input_file") +if [ -z "$duration" ]; then + echo "Error: Unable to determine the duration of the file." + exit 1 +fi +duration=${duration%.*} # Convert to integer seconds + +# Get the list of time codes +time_codes=("${@:2}") +# Add the end of the video to the list of time codes +time_codes+=("$(printf '%02d:%02d:%02d\n' $((duration/3600)) $((duration%3600/60)) $((duration%60)))") + +# Function to convert HH:MM:SS to seconds +time_to_seconds() { + local time="$1" + IFS=: read -r hh mm ss <<< "$time" + echo $((10#$hh * 3600 + 10#$mm * 60 + 10#$ss)) +} + +# Validate each time code +for time in "${time_codes[@]}"; do + total_seconds=$(time_to_seconds "$time") + if [ "$total_seconds" -gt "$duration" ]; then + echo "Error: Time code $time exceeds video duration." + exit 1 + fi +done + +# Process each segment +previous_time="00:00:00" +for i in "${!time_codes[@]}"; do + current_time="${time_codes[i]}" + if [ "$i" -eq $((${#time_codes[@]} - 1)) ]; then + # If this is the last time code, process until the end of the video + current_time=$(printf '%02d:%02d:%02d\n' $((duration/3600)) $((duration%3600/60)) $((duration%60))) + fi + + current_seconds=$(time_to_seconds "$current_time") + previous_seconds=$(time_to_seconds "$previous_time") + segment_duration=$((current_seconds - previous_seconds)) + + if [ "$segment_duration" -le 0 ]; then + echo "Error: Invalid time range $previous_time to $current_time." + exit 1 + fi + + # Generate output file name + output_file="${base_name}_${previous_time}-${current_time}.${extension}" + + echo "Creating segment: $output_file" + + if [ "$previous_time" == "00:00:00" ]; then + # For the first segment, ensure that it starts from the very first frame (avoiding keyframe issues) + ffmpeg -y -i "$input_file" -ss "$previous_time" -to "$current_time" -c:v copy -c:a copy -c:s copy -map 0 -copyts -vsync 1 "$output_file" + else + # For subsequent segments, simply seek and copy streams + ffmpeg -y -i "$input_file" -ss "$previous_time" -to "$current_time" -c:v copy -c:a copy -c:s copy -map 0 -copyts "$output_file" + fi + + previous_time="$current_time" +done + +echo "Splitting completed successfully." diff --git a/fftesting b/fftesting new file mode 100755 index 0000000..6efd42b --- /dev/null +++ b/fftesting @@ -0,0 +1,36 @@ +#!/bin/bash + +# Directory to scan for video files (default: current directory) +DIR=${1:-.} + +# Supported video file extensions +EXTENSIONS=("mp4" "mkv" "avi" "mov" "wmv" "flv" "webm" "MP4" "MKV" "AVI" "MOV" "WMV" "FLV" "WEBM") + +# Function to test a video file with ffmpeg +test_video() { + local file="$1" + echo "Testing video: $file" + # Run ffmpeg in error-detection mode + ffmpeg -v error -i "$file" -f null - 2> >(grep -E '.*' || true) +} + +# Find and test video files +if [[ ! -d "$DIR" ]]; then + echo "Error: Directory '$DIR' does not exist." + exit 1 +fi + +echo "Scanning directory: $DIR" +for ext in "${EXTENSIONS[@]}"; do + # Find files with the current extension + find "$DIR" -type f -iname "*.$ext" | while IFS= read -r file; do + # Quote the filename to handle spaces correctly + ERRORS=$(test_video "$file") + if [[ -n "$ERRORS" ]]; then + echo -e "\nErrors found in: $file" + echo "$ERRORS" + fi + done +done + +echo -e "\nScan complete." diff --git a/filter_extra b/filter_extra new file mode 100755 index 0000000..1786627 --- /dev/null +++ b/filter_extra @@ -0,0 +1,49 @@ +#!/bin/bash + +# Check if the user provided a time argument +if [ -z "$1" ]; then + echo "Usage: sort_extra {time(HH:MM:SS)}" + exit 1 +fi + +# Convert the provided time into total seconds +IFS=: read -r hours minutes seconds <<< "$1" +target_time=$((hours * 3600 + minutes * 60 + seconds)) + +# Create the extra folder if it doesn’t exist +mkdir -p extra + +# Iterate through all subdirectories except 'extra' +for dir in */; do + if [ "$dir" != "extra/" ]; then + find "$dir" -type f -name "*.mkv" | while read -r file; do + # Get the duration of the mkv file in seconds + duration=$(ffprobe -v error -select_streams v:0 -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file") + duration=${duration%.*} # Convert float to integer + + # Convert duration to HH:MM:SS format + hh=$((duration / 3600)) + mm=$(((duration % 3600) / 60)) + ss=$((duration % 60)) + formatted_duration=$(printf "%02d:%02d:%02d" $hh $mm $ss) + + # Check if duration is less than the target time + if [ "$duration" -lt "$target_time" ]; then + base_name=$(basename -- "$file") + target_file="extra/$base_name" + + # Ensure the file does not overwrite an existing file + if [ -e "$target_file" ]; then + count=1 + while [ -e "extra/${count}_$base_name" ]; do + ((count++)) + done + target_file="extra/${count}_$base_name" + fi + + mv "$file" "$target_file" + echo "Moved: $file -> $target_file (Length: $formatted_duration)" + fi + done + fi +done diff --git a/generate-thumb b/generate-thumb new file mode 100755 index 0000000..efa6263 --- /dev/null +++ b/generate-thumb @@ -0,0 +1,120 @@ +#!/bin/bash + +SCENEDETECT="$HOME/.venvs/scenedetect-env/bin/scenedetect" +PROCESSED_LOG="$HOME/.cache/processed_videos.log" + +set -e + +# Ensure log file exists +mkdir -p "$(dirname "$PROCESSED_LOG")" +touch "$PROCESSED_LOG" + +# Find all video files (case-insensitive match) +find . -type f \( -iname "*.mp4" -o -iname "*.mkv" -o -iname "*.avi" \) -print0 | +while IFS= read -r -d '' video; do + echo "🔍 Processing: $video" + + # Skip if already processed + if grep -Fxq "$video" "$PROCESSED_LOG"; then + echo "⚠️ Already processed, skipping: $video" + continue + fi + + # Get duration in seconds + duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$video") + duration=${duration%.*} + start=180 + end=$((duration - 180)) + [[ "$end" -le "$start" ]] && echo "❌ Skipping (too short): $video" && continue + length=$((end - start)) + + base="${video%.*}" + trimmed="${base}_trimmed_tmp.mp4" + workdir="${base}_thumb_tmp" + mkdir -p "$workdir" + + # Suppress ffmpeg output + ffmpeg -hide_banner -loglevel error -y -ss "$start" -i "$video" -t "$length" -c copy "$trimmed" + + # Scene detection + "$SCENEDETECT" -i "$trimmed" -o "$workdir" detect-content save-images + + # Rank images by sharpness + best_image="" + best_score=0 + for img in "$workdir"/*.jpg; do + score=$(identify -format "%[standard-deviation]" "$img" 2>/dev/null | cut -d',' -f1) + score_float=$(printf "%.6f\n" "$score") + if (( $(echo "$score_float > $best_score" | bc -l) )); then + best_score=$score_float + best_image="$img" + fi + done + + thumb="${base}-thumb.jpg" + cp "$best_image" "$thumb" + echo "✅ Thumbnail saved: $thumb" + + echo "$video" >> "$PROCESSED_LOG" + + rm -rf "$workdir" "$trimmed" +done + + + +########## Using full video duration ########## + +# #!/bin/bash + +# SCENEDETECT="$HOME/.venvs/scenedetect-env/bin/scenedetect" +# PROCESSED_LOG="$HOME/.cache/processed_videos.log" + +# set -e + +# video="$1" +# [[ ! -f "$video" ]] && echo "Video not found: $video" && exit 1 + +# # Ensure log file exists +# mkdir -p "$(dirname "$PROCESSED_LOG")" +# touch "$PROCESSED_LOG" + +# # Check if video is already processed +# if grep -Fxq "$video" "$PROCESSED_LOG"; then +# echo "⚠️ Video already processed: $video" +# exit 0 +# fi + +# # Prepare paths +# base="${video%.*}" +# workdir="${base}_thumb_tmp" +# mkdir -p "$workdir" + +# # Step 1: Scene detection +# "$SCENEDETECT" -i "$video" -o "$workdir" detect-content save-images + +# # Step 2: Rank images by sharpness (standard deviation) +# best_image="" +# best_score=0 + +# for img in "$workdir"/*.jpg; do +# # Get standard deviation of luminance +# score=$(identify -format "%[standard-deviation]" "$img" 2>/dev/null | cut -d',' -f1) + +# # Compare scores +# score_float=$(printf "%.6f\n" "$score") +# if (( $(echo "$score_float > $best_score" | bc -l) )); then +# best_score=$score_float +# best_image="$img" +# fi +# done + +# # Step 3: Save best image with Jellyfin naming scheme +# thumb="${base}-thumb.jpg" +# cp "$best_image" "$thumb" +# echo "✅ Thumbnail saved: $thumb" + +# # Step 4: Mark as processed +# echo "$video" >> "$PROCESSED_LOG" + +# # Step 5: Cleanup +# rm -rf "$workdir" diff --git a/grayjay b/grayjay new file mode 100755 index 0000000..5f41b5a --- /dev/null +++ b/grayjay @@ -0,0 +1,12 @@ +#!/bin/sh +APP_DIR="$HOME/.local/share/grayjay" + +# Check if app is already installed in user directory +if [ ! -d "$APP_DIR" ]; then + echo "First run - installing Grayjay to $APP_DIR" + mkdir -p "$APP_DIR" + cp -r /usr/share/grayjay/* "$APP_DIR/" + chmod u+w -R "$APP_DIR" +fi + +exec sh -c "cd '$APP_DIR' && exec ./Grayjay \"\$@\"" -- "$@" diff --git a/list_folder_empty b/list_folder_empty new file mode 100755 index 0000000..54f4ccf --- /dev/null +++ b/list_folder_empty @@ -0,0 +1,51 @@ +#!/bin/bash + +# Function to list files in a directory +list_contents() { + local dir="$1" + local result=() + + # Check if the directory contains files or subdirectories + local has_file=false + local has_folder=false + + # Loop through the directory contents + for item in "$dir"/*; do + if [ -f "$item" ]; then + # If it's a file, get its extension and add it to the result + ext="${item##*.}" + result+=("$ext") + has_file=true + elif [ -d "$item" ]; then + has_folder=true + fi + done + + # If files are found, return their extensions + if [ "$has_file" = true ]; then + echo "$(printf "%s, " "${result[@]}" | sed 's/, $//')" + elif [ "$has_folder" = true ]; then + echo "folder" + fi +} + +# Function to recursively list non-empty directories +list_non_empty_directories() { + local base_dir="$1" + + # Loop through the directories in the base directory + for dir in "$base_dir"/*; do + if [ -d "$dir" ]; then + # List contents of the directory + contents=$(list_contents "$dir") + if [ -n "$contents" ]; then + echo "$(basename "$dir") ($contents)" + fi + # Recursively check subdirectories + list_non_empty_directories "$dir" + fi + done +} + +# Start listing from the current directory +list_non_empty_directories "." diff --git a/nohup.out b/nohup.out new file mode 100755 index 0000000..fa7034a --- /dev/null +++ b/nohup.out @@ -0,0 +1,16 @@ +node:events:502 + throw er; // Unhandled 'error' event + ^ + +Error: EBADF: bad file descriptor, read +Emitted 'error' event on ReadStream instance at: + at emitErrorNT (node:internal/streams/destroy:169:8) + at errorOrDestroy (node:internal/streams/destroy:238:7) + at node:internal/fs/streams:272:9 + at FSReqCallback.wrapper [as oncomplete] (node:fs:683:5) { + errno: -9, + code: 'EBADF', + syscall: 'read' +} + +Node.js v20.19.0 diff --git a/rename_episodes b/rename_episodes new file mode 100755 index 0000000..5fb2a40 --- /dev/null +++ b/rename_episodes @@ -0,0 +1,27 @@ +#!/bin/bash + +# Use the first argument as the directory or default to the current directory +directory="${1:-$(pwd)}" + +# Change to the specified directory +cd "$directory" || { echo "Directory not found: $directory"; exit 1; } + +# Regular expression to match " 00 ", " 01 ", ..., " 99 " +for file in *; do + if [[ "$file" =~ ([[:space:]]([0-9]{2})[[:space:]]) ]]; then + # Extract the number (e.g., "00", "01") + number="${BASH_REMATCH[2]}" + + # Get the file extension + extension="${file##*.}" + [[ "$file" == "$extension" ]] && extension="" || extension=".$extension" + + # Construct the new filename + new_filename="E$number$extension" + + # Rename the file + mv -v "$file" "$new_filename" + else + echo "Skipped: $file (no match)" + fi +done diff --git a/rename_filtered b/rename_filtered new file mode 100755 index 0000000..767e085 --- /dev/null +++ b/rename_filtered @@ -0,0 +1,27 @@ +#!/bin/bash + +# Check if scheme is provided +if [ -z "$1" ]; then + echo "Usage: rename_filtered [folder]" + echo + echo "Example:" + echo " Files in folder:" + echo " myfile_E123.txt" + echo " myfile_E456.txt" + echo " myfile_E789.txt" + echo " myfile_E2468.txt" + echo + echo " Run:" + echo " rename_filtered \"????????E!!!\" ./" + echo + echo " Result:" + echo " myfile_E123.txt → 123.txt" + echo " myfile_E456.txt → 456.txt" + echo " myfile_E789.txt → 789.txt" + echo " myfile_E2468.txt → myfile_2468.txt" + + exit 1 +fi + +# Call the Python script with scheme and optional folder argument +python3 /home/honney/.bin/rename_filtered.py "$@" diff --git a/rename_filtered.py b/rename_filtered.py new file mode 100644 index 0000000..b00eba9 --- /dev/null +++ b/rename_filtered.py @@ -0,0 +1,64 @@ +import os +import sys +import glob + +def process_files(files, scheme): + scheme_len = len(scheme) + + for filename in files: + print(f"Processing file: {filename}") + print(f"Against scheme: {scheme}") + + # Get the actual filename without the directory path + base = os.path.basename(filename) + + name, ext = os.path.splitext(base) + + if scheme_len == len(name): + new_name = "" + for i in range(scheme_len): + current_letter = scheme[i] + if current_letter == "?": + pass + elif current_letter == "!": + new_name += name[i] + elif current_letter == name[i]: + new_name += current_letter + else: + print(f"Mismatch at position {i}, skipping {filename}") + new_name = None + break + if new_name: + rename_file(filename, new_name, ext) + else: + print("No new name Generated. It is not supposed to do that") + return + +def rename_file(file, new_name, ext): + dirname = os.path.dirname(file) # correct: preserve folder + old_base = os.path.basename(file) + + if old_base != new_name: + new_filepath = os.path.join(dirname, new_name + ext) + os.rename(file, new_filepath) + print(f"Renamed '{old_base}' → '{new_name + ext}'") + else: + print(f"No renaming needed for '{old_base}'.") + + +def main(): + if len(sys.argv) < 2: + print("Usage: scheme.py [folder]") + sys.exit(1) + + scheme = sys.argv[1] + folder = sys.argv[2] if len(sys.argv) > 2 else "." + + # Get all files in the folder (assuming files are in the current directory by default) + files = glob.glob(os.path.join(folder, "*")) + + # Run the renaming logic + process_files(files, scheme) + +if __name__ == "__main__": + main() diff --git a/rename_to_first4.py b/rename_to_first4.py new file mode 100755 index 0000000..f33ef71 --- /dev/null +++ b/rename_to_first4.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +""" +rename_to_first4.py + +Usage: + python3 rename_to_first4.py + +Renames each file in so its name becomes the first +four characters of the original filename (before the extension). +Keeps the file extension and avoids overwriting existing files. +""" +import os +import sys +from pathlib import Path + +def main(): + if len(sys.argv) != 2: + print("Usage: rename_to_first4.py ") + sys.exit(1) + + directory = Path(sys.argv[1]) + if not directory.is_dir(): + print(f"Error: '{directory}' is not a directory.") + sys.exit(1) + + for f in directory.iterdir(): + if not f.is_file(): + continue + + stem = f.stem[:4] # first 4 letters of filename (no extension) + new_name = f"{stem}{f.suffix}" + new_path = directory / new_name + + # avoid overwriting existing files + counter = 1 + while new_path.exists(): + new_name = f"{stem}_{counter}{f.suffix}" + new_path = directory / new_name + counter += 1 + + print(f"Renaming: {f.name} -> {new_name}") + f.rename(new_path) + + print("✅ Done renaming all files.") + +if __name__ == "__main__": + main() diff --git a/simple_ffprobe_script b/simple_ffprobe_script new file mode 100755 index 0000000..dc5aba8 --- /dev/null +++ b/simple_ffprobe_script @@ -0,0 +1,525 @@ +#!/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 diff --git a/split_mkv_by_chapter b/split_mkv_by_chapter new file mode 100755 index 0000000..1889666 --- /dev/null +++ b/split_mkv_by_chapter @@ -0,0 +1,137 @@ +#!/bin/bash + +# Function to convert timestamp to seconds, including milliseconds +timestamp_to_seconds() { + IFS=':' read -r hours minutes seconds <<< "$1" + IFS='.' read -r seconds milliseconds <<< "$seconds" + total_seconds=$(bc <<< "$hours * 3600 + $minutes * 60 + $seconds") + total_milliseconds=$(bc <<< "$milliseconds / 1000") + echo "$total_seconds + $total_milliseconds" | bc +} + +# Function to convert seconds back to HH:MM:SS.mmm format for mkvmerge +seconds_to_hms() { + total_seconds="$1" + # Separate whole seconds and milliseconds + seconds=$(echo "$total_seconds" | cut -d'.' -f1) + milliseconds=$(echo "$total_seconds" | cut -d'.' -f2 | sed 's/^[0-9]\{1,3\}$/&000/') # Add trailing zeros if necessary + + # Ensure milliseconds have 3 digits + milliseconds=$(echo "$milliseconds" | cut -c1-3) + + # Convert seconds to HH:MM:SS format + hours=$(bc <<< "$seconds / 3600") + minutes=$(bc <<< "($seconds % 3600) / 60") + seconds=$(bc <<< "$seconds % 60") + + # Print in HH:MM:SS.mmm format + printf "%02d:%02d:%02d.%03d" "$hours" "$minutes" "$seconds" "$milliseconds" +} + +# Function to split the video using mkvmerge +split_video() { + input_file="$1" + start_time="$2" + end_time="$3" + output_file="$4" + + start_hms=$(seconds_to_hms "$start_time") + end_hms=$(seconds_to_hms "$end_time") + + # Print the mkvmerge command before running it + echo "Running command: mkvmerge -o \"$output_file\" --split parts:\"$start_hms\"-\"$end_hms\" \"$input_file\"" + + # Execute the command + mkvmerge -o "$output_file" --split parts:"$start_hms"-"$end_hms" "$input_file" +} + +# Function to get video duration +get_duration() { + input_file="$1" + duration=$(ffprobe -v quiet -print_format json -show_format "$input_file" | jq -r '.format.duration') + echo "$duration" +} + +# Function to extract chapter start and end times from the input file +get_chapters() { + input_file="$1" + chapters=() + + # Extract the chapters using mkvinfo, then parse the start and end times + while IFS= read -r line; do + if [[ "$line" =~ "start" ]]; then + start_time=$(echo "$line" | awk '{print $2}') + chapters+=("$start_time") + fi + done < <(mkvinfo "$input_file" | grep "Chapter") + + echo "${chapters[@]}" +} + +# Main function +main() { + only_between=false + + if [ "$1" == "--only_between" ]; then + only_between=true + shift + fi + + if [ "$#" -lt 3 ]; then + echo "Usage: $0 [--only_between] ..." + exit 1 + fi + + input_file="$1" + shift + timestamps=($@) + output_folder="split_output" + mkdir -p "$output_folder" + + echo "Splitting video from provided timestamps..." + + # Handle chapter timecodes if present + if [[ "${timestamps[0]}" == "chapters" ]]; then + # Extract chapter start times + chapter_times=($(get_chapters "$input_file")) + + echo "Extracting chapters from the input file..." + + for ((i = 0; i < ${#chapter_times[@]} - 1; i++)); do + start_time="${chapter_times[$i]}" + end_time="${chapter_times[$i+1]}" + output_filename="$output_folder/chapter_$((i+1)).mkv" + echo "Splitting from Chapter $((i+1)) time $start_time to Chapter $((i+2)) time $end_time..." + split_video "$input_file" "$start_time" "$end_time" "$output_filename" + done + elif $only_between; then + echo "Extracting only specified segments..." + for ((i = 0; i < ${#timestamps[@]} - 1; i++)); do + start_time=$(timestamp_to_seconds "${timestamps[$i]}") + end_time=$(timestamp_to_seconds "${timestamps[$i+1]}") + output_filename="$output_folder/part_$((i+1)).mkv" + echo "Splitting from ${timestamps[$i]} to ${timestamps[$i+1]}..." + split_video "$input_file" "$start_time" "$end_time" "$output_filename" + done + else + echo "Extracting all segments..." + start_time=0 + output_index=1 + for timestamp in "${timestamps[@]}"; do + end_time=$(timestamp_to_seconds "$timestamp") + output_filename="$output_folder/part_${output_index}.mkv" + echo "Splitting from $start_time to $timestamp..." + split_video "$input_file" "$start_time" "$end_time" "$output_filename" + start_time=$end_time + ((output_index++)) + done + duration=$(get_duration "$input_file") + output_filename="$output_folder/part_${output_index}.mkv" + echo "Splitting from $start_time to $duration..." + split_video "$input_file" "$start_time" "$duration" "$output_filename" + fi + + echo "Splitting complete. Files saved in $output_folder." +} + +main "$@" diff --git a/split_mkv_by_chapter.py b/split_mkv_by_chapter.py new file mode 100644 index 0000000..03a950d --- /dev/null +++ b/split_mkv_by_chapter.py @@ -0,0 +1,122 @@ +import subprocess +import sys +import os +import json + +# Function to convert timestamp to seconds +def timestamp_to_seconds(timestamp): + hours, minutes, seconds = map(float, timestamp.split(":")) + return hours * 3600 + minutes * 60 + seconds + +# Function to convert seconds back to HH:MM:SS format for mkvmerge +def seconds_to_hms(seconds): + hours = int(seconds // 3600) + minutes = int((seconds % 3600) // 60) + seconds = seconds % 60 + return f"{hours:02}:{minutes:02}:{seconds:.3f}" + + +# Function to get chapters from the MKV file using ffprobe +def get_chapters(input_file): + cmd = ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_chapters", input_file] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode != 0: + print("Error: Unable to retrieve chapter information.") + sys.exit(1) + + chapters = json.loads(result.stdout)["chapters"] + chapter_times = [float(chapter["start_time"]) for chapter in chapters] # Convert to float + return chapter_times + +# Function to find the nearest chapter time to a given timestamp +def find_nearest_chapter(timestamp, chapter_times): + timestamp_seconds = timestamp_to_seconds(timestamp) + nearest_chapter = min(chapter_times, key=lambda chapter: abs(chapter - timestamp_seconds)) + return nearest_chapter + +# Function to split the video using mkvmerge +# Function to split the video using mkvmerge +def split_video(input_file, start_time, end_time, output_file): + # Convert start_time and end_time to HH:MM:SS format for mkvmerge + start_time_hms = seconds_to_hms(start_time) + end_time_hms = seconds_to_hms(end_time) + + cmd = [ + "mkvmerge", + "-o", output_file, + "--split", f"parts:{start_time_hms}-{end_time_hms}", + input_file + ] + + print(f"Running command: {' '.join(cmd)}") # Debugging: print the command + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode != 0: + print(f"Error splitting video: {result.stderr}") + sys.exit(1) + + +# Main function +def main(input_file, timestamps): + # Create output folder + output_folder = "split_output" + os.makedirs(output_folder, exist_ok=True) + + # Get chapter times from the video + print("Retrieving chapter times...") + chapter_times = get_chapters(input_file) + print(f"Found chapter times: {chapter_times}") + + # Initialize start time + start_time = 0.0 + output_index = 1 + + # Process each timestamp + for timestamp in timestamps: + print(f"Processing timestamp: {timestamp}") + + # Find the nearest chapter + nearest_chapter = find_nearest_chapter(timestamp, chapter_times) + + # Generate the output filename + output_filename = os.path.join(output_folder, f"part_{output_index}.mkv") + + # Split the video + print(f"Splitting from {start_time} to {nearest_chapter}...") + split_video(input_file, start_time, nearest_chapter, output_filename) + + # Update start time for the next part + start_time = nearest_chapter + output_index += 1 + + # Handle the last segment (from last chapter to the end of the video) + print(f"Handling final segment from {start_time} to the end of the video...") + + # Get the duration of the video + cmd = ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", input_file] + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode != 0: + print("Error: Unable to retrieve video duration.") + sys.exit(1) + + duration = json.loads(result.stdout)["format"]["duration"] + print(f"Video duration: {duration}") + + # Final segment + output_filename = os.path.join(output_folder, f"part_{output_index}.mkv") + print(f"Splitting from {start_time} to {duration}...") + split_video(input_file, start_time, float(duration), output_filename) + + print("Splitting complete. Files saved in split_output.") + +if __name__ == "__main__": + if len(sys.argv) < 3: + print("Usage: python split_mkv_by_chapter.py ...") + sys.exit(1) + + input_file = sys.argv[1] + timestamps = sys.argv[2:] + + main(input_file, timestamps) diff --git a/sub_dub b/sub_dub new file mode 100755 index 0000000..d43e3ba --- /dev/null +++ b/sub_dub @@ -0,0 +1,282 @@ +#!/bin/bash + +# 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" +) + +# 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 +} + +extract_audio_languages() { + 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 "," -) + languages="" + IFS=',' read -r -a audio_streams <<< "$audio_info" + for (( i=0; i<${#audio_streams[@]}; i+=3 )); do + lang_code="${audio_streams[i+2]}" + lang=$(translate_language "${lang_code:-" "}") + + # Collect languages + languages+="$lang" + + # Add comma separator if not the last item + if (( i+3 < ${#audio_streams[@]} )); then + languages+=", " + fi + done + + # Format output with languages first, followed by detailed info in brackets + echo "$languages" +} + +extract_subtitle_languages() { + subtitle_info=$(ffprobe -v quiet -select_streams s -show_entries stream_tags=language \ + -of default=noprint_wrappers=1:nokey=1 "$file" | paste -sd "," -) + languages="" + # echo "Subtitle Info: $subtitle_info" # Debugging line + IFS=',' read -r -a subtitle_streams <<< "$subtitle_info" + for ((i=0; i<${#subtitle_streams[@]}; i++)); do + lang_code="${subtitle_streams[i]}" + lang_code=$(echo "$lang_code" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]') # Normalize case + lang=$(translate_language "$lang_code") + languages+="$lang" + + # Add comma separator if not the last item + if (( i < ${#subtitle_streams[@]} - 1 )); then + languages+=", " + fi + done + # Format output with languages + echo "$languages" +} + + +# Function to detect if codec is h265 +check_h265_codec() { + local file="$1" + ffmpeg -i "$file" 2>&1 | grep -qi 'Video: hevc' && echo "h265" || echo "other" +} + +# Process all .mkv files in the directory +for file in *.mkv; do + if [[ -f "$file" ]]; then + # echo "Processing: $file" + + # Extract filename without extension + filename=$(basename "$file" .mkv) + + # Extract and translate audio and subtitle languages + audio_languages=$(extract_audio_languages "$file" "Audio") + subtitle_languages=$(extract_subtitle_languages "$file" "Subtitle") + + # Debugging: Ensure languages are detected + # echo "Audio Languages: $audio_languages" + # echo "Subtitle Languages: $subtitle_languages" + + # Detect codec + codec=$(check_h265_codec "$file") + + # Call Python script to update Excel + python3 /home/honney/.bin/sub_dub.py "$filename" "$audio_languages" "$subtitle_languages" "$codec" + fi +done + +# echo "Processing completed." diff --git a/sub_dub.py b/sub_dub.py new file mode 100644 index 0000000..88e1616 --- /dev/null +++ b/sub_dub.py @@ -0,0 +1,88 @@ +import openpyxl +import sys + +# Ensure correct number of arguments +if len(sys.argv) != 5: + print("Usage: update_excel.py ") + sys.exit(1) + +# Read command-line arguments +filename = sys.argv[1].strip() +audio_languages = sys.argv[2].strip() if sys.argv[2].strip() else "NONE" +subtitle_languages = sys.argv[3].strip() if sys.argv[3].strip() else "NONE" +codec = sys.argv[4].strip().lower() + +# Excel file path +excel_file = "/home/honney/mount/Storage/Nextcloud/DVDs_Blueray.xlsx" + +# Load the workbook and select the first sheet +workbook = openpyxl.load_workbook(excel_file) +sheet = workbook.active +temp = 0 + +# Find the row containing the filename +for row in sheet.iter_rows(min_row=2, max_row=sheet.max_row, min_col=1, max_col=1): + file_cell = row[0] + + if file_cell.value and isinstance(file_cell.value, str) and file_cell.value.strip() == filename: + temp += 1 + row_index = file_cell.row # Get row number + + # Ensure filename stays unchanged + sheet.cell(row=row_index, column=1).value = filename # Column A (Filename) + + # Update Audio and Subtitle languages (if empty, set to "NONE") + if sheet.cell(row=row_index, column=7).value == audio_languages: + temp += 1 + else: + sheet.cell(row=row_index, column=7).value = audio_languages # Column G (Audio) + + if sheet.cell(row=row_index, column=8).value == subtitle_languages: + temp += 1 + else: + sheet.cell(row=row_index, column=8).value = subtitle_languages # Column H (Subtitles) + + # If codec is h265, set columns D, E, F to 1 + if codec == "h265": + if sheet.cell(row=row_index, column=4).value == 1: + temp += 1 + else: + sheet.cell(row=row_index, column=4).value = 1 + + if sheet.cell(row=row_index, column=5).value == 1: + temp += 1 + else: + sheet.cell(row=row_index, column=5).value = 1 + + if sheet.cell(row=row_index, column=6).value == 1: + temp += 1 + else: + sheet.cell(row=row_index, column=6).value = 1 + if sheet.cell(row=row_index, column=3).value: + dvd = sheet.cell(row=row_index, column=3).value + else: + sheet.cell(row=row_index, column=3).value = "DVD" + dvd = "DVD" + if sheet.cell(row=row_index, column=2).value == "NOT CHECKED": + sheet.cell(row=row_index, column=2).value = "" + # Debugging: Confirm columns updated + else: print(f"❌ Codec Wrong {filename}") + + # Debugging: Confirm updates + # print(f"✅ Updated row {row_index} -> Audio: {audio_languages}, Subtitles: {subtitle_languages}, Codec: {codec}") + + break # Stop loop once match is found +if temp == 0: + print(f"❌ No matching row found for {filename}") +elif temp < 0 and temp != 6: + print(f"🟨 Updated Info for {filename}") +elif temp == 6: + print(f"🔵 Allready has info for {filename}") +else: + print(f"✅ {row_index}: {filename} | | {dvd} | 1 | 1 | 1 | {audio_languages} | {subtitle_languages}") + + +# Save the updated workbook +workbook.save(excel_file) + +# print(f"✅ Successfully updated Excel for {filename}") diff --git a/sync_delete b/sync_delete new file mode 100755 index 0000000..3aef7fc --- /dev/null +++ b/sync_delete @@ -0,0 +1,55 @@ +#!/bin/bash + +# --- +# A script to delete files in a target directory if they also exist in a reference directory. +# --- + +# Exit immediately if a command exits with a non-zero status. +set -e + +# Check if the correct number of arguments (two folders) was provided. +if [ "$#" -ne 2 ]; then + echo "Error: You must provide two directory paths." + echo "Usage: $0 " + echo " - target_directory: The folder FROM WHICH to delete files." + echo " - reference_directory: The folder used as a reference FOR deletion." + exit 1 +fi + +TARGET_DIR="$1" +REFERENCE_DIR="$2" + +# Check that both arguments are actual directories. +if [ ! -d "$TARGET_DIR" ]; then + echo "Error: Target directory '$TARGET_DIR' does not exist." + exit 1 +fi + +if [ ! -d "$REFERENCE_DIR" ]; then + echo "Error: Reference directory '$REFERENCE_DIR' does not exist." + exit 1 +fi + +echo "Scanning for files in '$TARGET_DIR' to delete based on the contents of '$REFERENCE_DIR'..." +echo "------------------------------------------------------------------" + +# Loop through every file in the reference directory. +# This method correctly handles filenames that may contain spaces or special characters. +find "$REFERENCE_DIR" -type f -print0 | while IFS= read -r -d '' reference_file; do + + # Get just the filename from the path. + filename=$(basename "$reference_file") + + # Create the full path for the corresponding file in the target directory. + target_file_path="$TARGET_DIR/$filename" + + # Check if that file actually exists in the target directory. + if [ -f "$target_file_path" ]; then + echo "Found match. Deleting: $target_file_path" + # The actual delete command. + rm "$target_file_path" + fi +done + +echo "------------------------------------------------------------------" +echo "Script finished." diff --git a/time_add.py b/time_add.py new file mode 100644 index 0000000..017de4c --- /dev/null +++ b/time_add.py @@ -0,0 +1,23 @@ +import re +from datetime import timedelta + +def parse_timecode(timecode): + parts = list(map(int, timecode.split(':'))) + if len(parts) == 2: # mm:ss format + return timedelta(minutes=parts[0], seconds=parts[1]) + elif len(parts) == 3: # hh:mm:ss format + return timedelta(hours=parts[0], minutes=parts[1], seconds=parts[2]) + else: + raise ValueError(f"Invalid timecode format: {timecode}") + +def add_timecodes(*timecodes): + total_time = sum((parse_timecode(tc) for tc in timecodes), timedelta()) + hours, remainder = divmod(total_time.seconds, 3600) + minutes, seconds = divmod(remainder, 60) + hours += total_time.days * 24 # Account for days if needed + return f"{hours:02}:{minutes:02}:{seconds:02}" + +if __name__ == "__main__": + timecodes = input("Enter timecodes separated by spaces: ").split() + total = add_timecodes(*timecodes) + print("Total time:", total)