concat.c 8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*****************************************************************************
 * concat.c: Concatenated inputs
 *****************************************************************************
 * Copyright (C) 2015 Rémi Denis-Courmont
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <assert.h>
26
#include <stdint.h>
27 28 29 30 31 32 33 34 35 36 37 38 39

#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_access.h>

struct access_entry
{
    struct access_entry *next;
    char mrl[1];
};

struct access_sys_t
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
40
    stream_t *access;
41 42 43 44 45 46 47 48 49 50
    struct access_entry *next;
    struct access_entry *first;
    bool can_seek;
    bool can_seek_fast;
    bool can_pause;
    bool can_control_pace;
    uint64_t size;
    int64_t caching;
};

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
51
static stream_t *GetAccess(stream_t *access)
52 53
{
    access_sys_t *sys = access->p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
54
    stream_t *a = sys->access;
55 56 57

    if (a != NULL)
    {
58
        if (!vlc_stream_Eof(a))
59 60
            return a;

61
        vlc_stream_Delete(a);
62 63 64 65 66 67 68 69
        sys->access = NULL;
    }

    if (sys->next == NULL)
        return NULL;

    a = vlc_access_NewMRL(VLC_OBJECT(access), sys->next->mrl);
    if (a == NULL)
70
        return NULL;
71 72 73 74 75 76

    sys->access = a;
    sys->next = sys->next->next;
    return a;
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
77
static ssize_t Read(stream_t *access, void *buf, size_t len)
78
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
79
    stream_t *a = GetAccess(access);
80 81 82 83 84 85 86 87 88
    if (a == NULL)
        return 0;

    /* NOTE: Since we recreate the underlying access, the access method can
     * change. We need to check it. For instance, a path could point to a
     * regular file during Open() yet point to a directory here and now. */
    if (unlikely(a->pf_read == NULL))
        return 0;

89
    return vlc_stream_ReadPartial(a, buf, len);
90 91
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
92
static block_t *Block(stream_t *access, bool *restrict eof)
93
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
94
    stream_t *a = GetAccess(access);
95
    if (a == NULL)
96 97
    {
        *eof = true;
98
        return NULL;
99
    }
100

101
    return vlc_stream_ReadBlock(a);
102 103
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
104
static int Seek(stream_t *access, uint64_t position)
105 106 107 108 109
{
    access_sys_t *sys = access->p_sys;

    if (sys->access != NULL)
    {
110
        vlc_stream_Delete(sys->access);
111 112 113 114 115
        sys->access = NULL;
    }

    sys->next = sys->first;

116
    for (uint64_t offset = 0;;)
117
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
118
        stream_t *a = GetAccess(access);
119 120 121 122
        if (a == NULL)
            break;

        bool can_seek;
123
        vlc_stream_Control(a, STREAM_CAN_SEEK, &can_seek);
124 125 126
        if (!can_seek)
            break;

127
        uint64_t size;
128

129
        if (vlc_stream_GetSize(a, &size))
130
            break;
131
        if (position - offset < size)
132
        {
133
            if (vlc_stream_Seek(a, position - offset))
134 135 136 137
                break;
            return VLC_SUCCESS;
        }

138
        offset += size;
139
        vlc_stream_Delete(a);
140 141 142 143 144 145
        sys->access = NULL;
    }

    return VLC_EGENERIC;
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
146
static int Control(stream_t *access, int query, va_list args)
147 148 149 150 151
{
    access_sys_t *sys = access->p_sys;

    switch (query)
    {
152
        case STREAM_CAN_SEEK:
153 154
            *va_arg(args, bool *) = sys->can_seek;
            break;
155
        case STREAM_CAN_FASTSEEK:
156 157
            *va_arg(args, bool *) = sys->can_seek_fast;
            break;
158
        case STREAM_CAN_PAUSE:
159 160
            *va_arg(args, bool *) = sys->can_pause;
            break;
161
        case STREAM_CAN_CONTROL_PACE:
162 163
            *va_arg(args, bool *) = sys->can_control_pace;
            break;
164
        case STREAM_GET_SIZE:
165 166
            if (sys->size == UINT64_MAX)
                return VLC_EGENERIC;
167 168
            *va_arg(args, uint64_t *) = sys->size;
            break;
169
        case STREAM_GET_PTS_DELAY:
170 171 172
            *va_arg(args, int64_t *) = sys->caching;
            break;

173 174
        case STREAM_GET_SIGNAL:
        case STREAM_SET_PAUSE_STATE:
175
            return vlc_stream_vaControl(sys->access, query, args);
176 177 178 179 180 181 182 183 184

        default:
            return VLC_EGENERIC;
    }
    return VLC_SUCCESS;
}

static int Open(vlc_object_t *obj)
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
185
    stream_t *access = (stream_t *)obj;
186

187
    char *list = var_CreateGetNonEmptyString(access, "concat-list");
188 189 190
    if (list == NULL)
        return VLC_EGENERIC;

191
    access_sys_t *sys = vlc_obj_malloc(obj, sizeof (*sys));
192 193 194 195 196 197
    if (unlikely(sys == NULL))
    {
        free(list);
        return VLC_ENOMEM;
    }

198 199
    var_SetString(access, "concat-list", ""); /* prevent recursion */

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
    bool read_cb = true;

    sys->access = NULL;
    sys->can_seek = true;
    sys->can_seek_fast = true;
    sys->can_pause = true;
    sys->can_control_pace = true;
    sys->size = 0;
    sys->caching = 0;

    struct access_entry **pp = &sys->first;

    for (char *buf, *mrl = strtok_r(list, ",", &buf);
         mrl != NULL;
         mrl = strtok_r(NULL, ",", &buf))
    {
        size_t mlen = strlen(mrl);
        struct access_entry *e = malloc(sizeof (*e) + mlen);
        if (unlikely(e == NULL))
            break;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
221
        stream_t *a = vlc_access_NewMRL(obj, mrl);
222 223 224 225 226 227 228 229 230 231 232 233
        if (a == NULL)
        {
            msg_Err(access, "cannot concatenate location %s", mrl);
            free(e);
            continue;
        }

        if (a->pf_read == NULL)
        {
            if (a->pf_block == NULL)
            {
                msg_Err(access, "cannot concatenate directory %s", mrl);
234
                vlc_stream_Delete(a);
235 236 237 238 239 240 241 242 243 244 245
                free(e);
                continue;
            }
            read_cb = false;
        }

        *pp = e;
        e->next = NULL;
        memcpy(e->mrl, mrl, mlen + 1);

        if (sys->can_seek)
246
            vlc_stream_Control(a, STREAM_CAN_SEEK, &sys->can_seek);
247
        if (sys->can_seek_fast)
248
            vlc_stream_Control(a, STREAM_CAN_FASTSEEK, &sys->can_seek_fast);
249
        if (sys->can_pause)
250
            vlc_stream_Control(a, STREAM_CAN_PAUSE, &sys->can_pause);
251
        if (sys->can_control_pace)
252 253
            vlc_stream_Control(a, STREAM_CAN_CONTROL_PACE,
                               &sys->can_control_pace);
254 255 256
        if (sys->size != UINT64_MAX)
        {
            uint64_t size;
257

258
            if (vlc_stream_GetSize(a, &size))
259 260 261 262
                sys->size = UINT64_MAX;
            else
                sys->size += size;
        }
263 264

        int64_t caching;
265
        vlc_stream_Control(a, STREAM_GET_PTS_DELAY, &caching);
266 267 268
        if (caching > sys->caching)
            sys->caching = caching;

269
        vlc_stream_Delete(a);
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
        pp = &e->next;
    }

    free(list);
    *pp = NULL;
    sys->next = sys->first;

    access->pf_read = read_cb ? Read : NULL;
    access->pf_block = read_cb ? NULL : Block;
    access->pf_seek = Seek;
    access->pf_control = Control;
    access->p_sys = sys;

    return VLC_SUCCESS;
}

static void Close(vlc_object_t *obj)
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
288
    stream_t *access = (stream_t *)obj;
289 290 291
    access_sys_t *sys = access->p_sys;

    if (sys->access != NULL)
292
        vlc_stream_Delete(sys->access);
293 294 295 296 297 298 299

    for (struct access_entry *e = sys->first, *next; e != NULL; e = next)
    {
        next = e->next;
        free(e);
    }

300
    var_Destroy(access, "concat-list");
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
}

#define INPUT_LIST_TEXT N_("Inputs list")
#define INPUT_LIST_LONGTEXT N_( \
    "Comma-separated list of input URLs to concatenate.")

vlc_module_begin()
    set_shortname(N_("Concatenation"))
    set_description(N_("Concatenated inputs"))
    set_category(CAT_INPUT)
    set_subcategory(SUBCAT_INPUT_ACCESS)
    add_string("concat-list", NULL, INPUT_LIST_TEXT, INPUT_LIST_LONGTEXT, true)
    set_capability("access", 0)
    set_callbacks(Open, Close)
    add_shortcut("concast", "list")
vlc_module_end()