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