def paint_image(self, coding, img_data, x, y, width, height, options, callbacks): """ can be called from any thread """ if USE_PIL and has_codec("PIL"): return GTKWindowBacking.paint_image(self, coding, img_data, x, y, width, height, options, callbacks) # gdk needs UI thread: gobject.idle_add(self.paint_pixbuf_gdk, coding, img_data, x, y, width, height, options, callbacks) return False
def do_get_core_encodings(self): """ This method returns the actual encodings supported. ie: ["rgb24", "vp8", "webp", "png", "png/L", "png/P", "jpeg", "jpeg2000", "h264", "vpx"] It is often overriden in the actual client class implementations, where extra encodings can be added (generally just 'rgb32' for transparency), or removed if the toolkit implementation class is more limited. """ #we always support rgb: core_encodings = ["rgb24", "rgb32"] for codec in ("dec_pillow", "dec_webp"): if has_codec(codec): c = get_codec(codec) for e in c.get_encodings(): if e not in core_encodings: core_encodings.append(e) #we enable all the video decoders we know about, #what will actually get used by the server will still depend on the csc modes supported video_decodings = getVideoHelper().get_decodings() log("video_decodings=%s", video_decodings) for encoding in video_decodings: if encoding not in core_encodings: core_encodings.append(encoding) #remove duplicates and use prefered encoding order: core_encodings = [ x for x in PREFERED_ENCODING_ORDER if x in set(core_encodings) and x in self.allowed_encodings ] return core_encodings
def paint_image(self, coding, img_data, x, y, width, height, options, callbacks): """ can be called from any thread """ if USE_PIL and has_codec("dec_pillow"): return GTKWindowBacking.paint_image(self, coding, img_data, x, y, width, height, options, callbacks) #gdk needs UI thread: self.idle_add(self.paint_pixbuf_gdk, coding, img_data, x, y, width, height, options, callbacks) return False
def webm_encode(image, quality): assert enc_webm and webp_handlers, "webp components are missing" BitmapHandler = webp_handlers.BitmapHandler handler_encs = { "RGB" : (BitmapHandler.RGB, "EncodeRGB", "EncodeLosslessRGB", False), "BGR" : (BitmapHandler.BGR, "EncodeBGR", "EncodeLosslessBGR", False), "RGBA": (BitmapHandler.RGBA, "EncodeRGBA", "EncodeLosslessRGBA", True), "RGBX": (BitmapHandler.RGBA, "EncodeRGBA", "EncodeLosslessRGBA", False), "BGRA": (BitmapHandler.BGRA, "EncodeBGRA", "EncodeLosslessBGRA", True), "BGRX": (BitmapHandler.BGRA, "EncodeBGRA", "EncodeLosslessBGRA", False), } pixel_format = image.get_pixel_format() h_e = handler_encs.get(pixel_format) assert h_e is not None, "cannot handle rgb format %s with webp!" % pixel_format bh, lossy_enc, lossless_enc, has_alpha = h_e q = max(1, quality) enc = None if q>=100 and has_codec("enc_webm_lossless"): enc = getattr(enc_webm, lossless_enc) kwargs = {} client_options = {} log("webm_encode(%s, %s) using lossless encoder=%s for %s", image, enc, pixel_format) if enc is None: enc = getattr(enc_webm, lossy_enc) kwargs = {"quality" : q} client_options = {"quality" : q} log("webm_encode(%s, %s) using lossy encoder=%s with quality=%s for %s", image, enc, q, pixel_format) handler = BitmapHandler(image.get_pixels(), bh, image.get_width(), image.get_height(), image.get_rowstride()) bpp = 24 if has_alpha: client_options["has_alpha"] = True bpp = 32 return "webp", Compressed("webp", str(enc(handler, **kwargs).data)), client_options, image.get_width(), image.get_height(), 0, bpp
def paint_image(self, coding, img_data, x, y, width, height, options, callbacks): """ can be called from any thread """ use_PIL = has_codec("PIL") and os.environ.get("XPRA_USE_PIL", "1")=="1" if use_PIL: return GTKWindowBacking.paint_image(self, coding, img_data, x, y, width, height, options, callbacks) #gdk needs UI thread: gobject.idle_add(self.paint_pixbuf_gdk, coding, img_data, x, y, width, height, options, callbacks) return False
def init_encodings(self): load_codecs(decoders=False) encs, core_encs = [], [] def add_encodings(encodings): for ce in encodings: e = {"rgb32" : "rgb", "rgb24" : "rgb"}.get(ce, ce) if self.allowed_encodings is not None and e not in self.allowed_encodings: #not in whitelist (if it exists) continue if e not in encs: encs.append(e) if ce not in core_encs: core_encs.append(ce) add_encodings(["rgb24", "rgb32"]) #video encoders (empty when first called - see threaded_init) ve = getVideoHelper().get_encodings() log("init_encodings() adding video encodings: %s", ve) add_encodings(ve) #ie: ["vp8", "h264"] #Pithon Imaging Libary: enc_pillow = get_codec("enc_pillow") if enc_pillow: pil_encs = enc_pillow.get_encodings() add_encodings(x for x in pil_encs if x!="webp") #Note: webp will only be enabled if we have a Python-PIL fallback #(either "webp" or "png") if has_codec("enc_webp") and ("webp" in pil_encs or "png" in pil_encs): add_encodings(["webp"]) if "webp" not in self.lossless_mode_encodings: self.lossless_mode_encodings.append("webp") #look for video encodings with lossless mode: for e in ve: for colorspace,especs in getVideoHelper().get_encoder_specs(e).items(): for espec in especs: if espec.has_lossless_mode: if e not in self.lossless_mode_encodings: log("found lossless mode for encoding %s with %s and colorspace %s", e, espec, colorspace) self.lossless_mode_encodings.append(e) break #now update the variables: self.encodings = encs self.core_encodings = core_encs self.lossless_encodings = [x for x in self.core_encodings if (x.startswith("png") or x.startswith("rgb") or x=="webp")] log("allowed encodings=%s, encodings=%s, core encodings=%s, lossless encodings=%s", self.allowed_encodings, encs, core_encs, self.lossless_encodings) pref = [x for x in PREFERED_ENCODING_ORDER if x in self.encodings] if pref: self.default_encoding = pref[0] else: self.default_encoding = None #default encoding: if not self.encoding or str(self.encoding).lower() in ("auto", "none"): self.default_encoding = None elif self.encoding in self.encodings: self.default_encoding = self.encoding else: log.warn("ignored invalid default encoding option: %s", self.encoding)
def get_encodings_caps(self): if B_FRAMES: video_b_frames = ["h264"] #only tested with dec_avcodec2 else: video_b_frames = [] caps = { "flush": PAINT_FLUSH, "scaling.control": self.video_scaling, "client_options": True, "csc_atoms": True, #TODO: check for csc support (swscale only?) "video_reinit": True, "video_scaling": True, "video_b_frames": video_b_frames, "webp_leaks": False, "transparency": self.has_transparency(), "rgb24zlib": True, "max-soft-expired": MAX_SOFT_EXPIRED, "send-timestamps": SEND_TIMESTAMPS, "supports_delta": tuple(x for x in ("png", "rgb24", "rgb32") if x in self.get_core_encodings()), } if self.encoding: caps[""] = self.encoding for k, v in codec_versions.items(): caps["%s.version" % k] = v if self.quality > 0: caps["quality"] = self.quality if self.min_quality > 0: caps["min-quality"] = self.min_quality if self.speed >= 0: caps["speed"] = self.speed if self.min_speed >= 0: caps["min-speed"] = self.min_speed #generic rgb compression flags: for x in compression.ALL_COMPRESSORS: caps["rgb_%s" % x] = x in compression.get_enabled_compressors() #these are the defaults - when we instantiate a window, #we can send different values as part of the map event #these are the RGB modes we want (the ones we are expected to be able to paint with): rgb_formats = ["RGB", "RGBX", "RGBA"] caps["rgb_formats"] = rgb_formats #figure out which CSC modes (usually YUV) can give us those RGB modes: full_csc_modes = getVideoHelper().get_server_full_csc_modes_for_rgb( *rgb_formats) if has_codec("dec_webp"): if self.opengl_enabled: full_csc_modes["webp"] = ("BGRX", "BGRA", "RGBX", "RGBA") else: full_csc_modes["webp"] = ( "BGRX", "BGRA", ) log("supported full csc_modes=%s", full_csc_modes) caps["full_csc_modes"] = full_csc_modes if "h264" in self.get_core_encodings(): # some profile options: "baseline", "main", "high", "high10", ... # set the default to "high10" for I420/YUV420P # as the python client always supports all the profiles # whereas on the server side, the default is baseline to accomodate less capable clients. # I422/YUV422P requires high422, and # I444/YUV444P requires high444, # so we don't bother specifying anything for those two. for old_csc_name, csc_name, default_profile in (("I420", "YUV420P", "high10"), ("I422", "YUV422P", ""), ("I444", "YUV444P", "")): profile = default_profile #try with the old prefix (X264) as well as the more correct one (H264): for H264_NAME in ("X264", "H264"): profile = os.environ.get( "XPRA_%s_%s_PROFILE" % (H264_NAME, old_csc_name), profile) profile = os.environ.get( "XPRA_%s_%s_PROFILE" % (H264_NAME, csc_name), profile) if profile: #send as both old and new names: for h264_name in ("x264", "h264"): caps["%s.%s.profile" % (h264_name, old_csc_name)] = profile caps["%s.%s.profile" % (h264_name, csc_name)] = profile log( "x264 encoding options: %s", str([(k, v) for k, v in caps.items() if k.startswith("x264.")])) log("encoding capabilities: %s", caps) return caps
def get_encodings_caps(self) -> dict: if B_FRAMES: video_b_frames = ("h264", ) #only tested with dec_avcodec2 else: video_b_frames = () caps = { "flush" : PAINT_FLUSH, #v4 servers assume this is available "video_scaling" : True, #v4 servers assume this is available "video_b_frames" : video_b_frames, "video_max_size" : self.video_max_size, "max-soft-expired" : MAX_SOFT_EXPIRED, "send-timestamps" : SEND_TIMESTAMPS, } if self.video_scaling is not None: caps["scaling.control"] = self.video_scaling if self.encoding: caps[""] = self.encoding for k,v in codec_versions.items(): caps["%s.version" % k] = v if self.quality>0: caps["quality"] = self.quality if self.min_quality>0: caps["min-quality"] = self.min_quality if self.speed>=0: caps["speed"] = self.speed if self.min_speed>=0: caps["min-speed"] = self.min_speed #generic rgb compression flags: for x in compression.ALL_COMPRESSORS: caps["rgb_%s" % x] = x in compression.get_enabled_compressors() #these are the defaults - when we instantiate a window, #we can send different values as part of the map event #these are the RGB modes we want (the ones we are expected to be able to paint with): rgb_formats = ["RGB", "RGBX", "RGBA"] caps["rgb_formats"] = rgb_formats #figure out which CSC modes (usually YUV) can give us those RGB modes: full_csc_modes = getVideoHelper().get_server_full_csc_modes_for_rgb(*rgb_formats) if has_codec("dec_webp"): if self.opengl_enabled: full_csc_modes["webp"] = ("BGRX", "BGRA", "RGBX", "RGBA") else: full_csc_modes["webp"] = ("BGRX", "BGRA", ) if has_codec("dec_jpeg") or has_codec("dec_pillow"): full_csc_modes["jpeg"] = ("BGRX", "BGRA", "YUV420P") if has_codec("dec_jpeg"): full_csc_modes["jpega"] = ("BGRA", "RGBA", ) log("supported full csc_modes=%s", full_csc_modes) caps["full_csc_modes"] = full_csc_modes if "h264" in self.get_core_encodings(): # some profile options: "baseline", "main", "high", "high10", ... # set the default to "high10" for YUV420P # as the python client always supports all the profiles # whereas on the server side, the default is baseline to accomodate less capable clients. # YUV422P requires high422, and # YUV444P requires high444, # so we don't bother specifying anything for those two. h264_caps = {} for csc_name, default_profile in ( ("YUV420P", "high"), ("YUV422P", ""), ("YUV444P", "")): profile = os.environ.get("XPRA_H264_%s_PROFILE" % (csc_name), default_profile) if profile: h264_caps["%s.profile" % (csc_name)] = profile h264_caps["fast-decode"] = envbool("XPRA_X264_FAST_DECODE", False) log("x264 encoding options: %s", h264_caps) updict(caps, "h264", h264_caps) log("encoding capabilities: %s", caps) return caps
def init_encodings(self): encs, core_encs = [], [] log("init_encodings() allowed_encodings=%s", self.allowed_encodings) def add_encoding(encoding): log("add_encoding(%s)", encoding) e = {"rgb32": "rgb", "rgb24": "rgb"}.get(encoding, encoding) if self.allowed_encodings is not None: if e not in self.allowed_encodings and encoding not in self.allowed_encodings: #not in whitelist (if it exists) return if e not in encs: encs.append(e) if encoding not in core_encs: core_encs.append(encoding) def add_encodings(*encodings): log("add_encodings%s", encodings) for encoding in encodings: add_encoding(encoding) add_encodings("rgb24", "rgb32", "scroll") lossless = [] if "scroll" in self.allowed_encodings and "scroll" not in self.lossless_mode_encodings: #scroll is lossless, but it also uses other picture codecs #and those allow changes in quality lossless.append("scroll") #video encoders (empty when first called - see threaded_init) ve = getVideoHelper().get_encodings() log("init_encodings() adding video encodings: %s", ve) add_encodings(*ve) #ie: ["vp8", "h264"] #Pithon Imaging Libary: enc_pillow = get_codec("enc_pillow") log("enc_pillow=%s", enc_pillow) if enc_pillow: pil_encs = enc_pillow.get_encodings() log("pillow encodings: %s", pil_encs) for encoding in pil_encs: if encoding != "webp": add_encoding(encoding) #Note: webp will only be enabled if we have a Python-PIL fallback #(either "webp" or "png") if has_codec("enc_webp") and ("webp" in pil_encs or "png" in pil_encs): add_encodings("webp") if "webp" not in lossless: lossless.append("webp") for codec_name in ("avif", "enc_jpeg", "enc_nvjpeg"): codec = get_codec(codec_name) if codec: add_encodings(*codec.get_encodings()) #look for video encodings with lossless mode: for e in ve: for colorspace, especs in getVideoHelper().get_encoder_specs( e).items(): for espec in especs: if espec.has_lossless_mode: if e not in lossless: log( "found lossless mode for encoding %s with %s and colorspace %s", e, espec, colorspace) lossless.append(e) break #now update the variables: encs.append("grayscale") self.encodings = encs self.core_encodings = tuple(core_encs) self.lossless_mode_encodings = tuple(lossless) self.lossless_encodings = tuple( x for x in self.core_encodings if (x.startswith("png") or x.startswith("rgb") or x == "webp")) log( "allowed encodings=%s, encodings=%s, core encodings=%s, lossless encodings=%s", self.allowed_encodings, encs, core_encs, self.lossless_encodings) pref = [x for x in PREFERRED_ENCODING_ORDER if x in self.encodings] if pref: self.default_encoding = pref[0] else: self.default_encoding = None #default encoding: if not self.encoding or str(self.encoding).lower() in ("auto", "none"): self.default_encoding = None elif self.encoding in self.encodings: self.default_encoding = self.encoding else: log.warn("ignored invalid default encoding option: %s", self.encoding)
def parse_encoding_caps(self, c): self.set_encoding(c.strget("encoding", None), None) #encoding options (filter): #1: these properties are special cased here because we #defined their name before the "encoding." prefix convention, #or because we want to pass default values (zlib/lz4): for k,ek in {"initial_quality" : "initial_quality", "quality" : "quality", }.items(): if k in c: self.encoding_options[ek] = c.intget(k) for k,ek in {"zlib" : "rgb_zlib", "lz4" : "rgb_lz4", }.items(): if k in c: self.encoding_options[ek] = c.boolget(k) #2: standardized encoding options: for k in c.keys(): #yaml gives us str.. k = bytestostr(k) if k.startswith("theme.") or k.startswith("encoding.icons."): self.icons_encoding_options[k.replace("encoding.icons.", "").replace("theme.", "")] = c.get(k) elif k.startswith("encoding."): stripped_k = k[len("encoding."):] if stripped_k in ("transparency", "rgb_zlib", "rgb_lz4", ): v = c.boolget(k) elif stripped_k in ("initial_quality", "initial_speed", "min-quality", "quality", "min-speed", "speed"): v = c.intget(k) else: v = c.get(k) self.encoding_options[stripped_k] = v log("encoding options: %s", self.encoding_options) log("icons encoding options: %s", self.icons_encoding_options) #handle proxy video: add proxy codec to video helper: pv = self.encoding_options.boolget("proxy.video") proxylog("proxy.video=%s", pv) if pv: #enabling video proxy: try: self.parse_proxy_video() except Exception: proxylog.error("failed to parse proxy video", exc_info=True) sc = self.encoding_options.get("scaling.control", self.scaling_control) if sc is not None: #"encoding_options" are exposed via "xpra info", #so we can't have None values in there (bencoder would choke) self.default_encoding_options["scaling.control"] = sc q = self.encoding_options.intget("quality", self.default_quality) #0.7 onwards: if q>0: self.default_encoding_options["quality"] = q mq = self.encoding_options.intget("min-quality", self.default_min_quality) if mq>0 and (q<=0 or q>mq): self.default_encoding_options["min-quality"] = mq s = self.encoding_options.intget("speed", self.default_speed) if s>0: self.default_encoding_options["speed"] = s ms = self.encoding_options.intget("min-speed", self.default_min_speed) if ms>0 and (s<=0 or s>ms): self.default_encoding_options["min-speed"] = ms log("default encoding options: %s", self.default_encoding_options) self.auto_refresh_delay = c.intget("auto_refresh_delay", 0) #are we going to need a cuda context? if getattr(self, "mmap_enabled", False): #not with mmap! return common_encodings = tuple(x for x in self.encodings if x in self.server_encodings) from xpra.codecs.loader import has_codec if (has_codec("nvenc") and "h264" in self.core_encodings or "h265" in self.core_encodings) \ or ("jpeg" in common_encodings and has_codec("enc_nvjpeg")): cudalog = Logger("cuda") try: from xpra.codecs.cuda_common.cuda_context import get_device_context self.cuda_device_context = get_device_context(self.encoding_options) cudalog("cuda_device_context=%s", self.cuda_device_context) except Exception as e: cudalog("failed to get a cuda device context using encoding options %s", self.encoding_options, exc_info=True) cudalog.error("Error: failed to allocate a CUDA context:") cudalog.error(" %s", e) cudalog.error(" NVJPEG and NVENC will not be available")