FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/concat.c
Date: 2026-05-03 08:24:11
Exec Total Coverage
Lines: 62 166 37.3%
Functions: 4 5 80.0%
Branches: 29 92 31.5%

Line Branch Exec Source
1 /*
2 * Concat URL protocol
3 * Copyright (c) 2006 Steve Lhomme
4 * Copyright (c) 2007 Wolfram Gloger
5 * Copyright (c) 2010 Michele OrrĂ¹
6 *
7 * This file is part of FFmpeg.
8 *
9 * FFmpeg is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * FFmpeg is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with FFmpeg; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 */
23
24 #include <string.h>
25
26 #include "config_components.h"
27
28 #include "libavutil/attributes.h"
29 #include "libavutil/avstring.h"
30 #include "libavutil/bprint.h"
31 #include "libavutil/error.h"
32 #include "libavutil/mem.h"
33
34 #include "avio_internal.h"
35 #include "url.h"
36
37 #define AV_CAT_SEPARATOR "|"
38
39 struct concat_nodes {
40 URLContext *uc; ///< node's URLContext
41 int64_t size; ///< url filesize
42 };
43
44 struct concat_data {
45 struct concat_nodes *nodes; ///< list of nodes to concat
46 size_t length; ///< number of cat'ed nodes
47 size_t current; ///< index of currently read node
48 int64_t total_size;
49 };
50
51 2 static av_cold int concat_close(URLContext *h)
52 {
53 2 int err = 0;
54 size_t i;
55 2 struct concat_data *data = h->priv_data;
56 2 struct concat_nodes *nodes = data->nodes;
57
58
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 for (i = 0; i != data->length; i++)
59 6 err |= ffurl_closep(&nodes[i].uc);
60
61 2 av_freep(&data->nodes);
62
63
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 return err < 0 ? -1 : 0;
64 }
65
66 #if CONFIG_CONCAT_PROTOCOL
67 2 static av_cold int concat_open(URLContext *h, const char *uri, int flags)
68 {
69 2 char *node_uri = NULL;
70 2 int err = 0;
71 2 int64_t size, total_size = 0;
72 size_t len, i;
73 URLContext *uc;
74 2 struct concat_data *data = h->priv_data;
75 struct concat_nodes *nodes;
76
77
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (!av_strstart(uri, "concat:", &uri)) {
78 av_log(h, AV_LOG_ERROR, "URL %s lacks prefix\n", uri);
79 return AVERROR(EINVAL);
80 }
81
82
2/2
✓ Branch 0 taken 292 times.
✓ Branch 1 taken 2 times.
294 for (i = 0, len = 1; uri[i]; i++) {
83
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 288 times.
292 if (uri[i] == *AV_CAT_SEPARATOR) {
84 4 len++;
85 }
86 }
87
88
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (!(nodes = av_realloc_array(NULL, len, sizeof(*nodes))))
89 return AVERROR(ENOMEM);
90 else
91 2 data->nodes = nodes;
92
93 /* handle input */
94
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!*uri)
95 err = AVERROR(ENOENT);
96
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 for (i = 0; *uri; i++) {
97 /* parsing uri */
98 6 len = strcspn(uri, AV_CAT_SEPARATOR);
99
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 if ((err = av_reallocp(&node_uri, len + 1)) < 0)
100 break;
101 6 av_strlcpy(node_uri, uri, len + 1);
102 6 uri += len + strspn(uri + len, AV_CAT_SEPARATOR);
103
104 /* creating URLContext */
105 6 err = ffurl_open_whitelist(&uc, node_uri, flags,
106 6 &h->interrupt_callback, NULL, h->protocol_whitelist, h->protocol_blacklist, h);
107
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (err < 0)
108 break;
109
110 /* creating size */
111
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 if ((size = ffurl_size(uc)) < 0) {
112 ffurl_close(uc);
113 err = AVERROR(ENOSYS);
114 break;
115 }
116
117
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (total_size > INT64_MAX - size) {
118 ffurl_close(uc);
119 err = AVERROR_INVALIDDATA;
120 break;
121 }
122
123 /* assembling */
124 6 nodes[i].uc = uc;
125 6 nodes[i].size = size;
126 6 total_size += size;
127 }
128 2 av_free(node_uri);
129 2 data->length = i;
130
131
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (err < 0)
132 concat_close(h);
133
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 else if (!(nodes = av_realloc(nodes, data->length * sizeof(*nodes)))) {
134 concat_close(h);
135 err = AVERROR(ENOMEM);
136 } else
137 2 data->nodes = nodes;
138 2 data->total_size = total_size;
139 2 return err;
140 }
141 #endif
142
143 40 static int concat_read(URLContext *h, unsigned char *buf, int size)
144 {
145 40 int result, total = 0;
146 40 struct concat_data *data = h->priv_data;
147 40 struct concat_nodes *nodes = data->nodes;
148 40 size_t i = data->current;
149
150
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 30 times.
79 while (size > 0) {
151 49 result = ffurl_read(nodes[i].uc, buf, size);
152
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 35 times.
49 if (result == AVERROR_EOF) {
153
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
18 if (i + 1 == data->length ||
154 4 ffurl_seek(nodes[++i].uc, 0, SEEK_SET) < 0)
155 break;
156 4 result = 0;
157 }
158
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
39 if (result < 0)
159 return total ? total : result;
160 39 total += result;
161 39 buf += result;
162 39 size -= result;
163 }
164 40 data->current = i;
165
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 9 times.
40 return total ? total : result;
166 }
167
168 4 static int64_t concat_seek(URLContext *h, int64_t pos, int whence)
169 {
170 int64_t result;
171 4 struct concat_data *data = h->priv_data;
172 4 struct concat_nodes *nodes = data->nodes;
173 size_t i;
174
175
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if ((whence & AVSEEK_SIZE))
176 4 return data->total_size;
177 switch (whence) {
178 case SEEK_END:
179 for (i = data->length - 1; i && pos < -nodes[i].size; i--)
180 pos += nodes[i].size;
181 break;
182 case SEEK_CUR:
183 /* get the absolute position */
184 for (i = 0; i != data->current; i++)
185 pos += nodes[i].size;
186 pos += ffurl_seek(nodes[i].uc, 0, SEEK_CUR);
187 whence = SEEK_SET;
188 /* fall through with the absolute position */
189 av_fallthrough;
190 case SEEK_SET:
191 for (i = 0; i != data->length - 1 && pos >= nodes[i].size; i++)
192 pos -= nodes[i].size;
193 break;
194 default:
195 return AVERROR(EINVAL);
196 }
197
198 result = ffurl_seek(nodes[i].uc, pos, whence);
199 if (result >= 0) {
200 data->current = i;
201 while (i)
202 result += nodes[--i].size;
203 }
204 return result;
205 }
206
207 #if CONFIG_CONCAT_PROTOCOL
208 const URLProtocol ff_concat_protocol = {
209 .name = "concat",
210 .url_open = concat_open,
211 .url_read = concat_read,
212 .url_seek = concat_seek,
213 .url_close = concat_close,
214 .priv_data_size = sizeof(struct concat_data),
215 .default_whitelist = "concat,file,subfile",
216 };
217 #endif
218
219 #if CONFIG_CONCATF_PROTOCOL
220 static av_cold int concatf_open(URLContext *h, const char *uri, int flags)
221 {
222 AVBPrint bp;
223 struct concat_data *data = h->priv_data;
224 AVIOContext *in = NULL;
225 const char *cursor;
226 int64_t total_size = 0;
227 unsigned int nodes_size = 0;
228 size_t i = 0;
229 int err;
230
231 if (!av_strstart(uri, "concatf:", &uri)) {
232 av_log(h, AV_LOG_ERROR, "URL %s lacks prefix\n", uri);
233 return AVERROR(EINVAL);
234 }
235
236 /* handle input */
237 if (!*uri)
238 return AVERROR(ENOENT);
239
240 err = ffio_open_whitelist(&in, uri, AVIO_FLAG_READ, &h->interrupt_callback,
241 NULL, h->protocol_whitelist, h->protocol_blacklist);
242 if (err < 0)
243 return err;
244
245 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
246 err = avio_read_to_bprint(in, &bp, SIZE_MAX);
247 avio_closep(&in);
248 if (err < 0) {
249 av_bprint_finalize(&bp, NULL);
250 return err;
251 }
252
253 cursor = bp.str;
254 while (*cursor) {
255 struct concat_nodes *nodes;
256 URLContext *uc;
257 char *node_uri;
258 int64_t size;
259 size_t len = i;
260 int leading_spaces = strspn(cursor, " \n\t\r");
261
262 if (!cursor[leading_spaces])
263 break;
264
265 node_uri = av_get_token(&cursor, "\r\n");
266 if (!node_uri) {
267 err = AVERROR(ENOMEM);
268 break;
269 }
270 if (*cursor)
271 cursor++;
272
273 if (++len == SIZE_MAX / sizeof(*nodes)) {
274 av_free(node_uri);
275 err = AVERROR(ENAMETOOLONG);
276 break;
277 }
278
279 /* creating URLContext */
280 err = ffurl_open_whitelist(&uc, node_uri, flags,
281 &h->interrupt_callback, NULL, h->protocol_whitelist, h->protocol_blacklist, h);
282 av_free(node_uri);
283 if (err < 0)
284 break;
285
286 /* creating size */
287 if ((size = ffurl_size(uc)) < 0) {
288 ffurl_close(uc);
289 err = AVERROR(ENOSYS);
290 break;
291 }
292
293 if (total_size > INT64_MAX - size) {
294 ffurl_close(uc);
295 err = AVERROR_INVALIDDATA;
296 break;
297 }
298
299 nodes = av_fast_realloc(data->nodes, &nodes_size, sizeof(*nodes) * len);
300 if (!nodes) {
301 ffurl_close(uc);
302 err = AVERROR(ENOMEM);
303 break;
304 }
305 data->nodes = nodes;
306
307 /* assembling */
308 data->nodes[i].uc = uc;
309 data->nodes[i++].size = size;
310 total_size += size;
311 }
312 av_bprint_finalize(&bp, NULL);
313 data->length = i;
314
315 if (!data->length)
316 err = AVERROR_INVALIDDATA;
317 if (err < 0)
318 concat_close(h);
319
320 data->total_size = total_size;
321 return err;
322 }
323
324 const URLProtocol ff_concatf_protocol = {
325 .name = "concatf",
326 .url_open = concatf_open,
327 .url_read = concat_read,
328 .url_seek = concat_seek,
329 .url_close = concat_close,
330 .priv_data_size = sizeof(struct concat_data),
331 .default_whitelist = "concatf,concat,file,subfile",
332 };
333 #endif
334