====== #!/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 ] [-r ] infile [outfile] conwav -h|--help conwav -V|--version Options: -f Use as the file format. Valid format specifications are: mp3, ogg -r Use 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 # 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 -- $infile $outfile } } } ######## 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 } } -* { 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]} { _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 } } ======