You are not logged in.

#1 2020-02-18 07:56:29

drtebi
Member
Registered: 2013-02-09
Posts: 114

Dealing with multiple files with spaces as script arguments

I have written a few little bash scripts that simplify tasks, one of them converts my FLAC files to MP3 files. It's just a simple front-end to ffmpeg.

What I cannot figure out is, how can I pass multiple files to the script? I would like to be able to do...

flac2mp3 *.flac

And have the script take all flac files as arguments.

I understand that the shell will expand the wildcard before it is "send" to my script, but the problem is, that my filenames have spaces, and I just cannot figure out how to get each file separately. I end up with either split words or all files as one argument. Do I have to resort to another language? Python would do, but I feel like there must be a way to do this in bash?

Offline

#2 2020-02-18 09:42:32

fukawi2
Administrator
From: .vic.au
Registered: 2007-09-28
Posts: 5,940
Website

Re: Dealing with multiple files with spaces as script arguments

In the absence of your current script, something like this will work.

$ cat loop-filenames.sh 
#!/bin/bash

for arg in "$@"; do
  echo "Convert file '$arg'"
done

$ ./loop-filenames.sh example.mp3 "foo bar.mp3"
Convert file 'example.mp3'
Convert file 'foo bar.mp3'
$ 

Offline

#3 2020-02-18 22:49:07

drtebi
Member
Registered: 2013-02-09
Posts: 114

Re: Dealing with multiple files with spaces as script arguments

Sorry, of course I should have provided a code example.

I believe the problem in my script is, that I am using a function to convert the files, and am trying to pass the "$@" as a variable to the function, which somehow doesn't work. Here is a simplified example of what I am trying to do:

#!/usr/bin/env bash

# Simplified for demonstration purposes, trying to make `program *.flac` work
#
# Converts FLAC file(s) `$1` to  MP3 files and saves these to directory `$2`
# $1 (str) - file path or pattern to FLAC files
# $2 (str) (optional) - path to output directory, default is current directory
function convertFlacs() {
    cd "${2:-.}"
    for f in "${1}"; do
        #ffmpeg -i "${f}" -b:a 256k "${f/%flac/mp3}"
        echo "${f}"
    done
    cd "${OLDPWD}"
}

# Get options
while getopts o: options; do
    case "${options}" in
        o) output_dir="${OPTARG}";;
    esac
done
shift $(($OPTIND - 1))
files="$@"

# Run functions
convertFlacs "${files}" "${output_dir}"

If I don't use a function, it would work with the example you provided. But since I have a few other functions in the script, I would like to keep things separated by using functions...

Is there a solution to do this?

Offline

#4 2020-02-18 23:01:36

jasonwryan
Anarchist
From: .nz
Registered: 2009-05-09
Posts: 29,026
Website

Re: Dealing with multiple files with spaces as script arguments

for file in *.flac; do
    ffmpeg -i "$file" -ab 320k -map_metadata 0 -id3v2_version 3 "${file%.flac}.mp3"
done

(( $? == 0 )) && rm *.flac

Arch + dwm   •   Mercurial repos  •   Surfraw

Registered Linux User #482438

Offline

#5 2020-02-18 23:17:15

drtebi
Member
Registered: 2013-02-09
Posts: 114

Re: Dealing with multiple files with spaces as script arguments

jasonwryan wrote:
for file in *.flac; do
    ffmpeg -i "$file" -ab 320k -map_metadata 0 -id3v2_version 3 "${file%.flac}.mp3"
done

(( $? == 0 )) && rm *.flac

I do understand that part. But in my script I want to offer a few more options, like a different output directory, sample rate, etc.

Anyway, I believe I have now figured out how to pass files to the script, and use them in a function. This sample snippet works as far as I can see:

#!/usr/bin/env bash

# Simplified for demonstration purposes, trying to make `program *.flac` work

# Default variables
output_dir="${PWD}"

