summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2013-08-25 22:58:29 +0200
committerwm4 <wm4@nowhere>2013-08-26 10:09:46 +0200
commit3fc3bf70f972f4e68258193f0f9c70ee6b85be5e (patch)
tree7c953cee512669da24d83c1b6ffefce74f3ddc53
parent74b846e2f7d95e4dea2fdae4b1301a2c83f2acd3 (diff)
downloadmpv-3fc3bf70f972f4e68258193f0f9c70ee6b85be5e.tar.bz2
mpv-3fc3bf70f972f4e68258193f0f9c70ee6b85be5e.tar.xz
stream: add uncompressed rar support
Apparently, it is popular to store large files in uncompressed rar archives. Extracting files is not practical, and some media players suport playing directly from uncompressed rar (at least VLC and some DirectShow components). Storing or accessing files this way is completely idiotic, but it is a common practice, and the ones subjected to this practice can't do much to change this (at least that's what I assume/hope). Also, it's a feature request, so we say yes. This code is mostly taken from VLC (commit f6e7240 from their git tree). We also copy the way this is done: opening a rar file by itself yields a playlist, which contains URLs to the actual entries in the rar file. Compressed entries are simply skipped.
-rw-r--r--Makefile2
-rw-r--r--demux/demux.c4
-rw-r--r--stream/rar.c454
-rw-r--r--stream/rar.h60
-rw-r--r--stream/stream.c34
-rw-r--r--stream/stream.h1
-rw-r--r--stream/stream_rar.c198
7 files changed, 753 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index a2bb6b7300..440820b281 100644
--- a/Makefile
+++ b/Makefile
@@ -208,6 +208,7 @@ SOURCES = talloc.c \
osdep/numcores.c \
osdep/timer.c \
stream/cookies.c \
+ stream/rar.c \
stream/stream.c \
stream/stream_avdevice.c \
stream/stream_file.c \
@@ -215,6 +216,7 @@ SOURCES = talloc.c \
stream/stream_memory.c \
stream/stream_mf.c \
stream/stream_null.c \
+ stream/stream_rar.c \
sub/dec_sub.c \
sub/draw_bmp.c \
sub/find_subfiles.c \
diff --git a/demux/demux.c b/demux/demux.c
index 404050a420..f8653b4411 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -604,6 +604,10 @@ struct demuxer *demux_open(struct stream *stream, char *force_format,
}
}
+ // Peek this much data to avoid that stream_read() run by some demuxers
+ // or stream filters will flush previous peeked data.
+ stream_peek(stream, STREAM_BUFFER_SIZE);
+
// Test demuxers from first to last, one pass for each check_levels[] entry
for (int pass = 0; check_levels[pass] != -1; pass++) {
enum demux_check level = check_levels[pass];
diff --git a/stream/rar.c b/stream/rar.c
new file mode 100644
index 0000000000..fd4dbf008d
--- /dev/null
+++ b/stream/rar.c
@@ -0,0 +1,454 @@
+/*****************************************************************************
+ * rar.c: uncompressed RAR parser
+ *****************************************************************************
+ * Copyright (C) 2008-2010 Laurent Aimar
+ * $Id: f368245f4260f913f5c211e09b7dd511a96525e6 $
+ *
+ * Author: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
+ *
+ * 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include <libavutil/intreadwrite.h>
+
+#include "mpvcore/mp_common.h"
+#include "mpvcore/mp_talloc.h"
+#include "stream.h"
+#include "rar.h"
+
+static const uint8_t rar_marker[] = {
+ 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00
+};
+static const int rar_marker_size = sizeof(rar_marker);
+
+void RarFileDelete(rar_file_t *file)
+{
+ for (int i = 0; i < file->chunk_count; i++) {
+ free(file->chunk[i]->mrl);
+ free(file->chunk[i]);
+ }
+ talloc_free(file->chunk);
+ free(file->name);
+ free_stream(file->s);
+ free(file);
+}
+
+typedef struct {
+ uint16_t crc;
+ uint8_t type;
+ uint16_t flags;
+ uint16_t size;
+ uint32_t add_size;
+} rar_block_t;
+
+enum {
+ RAR_BLOCK_MARKER = 0x72,
+ RAR_BLOCK_ARCHIVE = 0x73,
+ RAR_BLOCK_FILE = 0x74,
+ RAR_BLOCK_SUBBLOCK = 0x7a,
+ RAR_BLOCK_END = 0x7b,
+};
+enum {
+ RAR_BLOCK_END_HAS_NEXT = 0x0001,
+};
+enum {
+ RAR_BLOCK_FILE_HAS_PREVIOUS = 0x0001,
+ RAR_BLOCK_FILE_HAS_NEXT = 0x0002,
+ RAR_BLOCK_FILE_HAS_HIGH = 0x0100,
+};
+
+static int PeekBlock(struct stream *s, rar_block_t *hdr)
+{
+ bstr data = stream_peek(s, 11);
+ const uint8_t *peek = (uint8_t *)data.start;
+ int peek_size = data.len;
+
+ if (peek_size < 7)
+ return -1;
+
+ hdr->crc = AV_RL16(&peek[0]);
+ hdr->type = peek[2];
+ hdr->flags = AV_RL16(&peek[3]);
+ hdr->size = AV_RL16(&peek[5]);
+ hdr->add_size = 0;
+ if ((hdr->flags & 0x8000) ||
+ hdr->type == RAR_BLOCK_FILE ||
+ hdr->type == RAR_BLOCK_SUBBLOCK) {
+ if (peek_size < 11)
+ return -1;
+ hdr->add_size = AV_RL32(&peek[7]);
+ }
+
+ if (hdr->size < 7)
+ return -1;
+ return 0;
+}
+static int SkipBlock(struct stream *s, const rar_block_t *hdr)
+{
+ uint64_t size = (uint64_t)hdr->size + hdr->add_size;
+
+ while (size > 0) {
+ int skip = MPMIN(size, INT_MAX);
+ if (!stream_skip(s, skip))
+ return -1;
+
+ size -= skip;
+ }
+ return 0;
+}
+
+static int IgnoreBlock(struct stream *s, int block)
+{
+ /* */
+ rar_block_t bk;
+ if (PeekBlock(s, &bk) || bk.type != block)
+ return -1;
+ return SkipBlock(s, &bk);
+}
+
+static int SkipEnd(struct stream *s, const rar_block_t *hdr)
+{
+ if (!(hdr->flags & RAR_BLOCK_END_HAS_NEXT))
+ return -1;
+
+ if (SkipBlock(s, hdr))
+ return -1;
+
+ /* Now, we need to look for a marker block,
+ * It seems that there is garbage at EOF */
+ for (;;) {
+ bstr peek = stream_peek(s, rar_marker_size);
+
+ if (peek.len < rar_marker_size)
+ return -1;
+
+ if (!memcmp(peek.start, rar_marker, rar_marker_size))
+ break;
+
+ if (!stream_skip(s, 1))
+ return -1;
+ }
+
+ /* Skip marker and archive blocks */
+ if (IgnoreBlock(s, RAR_BLOCK_MARKER))
+ return -1;
+ if (IgnoreBlock(s, RAR_BLOCK_ARCHIVE))
+ return -1;
+
+ return 0;
+}
+
+static int SkipFile(struct stream *s, int *count, rar_file_t ***file,
+ const rar_block_t *hdr, const char *volume_mrl)
+{
+ int min_size = 7+21;
+ if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH)
+ min_size += 8;
+ if (hdr->size < (unsigned)min_size)
+ return -1;
+
+ bstr data = stream_peek(s, min_size);
+ if (data.len < min_size)
+ return -1;
+ const uint8_t *peek = (uint8_t *)data.start;
+
+ /* */
+ uint32_t file_size_low = AV_RL32(&peek[7+4]);
+ uint8_t method = peek[7+18];
+ uint16_t name_size = AV_RL16(&peek[7+19]);
+ uint32_t file_size_high = 0;
+ if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH)
+ file_size_high = AV_RL32(&peek[7+29]);
+ const uint64_t file_size = ((uint64_t)file_size_high << 32) | file_size_low;
+
+ char *name = calloc(1, name_size + 1);
+ if (!name)
+ return -1;
+
+ const int name_offset = (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH) ? (7+33) : (7+25);
+ if (name_offset + name_size <= hdr->size) {
+ const int max_size = name_offset + name_size;
+ bstr data = stream_peek(s, max_size);
+ if (data.len < max_size) {
+ free(name);
+ return -1;
+ }
+ memcpy(name, &data.start[name_offset], name_size);
+ }
+
+ rar_file_t *current = NULL;
+ if (method != 0x30) {
+ mp_msg(MSGT_STREAM, MSGL_WARN, "Ignoring compressed file %s (method=0x%2.2x)\n", name, method);
+ goto exit;
+ }
+
+ /* */
+ if( *count > 0 )
+ current = (*file)[*count - 1];
+
+ if (current &&
+ (current->is_complete ||
+ strcmp(current->name, name) ||
+ (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS) == 0))
+ current = NULL;
+
+ if (!current) {
+ if (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS)
+ goto exit;
+ current = calloc(1, sizeof(*current));
+ if (!current)
+ goto exit;
+ MP_TARRAY_APPEND(NULL, *file, *count, current);
+
+ current->name = name;
+ current->size = file_size;
+ current->is_complete = false;
+ current->real_size = 0;
+ current->chunk_count = 0;
+ current->chunk = NULL;
+
+ name = NULL;
+ }
+
+ /* Append chunks */
+ rar_file_chunk_t *chunk = malloc(sizeof(*chunk));
+ if (chunk) {
+ chunk->mrl = strdup(volume_mrl);
+ chunk->offset = stream_tell(s) + hdr->size;
+ chunk->size = hdr->add_size;
+ chunk->cummulated_size = 0;
+ if (current->chunk_count > 0) {
+ rar_file_chunk_t *previous = current->chunk[current->chunk_count-1];
+
+ chunk->cummulated_size += previous->cummulated_size +
+ previous->size;
+ }
+
+ MP_TARRAY_APPEND(NULL, current->chunk, current->chunk_count, chunk);
+
+ current->real_size += hdr->add_size;
+ }
+ if ((hdr->flags & RAR_BLOCK_FILE_HAS_NEXT) == 0)
+ current->is_complete = true;
+
+exit:
+ /* */
+ free(name);
+
+ /* We stop on the first non empty file if we cannot seek */
+ if (current) {
+ bool can_seek = s->end_pos > 0;
+ if (!can_seek && current->size > 0)
+ return -1;
+ }
+
+ if (SkipBlock(s, hdr))
+ return -1;
+ return 0;
+}
+
+int RarProbe(struct stream *s)
+{
+ bstr peek = stream_peek(s, rar_marker_size);
+ if (peek.len < rar_marker_size)
+ return -1;
+ if (memcmp(peek.start, rar_marker, rar_marker_size))
+ return -1;
+ return 0;
+}
+
+typedef struct {
+ const char *match;
+ const char *format;
+ int start;
+ int stop;
+} rar_pattern_t;
+
+static const rar_pattern_t *FindVolumePattern(const char *location)
+{
+ static const rar_pattern_t patterns[] = {
+ { ".part1.rar", "%s.part%.1d.rar", 2, 9 },
+ { ".part01.rar", "%s.part%.2d.rar", 2, 99, },
+ { ".part001.rar", "%s.part%.3d.rar", 2, 999 },
+ { ".rar", "%s.%c%.2d", 0, 999 },
+ { NULL, NULL, 0, 0 },
+ };
+
+ const size_t location_size = strlen(location);
+ for (int i = 0; patterns[i].match != NULL; i++) {
+ const size_t match_size = strlen(patterns[i].match);
+
+ if (location_size < match_size)
+ continue;
+ if (!strcmp(&location[location_size - match_size], patterns[i].match))
+ return &patterns[i];
+ }
+ return NULL;
+}
+
+int RarParse(struct stream *s, int *count, rar_file_t ***file)
+{
+ *count = 0;
+ *file = NULL;
+
+ const rar_pattern_t *pattern = FindVolumePattern(s->url);
+ int volume_offset = 0;
+
+ char *volume_mrl;
+ if (asprintf(&volume_mrl, "%s", s->url) < 0)
+ return -1;
+
+ struct stream *vol = s;
+ for (;;) {
+ /* Skip marker & archive */
+ if (IgnoreBlock(vol, RAR_BLOCK_MARKER) ||
+ IgnoreBlock(vol, RAR_BLOCK_ARCHIVE)) {
+ if (vol != s)
+ free_stream(vol);
+ free(volume_mrl);
+ return -1;
+ }
+
+ /* */
+ int has_next = -1;
+ for (;;) {
+ rar_block_t bk;
+ int ret;
+
+ if (PeekBlock(vol, &bk))
+ break;
+
+ switch(bk.type) {
+ case RAR_BLOCK_END:
+ ret = SkipEnd(vol, &bk);
+ has_next = ret && (bk.flags & RAR_BLOCK_END_HAS_NEXT);
+ break;
+ case RAR_BLOCK_FILE:
+ ret = SkipFile(vol, count, file, &bk, volume_mrl);
+ break;
+ default:
+ ret = SkipBlock(vol, &bk);
+ break;
+ }
+ if (ret)
+ break;
+ }
+ if (has_next < 0 && *count > 0 && !(*file)[*count -1]->is_complete)
+ has_next = 1;
+ if (vol != s)
+ free_stream(vol);
+
+ if (!has_next || !pattern)
+ goto done;
+
+ /* Open next volume */
+ const int volume_index = pattern->start + volume_offset++;
+ if (volume_index > pattern->stop)
+ goto done;
+
+ char *volume_base;
+ if (asprintf(&volume_base, "%.*s",
+ (int)(strlen(s->url) - strlen(pattern->match)), s->url) < 0) {
+ goto done;
+ }
+
+ free(volume_mrl);
+ if (pattern->start) {
+ if (asprintf(&volume_mrl, pattern->format, volume_base, volume_index) < 0)
+ volume_mrl = NULL;
+ } else {
+ if (asprintf(&volume_mrl, pattern->format, volume_base,
+ 'r' + volume_index / 100, volume_index % 100) < 0)
+ volume_mrl = NULL;
+ }
+ free(volume_base);
+
+ if (!volume_mrl)
+ goto done;
+
+ vol = stream_create(volume_mrl, STREAM_READ | STREAM_NO_FILTERS, s->opts);
+
+ if (!vol)
+ goto done;
+ }
+
+done:
+ free(volume_mrl);
+ if (*count == 0) {
+ talloc_free(*file);
+ return -1;
+ }
+ return 0;
+}
+
+int RarSeek(rar_file_t *file, uint64_t position)
+{
+ if (position > file->real_size)
+ position = file->real_size;
+
+ /* Search the chunk */
+ const rar_file_chunk_t *old_chunk = file->current_chunk;
+ for (int i = 0; i < file->chunk_count; i++) {
+ file->current_chunk = file->chunk[i];
+ if (position < file->current_chunk->cummulated_size + file->current_chunk->size)
+ break;
+ }
+ file->i_pos = position;
+
+ const uint64_t offset = file->current_chunk->offset +
+ (position - file->current_chunk->cummulated_size);
+
+ if (strcmp(old_chunk->mrl, file->current_chunk->mrl)) {
+ if (file->s)
+ free_stream(file->s);
+ file->s = stream_create(file->current_chunk->mrl,
+ STREAM_READ | STREAM_NO_FILTERS,
+ file->opts);
+ }
+ return file->s ? stream_seek(file->s, offset) : 0;
+}
+
+ssize_t RarRead(rar_file_t *file, void *data, size_t size)
+{
+ size_t total = 0;
+ while (total < size) {
+ const uint64_t chunk_end = file->current_chunk->cummulated_size + file->current_chunk->size;
+ int max = MPMIN(MPMIN((int64_t)(size - total), (int64_t)(chunk_end - file->i_pos)), INT_MAX);
+ if (max <= 0)
+ break;
+
+ int r = file->s ? stream_read(file->s, data, max) : -1;
+ if (r <= 0)
+ break;
+
+ total += r;
+ if( data )
+ data = (char *)data + r;
+ file->i_pos += r;
+ if (file->i_pos >= chunk_end &&
+ RarSeek(file, file->i_pos))
+ break;
+ }
+ return total;
+
+}
diff --git a/stream/rar.h b/stream/rar.h
new file mode 100644
index 0000000000..6b001244a5
--- /dev/null
+++ b/stream/rar.h
@@ -0,0 +1,60 @@
+/*****************************************************************************
+ * rar.h: uncompressed RAR parser
+ *****************************************************************************
+ * Copyright (C) 2008-2010 Laurent Aimar
+ * $Id: 4dea45925c2d8f319d692475bc0307fdd9f6cfe7 $
+ *
+ * Author: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
+ *
+ * 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.
+ *****************************************************************************/
+
+#ifndef MP_RAR_H
+#define MP_RAR_H
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+typedef struct {
+ char *mrl;
+ uint64_t offset;
+ uint64_t size;
+ uint64_t cummulated_size;
+} rar_file_chunk_t;
+
+typedef struct {
+ char *name;
+ uint64_t size;
+ bool is_complete;
+
+ int chunk_count;
+ rar_file_chunk_t **chunk;
+ uint64_t real_size; /* Gathered size */
+
+ // When actually reading the data
+ struct MPOpts *opts;
+ uint64_t i_pos;
+ stream_t *s;
+ rar_file_chunk_t *current_chunk;
+} rar_file_t;
+
+int RarProbe(struct stream *);
+void RarFileDelete(rar_file_t *);
+int RarParse(struct stream *, int *, rar_file_t ***);
+
+int RarSeek(rar_file_t *file, uint64_t position);
+ssize_t RarRead(rar_file_t *file, void *data, size_t size);
+
+#endif
diff --git a/stream/stream.c b/stream/stream.c
index 474e46d96a..ebd582de73 100644
--- a/stream/stream.c
+++ b/stream/stream.c
@@ -75,6 +75,8 @@ extern const stream_info_t stream_info_file;
extern const stream_info_t stream_info_ifo;
extern const stream_info_t stream_info_dvd;
extern const stream_info_t stream_info_bluray;
+extern const stream_info_t stream_info_rar_filter;
+extern const stream_info_t stream_info_rar_entry;
static const stream_info_t *const stream_list[] = {
#ifdef CONFIG_VCD
@@ -111,6 +113,8 @@ static const stream_info_t *const stream_list[] = {
&stream_info_memory,
&stream_info_null,
&stream_info_mf,
+ &stream_info_rar_filter,
+ &stream_info_rar_entry,
&stream_info_file,
NULL
};
@@ -148,6 +152,36 @@ void mp_url_unescape_inplace(char *buf)
buf[o++] = '\0';
}
+// Escape according to http://tools.ietf.org/html/rfc3986#section-2.1
+// Only unreserved characters are not escaped.
+// The argument ok (if not NULL) is as follows:
+// ok[0] != '~': additional characters that are not escaped
+// ok[0] == '~': do not escape anything but these characters
+// (can't override the unreserved characters, which are
+// never escaped, and '%', which is always escaped)
+char *mp_url_escape(void *talloc_ctx, const char *s, const char *ok)
+{
+ int len = strlen(s);
+ char *buf = talloc_array(talloc_ctx, char, len * 3 + 1);
+ int o = 0;
+ for (int i = 0; i < len; i++) {
+ unsigned char c = s[i];
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') || strchr("-._~", c) ||
+ (ok && ((ok[0] != '~') == !!strchr(ok, c)) && c != '%'))
+ {
+ buf[o++] = c;
+ } else {
+ const char hex[] = "0123456789ABCDEF";
+ buf[o++] = '%';
+ buf[o++] = hex[c / 16];
+ buf[o++] = hex[c % 16];
+ }
+ }
+ buf[o++] = '\0';
+ return buf;
+}
+
static const char *find_url_opt(struct stream *s, const char *opt)
{
for (int n = 0; s->info->url_options && s->info->url_options[n]; n++) {
diff --git a/stream/stream.h b/stream/stream.h
index 6f389ceb04..525265b52c 100644
--- a/stream/stream.h
+++ b/stream/stream.h
@@ -271,5 +271,6 @@ typedef struct {
} stream_language_t;
void mp_url_unescape_inplace(char *buf);
+char *mp_url_escape(void *talloc_ctx, const char *s, const char *ok);
#endif /* MPLAYER_STREAM_H */
diff --git a/stream/stream_rar.c b/stream/stream_rar.c
new file mode 100644
index 0000000000..d3ccfa3bf3
--- /dev/null
+++ b/stream/stream_rar.c
@@ -0,0 +1,198 @@
+// Major parts based on:
+/*****************************************************************************
+ * access.c: uncompressed RAR access
+ *****************************************************************************
+ * Copyright (C) 2008-2010 Laurent Aimar
+ * $Id: dcd973529e0029abe326d31f8d58cd13bbcc276c $
+ *
+ * Author: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
+ *
+ * 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.
+ *****************************************************************************/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "osdep/io.h"
+
+#include "mpvcore/mp_msg.h"
+#include "stream.h"
+#include "mpvcore/m_option.h"
+#include "rar.h"
+
+/*
+This works as follows:
+
+- stream_open() with file01.rar
+ - is opened as normal file (stream_file.c or others) first
+ - stream_info_rar_filter stream filter applies
+ - leads to rar_filter_open()
+ - if multi-part, opens file02.rar, file03.rar, etc. as actual streams
+ (recursive opening is prevented with the STREAM_NO_FILTERS flag)
+ - read accesses return a m3u playlist with entries like:
+ rar://bla01.rar|subfile.mkv
+ (one such entry for each file contained in the rar)
+- stream_open() with the playlist entry, e.g. rar://bla01.rar|subfile.mkv
+ - leads to rar_entry_open()
+ - opens bla01.rar etc. again as actual streams
+ (again, STREAM_NO_FILTERS to open the actual files)
+ - read accesses go into subfile.mkv contained in the rar file(s)
+*/
+
+static int rar_entry_fill_buffer(stream_t *s, char *buffer, int max_len)
+{
+ rar_file_t *rar_file = s->priv;
+ return RarRead(rar_file, buffer, max_len);
+}
+
+static int rar_entry_seek(stream_t *s, int64_t newpos)
+{
+ rar_file_t *rar_file = s->priv;
+ return RarSeek(rar_file, newpos);
+}
+
+static void rar_entry_close(stream_t *s)
+{
+ rar_file_t *rar_file = s->priv;
+ RarFileDelete(rar_file);
+}
+
+static int rar_entry_open(stream_t *stream, int mode)
+{
+ if (!strchr(stream->path, '|'))
+ return STREAM_ERROR;
+
+ char *base = talloc_strdup(stream, stream->path);
+ char *name = strchr(base, '|');
+ *name++ = '\0';
+ mp_url_unescape_inplace(base);
+
+ struct stream *rar =
+ stream_create(base, STREAM_READ | STREAM_NO_FILTERS, stream->opts);
+ if (!rar)
+ return STREAM_ERROR;
+
+ int count;
+ rar_file_t **files;
+ if (RarProbe(rar) || RarParse(rar, &count, &files)) {
+ free_stream(rar);
+ return STREAM_ERROR;
+ }
+
+ rar_file_t *file = NULL;
+ for (int i = 0; i < count; i++) {
+ if (!file && strcmp(files[i]->name, name) == 0)
+ file = files[i];
+ else
+ RarFileDelete(files[i]);
+ }
+ talloc_free(files);
+ if (!file) {
+ free_stream(rar);
+ return STREAM_ERROR;
+ }
+
+ rar_file_chunk_t dummy = {
+ .mrl = base,
+ };
+ file->current_chunk = &dummy;
+ file->s = rar; // transfer ownership
+ file->opts = stream->opts;
+ RarSeek(file, 0);
+
+ stream->priv = file;
+ stream->end_pos = file->size;
+ stream->fill_buffer = rar_entry_fill_buffer;
+ stream->seek = rar_entry_seek;
+ stream->close = rar_entry_close;
+
+ return STREAM_OK;
+}
+
+static int rar_filter_fill_buffer(stream_t *s, char *buffer, int max_len)
+{
+ struct stream *m = s->priv;
+ return stream_read_partial(m, buffer, max_len);
+}
+
+static int rar_filter_seek(stream_t *s, int64_t newpos)
+{
+ struct stream *m = s->priv;
+ return stream_seek(m, newpos);
+}
+
+static void rar_filter_close(stream_t *s)
+{
+ struct stream *m = s->priv;
+ free_stream(m);
+}
+
+static int rar_filter_open(stream_t *stream, int mode)
+{
+ if (mode != STREAM_READ)
+ return STREAM_UNSUPPORTED;
+
+ struct stream *rar = stream->source;
+ if (!rar)
+ return STREAM_UNSUPPORTED;
+
+ int count;
+ rar_file_t **files;
+ if (!rar || RarProbe(rar) || RarParse(rar, &count, &files))
+ return STREAM_UNSUPPORTED;
+
+ void *tmp = talloc_new(NULL);
+
+ // Create a playlist containing all entries of the .rar file. The URLs
+ // link to rar_entry_open().
+ char *prefix = mp_url_escape(tmp, stream->url, "~|");
+ char *pl = talloc_strdup(tmp, "#EXTM3U\n");
+ for (int n = 0; n < count; n++) {
+ pl = talloc_asprintf_append_buffer(pl, "rar://%s|%s\n",
+ prefix, files[n]->name);
+ RarFileDelete(files[n]);
+ }
+ talloc_free(files);
+
+ struct stream *m = open_memory_stream(pl, strlen(pl));
+
+ stream->priv = m;
+ stream->end_pos = m->end_pos;
+ stream->fill_buffer = rar_filter_fill_buffer;
+ stream->seek = rar_filter_seek;
+ stream->close = rar_filter_close;
+ stream->safe_origin = true;
+
+ talloc_free(tmp);
+ return STREAM_OK;
+}
+
+const stream_info_t stream_info_rar_entry = {
+ .name = "rar_entry",
+ .open = rar_entry_open,
+ .protocols = (const char*[]){ "rar", NULL },
+};
+
+const stream_info_t stream_info_rar_filter = {
+ .name = "rar_filter",
+ .open = rar_filter_open,
+ .stream_filter = true,
+};