Skip to content

Instantly share code, notes, and snippets.

@andre-st
Last active October 9, 2021 04:20
Show Gist options
  • Save andre-st/60c3db8f68c7d9aa191cd13b3621efbb to your computer and use it in GitHub Desktop.
Save andre-st/60c3db8f68c7d9aa191cd13b3621efbb to your computer and use it in GitHub Desktop.
Organize access to media files via tags
#!/bin/bash
# Purpose: Creates sub-directories with symlinks to files that are scattered over
# different directories but share a commonality, e.g, being 'a favourite'
# Context: Media collections
# Analogy: Database views, locate-command result saved as filesystem objects
# Author : github.com/andre-st
# Version: 2021-01-28
#
# Usage:
# 1. Copy this file to /usr/loca/bin/
# 2. create a ".views" directory in your collection
# 3. $ ln -s /usr/local/bin/mkview.sh your_collection/.views/update.sh
# 4. Tag filenames in your collection with:
# + or ++ or +++ to indicate favourites (sorts them to the top in each dir too)
# todo to indicate a planned edit
#
# Note:
# - browse XXXmb directories to find and delete big files if HDD space shrinks
# (favourites are excluded by default)
# - adjust views in this file as you need or create an "update.cfg" in a .views-directory
# with nothing more than custom 'mkview', 'mksample' and 'mkquality'
# calls similar to this executable.
# Update.cfg can also sets extra variables such as: FINDOPTEXTRA=-follow to follow symbolic links
# - dot-directories are ignored
#
#
# Example result:
# your_collection
# |-- .views
# | |-- 100mb
# | |-- 200mb
# | |-- 80mb
# | | |-- 1080p
# | | `-- 360p
# | |-- fav+
# | | `-- sample
# | |-- fav++
# | | `-- sample
# | |-- fav+++
# | |-- recent
# | `-- todo
# |
# |-- your_A
# |-- your_B
# |-- ...
# `-- your_Z
# Creates a "dir/sample" directory with a random selection of files from a source directory "dir"
# given that "dir" has more than 60 symbolic links.
# -> string dir with symlinks
function mksample()
{
dir="$1"
sampledir="sample"
samplesize=60
largersamplesize=$(( samplesize*2 ))
pushd "$dir" > /dev/null
rm -rf "$sampledir"
numlinks=$( find . -type l -maxdepth 1 | wc -l )
if (( $numlinks > $samplesize ))
then
mkdir "$sampledir"
find . -type l -maxdepth 1 | shuf -n $largersamplesize | shuf -n $samplesize | while read linkname
do
abspath=$( readlink -e "$linkname" )
relpath=$( realpath --relative-to "$sampledir" "$abspath" )
ln -sfn "$relpath" "$sampledir/$linkname"
done
fi
popd > /dev/null
}
# Creates a subdir based on video resolution from links in a source directory "dir".
# -> string dir with symlinks
function mkquality()
{
dir="$1"
lq_height=360
hq_height=1080
lq_dir="${lq_height}p"
hq_dir="${hq_height}p"
pushd "$dir" > /dev/null
rm -rf "$lq_dir" "$hq_dir"
mkdir "$lq_dir" "$hq_dir"
find . -type l -maxdepth 1 | while read linkname
do
abspath=$( readlink -e "$linkname" )
relpath=$( realpath --relative-to "$lq_dir" "$abspath" )
height=$( ffprobe -v quiet -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 "$abspath" | head -n 1 )
[ -z "$height" ] && continue
if (( "$height" <= $lq_height ))
then
ln -sfn "$relpath" "$lq_dir/$linkname"
elif (( "$height" >= $hq_height ))
then
ln -sfn "$relpath" "$hq_dir/$linkname"
fi
done
popd > /dev/null
}
# -> string view name
# -> string find-command expressions and test options (view filter)
# -> --add dont delete links in view before adding new ones
function mkview()
{
viewdir=$1
findopt="-type f $2 -and ! -name '*.txt' -and ! -path '*/.*' $SKIPFINDOPT"
findcmd="find .. $FINDOPTEXTRA \( $findopt \) -print $findpipe"
mkdir -p $viewdir
if [[ "$@" != *"--add"* ]]
then
find $viewdir -type l -delete
fi
IFS=$'\n' # Bash's field seperator != space (in filenames)
while read filename
do
linkname=$(basename "$filename")
while [ -e "$viewdir/$linkname" ] # if dir1/f.mp4 and dir2/f.mp4 rename one link:
do
target_of_exist_link=`readlink $viewdir/$linkname`
[[ "$target_of_exist_link" == "../$filename" ]] && break # identical targets
linkname="_$linkname" # _f.mp4, then __f.mp4, ___f.mp4 until unique
done
ln -s "../$filename" "$viewdir/$linkname" 2>/dev/null
done < <(eval "$findcmd")
}
echo "Updating .views"
pushd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null
# Skip sub-directories with own .views (useless redundancy otherwise):
skipfindoptarr=(`find .. -type d -name '.views' -and ! -path '../.views' -printf '-and ! -path "%h/*" '`)
SKIPFINDOPT="${skipfindoptarr[@]}"
# Custom views and changes to global vars with effect on default views:
[[ -e update.cfg ]] && source update.cfg
# Default views:
mkview 'todo' '-iname "*todo*"'
mkview 'fav+' '-name "*+*" -and ! -name "*.jpg" -and ! -name "* + *" '
mkview 'fav++' '-name "*++*" -and ! -name "*.jpg" -and ! -name "* + *" '
mkview 'fav+++' '-name "*+++*" -and ! -name "*.jpg" -and ! -name "* + *" '
mksample 'fav+'
mksample 'fav++'
mksample 'fav+++'
mkview '80mb' '-size +80000k -and ! -name "*+*"'
mkview '100mb' '-size +100000k -and ! -name "*+*"'
mkview '200mb' '-size +200000k -and ! -name "*+*"'
mkquality '80mb'
mkview 'recent' '-mtime -31' # 31 days
#mkview 'oldfmt' '-name "**.avi" -or -name "*.mpg" -or -name "*.mpeg" -or -name "*.wmv"'
#mkview 'cut' '-iname "*.cut.*"'
popd > /dev/null
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment