Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <stdarg.h> |
19 | | #include <math.h> |
20 | | #include <assert.h> |
21 | | |
22 | | #include <libavutil/common.h> |
23 | | #include <libavutil/error.h> |
24 | | #include <libavutil/mathematics.h> |
25 | | |
26 | | #include "mpv_talloc.h" |
27 | | #include "misc/bstr.h" |
28 | | #include "misc/ctype.h" |
29 | | #include "common/common.h" |
30 | | #include "osdep/strnlen.h" |
31 | | |
32 | | #define appendf(ptr, ...) \ |
33 | 2.16k | do {(*(ptr)) = talloc_asprintf_append_buffer(*(ptr), __VA_ARGS__);} while(0) |
34 | | |
35 | | // Return a talloc'ed string formatted according to the format string in fmt. |
36 | | // On error, return NULL. |
37 | | // Valid formats: |
38 | | // %H, %h: hour (%H is padded with 0 to two digits) |
39 | | // %M: minutes from 00-59 (hours are subtracted) |
40 | | // %m: total minutes (includes hours, unlike %M) |
41 | | // %S: seconds from 00-59 (minutes and hours are subtracted) |
42 | | // %s: total seconds (includes hours and minutes) |
43 | | // %f: like %s, but as float |
44 | | // %T: milliseconds (000-999) |
45 | | char *mp_format_time_fmt(const char *fmt, double time) |
46 | 421 | { |
47 | 421 | if (time == MP_NOPTS_VALUE) |
48 | 3 | return talloc_strdup(NULL, "unknown"); |
49 | 418 | char *sign = time < 0 ? "-" : ""; |
50 | 418 | time = time < 0 ? -time : time; |
51 | 418 | long long int itime = time; |
52 | 418 | long long int h, m, tm, s; |
53 | 418 | int ms = lrint((time - itime) * 1000); |
54 | 418 | if (ms >= 1000) { |
55 | 0 | ms -= 1000; |
56 | 0 | itime += 1; |
57 | 0 | } |
58 | 418 | s = itime; |
59 | 418 | tm = s / 60; |
60 | 418 | h = s / 3600; |
61 | 418 | s -= h * 3600; |
62 | 418 | m = s / 60; |
63 | 418 | s -= m * 60; |
64 | 418 | char *res = talloc_strdup(NULL, ""); |
65 | 2.57k | while (*fmt) { |
66 | 2.16k | if (fmt[0] == '%') { |
67 | 1.28k | fmt++; |
68 | 1.28k | switch (fmt[0]) { |
69 | 0 | case 'h': appendf(&res, "%s%lld", sign, h); break; |
70 | 418 | case 'H': appendf(&res, "%s%02lld", sign, h); break; |
71 | 0 | case 'm': appendf(&res, "%s%lld", sign, tm); break; |
72 | 418 | case 'M': appendf(&res, "%02lld", m); break; |
73 | 0 | case 's': appendf(&res, "%s%lld", sign, itime); break; |
74 | 418 | case 'S': appendf(&res, "%02lld", s); break; |
75 | 35 | case 'T': appendf(&res, "%03d", ms); break; |
76 | 0 | case 'f': appendf(&res, "%f", time); break; |
77 | 0 | case '%': appendf(&res, "%s", "%"); break; |
78 | 0 | default: goto error; |
79 | 1.28k | } |
80 | 1.28k | fmt++; |
81 | 1.28k | } else { |
82 | 871 | appendf(&res, "%c", *fmt); |
83 | 871 | fmt++; |
84 | 871 | } |
85 | 2.16k | } |
86 | 418 | return res; |
87 | 0 | error: |
88 | 0 | talloc_free(res); |
89 | 0 | return NULL; |
90 | 418 | } |
91 | | |
92 | | char *mp_format_time(double time, bool fractions) |
93 | 421 | { |
94 | 421 | return mp_format_time_fmt(fractions ? "%H:%M:%S.%T" : "%H:%M:%S", time); |
95 | 421 | } |
96 | | |
97 | | char *mp_format_double(void *talloc_ctx, double val, int precision, |
98 | | bool plus_sign, bool percent_sign, bool trim) |
99 | 25.2k | { |
100 | 25.2k | bstr str = {0}; |
101 | 25.2k | const char *fmt = plus_sign ? "%+.*f" : "%.*f"; |
102 | 25.2k | bstr_xappend_asprintf(talloc_ctx, &str, fmt, precision, val); |
103 | 25.2k | size_t pos = str.len; |
104 | 25.2k | if (trim) { |
105 | 118k | while (--pos && str.start[pos] == '0') |
106 | 93.5k | str.len--; |
107 | 24.8k | if (str.start[pos] == '.') |
108 | 22.6k | str.len--; |
109 | 24.8k | } |
110 | 25.2k | if (percent_sign) |
111 | 80 | bstr_xappend(talloc_ctx, &str, bstr0("%")); |
112 | 25.2k | str.start[str.len] = '\0'; |
113 | 25.2k | return str.start; |
114 | 25.2k | } |
115 | | |
116 | | // Set rc to the union of rc and rc2 |
117 | | void mp_rect_union(struct mp_rect *rc, const struct mp_rect *rc2) |
118 | 0 | { |
119 | 0 | rc->x0 = MPMIN(rc->x0, rc2->x0); |
120 | 0 | rc->y0 = MPMIN(rc->y0, rc2->y0); |
121 | 0 | rc->x1 = MPMAX(rc->x1, rc2->x1); |
122 | 0 | rc->y1 = MPMAX(rc->y1, rc2->y1); |
123 | 0 | } |
124 | | |
125 | | // Returns whether or not a point is contained by rc |
126 | | bool mp_rect_contains(struct mp_rect *rc, int x, int y) |
127 | 0 | { |
128 | 0 | return rc->x0 <= x && x < rc->x1 && rc->y0 <= y && y < rc->y1; |
129 | 0 | } |
130 | | |
131 | | // Set rc to the intersection of rc and src. |
132 | | // Return false if the result is empty. |
133 | | bool mp_rect_intersection(struct mp_rect *rc, const struct mp_rect *rc2) |
134 | 0 | { |
135 | 0 | rc->x0 = MPMAX(rc->x0, rc2->x0); |
136 | 0 | rc->y0 = MPMAX(rc->y0, rc2->y0); |
137 | 0 | rc->x1 = MPMIN(rc->x1, rc2->x1); |
138 | 0 | rc->y1 = MPMIN(rc->y1, rc2->y1); |
139 | |
|
140 | 0 | return rc->x1 > rc->x0 && rc->y1 > rc->y0; |
141 | 0 | } |
142 | | |
143 | | bool mp_rect_equals(const struct mp_rect *rc1, const struct mp_rect *rc2) |
144 | 2.87M | { |
145 | 2.87M | return rc1->x0 == rc2->x0 && rc1->y0 == rc2->y0 && |
146 | 2.87M | rc1->x1 == rc2->x1 && rc1->y1 == rc2->y1; |
147 | 2.87M | } |
148 | | |
149 | | // Rotate mp_rect by 90 degrees increments |
150 | | void mp_rect_rotate(struct mp_rect *rc, int w, int h, int rotation) |
151 | 0 | { |
152 | 0 | rotation %= 360; |
153 | |
|
154 | 0 | if (rotation >= 180) { |
155 | 0 | rotation -= 180; |
156 | 0 | MPSWAP(int, rc->x0, rc->x1); |
157 | 0 | MPSWAP(int, rc->y0, rc->y1); |
158 | 0 | } |
159 | |
|
160 | 0 | if (rotation == 90) { |
161 | 0 | *rc = (struct mp_rect) { |
162 | 0 | .x0 = rc->y1, |
163 | 0 | .y0 = rc->x0, |
164 | 0 | .x1 = rc->y0, |
165 | 0 | .y1 = rc->x1, |
166 | 0 | }; |
167 | 0 | } |
168 | |
|
169 | 0 | if (rc->x1 < rc->x0) { |
170 | 0 | rc->x0 = w - rc->x0; |
171 | 0 | rc->x1 = w - rc->x1; |
172 | 0 | } |
173 | |
|
174 | 0 | if (rc->y1 < rc->y0) { |
175 | 0 | rc->y0 = h - rc->y0; |
176 | 0 | rc->y1 = h - rc->y1; |
177 | 0 | } |
178 | 0 | } |
179 | | |
180 | | // Compute rc1-rc2, put result in res_array, return number of rectangles in |
181 | | // res_array. In the worst case, there are 4 rectangles, so res_array must |
182 | | // provide that much storage space. |
183 | | int mp_rect_subtract(const struct mp_rect *rc1, const struct mp_rect *rc2, |
184 | | struct mp_rect res[4]) |
185 | 0 | { |
186 | 0 | struct mp_rect rc = *rc1; |
187 | 0 | if (!mp_rect_intersection(&rc, rc2)) |
188 | 0 | return 0; |
189 | | |
190 | 0 | int cnt = 0; |
191 | 0 | if (rc1->y0 < rc.y0) |
192 | 0 | res[cnt++] = (struct mp_rect){rc1->x0, rc1->y0, rc1->x1, rc.y0}; |
193 | 0 | if (rc1->x0 < rc.x0) |
194 | 0 | res[cnt++] = (struct mp_rect){rc1->x0, rc.y0, rc.x0, rc.y1}; |
195 | 0 | if (rc1->x1 > rc.x1) |
196 | 0 | res[cnt++] = (struct mp_rect){rc.x1, rc.y0, rc1->x1, rc.y1}; |
197 | 0 | if (rc1->y1 > rc.y1) |
198 | 0 | res[cnt++] = (struct mp_rect){rc1->x0, rc.y1, rc1->x1, rc1->y1}; |
199 | 0 | return cnt; |
200 | 0 | } |
201 | | |
202 | | // This works like snprintf(), except that it starts writing the first output |
203 | | // character to str[strlen(str)]. This returns the number of characters the |
204 | | // string would have *appended* assuming a large enough buffer, will make sure |
205 | | // str is null-terminated, and will never write to str[size] or past. |
206 | | // Example: |
207 | | // int example(char *buf, size_t buf_size, double num, char *str) { |
208 | | // int n = 0; |
209 | | // n += mp_snprintf_cat(buf, size, "%f", num); |
210 | | // n += mp_snprintf_cat(buf, size, "%s", str); |
211 | | // return n; } |
212 | | // Note how this can be chained with functions similar in style. |
213 | | int mp_snprintf_cat(char *str, size_t size, const char *format, ...) |
214 | 2.47M | { |
215 | 2.47M | size_t len = strnlen(str, size); |
216 | 2.47M | mp_assert(!size || len < size); // str with no 0-termination is not allowed |
217 | 2.47M | int r; |
218 | 2.47M | va_list ap; |
219 | 2.47M | va_start(ap, format); |
220 | 2.47M | r = vsnprintf(str + len, size - len, format, ap); |
221 | 2.47M | va_end(ap); |
222 | 2.47M | return r; |
223 | 2.47M | } |
224 | | |
225 | | // Encode the unicode codepoint as UTF-8, and append to the end of the |
226 | | // talloc'ed buffer. All guarantees bstr_xappend() give applies, such as |
227 | | // implicit \0-termination for convenience. |
228 | | void mp_append_utf8_bstr(void *talloc_ctx, struct bstr *buf, uint32_t codepoint) |
229 | 30.4M | { |
230 | 30.4M | char data[8]; |
231 | 30.4M | uint8_t tmp; |
232 | 30.4M | char *output = data; |
233 | 30.4M | PUT_UTF8(codepoint, tmp, *output++ = tmp;); |
234 | 30.4M | bstr_xappend(talloc_ctx, buf, (bstr){data, output - data}); |
235 | 30.4M | } |
236 | | |
237 | | // Parse a C/JSON-style escape beginning at code, and append the result to *str |
238 | | // using talloc. The input string (*code) must point to the first character |
239 | | // after the initial '\', and after parsing *code is set to the first character |
240 | | // after the current escape. |
241 | | // On error, false is returned, and all input remains unchanged. |
242 | | static bool mp_parse_escape(void *talloc_ctx, bstr *dst, bstr *code) |
243 | 38.1k | { |
244 | 38.1k | if (code->len < 1) |
245 | 343 | return false; |
246 | 37.8k | char replace = 0; |
247 | 37.8k | switch (code->start[0]) { |
248 | 1.43k | case '"': replace = '"'; break; |
249 | 1.79k | case '\\': replace = '\\'; break; |
250 | 953 | case '/': replace = '/'; break; |
251 | 1.23k | case 'b': replace = '\b'; break; |
252 | 1.78k | case 'f': replace = '\f'; break; |
253 | 1.36k | case 'n': replace = '\n'; break; |
254 | 1.80k | case 'r': replace = '\r'; break; |
255 | 1.81k | case 't': replace = '\t'; break; |
256 | 1.71k | case 'e': replace = '\x1b'; break; |
257 | 4.84k | case '\'': replace = '\''; break; |
258 | 37.8k | } |
259 | 37.8k | if (replace) { |
260 | 18.7k | bstr_xappend(talloc_ctx, dst, (bstr){&replace, 1}); |
261 | 18.7k | *code = bstr_cut(*code, 1); |
262 | 18.7k | return true; |
263 | 18.7k | } |
264 | 19.1k | if (code->start[0] == 'x' && code->len >= 3) { |
265 | 8.77k | bstr num = bstr_splice(*code, 1, 3); |
266 | 8.77k | char c = bstrtoll(num, &num, 16); |
267 | 8.77k | if (num.len) |
268 | 267 | return false; |
269 | 8.50k | bstr_xappend(talloc_ctx, dst, (bstr){&c, 1}); |
270 | 8.50k | *code = bstr_cut(*code, 3); |
271 | 8.50k | return true; |
272 | 8.77k | } |
273 | 10.3k | if (code->start[0] == 'u' && code->len >= 5) { |
274 | 9.74k | bstr num = bstr_splice(*code, 1, 5); |
275 | 9.74k | uint32_t c = bstrtoll(num, &num, 16); |
276 | 9.74k | if (num.len || c > 0x10FFFF) |
277 | 491 | return false; |
278 | 9.25k | if (c >= 0xd800 && c <= 0xdbff) { |
279 | 2.41k | if (code->len < 5 + 6 // udddd + \udddd |
280 | 2.41k | || code->start[5] != '\\' || code->start[6] != 'u') |
281 | 735 | return false; |
282 | 1.68k | *code = bstr_cut(*code, 5 + 1); |
283 | 1.68k | bstr num2 = bstr_splice(*code, 1, 5); |
284 | 1.68k | uint32_t c2 = bstrtoll(num2, &num2, 16); |
285 | 1.68k | if (num2.len || c2 < 0xdc00 || c2 > 0xdfff) |
286 | 844 | return false; |
287 | 837 | c = ((c - 0xd800) << 10) + 0x10000 + (c2 - 0xdc00); |
288 | 837 | } |
289 | 7.67k | mp_append_utf8_bstr(talloc_ctx, dst, c); |
290 | 7.67k | *code = bstr_cut(*code, 5); |
291 | 7.67k | return true; |
292 | 9.25k | } |
293 | 585 | return false; |
294 | 10.3k | } |
295 | | |
296 | | // Like mp_append_escaped_string, but set *dst to sliced *src if no escape |
297 | | // sequences have to be parsed (i.e. no memory allocation is required), and |
298 | | // if dst->start was NULL on function entry. |
299 | | bool mp_append_escaped_string_noalloc(void *talloc_ctx, bstr *dst, bstr *src) |
300 | 1.25M | { |
301 | 1.25M | bstr t = *src; |
302 | 1.25M | int cur = 0; |
303 | 13.9M | while (1) { |
304 | 13.9M | if (cur >= t.len || t.start[cur] == '"') { |
305 | 1.25M | *src = bstr_cut(t, cur); |
306 | 1.25M | t = bstr_splice(t, 0, cur); |
307 | 1.25M | if (dst->start == NULL) { |
308 | 1.24M | *dst = t; |
309 | 1.24M | } else { |
310 | 6.85k | bstr_xappend(talloc_ctx, dst, t); |
311 | 6.85k | } |
312 | 1.25M | return true; |
313 | 12.7M | } else if (t.start[cur] == '\\') { |
314 | 38.1k | bstr_xappend(talloc_ctx, dst, bstr_splice(t, 0, cur)); |
315 | 38.1k | t = bstr_cut(t, cur + 1); |
316 | 38.1k | cur = 0; |
317 | 38.1k | if (!mp_parse_escape(talloc_ctx, dst, &t)) |
318 | 3.26k | goto error; |
319 | 12.6M | } else { |
320 | 12.6M | cur++; |
321 | 12.6M | } |
322 | 13.9M | } |
323 | 3.26k | error: |
324 | 3.26k | return false; |
325 | 1.25M | } |
326 | | |
327 | | // src is expected to point to a C-style string literal, *src pointing to the |
328 | | // first char after the starting '"'. It will append the contents of the literal |
329 | | // to *dst (using talloc_ctx) until the first '"' or the end of *str is found. |
330 | | // See bstr_xappend() how data is appended to *dst. |
331 | | // On success, *src will either start with '"', or be empty. |
332 | | // On error, return false, and *dst will contain the string until the first |
333 | | // error, *src is not changed. |
334 | | // Note that dst->start will be implicitly \0-terminated on successful return, |
335 | | // and if it was NULL or \0-terminated before calling the function. |
336 | | // As mentioned above, the caller is responsible for skipping the '"' chars. |
337 | | bool mp_append_escaped_string(void *talloc_ctx, bstr *dst, bstr *src) |
338 | 10.2k | { |
339 | 10.2k | if (mp_append_escaped_string_noalloc(talloc_ctx, dst, src)) { |
340 | | // Guarantee copy (or allocation). |
341 | 9.93k | if (!dst->start || dst->start == src->start) { |
342 | 63 | bstr res = *dst; |
343 | 63 | *dst = (bstr){0}; |
344 | 63 | bstr_xappend(talloc_ctx, dst, res); |
345 | 63 | } |
346 | 9.93k | return true; |
347 | 9.93k | } |
348 | 350 | return false; |
349 | 10.2k | } |
350 | | |
351 | | // Behaves like strerror()/strerror_r(), but is thread- and GNU-safe. |
352 | | char *mp_strerror_buf(char *buf, size_t buf_size, int errnum) |
353 | 3.50k | { |
354 | | // This handles the nasty details of calling the right function for us. |
355 | 3.50k | av_strerror(AVERROR(errnum), buf, buf_size); |
356 | 3.50k | return buf; |
357 | 3.50k | } |
358 | | |
359 | | char *mp_tag_str_buf(char *buf, size_t buf_size, uint32_t tag) |
360 | 0 | { |
361 | 0 | if (buf_size < 1) |
362 | 0 | return buf; |
363 | 0 | buf[0] = '\0'; |
364 | 0 | for (int n = 0; n < 4; n++) { |
365 | 0 | uint8_t val = (tag >> (n * 8)) & 0xFF; |
366 | 0 | if (mp_isalnum(val) || val == '_' || val == ' ') { |
367 | 0 | mp_snprintf_cat(buf, buf_size, "%c", val); |
368 | 0 | } else { |
369 | 0 | mp_snprintf_cat(buf, buf_size, "[%d]", val); |
370 | 0 | } |
371 | 0 | } |
372 | 0 | return buf; |
373 | 0 | } |
374 | | |
375 | | char *mp_tprintf_buf(char *buf, size_t buf_size, const char *format, ...) |
376 | 938k | { |
377 | 938k | va_list ap; |
378 | 938k | va_start(ap, format); |
379 | 938k | vsnprintf(buf, buf_size, format, ap); |
380 | 938k | va_end(ap); |
381 | 938k | return buf; |
382 | 938k | } |
383 | | |
384 | | char **mp_dup_str_array(void *tctx, char **s) |
385 | 304k | { |
386 | 304k | char **r = NULL; |
387 | 304k | int num_r = 0; |
388 | 320k | for (int n = 0; s && s[n]; n++) |
389 | 15.6k | MP_TARRAY_APPEND(tctx, r, num_r, talloc_strdup(tctx, s[n])); |
390 | 304k | if (r) |
391 | 11.7k | MP_TARRAY_APPEND(tctx, r, num_r, NULL); |
392 | 304k | return r; |
393 | 304k | } |
394 | | |
395 | | // Return rounded down integer log 2 of v, i.e. position of highest set bit. |
396 | | // mp_log2(0) == 0 |
397 | | // mp_log2(1) == 0 |
398 | | // mp_log2(31) == 4 |
399 | | // mp_log2(32) == 5 |
400 | | unsigned int mp_log2(uint32_t v) |
401 | 51.6M | { |
402 | 51.6M | #if (defined(__GNUC__) && __GNUC__ >= 4) || defined(__clang__) |
403 | 51.6M | return v ? 31 - __builtin_clz(v) : 0; |
404 | | #else |
405 | | for (int x = 31; x >= 0; x--) { |
406 | | if (v & (((uint32_t)1) << x)) |
407 | | return x; |
408 | | } |
409 | | return 0; |
410 | | #endif |
411 | 51.6M | } |
412 | | |
413 | | // If a power of 2, return it, otherwise return the next highest one, or 0. |
414 | | // mp_round_next_power_of_2(65) == 128 |
415 | | // mp_round_next_power_of_2(64) == 64 |
416 | | // mp_round_next_power_of_2(0) == 1 |
417 | | // mp_round_next_power_of_2(UINT32_MAX) == 0 |
418 | | uint32_t mp_round_next_power_of_2(uint32_t v) |
419 | 3.51M | { |
420 | 3.51M | if (!v) |
421 | 0 | return 1; |
422 | 3.51M | if (!(v & (v - 1))) |
423 | 3.39M | return v; |
424 | 122k | int l = mp_log2(v) + 1; |
425 | 122k | return l == 32 ? 0 : (uint32_t)1 << l; |
426 | 3.51M | } |
427 | | |
428 | | int mp_lcm(int x, int y) |
429 | 797k | { |
430 | 797k | mp_assert(x && y); |
431 | 797k | return x * (y / av_gcd(x, y)); |
432 | 797k | } |