conwav

#!/usr/bin/env tclsh
#
#
# conwav 
#
# A simple example of a WAV to MP3 or OGG converter
#
#
# Copyright 2019  Michael Siegel
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
# USE OR PERFORMANCE OF THIS SOFTWARE.


######## INCLUDES ########

# Include helper functions for parsing command-line arguments
source ../getargs


######## CONSTANTS ######## 

set cmd conwav
set help {conwav

A simple example of a WAV to MP3 or OGG converter

Usage:
  conwav [-f <format>] [-r <rate>] infile [outfile]
  conwav -h|--help
  conwav -V|--version

Options:
  -f <format>    Use <format> as the file format. Valid format specifications
                 are: mp3, ogg 
  -r <rate>      Use <rate> as the bit rate for the MP3 or OGG file. Supported
                 bit rates are: 96, 128, 192, 256
  -h, --help     Show this help
  -V, --version  Show version information}
set help_hint "Run '$cmd -h' for help."
set version "$cmd 1.0"

set formats {mp3 ogg};  # Valid file formats
set rates {96 128 192 256};  # Supported bit rates


######## FUNCTIONS ########

proc _perr {msg {hint_help 0}} {
    # Print error message

    global cmd
    global help_hint

    puts stderr "$cmd: $msg"
    if {$hint_help != 0} {
        puts stderr $help_hint
    }
}

proc _convert {format rate infile outfile} {
    # Convert WAV file to MP3 or OGG

    switch $format {
        mp3 {
            if {"[auto_execok lame]" == ""} {
                return -code error "lame not found"
            }
            exec lame -b $rate $infile $outfile 2>@1
            # LAME doesn't support `--' as the end-of-options indicator.
        }
        ogg {
            if {"[auto_execok oggenc]" == ""} {
                return -code error "oggenc not found" 
            }
            exec oggenc -b $rate -o $outfile -- $infile 2>@1
        }
    }
}


######## MAIN ########

#### Parse command-line arguments ####

# Define an action to perfom when the program is called without arguments
if {! [llength $argv]} {
    puts stderr $help
    exit 1
}

set operands {};  # Initialize list of operands
set operandc_max 2;  # Set maximum number of operands allowed
# If the number of operands were unlimited, operandc_max would have to be set
# to 0 or any value below 0 and calling get_operands would not need to be inside
# a `catch' command.
# Initialize simple switches:
set verbose 0;  # Verbosity switch
# Verbosity could also be implemented in a way that allows for different levels.
set del_src 0;  # Source file deletion switch

# As long as there are arguments, parse them.
while {[llength $argv]} {
    set arg [lindex $argv 0]
    switch -glob $arg {
        -d {
            ## Switch on source file deletion
            shift
            set del_src 1
        }
        -f {
            ## Set file format
            shift
            # Get option-argument
            if {[catch {get_optarg $arg format} err_msg]} {
                _perr $err_msg 1  
                exit 1
            }

            # No need to check for too many arguments here, since get_optarg
            # wouldn't have taken them in anyway. Those will be dealt with in
            # the `--' case and the default case of the main parsing routine.
            # Ergo: Superfluous arguments fall through.

            # The specified format is not checked for validity here because:
            # Parsing is syntax analysis only.
        }
        -r {
            ## Set bit rate
            shift
            # Get option-argument
            if {[catch {get_optarg $arg rate} err_msg]} {
                _perr $err_msg 1
                exit 1
            }
        }
        -v {
            ## Switch on verbose output
            shift
            set verbose 1
            # Note: The program doesn't actually offer verbosity. This is just
            # to show how the option could be implemented and for the purpose
            # of demonstrating single-letter option combining.
        }
        -h -
        --help {
            puts $help
            exit
        }
        -V -
        --version {
            puts $version
            exit
        }
        -- {
            ## Parse all arguments after `--' as operands
            shift
            if {[catch {get_operands $operandc_max operands} err_msg]} {
                _perr $err_msg 1
                exit 1
            }
        }
        -* {
            ## Check for invalid option or combination of single-letter options
            ## and act accordingly.
            shift
            if {[catch {probe_opt $arg} err_msg]} {
                _perr $err_msg 1
                exit 1
            }
        }
        default {
            ## Parse all remeining arguments as operands
            if {[catch {get_operands $operandc_max operands} err_msg]} {
                _perr $err_msg 1
                 exit 1
            }
        }
    }
}

#### Check input validity ####

## Options and option-arguments ##

if {[info exists format]} {
    if {[lsearch $formats $format] == -1} {
        _perr "unknown format -- '$format'" 1
        exit 1
    }
} else {
    # Use default format
    set format mp3
}

if {[info exists rate]} {
    if {[lsearch $rates $rate] == -1} {
        _perr "invalid rate value -- '$rate'" 1
        exit 1
    }
} else {
    # Use default rate
    set rate 128
}

## Operands ##

set infile [lindex $operands 0]

switch [llength $operands] {
    1 {
        set outfile $infile.$format
    }
    2 {
        set outfile [lindex $operands 1]
    }
}

#### Action ####

if {! [file exists $infile]} {
    _perr "file not found -- '$infile'"
} else {
    if {[catch {_convert $format $rate $infile $outfile} err_msg] == 1} {
        _perr $err_msg 1
        exit 1
    }

    # Conditionally remove source file
    if {$del_src == 1} {
        file delete -- $infile
    }
}