stream filter: increase priorities
[vlc.git] / modules / stream_filter / skiptags.c
1 /*****************************************************************************
2  * skiptags.c:
3  *****************************************************************************
4  * Copyright © 2005-2008 VLC authors and VideoLAN
5  * Copyright © 2016-2017 Rémi Denis-Courmont
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20  *****************************************************************************/
21
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #include <limits.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include <vlc_common.h>
31 #include <vlc_plugin.h>
32 #include <vlc_stream.h>
33 #include <vlc_block.h>
34
35 #define MAX_TAGS 16
36 #define MAX_TAG_SIZE (1<<17)
37
38 struct skiptags_sys_t
39 {
40     uint64_t header_skip;
41     /* TODO? also discard trailer tags? */
42     block_t *p_tags;
43 };
44
45 static struct skiptags_sys_t * skiptags_sys_New(void)
46 {
47     struct skiptags_sys_t *sys = malloc(sizeof (*sys));
48     if(sys)
49     {
50         sys->header_skip = 0;
51         sys->p_tags = NULL;
52     }
53     return sys;
54 }
55
56 static void skiptags_sys_Delete(struct skiptags_sys_t *sys)
57 {
58     block_ChainRelease(sys->p_tags);
59     free(sys);
60 }
61
62 static uint_fast32_t SkipID3Tag(stream_t *s)
63 {
64     const uint8_t *peek;
65
66     /* Get 10-bytes ID3 header */
67     if (vlc_stream_Peek(s, &peek, 10) < 10)
68         return 0;
69     if (memcmp(peek, "ID3", 3))
70         return 0;
71
72     uint_fast8_t version = peek[3];
73     uint_fast8_t revision = peek[4];
74     bool has_footer = (peek[5] & 0x10) != 0;
75     uint_fast32_t size = 10u + (peek[6] << 21) + (peek[7] << 14)
76                          + (peek[8] << 7) + peek[9] + (has_footer ? 10u : 0u);
77
78     /* Skip the entire tag */
79     msg_Dbg(s, "ID3v2.%"PRIuFAST8" revision %"PRIuFAST8" tag found, "
80             "skipping %"PRIuFAST32" bytes", version, revision, size);
81
82     return size;
83 }
84
85 static uint_fast32_t SkipAPETag(stream_t *s)
86 {
87     const uint8_t *peek;
88
89     /* Get 32-bytes APE header */
90     if (vlc_stream_Peek(s, &peek, 32) < 32)
91         return 0;
92     if (memcmp(peek, "APETAGEX", 8))
93         return 0;
94
95     uint_fast32_t version = GetDWLE(peek + 8);
96     if (version != 1000 && version != 2000)
97         return 0;
98
99     uint_fast32_t size = GetDWLE(peek + 12);
100     if (size > SSIZE_MAX - 32u)
101         return 0; /* impossibly long tag */
102
103     uint_fast32_t flags = GetDWLE(peek + 16);
104     if ((flags & (1u << 29)) == 0)
105         return 0;
106
107     if (flags & (1u << 30))
108         size += 32;
109
110     msg_Dbg(s, "AP2 v%"PRIuFAST32" tag found, "
111             "skipping %"PRIuFAST32" bytes", version / 1000, size);
112     return size;
113 }
114
115 static bool SkipTag(stream_t *s, uint_fast32_t (*skipper)(stream_t *),
116                     block_t **pp_block, unsigned *pi_tags_count)
117 {
118     uint_fast64_t offset = vlc_stream_Tell(s);
119     uint_fast32_t size = skipper(s);
120     if(size> 0)
121     {
122         /* Skip the entire tag */
123         ssize_t read;
124         if(*pi_tags_count < MAX_TAGS && size <= MAX_TAG_SIZE)
125         {
126             *pp_block = vlc_stream_Block(s, size);
127             read = *pp_block ? (ssize_t)(*pp_block)->i_buffer : -1;
128         }
129         else
130         {
131             read = vlc_stream_Read(s, NULL, size);
132         }
133
134         if(read < (ssize_t)size)
135         {
136             block_ChainRelease(*pp_block);
137             *pp_block = NULL;
138             if (unlikely(read < 0))
139             {   /* I/O error, try to restore offset. If it fails, screwed. */
140                 if (vlc_stream_Seek(s, offset))
141                     msg_Err(s, "seek failure");
142                 return false;
143             }
144         }
145         else (*pi_tags_count)++;
146     }
147     return size != 0;
148 }
149
150 static ssize_t Read(stream_t *stream, void *buf, size_t buflen)
151 {
152     return vlc_stream_Read(stream->s, buf, buflen);
153 }
154
155 static int ReadDir(stream_t *stream, input_item_node_t *node)
156 {
157     (void) stream; (void) node;
158     return VLC_EGENERIC;
159 }
160
161 static int Seek(stream_t *stream, uint64_t offset)
162 {
163     const struct skiptags_sys_t *sys = stream->p_sys;
164
165     if (unlikely(offset + sys->header_skip < offset))
166         return -1;
167     return vlc_stream_Seek(stream->s, sys->header_skip + offset);
168 }
169
170 static int Control(stream_t *stream, int query, va_list args)
171 {
172     const struct skiptags_sys_t *sys = stream->p_sys;
173     /* In principles, we should return the meta-data embedded in the skipped
174      * tags in STREAM_GET_META. But the meta engine is devoted to that already.
175      */
176     if( query == STREAM_GET_TAGS && sys->p_tags )
177     {
178         *va_arg( args, const block_t ** ) = sys->p_tags;
179         return VLC_SUCCESS;
180     }
181     else if(query == STREAM_GET_SIZE)
182     {
183         uint64_t size;
184         int i_ret = vlc_stream_GetSize(stream->s, &size);
185         if(i_ret == VLC_SUCCESS)
186             *va_arg(args, uint64_t *) = size - sys->header_skip;
187         return i_ret;
188     }
189
190     return vlc_stream_vaControl(stream->s, query, args);
191 }
192
193 static int Open(vlc_object_t *obj)
194 {
195     stream_t *stream = (stream_t *)obj;
196     stream_t *s = stream->s;
197     struct skiptags_sys_t *sys;
198
199     block_t *p_tags = NULL, *p_tag = NULL;
200     unsigned i_tagscount = 0;
201
202     while (SkipTag(s, SkipID3Tag, &p_tag, &i_tagscount)||
203            SkipTag(s, SkipAPETag, &p_tag, &i_tagscount))
204     {
205         if(p_tag)
206         {
207             p_tag->p_next = p_tags;
208             p_tags = p_tag;
209             p_tag = NULL;
210         }
211     }
212
213     uint_fast64_t offset = vlc_stream_Tell(s);
214     if (offset == 0 || !(sys = skiptags_sys_New()))
215     {
216         block_ChainRelease( p_tags );
217         return VLC_EGENERIC; /* nothing to do */
218     }
219
220     sys->header_skip = offset;
221     sys->p_tags = p_tags;
222     stream->p_sys = sys;
223     stream->pf_read = Read;
224     stream->pf_readdir = ReadDir;
225     stream->pf_seek = Seek;
226     stream->pf_control = Control;
227     return VLC_SUCCESS;
228 }
229
230 static void Close(vlc_object_t *obj)
231 {
232     stream_t *stream = (stream_t *)obj;
233     struct skiptags_sys_t *sys = (struct skiptags_sys_t *) stream->p_sys;
234
235     skiptags_sys_Delete(sys);
236 }
237
238 vlc_module_begin()
239     set_category(CAT_INPUT)
240     set_subcategory(SUBCAT_INPUT_STREAM_FILTER)
241     set_capability("stream_filter", 330)
242
243     set_description(N_("APE/ID3 tags-skipping filter"))
244     set_callbacks(Open, Close)
245 vlc_module_end()