# Converts FLAC file(s) `$1` to  MP3 files and saves these to `$output_dir`
# $1 (str) - file path or pattern to FLAC files
function convertFlacs() {
    cd "${output_dir}"
    files=("$@")
    for f in "${files[@]}"; do
        #ffmpeg -i "${f}" -b:a 256k "${f/%flac/mp3}"
        echo "${f}"
    done
    cd "${OLDPWD}"
}

# Get options
while getopts o: options; do
    case "${options}" in
        o) output_dir="${OPTARG}";;
    esac
done
shift $(($OPTIND - 1))
files=("$@")

# Run functions
convertFlacs "${files[@]}"

Offline

#6 2020-02-19 00:11:49

drtebi
Member
Registered: 2013-02-09
Posts: 114

Re: Dealing with multiple files with spaces as script arguments

OK, I have my script working fine now. I am sure it could be polished some more, any comments are welcome.

Here the full script:

#!/usr/bin/env bash

# Simple script that uses ffmpeg to convert flac files to MP3 files.

# @author: DrTebi@gmail.com
# @since: 2014-11-30
# @version: 0.4
# @license: GPL

program="${0##*/}"
program_helpers=('ffmpeg')
sample_rates=(64 128 160 192 256 320)
sample_rate_default=256
output_dir="${PWD}"

#
# Echos arguments as colored text with no new-line
# $1 (int)    - color code
# $2..n (str) - text to colorize
function echoColor() {
    color="$1"
    shift
    text="$@"
    echo -n "[0;${color}m${text}[1;0m"
}

#
# Shows basic program usage
function showUsage() {
    echoColor 33 "Usage: ${program}  -h | [ -r <sample-rate> ] [-o <output-directory> ] <flac-file(s)>"
    echo
}

#
# Shows program help
function showHelp() {
    cat << EOF
${program} converts FLAC files to MP3 files.

If no <output-directory> is not specified, MP3 files will be saved to the
current directory.

EOF
    showUsage
    cat << EOF

Options are:
  -h                    Display this help message
  -r <sample-rate>      Use specified sample rate <sample-rate> instead of default ${sample_rate_default}.
                        Can be one of 64, 128, 160, 192, 256, or 320
  -o <output-directory> The directory where MP3 files will be saved to

Required arguments:
  <flac-file(s)>        The path to one or more FLAC files, e.g. "Blues/*.flac"

EOF
}

#
# Checks whether programs defined in `$program_helpers` exist
function checkPrograms() {
    local not_found=()
    for x in "${program_helpers[@]}"; do
        type "${x}" > '/dev/null' 2>&1 || not_found+=("${x}")
    done
    if [[ ${#not_found[@]} != 0 ]]; then
        echoColor 31 "Error: ${program} requires the following programs: ${not_found[@]}"
        echo
        exit 1
    fi
}

#
# Returns true if array `$2` contains `$1`
# $1 (str|int) - the element value to search for
# $2 (array)   - the array to be searched
function in_array() {
    local needle=$1
    shift
    local array=($@)
    for el in "${array[@]}"; do
        if [[ "${el}" == "${needle}" ]]; then
            return 0;
        fi
    done
    return 1
}

#
# Converts FLAC files `$files` to MP3 files and saves them to `$output_dir`
function convertFlacs() {
    for f in "${files[@]}"; do
        mp3_filename="${f/%flac/mp3}"
        ffmpeg -vsync 2 -i "${f}" -b:a "${sample_rate_default}"k "${output_dir}"/"${mp3_filename##*/}"
    done
}

#
# Get arguments and files
while getopts r:o:h options; do
    case "${options}" in
        r) sample_rate_default="${OPTARG}";;
        o) output_dir="${OPTARG}";;
        h) showHelp && exit 0;;
        \?) showUsage && exit 1;;
    esac
