Created
July 11, 2024 00:15
-
-
Save gretel/83a9d4ec3024cc8e07af45837a9a0ec6 to your computer and use it in GitHub Desktop.
Video File Date Adjustment Script - set file data according to the metadata of the video content
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# Video File Date Adjustment Script | |
# | |
# This script traverses a directory (and its subdirectories) to process video files | |
# (mp4, avi, mkv, mov) and their containing directories. For each video file, it: | |
# | |
# 1. Extracts the creation date from the file's metadata, prioritizing: | |
# a) com.apple.quicktime.creationdate | |
# b) container metadata | |
# c) video stream metadata | |
# 2. Checks if the file's current creation time is valid (not in the future). | |
# 3. If metadata is available and valid, sets the file's creation time to match the metadata. | |
# 4. If metadata is unavailable or the date is in the future, sets the creation time to current time minus one minute. | |
# 5. Sets the file's modification time to the current time if any changes were made. | |
# 6. Updates the containing directory's modification time to match the oldest file within it. | |
# | |
# The script outputs a line for each processed video file in the format: | |
# filename|status|set_date|file_creation_time | |
# | |
# If any errors occur during processing, they are logged to stderr. | |
# | |
# Usage: | |
# ./videodate.sh [directory_path] | |
# | |
# If no directory path is provided, the script processes the current directory. | |
# | |
# Dependencies: ffprobe (part of ffmpeg), date, stat, touch, find, sed, cut, basename, dirname | |
# | |
# Note: This script is designed for FreeBSD systems. Some modifications may be | |
# needed for other Unix-like systems. | |
set -e | |
filename_width=50 | |
error_exit() { | |
echo "error: $1" >&2 | |
exit 1 | |
} | |
check_dependencies() { | |
required_commands="ffprobe date stat touch find sed cut basename dirname" | |
for cmd in $required_commands; do | |
if ! command -v "$cmd" > /dev/null 2>&1; then | |
error_exit "required command '$cmd' not found. please install it and try again." | |
fi | |
done | |
if ! ffprobe -version | grep -q "ffprobe version"; then | |
error_exit "ffprobe found, but unable to determine version. please ensure ffmpeg is correctly installed." | |
fi | |
} | |
truncate_name() { | |
name="$1" | |
if [ ${#name} -gt $filename_width ]; then | |
truncated=$(echo "$name" | cut -c1-$((filename_width-3))) | |
printf "%-${filename_width}s" "${truncated}..." | |
else | |
printf "%-${filename_width}s" "$name" | |
fi | |
} | |
get_creation_date() { | |
file="$1" | |
ffprobe_output=$(ffprobe -v quiet -print_format json -show_format -show_streams "$file" 2>/dev/null) | |
# Check for QuickTime creation date | |
quicktime_date=$(echo "$ffprobe_output" | sed -n 's/.*"com.apple.quicktime.creationdate": "\([^"]*\)".*/\1/p') | |
if [ -n "$quicktime_date" ]; then | |
echo "quicktime|$quicktime_date" | |
return | |
fi | |
# Check for container metadata | |
container_date=$(echo "$ffprobe_output" | sed -n 's/.*"creation_time": "\([^"]*\)".*/\1/p' | head -n 1) | |
if [ -n "$container_date" ]; then | |
echo "container|$container_date" | |
return | |
fi | |
# Check for stream metadata | |
stream_date=$(echo "$ffprobe_output" | sed -n 's/.*"tags": {[^}]*"creation_time": "\([^"]*\)".*/\1/p' | head -n 1) | |
if [ -n "$stream_date" ]; then | |
echo "stream|$stream_date" | |
return | |
fi | |
echo "" # return empty string if no date found | |
} | |
print_aligned_output() { | |
printf "%s|%-25s|%-29s|%s\n" "$1" "$2" "$3" "$4" | |
} | |
process_file() { | |
file="$1" | |
if [ ! -f "$file" ]; then | |
echo "error: file does not exist: $file" >&2 | |
return | |
fi | |
filename=$(basename "$file") | |
# Skip files starting with a dot | |
case "$filename" in | |
._*) return ;; | |
esac | |
truncated_filename=$(truncate_name "$filename") | |
current_time=$(date +%s) | |
one_minute_ago=$((current_time - 60)) | |
metadata_result=$(get_creation_date "$file") | |
if [ -z "$metadata_result" ]; then | |
new_time=$one_minute_ago | |
status="no_metadata_set_recent" | |
else | |
metadata_source=$(echo "$metadata_result" | cut -d'|' -f1) | |
metadata_date=$(echo "$metadata_result" | cut -d'|' -f2) | |
formatted_date=$(echo "$metadata_date" | sed 's/\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\)T\([0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}\).*/\1 \2/') | |
if [ -z "$formatted_date" ]; then | |
new_time=$one_minute_ago | |
status="date_format_failed_set_recent" | |
else | |
if ! metadata_seconds=$(date -j -f "%Y-%m-%d %H:%M:%S" "$formatted_date" "+%s" 2>/dev/null); then | |
new_time=$one_minute_ago | |
status="date_parse_failed_set_recent" | |
else | |
if [ "$metadata_seconds" -gt "$current_time" ]; then | |
new_time=$one_minute_ago | |
status="future_date_set_recent" | |
else | |
new_time=$metadata_seconds | |
status="${metadata_source}_metadata" | |
fi | |
fi | |
fi | |
fi | |
current_birth_time=$(stat -f "%B" "$file") | |
if [ "$current_birth_time" -eq "$new_time" ]; then | |
print_aligned_output "$truncated_filename" "already_correct" "$(date -r "$new_time")" "$(date -r "$current_birth_time")" | |
return | |
fi | |
if ! touch -h -t "$(date -r "$new_time" "+%Y%m%d%H%M.%S")" "$file"; then | |
echo "error: failed to update creation time for $file" >&2 | |
print_aligned_output "$truncated_filename" "update_failed" "$(date -r "$new_time")" "" | |
return | |
fi | |
if ! touch -h -m -t "$(date -r "$current_time" "+%Y%m%d%H%M.%S")" "$file"; then | |
echo "error: failed to update modification time for $file" >&2 | |
print_aligned_output "$truncated_filename" "mod_time_update_failed" "$(date -r "$new_time")" "" | |
return | |
fi | |
new_birth_time=$(stat -f "%B" "$file") | |
print_aligned_output "$truncated_filename" "$status" "$(date -r "$new_time")" "$(date -r "$new_birth_time")" | |
# Update the containing directory | |
dir=$(dirname "$file") | |
update_directory "$dir" | |
} | |
update_directory() { | |
dir="$1" | |
if [ ! -d "$dir" ]; then | |
echo "error: directory does not exist: $dir" >&2 | |
return | |
fi | |
# Find the oldest file in the directory | |
oldest_file=$(find "$dir" -type f -not -name ".*" -exec stat -f "%m %N" {} + | sort -n | head -1 | cut -d' ' -f2-) | |
if [ -z "$oldest_file" ]; then | |
return | |
fi | |
oldest_time=$(stat -f "%m" "$oldest_file") | |
current_dir_time=$(stat -f "%m" "$dir") | |
if [ "$current_dir_time" -ne "$oldest_time" ]; then | |
if ! touch -h -t "$(date -r "$oldest_time" "+%Y%m%d%H%M.%S")" "$dir"; then | |
echo "error: failed to update directory time for $dir" >&2 | |
fi | |
fi | |
} | |
traverse_directory() { | |
if [ ! -d "$1" ]; then | |
error_exit "not a directory: $1" | |
fi | |
# Print header | |
print_aligned_output "File" "Status" "Set Date" "Actual Creation Time" | |
find "$1" -type f \( -name "*.mp4" -o -name "*.avi" -o -name "*.mkv" -o -name "*.mov" \) | while read -r file; do | |
process_file "$file" || echo "error: failed to process file: $file" >&2 | |
done | |
} | |
# main script | |
check_dependencies | |
if [ $# -eq 0 ]; then | |
start_dir="." | |
elif [ $# -eq 1 ]; then | |
start_dir="$1" | |
if [ ! -d "$start_dir" ]; then | |
error_exit "specified path is not a directory: $start_dir" | |
fi | |
else | |
error_exit "usage: $0 [directory_path]" | |
fi | |
traverse_directory "$start_dir" | |
echo "processing complete." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment