/src/mpv/stream/stream_slice.c
Line | Count | Source |
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 | | #include <stdint.h> |
20 | | #include <string.h> |
21 | | #include <sys/types.h> |
22 | | |
23 | | #include "common/common.h" |
24 | | #include "options/m_option.h" |
25 | | #include "options/path.h" |
26 | | #include "stream.h" |
27 | | |
28 | | struct priv { |
29 | | int64_t slice_start; |
30 | | int64_t slice_max_end; // 0 for no limit |
31 | | struct stream *inner; |
32 | | }; |
33 | | |
34 | | static int fill_buffer(struct stream *s, void *buffer, int len) |
35 | 1.79k | { |
36 | 1.79k | struct priv *p = s->priv; |
37 | | |
38 | 1.79k | if (p->slice_max_end) { |
39 | | // We don't simply use (s->pos >= size) to avoid early return |
40 | | // if the file is still being appended to. |
41 | 426 | if (s->pos + p->slice_start >= p->slice_max_end) |
42 | 96 | return -1; |
43 | | // Avoid rading beyond p->slice_max_end |
44 | 330 | len = MPMIN(len, p->slice_max_end - s->pos); |
45 | 330 | } |
46 | | |
47 | 1.70k | return stream_read_partial(p->inner, buffer, len); |
48 | 1.79k | } |
49 | | |
50 | | static int seek(struct stream *s, int64_t newpos) |
51 | 209 | { |
52 | 209 | struct priv *p = s->priv; |
53 | 209 | return stream_seek(p->inner, newpos + p->slice_start); |
54 | 209 | } |
55 | | |
56 | | static int64_t get_size(struct stream *s) |
57 | 353 | { |
58 | 353 | struct priv *p = s->priv; |
59 | 353 | int64_t size = stream_get_size(p->inner); |
60 | 353 | if (size <= 0) |
61 | 162 | return size; |
62 | 191 | if (size <= p->slice_start) |
63 | 50 | return 0; |
64 | 141 | if (p->slice_max_end) |
65 | 30 | size = MPMIN(size, p->slice_max_end); |
66 | 141 | return size - p->slice_start; |
67 | 191 | } |
68 | | |
69 | | static void s_close(struct stream *s) |
70 | 144 | { |
71 | 144 | struct priv *p = s->priv; |
72 | 144 | free_stream(p->inner); |
73 | 144 | } |
74 | | |
75 | | static int parse_slice_range(stream_t *stream) |
76 | 4.38k | { |
77 | 4.38k | struct priv *p = stream->priv; |
78 | | |
79 | 4.38k | struct bstr b_url = bstr0(stream->url); |
80 | 4.38k | struct bstr proto_with_range, inner_url; |
81 | | |
82 | 4.38k | bool has_at = bstr_split_tok(b_url, "@", &proto_with_range, &inner_url); |
83 | | |
84 | 4.38k | if (!has_at) { |
85 | 1 | MP_ERR(stream, "Expected slice://start[-end]@URL: '%s'\n", stream->url); |
86 | 1 | return STREAM_ERROR; |
87 | 1 | } |
88 | | |
89 | 4.38k | if (!inner_url.len) { |
90 | 1 | MP_ERR(stream, "URL expected to follow 'slice://start[-end]@': '%s'.\n", stream->url); |
91 | 1 | return STREAM_ERROR; |
92 | 1 | } |
93 | 4.38k | stream->path = bstrto0(stream, inner_url); |
94 | | |
95 | 4.38k | mp_split_proto(proto_with_range, &proto_with_range); |
96 | 4.38k | struct bstr range = proto_with_range; |
97 | | |
98 | 4.38k | struct bstr start, end; |
99 | 4.38k | bool has_end = bstr_split_tok(range, "-", &start, &end); |
100 | | |
101 | 4.38k | if (!start.len) { |
102 | 1 | MP_ERR(stream, "The byte range must have a start, and it can't be negative: '%s'\n", stream->url); |
103 | 1 | return STREAM_ERROR; |
104 | 1 | } |
105 | | |
106 | 4.38k | if (has_end && !end.len) { |
107 | 1 | MP_ERR(stream, "The byte range end can be omitted, but it can't be empty: '%s'\n", stream->url); |
108 | 1 | return STREAM_ERROR; |
109 | 1 | } |
110 | | |
111 | 4.38k | const struct m_option opt = { |
112 | 4.38k | .type = &m_option_type_byte_size, |
113 | 4.38k | }; |
114 | | |
115 | 4.38k | if (m_option_parse(stream->log, &opt, bstr0("slice_start"), start, &p->slice_start) < 0) |
116 | 20 | return STREAM_ERROR; |
117 | | |
118 | 4.38k | bool max_end_is_offset = bstr_startswith0(end, "+"); |
119 | 4.36k | if (has_end) { |
120 | 438 | if (m_option_parse(stream->log, &opt, bstr0("slice_max_end"), end, &p->slice_max_end) < 0) |
121 | 21 | return STREAM_ERROR; |
122 | 438 | } |
123 | | |
124 | 4.34k | if (max_end_is_offset) |
125 | 1 | p->slice_max_end += p->slice_start; |
126 | | |
127 | 4.34k | if (p->slice_max_end && p->slice_max_end < p->slice_start) { |
128 | 3 | MP_ERR(stream, "The byte range end (%"PRId64") can't be smaller than the start (%"PRId64"): '%s'\n", |
129 | 3 | p->slice_max_end, |
130 | 3 | p->slice_start, |
131 | 3 | stream->url); |
132 | 3 | return STREAM_ERROR; |
133 | 3 | } |
134 | | |
135 | 4.34k | return STREAM_OK; |
136 | 4.34k | } |
137 | | |
138 | | static int open2(struct stream *stream, const struct stream_open_args *args) |
139 | 4.38k | { |
140 | 4.38k | struct priv *p = talloc_zero(stream, struct priv); |
141 | 4.38k | stream->priv = p; |
142 | | |
143 | 4.38k | stream->fill_buffer = fill_buffer; |
144 | 4.38k | stream->seek = seek; |
145 | 4.38k | stream->get_size = get_size; |
146 | 4.38k | stream->close = s_close; |
147 | | |
148 | 4.38k | int parse_ret = parse_slice_range(stream); |
149 | 4.38k | if (parse_ret != STREAM_OK) { |
150 | 48 | return parse_ret; |
151 | 48 | } |
152 | | |
153 | 4.34k | struct stream_open_args args2 = *args; |
154 | 4.34k | args2.url = stream->path; |
155 | 4.34k | int inner_ret = stream_create_with_args(&args2, &p->inner); |
156 | 4.34k | if (inner_ret != STREAM_OK) { |
157 | 4.19k | return inner_ret; |
158 | 4.19k | } |
159 | | |
160 | 144 | if (p->inner->is_directory) { |
161 | 0 | MP_FATAL(stream, "Inner stream '%s' is a directory\n", p->inner->url); |
162 | 0 | free_stream(p->inner); |
163 | 0 | return STREAM_ERROR; |
164 | 144 | } else if (!p->inner->seekable) { |
165 | 0 | MP_FATAL(stream, "Non-seekable stream '%s' can't be used with 'slice://'\n", p->inner->url); |
166 | 0 | free_stream(p->inner); |
167 | 0 | return STREAM_ERROR; |
168 | 0 | } |
169 | | |
170 | 144 | stream->seekable = 1; |
171 | 144 | stream->stream_origin = p->inner->stream_origin; |
172 | | |
173 | 144 | if (p->slice_start) |
174 | 111 | seek(stream, 0); |
175 | | |
176 | 144 | return STREAM_OK; |
177 | 144 | } |
178 | | |
179 | | const stream_info_t stream_info_slice = { |
180 | | .name = "slice", |
181 | | .open2 = open2, |
182 | | .protocols = (const char*const[]){ "slice", NULL }, |
183 | | .can_write = false, |
184 | | }; |