Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2# This file is part of Xpra. 

3# Copyright (C) 2010-2019 Antoine Martin <antoine@xpra.org> 

4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

5# later version. See the file COPYING for details. 

6 

7import sys 

8import os.path 

9 

10from xpra.util import envbool, csv 

11from xpra.log import Logger 

12log = Logger("codec", "loader") 

13 

14 

15#these codecs may well not load because we 

16#do not require the libraries to be installed 

17NOWARN = ["nvenc", "enc_x265", "enc_ffmpeg"] 

18 

19SELFTEST = envbool("XPRA_CODEC_SELFTEST", True) 

20FULL_SELFTEST = envbool("XPRA_CODEC_FULL_SELFTEST", False) 

21 

22CODEC_FAIL_IMPORT = os.environ.get("XPRA_CODEC_FAIL_IMPORT", "").split(",") 

23CODEC_FAIL_SELFTEST = os.environ.get("XPRA_CODEC_FAIL_SELFTEST", "").split(",") 

24 

25log("codec loader settings: SELFTEST=%s, FULL_SELFTEST=%s, CODEC_FAIL_IMPORT=%s, CODEC_FAIL_SELFTEST=%s", 

26 SELFTEST, FULL_SELFTEST, CODEC_FAIL_IMPORT, CODEC_FAIL_SELFTEST) 

27 

28codec_errors = {} 

29codecs = {} 

30def codec_import_check(name, description, top_module, class_module, classnames): 

31 log("%s:", name) 

32 log(" codec_import_check%s", (name, description, top_module, class_module, classnames)) 

33 try: 

34 try: 

35 if name in CODEC_FAIL_IMPORT: 

36 raise ImportError("codec found in fail import list") 

37 __import__(top_module, {}, {}, []) 

38 except ImportError as e: 

39 log("failed to import %s (%s)", description, name) 

40 log("", exc_info=True) 

41 codec_errors[name] = str(e) 

42 return None 

43 except Exception as e: 

44 log.warn(" cannot load %s (%s):", name, description, exc_info=True) 

45 codec_errors[name] = str(e) 

46 return None 

47 classname = None 

48 try: 

49 #module is present 

50 try: 

51 log(" %s found, will check for %s in %s", top_module, classnames, class_module) 

52 ic = __import__(class_module, {}, {}, classnames) 

53 try: 

54 #run init_module? 

55 init_module = getattr(ic, "init_module", None) 

56 log("%s: init_module=%s", class_module, init_module) 

57 if init_module: 

58 init_module() 

59 

60 if classnames: 

61 for classname in classnames: 

62 clazz = getattr(ic, classname) 

63 log("%s: %s=%s", class_module, classname, clazz) 

64 

65 selftest = getattr(ic, "selftest", None) 

66 log("%s.selftest=%s", name, selftest) 

67 if SELFTEST and selftest: 

68 if name in CODEC_FAIL_SELFTEST: 

69 raise ImportError("codec found in fail selftest list") 

70 try: 

71 selftest(FULL_SELFTEST) 

72 except Exception as e: 

73 log.warn("Warning: %s failed its self test", name) 

74 for x in str(e).splitlines(): 

75 log.warn(" %s", x) 

76 log("%s failed", selftest, exc_info=True) 

77 return None 

78 finally: 

79 cleanup_module = getattr(ic, "cleanup_module", None) 

80 log("%s: cleanup_module=%s", class_module, cleanup_module) 

81 if cleanup_module: 

82 cleanup_module() 

83 #log.warn("codec_import_check(%s, ..)=%s" % (name, ic)) 

84 log(" found %s : %s", name, ic) 

85 codecs[name] = ic 

86 return ic 

87 except ImportError as e: 

88 codec_errors[name] = str(e) 

89 l = log.error 

90 if name in NOWARN: 

91 l = log.debug 

92 l("Error importing %s (%s)", description, name) 

93 l(" %s", e) 

94 log("", exc_info=True) 

95 except Exception as e: 

96 codec_errors[name] = str(e) 

97 if classname: 

98 log.warn(" cannot load %s (%s): %s missing from %s", 

99 name, description, classname, class_module, exc_info=True) 

100 else: 

101 log.warn(" cannot load %s (%s)", 

102 name, description, exc_info=True) 

103 return None 

104codec_versions = {} 

105def add_codec_version(name, top_module, version="get_version()", alt_version="__version__"): 

106 try: 

107 fieldnames = [x for x in (version, alt_version) if x is not None] 

108 for fieldname in fieldnames: 

109 f = fieldname 

110 if f.endswith("()"): 

111 f = version[:-2] 

112 module = __import__(top_module, {}, {}, [f]) 

113 if not hasattr(module, f): 

114 continue 

115 v = getattr(module, f) 

116 if fieldname.endswith("()") and v: 

117 v = v() 

118 global codec_versions 

119 codec_versions[name] = v 

120 #optional info: 

121 if hasattr(module, "get_info"): 

122 info = getattr(module, "get_info") 

123 log(" %s %s.%s=%s", name, top_module, info, info()) 

124 return v 

125 if name in codecs: 

126 log.warn(" cannot find %s in %s", " or ".join(fieldnames), module) 

127 else: 

128 log(" no version information for missing codec %s", name) 

129 except ImportError as e: 

130 #not present 

131 log(" cannot import %s: %s", name, e) 

132 log("", exc_info=True) 

133 except Exception as e: 

134 log.warn("error during codec import: %s", e) 

135 log.warn("", exc_info=True) 

136 return None 

137 

138def xpra_codec_import(name, description, top_module, class_module, classname): 

139 xpra_top_module = "xpra.codecs.%s" % top_module 

140 xpra_class_module = "%s.%s" % (xpra_top_module, class_module) 

141 if codec_import_check(name, description, xpra_top_module, xpra_class_module, classname): 

142 version_name = name 

143 if name.startswith("enc_") or name.startswith("dec_") or name.startswith("csc_"): 

144 version_name = name[4:] 

145 add_codec_version(version_name, xpra_class_module) 

146 

147 

148CODEC_OPTIONS = { 

149 #encoders: 

150 "enc_pillow" : ("Pillow encoder", "pillow", "encoder", "encode"), 

151 "enc_webp" : ("webp encoder", "webp", "encoder", "encode"), 

152 "enc_jpeg" : ("JPEG encoder", "jpeg", "encoder", "encode"), 

153 #video encoders: 

154 "enc_vpx" : ("vpx encoder", "vpx", "encoder", "Encoder"), 

155 "enc_x264" : ("x264 encoder", "enc_x264", "encoder", "Encoder"), 

156 "enc_x265" : ("x265 encoder", "enc_x265", "encoder", "Encoder"), 

157 "nvenc" : ("nvenc encoder", "nvenc", "encoder", "Encoder"), 

158 "enc_ffmpeg" : ("ffmpeg encoder", "enc_ffmpeg", "encoder", "Encoder"), 

159 #csc: 

160 "csc_swscale" : ("swscale colorspace conversion", "csc_swscale", "colorspace_converter", "ColorspaceConverter"), 

161 "csc_libyuv" : ("libyuv colorspace conversion", "csc_libyuv", "colorspace_converter", "ColorspaceConverter"), 

162 "csc_cython" : ("cython colorspace conversion", "csc_cython", "colorspace_converter", "ColorspaceConverter"), 

163 #decoders: 

164 "dec_pillow" : ("Pillow decoder", "pillow", "decoder", "decompress"), 

165 "dec_webp" : ("webp decoder", "webp", "decoder", "decompress"), 

166 "dec_jpeg" : ("JPEG decoder", "jpeg", "decoder", "decompress_to_rgb", "decompress_to_yuv"), 

167 #video decoders: 

168 "dec_vpx" : ("vpx decoder", "vpx", "decoder", "Decoder"), 

169 "dec_avcodec2" : ("avcodec2 decoder", "dec_avcodec2", "decoder", "Decoder"), 

170 } 

171 

172def load_codec(name): 

173 if has_codec(name): 

174 return 

175 try: 

176 option = CODEC_OPTIONS[name] 

177 description, top_module, class_module = option[:3] 

178 classnames = option[3:] 

179 except KeyError: 

180 log("load_codec(%s)", name, exc_info=True) 

181 log.error("Error: invalid codec name '%s'", name) 

182 else: 

183 xpra_codec_import(name, description, top_module, class_module, classnames) 

184 

185 

186def load_codecs(encoders=True, decoders=True, csc=True, video=True): 

187 show = [] 

188 log("loading codecs") 

189 

190 def load(*names): 

191 for name in names: 

192 load_codec(name) 

193 

194 if encoders: 

195 show += list(ENCODER_CODECS) 

196 load(*ENCODER_CODECS) 

197 if video: 

198 show += list(ENCODER_VIDEO_CODECS) 

199 load(*ENCODER_VIDEO_CODECS) 

200 if csc and video: 

201 show += list(CSC_CODECS) 

202 load(*CSC_CODECS) 

203 if decoders: 

204 show += list(DECODER_CODECS) 

205 load(*DECODER_CODECS) 

206 if video: 

207 show += list(DECODER_VIDEO_CODECS) 

208 load(*DECODER_VIDEO_CODECS) 

209 

210 log("done loading codecs") 

211 log("found:") 

212 #print("codec_status=%s" % codecs) 

213 for name in sorted(ALL_CODECS): 

214 log("* %s : %s %s" % (name.ljust(20), str(name in codecs).ljust(10), codecs.get(name, ""))) 

215 log("codecs versions:") 

216 for name, version in codec_versions.items(): 

217 log("* %s : %s" % (name.ljust(20), version)) 

218 

219 

220def get_codec_error(name): 

221 return codec_errors.get(name) 

222 

223def get_codec(name): 

224 return codecs.get(name) 

225 

226def get_codec_version(name): 

227 return codec_versions.get(name) 

228 

229def has_codec(name) -> bool: 

230 return name in codecs 

231 

232 

233CSC_CODECS = "csc_swscale", "csc_cython", "csc_libyuv" 

234ENCODER_CODECS = "enc_pillow", "enc_webp", "enc_jpeg" 

235ENCODER_VIDEO_CODECS = "enc_vpx", "enc_x264", "enc_x265", "nvenc", "enc_ffmpeg" 

236DECODER_CODECS = "dec_pillow", "dec_webp", "dec_jpeg" 

237DECODER_VIDEO_CODECS = "dec_vpx", "dec_avcodec2" 

238 

239ALL_CODECS = tuple(set(CSC_CODECS + ENCODER_CODECS + ENCODER_VIDEO_CODECS + DECODER_CODECS + DECODER_VIDEO_CODECS)) 

240 

241 

242def get_rgb_compression_options(): 

243 from xpra.net import compression 

244 compressors = compression.get_enabled_compressors() 

245 compressors = [x for x in compressors if x!="brotli"] 

246 RGB_COMP_OPTIONS = ["Raw RGB"] 

247 if compressors: 

248 RGB_COMP_OPTIONS += ["/".join(compressors)] 

249 return RGB_COMP_OPTIONS 

250 

251def get_encoding_name(encoding): 

252 ENCODINGS_TO_NAME = { 

253 "auto" : "automatic", 

254 "h264" : "H.264", 

255 "h265" : "H.265", 

256 "mpeg4" : "MPEG4", 

257 "vp8" : "VP8", 

258 "webp" : "WebP", 

259 "vp9" : "VP9", 

260 "png" : "PNG (24/32bpp)", 

261 "png/P" : "PNG (8bpp colour)", 

262 "png/L" : "PNG (8bpp grayscale)", 

263 "jpeg" : "JPEG", 

264 "rgb" : " + ".join(get_rgb_compression_options()) + " (24/32bpp)", 

265 } 

266 return ENCODINGS_TO_NAME.get(encoding, encoding) 

267 

268def get_encoding_help(encoding): 

269 from xpra.net import compression 

270 compressors = compression.get_enabled_compressors() 

271 compressors = [x for x in compressors if x!="brotli"] 

272 return { 

273 "auto" : "automatic mode (recommended)", 

274 "grayscale" : "same as 'auto' but in grayscale mode", 

275 "h264" : "H.264 video codec", 

276 "h265" : "H.265 (HEVC) video codec (not recommended)", 

277 "vp8" : "VP8 video codec", 

278 "vp9" : "VP9 video codec", 

279 "mpeg4" : "MPEG-4 video codec", 

280 "png" : "Portable Network Graphics (lossless, 24bpp or 32bpp for transparency)", 

281 "png/P" : "Portable Network Graphics (lossy, 8bpp colour)", 

282 "png/L" : "Portable Network Graphics (lossy, 8bpp grayscale)", 

283 "webp" : "WebP compression (supports lossless and lossy modes)", 

284 "jpeg" : "JPEG lossy compression", 

285 "rgb" : "Raw RGB pixels, lossless," 

286 +" compressed using %s (24bpp or 32bpp for transparency)" % (" or ".join(compressors)), 

287 "scroll" : "motion vectors, supplemented with picture codecs", 

288 }.get(encoding) 

289 

290 

291def encodings_help(encodings): 

292 from xpra.codecs.codec_constants import HELP_ORDER 

293 h = [] 

294 for e in HELP_ORDER: 

295 if e in encodings: 

296 h.append(encoding_help(e)) 

297 return h 

298 

299def encoding_help(encoding): 

300 ehelp = get_encoding_help(encoding) or "" 

301 return encoding.ljust(12) + ehelp 

302 

303 

304 

305def main(): 

306 from xpra.platform import program_context 

307 from xpra.log import enable_color, LOG_FORMAT, NOPREFIX_FORMAT 

308 from xpra.util import print_nested_dict, pver 

309 with program_context("Loader", "Encoding Info"): 

310 verbose = "-v" in sys.argv or "--verbose" in sys.argv 

311 format_string = NOPREFIX_FORMAT 

312 if verbose: 

313 format_string = LOG_FORMAT 

314 log.enable_debug() 

315 enable_color(format_string) 

316 

317 load_codecs() 

318 #not really a codec, but gets used by codecs, so include version info: 

319 add_codec_version("numpy", "numpy") 

320 print("codecs and csc modules found:") 

321 #print("codec_status=%s" % codecs) 

322 for name in sorted(ALL_CODECS): 

323 mod = codecs.get(name, "") 

324 f = mod 

325 if mod and hasattr(mod, "__file__"): 

326 f = mod.__file__ 

327 if f.startswith(os.getcwd()): 

328 f = f[len(os.getcwd()):] 

329 if f.startswith(os.path.sep): 

330 f = f[1:] 

331 print("* %s : %s" % (name.ljust(20), f)) 

332 if mod and verbose: 

333 try: 

334 if name.find("csc")>=0: 

335 cs = list(mod.get_input_colorspaces()) 

336 for c in list(cs): 

337 cs += list(mod.get_output_colorspaces(c)) 

338 print(" colorspaces: %s" % csv(list(set(cs)))) 

339 elif name.find("enc")>=0 or name.find("dec")>=0: 

340 encodings = mod.get_encodings() 

341 print(" encodings: %s" % csv(encodings)) 

342 try: 

343 i = mod.get_info() 

344 for k,v in sorted(i.items()): 

345 print(" %s = %s" % (k,v)) 

346 except Exception: 

347 pass 

348 except Exception as e: 

349 print("error getting extra information on %s: %s" % (name, e)) 

350 print("") 

351 print("codecs versions:") 

352 def forcever(v): 

353 return pver(v, numsep=".", strsep=".").lstrip("v") 

354 print_nested_dict(codec_versions, vformat=forcever) 

355 return 0 

356 

357 

358if __name__ == "__main__": 

359 main()