1 | // Aseprite |
2 | // Copyright (C) 2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2017 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "app/filename_formatter.h" |
13 | |
14 | #include "app/file/file.h" |
15 | #include "app/file/split_filename.h" |
16 | #include "base/convert_to.h" |
17 | #include "base/fs.h" |
18 | #include "base/replace_string.h" |
19 | |
20 | #include <cstdlib> |
21 | #include <cstring> |
22 | #include <vector> |
23 | |
24 | namespace app { |
25 | |
26 | static bool replace_frame(const char* frameKey, // E.g. = "{frame" |
27 | int frameBase, |
28 | std::string& str) |
29 | { |
30 | size_t i = str.find(frameKey); |
31 | if (i != std::string::npos) { |
32 | int keyLen = std::strlen(frameKey); |
33 | |
34 | size_t j = str.find("}" , i+keyLen); |
35 | if (j != std::string::npos) { |
36 | std::string from = str.substr(i, j - i + 1); |
37 | if (frameBase >= 0) { |
38 | std::vector<char> to(32); |
39 | int offset = std::strtol(from.c_str()+keyLen, NULL, 10); |
40 | |
41 | std::sprintf(&to[0], "%0*d" , (int(j)-int(i+keyLen)), frameBase + offset); |
42 | base::replace_string(str, from, &to[0]); |
43 | } |
44 | else |
45 | base::replace_string(str, from, "" ); |
46 | } |
47 | return true; |
48 | } |
49 | else |
50 | return false; |
51 | } |
52 | |
53 | bool get_frame_info_from_filename_format( |
54 | const std::string& format, int* frameBase, int* width) |
55 | { |
56 | const char* frameKey = "{frame" ; |
57 | size_t i = format.find(frameKey); |
58 | if (i != std::string::npos) { |
59 | int keyLen = std::strlen(frameKey); |
60 | |
61 | size_t j = format.find("}" , i+keyLen); |
62 | if (j != std::string::npos) { |
63 | std::string frameStr = format.substr(i, j - i + 1); |
64 | |
65 | if (frameBase) |
66 | *frameBase = std::strtol(frameStr.c_str()+keyLen, NULL, 10); |
67 | |
68 | if (width) |
69 | *width = (int(j) - int(i+keyLen)); |
70 | } |
71 | return true; |
72 | } |
73 | else |
74 | return false; |
75 | } |
76 | |
77 | bool is_tag_in_filename_format(const std::string& format) |
78 | { |
79 | return (format.find("{tag}" ) != std::string::npos); |
80 | } |
81 | |
82 | bool is_layer_in_filename_format(const std::string& format) |
83 | { |
84 | return (format.find("{layer}" ) != std::string::npos); |
85 | } |
86 | |
87 | bool is_group_in_filename_format(const std::string& format) |
88 | { |
89 | return (format.find("{group}" ) != std::string::npos); |
90 | } |
91 | |
92 | bool is_slice_in_filename_format(const std::string& format) |
93 | { |
94 | return (format.find("{slice}" ) != std::string::npos); |
95 | } |
96 | |
97 | std::string filename_formatter( |
98 | const std::string& format, |
99 | FilenameInfo& info, |
100 | const bool replaceFrame) |
101 | { |
102 | const std::string& filename = info.filename(); |
103 | std::string path = base::get_file_path(filename); |
104 | if (path.empty()) |
105 | path = "." ; |
106 | |
107 | std::string output = format; |
108 | base::replace_string(output, "{fullname}" , filename); |
109 | base::replace_string(output, "{path}" , path); |
110 | base::replace_string(output, "{name}" , base::get_file_name(filename)); |
111 | base::replace_string(output, "{title}" , base::get_file_title(filename)); |
112 | base::replace_string(output, "{extension}" , base::get_file_extension(filename)); |
113 | base::replace_string(output, "{layer}" , info.layerName()); |
114 | base::replace_string(output, "{group}" , info.groupName()); |
115 | base::replace_string(output, "{slice}" , info.sliceName()); |
116 | |
117 | if (replaceFrame) { |
118 | base::replace_string(output, "{tag}" , info.innerTagName()); |
119 | base::replace_string(output, "{innertag}" , info.innerTagName()); |
120 | base::replace_string(output, "{outertag}" , info.outerTagName()); |
121 | base::replace_string(output, "{duration}" , std::to_string(info.duration())); |
122 | replace_frame("{frame" , info.frame(), output); |
123 | replace_frame("{tagframe" , info.tagFrame(), output); |
124 | } |
125 | |
126 | return output; |
127 | } |
128 | |
129 | std::string get_default_filename_format( |
130 | std::string& filename, |
131 | const bool withPath, |
132 | const bool hasFrames, |
133 | const bool hasLayer, |
134 | const bool hasTag) |
135 | { |
136 | std::string format; |
137 | |
138 | if (withPath) |
139 | format += "{path}/" ; |
140 | |
141 | format += "{title}" ; |
142 | |
143 | if (hasLayer) |
144 | format += " ({layer})" ; |
145 | |
146 | if (hasTag) |
147 | format += " #{tag}" ; |
148 | |
149 | if (hasFrames && is_static_image_format(filename) && |
150 | filename.find("{frame" ) == std::string::npos && |
151 | filename.find("{tagframe" ) == std::string::npos) { |
152 | const bool autoFrameFromLastDigit = |
153 | (!hasLayer && |
154 | !hasTag); |
155 | |
156 | // Check if we already have a frame number at the end of the |
157 | // filename (e.g. output01.png) |
158 | int frameBase = -1, frameWidth = 0; |
159 | std::string left, right; |
160 | if (autoFrameFromLastDigit) |
161 | frameBase = split_filename(filename, left, right, frameWidth); |
162 | if (frameBase >= 0) { |
163 | std::vector<char> buf(32); |
164 | std::sprintf(&buf[0], "{frame%0*d}" , frameWidth, frameBase); |
165 | |
166 | if (hasLayer || hasTag) |
167 | format += " " ; |
168 | format += &buf[0]; |
169 | |
170 | // Remove the frame number from the filename part. |
171 | filename = left; |
172 | filename += right; |
173 | } |
174 | // Check if there is already a {frame} tag in the filename |
175 | else if (get_frame_info_from_filename_format(filename, &frameBase, &frameWidth)) { |
176 | // Do nothing |
177 | } |
178 | else { |
179 | if (hasLayer || hasTag) |
180 | format += " {frame}" ; |
181 | else |
182 | format += "{frame1}" ; |
183 | } |
184 | } |
185 | |
186 | format += ".{extension}" ; |
187 | return format; |
188 | } |
189 | |
190 | std::string get_default_filename_format_for_sheet( |
191 | const std::string& filename, |
192 | const bool hasFrames, |
193 | const bool hasLayer, |
194 | const bool hasTag) |
195 | { |
196 | std::string format = "{title}" ; |
197 | |
198 | if (hasLayer) |
199 | format += " ({layer})" ; |
200 | |
201 | if (hasTag) |
202 | format += " #{tag}" ; |
203 | |
204 | if (hasFrames) { |
205 | int frameBase, frameWidth; |
206 | |
207 | // Check if there is already a {frame} tag in the filename |
208 | if (get_frame_info_from_filename_format(filename, &frameBase, &frameWidth)) { |
209 | // Do nothing |
210 | } |
211 | else { |
212 | format += " {frame}" ; |
213 | } |
214 | } |
215 | |
216 | format += ".{extension}" ; |
217 | return format; |
218 | } |
219 | |
220 | } // namespace app |
221 | |