summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xlocalsonganizer433
1 files changed, 433 insertions, 0 deletions
diff --git a/localsonganizer b/localsonganizer
new file mode 100755
index 0000000..6836118
--- /dev/null
+++ b/localsonganizer
@@ -0,0 +1,433 @@
+#!/bin/bash
+#
+# songanizer - script to organize ogg and mp3 files
+# Copyright (c) 2002-2004 Patrick Ohnewein.
+# All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+#
+# Description:
+#
+# Script to organize a directory containing ogg and mp3 files.
+#
+# The biggest problem for me, during my efforts to organize music files, was
+# the choice of the directory structure. Should the directory structure reflect
+# the author or the genre or may be the album? I ended up with the conclusion,
+# that no ideal directory structure exists. So I wanted different virtual
+# directory structures to the same data. Thanks to the symbolic links
+# capability of the file systems I use (ext2, ext3, ...) this dream has become
+# reality!
+#
+# The script gets a list of _data directories, in which the real ogg and mp3
+# files reside. The script has to read informations like author, album, genre,
+# ... from these files and create parallel directory structures, which
+# just contain symbolic links to the real files.
+#
+# The goal is to create virtual directory structures, which give different
+# views of the data, but without having redundant copies of the files
+# themselves.
+#
+# dependences:
+# - It's a bash script and therefore needs a running bash.
+# - Uses getopt (enhanced) to extract options from the command line arguments.
+# - Uses gettext (gettext.sh) for i18n support.
+# - The link structures are created using links on the file system and
+# therefore the file system must support them (i.e. ext2, ext3, ...)
+# - To read the mp3 tags the mp3info tool gets used.
+# Get it at http://ibiblio.org/mp3info/
+#
+# How is the directory structure organized?
+# In the base directory live the following directrories:
+# _data* all directories, with a name starting with _data, contain the
+# real data (all links will point to its content). These can also
+# be symbolic links to directories on other devices.
+# _artist contains the link structure on the basis of the artist tag
+# _genre contains the link structure on the basis of the genre tag
+# _initial contains the link structure on the basis of the initials
+# ... and more, depending on the switchs passed to the script
+#
+#
+# @version 0.8.20060507 - Make all options singular (fixing broken artist)
+# - Major reorganizing to separate targets from tags.
+# - Add new funtions: enableTargets, resolveTag,
+# resolveTarget
+# - Add new targets: decade, yeartree, decadetree,
+# genretree, artisttree, alltree
+# - Drop target: comments
+# - Fix bailing out if unable to create temp dir
+# - Rephrase help text
+# - Use safe tempdir
+# @version 0.8.20060211 - Major reorganizing to loop only once (not per-tag)
+# @version 0.8, 2005-11-02 - replaced ls with find [savannah bug #4932
+# task #2546]
+# - there can be multiple levels of sub-directories
+# inside every data directory [savannah bug #4933
+# task #2547]
+# - symbolic links point to individual files instead
+# of directories
+# - added the --all switch
+# - changed the creation algo, so it will first create
+# the structure in a tmp directory, and only after
+# completion of this heavy task, in a short
+# operation the structure is moved to its desired
+# location
+# @version 0.7, 2004-02-01 - added gettext support for i18n
+# @version 0.6, 2004-01-24 - added long options support
+# @version 0.5, 2003-08-22 - fixed problem with same album in more than one
+# data directory [savannah bug #4877]
+# @version 0.4, 2003-08-17 - added support for multiple data directories
+# @version 0.3, 2002-12-28 - translated all texts into english
+# @version 0.2, 2002-12-10 - created generic function organizeOnBaseOfTag()
+# @version 0.1, 2002-11-01 - started the project
+# @author Patrick Ohnewein (@lugbz.org)
+#
+# ToDo:
+# - add support for ID3v2 Tags - probably should go into mp3info
+#
+
+VERSION="0.8"
+
+# use gettext for internationalization
+. gettext.sh
+
+TEXTDOMAIN=songanizer
+export TEXTDOMAIN
+TEXTDOMAINDIR=$(dirname $0)/../share/locale
+export TEXTDOMAINDIR
+
+program_name=$(basename $0)
+# definition of the used error codes
+EX_OK=0
+EX_USAGE=64
+EX_SOFTWARE=70
+EX_CONFIG=78
+if [ -f sysexits ]; then
+ # If an external sysexits file exists, we source it. This allows the
+ # external overwriting of the error codes.
+ source sysexits
+fi
+
+ifs="$IFS"
+newline='
+'
+
+# variables
+verbose=0
+alltargets="artist artisttree genre genretree year yeartree decade decadetree album"
+alltreetargets="artisttree genretree yeartree decadetree"
+alltags="artist genre year decade album"
+defaulttargets=""
+
+# Prints a version message.
+print_version ()
+{
+ eval_gettext "songanizer, version ${VERSION}"
+ echo
+ # do not translate copyright notice.
+ echo "Copyright (C) 2002-2004 Patrick Ohnewein"
+}
+
+# Prints a help message, explaining all the available options.
+print_help ()
+{
+ gettext "Organizes files in one or more virtual directory structures."
+ echo
+ gettext "Options:"
+ echo
+ gettext " -A|--artist Group songs by artist"
+ echo
+ gettext " --artisttree Group songs by artist and album"
+ echo
+ gettext " -G|--genre Group songs by genre"
+ echo
+ gettext " --genretree Group songs by genre, artist and album"
+ echo
+ gettext " -Y|--year Group songs by year"
+ echo
+ gettext " --yeartree Group songs by year, artist and album"
+ echo
+ gettext " -D|--decade Group songs by decade"
+ echo
+ gettext " --decadetree Group songs by decade, artist and album"
+ echo
+ gettext " -L|--album Group songs by album"
+ echo
+ gettext " --all Group songs by any and all of the above"
+ echo
+ gettext " --alltree Group songs by any and all of above trees"
+ echo
+ gettext " -h|--help Print this help screen"
+ echo
+ gettext " -v|--verbose Activate verbose mode"
+ echo
+ gettext " --version Print version information"
+ echo
+}
+
+# Prints a usage message, explaining how the script has to be called.
+print_usage ()
+{
+ eval_gettext "Usage: $program_name [options] basedir"
+ echo
+ print_help
+}
+
+enableTargets () {
+ for target in $@; do
+ target_is_wanted=""
+ for possibletarget in $alltargets; do
+ if [ "$possibletarget" = "$target" ]; then
+ target_is_wanted="1"
+ fi
+ done
+ if [ -n "$target_is_wanted" ]; then
+ for existingtarget in $wantedtargets; do
+ if [ "$existingtarget" = "$target" ]; then
+ target_is_wanted=""
+ fi
+ done
+ if [ -n "$target_is_wanted" ]; then
+ wantedtargets="$wantedtargets $target"
+ fi
+ else
+ eval_gettext "Error ${program_name}: internal error: unknown target \"$target\""
+ echo
+ exit ${EX_SOFTWARE}
+ fi
+ done
+}
+
+TEMP=$(getopt -o :AGYDLhv --long all,alltree,artist,artisttree,genre,genretree,year,decade,decadetree,album,help,verbose,version -n "$program_name" -- "$@") #"
+
+if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
+
+eval set -- "$TEMP"
+
+wantedtargets="$defaulttargets"
+while true ; do
+ case "$1" in
+ -A|--artist) enableTargets artist; shift;;
+ --artisttree) enableTargets artisttree; shift;;
+ -G|--genre) enableTargets genre; shift;;
+ --genretree) enableTargets genretree; shift;;
+ -Y|--year) enableTargets year; shift;;
+ --yeartree) enableTargets yeartree; shift;;
+ -D|--decade) enableTargets decade; shift;;
+ --decadetree) enableTargets decadetree; shift;;
+ -L|--album) enableTargets album; shift;;
+ --all) enableTargets "$alltargets"; shift;;
+ --alltree) enableTargets "$alltreetargets"; shift;;
+ -h|--help) print_help ; exit ${EX_OK} ; shift;;
+ -v|--verbose) verbose=1; shift;;
+ --version) print_version ; exit ${EX_OK} ; shift;;
+ --) shift ; break ;;
+ *) gettext "Unimplemented option choosen. Use -h to visualize a help screen."; echo; exit ${EX_USAGE} ;;
+ esac
+done
+
+if [ $# -eq 0 ]; then
+ print_usage
+ exit ${EX_USAGE}
+fi
+
+tempdir="`mktemp -tdq \"songanizer.XXXXXXXX\"`"
+if [ ! $? -eq 0 ]; then
+ eval_gettext "Error ${program_name}: Could not create temporary config file. exiting"
+ echo
+ exit ${EX_CONFIG}
+fi
+
+basedir=${1}
+if [ "${basedir:0:1}" != "/" ]; then
+ basedir="`pwd`/${basedir}"
+fi
+
+if [ ! -d ${basedir} ]; then
+ eval_gettext "Error ${program_name}: base directory ${basedir} doesn't exist"
+ echo
+ exit ${EX_CONFIG}
+fi
+
+datadir="${basedir}/_data"
+
+datadirs="${datadir}*"
+for datadir_elem in ${datadirs}; do
+ if [ ! -d ${datadir_elem} ]; then
+ eval_gettext "Error ${program_name}: invalid or no data directory ${datadir_elem}"
+ echo
+ exit ${EX_CONFIG}
+ elif [ $verbose -ne 0 ]; then
+ echo "Detected data directory: ${datadir_elem}"
+ fi
+done
+
+initTempTargetDirs () {
+ # pessimistic aproach
+ EXIT_CODE=${EX_SOFTWARE}
+
+ IFS="$newline"
+ for targetname in $@; do IFS="$ifs";
+
+ temptargetdir="${tempdir}/_${targetname}"
+
+ if [ ! -d "${temptargetdir}" ]; then
+ mkdir "${temptargetdir}"
+ else
+ # remove all sub directories
+ if [ $verbose -ne 0 ]; then echo "rm -rf ${temptargetdir}/*"; fi
+ rm -rf "${temptargetdir}/"*
+ fi
+ if [ ! -d "${temptargetdir}" ]; then
+ eval_gettext "Error ${program_name}: Couldn't create directory ${temptargetdir}"
+ echo
+ exit ${EX_SOFTWARE}
+ fi
+ done
+ IFS="$ifs"
+}
+
+resolveTag () {
+ # pessimistic aproach
+ EXIT_CODE=${EX_SOFTWARE}
+
+ tagname="$1"; shift
+ tagdump="$1"; shift
+ case "$tagname" in
+ genre) tagpattern="^GENRE";;
+ artist) tagpattern="^ARTIST";;
+ album) tagpattern="^ALBUM";;
+ year) tagpattern="^DATE";;
+ decade) tagpattern="^DATE";;
+ esac
+ tag="`echo \"$tagdump\" | grep \"$tagpattern\" | awk -F= '{ print $2 }'`"
+
+ case "$tagname" in
+ decade) if [ -n "$tag" ]; then tag="`perl -e \"print int($tag / 10) . 0 if $tag\"`"; fi
+ esac
+
+ # exchange '/', which is invalid for directory
+ # names with the neutral character '-'
+ tag="${tag//\//-}"
+
+ # if the filetag begins with two dots '..'
+ # than we exchange them with a '-', to avoid
+ # confusing the file operations
+ tag="${tag//#../-}"
+
+ if [ -z "${tag}" ]; then
+ tag="UNKNOWN $tagname"
+ fi
+
+ echo "$tag"
+}
+
+resolveTarget () {
+ # pessimistic aproach
+ EXIT_CODE=${EX_SOFTWARE}
+
+ targetname="$1"; shift
+ tagdump="$1"; shift
+
+ for tag in $alltags; do
+ if [ "$tag" = "$targetname" ]; then
+ target="`resolveTag \"$targetname\" \"$tagdump\"`"
+ fi
+ done
+
+ case "$targetname" in
+ artisttree) target="`resolveTag artist \"$tagdump\"`/`resolveTag album \"$tagdump\"`";;
+ genretree) target="`resolveTag genre \"$tagdump\"`/`resolveTag artist \"$tagdump\"`/`resolveTag album \"$tagdump\"`";;
+ yeartree) target="`resolveTag year \"$tagdump\"`/`resolveTag artist \"$tagdump\"`/`resolveTag album \"$tagdump\"`";;
+ decadetree) target="`resolveTag decade \"$tagdump\"`/`resolveTag artist \"$tagdump\"`/`resolveTag album \"$tagdump\"`";;
+ esac
+
+ echo "$target"
+}
+
+symlinksToFile () {
+
+ # pessimistic aproach
+ EXIT_CODE=${EX_SOFTWARE}
+
+ file="$1"; shift
+ filetype="$1"; shift
+ filename="`basename "$file"`"
+
+ case "$filetype" in
+ ogg)
+ tagdump="`vorbiscomment -l "${file}"`"
+ ;;
+ mp3)
+ tagdump="`mp3info -p 'GENRE=%g\nARTIST=%a\nALBUM=%n\nDATE=%y\n' \"${file}\" 2>/dev/null`"
+ ;;
+ *)
+ echo "Error ${program_name}: unsupported filetype"
+ echo
+ exit ${EX_SOFTWARE}
+ ;;
+ esac
+
+ IFS="$newline"
+ for targetname in $@; do IFS="$ifs";
+ target="`resolveTarget "$targetname" "$tagdump"`"
+
+ targettempdir="${tempdir}/_${targetname}"
+
+ destdir="${targettempdir}/${target}"
+ if [ ! -d "${destdir}" ]; then
+ if [ $verbose -ne 0 ]; then eval_gettext "Creating ${destdir} ..."; echo; fi
+ mkdir -p "${destdir}"
+ fi
+ if [ ! -d "${destdir}" ]; then
+ eval_gettext "Warning ${program_name}: Couldn't create directory ${destdir}"
+ echo
+ else
+ destfile="${destdir}/${filename}"
+ if [ -e "${destfile}" ]; then
+ eval_gettext "Warning ${program_name}: Link already exists. File '${filename}' is probably contained in more than one data directory!"
+ echo
+ else
+ if [ $verbose -ne 0 ]; then eval_gettext "Linking ${file} to ${destfile}"; echo; fi
+ ln -s "${file}" "${destfile}"
+ fi
+ fi
+ done
+ IFS="$ifs"
+}
+
+moveSymlinkDirs () {
+ # pessimistic aproach
+ EXIT_CODE=${EX_SOFTWARE}
+
+ IFS="$newline"
+ for targetname in $@; do IFS="$ifs";
+ targettempdir="${tempdir}/_${targetname}"
+ eval_gettext "Moving ${targettempdir} to ${basedir} ..."
+ echo
+ mv ${targettempdir} ${basedir} 2>/dev/null
+ if [ ! -d "${basedir}/_${targetname}" ]; then
+ eval_gettext "Error $program_name: Couldn't move ${targettempdir} to ${basedir}!"
+ echo
+ exit ${EX_SOFTWARE}
+ fi
+ done
+ IFS="$ifs"
+}
+
+initTempTargetDirs $wantedtargets
+find ${datadirs} \( -iname "*.mp3" -or -iname "*.mpeg" \) -and -type f -print | (while read file; do symlinksToFile "$file" mp3 $wantedtargets; done)
+find ${datadirs} -iname "*.ogg" -and -type f -print | (while read file; do symlinksToFile "$file" ogg $wantedtargets; done)
+moveSymlinkDirs $wantedtargets