/autom4te.cache/
/config/
/configure
+/gremlin/Makefile.in
###--------------------------------------------------------------------------
### Subdirectories.
+SUBDIRS += gremlin
+
###--------------------------------------------------------------------------
### Release tweaking.
dist-hook::
echo $(VERSION) >$(distdir)/RELEASE
-## Bodge for now.
-EXTRA_DIST += gremlin/gremlin
-
## Additional build tools.
EXTRA_DIST += config/auto-version
EXTRA_DIST += config/confsubst
EXTRA_DIST += debian/compat
EXTRA_DIST += debian/source/format
+EXTRA_DIST += debian/gremlin.install
+
###----- That's all, folks --------------------------------------------------
###--------------------------------------------------------------------------
### Debian.
-EXTRA_DIST = gremlin/gremlin config/auto-version config/confsubst \
- debian/rules debian/control debian/changelog debian/copyright \
- debian/compat debian/source/format
+EXTRA_DIST = config/auto-version config/confsubst debian/rules \
+ debian/control debian/changelog debian/copyright debian/compat \
+ debian/source/format debian/gremlin.install
CLEANFILES =
DISTCLEANFILES =
MAINTAINERCLEANFILES =
V_SUBST_ = $(V_SUBST_$(AM_DEFAULT_VERBOSITY))
V_SUBST_0 = @echo " SUBST $@";
SUBST = $(V_SUBST)$(confsubst)
-SUBDIRS =
+
+###--------------------------------------------------------------------------
+### Subdirectories.
+SUBDIRS = gremlin
all: all-recursive
.SUFFIXES:
###----- That's all, folks --------------------------------------------------
-###--------------------------------------------------------------------------
-### Subdirectories.
-
###--------------------------------------------------------------------------
### Release tweaking.
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for autoys 0.1.0-pre0.
+# Generated by GNU Autoconf 2.69 for autoys 0.1.0.
#
# Report bugs to <mdw@distorted.org.uk>.
#
# Identity of this package.
PACKAGE_NAME='autoys'
PACKAGE_TARNAME='autoys'
-PACKAGE_VERSION='0.1.0-pre0'
-PACKAGE_STRING='autoys 0.1.0-pre0'
+PACKAGE_VERSION='0.1.0'
+PACKAGE_STRING='autoys 0.1.0'
PACKAGE_BUGREPORT='mdw@distorted.org.uk'
PACKAGE_URL=''
-ac_unique_file="gremlin/gremlin"
+ac_unique_file="gremlin/gremlin.in"
ac_subst_vars='am__EXEEXT_FALSE
am__EXEEXT_TRUE
LTLIBOBJS
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures autoys 0.1.0-pre0 to adapt to many kinds of systems.
+\`configure' configures autoys 0.1.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of autoys 0.1.0-pre0:";;
+ short | recursive ) echo "Configuration of autoys 0.1.0:";;
esac
cat <<\_ACEOF
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-autoys configure 0.1.0-pre0
+autoys configure 0.1.0
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by autoys $as_me 0.1.0-pre0, which was
+It was created by autoys $as_me 0.1.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
# Define the identity of the package.
PACKAGE='autoys'
- VERSION='0.1.0-pre0'
+ VERSION='0.1.0'
cat >>confdefs.h <<_ACEOF
-ac_config_files="$ac_config_files Makefile"
+ac_config_files="$ac_config_files Makefile gremlin/Makefile"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by autoys $as_me 0.1.0-pre0, which was
+This file was extended by autoys $as_me 0.1.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-autoys config.status 0.1.0-pre0
+autoys config.status 0.1.0
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
case $ac_config_target in
"depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;;
"Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+ "gremlin/Makefile") CONFIG_FILES="$CONFIG_FILES gremlin/Makefile" ;;
*) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
esac
mdw_AUTO_VERSION
AC_INIT([autoys], AUTO_VERSION, [mdw@distorted.org.uk])
-AC_CONFIG_SRCDIR([gremlin/gremlin])
+AC_CONFIG_SRCDIR([gremlin/gremlin.in])
AC_CONFIG_AUX_DIR([config])
AM_INIT_AUTOMAKE([foreign])
mdw_SILENT_RULES
dnl--------------------------------------------------------------------------
dnl Output.
-AC_CONFIG_FILES([Makefile])
+AC_CONFIG_FILES(
+ [Makefile]
+ [gremlin/Makefile])
AC_OUTPUT
dnl----- That's all, folks --------------------------------------------------
+autoys (0.1.0) experimental; urgency=low
+
+ * gremlin: A batch audio conversion tool.
+
+ -- Mark Wooding <mdw@distorted.org.uk> Sun, 14 Feb 2016 02:01:20 +0000
+
autoys (0.1.0~pre0) experimental; urgency=low
* Preliminary prerelease.
Package: autoys
Architecture: all
Section: sound
+Depends:
+ gremlin
Description: A convenience package which depends on the other `autoys' packages.
+
+Package: gremlin
+Architecture: all
+Section: sound
+Depends: ${python:Depends},
+ python-pyparsing,
+ python-gst0.10, python-gobject-2,
+ python-eyed3, python-imaging
+Description: Maintain converted trees of audio files.
+ The `gremlin' program converts audio files in an input `master' directory
+ tree, which presumably contains high-quality (ideally lossless) encodings of
+ interesting audio, writing corresponding converted files to a collection of
+ output directory trees. It's non-interactive, idempotent, and restartable;
+ it never modifies its master tree. It's exactly the sort of thing you want
+ to install as a daily cron job.
+ .
+ The gremlin reads a configuration file which describes the conversion policy
+ for each of the output trees. The policy can say things like: copy MP3
+ files up to 160kb/s, or Ogg Vorbis files up to 128kb/s; and convert
+ everything else to 128kb/s Ogg Vorbis.
+ .
+ The gremlin can also convert image files, such as cover art.
+ .
+ Input files can be anything which GStreamer and/or the Python Imaging
+ Library can understand; output files are more constrained, because the
+ gremlin has to be able to understand their relevant properties. The
+ currently supported audio formats are Ogg Vorbis and MP3; image formats are
+ JPEG, PNG, and BMP.
--- /dev/null
+usr/bin/gremlin
+usr/share/man/man1/gremlin.1
--- /dev/null
+### -*-makefile-*-
+###
+### Build script for the conversion gremlin
+###
+### (c) 2016 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the `autoys' audio tools collection.
+###
+### `autoys' 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.
+###
+### `autoys' 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 `autoys'; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+include $(top_srcdir)/vars.am
+
+###--------------------------------------------------------------------------
+### The gremlin.
+
+if HAVE_PYTHON
+
+bin_SCRIPTS += gremlin
+CLEANFILES += gremlin
+EXTRA_DIST += gremlin.in
+
+gremlin: gremlin.in
+ $(SUBST) $(srcdir)/gremlin.in >$@.new $(SUBSTITUTIONS) && \
+ chmod +x $@.new && mv $@.new $@
+
+dist_man_MANS += gremlin.1
+
+endif
+
+###----- That's all, folks --------------------------------------------------
--- /dev/null
+# Makefile.in generated by automake 1.11.6 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+# Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+### -*-makefile-*-
+###
+### Build script for the conversion gremlin
+###
+### (c) 2016 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the `autoys' audio tools collection.
+###
+### `autoys' 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.
+###
+### `autoys' 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 `autoys'; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+### -*-makefile-*-
+###
+### Common definitions
+###
+### (c) 2016 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This file is part of the `autoys' audio tools collection.
+###
+### `autoys' 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.
+###
+### `autoys' 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 `autoys'; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+###--------------------------------------------------------------------------
+### Initial values of common variables.
+
+
+VPATH = @srcdir@
+am__make_dryrun = \
+ { \
+ am__dry=no; \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ echo 'am--echo: ; @echo "AM" OK' | $(MAKE) -f - 2>/dev/null \
+ | grep '^AM OK$$' >/dev/null || am__dry=yes;; \
+ *) \
+ for am__flg in $$MAKEFLAGS; do \
+ case $$am__flg in \
+ *=*|--*) ;; \
+ *n*) am__dry=yes; break;; \
+ esac; \
+ done;; \
+ esac; \
+ test $$am__dry = yes; \
+ }
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+DIST_COMMON = $(dist_man_MANS) $(srcdir)/Makefile.am \
+ $(srcdir)/Makefile.in $(top_srcdir)/vars.am
+bin_PROGRAMS =
+
+###--------------------------------------------------------------------------
+### The gremlin.
+@HAVE_PYTHON_TRUE@am__append_1 = gremlin
+@HAVE_PYTHON_TRUE@am__append_2 = gremlin
+@HAVE_PYTHON_TRUE@am__append_3 = gremlin.in
+@HAVE_PYTHON_TRUE@am__append_4 = gremlin.1
+subdir = gremlin
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(bindir)" \
+ "$(DESTDIR)$(man1dir)"
+PROGRAMS = $(bin_PROGRAMS)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+SCRIPTS = $(bin_SCRIPTS)
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+man1dir = $(mandir)/man1
+NROFF = nroff
+MANS = $(dist_man_MANS)
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BASH = @BASH@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EXEEXT = @EXEEXT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = $(am__append_3)
+CLEANFILES = $(am__append_2)
+DISTCLEANFILES =
+MAINTAINERCLEANFILES =
+bin_SCRIPTS = $(am__append_1)
+dist_man_MANS = $(am__append_4)
+
+###--------------------------------------------------------------------------
+### Standard configuration substitutions.
+confsubst = $(top_srcdir)/config/confsubst
+SUBSTITUTIONS = \
+ prefix=$(prefix) exec_prefix=$(exec_prefix) \
+ libdir=$(libdir) includedir=$(includedir) \
+ bindir=$(bindir) sbindir=$(sbindir) \
+ PACKAGE=$(PACKAGE) VERSION=$(VERSION) \
+ PYTHON=$(PYTHON) BASH=$(BASH)
+
+V_SUBST = $(V_SUBST_$(V))
+V_SUBST_ = $(V_SUBST_$(AM_DEFAULT_VERBOSITY))
+V_SUBST_0 = @echo " SUBST $@";
+SUBST = $(V_SUBST)$(confsubst)
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(top_srcdir)/vars.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign gremlin/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign gremlin/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+$(top_srcdir)/vars.am:
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p; \
+ then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+install-binSCRIPTS: $(bin_SCRIPTS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_SCRIPTS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n' \
+ -e 'h;s|.*|.|' \
+ -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) { files[d] = files[d] " " $$1; \
+ if (++n[d] == $(am__install_max)) { \
+ print "f", d, files[d]; n[d] = 0; files[d] = "" } } \
+ else { print "f", d "/" $$4, $$1 } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binSCRIPTS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_SCRIPTS)'; test -n "$(bindir)" || exit 0; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 's,.*/,,;$(transform)'`; \
+ dir='$(DESTDIR)$(bindir)'; $(am__uninstall_files_from_dir)
+install-man1: $(dist_man_MANS)
+ @$(NORMAL_INSTALL)
+ @list1=''; \
+ list2='$(dist_man_MANS)'; \
+ test -n "$(man1dir)" \
+ && test -n "`echo $$list1$$list2`" \
+ || exit 0; \
+ echo " $(MKDIR_P) '$(DESTDIR)$(man1dir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(man1dir)" || exit 1; \
+ { for i in $$list1; do echo "$$i"; done; \
+ if test -n "$$list2"; then \
+ for i in $$list2; do echo "$$i"; done \
+ | sed -n '/\.1[a-z]*$$/p'; \
+ fi; \
+ } | while read p; do \
+ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; echo "$$p"; \
+ done | \
+ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
+ sed 'N;N;s,\n, ,g' | { \
+ list=; while read file base inst; do \
+ if test "$$base" = "$$inst"; then list="$$list $$file"; else \
+ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man1dir)/$$inst'"; \
+ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man1dir)/$$inst" || exit $$?; \
+ fi; \
+ done; \
+ for i in $$list; do echo "$$i"; done | $(am__base_list) | \
+ while read files; do \
+ test -z "$$files" || { \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man1dir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(man1dir)" || exit $$?; }; \
+ done; }
+
+uninstall-man1:
+ @$(NORMAL_UNINSTALL)
+ @list=''; test -n "$(man1dir)" || exit 0; \
+ files=`{ for i in $$list; do echo "$$i"; done; \
+ l2='$(dist_man_MANS)'; for i in $$l2; do echo "$$i"; done | \
+ sed -n '/\.1[a-z]*$$/p'; \
+ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+ dir='$(DESTDIR)$(man1dir)'; $(am__uninstall_files_from_dir)
+tags: TAGS
+TAGS:
+
+ctags: CTAGS
+CTAGS:
+
+
+distdir: $(DISTFILES)
+ @list='$(MANS)'; if test -n "$$list"; then \
+ list=`for p in $$list; do \
+ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+ if test -f "$$d$$p"; then echo "$$d$$p"; else :; fi; done`; \
+ if test -n "$$list" && \
+ grep 'ab help2man is required to generate this page' $$list >/dev/null; then \
+ echo "error: found man pages containing the \`missing help2man' replacement text:" >&2; \
+ grep -l 'ab help2man is required to generate this page' $$list | sed 's/^/ /' >&2; \
+ echo " to fix them, install help2man, remove and regenerate the man pages;" >&2; \
+ echo " typically \`make maintainer-clean' will remove them" >&2; \
+ exit 1; \
+ else :; fi; \
+ else :; fi
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(SCRIPTS) $(MANS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES)
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-man
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS install-binSCRIPTS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man: install-man1
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS uninstall-binSCRIPTS uninstall-man
+
+uninstall-man: uninstall-man1
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-binPROGRAMS \
+ clean-generic distclean distclean-generic distdir dvi dvi-am \
+ html html-am info info-am install install-am \
+ install-binPROGRAMS install-binSCRIPTS install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-man1 install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic pdf \
+ pdf-am ps ps-am uninstall uninstall-am uninstall-binPROGRAMS \
+ uninstall-binSCRIPTS uninstall-man uninstall-man1
+
+
+###----- That's all, folks --------------------------------------------------
+
+@HAVE_PYTHON_TRUE@gremlin: gremlin.in
+@HAVE_PYTHON_TRUE@ $(SUBST) $(srcdir)/gremlin.in >$@.new $(SUBSTITUTIONS) && \
+@HAVE_PYTHON_TRUE@ chmod +x $@.new && mv $@.new $@
+
+###----- That's all, folks --------------------------------------------------
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
--- /dev/null
+.\" -*-nroff-*-
+.\"
+.\" Manual for the audio conversion gremlin
+.\"
+.\" (c) 2016 Mark Wooding
+.\"
+.
+.\"----- Licensing notice ---------------------------------------------------
+.\"
+.\" This file is part of the `autoys' audio tools collection.
+.\"
+.\" `autoys' 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.
+.\"
+.\" `autoys' 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 `autoys'; if not, write to the Free Software Foundation,
+.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.
+.TH gremlin 1 "13 February 2016" "Mark Wooding" "autoys"
+.
+.\"--------------------------------------------------------------------------
+.SH NAME
+gremlin \- batch audio file converter
+.
+.SH SYNOPSIS
+.B gremlin
+.RB [ \-in ]
+.RB [ \-T
+.IR timeout ]
+.RB [ \-t
+.IR timeout ]
+.I config
+.
+.\"--------------------------------------------------------------------------
+.SH DESCRIPTION
+.
+The
+.B gremlin
+program converts audio files
+in an input `master' directory tree,
+which presumably contains
+high-quality (ideally lossless) encodings
+of interesting audio,
+writing corresponding converted files
+to a collection of output directory trees.
+It's non-interactive, idempotent, and restartable;
+it never modifies its master tree.
+It's exactly the sort of thing you want to
+install as a daily cron job.
+.PP
+The
+.B gremlin
+reads a configuration file
+which describes the conversion policy for each of the output trees.
+The policy can say things like:
+copy MP3 files up to 160kb/s,
+or Ogg Vorbis files up to 128kb/s;
+and convert everything else to 128kb/s Ogg Vorbis.
+.PP
+The
+.B gremlin
+can also convert image files, such as cover art.
+.PP
+Input files can be anything which
+GStreamer and/or the Python Imaging Library can understand;
+output files are more constrained,
+because the
+.B gremlin
+has to be able to understand
+their relevant properties.
+The currently supported audio formats are
+Ogg Vorbis and
+MP3;
+image formats are
+JPEG,
+PNG, and
+BMP.
+.PP
+In a little more detail:
+the
+.B gremlin
+works through its input master tree,
+one directory at a time.
+For each master directory,
+it tries to write a converted version
+to a corresponding output directory
+in each of the output trees.
+For each file in the master directory,
+it determines which files should be made
+in each output directory:
+if those files exist,
+and are not older than the master file,
+then they're left alone on the assumption that they're up-to-date;
+otherwise, the
+.B gremlin
+will make the output files by converting the master file.
+.PP
+Any other files or directories in the output directory
+will be
+.IR deleted .
+The
+.B gremlin
+assumes that its output trees belong entirely to it,
+to maintain according to its configuration,
+and that unexpected files are either
+debris left over from an earlier failure
+or a result of a policy change,
+and in either case the right thing to do is
+to delete the offending files.
+.
+.SS "Command line syntax"
+The following options are recognized.
+.TP
+.B "\-h, \-\-help"
+Write a help message to standard output
+describing the
+.BR gremlin 's
+command-line options,
+and exit with status zero.
+.TP
+.B "\-\-version"
+Write the
+.BR gremlin 's
+version number to standard output
+and exit with status zero.
+.TP
+.B "\-i, \-\-interactive"
+Write progress eyecandy to standard output while running.
+While walking the master tree,
+the
+.B gremlin
+shows which directory it's currently examining.
+While converting audio files,
+it shows a progress meter showing
+a bar chart of the job in progress,
+the percentage of the job which is complete,
+and an estimated time to completion.
+(This last starts out rather inaccurate,
+but seems to be pretty good after a couple of seconds.)
+All this is done automatically if standard output is a terminal;
+this option can be used to turn it on under other circumstances.
+.TP
+.B "\-n, \-\-no-act"
+Don't actually modify the filesystem.
+No files will be created or removed.
+.TP
+.BI "\-t, \-\-timeout=" timeout
+Only run for about
+.I timeout
+seconds.
+Once the timeout has expired,
+.B gremlin
+will try to finish what it's doing
+and then exit with status zero.
+.IP
+(This might seem a surprising choice of exit status.
+The idea is that the
+.B gremlin
+was asked to spend some amount of time converting files,
+and it has done that successfully.)
+.TP
+.BI "\-T, \-\-timeout-nasty=" timeout
+If the timeout set by the
+.B \-t
+option (above) has expired,
+and a further
+.I timeout
+seconds have elapsed
+but the
+.B gremlin
+still hasn't managed to wrap things up,
+then exit immediately with status 3,
+possibly leaving files partially converted,
+or other kinds of incompleteness.
+(A future run of the
+.B gremlin
+will notice this wreckage and clean it up.)
+.
+.\"--------------------------------------------------------------------------
+.SH CONFIGURATION FILE
+.
+.SS "Lexical syntax"
+The
+.BR gremlin 's
+configuration file has a simple token-oriented lexical syntax.
+Whitespace acts to separate tokens but has no other meaning.
+A hash sign
+.RB ` # '
+outside of a quoted string introduces a comment
+which extends to the end of the line;
+newlines otherwise just separate tokens, just like other whitespace.
+There are no `reserved words',
+but some names have special meanings,
+depending on the context.
+.PP
+Integers are written in decimal.
+(There is no provision for entering numbers in hex or octal.)
+.IP
+.I int
+::=
+.I digit
+\&...
+.br
+.I digit
+::=
+.B 0
+|
+.B 1
+|
+.B 2
+|
+.B 3
+|
+.B 4
+|
+.B 5
+|
+.B 6
+|
+.B 7
+|
+.B 8
+|
+.B 9
+.PP
+Strings (mostly used for pathnames and suchlike)
+are enclosed in double quotes
+.RB ` """" ';
+quotes and backslashes to be included in the string
+must be escaped by preceding them with a backslash
+.RB ` \e '.
+.IP
+.I string
+::=
+.B """"
+.IR string-char ...\&
+.B """"
+.br
+.I string-char
+::=
+any character other than
+.B """"
+or
+.B \e
+.br
+\h'4m'|
+.B "\e"""
+|
+.B \e\e
+.
+.SS "Top-level syntax"
+At a high level,
+the configuration consists of a sequence of
+.IR "top-level items" .
+.IP
+.I config
+::=
+.I toplevel-item
+\&...
+.
+.SS "Global settings"
+Miscellaneous configuration for the whole program
+goes in a top-level
+.B vars
+section.
+.IP
+.I toplevel-item
+::=
+.I vars-section
+.br
+.I vars-section
+::=
+.B vars
+.B {
+.IR var-setting
+\&...\&
+.B }
+.PP
+There may be multiple such sections.
+The same variable may be set more than once;
+if that happens,
+only the last such setting has affect.
+.IP
+.I var-setting
+::=
+.B master
+.B =
+.I path
+.br
+.I path
+::=
+.I string
+.PP
+The
+.B master
+variable holds the pathname of the top of the master tree.
+.PP
+There are, at present, no other global settings.
+.
+.SS "Target definitions"
+The other kind of top-level configuration item
+defines a target directory
+to be constructed or updated
+by the
+.BR gremlin .
+.IP
+.I toplevel-item
+::=
+.I target-def
+.br
+.I target-def
+::=
+.B target
+.I path
+.B {
+.I type-clause
+\&...\&
+.B }
+.br
+.I type-clause
+::=
+.B type
+.I type
+.B {
+.I policy
+\&...\&
+.B }
+.PP
+A
+.B target
+definition tells the
+.B gremlin
+to populate a directory tree,
+named rooted at the given
+.IR path .
+The body of the target definition consists of
+a sequence of
+.B type
+clauses
+which explain what to do with different kinds of file.
+The possible
+.I type
+tokens are as follows.
+.TP
+.B audio
+Encoded audio files,
+which can be decoded by the GStreamer library.
+.TP
+.B image
+Image files,
+which can be decoded by the Python Imaging Library.
+.PP
+The body of the type clause defines a
+.I policy
+for converting files of that type.
+.
+.SS "Policy descriptions"
+There are two kinds of
+.I primitive
+policies,
+which are described in full below:
+.BR accept ,
+which copies (or links) a master file
+if its format is appropriate,
+or does nothing;
+and
+.BR convert ,
+which converts a master file into a chosen format,
+and (in principle) should always succeed.
+There are also two ways to build up
+.I compound
+policies from simpler ones.
+.IP
+.I policy
+::=
+.B and
+.B {
+.I policy
+\&...\&
+.B }
+.br
+\h'4m'|
+.B or
+.B {
+.I policy
+\&...\&
+.B }
+.PP
+The
+.B and
+policy applies
+.I all
+of its operand policies,
+potentially producing multiple output files.
+.PP
+The body of a
+.I type-clause
+consists of a sequence of policies
+which are implicitly combined together in this way.
+.PP
+The
+.B or
+policy
+tries its operand policies in turn,
+in the order specified,
+until one of them succeeds;
+no more policies are tried after this.
+.IP
+.I policy
+::=
+.B accept
+.I format-spec
+.br
+\h'4m'|
+.B convert
+.I format-spec
+.IP
+.I format-spec
+::=
+.I format-name
+.br
+\h'4m'|
+.I format-name
+.B {
+.I format-prop
+\&...\&
+.B }
+.PP
+(The possible
+.IR format-name s
+and the corresponding
+.IR format-spec s
+are described in the section below.)
+.PP
+The
+.B convert
+policy converts a file to the specified format.
+More specifically:
+if the file's format already matches the
+.I format-spec
+then it is copied to the target directory.
+(Indeed, if possible,
+the file is hard linked into the target directory.)
+If the file's format doesn't match,
+then the
+.B gremlin
+converts it,
+producing an output file of the requested format.
+.PP
+The
+.B accept
+policy copies or links a file if its format matches the
+.IR format-spec ,
+just as
+.B convert
+does.
+However, if the file doesn't match then
+.B accept
+fails.
+.PP
+The usual use of
+.B accept
+is within an
+.B or
+block.
+For example, suppose that the master tree mostly contains
+losslessly encoded files, such as FLAC,
+and we usually want to produce Ogg Vorbis
+for use on devices with limited storage capacity;
+but some of the master files are only available as MP3,
+and re-encoding MP3 as Ogg Vorbis won't be good for sound quality.
+Therefore, you can say something like
+.IP
+.nf
+.ft B
+or {
+ accept mp3 { bitrate = 160 }
+ convert ogg-vorbis { bitrate = 128 }
+}
+.fi
+.ft P
+.PP
+which means:
+if a master file is an MP3 file with bitrate approximately 160kb/s or less,
+then copy it;
+otherwise, convert the file to Ogg Vorbis, at about 128kb/s.
+.PP
+It's possible that even a simple policy
+acting on the files in a master directory
+will come up with multiple ways
+to produce the same output file.
+The rule used to decide is as follows:
+if the
+.B gremlin
+can make the output file by copying one of the master files
+then it does that;
+otherwise it converts one of the inputs chosen arbitrarily.
+For example,
+suppose that a policy for
+.B audio
+files says
+.IP
+.B convert ogg-vorbis
+.PP
+and the master directory contains
+.B foo.flac
+and
+.BR foo.ogg ;
+then it will copy
+.B foo.ogg
+and ignore
+.BR foo.flac .
+If, instead, the master contains
+.B foo.flac
+and
+.BR foo.mp3 ,
+then one of these will be converted,
+but it's hard to predict which.
+.
+.SS "Audio formats"
+Two audio
+.IR format-type s
+are defined.
+.PP
+All audio formats support a
+.B bitrate
+property.
+.IP
+.I format-prop
+::=
+.B bitrate
+.B =
+.I int
+.PP
+The bitrate is expressed in kilobits per second.
+For an existing file to match a
+.I format-spec
+containing a
+.B bitrate
+property,
+the file's bitrate must be less than
+the specified bitrate times a fudge factor
+(currently sqrt(2)).
+(The
+.B bitrate
+property is notionally the desired
+.I output
+bitrate;
+the
+.B gremlin
+assumes that it's better to make output files a bit larger
+than to re-encode an already lossily compressed master file.)
+.PP
+At present, the audio formats define no other properties.
+.TP
+.B mp3
+The MP3 format that everyone knows and loves.
+For encoding, the
+.B gremlin
+uses Lame,
+and stores metadata in an ID3v2 tag;
+it also tries to store an ID3v1.1 tag,
+but this can fail for a number of reasons
+(e.g., if the genre can't be represented,
+or text contains characters outside of the ISO 8859-1 character set
+used in ID3v1 tags).
+.TP
+.B ogg-vorbis
+Vorbis-encoded audio in an Ogg container,
+as defined by the Xiph.Org Foundation.
+On encoding, the
+.B bitrate
+parameter is actually mapped to a quality setting
+chosen to produce approximately the right bitrate.
+.
+.SS "Image formats"
+Three image
+.IR format-type s
+are defined.
+.PP
+All image formats support a
+.B size
+property.
+.IP
+.I format-prop
+::=
+.B size
+.B =
+.I int
+.PP
+The size provides an upper bound on the width and height of the image.
+A master file will only match if
+both its width and height are
+less than the stated size.
+On output, the image will be scaled to the right size,
+preserving its aspect ratio.
+.TP
+.B jpeg
+The JFIF format, defined by the Joint Photographic Experts Group.
+The following additional properties can be set;
+they affect output only.
+.RS
+.TP
+.B optimize
+Spend longer to select optimal encoder settings.
+.TP
+.B progressive
+Make a progressively-rendering output file.
+This isn't usually a good idea.
+.TP
+.BI "quality = " int
+Set the image quality (at the expense of file size).
+This is a percentage; the default is 75.
+.RE
+.TP
+.B png
+The Portable Network Graphics format,
+originally defined in RFC2083.
+The following additional properties can be set;
+they affect output only.
+.RS
+.TP
+.B optimize
+Spend longer to try to make the output file smaller.
+.RE
+.TP
+.B bmp
+The Windows BMP format.
+There are no additional properties.
+.
+.SS "Example file"
+The following is the author's configuration file.
+I have an archive which mostly consists of FLAC files,
+with a few MP3 files where I've been unable to obtain physical CDs.
+I generate two output trees.
+One mostly contains Ogg Vorbis files,
+but tolerates occasional MP3
+rather than suffer the quality loss of re-encoding.
+It also generates small BMP-format images from cover art,
+because I have an old portable audio player
+which runs the free RockBox firmware,
+whose player is only capable of displaying such images.
+.IP
+.nf
+.ft B
+### -*-conf-*-
+
+vars {
+ master = "/mnt/jb/master"
+}
+
+target "/mnt/jb/gremlin/ogg-vorbis-128" {
+ type audio {
+ or {
+ accept mp3 { bitrate = 160 }
+ convert ogg-vorbis { bitrate = 128 }
+ }
+ }
+ type image {
+ or {
+ accept png
+ convert jpeg { quality = 7 }
+ }
+ convert bmp { size = 75 }
+ }
+}
+
+target "/mnt/jb/gremlin/mp3-160" {
+ type audio {
+ convert mp3 { bitrate = 160 }
+ }
+ type image {
+ or {
+ accept png
+ convert jpeg { quality = 7 }
+ }
+ }
+}
+.fi
+.ft P
+.
+.\"--------------------------------------------------------------------------
+.SH BUGS
+.
+The
+.B gremlin
+makes no effort to process more than one file at a time.
+.PP
+It should probably support more audio formats.
+They're quite easy to add,
+but I don't have a good feel for which formats are good.
+Patches and advice are welcome.
+.PP
+The
+.B and
+and
+.B or
+policy names are possibly confusing.
+They suggest that they work like the standard logical operators;
+while
+.B or
+sort of does, if you squint a bit,
+.B and
+certainly doesn't;
+on the other hand, it does try to do all of the things you ask of it.
+.PP
+.B gremlin
+is a very unhelpful name for the program.
+.
+.\"--------------------------------------------------------------------------
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
+.
+.SH SEE ALSO
+.BR hush (1),
+.BR rsync (1).
+.
+.\"----- That's all, folks --------------------------------------------------
-#! /usr/bin/python
+#! @PYTHON@
###
### Convert a directory tree of audio files
###
###--------------------------------------------------------------------------
### Special initialization.
-VERSION = '1.0.0~pre'
+VERSION = '@VERSION@'
## GLib.
G.threads_init()
else: w += 1
## Done.
- #print ';; %r -> %d' % (s, w)
return w
class StatusLine (object):
## Eyecandy update.
if me.eyecandyp:
- #print
- #print ';; new status %r' % line
## If the old line was longer, we need to clobber its tail, so work out
## what that involves.
## Actually do the output, all in one syscall.
b = charwidth(me._last[i:])
SYS.stdout.write(pre + '\b'*b + line[i:])
- #print ';; => %r' % (pre + '\b'*b + line[i:])
SYS.stdout.flush()
## Update our idea of what's gone on.
## Handy abbreviations for constructed parser elements.
def K(k): return P.Keyword(k).suppress()
def D(d): return P.Literal(d).suppress()
-##R = P.ZeroOrMore
def R(p): return P.ZeroOrMore(p).setParseAction(lambda s, l, t: [t])
O = P.Optional
class OggVorbisFormat (AudioFormat):
"AudioFormat object for Ogg Vorbis."
- ## From http://en.wikipedia.org/wiki/Vorbis
+ ## From https://en.wikipedia.org/wiki/Vorbis
QMAP = [(-1, 45), ( 0, 64), ( 1, 80), ( 2, 96),
( 3, 112), ( 4, 128), ( 5, 160), ( 6, 192),
( 7, 224), ( 8, 256), ( 9, 320), (10, 500)]
EXT = 'ogg'
def encoder_chain(me):
- for q, br in me.QMAP:
- if br >= me.bitrate:
- break
- else:
- raise ValueError, 'no suitable quality setting found'
- return [make_element('vorbisenc',
- quality = q/10.0),
+ encprops = {}
+ if me.bitrate is not None:
+ for q, br in me.QMAP:
+ if br >= me.bitrate:
+ break
+ else:
+ raise ValueError, 'no suitable quality setting found'
+ encprops['quality'] = q/10.0
+ return [make_element('vorbisenc', **encprops),
make_element('oggmux')]
defformat('ogg-vorbis', OggVorbisFormat)
EXT = 'mp3'
def encoder_chain(me):
- return [make_element('lame',
- vbr_mean_bitrate = me.bitrate,
- vbr = 4),
+ encprops = {}
+ if me.bitrate is not None: encprops['vbr_mean_bitrate'] = me.bitrate
+ return [make_element('lame', vbr = 4, **encprops),
make_element('xingmux'),
make_element('id3v2mux')]
optimize
If present, take a second pass to select optimal encoder settings.
- progression
+ progressive
If present, make a progressive file.
quality Integer from 1--100 (worst to best); default is 75.
defformat('bmp', BMPFormat)
+###--------------------------------------------------------------------------
+### Remaining parsing machinery.
+
+Type = K('type') - Name - D('{') - R(Policy) - D('}')
+def build_type(s, l, t):
+ try:
+ cat = CATEGORYMAP[t[0]]
+ except KeyError:
+ raise P.ParseException(s, loc, "Unknown category `%s'" % t[0])
+ pols = t[1]
+ if len(pols) == 1: pol = pols[0]
+ else: pol = AndPolicy(pols)
+ pol.setcategory(cat)
+ return pol
+Type.setParseAction(build_type)
+
+TARGETS = []
+class TargetJob (object):
+ def __init__(me, targetdir, policies):
+ me.targetdir = targetdir
+ me.policies = policies
+ def perform(me):
+ TARGETS.append(me)
+
+Target = K('target') - String - D('{') - R(Type) - D('}')
+def build_target(s, l, t):
+ return TargetJob(t[0], t[1])
+Target.setParseAction(build_target)
+
+VARS = { 'master': None }
+class VarsJob (object):
+ def __init__(me, vars):
+ me.vars = vars
+ def perform(me):
+ for k, v in me.vars:
+ VARS[k] = v
+
+Var = prop('master', String)
+Vars = K('vars') - D('{') - R(Var) - D('}')
+def build_vars(s, l, t):
+ return VarsJob(t[0])
+Vars.setParseAction(build_vars)
+
+TopLevel = Vars | Target
+Config = R(TopLevel)
+Config.ignore(P.pythonStyleComment)
+
###--------------------------------------------------------------------------
### The directory grobbler.
-class Grobbler (object):
+def grobble(master, targets, noact = False):
"""
- The directory grobbler copies a directory tree, converting files.
+ Work through the MASTER directory, writing converted files to TARGETS.
+
+ The TARGETS are a list of `TargetJob' objects, each describing a target
+ directory and a policy to apply to it.
+
+ If NOACT is true, then don't actually do anything permanent to the
+ filesystem.
"""
- def __init__(me, policies, noact = False):
- """
- Create a new Grobbler, working with the given POLICIES.
- """
- me._pmap = {}
- me._noact = noact
- for p in policies:
- me._pmap.setdefault(p.cat, []).append(p)
- me._dirs = []
+ ## Transform the targets into a more convenient data structure.
+ tpolmap = []
+ for t in targets:
+ pmap = {}
+ tpolmap.append(pmap)
+ for p in t.policies: pmap.setdefault(p.cat, []).append(p)
- def _grobble_file(me, master, targetdir, cohorts):
- """
- Convert MASTER, writing the result to TARGETDIR.
+ ## Keep track of the current position in the master tree.
+ dirs = []
- The COHORTS are actually (CAT, ID, COHORT) triples, where a COHORT is a
- list of (FILENAME, ID) pairs.
+ ## And the files which haven't worked.
+ broken = []
- Since this function might convert the MASTER file, the caller doesn't
- know the name of the output files, so we return then as a list.
- """
+ def grobble_file(master, pmap, targetdir, cohorts):
+ ## Convert MASTER, writing the result to TARGETDIR.
+ ##
+ ## The COHORTS are actually (CAT, ID, COHORT) triples, where a COHORT is
+ ## a list of (FILENAME, ID) pairs.
+ ##
+ ## Since this function might convert the MASTER file, the caller doesn't
+ ## know the name of the output files, so we return then as a list.
done = set()
st_m = OS.stat(master)
## Go through the category's policies and see if any match. If we fail
## here, see if there are more categories to try.
- for pol in me._pmap[cat]:
+ for pol in pmap[cat]:
acts = pol.actions(master, targetdir, id, cohort)
if acts: break
else:
## Remove the target. (A hardlink will fail if the target already
## exists.)
- if not me._noact:
+ if not noact:
try:
OS.unlink(a.target)
except OSError, err:
raise
## Do whatever it is we decided to do.
- if me._noact:
+ if noact:
STATUS.commit(filestatus(master, a))
else:
a.perform()
return list(done)
@contextmanager
- def _wrap(me, masterfile):
- """
- Handle exceptions found while trying to convert a particular file or
- directory.
- """
+ def wrap(masterfile):
+ ## Handle exceptions found while trying to convert a particular file or
+ ## directory.
try:
yield masterfile
except (IOError, OSError), exc:
STATUS.clear()
STATUS.commit(filestatus(masterfile, 'failed (%s)' % exc))
- me._broken.append((masterfile, exc))
+ broken.append((masterfile, exc))
- def _grobble_dir(me, master, target):
- """
- Recursively convert files in MASTER, writing them to TARGET.
- """
+ def grobble_dir(master, targets):
+ ## Recursively convert files in MASTER, writing them to the TARGETS.
- ## Make sure the TARGET exists and is a directory. It's a fundamental
- ## assumption of this program that the entire TARGET tree is disposable,
- ## so if something exists but isn't a directory, we should kill it.
- if OS.path.isdir(target):
- pass
- else:
- if OS.path.exists(target):
- STATUS.commit(filestatus(target, 'clear nondirectory'))
- if not me._noact:
- OS.unlink(target)
- STATUS.commit(filestatus(target, 'create directory'))
- if not me._noact:
- OS.mkdir(target)
-
- ## Keep a list of things in the target. As we convert files, we'll check
- ## them off. Anything left over is rubbish and needs to be deleted.
- checklist = {}
- try:
- for i in OS.listdir(target):
- checklist[i] = False
- except OSError, err:
- if err.errno not in (E.ENOENT, E.ENOTDIR):
- raise
-
- ## Keep track of the files in each category.
- catmap = {}
- todo = []
- done = []
-
- ## Work through the master files.
- for f in sorted(OS.listdir(master)):
-
- ## If the killswitch has been pulled then stop. The whole idea is that
- ## we want to cause a clean shutdown if possible, so we don't want to
- ## do it in the middle of encoding because the encoding effort will
- ## have been wasted. This is the only place we need to check. If
- ## we've exited the loop, then clearing old files will probably be
- ## fast, and we'll either end up here when the recursive call returns
- ## or we'll be in the same boat as before, clearing old files, only up
- ## a level. If worst comes to worst, we'll be killed forcibly
- ## somewhere inside `SH.rmtree', and that can continue where it left
- ## off.
- if KILLSWITCH.is_set():
- return
-
- ## Do something with the file.
- with me._wrap(OS.path.join(master, f)) as masterfile:
-
- ## If it's a directory then grobble it recursively. Keep the user
- ## amused by telling him where we are in the tree.
- if OS.path.isdir(masterfile):
- me._dirs.append(f)
- STATUS.set('/'.join(me._dirs))
- try:
- done += me._grobble_dir(masterfile, OS.path.join(target, f))
- finally:
- me._dirs.pop()
- STATUS.set('/'.join(me._dirs))
-
- ## Otherwise it's a file. Work out what kind, and stash it under
- ## the appropriate categories. Later, we'll apply policy to the
- ## files, by category, and work out what to do with them all.
- else:
- gf = GIO.File(masterfile)
- mime = gf.query_info('standard::content-type').get_content_type()
- cats = []
- for cat in me._pmap.iterkeys():
- id = cat.identify(masterfile, mime)
- if id is None: continue
- catmap.setdefault(cat, []).append((masterfile, id))
- cats.append((cat, id))
- if not cats:
- catmap.setdefault(None, []).append((masterfile, id))
- todo.append((masterfile, cats))
-
- ## Work through the categorized files to see what actions to do for
- ## them.
- for masterfile, cats in todo:
- with me._wrap(masterfile):
- done += me._grobble_file(masterfile, target,
- [(cat, id, catmap[cat])
- for cat, id in cats])
-
- ## Check the results off the list so that we don't clear it later.
- for f in done:
- checklist[OS.path.basename(f)] = True
-
- ## Maybe there's stuff in the target which isn't accounted for. Delete
- ## it: either the master has changed, or the policy for this target has
- ## changed. Either way, the old files aren't wanted.
- for f in checklist:
- if not checklist[f]:
- STATUS.commit(filestatus(f, 'clear bogus file'))
- if not me._noact:
- bogus = OS.path.join(target, f)
- try:
- if OS.path.isdir(bogus):
- SH.rmtree(bogus)
- else:
- OS.unlink(bogus)
- except OSError, err:
- if err.errno != E.ENOENT:
- raise
-
- ## Return the target name, so that it can be checked off.
- return [target]
-
- def grobble(me, master, target):
- """
- Convert MASTER, writing a directory tree TARGET.
-
- Returns a list of files which couldn't be converted.
- """
- try:
- me._broken = []
- me._grobble_dir(master, target)
- return me._broken
- finally:
- del me._broken
-
-###--------------------------------------------------------------------------
-### Remaining parsing machinery.
-
-Type = K('type') - Name - D('{') - R(Policy) - D('}')
-def build_type(s, l, t):
- try:
- cat = CATEGORYMAP[t[0]]
- except KeyError:
- raise P.ParseException(s, loc, "Unknown category `%s'" % t[0])
- pols = t[1]
- if len(pols) == 1: pol = pols[0]
- else: pol = AndPolicy(pols)
- pol.setcategory(cat)
- return pol
-Type.setParseAction(build_type)
-
-TARGETS = []
-class TargetJob (object):
- def __init__(me, targetdir, policies):
- me.targetdir = targetdir
- me.policies = policies
- def perform(me):
- TARGETS.append(me)
-
-Target = K('target') - String - D('{') - R(Type) - D('}')
-def build_target(s, l, t):
- return TargetJob(t[0], t[1])
-Target.setParseAction(build_target)
+ ## Keep track of the subdirectories we encounter, because we'll need to
+ ## do all of those in one go at the end.
+ subdirs = set()
-VARS = { 'master': None }
-class VarsJob (object):
- def __init__(me, vars):
- me.vars = vars
- def perform(me):
- for k, v in me.vars:
- VARS[k] = v
+ ## Work through each target directory in turn.
+ for target, pmap in zip(targets, tpolmap):
-Var = prop('master', String)
-Vars = K('vars') - D('{') - R(Var) - D('}')
-def build_vars(s, l, t):
- return VarsJob(t[0])
-Vars.setParseAction(build_vars)
+ ## Make sure the TARGET exists and is a directory. It's a fundamental
+ ## assumption of this program that the entire TARGET tree is
+ ## disposable, so if something exists but isn't a directory, we should
+ ## kill it.
+ if OS.path.isdir(target):
+ pass
+ else:
+ if OS.path.exists(target):
+ STATUS.commit(filestatus(target, 'clear nondirectory'))
+ if not noact:
+ OS.unlink(target)
+ STATUS.commit(filestatus(target, 'create directory'))
+ if not noact:
+ OS.mkdir(target)
+
+ ## Keep a list of things in the target. As we convert files, we'll
+ ## check them off. Anything left over is rubbish and needs to be
+ ## deleted.
+ checklist = {}
+ try:
+ for i in OS.listdir(target):
+ checklist[i] = False
+ except OSError, err:
+ if err.errno not in (E.ENOENT, E.ENOTDIR):
+ raise
+
+ ## Keep track of the files in each category.
+ catmap = {}
+ todo = []
+ done = []
+
+ ## Work through the master files.
+ for f in sorted(OS.listdir(master)):
+
+ ## If the killswitch has been pulled then stop. The whole idea is
+ ## that we want to cause a clean shutdown if possible, so we don't
+ ## want to do it in the middle of encoding because the encoding
+ ## effort will have been wasted. This is the only place we need to
+ ## check. If we've exited the loop, then clearing old files will
+ ## probably be fast, and we'll either end up here when the recursive
+ ## call returns or we'll be in the same boat as before, clearing old
+ ## files, only up a level. If worst comes to worst, we'll be killed
+ ## forcibly somewhere inside `SH.rmtree', and that can continue where
+ ## it left off.
+ if KILLSWITCH.is_set():
+ return
+
+ ## Do something with the file.
+ with wrap(OS.path.join(master, f)) as masterfile:
+
+ ## If it's a directory then prepare to grobble it recursively, but
+ ## don't do that yet.
+ if OS.path.isdir(masterfile):
+ subdirs.add(f)
+ done.append(OS.path.join(target, f))
+
+ ## Otherwise it's a file. Work out what kind, and stash it under
+ ## the appropriate categories. Later, we'll apply policy to the
+ ## files, by category, and work out what to do with them all.
+ else:
+ gf = GIO.File(masterfile)
+ mime = gf.query_info('standard::content-type').get_content_type()
+ cats = []
+ for cat in pmap.iterkeys():
+ id = cat.identify(masterfile, mime)
+ if id is None: continue
+ catmap.setdefault(cat, []).append((masterfile, id))
+ cats.append((cat, id))
+ if not cats:
+ catmap.setdefault(None, []).append((masterfile, id))
+ todo.append((masterfile, cats))
+
+ ## Work through the categorized files to see what actions to do for
+ ## them.
+ for masterfile, cats in todo:
+ with wrap(masterfile):
+ done += grobble_file(masterfile, pmap, target,
+ [(cat, id, catmap[cat]) for cat, id in cats])
+
+ ## Check the results off the list so that we don't clear it later.
+ for f in done:
+ checklist[OS.path.basename(f)] = True
+
+ ## Maybe there's stuff in the target which isn't accounted for. Delete
+ ## it: either the master has changed, or the policy for this target has
+ ## changed. Either way, the old files aren't wanted.
+ for f in checklist:
+ if not checklist[f]:
+ STATUS.commit(filestatus(f, 'clear bogus file'))
+ if not noact:
+ bogus = OS.path.join(target, f)
+ try:
+ if OS.path.isdir(bogus):
+ SH.rmtree(bogus)
+ else:
+ OS.unlink(bogus)
+ except OSError, err:
+ if err.errno != E.ENOENT:
+ raise
+
+ ## If there are subdirectories which want processing then do those.
+ ## Keep the user amused by telling him where we are in the tree.
+ for d in sorted(subdirs):
+ dirs.append(d)
+ STATUS.set('/'.join(dirs))
+ with wrap(OS.path.join(master, d)) as masterdir:
+ try:
+ grobble_dir(masterdir,
+ [OS.path.join(target, d) for target in targets])
+ finally:
+ dirs.pop()
+ STATUS.set('/'.join(dirs))
-TopLevel = Vars | Target
-Config = R(TopLevel)
-Config.ignore(P.pythonStyleComment)
+ ## Right. We're ready to go.
+ grobble_dir(master, [t.targetdir for t in targets])
+ return broken
###--------------------------------------------------------------------------
### Command-line interface.
## Build the option parser object.
op = OP.OptionParser(prog = QUIS, version = VERSION,
- usage = '%prog [-t TIMEOUT] CONFIG',
+ usage = '%prog [-in] [-t TIMEOUT] [-T TIMEOUT] '
+ 'CONFIG',
description = """\
Convert a directory tree of files according to the configuration file
CONFIG.
opts = parse_opts(SYS.argv[1:])
if 'master' not in VARS:
die("no master directory set")
- broken = []
- for t in TARGETS:
- g = Grobbler(t.policies, opts.noact)
- b = g.grobble(VARS['master'], t.targetdir)
- broken += b
+ broken = grobble(VARS['master'], TARGETS, opts.noact)
if broken:
moan('failed to convert some files:')
for file, exc in broken: