1# Copyright 2009-2011 Steven Robertson, Christoph Reiter 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2 of the License, or 6# (at your option) any later version. 7 8import collections 9import subprocess 10 11from gi.repository import GLib, Gst 12 13from quodlibet import _ 14from quodlibet.util.string import decode 15from quodlibet.util import is_linux, is_windows 16from quodlibet.player import PlayerError 17 18 19def pulse_is_running(): 20 """Returns whether pulseaudio is running""" 21 22 # If we have a pulsesink we can get the server presence through 23 # setting the ready state 24 element = Gst.ElementFactory.make("pulsesink", None) 25 if element is not None: 26 element.set_state(Gst.State.READY) 27 res = element.get_state(0)[0] 28 element.set_state(Gst.State.NULL) 29 return res != Gst.StateChangeReturn.FAILURE 30 31 # NOTE: Don't check with 'pulseaudio --check' because it can't guarantee 32 # Gstreamer works with PA (e.g., when 'pulsesink' not installed). 33 return False 34 35 36def link_many(elements): 37 last = None 38 for element in elements: 39 if last: 40 if not Gst.Element.link(last, element): 41 return False 42 last = element 43 return True 44 45 46def unlink_many(elements): 47 last = None 48 for element in elements: 49 if last: 50 if not Gst.Element.unlink(last, element): 51 return False 52 last = element 53 return True 54 55 56def iter_to_list(func): 57 objects = [] 58 59 iter_ = func() 60 while 1: 61 status, value = iter_.next() 62 if status == Gst.IteratorResult.OK: 63 objects.append(value) 64 else: 65 break 66 return objects 67 68 69def find_audio_sink(): 70 """Get the best audio sink available. 71 72 Returns (element, description) or raises PlayerError. 73 """ 74 75 if is_windows(): 76 sinks = [ 77 "directsoundsink", 78 ] 79 elif is_linux() and pulse_is_running(): 80 sinks = [ 81 "pulsesink", 82 ] 83 else: 84 sinks = [ 85 "autoaudiosink", # plugins-good 86 "pulsesink", # plugins-good 87 "alsasink", # plugins-base 88 ] 89 90 for name in sinks: 91 element = Gst.ElementFactory.make(name, None) 92 if element is not None: 93 return (element, name) 94 else: 95 details = " (%s)" % ", ".join(sinks) if sinks else "" 96 raise PlayerError(_("No GStreamer audio sink found") + details) 97 98 99def GStreamerSink(pipeline_desc): 100 """Returns a list of unlinked gstreamer elements ending with an audio sink 101 and a textual description of the pipeline. 102 103 `pipeline_desc` can be gst-launch syntax for multiple elements 104 with or without an audiosink. 105 106 In case of an error, raises PlayerError 107 """ 108 109 pipe = None 110 if pipeline_desc: 111 try: 112 pipe = [Gst.parse_launch(e) for e in pipeline_desc.split('!')] 113 except GLib.GError as e: 114 message = e.message 115 raise PlayerError(_("Invalid GStreamer output pipeline"), message) 116 117 if pipe: 118 # In case the last element is linkable with a fakesink 119 # it is not an audiosink, so we append the default one 120 fake = Gst.ElementFactory.make('fakesink', None) 121 if link_many([pipe[-1], fake]): 122 unlink_many([pipe[-1], fake]) 123 default_elm, default_desc = find_audio_sink() 124 pipe += [default_elm] 125 pipeline_desc += " ! " + default_desc 126 else: 127 elm, pipeline_desc = find_audio_sink() 128 pipe = [elm] 129 130 return pipe, pipeline_desc 131 132 133class TagListWrapper(collections.Mapping): 134 def __init__(self, taglist, merge=False): 135 self._list = taglist 136 self._merge = merge 137 138 def __len__(self): 139 return self._list.n_tags() 140 141 def __iter__(self): 142 for i in range(len(self)): 143 yield self._list.nth_tag_name(i) 144 145 def __getitem__(self, key): 146 if not Gst.tag_exists(key): 147 raise KeyError 148 149 values = [] 150 index = 0 151 while 1: 152 value = self._list.get_value_index(key, index) 153 if value is None: 154 break 155 values.append(value) 156 index += 1 157 158 if not values: 159 raise KeyError 160 161 if self._merge: 162 try: 163 return " - ".join(values) 164 except TypeError: 165 return values[0] 166 167 return values 168 169 170def parse_gstreamer_taglist(tags): 171 """Takes a GStreamer taglist and returns a dict containing only 172 numeric and unicode values and str keys.""" 173 174 merged = {} 175 for key in tags.keys(): 176 value = tags[key] 177 # extended-comment sometimes contains a single vorbiscomment or 178 # a list of them ["key=value", "key=value"] 179 if key == "extended-comment": 180 if not isinstance(value, list): 181 value = [value] 182 for val in value: 183 if not isinstance(val, str): 184 continue 185 split = val.split("=", 1) 186 sub_key = split[0] 187 val = split[-1] 188 if sub_key in merged: 189 sub_val = merged[sub_key] 190 if not isinstance(sub_val, str): 191 continue 192 if val not in sub_val.split("\n"): 193 merged[sub_key] += "\n" + val 194 else: 195 merged[sub_key] = val 196 elif isinstance(value, Gst.DateTime): 197 value = value.to_iso8601_string() 198 merged[key] = value 199 else: 200 if isinstance(value, (int, float)): 201 merged[key] = value 202 continue 203 204 if isinstance(value, bytes): 205 value = decode(value) 206 207 if not isinstance(value, str): 208 value = str(value) 209 210 if key in merged: 211 merged[key] += "\n" + value 212 else: 213 merged[key] = value 214 215 return merged 216 217 218def bin_debug(elements, depth=0, lines=None): 219 """Takes a list of gst.Element that are part of a prerolled pipeline, and 220 recursively gets the children and all caps between the elements. 221 222 Returns a list of text lines suitable for printing. 223 """ 224 225 from quodlibet.util.dprint import Colorise 226 227 if lines is None: 228 lines = [] 229 else: 230 lines.append(" " * (depth - 1) + "\\") 231 232 for i, elm in enumerate(elements): 233 for pad in iter_to_list(elm.iterate_sink_pads): 234 caps = pad.get_current_caps() 235 if caps: 236 lines.append("%s| %s" % (" " * depth, caps.to_string())) 237 name = elm.get_name() 238 cls = Colorise.blue(type(elm).__name__.split(".", 1)[-1]) 239 lines.append("%s|-%s (%s)" % (" " * depth, cls, name)) 240 241 if isinstance(elm, Gst.Bin): 242 children = reversed(iter_to_list(elm.iterate_sorted)) 243 bin_debug(children, depth + 1, lines) 244 245 return lines 246