Coverage for lib/lottie/exporters/tgs_validator.py: 39%

75 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-20 16:17 +0100

1import os 

2import enum 

3import json 

4import inspect 

5 

6from ..parsers.tgs import parse_tgs 

7from ..objects.base import ObjectVisitor 

8from ..objects.animation import Animation 

9from ..objects import layers 

10from ..objects import shapes 

11from ..objects import helpers 

12 

13 

14class Severity(enum.Enum): 

15 Note = enum.auto() 

16 Warning = enum.auto() 

17 Error = enum.auto() 

18 

19 

20class TgsError: 

21 def __init__(self, message, target, severity=Severity.Warning): 

22 self.message = message 

23 self.target = target 

24 self.severity = severity 

25 

26 def target_id(self): 

27 if isinstance(self.target, str): 

28 return self.target 

29 if getattr(self.target, "name", ""): 

30 return self.target.name 

31 return self.target.__class__.__name__ 

32 

33 def __str__(self): 

34 return "%s: on %s: %s" % ( 

35 self.severity.name, 

36 self.target_id(), 

37 self.message 

38 ) 

39 

40 

41class TgsValidator(ObjectVisitor): 

42 def __init__(self, severity=Severity.Note): 

43 self.errors = [] 

44 self.severity = severity 

45 

46 def _check(self, expr, message, target, severity=Severity.Warning): 

47 if severity.value >= self.severity.value and not expr: 

48 self.errors.append(TgsError(message, target, severity)) 

49 

50 def check_file_size(self, filename): 

51 return self.check_size(os.path.getsize(filename)) 

52 

53 def check_size(self, bytes, filename="file"): 

54 size_k = bytes / 1024 

55 self._check( 

56 size_k <= 64, 

57 "Invalid size (%.1fk), should be less than 64k" % size_k, 

58 filename, 

59 Severity.Error 

60 ) 

61 

62 def check_file(self, filename): 

63 self.check_file_size(filename) 

64 try: 

65 self(parse_tgs(filename)) 

66 except json.decoder.JSONDecodeError as e: 

67 self._check( 

68 False, 

69 "Invalid JSON: %s" % e, 

70 filename, 

71 Severity.Error 

72 ) 

73 

74 def visit(self, object): 

75 for cls in inspect.getmro(object.__class__): 

76 callback = "_visit_%s" % cls.__name__.lower() 

77 if hasattr(self, callback): 

78 getattr(self, callback)(object) 

79 

80 def _visit_animation(self, o: Animation): 

81 self._check( 

82 o.frame_rate in {30, 60}, 

83 "Invalid framerate %s, should be 30 or 60" % o.frame_rate, 

84 o, 

85 Severity.Error 

86 ) 

87 self._check( 

88 o.width == 512, 

89 "Invalid width %s, should be 512" % o.width, 

90 o, 

91 Severity.Error 

92 ) 

93 self._check( 

94 o.height == 512, 

95 "Invalid height %s, should be 512" % o.height, 

96 o, 

97 Severity.Error 

98 ) 

99 self._check( 

100 (o.out_point-o.in_point) <= 180, 

101 "Too many frames (%s), should be less than 180" % (o.out_point-o.in_point), 

102 o, 

103 Severity.Error 

104 ) 

105 

106 def _visit_layer(self, o: layers.Layer): 

107 self._check( 

108 not o.has_masks and not o.masks, 

109 "Masks are not officially supported", 

110 o, 

111 Severity.Note 

112 ) 

113 self._check( 

114 not o.effects, 

115 "Effects are not supported", 

116 o, 

117 Severity.Warning 

118 ) 

119 self._check( 

120 not o.threedimensional, 

121 "3D layers are not supported", 

122 o, 

123 Severity.Warning 

124 ) 

125 self._check( 

126 not isinstance(o, layers.TextLayer), 

127 "Text layers are not supported", 

128 o, 

129 Severity.Warning 

130 ) 

131 self._check( 

132 not isinstance(o, layers.ImageLayer), 

133 "Image layers are not supported", 

134 o, 

135 Severity.Warning 

136 ) 

137 self._check( 

138 not o.auto_orient, 

139 "Auto-orient layers are not supported", 

140 o, 

141 Severity.Warning 

142 ) 

143 self._check( 

144 o.matte_mode in {None, layers.MatteMode.Normal}, 

145 "Mattes are not supported", 

146 o, 

147 Severity.Warning 

148 ) 

149 

150 def _visit_precomplayer(self, o: layers.PreCompLayer): 

151 self._check( 

152 o.time_remapping is None, 

153 "Time remapping is not supported", 

154 o, 

155 Severity.Warning 

156 ) 

157 

158 def _visit_merge(self, o: shapes.Merge): 

159 self._check( 

160 False, 

161 "Merge paths are not supported", 

162 o, 

163 Severity.Warning 

164 ) 

165 

166 def _visit_transform(self, o: helpers.Transform): 

167 self._check( 

168 o.skew is None or ( 

169 not o.skew.animated and o.skew.value == 0 

170 ), 

171 "Skew transforms are not supported", 

172 o, 

173 Severity.Warning 

174 ) 

175 

176 def _visit_gradientstroke(self, o: shapes.GradientStroke): 

177 self._check( 

178 False, 

179 "Gradient strokes are not officially supported", 

180 o, 

181 Severity.Note 

182 ) 

183 

184 def _visit_star(self, o: shapes.Star): 

185 self._check( 

186 False, 

187 "Star Shapes are not officially supported", 

188 o, 

189 Severity.Note 

190 ) 

191 

192 def _visit_repeater(self, o: shapes.Repeater): 

193 self._check( 

194 False, 

195 "Repeaters are not officially supported", 

196 o, 

197 Severity.Note 

198 )