1

I want to use metaflac (https://linux.die.net/man/1/metaflac) command from within a python script.

from subprocess import run

flac_files = "/home/fricadelle/Artist - Album (2008)/*.flac"
run(['metaflac', '--add-replay-gain', flac_files])

I get

The FLAC file could not be opened.  Most likely the file does not exist

or is not readable.

if I add shell = True to the run function I'd get:

ERROR: you must specify at least one FLAC file;
   metaflac cannot be used as a pipe

So what do I do wrong? Thanks!

PS: of course the command works fine in a shell:

metaflac --add-replay-gain /home/fricadelle/Artist\ -\ Album \(2008\)/*.flac
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
fricadelle
  • 511
  • 1
  • 8
  • 26
  • This is yet another duplicate. For the love of God start closing-as-duplicate and stop answering duplicates till the cows come home... it's only polluting SO. – smci May 18 '18 at 00:27

2 Answers2

2

Unless you specify shell=True (and as a first approximation, you should never specify shell=True), the arguments you provide are passed as is, with no shell expansions, word-splitting or dequoting. So the filename you pass as an argument is precisely /home/fricadelle/Artist - Album (2008)/*.flac, which is not the name of any file. (That's why you don't need to add backslashes before the spaces and parentheses. If you specified shell=True -- and I repeat, you really should avoid that -- then you would need to include backslashes so that the shell doesn't split the name into several different words.)

When you type flac_files = "/home/fricadelle/Artist - Album (2008)/*.flac unquoted in a shell, the shell will try to expand that to a list of all the files whose names match then pattern, and will then pass that list as separate arguments. Since subprocess.run doesn't do this, you will have to do it yourself, which you would normally do with glob.glob. For example,

from subprocess import run
from glob import glob
flac_files = "/home/fricadelle/Artist - Album (2008)/*.flac"
run(['metaflac', '--add-replay-gain'] + glob(flac_files))

Note: unlike the shell, glob.glob will return an empty list if the pattern matches no files. You really should check for this error rather than invoke metaflac with no filename options.

rici
  • 234,347
  • 28
  • 237
  • 341
-1

See the answer here for a better explanation.

Globbing doesn't work the way you're expecting it to here, you need to specify shell=True, but then you'll need to drop the list.

run('metaflac --add-replay-gain ' + flac_files, shell=True)

Should do the trick.

Smilliam
  • 99
  • 1
  • 5
  • 1
    Using `shell=True` with parameterized / non-constant content folded into the shell script portion is a really, *really* bad idea (it's possible to use `shell=True` safely, but only when content is either quoted with `shlex.quote()`/`pipes.quote()` before being added to the string, or added to the argument list out-of-band from that string). If you're getting your filenames from an upload folder, and someone creates an artist named `$(rm -rf ~)`, then you just had an Extremely Bad Day. See the big red section in https://docs.python.org/2/library/subprocess.html#frequently-used-arguments – Charles Duffy May 17 '18 at 23:03
  • (...and more creative names that defy easy attempts at quoting are possible; consider the directory created by the command `mkdir $'$(rm -rf ~)\'$(rm -rf ~)\''`). – Charles Duffy May 17 '18 at 23:05
  • ...also, it doesn't look like `flac_files` is quoted by the code here, so the spaces inside the name would be split into separate words by the shell -- that is, it would look for a file named `/home/fricadelle/Artist`, then one named `-`, then one named `Album`, then try to expand the glob `(2008)/*.flac`. – Charles Duffy May 17 '18 at 23:06
  • @charles: and the parentheses are also going to be a problem. – rici May 17 '18 at 23:12