2023-08-06 22:47:24 +02:00
|
|
|
|
#!/usr/bin/env bash
|
2023-06-20 17:22:54 +02:00
|
|
|
|
|
|
|
|
|
## rename-ext
|
|
|
|
|
# > changes file-extension based on mime-type
|
|
|
|
|
|
|
|
|
|
## also
|
|
|
|
|
# - removes `*?|^:"<>` characters from the file-name
|
|
|
|
|
# - reducing multiple spaces, dots, and underscores
|
|
|
|
|
# - adds rename-date suffix to ensure uniquness of the filename
|
|
|
|
|
# - strips exec-flag from non-executive types (usual leftover from FAT/NTFS)
|
|
|
|
|
# - optimizes images while at it (shows reduction size in `kb` and `%`)
|
|
|
|
|
|
|
|
|
|
## example
|
|
|
|
|
# rename-ext /Data/Pictures/_unsorted/**/*
|
|
|
|
|
|
|
|
|
|
## requires
|
2023-08-06 22:47:24 +02:00
|
|
|
|
# - file - to determine file type
|
2023-06-20 17:22:54 +02:00
|
|
|
|
# - `cargo install sd` – better sed
|
2023-08-06 22:47:24 +02:00
|
|
|
|
# - jpegoptim – to compress JPEGs
|
2023-08-27 01:45:07 +02:00
|
|
|
|
# - imagemagick or graphicsmagic – to mogrify PNGs
|
2023-06-20 17:22:54 +02:00
|
|
|
|
# - pngcrush – to compress PNGs (losless)
|
|
|
|
|
# // - pngquant – to compress PNGs (lossy)
|
|
|
|
|
|
|
|
|
|
ALL_EXTS=`echo \
|
|
|
|
|
'gif|jpe?g|jp2|jfif|a?png|web(p|m)|svg
|
|
|
|
|
|eps|tga|tiff?|psd|ico|xcf|heic
|
|
|
|
|
|eot|otf|ttf|epub|doc|docx|xls|swf|pdf|odt
|
|
|
|
|
|flac|opus|ogg|m4a|wav
|
2023-08-27 01:45:07 +02:00
|
|
|
|
|mpe?g|mp\d|mov|mkv|avif?|asf|3gpp?|av1
|
2023-06-20 17:22:54 +02:00
|
|
|
|
|html?|sh|py|php
|
|
|
|
|
' \
|
|
|
|
|
| tr -d '[:space:]' \
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
mkdir -p ~/tmp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
OPTIONS=''
|
|
|
|
|
CHX_CHANGELOG=''
|
|
|
|
|
NR='1'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
calc_reduction()
|
|
|
|
|
{
|
|
|
|
|
SIZE_PRE="$1"
|
|
|
|
|
SIZE_POST="$2"
|
|
|
|
|
PERECENT=` echo "scale=2; 100 - (100 * $SIZE_POST / $SIZE_PRE)" | bc `
|
|
|
|
|
RESULT="$(( $SIZE_PRE / 1000 ))-$(( ( $SIZE_PRE - $SIZE_POST ) / 1000 )) kb \
|
|
|
|
|
($PERECENT%)
|
|
|
|
|
"
|
|
|
|
|
echo "$RESULT"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ch_x()
|
|
|
|
|
{
|
|
|
|
|
FILENAME="$1"
|
|
|
|
|
# echo "FILENAME = '$FILENAME'"
|
|
|
|
|
|
|
|
|
|
CHX_CHANGELOG=''
|
|
|
|
|
|
|
|
|
|
IS_X_INTENDED='0'
|
|
|
|
|
if [ ! -z "$2" ]; then
|
|
|
|
|
IS_X_INTENDED='1'
|
|
|
|
|
fi
|
|
|
|
|
# echo "IS_X_INTENDED = '$IS_X_INTENDED'"
|
|
|
|
|
|
|
|
|
|
IS_X=`stat -c '%A' "$FILENAME" | grep 'x' | wc -l`
|
|
|
|
|
# echo "IS_X = '$IS_X'"
|
|
|
|
|
|
|
|
|
|
MODE='-x'
|
|
|
|
|
if [[ '1' == "$IS_X_INTENDED" ]]; then
|
|
|
|
|
MODE='+x'
|
|
|
|
|
fi
|
|
|
|
|
# echo "MODE = '$MODE'"
|
|
|
|
|
|
|
|
|
|
if [[ ! "$OPTIONS" =~ 'test-run' ]]; then
|
|
|
|
|
# echo '(not a test-run)'
|
|
|
|
|
if [[ ! "$OPTIONS" =~ 'only-ext' ]]; then
|
|
|
|
|
# echo '(not only-ext)'
|
|
|
|
|
if [[ ! "$IS_X" == "$IS_X_INTENDED" ]]; then
|
|
|
|
|
CHX_CHANGELOG="chmod $MODE"
|
|
|
|
|
fi
|
|
|
|
|
chmod "$MODE" "$FILENAME"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
rename_ext()
|
|
|
|
|
{
|
|
|
|
|
NAME="$1"
|
|
|
|
|
EXT="$2"
|
|
|
|
|
NOTE=''
|
|
|
|
|
TARGET=''
|
|
|
|
|
|
|
|
|
|
ch_x "$FILENAME"
|
|
|
|
|
|
|
|
|
|
if [[ "$OPTIONS" =~ 'only-ext' ]]; then
|
|
|
|
|
NAME_NO_EXT=` echo "$NAME" \
|
|
|
|
|
| sd -f i "(\.+($ALL_EXTS))$" '' \
|
|
|
|
|
`
|
|
|
|
|
TARGET="$NAME_NO_EXT.$EXT"
|
|
|
|
|
else
|
|
|
|
|
if [[ ! "$OPTIONS" =~ 'test-run' ]]; then
|
|
|
|
|
|
|
|
|
|
SIZE_PRE=` stat "$FILENAME" -c '%s' `
|
|
|
|
|
|
|
|
|
|
[ 'jpg' = "$EXT" ] \
|
|
|
|
|
&& jpegoptim "$FILENAME" -qsftp
|
|
|
|
|
if [ 'png' = "$EXT" ]; then
|
|
|
|
|
mogrify "$FILENAME"
|
|
|
|
|
pngcrush -nolimits -s "$FILENAME" "$FILENAME.tmp.png"
|
|
|
|
|
# pngquant "$FILENAME" --output "$FILENAME" \
|
|
|
|
|
# --quality=100 --force --skip-if-larger --speed 1
|
|
|
|
|
if [ -f "$FILENAME.tmp.png" ] \
|
|
|
|
|
&& [ -s "$FILENAME.tmp.png" ]
|
|
|
|
|
then
|
|
|
|
|
mv "$FILENAME.tmp.png" "$FILENAME"
|
|
|
|
|
else
|
|
|
|
|
rm "$FILENAME.tmp.png"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
SIZE_POST=` stat "$FILENAME" -c '%s' `
|
|
|
|
|
|
|
|
|
|
[ $SIZE_PRE -gt $SIZE_POST ] \
|
|
|
|
|
&& NOTE=` calc_reduction "$SIZE_PRE" "$SIZE_POST" `
|
|
|
|
|
fi
|
|
|
|
|
DATE=` date -r "$NAME" -u "+%Y%m%d%H%M%S" `
|
|
|
|
|
TCLEAN=` echo "$NAME" \
|
|
|
|
|
| sd '[\\\?\*\|^:"<>]+' '_' \
|
|
|
|
|
| sd '_\d{12,}\.' '.' \
|
|
|
|
|
| sd -f i "(\.+($ALL_EXTS))+$" '' \
|
|
|
|
|
`
|
|
|
|
|
TARGET=` echo "${TCLEAN}_$DATE.$EXT" \
|
|
|
|
|
| sd '([\s\.,_]){3,}' '$1' \
|
|
|
|
|
`
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
IS_SAME_NAME='0'
|
|
|
|
|
if [[ "$NAME" == "$TARGET" ]]; then
|
|
|
|
|
IS_SAME_NAME='1'
|
|
|
|
|
fi
|
|
|
|
|
# echo "IS_SAME_NAME = '$IS_SAME_NAME'"
|
|
|
|
|
|
|
|
|
|
# echo "CHX_CHANGELOG = '$CHX_CHANGELOG'"
|
|
|
|
|
if [[ '1' == "$IS_SAME_NAME" ]] \
|
|
|
|
|
&& [ -z "$CHX_CHANGELOG" ]
|
|
|
|
|
then
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "$NR:"
|
|
|
|
|
NR="$(($NR+1))"
|
|
|
|
|
echo " $FILENAME"
|
|
|
|
|
|
|
|
|
|
if [ ! -z "$CHX_CHANGELOG" ]; then
|
|
|
|
|
echo " $CHX_CHANGELOG"
|
|
|
|
|
CHX_CHANGELOG=''
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ '1' == "$IS_SAME_NAME" ]]; then
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$OPTIONS" =~ 'test-run' ]]; then
|
|
|
|
|
if [ -f "$TARGET" ]; then
|
|
|
|
|
if [[ "$PARAMS" =~ 'force-replace' ]]; then
|
|
|
|
|
echo " >> replacing existing file >>"
|
|
|
|
|
else
|
|
|
|
|
echo " !! file already exist !!"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
echo "$TARGET"
|
|
|
|
|
else
|
|
|
|
|
if [ ! -f "$TARGET" ]; then
|
|
|
|
|
echo " >> $NOTE >>"
|
|
|
|
|
echo " $TARGET"
|
|
|
|
|
mv "$NAME" "$TARGET"
|
|
|
|
|
else
|
|
|
|
|
if [[ "$PARAMS" =~ 'force-replace' ]]; then
|
|
|
|
|
echo " >> $NOTE ## replacing existing file >>"
|
|
|
|
|
echo " $TARGET"
|
|
|
|
|
mv "$NAME" "$TARGET"
|
|
|
|
|
else
|
|
|
|
|
echo ' !! File already exist, use `--force-replace` to overwrite !! '
|
|
|
|
|
echo " $TARGET"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for PARAM in "$@"; do
|
|
|
|
|
# echo "PARAM = '$PARAM'"
|
|
|
|
|
if [[ '--only-ext' == "$PARAM" ]]; then
|
|
|
|
|
OPTIONS="$OPTIONS;only-ext"
|
|
|
|
|
# echo "OPTIONS = '$OPTIONS'"
|
|
|
|
|
elif [[ '--test-run' == "$PARAM" ]]; then
|
|
|
|
|
OPTIONS="$OPTIONS;test-run"
|
|
|
|
|
# echo "OPTIONS = '$OPTIONS'"
|
|
|
|
|
elif [[ '--force-replace' == "$PARAM" ]]; then
|
|
|
|
|
OPTIONS="$OPTIONS;force-replace"
|
|
|
|
|
# echo "OPTIONS = '$OPTIONS'"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
[ ! -z "$OPTIONS" ] \
|
|
|
|
|
&& echo "OPTIONS: '$OPTIONS'"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# echo "@ = '$@'"
|
|
|
|
|
for FILENAME in "$@"; do
|
|
|
|
|
|
|
|
|
|
# echo "FILENAME = '$FILENAME'"
|
|
|
|
|
|
|
|
|
|
# NOT_F_=`[ ! -f "$FILENAME" ]`
|
|
|
|
|
# echo "NOT_F_ = '$NOT_F_'"
|
|
|
|
|
|
|
|
|
|
# [ ! -f "$FILENAME" ] \
|
|
|
|
|
# && continue
|
|
|
|
|
|
|
|
|
|
FILE_TYPE=` file -b --mime-type "$FILENAME" `
|
|
|
|
|
# echo "FILE_TYPE = '$FILE_TYPE'"
|
|
|
|
|
|
|
|
|
|
case "$FILE_TYPE" in
|
|
|
|
|
|
|
|
|
|
# pixel-based
|
|
|
|
|
'image/gif') rename_ext "$FILENAME" 'gif' ;;
|
|
|
|
|
'image/jpeg') rename_ext "$FILENAME" 'jpg' ;;
|
|
|
|
|
'image/jp2') rename_ext "$FILENAME" 'jp2' ;;
|
|
|
|
|
'image/png') rename_ext "$FILENAME" 'png' ;;
|
|
|
|
|
'image/webp') rename_ext "$FILENAME" 'webp' ;;
|
2023-08-27 01:45:07 +02:00
|
|
|
|
'image/avif') rename_ext "$FILENAME" 'avif' ;;
|
2023-06-20 17:22:54 +02:00
|
|
|
|
|
|
|
|
|
# vector-based
|
|
|
|
|
'image/svg+xml') rename_ext "$FILENAME" 'svg' ;;
|
|
|
|
|
'image/x-eps') rename_ext "$FILENAME" 'eps' ;;
|
|
|
|
|
|
|
|
|
|
# some less common image formats
|
|
|
|
|
'image/heic') rename_ext "$FILENAME" 'heic' ;;
|
|
|
|
|
'image/tiff') rename_ext "$FILENAME" 'tiff' ;;
|
|
|
|
|
'image/vnd.adobe.photoshop') rename_ext "$FILENAME" 'psd' ;;
|
|
|
|
|
'image/vnd.microsoft.icon') rename_ext "$FILENAME" 'ico' ;;
|
|
|
|
|
'image/x-tga') rename_ext "$FILENAME" 'tga' ;;
|
|
|
|
|
'image/x-xcf') rename_ext "$FILENAME" 'xcf' ;;
|
|
|
|
|
|
|
|
|
|
# fonts
|
|
|
|
|
'application/vnd.ms-fontobject') rename_ext "$FILENAME" 'eot' ;;
|
|
|
|
|
'application/vnd.ms-opentype') rename_ext "$FILENAME" 'otf' ;;
|
|
|
|
|
'font/sfnt') rename_ext "$FILENAME" 'ttf' ;;
|
|
|
|
|
|
|
|
|
|
# documents
|
|
|
|
|
'application/epub+zip') rename_ext "$FILENAME" 'epub' ;;
|
|
|
|
|
'application/msword') rename_ext "$FILENAME" 'doc' ;;
|
|
|
|
|
'application/pdf') ch_x "$FILENAME" ;; # can be .ai
|
|
|
|
|
'application/vnd.ms-excel') rename_ext "$FILENAME" 'xls' ;;
|
|
|
|
|
'application/vnd.oasis.opendocument.text') rename_ext "$FILENAME" 'odt' ;;
|
|
|
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document') rename_ext "$FILENAME" 'docx' ;;
|
|
|
|
|
'application/x-shockwave-flash') rename_ext "$FILENAME" 'swf' ;;
|
|
|
|
|
|
|
|
|
|
# audio
|
|
|
|
|
'audio/flac') rename_ext "$FILENAME" 'flac' ;;
|
|
|
|
|
'audio/mpeg') rename_ext "$FILENAME" 'mp3' ;;
|
|
|
|
|
'audio/ogg') rename_ext "$FILENAME" 'ogg' ;;
|
|
|
|
|
'audio/x-m4a') rename_ext "$FILENAME" 'm4a' ;;
|
|
|
|
|
'audio/x-wav') rename_ext "$FILENAME" 'wav' ;;
|
|
|
|
|
|
|
|
|
|
# video
|
|
|
|
|
'video/3gpp') rename_ext "$FILENAME" '3gp' ;;
|
|
|
|
|
'video/av1') rename_ext "$FILENAME" 'avi' ;;
|
|
|
|
|
'video/MP2T') rename_ext "$FILENAME" 'mp2' ;;
|
|
|
|
|
'video/mp4') rename_ext "$FILENAME" 'mp4' ;;
|
|
|
|
|
'video/quicktime') rename_ext "$FILENAME" 'mov' ;;
|
|
|
|
|
'video/webm') rename_ext "$FILENAME" 'webm' ;;
|
|
|
|
|
'video/x-m4v') rename_ext "$FILENAME" 'mp4' ;;
|
|
|
|
|
'video/x-matroska') rename_ext "$FILENAME" 'mkv' ;;
|
|
|
|
|
'video/x-ms-asf') rename_ext "$FILENAME" 'asf' ;;
|
|
|
|
|
|
|
|
|
|
# text
|
|
|
|
|
'application/json') ch_x "$FILENAME" ;; # can be any other language
|
|
|
|
|
'text/html') ch_x "$FILENAME" ;; # can be .htm, .htc, .mht, ...
|
|
|
|
|
'text/xml') ch_x "$FILENAME" ;; # can be .xml, .opml, .rss, ...
|
|
|
|
|
'text/plain') ;; # can be any file with not enough text
|
|
|
|
|
'text/x-python') ;; # can be .py, .py3, have no .ext
|
|
|
|
|
'text/x-shellscript') ;; # can be .sh, .zsh, have no .ext
|
|
|
|
|
|
|
|
|
|
# binady data
|
|
|
|
|
'application/x-sqlite3') ch_x "$FILENAME" ;; # can be .db, .sql, .sq3
|
|
|
|
|
|
|
|
|
|
# special
|
|
|
|
|
'application/octet-stream') ;; # can be anything
|
|
|
|
|
'inode/directory')
|
|
|
|
|
# echo "$FILENAME is a folder"
|
|
|
|
|
# echo "about to ch_x"
|
|
|
|
|
ch_x "$FILENAME" '+'
|
|
|
|
|
;; # a folder
|
|
|
|
|
'inode/x-empty') ch_x "$FILENAME" ;; # zero-size
|
|
|
|
|
|
|
|
|
|
# default
|
|
|
|
|
*) echo "?? $FILENAME : $FILE_TYPE" ;;
|
|
|
|
|
|
|
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# if [[ "@#" -eq 1 ]]; then
|
|
|
|
|
# if [[ `file -b --mime-type "$1"` == "inode/directory" ]]; then
|
|
|
|
|
# find "$1"
|
|
|
|
|
# fi
|
|
|
|
|
# fi
|