func init() {
	log.SetFlags(0)
	log.SetPrefix(":: ")
	if XDG_CONFIG_HOME == "" {
		XDG_CONFIG_HOME = filepath.Join(os.Getenv("HOME"), ".config")
	}
	if XDG_DATA_DIRS == "" {
		XDG_DATA_DIRS = "/usr/local/share/:/usr/share"
	}
	cache.actions = make(map[string]string)
	config = os.Getenv("DEMLO_CONFIG")
	if config == "" {
		config = filepath.Join(XDG_CONFIG_HOME, application, "config.lua")
		if _, err := os.Stat(config); err != nil {
			config = findInPath(XDG_DATA_DIRS, filepath.Join(application, "config.lua"))
		}
	}
}
Note to packagers: those following lines can be patched to fit the local
filesystem.
package main
import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strings"
	"github.com/ambrevar/demlo/cuesheet"
	"github.com/mgutz/ansi"
)
const (
	application = "demlo"
	copyright   = "Copyright (C) 2013-2018 Pierre Neidhardt"
	URL         = "http://ambrevar.bitbucket.io/demlo"
)
var version = "<tip>"
const usage = `Batch-transcode files with user-written Lua scripts for dynamic tagging
and encoding.
Folders are processed recursively. Only files with known extensions are processed.
New extensions can be specified from commandline options.
Commandline options come before file arguments.
All flags that do not require an argument are booleans. Without argument, they
take the true value. To negate them, use the form '-flag=false'.
When specifying a script or action file, if the name contains a path separator,
then the path is looked up directly. Else, the name with and without extension
is searched in the user folder and then in the system folder.
Unless '-p' is used, no action is taken, only the preview is shown.
Tag field names are printed as they are stored in the input and output files.
See '-help' for more details.
`
const (
	coverChecksumBlock = 8 * 4096
	cuesheetMaxsize = 10 * 1024 * 1024
	indexMaxsize    = 10 * 1024 * 1024
	codeMaxsize     = 10 * 1024 * 1024
	existWriteOver   = "overwrite"
	existWriteSkip   = "skip"
	existWriteSuffix = "suffix"
	actionExist = "exist"
)
var (
	XDG_CONFIG_HOME = os.Getenv("XDG_CONFIG_HOME")
	XDG_DATA_DIRS   = os.Getenv("XDG_DATA_DIRS")
	config string
	warning = log.New(os.Stderr, ":: Warning: ", 0)
	previewOptions = struct {
		printIndex bool
		printDiff  bool
	}{false, true}
	cache = struct {
		index   map[string][]outputInfo
		scripts []scriptBuffer
		actions map[string]string
	}{}
	options Options
)
type Options struct {
	Color       bool
	Cores       int
	Debug       bool
	Exist       string
	Extensions  stringSetFlag
	Getcover    bool
	Gettags     bool
	Index       string
	IndexOutput string
	PrintIndex  bool
	Postscript  string
	Prescript   string
	Process     bool
	Scripts     []string
}
type dstCoverKey struct {
	path     string
	checksum string
}
type scriptBuffer struct {
	name string
	buf  string
}
type scriptBufferSlice []scriptBuffer
func (s scriptBufferSlice) Len() int { return len(s) }
func (s scriptBufferSlice) Less(i, j int) bool {
	return s[i].name < s[j].name
}
func (s scriptBufferSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
type scriptSelection map[string]bool
func (s scriptSelection) Select(name string) (paths []string, err error) {
	if strings.ContainsRune(name, os.PathSeparator) {
		s[name] = true
		paths = append(paths, name)
	} else {
		re, err := regexp.Compile(name)
		if err != nil {
			if strings.Contains(filepath.Base(name), name) {
				s[name] = true
			} else {
				err = fmt.Errorf("Could not find any file matching '%v'", name)
			}
		} else {
			err = fmt.Errorf("Could not find any file matching '%v'", name)
			for file := range s {
				if re.MatchString(StripExt(filepath.Base(file))) {
					paths = append(paths, file)
					err = nil
					s[file] = true
				}
			}
		}
	}
	return paths, err
}
func (s scriptSelection) String() string {
	names := []string{}
	for script, selected := range s {
		if selected {
			names = append(names, filepath.Base(script))
		}
	}
	sort.Strings(names)
	return strings.Join(names, " ")
}
func (s scriptSelection) Set(name string) error {
	_, err := s.Select(name)
	return err
}
type scriptRemoveFlag scriptSelection
func (s scriptRemoveFlag) String() string {
	return ""
}
func (s scriptRemoveFlag) Set(arg string) error {
	re, err := regexp.Compile(arg)
	if err != nil {
		return err
	}
	for file := range s {
		if re.MatchString(StripExt(filepath.Base(file))) {
			s[file] = false
		}
	}
	return nil
}
type stringSetFlag map[string]bool  
func (s *stringSetFlag) String() string {
	keylist := []string{}
	for k := range *s {
		keylist = append(keylist, k)
	}
	sort.Strings(keylist)
	return ": " + strings.Join(keylist, " ")
}
func (s *stringSetFlag) Set(arg string) error {
	(*s)[arg] = true
	return nil
}
type inputCover struct {
	format string
	width  int
	height int
	checksum string
}
type outputCover struct {
	Path       string   `lua:"path"`
	Format     string   `lua:"format"`
	Parameters []string `lua:"parameters"`
}
type inputInfo struct {
	path    string  
	bitrate int     
	tags    map[string]string
	modTime struct {
		sec  int64
		nsec int
	} `lua:"time"`
	embeddedCovers []inputCover          `lua:"embeddedcovers"`
	externalCovers map[string]inputCover `lua:"externalcovers"`
	onlineCover    inputCover            `lua:"onlinecover"`
	audioIndex int
	Format  map[string]interface{}   `lua:"format"`
	Streams []map[string]interface{} `lua:"streams"`
	filetags map[string]string
	cuesheet cuesheet.Cuesheet
	cuesheetFile string `lua:"cuesheetfile"`
	trackCount   int    `lua:"trackcount"`
}
type outputInfo struct {
	Path           string                 `lua:"path"`
	Format         string                 `lua:"format"`
	Parameters     []string               `lua:"parameters"`
	Tags           map[string]string      `lua:"tags"`
	EmbeddedCovers []outputCover          `lua:"embeddedcovers"`
	ExternalCovers map[string]outputCover `lua:"externalcovers"`
	OnlineCover    outputCover            `lua:"onlinecover"`
	Write          string                 `lua:"write"`
	Removesource   bool                   `lua:"removesource"`
}
type outputStatus int
const (
	statusOK    outputStatus = iota  
	statusFail                       
	statusExist                      
)
type FileRecord struct {
	input  inputInfo
	exist  inputInfo
	output []outputInfo
	status []outputStatus
	Format struct {
		Bitrate    string `json:"bit_rate"`
		Duration   string
		FormatName string `json:"format_name"`
		NbStreams  int    `json:"nb_streams"`
		Tags       map[string]string
	}
	Streams []struct {
		Bitrate   string `json:"bit_rate"`
		CodecName string `json:"codec_name"`
		CodecType string `json:"codec_type"`
		Duration  string
		Height    int
		Tags      map[string]string
		Width     int
	}
	embeddedCoverCache [][]byte
	onlineCoverCache   []byte
	debug   *log.Logger
	info    *log.Logger
	plain   *log.Logger
	section *log.Logger
	warning *log.Logger
	error   *log.Logger
	logBuf bytes.Buffer
}
func (f *FileRecord) String() string {
	return f.logBuf.String()
}
func newFileRecord(path string) *FileRecord {
	fr := FileRecord{}
	fr.input.path = path
	fr.debug = log.New(ioutil.Discard, "@@ ", 0)
	fr.info = log.New(&fr.logBuf, ":: ", 0)
	fr.plain = log.New(&fr.logBuf, "", 0)
	fr.section = log.New(&fr.logBuf, "==> ", 0)
	fr.warning = log.New(&fr.logBuf, ":: Warning: ", 0)
	fr.error = log.New(&fr.logBuf, ":: Error: ", 0)
	if options.Debug {
		fr.debug.SetOutput(&fr.logBuf)
	}
	if options.Color {
		fr.debug.SetPrefix(ansi.Color(fr.debug.Prefix(), "cyan+b"))
		fr.info.SetPrefix(ansi.Color(fr.info.Prefix(), "magenta+b"))
		fr.section.SetPrefix(ansi.Color(fr.section.Prefix(), "green+b"))
		fr.warning.SetPrefix(ansi.Color(fr.warning.Prefix(), "yellow+b"))
		fr.error.SetPrefix(ansi.Color(fr.error.Prefix(), "red+b"))
	}
	return &fr
}
func findInPath(pathlist, subpath string) string {
	for _, dir := range filepath.SplitList(pathlist) {
		if dir == "" {
			dir = "."
		}
		file := filepath.Join(dir, subpath)
		_, err := os.Stat(file)
		if err == nil {
			return file
		}
	}
	return ""
}
func listCode(name string) (sel scriptSelection) {
	list := func(name, folder string, fileList map[string]string) {
		f, err := os.Open(folder)
		if err != nil {
			if !os.IsNotExist(err) {
				warning.Printf("%v folder %#v: %s", name, folder, err)
			} else {
				log.Printf("%v folder: missing", name)
			}
			return
		}
		defer f.Close()
		log.Printf("%v folder: %v", name, folder)
		dn, err := f.Readdirnames(0)
		if err != nil {
			warning.Print(err)
			return
		}
		luaFiles := []string{}
		for _, v := range dn {
			if strings.ToLower(Ext(v)) == "lua" {
				luaFiles = append(luaFiles, v)
			}
		}
		sort.StringSlice(luaFiles).Sort()
		log.Printf("%v: %q", name, luaFiles)
		for _, v := range luaFiles {
			fileList[StripExt(v)] = filepath.Join(folder, v)
			fileList[v] = filepath.Join(folder, v)
		}
	}
	systemScriptRoot := findInPath(XDG_DATA_DIRS, filepath.Join(application, name))
	userScriptRoot := findInPath(XDG_CONFIG_HOME, filepath.Join(application, name))
	scriptMap := make(map[string]string)
	list("System "+name, systemScriptRoot, scriptMap)
	list("User "+name, userScriptRoot, scriptMap)
	scriptNames := map[string]bool{}
	for _, v := range scriptMap {
		scriptNames[v] = false
	}
	return scriptNames
}
func cacheAction(name, path string) {
	if path == "" {
		return
	}
	st, err := os.Stat(path)
	if err != nil {
		warning.Print(err)
		return
	}
	if sz := st.Size(); sz > codeMaxsize {
		warning.Printf("code size %v > %v bytes, skipping: %v", sz, codeMaxsize, path)
		return
	}
	buf, err := ioutil.ReadFile(path)
	if err != nil {
		warning.Print("code is not readable: ", err)
		return
	}
	cache.actions[name] = string(buf)
	log.Printf("Load action %#v: %v", name, path)
}
func cacheScripts(scriptFiles map[string]bool) {
	visited := map[string]bool{}
	pathMap := map[string]string{}
	for path, selected := range scriptFiles {
		if !selected || visited[path] {
			continue
		}
		visited[path] = true
		st, err := os.Stat(path)
		if err != nil {
			warning.Print("code is not readable: ", err)
			continue
		}
		if sz := st.Size(); sz > codeMaxsize {
			warning.Printf("code size %v > %v bytes, skipping: %v", sz, codeMaxsize, path)
			continue
		}
		buf, err := ioutil.ReadFile(path)
		if err != nil {
			warning.Print("code is not readable: ", err)
			continue
		}
		cache.scripts = append(cache.scripts, scriptBuffer{name: StripExt(filepath.Base(path)), buf: string(buf)})
		pathMap[StripExt(filepath.Base(path))] = path
	}
	sort.Sort(scriptBufferSlice(cache.scripts))
	for _, s := range cache.scripts {
		log.Printf("Load script %v: %v", s.name, pathMap[s.name])
	}
	if options.Prescript != "" {
		cache.scripts = append([]scriptBuffer{{name: "/prescript/", buf: options.Prescript}}, cache.scripts...)
	}
	if options.Postscript != "" {
		cache.scripts = append(cache.scripts, scriptBuffer{name: "/postscript/", buf: options.Postscript})
	}
}
func cacheIndex() {
	if options.Index == "" {
		return
	}
	st, err := os.Stat(options.Index)
	if err != nil {
		warning.Printf("index not found: [%v]", options.Index)
	} else if st.Size() > indexMaxsize {
		warning.Printf("index size > %v bytes, skipping: %v", indexMaxsize, options.Index)
	} else if buf, err := ioutil.ReadFile(options.Index); err != nil {
		warning.Print("index is not readable:", err)
	} else {
		buf = append(append([]byte{'{'}, buf...), []byte(`"": null}`)...)
		err = json.Unmarshal(buf, &cache.index)
		if err != nil {
			warning.Printf("invalid index %v: %v", options.Index, err)
		}
	}
}