Coverage for lib/lottie/objects/assets.py: 27%

114 statements  

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

1import os 

2import re 

3import base64 

4import mimetypes 

5from io import BytesIO 

6from .base import LottieProp, PseudoBool, Index 

7from .layers import Layer 

8from .composition import Composition 

9from .helpers import VisualObject 

10 

11 

12## @ingroup Lottie 

13class Asset(VisualObject): 

14 _props = [ 

15 LottieProp("id", "id", str, False), 

16 ] 

17 

18 @classmethod 

19 def _load_get_class(cls, lottiedict): 

20 if lottiedict.get("t", None) == 3: 

21 return DataSource 

22 if "p" in lottiedict or "u" in lottiedict: 

23 if "w" in lottiedict: 

24 return Image 

25 return Sound 

26 if "layers" in lottiedict: 

27 return Precomp 

28 

29 

30#ingroup Lottie 

31class FileAsset(Asset): 

32 """! 

33 Asset referencing a file 

34 """ 

35 _props = [ 

36 LottieProp("path", "u", str, False), 

37 LottieProp("file_name", "p", str, False), 

38 LottieProp("is_embedded", "e", PseudoBool, False), 

39 ] 

40 

41 def __init__(self): 

42 super().__init__() 

43 

44 ## Path to the directory containing a file 

45 self.path = "" 

46 ## Filename or data url 

47 self.file_name = "" 

48 ## Whether the file is embedded 

49 self.is_embedded = False 

50 

51 def _id_from_file(self, file): 

52 if not self.id: 

53 if isinstance(file, str): 

54 self.id = os.path.basename(file) 

55 elif hasattr(file, "name"): 

56 self.id = os.path.basename(file.name) 

57 elif hasattr(file, "filename"): 

58 self.id = os.path.basename(file.filename) 

59 else: 

60 self.id = "image_%s" % id(self) 

61 

62 def data(self): 

63 """ 

64 Returns a tuple (format, data) with the contents of the file 

65 

66 `format` is a string like "png", and `data` is just raw binary data. 

67 

68 If it's impossible to fetch this info, returns (None, None) 

69 """ 

70 if self.is_embedded: 

71 m = re.match("data:[^/]+/([^;,]+);base64,(.*)", self.file_name) 

72 if m: 

73 return m.group(1), base64.b64decode(m.group(2)) 

74 return None, None 

75 path = self.path + self.file_name 

76 if os.path.isfile(path): 

77 with open(path, "rb") as imgfile: 

78 return os.path.splitext(path)[1][1:], imgfile.read() 

79 return None, None 

80 

81 

82## @ingroup Lottie 

83class Image(FileAsset): 

84 """! 

85 External image 

86 

87 @see http://docs.aenhancers.com/sources/filesource/ 

88 """ 

89 _props = [ 

90 LottieProp("height", "h", float, False), 

91 LottieProp("width", "w", float, False), 

92 LottieProp("type", "t", str, False), 

93 ] 

94 

95 @staticmethod 

96 def guess_mime(file): 

97 if isinstance(file, str): 

98 filename = file 

99 elif hasattr(file, "name"): 

100 filename = file.name 

101 else: 

102 return "application/octet-stream" 

103 return mimetypes.guess_type(filename) 

104 

105 def __init__(self, id=""): 

106 super().__init__() 

107 

108 ## Image Height 

109 self.height = 0 

110 ## Image Width 

111 self.width = 0 

112 ## Image ID 

113 self.id = id 

114 ## If "seq", marks it as part of an image sequence 

115 self.type = None 

116 

117 def load(self, file, format=None): 

118 """! 

119 @param file Filename, file object, or PIL.Image.Image to load 

120 @param format Format to store the image data as 

121 """ 

122 from PIL import Image 

123 

124 if not isinstance(file, Image.Image): 

125 image = Image.open(file) 

126 else: 

127 image = file 

128 

129 self._id_from_file(file) 

130 

131 self.path = "" 

132 if format is None: 

133 format = (image.format or "png").lower() 

134 self.width, self.height = image.size 

135 output = BytesIO() 

136 image.save(output, format=format) 

137 self.file_name = "data:image/%s;base64,%s" % ( 

138 format, 

139 base64.b64encode(output.getvalue()).decode("ascii") 

140 ) 

141 self.is_embedded = True 

142 return self 

143 

144 @classmethod 

145 def embedded(cls, image, format=None): 

146 """! 

147 Create an object from an image file 

148 """ 

149 lottie_image = cls() 

150 return lottie_image.load(image, format) 

151 

152 @classmethod 

153 def linked(cls, filename): 

154 from PIL import Image 

155 image = Image.open(filename) 

156 lottie_image = cls() 

157 lottie_image._id_from_file(filename) 

158 lottie_image.path, lottie_image.file_name = os.path.split(filename) 

159 lottie_image.path += "/" 

160 lottie_image.width = image.width 

161 lottie_image.height = image.height 

162 return lottie_image 

163 

164 

165## @ingroup Lottie 

166class Precomp(Asset, Composition): 

167 _props = [ 

168 LottieProp("frame_rate", "fr", float, False), 

169 ] 

170 

171 def __init__(self, id="", animation=None): 

172 super().__init__() 

173 ## Precomp ID 

174 self.id = id 

175 self.animation = animation 

176 if animation: 176 ↛ 177line 176 didn't jump to line 177, because the condition on line 176 was never true

177 self.animation.assets.append(self) 

178 self.name = None 

179 self.frame_rate = None 

180 

181 def _on_prepare_layer(self, layer): 

182 if self.animation: 

183 self.animation.prepare_layer(layer) 

184 

185 def set_timing(self, outpoint, inpoint=0, override=True): 

186 for layer in self.layers: 

187 if override or layer.in_point is None: 

188 layer.in_point = inpoint 

189 if override or layer.out_point is None: 

190 layer.out_point = outpoint 

191 

192 

193#ingroup Lottie 

194class DataSource(FileAsset): 

195 """! 

196 External data source, usually a JSON file 

197 """ 

198 _props = [ 

199 LottieProp("type", "t", int, False), 

200 ] 

201 type = 3 

202 

203 

204#ingroup Lottie 

205class Sound(FileAsset): 

206 """! 

207 External sound 

208 """