mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 16:42:42 +01:00
util.command_output: return stderr, too
Return a namedtuple CommandOutput(stdout, stderr) instead of just stdout from util.command_ouput, allowing separate access to stdout and stderr. This change is required by the ffmpeg replaygain backend (GitHub PullRequest #3056) as ffmpeg's ebur128 filter outputs only to stderr.
This commit is contained in:
parent
f39cff71d3
commit
30395911e2
9 changed files with 31 additions and 21 deletions
|
|
@ -24,7 +24,7 @@ import re
|
|||
import shutil
|
||||
import fnmatch
|
||||
import functools
|
||||
from collections import Counter
|
||||
from collections import Counter, namedtuple
|
||||
from multiprocessing.pool import ThreadPool
|
||||
import traceback
|
||||
import subprocess
|
||||
|
|
@ -763,7 +763,11 @@ def cpu_count():
|
|||
num = 0
|
||||
elif sys.platform == 'darwin':
|
||||
try:
|
||||
num = int(command_output(['/usr/sbin/sysctl', '-n', 'hw.ncpu']))
|
||||
num = int(command_output([
|
||||
'/usr/sbin/sysctl',
|
||||
'-n',
|
||||
'hw.ncpu',
|
||||
]).stdout)
|
||||
except (ValueError, OSError, subprocess.CalledProcessError):
|
||||
num = 0
|
||||
else:
|
||||
|
|
@ -794,9 +798,15 @@ def convert_command_args(args):
|
|||
return [convert(a) for a in args]
|
||||
|
||||
|
||||
# stdout and stderr as bytes
|
||||
CommandOutput = namedtuple("CommandOutput", ("stdout", "stderr"))
|
||||
|
||||
|
||||
def command_output(cmd, shell=False):
|
||||
"""Runs the command and returns its output after it has exited.
|
||||
|
||||
Returns a CommandOutput.
|
||||
|
||||
``cmd`` is a list of arguments starting with the command names. The
|
||||
arguments are bytes on Unix and strings on Windows.
|
||||
If ``shell`` is true, ``cmd`` is assumed to be a string and passed to a
|
||||
|
|
@ -831,7 +841,7 @@ def command_output(cmd, shell=False):
|
|||
cmd=' '.join(cmd),
|
||||
output=stdout + stderr,
|
||||
)
|
||||
return stdout
|
||||
return CommandOutput(stdout, stderr)
|
||||
|
||||
|
||||
def max_filename_length(path, limit=MAX_FILENAME_LENGTH):
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ def im_getsize(path_in):
|
|||
['-format', '%w %h', util.syspath(path_in, prefix=False)]
|
||||
|
||||
try:
|
||||
out = util.command_output(cmd)
|
||||
out = util.command_output(cmd).stdout
|
||||
except subprocess.CalledProcessError as exc:
|
||||
log.warning(u'ImageMagick size query failed')
|
||||
log.debug(
|
||||
|
|
@ -265,7 +265,7 @@ def get_im_version():
|
|||
cmd = cmd_name + ['--version']
|
||||
|
||||
try:
|
||||
out = util.command_output(cmd)
|
||||
out = util.command_output(cmd).stdout
|
||||
except (subprocess.CalledProcessError, OSError) as exc:
|
||||
log.debug(u'ImageMagick version check failed: {}', exc)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ def call(args):
|
|||
Raise a AnalysisABSubmitError on failure.
|
||||
"""
|
||||
try:
|
||||
return util.command_output(args)
|
||||
return util.command_output(args).stdout
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise ABSubmitError(
|
||||
u'{0} exited with status {1}'.format(args[0], e.returncode)
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ class DuplicatesPlugin(BeetsPlugin):
|
|||
u'computing checksum',
|
||||
key, displayable_path(item.path))
|
||||
try:
|
||||
checksum = command_output(args)
|
||||
checksum = command_output(args).stdout
|
||||
setattr(item, key, checksum)
|
||||
item.store()
|
||||
self._log.debug(u'computed checksum for {0} using {1}',
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ class IPFSPlugin(BeetsPlugin):
|
|||
cmd = "ipfs add -q -r".split()
|
||||
cmd.append(album_dir)
|
||||
try:
|
||||
output = util.command_output(cmd).split()
|
||||
output = util.command_output(cmd).stdout.split()
|
||||
except (OSError, subprocess.CalledProcessError) as exc:
|
||||
self._log.error(u'Failed to add {0}, error: {1}', album_dir, exc)
|
||||
return False
|
||||
|
|
@ -183,7 +183,7 @@ class IPFSPlugin(BeetsPlugin):
|
|||
else:
|
||||
cmd = "ipfs add -q ".split()
|
||||
cmd.append(tmp.name)
|
||||
output = util.command_output(cmd)
|
||||
output = util.command_output(cmd).stdout
|
||||
except (OSError, subprocess.CalledProcessError) as err:
|
||||
msg = "Failed to publish library. Error: {0}".format(err)
|
||||
self._log.error(msg)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class KeyFinderPlugin(BeetsPlugin):
|
|||
|
||||
try:
|
||||
output = util.command_output([bin, '-f',
|
||||
util.syspath(item.path)])
|
||||
util.syspath(item.path)]).stdout
|
||||
except (subprocess.CalledProcessError, OSError) as exc:
|
||||
self._log.error(u'execution failed: {0}', exc)
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -47,12 +47,12 @@ class FatalGstreamerPluginReplayGainError(FatalReplayGainError):
|
|||
loading the required plugins."""
|
||||
|
||||
|
||||
def call(args):
|
||||
def call(args, **kwargs):
|
||||
"""Execute the command and return its output or raise a
|
||||
ReplayGainError on failure.
|
||||
"""
|
||||
try:
|
||||
return command_output(args)
|
||||
return command_output(args, **kwargs)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise ReplayGainError(
|
||||
u"{0} exited with status {1}".format(args[0], e.returncode)
|
||||
|
|
@ -206,7 +206,7 @@ class Bs1770gainBackend(Backend):
|
|||
self._log.debug(
|
||||
u'executing {0}', u' '.join(map(displayable_path, args))
|
||||
)
|
||||
output = call(args)
|
||||
output = call(args).stdout
|
||||
|
||||
self._log.debug(u'analysis finished: {0}', output)
|
||||
results = self.parse_tool_output(output, path_list, is_album)
|
||||
|
|
@ -378,7 +378,7 @@ class CommandBackend(Backend):
|
|||
|
||||
self._log.debug(u'analyzing {0} files', len(items))
|
||||
self._log.debug(u"executing {0}", " ".join(map(displayable_path, cmd)))
|
||||
output = call(cmd)
|
||||
output = call(cmd).stdout
|
||||
self._log.debug(u'analysis finished')
|
||||
return self.parse_tool_output(output,
|
||||
len(items) + (1 if is_album else 0))
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class KeyFinderTest(unittest.TestCase, TestHelper):
|
|||
item = Item(path='/file')
|
||||
item.add(self.lib)
|
||||
|
||||
command_output.return_value = 'dbm'
|
||||
command_output.return_value = util.CommandOutput(b"dbm", b"")
|
||||
self.run_command('keyfinder')
|
||||
|
||||
item.load()
|
||||
|
|
@ -47,7 +47,7 @@ class KeyFinderTest(unittest.TestCase, TestHelper):
|
|||
['KeyFinder', '-f', util.syspath(item.path)])
|
||||
|
||||
def test_add_key_on_import(self, command_output):
|
||||
command_output.return_value = 'dbm'
|
||||
command_output.return_value = util.CommandOutput(b"dbm", b"")
|
||||
importer = self.create_importer()
|
||||
importer.run()
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ class KeyFinderTest(unittest.TestCase, TestHelper):
|
|||
item = Item(path='/file', initial_key='F')
|
||||
item.add(self.lib)
|
||||
|
||||
command_output.return_value = 'C#m'
|
||||
command_output.return_value = util.CommandOutput(b"C#m", b"")
|
||||
self.run_command('keyfinder')
|
||||
|
||||
item.load()
|
||||
|
|
@ -70,7 +70,7 @@ class KeyFinderTest(unittest.TestCase, TestHelper):
|
|||
item = Item(path='/file', initial_key='F')
|
||||
item.add(self.lib)
|
||||
|
||||
command_output.return_value = 'dbm'
|
||||
command_output.return_value = util.CommandOutput(b"dbm", b"")
|
||||
self.run_command('keyfinder')
|
||||
|
||||
item.load()
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from mock import patch
|
|||
from test.helper import TestHelper, capture_log, has_program
|
||||
|
||||
from beets import config
|
||||
from beets.util import CommandOutput
|
||||
from mediafile import MediaFile
|
||||
from beetsplug.replaygain import (FatalGstreamerPluginReplayGainError,
|
||||
GStreamerBackend)
|
||||
|
|
@ -169,7 +170,6 @@ class ReplayGainLdnsCliTest(ReplayGainCliTestBase, unittest.TestCase):
|
|||
|
||||
|
||||
class ReplayGainLdnsCliMalformedTest(TestHelper, unittest.TestCase):
|
||||
|
||||
@patch('beetsplug.replaygain.call')
|
||||
def setUp(self, call_patch):
|
||||
self.setup_beets()
|
||||
|
|
@ -186,14 +186,14 @@ class ReplayGainLdnsCliMalformedTest(TestHelper, unittest.TestCase):
|
|||
@patch('beetsplug.replaygain.call')
|
||||
def test_malformed_output(self, call_patch):
|
||||
# Return malformed XML (the ampersand should be &)
|
||||
call_patch.return_value = """
|
||||
call_patch.return_value = CommandOutput(stdout="""
|
||||
<album>
|
||||
<track total="1" number="1" file="&">
|
||||
<integrated lufs="0" lu="0" />
|
||||
<sample-peak spfs="0" factor="0" />
|
||||
</track>
|
||||
</album>
|
||||
"""
|
||||
""", stderr="")
|
||||
|
||||
with capture_log('beets.replaygain') as logs:
|
||||
self.run_command('replaygain')
|
||||
|
|
|
|||
Loading…
Reference in a new issue