gui: Remove --timeout.
[paraslash.git] / spx_afh.c
1 /*
2  * Copyright (C) 2010 Andre Noll <maan@tuebingen.mpg.de>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /* This file is based on speexdec.c, by Jean-Marc Valin, see below. */
8
9 /* Copyright (C) 2002-2006 Jean-Marc Valin
10    File: speexdec.c
11
12    Redistribution and use in source and binary forms, with or without
13    modification, are permitted provided that the following conditions
14    are met:
15    
16    - Redistributions of source code must retain the above copyright
17    notice, this list of conditions and the following disclaimer.
18    
19    - Redistributions in binary form must reproduce the above copyright
20    notice, this list of conditions and the following disclaimer in the
21    documentation and/or other materials provided with the distribution.
22    
23    - Neither the name of the Xiph.org Foundation nor the names of its
24    contributors may be used to endorse or promote products derived from
25    this software without specific prior written permission.
26    
27    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30    A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
31    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
33    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
34    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
35    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
36    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 */
39 /** \file spx_afh.c Audio format handler for ogg/speex files. */
40
41 #include <ogg/ogg.h>
42 #include <regex.h>
43 #include <speex/speex.h>
44 #include <speex/speex_header.h>
45 #include <speex/speex_stereo.h>
46
47 #include "para.h"
48 #include "afh.h"
49 #include "error.h"
50 #include "portable_io.h"
51 #include "string.h"
52 #include "spx.h"
53 #include "ogg_afh_common.h"
54
55 struct private_spx_data {
56         struct spx_header_info shi;
57 };
58
59 static bool copy_if_tag_type(const char *tag, int taglen, const char *type,
60                 char **p)
61 {
62         char *q = key_value_copy(tag, taglen, type);
63         if (!q)
64                 return false;
65         free(*p);
66         *p = q;
67         return true;
68 }
69
70 static int spx_get_comments(unsigned char *comments, int length,
71                 struct taginfo *tags)
72 {
73         char *c = (char *)comments;
74         uint32_t len, nb_fields;
75         int i;
76         char *end;
77
78         if (length < 8)
79                 return -E_SPX_COMMENT;
80         end = c + length;
81         len = read_u32(c);
82         c += 4;
83         if (c + len > end)
84                 return -E_SPX_COMMENT;
85         tags->comment = safe_strdup(c, len);
86
87         c += len;
88         if (c + 4 > end)
89                 return -E_SPX_COMMENT;
90         nb_fields = read_u32(c);
91         PARA_DEBUG_LOG("%u comment(s)\n", nb_fields);
92         c += 4;
93         for (i = 0; i < nb_fields; i++, c += len) {
94                 char *tag;
95
96                 if (c + 4 > end)
97                         return -E_SPX_COMMENT;
98                 len = read_u32(c);
99                 c += 4;
100                 if (c + len > end)
101                         return -E_SPX_COMMENT;
102                 if (copy_if_tag_type(c, len, "author", &tags->artist))
103                         continue;
104                 if (copy_if_tag_type(c, len, "artist", &tags->artist))
105                         continue;
106                 if (copy_if_tag_type(c, len, "title", &tags->title))
107                         continue;
108                 if (copy_if_tag_type(c, len, "album", &tags->album))
109                         continue;
110                 if (copy_if_tag_type(c, len, "year", &tags->year))
111                         continue;
112                 if (copy_if_tag_type(c, len, "comment", &tags->comment))
113                         continue;
114                 tag = safe_strdup(c, len);
115                 PARA_NOTICE_LOG("unrecognized comment: %s\n", tag);
116                 free(tag);
117         }
118         return 1;
119 }
120
121 static int spx_packet_callback(ogg_packet *packet, int packet_num,
122                 __a_unused int serial, struct afh_info *afhi,
123                 void *private_data)
124 {
125         struct private_spx_data *psd = private_data;
126         int ret;
127
128         if (packet_num == 0) {
129                 ret = spx_process_header(packet->packet, packet->bytes,
130                         &psd->shi);
131                 if (ret < 0)
132                         return ret;
133                 afhi->channels = psd->shi.channels;
134                 afhi->frequency = psd->shi.sample_rate;
135                 afhi->bitrate = psd->shi.bitrate / 1000;
136                 afhi->techinfo = make_message("%s, v%d", psd->shi.mode->modeName,
137                         psd->shi.mode->bitstream_version);
138                 return 1;
139         }
140         if (packet_num == 1) {
141                 ret = spx_get_comments(packet->packet, packet->bytes,
142                         &afhi->tags);
143                 if (ret < 0)
144                         return ret;
145                 return 0; /* header complete */
146         }
147         /* never reached */
148         return 0;
149 }
150
151 static int spx_get_file_info(char *map, size_t numbytes, __a_unused int fd,
152                 struct afh_info *afhi)
153 {
154         struct private_spx_data psd;
155         struct ogg_afh_callback_info spx_callback_info = {
156                 .packet_callback = spx_packet_callback,
157                 .private_data = &psd,
158         };
159
160         memset(&psd, 0, sizeof(psd));
161         return ogg_get_file_info(map, numbytes, afhi, &spx_callback_info);
162 }
163
164 static size_t spx_make_meta_packet(struct taginfo *tags, char **result)
165 {
166         size_t sz;
167         char *buf, *p;
168         size_t comment_len = strlen(tags->comment),
169                 artist_len = strlen(tags->artist),
170                 title_len = strlen(tags->title),
171                 album_len = strlen(tags->album),
172                 year_len = strlen(tags->year);
173         uint32_t comment_sz = comment_len,
174                 artist_sz = artist_len + strlen("artist="),
175                 title_sz = title_len + strlen("title="),
176                 album_sz = album_len + strlen("album="),
177                 year_sz = year_len + strlen("year=");
178         uint32_t num_tags;
179
180         sz = 4 /* comment length (always present) */
181                 + comment_sz
182                 + 4; /* number of tags */
183         num_tags = 0;
184         if (artist_len) {
185                 num_tags++;
186                 sz += 4 + artist_sz;
187         }
188         if (title_len) {
189                 num_tags++;
190                 sz += 4 + title_sz;
191         }
192         if (album_len) {
193                 num_tags++;
194                 sz += 4 + album_sz;
195         }
196         if (year_len) {
197                 num_tags++;
198                 sz += 4 + year_sz;
199         }
200         PARA_DEBUG_LOG("meta packet size: %zu bytes\n", sz);
201         /* terminating zero byte for the last sprintf() */
202         buf = p = para_malloc(sz + 1);
203         write_u32(p, comment_sz);
204         p += 4;
205         strcpy(p, tags->comment);
206         p += comment_sz;
207         write_u32(p, num_tags);
208         p += 4;
209         if (artist_len) {
210                 write_u32(p, artist_sz);
211                 p += 4;
212                 sprintf(p, "artist=%s", tags->artist);
213                 p += artist_sz;
214         }
215         if (title_len) {
216                 write_u32(p, title_sz);
217                 p += 4;
218                 sprintf(p, "title=%s", tags->title);
219                 p += title_sz;
220         }
221         if (album_len) {
222                 write_u32(p, album_sz);
223                 p += 4;
224                 sprintf(p, "album=%s", tags->album);
225                 p += album_sz;
226         }
227         if (year_len) {
228                 write_u32(p, year_sz);
229                 p += 4;
230                 sprintf(p, "year=%s", tags->year);
231                 p += year_sz;
232         }
233         assert(p == buf + sz);
234         *result = buf;
235         return sz;
236 }
237
238 static int spx_rewrite_tags(const char *map, size_t mapsize,
239                 struct taginfo *tags, int output_fd,
240                 __a_unused const char *filename)
241 {
242         char *meta_packet;
243         size_t meta_sz;
244         int ret;
245
246         meta_sz = spx_make_meta_packet(tags, &meta_packet);
247         ret = ogg_rewrite_tags(map, mapsize, output_fd, meta_packet, meta_sz);
248         free(meta_packet);
249         return ret;
250 }
251
252 static const char * const speex_suffixes[] = {"spx", "speex", NULL};
253
254 /**
255  * The init function of the ogg/speex audio format handler.
256  *
257  * \param afh Pointer to the struct to initialize.
258  */
259 void spx_afh_init(struct audio_format_handler *afh)
260 {
261         afh->get_file_info = spx_get_file_info,
262         afh->suffixes = speex_suffixes;
263         afh->rewrite_tags = spx_rewrite_tags;
264 }