======
#!/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 $inoutfile -- $outinfile 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 a 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
} # The above if-statement needs to evaluate the inversion of the return
# code from `_convert' because executing Unix commands with `exec' will
# lead to those commands returning 0 on success and 1 on failure, as
# usual. Maybe put an extra `catch' command around the `exec' commands
# in `_covert' to keep the special case in its own sphere.
# Conditionally remove source file
if {$del_src == 1} {
file delete -- $infile
}
}
======