import functools import os import platform import sys import pkg_resources from mopidy.internal import formatting from mopidy.internal.gi import Gst, gi def format_dependency_list(adapters=None): if adapters is None: dist_names = { ep.dist.project_name for ep in pkg_resources.iter_entry_points("mopidy.ext") if ep.dist.project_name != "Mopidy" } dist_infos = [ functools.partial(pkg_info, dist_name) for dist_name in dist_names ] adapters = ( [ executable_info, platform_info, python_info, functools.partial(pkg_info, "Mopidy", True), ] + dist_infos + [gstreamer_info] ) return "\n".join([_format_dependency(a()) for a in adapters]) def _format_dependency(dep_info): lines = [] if "version" not in dep_info: lines.append(f"{dep_info['name']}: not found") else: source = f" from {dep_info['path']}" if "path" in dep_info else "" lines.append(f"{dep_info['name']}: {dep_info['version']}{source}") if "other" in dep_info: details = formatting.indent(dep_info["other"], places=4) lines.append(f" Detailed information: {details}") if dep_info.get("dependencies", []): for sub_dep_info in dep_info["dependencies"]: sub_dep_lines = _format_dependency(sub_dep_info) lines.append( formatting.indent(sub_dep_lines, places=2, singles=True) ) return "\n".join(lines) def executable_info(): return { "name": "Executable", "version": sys.argv[0], } def platform_info(): return { "name": "Platform", "version": platform.platform(), } def python_info(): return { "name": "Python", "version": ( f"{platform.python_implementation()} {platform.python_version()}" ), "path": os.path.dirname(platform.__file__), } def pkg_info( project_name=None, include_transitive_deps=True, include_extras=False ): if project_name is None: project_name = "Mopidy" try: distribution = pkg_resources.get_distribution(project_name) extras = include_extras and distribution.extras or [] if include_transitive_deps: dependencies = [ pkg_info( d.project_name, include_transitive_deps=d.project_name != "Mopidy", ) for d in distribution.requires(extras) ] else: dependencies = [] return { "name": project_name, "version": distribution.version, "path": distribution.location, "dependencies": dependencies, } except pkg_resources.ResolutionError: return { "name": project_name, } def gstreamer_info(): other = [] other.append(f"Python wrapper: python-gi {gi.__version__}") found_elements = [] missing_elements = [] for name, status in _gstreamer_check_elements(): if status: found_elements.append(name) else: missing_elements.append(name) other.append("Relevant elements:") other.append(" Found:") for element in found_elements: other.append(f" {element}") if not found_elements: other.append(" none") other.append(" Not found:") for element in missing_elements: other.append(f" {element}") if not missing_elements: other.append(" none") return { "name": "GStreamer", "version": ".".join(map(str, Gst.version())), "path": os.path.dirname(gi.__file__), "other": "\n".join(other), } def _gstreamer_check_elements(): elements_to_check = [ # Core playback "uridecodebin", # External HTTP streams "souphttpsrc", # Spotify "appsrc", # Audio sinks "alsasink", "osssink", "oss4sink", "pulsesink", # MP3 encoding and decoding # # One of flump3dec, mad, and mpg123audiodec is required for MP3 # playback. "flump3dec", "id3demux", "id3v2mux", "lamemp3enc", "mad", "mpegaudioparse", "mpg123audiodec", # Ogg Vorbis encoding and decoding "vorbisdec", "vorbisenc", "vorbisparse", "oggdemux", "oggmux", "oggparse", # Flac decoding "flacdec", "flacparse", # Shoutcast output "shout2send", ] known_elements = [ factory.get_name() for factory in Gst.Registry.get().get_feature_list(Gst.ElementFactory) ] return [ (element, element in known_elements) for element in elements_to_check ]