Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/mixins/encoding_server.py : 71%
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# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2010-2020 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#pylint: disable-msg=E1101
8from xpra.scripts.config import parse_bool_or_int
9from xpra.codecs.codec_constants import PREFERRED_ENCODING_ORDER, PROBLEMATIC_ENCODINGS
10from xpra.codecs.loader import get_codec, has_codec, codec_versions, load_codec
11from xpra.codecs.video_helper import getVideoHelper
12from xpra.server.mixins.stub_server_mixin import StubServerMixin
13from xpra.log import Logger
15log = Logger("encoding")
18"""
19Mixin for adding encodings to a server
20"""
21class EncodingServer(StubServerMixin):
23 def __init__(self):
24 self.default_quality = -1
25 self.default_min_quality = 0
26 self.default_speed = -1
27 self.default_min_speed = 0
28 self.allowed_encodings = None
29 self.core_encodings = []
30 self.encodings = []
31 self.lossless_encodings = []
32 self.lossless_mode_encodings = []
33 self.default_encoding = None
34 self.scaling_control = None
36 def init(self, opts):
37 self.encoding = opts.encoding
38 self.allowed_encodings = opts.encodings
39 self.default_quality = opts.quality
40 self.default_min_quality = opts.min_quality
41 self.default_speed = opts.speed
42 self.default_min_speed = opts.min_speed
43 if opts.video_scaling.lower() not in ("auto", "on"):
44 self.scaling_control = parse_bool_or_int("video-scaling", opts.video_scaling)
45 getVideoHelper().set_modules(video_encoders=opts.video_encoders, csc_modules=opts.csc_modules)
47 def setup(self):
48 #always load pillow early,
49 #so we have png and jpeg support before calling threaded_setup
50 load_codec("enc_pillow")
51 self.init_encodings()
53 def threaded_setup(self):
54 #load video codecs:
55 getVideoHelper().init()
56 #and load the picture codecs:
57 load_codec("enc_pillow")
58 ae = self.allowed_encodings
59 if "jpeg" in ae:
60 #try to load the fast jpeg encoder:
61 load_codec("enc_jpeg")
62 if "webp" in ae:
63 #try to load the fast webp encoder:
64 load_codec("enc_webp")
65 self.init_encodings()
67 def cleanup(self):
68 getVideoHelper().cleanup()
71 def get_server_features(self, _source=None):
72 return {
73 "auto-video-encoding" : True, #from v4.0, clients assume this is available
74 }
76 def get_info(self, _proto) -> dict:
77 info = {
78 "encodings" : self.get_encoding_info(),
79 "video" : getVideoHelper().get_info(),
80 }
81 for k,v in codec_versions.items():
82 info.setdefault("encoding", {}).setdefault(k, {})["version"] = v
83 return info
85 def get_encoding_info(self) -> dict:
86 return {
87 "" : self.encodings,
88 "core" : self.core_encodings,
89 "allowed" : self.allowed_encodings,
90 "lossless" : self.lossless_encodings,
91 "problematic" : [x for x in self.core_encodings if x in PROBLEMATIC_ENCODINGS],
92 "with_speed" : tuple(set({"rgb32" : "rgb", "rgb24" : "rgb"}.get(x, x)
93 for x in self.core_encodings if x in (
94 "h264", "vp8", "vp9",
95 "rgb24", "rgb32",
96 "png", "png/P", "png/L", "webp",
97 "scroll",
98 ))),
99 "with_quality" : [x for x in self.core_encodings if x in ("jpeg", "webp", "h264", "vp8", "vp9", "scroll")],
100 "with_lossless_mode" : self.lossless_mode_encodings,
101 }
103 def init_encodings(self):
104 encs, core_encs = [], []
105 log("init_encodings() allowed_encodings=%s", self.allowed_encodings)
106 def add_encodings(encodings):
107 log("add_encodings(%s)", encodings)
108 for ce in encodings:
109 e = {"rgb32" : "rgb", "rgb24" : "rgb"}.get(ce, ce)
110 if self.allowed_encodings is not None:
111 if e not in self.allowed_encodings and ce not in self.allowed_encodings:
112 #not in whitelist (if it exists)
113 continue
114 if e not in encs:
115 encs.append(e)
116 if ce not in core_encs:
117 core_encs.append(ce)
119 add_encodings(["rgb24", "rgb32", "scroll"])
120 if "scroll" in self.allowed_encodings and "scroll" not in self.lossless_mode_encodings:
121 #scroll is lossless, but it also uses other picture codecs
122 #and those allow changes in quality
123 self.lossless_mode_encodings.append("scroll")
125 #video encoders (empty when first called - see threaded_init)
126 ve = getVideoHelper().get_encodings()
127 log("init_encodings() adding video encodings: %s", ve)
128 add_encodings(ve) #ie: ["vp8", "h264"]
129 #Pithon Imaging Libary:
130 enc_pillow = get_codec("enc_pillow")
131 log("enc_pillow=%s", enc_pillow)
132 if enc_pillow:
133 pil_encs = enc_pillow.get_encodings()
134 log("pillow encodings: %s", pil_encs)
135 add_encodings(x for x in pil_encs if x!="webp")
136 #Note: webp will only be enabled if we have a Python-PIL fallback
137 #(either "webp" or "png")
138 if has_codec("enc_webp") and ("webp" in pil_encs or "png" in pil_encs):
139 add_encodings(["webp"])
140 if "webp" not in self.lossless_mode_encodings:
141 self.lossless_mode_encodings.append("webp")
142 #look for video encodings with lossless mode:
143 for e in ve:
144 for colorspace,especs in getVideoHelper().get_encoder_specs(e).items():
145 for espec in especs:
146 if espec.has_lossless_mode:
147 if e not in self.lossless_mode_encodings:
148 log("found lossless mode for encoding %s with %s and colorspace %s", e, espec, colorspace)
149 self.lossless_mode_encodings.append(e)
150 break
151 #now update the variables:
152 encs.append("grayscale")
153 self.encodings = encs
154 self.core_encodings = core_encs
155 self.lossless_encodings = [x for x in self.core_encodings
156 if (x.startswith("png") or x.startswith("rgb") or x=="webp")]
157 log("allowed encodings=%s, encodings=%s, core encodings=%s, lossless encodings=%s",
158 self.allowed_encodings, encs, core_encs, self.lossless_encodings)
159 pref = [x for x in PREFERRED_ENCODING_ORDER if x in self.encodings]
160 if pref:
161 self.default_encoding = pref[0]
162 else:
163 self.default_encoding = None
164 #default encoding:
165 if not self.encoding or str(self.encoding).lower() in ("auto", "none"):
166 self.default_encoding = None
167 elif self.encoding in self.encodings:
168 self.default_encoding = self.encoding
169 else:
170 log.warn("ignored invalid default encoding option: %s", self.encoding)
173 def _process_encoding(self, proto, packet):
174 encoding = packet[1].decode("latin1")
175 ss = self.get_server_source(proto)
176 if ss is None:
177 return
178 if len(packet)>=3:
179 #client specified which windows this is for:
180 in_wids = packet[2]
181 wids = []
182 wid_windows = {}
183 for wid in in_wids:
184 if wid not in self._id_to_window:
185 continue
186 wids.append(wid)
187 wid_windows[wid] = self._id_to_window.get(wid)
188 else:
189 #apply to all windows:
190 wids = None
191 wid_windows = self._id_to_window
192 ss.set_encoding(encoding, wids)
193 self._refresh_windows(proto, wid_windows, {})
195 def _process_quality(self, proto, packet):
196 quality = packet[1]
197 log("Setting quality to %s", quality)
198 ss = self.get_server_source(proto)
199 if ss:
200 ss.set_quality(quality)
201 self.call_idle_refresh_all_windows(proto)
203 def _process_min_quality(self, proto, packet):
204 min_quality = packet[1]
205 log("Setting min quality to %s", min_quality)
206 ss = self.get_server_source(proto)
207 if ss:
208 ss.set_min_quality(min_quality)
209 self.call_idle_refresh_all_windows(proto)
211 def _process_speed(self, proto, packet):
212 speed = packet[1]
213 log("Setting speed to ", speed)
214 ss = self.get_server_source(proto)
215 if ss:
216 ss.set_speed(speed)
217 self.call_idle_refresh_all_windows(proto)
219 def _process_min_speed(self, proto, packet):
220 min_speed = packet[1]
221 log("Setting min speed to ", min_speed)
222 ss = self.get_server_source(proto)
223 if ss:
224 ss.set_min_speed(min_speed)
225 self.call_idle_refresh_all_windows(proto)
228 def call_idle_refresh_all_windows(self, proto):
229 #we can't assume that the window server mixin is loaded:
230 refresh = getattr(self, "_idle_refresh_all_windows", None)
231 if refresh:
232 refresh(proto)
235 def init_packet_handlers(self):
236 self.add_packet_handlers({
237 "quality" : self._process_quality,
238 "min-quality" : self._process_min_quality,
239 "speed" : self._process_speed,
240 "min-speed" : self._process_min_speed,
241 "encoding" : self._process_encoding,
242 })