Add All existing
This commit is contained in:
64
append
Executable file
64
append
Executable file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Usage info
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
echo "Usage: $0 {front|back|ext|extension} <string> [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."
|
||||||
74
create_folder_counting
Executable file
74
create_folder_counting
Executable file
@@ -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."
|
||||||
40
create_folder_date
Executable file
40
create_folder_date
Executable file
@@ -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."
|
||||||
95
deappend
Executable file
95
deappend
Executable file
@@ -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 <mode> <string> [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()
|
||||||
64
deinterlacing_cpu.sh
Executable file
64
deinterlacing_cpu.sh
Executable file
@@ -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"
|
||||||
32
deinterlacing_gpu.sh
Executable file
32
deinterlacing_gpu.sh
Executable file
@@ -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"
|
||||||
78
embed_audio
Executable file
78
embed_audio
Executable file
@@ -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: <video_file> <audio_file> <output_file>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
49
embed_subtitles
Executable file
49
embed_subtitles
Executable file
@@ -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 <video_file> <subtitle_file> <output_file> # Manual subtitel embed"
|
||||||
|
echo " $0 # Auto mode (pair audio + embed subtitles)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
66
embed_subtitles.py
Normal file
66
embed_subtitles.py
Normal file
@@ -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 <video_file_or_directory>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
input_path = sys.argv[1]
|
||||||
|
process_path(input_path)
|
||||||
80
episode_numbers
Executable file
80
episode_numbers
Executable file
@@ -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"
|
||||||
37
extract_audio
Executable file
37
extract_audio
Executable file
@@ -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
|
||||||
95
extract_audio.py
Normal file
95
extract_audio.py
Normal file
@@ -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)
|
||||||
37
extract_subtitles
Executable file
37
extract_subtitles
Executable file
@@ -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
|
||||||
88
extract_subtitles.py
Normal file
88
extract_subtitles.py
Normal file
@@ -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)
|
||||||
|
|
||||||
19
ffbitrate
Executable file
19
ffbitrate
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if the user provided a file as an argument
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: $0 <video_file>"
|
||||||
|
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
|
||||||
88
ffsplit
Executable file
88
ffsplit
Executable file
@@ -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."
|
||||||
36
fftesting
Executable file
36
fftesting
Executable file
@@ -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."
|
||||||
49
filter_extra
Executable file
49
filter_extra
Executable file
@@ -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
|
||||||
120
generate-thumb
Executable file
120
generate-thumb
Executable file
@@ -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"
|
||||||
12
grayjay
Executable file
12
grayjay
Executable file
@@ -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 \"\$@\"" -- "$@"
|
||||||
51
list_folder_empty
Executable file
51
list_folder_empty
Executable file
@@ -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 "."
|
||||||
16
nohup.out
Executable file
16
nohup.out
Executable file
@@ -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
|
||||||
27
rename_episodes
Executable file
27
rename_episodes
Executable file
@@ -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
|
||||||
27
rename_filtered
Executable file
27
rename_filtered
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if scheme is provided
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: rename_filtered <scheme> [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 "$@"
|
||||||
64
rename_filtered.py
Normal file
64
rename_filtered.py
Normal file
@@ -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 <scheme> [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()
|
||||||
47
rename_to_first4.py
Executable file
47
rename_to_first4.py
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
rename_to_first4.py
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 rename_to_first4.py <directory>
|
||||||
|
|
||||||
|
Renames each file in <directory> 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 <directory>")
|
||||||
|
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()
|
||||||
525
simple_ffprobe_script
Executable file
525
simple_ffprobe_script
Executable file
@@ -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
|
||||||
137
split_mkv_by_chapter
Executable file
137
split_mkv_by_chapter
Executable file
@@ -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] <input_file> <timestamp1> <timestamp2> ..."
|
||||||
|
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 "$@"
|
||||||
122
split_mkv_by_chapter.py
Normal file
122
split_mkv_by_chapter.py
Normal file
@@ -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 <input_file> <timestamp1> <timestamp2> ...")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
input_file = sys.argv[1]
|
||||||
|
timestamps = sys.argv[2:]
|
||||||
|
|
||||||
|
main(input_file, timestamps)
|
||||||
282
sub_dub
Executable file
282
sub_dub
Executable file
@@ -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."
|
||||||
88
sub_dub.py
Normal file
88
sub_dub.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import openpyxl
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Ensure correct number of arguments
|
||||||
|
if len(sys.argv) != 5:
|
||||||
|
print("Usage: update_excel.py <filename> <audio_languages> <subtitle_languages> <codec>")
|
||||||
|
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}")
|
||||||
55
sync_delete
Executable file
55
sync_delete
Executable file
@@ -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 <target_directory> <reference_directory>"
|
||||||
|
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."
|
||||||
23
time_add.py
Normal file
23
time_add.py
Normal file
@@ -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)
|
||||||
Reference in New Issue
Block a user