#!/bin/bash
#
# Edit Ogg and FLAC comment fields with the default text editor ($EDITOR)
#
# If multiple arguments are given, only the common fields are edited.
# Any fields added while editing override what's in the files.
#
# Usage: vorbiscommentedit [file [file [file...] ] ]
#
# To do: Handle fields with newlines in the value.
#
# Author: Bert Bos <bert@w3.org>
# Created: 17 Nov 2007
# Version: 1.0 (25 Nov 2007)


# Differences between Linux and BSD:
#
if stat -c "%Y" . >/dev/null 2>&1; then
  function mystat { stat -L -c '%Y %s %D %F %i' "$@"; }	# Linux
else
  function mystat { stat -L -f '%m %z %d %p %i' "$@"; }	# BSD
fi

# get-tags -- extract tags from Ogg or FLAC
function get-tags
{
  # Using "file" is safer than the file extension, but much slower...
  # case `file "$1"` in
  #   "$1: "Ogg*Vorbis*) vorbiscomment -R -l "$1";;
  #   "$1: "FLAC*) metaflac --no-utf8-convert --export-tags-to=- "$1";;
  #   *) echo "Error: file is neither Ogg Vorbis nor FLAC: $1" >&2;;
  # esac
  case "$1" in
    *.ogg|*.ogx|*.ogv|*.oga|*.spx) vorbiscomment -R -l "$1";;
    *.flac) metaflac --no-utf8-convert --export-tags-to=- "$1";;
    *) echo "Error: file is neither Ogg Vorbis nor FLAC: $1" >&2;;
  esac
}

# set-tags -- replace tags in Ogg or FLAC with those from stdin
function set-tags
{
  # Using "file" is safer than the file extension, but much slower...
  # case `file "$1"` in
  #   "$1: "Ogg*Vorbis*) vorbiscomment -R -w -c - "$1";;
  #   "$1: "FLAC*) metaflac --remove-all-tags --no-utf8-convert \
  #    --import-tags-from=- "$1";;
  #   *) echo "Error: file is neither Ogg Vorbis nor FLAC: $1" >&2;;
  # esac
  case "$1" in
    *.ogg|*.ogx|*.ogv|*.oga|*.spx) vorbiscomment -R -w -c - "$1";;
    *.flac) metaflac --remove-all-tags --no-utf8-convert \
     --import-tags-from=- "$1";;
    *) echo "Error: file is neither Ogg Vorbis nor FLAC: $1" >&2;;
  esac
}

# one_line_per_key -- put multivalued fields on one line, separated by tab
function one_line_per_key
{
  awk -F = '
    { key = toupper($1)
      if (!curline) {curkey = key; curline = $0}
      else if (key == curkey) {curline = curline "	" $0}
      else {print curline; curkey = key; curline = $0} }
    END {if (curline) print curline}'
}


[[ -z "$1" ]] && exit

trap 'rm -f $COMMON $OTHERTAGS $TMP1 $TMP2 $TMP3' 0
COMMON=`mktemp` || exit 1
OTHERTAGS=`mktemp` || exit 1
TMP1=`mktemp` || exit 1
TMP2=`mktemp` || exit 1
TMP3=`mktemp` || exit 1

# Get all common fields of the given files into $COMMON
# Get the tags of all non-common fields into $OTHERTAGS
#
first="$1"
shift
echo -e ".\c"
get-tags "$first" | sort -fu | one_line_per_key >$COMMON

for f; do
  echo -e ".\c"
  get-tags "$f" | sort -fu | one_line_per_key >$TMP1
  comm -12 $COMMON $TMP1 >$TMP2
  cut -d= -f1 <(comm -23 $COMMON $TMP1) <(comm -13 $COMMON $TMP1) \
    | sort -fu - $OTHERTAGS >$TMP3
  mv $TMP2 $COMMON
  mv $TMP3 $OTHERTAGS
done
echo -e " \c"

# Add the fields that weren't common, prefixed with a "~", so the user
# can see what other fields exist. Then let the user edit. Exit if
# user made no edits. Otherwise, remove "~" prefixed fields and
# empty lines.
#
cat <(tr '\t' '\n' <$COMMON) <(sed -e 's/^/~ /' $OTHERTAGS) >$TMP1 \
  && mv $TMP1 $COMMON
s=`mystat $COMMON`
${EDITOR:-editor} $COMMON
[[ "$s" = `mystat $COMMON` ]] && echo && exit
egrep -v '^$|^~' $COMMON >$TMP1
mv $TMP1 $COMMON

# Check if result is reasonably well-formed
#
LC_ALL=C egrep -q -v '^[ -}]+=' $COMMON \
  && echo "Error: malformed tags" >&2 && exit 2

# If the user edited fields that existed, but were previously not
# common to the ogg files, remove their tags from OTHERTAGS (the list of
# tags that were not common), because the user wants them to become
# common.
#
for tag in $(cut -d= -f1 $COMMON | sort -fu); do
  egrep -v -i "^$tag\$" $OTHERTAGS >$TMP1
  mv $TMP1 $OTHERTAGS
done

# Put modified fields back. Copy all fields that were not in the set
# that was edited (and should therefore not be touched), concatenate
# the result with the edited set, and write the result into the Ogg
# file.
#
echo -e ".\c"
get-tags "$first" >$TMP1
for tag in $(< $OTHERTAGS); do egrep -i "^$tag=" $TMP1; done >$TMP2
cat $COMMON $TMP2 | set-tags "$first"

for f; do
  echo -e ".\c"
  get-tags "$f" >$TMP1
  for tag in $(< $OTHERTAGS); do egrep -i "^$tag=" $TMP1; done >$TMP2
  cat $COMMON $TMP2 | set-tags "$f"
done
echo
