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# -*- 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 

7 

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 

14 

15log = Logger("encoding") 

16 

17 

18""" 

19Mixin for adding encodings to a server 

20""" 

21class EncodingServer(StubServerMixin): 

22 

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 

35 

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) 

46 

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() 

52 

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() 

66 

67 def cleanup(self): 

68 getVideoHelper().cleanup() 

69 

70 

71 def get_server_features(self, _source=None): 

72 return { 

73 "auto-video-encoding" : True, #from v4.0, clients assume this is available 

74 } 

75 

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 

84 

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 } 

102 

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) 

118 

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") 

124 

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) 

171 

172 

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, {}) 

194 

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) 

202 

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) 

210 

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) 

218 

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) 

226 

227 

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) 

233 

234 

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 })