Coverage Report

Created: 2025-09-04 07:15

/src/mpv/stream/stream_concat.c
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 <libavutil/common.h>
19
20
#include "common/common.h"
21
#include "stream.h"
22
23
struct priv {
24
    struct stream **streams;
25
    int num_streams;
26
27
    int64_t size;
28
29
    int cur; // streams[cur] is the stream for current stream.pos
30
};
31
32
static int fill_buffer(struct stream *s, void *buffer, int len)
33
0
{
34
0
    struct priv *p = s->priv;
35
36
0
    while (1) {
37
0
        int res = stream_read_partial(p->streams[p->cur], buffer, len);
38
0
        if (res || p->cur == p->num_streams - 1)
39
0
            return res;
40
41
0
        p->cur += 1;
42
0
        if (s->seekable)
43
0
            stream_seek(p->streams[p->cur], 0);
44
0
    }
45
0
}
46
47
static int seek(struct stream *s, int64_t newpos)
48
0
{
49
0
    struct priv *p = s->priv;
50
51
0
    int64_t next_pos = 0;
52
0
    int64_t base_pos = 0;
53
54
    // Look for the last stream whose start position is <= newpos.
55
    // Note that the last stream's size is essentially ignored. The last
56
    // stream is allowed to have an unknown size.
57
0
    for (int n = 0; n < p->num_streams; n++) {
58
0
        if (next_pos > newpos)
59
0
            break;
60
61
0
        base_pos = next_pos;
62
0
        p->cur = n;
63
64
0
        int64_t size = stream_get_size(p->streams[n]);
65
0
        if (size < 0)
66
0
            break;
67
68
0
        next_pos = base_pos + size;
69
0
    }
70
71
0
    int ok = stream_seek(p->streams[p->cur], newpos - base_pos);
72
0
    s->pos = base_pos + stream_tell(p->streams[p->cur]);
73
0
    return ok;
74
0
}
75
76
static int64_t get_size(struct stream *s)
77
0
{
78
0
    struct priv *p = s->priv;
79
0
    return p->size;
80
0
}
81
82
static void s_close(struct stream *s)
83
0
{
84
0
    struct priv *p = s->priv;
85
86
0
    for (int n = 0; n < p->num_streams; n++)
87
0
        free_stream(p->streams[n]);
88
0
}
89
90
// Return the "worst" origin value of the two. cur can be 0 to mean unset.
91
static int combine_origin(int cur, int new)
92
0
{
93
0
    if (cur == STREAM_ORIGIN_UNSAFE || new == STREAM_ORIGIN_UNSAFE)
94
0
        return STREAM_ORIGIN_UNSAFE;
95
0
    if (cur == STREAM_ORIGIN_NET || new == STREAM_ORIGIN_NET)
96
0
        return STREAM_ORIGIN_NET;
97
0
    if (cur == STREAM_ORIGIN_FS || new == STREAM_ORIGIN_FS)
98
0
        return STREAM_ORIGIN_FS;
99
0
    return new; // including cur==0
100
0
}
101
102
static int open2(struct stream *stream, const struct stream_open_args *args)
103
0
{
104
0
    struct priv *p = talloc_zero(stream, struct priv);
105
0
    stream->priv = p;
106
107
0
    stream->fill_buffer = fill_buffer;
108
0
    stream->get_size = get_size;
109
0
    stream->close = s_close;
110
111
0
    stream->seekable = true;
112
113
0
    struct priv *list = args->special_arg;
114
0
    if (!list || !list->num_streams) {
115
0
        MP_FATAL(stream, "No sub-streams.\n");
116
0
        return STREAM_ERROR;
117
0
    }
118
119
0
    stream->stream_origin = 0;
120
121
0
    for (int n = 0; n < list->num_streams; n++) {
122
0
        struct stream *sub = list->streams[n];
123
124
0
        if (sub->is_directory) {
125
0
            MP_FATAL(stream, "Sub stream %d is a directory.\n", n);
126
0
            return STREAM_ERROR;
127
0
        }
128
129
0
        int64_t size = stream_get_size(sub);
130
0
        if (n != list->num_streams - 1 && size < 0) {
131
0
            MP_WARN(stream, "Sub stream %d has unknown size.\n", n);
132
0
            stream->seekable = false;
133
0
            p->size = -1;
134
0
        } else if (size >= 0 && p->size >= 0) {
135
0
            p->size += size;
136
0
        }
137
138
0
        if (!sub->seekable)
139
0
            stream->seekable = false;
140
141
0
        stream->stream_origin =
142
0
            combine_origin(stream->stream_origin, sub->stream_origin);
143
144
0
        MP_TARRAY_APPEND(p, p->streams, p->num_streams, sub);
145
0
    }
146
147
0
    if (stream->seekable)
148
0
        stream->seek = seek;
149
150
0
    return STREAM_OK;
151
0
}
152
153
static const stream_info_t stream_info_concat = {
154
    .name = "concat",
155
    .open2 = open2,
156
    .protocols = (const char*const[]){ "concat", NULL },
157
};
158
159
// Create a stream with a concatenated view on streams[]. Copies the streams
160
// array. Takes over ownership of every stream passed to it (it will free them
161
// if the concat stream is closed).
162
// If an error happens, NULL is returned, and the streams are not freed.
163
struct stream *stream_concat_open(struct mpv_global *global, struct mp_cancel *c,
164
                                  struct stream **streams, int num_streams)
165
0
{
166
    // (struct priv is blatantly abused to pass in the stream list)
167
0
    struct priv arg = {
168
0
        .streams = streams,
169
0
        .num_streams = num_streams,
170
0
    };
171
172
0
    struct stream_open_args sargs = {
173
0
        .global = global,
174
0
        .cancel = c,
175
0
        .url = "concat://",
176
0
        .flags = STREAM_READ | STREAM_SILENT | STREAM_ORIGIN_DIRECT,
177
0
        .sinfo = &stream_info_concat,
178
0
        .special_arg = &arg,
179
0
    };
180
181
0
    struct stream *s = NULL;
182
0
    stream_create_with_args(&sargs, &s);
183
0
    return s;
184
0
}