done
if ! in_array "${sample_rate_default}" "${sample_rates[@]}"; then
    echoColor 31 "Error: Invalid sample rate: ${sample_rate_default}"
    echo
    exit 1
fi
shift $(($OPTIND - 1))
files=("$@")
if [[ ! "${files}" ]]; then
    echoColor 31 "Error: No files specified"
    echo
    exit 1
fi

# Run functions
checkPrograms
convertFlacs

Thanks for everyones help!

Offline

#7 2020-02-19 13:38:55

qinohe
Member
From: Netherlands
Registered: 2012-06-20
Posts: 1,045

Re: Dealing with multiple files with spaces as script arguments

I have not really tested yet, but shouldn't this be

echo -ne "\033[${color}m${text}\e[0m"

? Otherwise I get no color output;)

Offline

#8 2020-02-19 17:56:03

drtebi
Member
Registered: 2013-02-09
Posts: 114

Re: Dealing with multiple files with spaces as script arguments

qinohe wrote:

I have not really tested yet, but shouldn't this be

echo -ne "\033[${color}m${text}\e[0m"

? Otherwise I get no color output;)

You are right. The escape characters appear to got lost with copy and paste, or maybe with the BB code.

In my editor it actually looks like this, which I think is just another way of writing \033 ?

echo -n "^[[0;${color}m${text}^[[1;0m"

Offline

#9 2020-02-19 19:39:42

qinohe
Member
From: Netherlands
Registered: 2012-06-20
Posts: 1,045

Re: Dealing with multiple files with spaces as script arguments

drtebi wrote:

...

In my editor it actually looks like this, which I think is just another way of writing \033 ?

echo -n "^[[0;${color}m${text}^[[1;0m"

Could be but it isn't usable code in bash/zsh xterm/urxvt..;)

Offline

#10 2020-02-20 14:13:28

drtebi
Member
Registered: 2013-02-09
Posts: 114

Re: Dealing with multiple files with spaces as script arguments

qinohe wrote:
drtebi wrote:

...

In my editor it actually looks like this, which I think is just another way of writing \033 ?

echo -n "^[[0;${color}m${text}^[[1;0m"

Could be but it isn't usable code in bash/zsh xterm/urxvt..;)

Hmmm... works fine in my terminal with bash. But \033 is probably safer.

Offline

#11 2020-03-04 23:46:22

DrZaius
Member
Registered: 2008-01-02
Posts: 191

Re: Dealing with multiple files with spaces as script arguments

drtebi wrote:
        ffmpeg -vsync 2 -i "${f}" -b:a "${sample_rate_default}"k "${output_dir}"/"${mp3_filename##*/}"

