1 /* EasyTAG - Tag editor for audio files
2 * Copyright (C) 2014 David King <amigadave@amigadave.com>
3 * Copyright (C) 2000-2003 Jerome Couderc <easytag@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include "config.h" /* For definition of ENABLE_OGG. */
21
22 #ifdef ENABLE_OGG
23
24 #include <glib/gi18n.h>
25 #include <errno.h>
26 #include <vorbis/codec.h>
27 #include <vorbis/vorbisfile.h>
28
29 #ifdef ENABLE_SPEEX
30 #include <speex/speex_header.h>
31 #include "vcedit.h"
32 #endif
33
34 #include "ogg_header.h"
35 #include "et_core.h"
36 #include "misc.h"
37
38 /*
39 * et_ogg_error_quark:
40 *
41 * To get EtOGGError domain.
42 *
43 * Returns: GQuark for EtOGGError domain
44 */
45 GQuark
et_ogg_error_quark(void)46 et_ogg_error_quark (void)
47 {
48 return g_quark_from_static_string ("et-ogg-error-quark");
49 }
50
51 /*
52 * EtOggHeaderState:
53 * @file: the Ogg file which is currently being parsed
54 * @istream: an input stream for the current Ogg file
55 * @error: either the most recent error, or %NULL
56 *
57 * The current state of the Ogg parser, for passing between the callbacks used
58 * in ov_open_callbacks().
59 */
60 typedef struct
61 {
62 GFile *file;
63 GInputStream *istream;
64 GError *error;
65 } EtOggHeaderState;
66
67 /*
68 * et_ogg_read_func:
69 * @ptr: the buffer to fill with data
70 * @size: the size of individual reads
71 * @nmemb: the number of members to read
72 * @datasource: the Ogg parser state
73 *
74 * Read a number of bytes from the Ogg file.
75 *
76 * Returns: the number of bytes read from the stream. Returns 0 on end-of-file.
77 * Sets errno and returns 0 on error
78 */
79 static size_t
et_ogg_read_func(void * ptr,size_t size,size_t nmemb,void * datasource)80 et_ogg_read_func (void *ptr, size_t size, size_t nmemb, void *datasource)
81 {
82 EtOggHeaderState *state = (EtOggHeaderState *)datasource;
83 gssize bytes_read;
84
85 bytes_read = g_input_stream_read (state->istream, ptr, size * nmemb, NULL,
86 &state->error);
87
88 if (bytes_read == -1)
89 {
90 /* FIXME: Convert state->error to errno. */
91 errno = EIO;
92 return 0;
93 }
94 else
95 {
96 return bytes_read;
97 }
98 }
99
100 /*
101 * et_ogg_seek_func:
102 * @datasource: the Ogg parser state
103 * @offset: the number of bytes to seek
104 * @whence: either %SEEK_SET, %SEEK_CUR or %SEEK_END
105 *
106 * Seek in the currently-open Ogg file.
107 *
108 * Returns: 0 on success, -1 and sets errno on error
109 */
110 static int
et_ogg_seek_func(void * datasource,ogg_int64_t offset,int whence)111 et_ogg_seek_func (void *datasource, ogg_int64_t offset, int whence)
112 {
113 EtOggHeaderState *state = (EtOggHeaderState *)datasource;
114 GSeekType seektype;
115
116 if (!g_seekable_can_seek (G_SEEKABLE (state->istream)))
117 {
118 return -1;
119 }
120 else
121 {
122 switch (whence)
123 {
124 case SEEK_SET:
125 seektype = G_SEEK_SET;
126 break;
127 case SEEK_CUR:
128 seektype = G_SEEK_CUR;
129 break;
130 case SEEK_END:
131 seektype = G_SEEK_END;
132 break;
133 default:
134 errno = EINVAL;
135 return -1;
136 }
137
138 if (g_seekable_seek (G_SEEKABLE (state->istream), offset, seektype,
139 NULL, &state->error))
140 {
141 return 0;
142 }
143 else
144 {
145 errno = EBADF;
146 return -1;
147 }
148 }
149 }
150
151 /*
152 * et_ogg_close_func:
153 * @datasource: the Ogg parser state
154 *
155 * Close the Ogg stream and invalidate the parser state given by @datasource.
156 * Be sure to check the error field before invaidating the state.
157 *
158 * Returns: 0
159 */
160 static int
et_ogg_close_func(void * datasource)161 et_ogg_close_func (void *datasource)
162 {
163 EtOggHeaderState *state = (EtOggHeaderState *)datasource;
164
165 g_clear_object (&state->istream);
166 g_clear_error (&state->error);
167
168 return 0;
169 }
170
171 /*
172 * et_ogg_tell_func:
173 * @datasource: the Ogg parser state
174 *
175 * Tell the current position of the stream from the beginning of the Ogg file.
176 *
177 * Returns: the current position in the Ogg file
178 */
179 static long
et_ogg_tell_func(void * datasource)180 et_ogg_tell_func (void *datasource)
181 {
182 EtOggHeaderState *state = (EtOggHeaderState *)datasource;
183
184 return g_seekable_tell (G_SEEKABLE (state->istream));
185 }
186
187 gboolean
et_ogg_header_read_file_info(GFile * file,ET_File_Info * ETFileInfo,GError ** error)188 et_ogg_header_read_file_info (GFile *file,
189 ET_File_Info *ETFileInfo,
190 GError **error)
191 {
192 OggVorbis_File vf;
193 vorbis_info *vi;
194 gint encoder_version = 0;
195 gint channels = 0;
196 glong rate = 0;
197 glong bitrate_nominal = 0;
198 gdouble duration = 0;
199 gint res;
200 ov_callbacks callbacks = { et_ogg_read_func, et_ogg_seek_func,
201 et_ogg_close_func, et_ogg_tell_func };
202 EtOggHeaderState state;
203 GFileInfo *info;
204
205 g_return_val_if_fail (file != NULL && ETFileInfo != NULL, FALSE);
206 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
207
208 info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE,
209 G_FILE_QUERY_INFO_NONE, NULL, error);
210
211 if (!info)
212 {
213 return FALSE;
214 }
215
216 ETFileInfo->size = g_file_info_get_size (info);
217 g_object_unref (info);
218
219 state.file = file;
220 state.error = NULL;
221 state.istream = G_INPUT_STREAM (g_file_read (state.file, NULL,
222 &state.error));
223
224 if (!state.istream)
225 {
226 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
227 _("Error while opening file: %s"), state.error->message);
228 return FALSE;
229 }
230
231 if ((res = ov_open_callbacks (&state, &vf, NULL, 0, callbacks)) == 0)
232 {
233 if ( (vi=ov_info(&vf,0)) != NULL )
234 {
235 encoder_version = vi->version; // Vorbis encoder version used to create this bitstream.
236 channels = vi->channels; // Number of channels in bitstream.
237 rate = vi->rate; // (Hz) Sampling rate of the bitstream.
238 bitrate_nominal = vi->bitrate_nominal; // (b/s) Specifies the average bitrate for a VBR bitstream.
239 }
240 else
241 {
242 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "%s",
243 _("The specified bitstream does not exist or the "
244 "file has been initialized improperly"));
245 et_ogg_close_func (&state);
246 return FALSE;
247 }
248
249 duration = ov_time_total(&vf,-1); // (s) Total time.
250
251 ov_clear(&vf); // This close also the file
252 }else
253 {
254 /* On error. */
255 if (state.error)
256 {
257 gchar *message;
258
259 switch (res)
260 {
261 case OV_EREAD:
262 message = _("Read from media returned an error");
263 break;
264 case OV_ENOTVORBIS:
265 message = _("Bitstream is not Vorbis data");
266 break;
267 case OV_EVERSION:
268 message = _("Vorbis version mismatch");
269 break;
270 case OV_EBADHEADER:
271 message = _("Invalid Vorbis bitstream header");
272 break;
273 case OV_EFAULT:
274 message = _("Internal logic fault, indicates a bug or heap/stack corruption");
275 break;
276 default:
277 message = _("Error reading tags from file");
278 break;
279 }
280
281 g_set_error (error, state.error->domain, state.error->code,
282 "%s", message);
283 et_ogg_close_func (&state);
284 return FALSE;
285 }
286
287 et_ogg_close_func (&state);
288 }
289
290 ETFileInfo->version = encoder_version;
291 ETFileInfo->bitrate = bitrate_nominal/1000;
292 ETFileInfo->samplerate = rate;
293 ETFileInfo->mode = channels;
294 ETFileInfo->duration = duration;
295
296 return TRUE;
297 }
298
299
300 #ifdef ENABLE_SPEEX
301
302 gboolean
et_speex_header_read_file_info(GFile * file,ET_File_Info * ETFileInfo,GError ** error)303 et_speex_header_read_file_info (GFile *file,
304 ET_File_Info *ETFileInfo,
305 GError **error)
306 {
307 EtOggState *state;
308 const SpeexHeader *si;
309 const gchar *encoder_version = NULL;
310 gint channels = 0;
311 glong rate = 0;
312 glong bitrate = 0;
313 gdouble duration = 0;
314 GFileInfo *info;
315 GError *tmp_error = NULL;
316
317 g_return_val_if_fail (file != NULL && ETFileInfo != NULL, FALSE);
318 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
319
320 state = vcedit_new_state(); // Allocate memory for 'state'
321
322 if (!vcedit_open (state, file, &tmp_error))
323 {
324 g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
325 _("Failed to open file as Vorbis: %s"),
326 tmp_error->message);
327 g_error_free (tmp_error);
328 vcedit_clear (state);
329 return FALSE;
330 }
331
332 info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE,
333 G_FILE_QUERY_INFO_NONE, NULL, error);
334
335 if (!info)
336 {
337 return FALSE;
338 }
339
340 ETFileInfo->size = g_file_info_get_size (info);
341 g_object_unref (info);
342
343 /* Get Speex information. */
344 if ((si = vcedit_speex_header (state)) != NULL)
345 {
346 encoder_version = si->speex_version;
347 channels = si->nb_channels; // Number of channels in bitstream.
348 rate = si->rate; // (Hz) Sampling rate of the bitstream.
349 bitrate = si->bitrate; // (b/s) Specifies the bitrate
350
351 duration = 0;//ov_time_total(&vf,-1); // (s) Total time.
352
353 //g_print("play time: %ld s\n",(long)ov_time_total(&vf,-1));
354 //g_print("serialnumber: %ld\n",(long)ov_serialnumber(&vf,-1));
355 //g_print("compressed length: %ld bytes\n",(long)(ov_raw_total(&vf,-1)));
356 }
357
358 ETFileInfo->mpc_version = g_strdup(encoder_version);
359 ETFileInfo->bitrate = bitrate/1000;
360 ETFileInfo->samplerate = rate;
361 ETFileInfo->mode = channels;
362 //if (bitrate > 0)
363 // ETFileInfo->duration = filesize*8/bitrate/1000; // FIXME : Approximation!! Needs to remove tag size!
364 //else
365 ETFileInfo->duration = duration;
366
367 vcedit_clear(state);
368 return TRUE;
369 }
370 #endif
371
372 EtFileHeaderFields *
et_ogg_header_display_file_info_to_ui(const ET_File * ETFile)373 et_ogg_header_display_file_info_to_ui (const ET_File *ETFile)
374 {
375 EtFileHeaderFields *fields;
376 ET_File_Info *info;
377 gchar *time = NULL;
378 gchar *time1 = NULL;
379 gchar *size = NULL;
380 gchar *size1 = NULL;
381
382 info = ETFile->ETFileInfo;
383 fields = g_slice_new (EtFileHeaderFields);
384
385 if (ETFile->ETFileDescription->FileType == OGG_FILE)
386 {
387 fields->description = _("Ogg Vorbis File");
388 }
389 else if (ETFile->ETFileDescription->FileType == SPEEX_FILE)
390 {
391 fields->description = _("Speex File");
392 }
393 else
394 {
395 g_assert_not_reached ();
396 }
397
398 /* Encoder version */
399 fields->version_label = _("Encoder:");
400
401 if (!info->mpc_version)
402 {
403 fields->version = g_strdup_printf ("%d", info->version);
404 }
405 else
406 {
407 fields->version = g_strdup (info->mpc_version);
408 }
409
410 /* Bitrate */
411 fields->bitrate = g_strdup_printf (_("%d kb/s"), info->bitrate);
412
413 /* Samplerate */
414 fields->samplerate = g_strdup_printf (_("%d Hz"), info->samplerate);
415
416 /* Mode */
417 fields->mode_label = _("Channels:");
418 fields->mode = g_strdup_printf ("%d", info->mode);
419
420 /* Size */
421 size = g_format_size (info->size);
422 size1 = g_format_size (ETCore->ETFileDisplayedList_TotalSize);
423 fields->size = g_strdup_printf ("%s (%s)", size, size1);
424 g_free (size);
425 g_free (size1);
426
427 /* Duration */
428 time = Convert_Duration (info->duration);
429 time1 = Convert_Duration (ETCore->ETFileDisplayedList_TotalDuration);
430 fields->duration = g_strdup_printf ("%s (%s)", time, time1);
431 g_free (time);
432 g_free (time1);
433
434 return fields;
435 }
436
437 void
et_ogg_file_header_fields_free(EtFileHeaderFields * fields)438 et_ogg_file_header_fields_free (EtFileHeaderFields *fields)
439 {
440 g_return_if_fail (fields != NULL);
441
442 g_free (fields->version);
443 g_free (fields->bitrate);
444 g_free (fields->samplerate);
445 g_free (fields->mode);
446 g_free (fields->size);
447 g_free (fields->duration);
448 g_slice_free (EtFileHeaderFields, fields);
449 }
450
451 #endif /* ENABLE_OGG */
452