0

Is there a zsh script that can convert a directory files in one music format and be converted into Ogg opus format. The filenames have spaces in their names.

For example, a directory contains 10 files with *.wma extensions

Files are converted into *.wav format using ffmpeg -i filename.wma filename.wav

The *.wav files are converted to opus using opusenc --bitrate 160 filename.wav filename.opus

Update: ffmpeg -i filename.wma -c:a libopus -b:a 128k filename.opus converts the file with one command

A partially working script will process filenames in current directory from .wma to .wav, even with spaces. However, the .wav extension is added rather than replacing the *.wma file extension

This script was added to a file called convert and that file made executable

IFS=$'\n'
for x in *.wma ; do
  echo $x
  ffmpeg -i "$x" $x.wav
done

Trying to use Zsh modifiers to substitute the filename extension with: ${x:e}".wav (and after a suggestion by ilkkachu I also tried ${x:r}".wav)

IFS=$'\n'
for x in *.wma ; do
  ffmpeg -i "$x" "${x:e}".wav
done

Calling this from a file called convert, the following error is returned

./convert: 3: Bad substitution

The same error happens with

IFS=$'\n'
for x in *.wma ; do
  ffmpeg -i "$x" "${x:r}".wav
done

I assume the syntax is not quite write or modifiers do not work when filenames have spaces. Or I still have a lot to learn about zsh :)

Is there a correct way to substitute a filename extension in Zsh (when file names contain spaces)

Thank you.

2 Answers2

2

While your problem was that you had the script interpreted by sh instead of zsh, in zsh, you could use zmv here:

#! /bin/zsh -
autoload zmv
zmv -P ffmpeg -o -i './(*).wma' './$1.wav'

zmv normally does batch renaming, but can also do batch copying / linking or batch-anything if given the name of the command to do the anything.

You benefit from the few safeguards that it performs before doing anything (check that the destination files don't exist or that not two source files map to the same target).

Note also the ./ prefix to avoid problems with filenames starting with - or in general to work around the unsafe CLI of ffmpeg.

In any case the IFS=$'\n' in your code serves no purpose as you're not using IFS word splitting (only done upon command substitution and $=param in zsh), nor using things like "$array[*]" to join elements of an array with the first element of $IFS and anyway in zsh, you'd rather use the s[sep] / f / 0 parameter expansion flags to split or j[sep] to join rather than changing $IFS globally. Also beware that newline is as valid a character as any in a file name.

0

To use Zsh modifiers (and other zsh specific syntax) a script added to a file should start by defining it is a zsh script

#!/usr/bin/zsh

Optionally, check the location of zsh first by using the which zsh if unsure your OS uses this location

Adding this line may also add specific zsh syntax highlighting when opening the file in an editor

The shell variable IFS (Internal Field Separator) determines how word boundaries are recognised when splitting a sequence of character strings, such as a directory listing.

Setting IFS to $'\n' in zsh allows use of filenames that contain spaces

IFS=$'\n'

Using a for loop will iterate through all the files in the current directory, using wildcards - *.wma for that file extension in the current directory or **/*.wma for current and sub-directories

#!/usr/bin/zsh

IFS=$'\n' for x in *.wma ; do ffmpeg -i "$x" "${x:r}".wav done

ffmpeg can convert to opus files using the libopus audio codec, so do not need to be converted to a format that opusenc tool supports.

ffmpeg -i original-file.wma -b:a 128k -c:a libopus ouptut-file.opus

where -b:a is the audio bitrate and -c:a is the audio codec

Putting that all together the final script is:

#!/usr/bin/zsh

IFS=$'\n' for x in *.wma ; do ffmpeg -i "$x" -b:a 128k -c:a libopus "${x:r}".opus done