-b:a is the bitrate, but you are calling it sample rate (-ar option) which is something completely different.
No need for "-vsync 2" (and don't use it as an input option). Use "-c:v copy" output option instead to deal with cover/album image.

Offline

#12 2020-03-05 00:11:01

drtebi
Member
Registered: 2013-02-09
Posts: 114

Re: Dealing with multiple files with spaces as script arguments

Thanks for the hint. So the "-c:v copy" option is only useful to ensure the cover image is copied? I looked up the documentation, but don't quite understand this option.

Regarding "-b:a", you are referring to my choice of variable name I suppose? I should call it bitrate, makes more sense of course.

Offline

#13 2020-03-05 00:17:58

DrZaius
Member
Registered: 2008-01-02
Posts: 191

Re: Dealing with multiple files with spaces as script arguments

drtebi wrote:

Thanks for the hint. So the "-c:v copy" option is only useful to ensure the cover image is copied? I looked up the documentation, but don't quite understand this option.

"-c:v copy" will just re-mux (stream copy) the cover image instead of additionally re-encoding it. If the input has no cover image then this option will be ignored. Using "-c:v copy" should allow you to avoid the warning:

Frame rate very high for a muxer not efficiently supporting it.
Please consider specifying a lower framerate, a different muxer or -vsync 2
drtebi wrote:

Regarding "-b:a", you are referring to my choice of variable name I suppose? I should call it bitrate, makes more sense of course.

Yes.

Offline

#14 2020-03-05 04:45:29

drtebi
Member
Registered: 2013-02-09
Posts: 114

Re: Dealing with multiple files with spaces as script arguments

OK, thanks for your input, works great, just tested. Here is the updated script:

#!/usr/bin/env bash

# Simple script that uses ffmpeg to convert flac files to MP3 files.

# @author: DrTebi@gmail.com
# @since: 2014-11-30
# @version: 0.4.1
# @license: GPL

program="${0##*/}"
program_helpers=('ffmpeg')
bit_rates=(64 128 160 192 256 320)
bit_rate_default=256
output_dir="${PWD}"

#
# Echos arguments as colored text with no new-line
# $1 (int)    - color code
# $2..n (str) - text to colorize
function echoColor() {
    color="$1"
    shift
    text="$@"
    echo -e -n "\033[0;${color}m${text}\033[1;0m"
}

#
# Shows basic program usage
function showUsage() {
    echoColor 33 "Usage: ${program}  -h | [ -r <bit-rate> ] [-o <output-directory> ] <flac-file(s)>"
    echo
}

#
# Shows program help
function showHelp() {
    cat << EOF
${program} converts FLAC files to MP3 files.

If no <output-directory> is not specified, MP3 files will be saved to the
current directory.

EOF
    showUsage
    cat << EOF

Options are:
  -h                    Display this help message
  -r <bit-rate>         Use specified bit rate <bit-rate> instead of default ${bit_rate_default}.
                        Can be one of 64, 128, 160, 192, 256, or 320
  -o <output-directory> The directory where MP3 files will be saved to

Required arguments:
  <flac-file(s)>        The path to one or more FLAC files, e.g. "Blues/*.flac"

EOF
}

#
# Checks whether programs defined in `$program_helpers` exist
function checkPrograms() {
    local not_found=()
    for x in "${program_helpers[@]}"; do
        type "${x}" > '/dev/null' 2>&1 || not_found+=("${x}")
    done
    if [[ ${#not_found[@]} != 0 ]]; then
        echoColor 31 "Error: ${program} requires the following programs: ${not_found[@]}"
        echo
        exit 1
    fi
}

#
# Returns true if array `$2` contains `$1`
# $1 (str|int) - the element value to search for
# $2 (array)   - the array to be searched
function in_array() {
    local needle=$1
    shift
    local array=($@)
    for el in "${array[@]}"; do
        if [[ "${el}" == "${needle}" ]]; then
            return 0;
        fi
    done
    return 1
}

#
# Converts FLAC files `$files` to MP3 files and saves them to `$output_dir`
function convertFlacs() {
    for f in "${files[@]}"; do
        mp3_filename="${f/%flac/mp3}"
        ffmpeg -i "${f}" -c:v copy -b:a "${bit_rate_default}"k "${output_dir}"/"${mp3_filename##*/}"
    done
}

#
# Get arguments and files
while getopts r:o:h options; do
    case "${options}" in
        r) bit_rate_default="${OPTARG}";;
        o) output_dir="${OPTARG}";;
        h) showHelp && exit 0;;
        \?) showUsage && exit 1;;
    esac
done
if ! in_array "${bit_rate_default}" "${bit_rates[@]}"; then
    echoColor 31 "Error: Invalid bit rate: ${bit_rate_default}"
    echo
    exit 1
fi
shift $(($OPTIND - 1))
files=("$@")
if [[ ! "${files}" ]]; then
    echoColor 31 "Error: No files specified"
    echo
    exit 1
fi

# Run functions
checkPrograms
convertFlacs

I am sure it could be improved further, but for now it's pretty neat and simple already.

Last edited by drtebi (2020-03-05 05:02:36)

Offline

Board footer

Powered by FluxBB