312 lines
7.9 KiB
Bash
Executable File
312 lines
7.9 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
## 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
|
||
# - `cargo install sd` – better sed
|
||
# - pngcrush – to compress PNGs (losless)
|
||
# // - pngquant – to compress PNGs (lossy)
|
||
# - jpegoptim – to compress JPEGs
|
||
|
||
|
||
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
|
||
|mpe?g|mp\d|mov|mkv|avif?|asf|3gpp?
|
||
|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' ;;
|
||
|
||
# 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
|