1 /*
2 * Copyright (c) 2005-2006 Jean-François Wauthy (pollux@xfce.org)
3 * Copyright (c) 2008 David Mohr (dmohr@mcbf.net)
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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif /* !HAVE_CONFIG_H */
23
24 #ifdef HAVE_STRING_H
25 #include <string.h>
26 #endif
27
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32
33 #include <errno.h>
34
35 #include <libxfce4util/libxfce4util.h>
36 #include <libxfce4ui/libxfce4ui.h>
37
38 #include <libburn.h>
39
40 #include "xfburn-global.h"
41 #include "xfburn-error.h"
42
43 #include "xfburn-transcoder-basic.h"
44
45 /** Prototypes **/
46 /* class initialization */
47 static void xfburn_transcoder_basic_finalize (GObject * object);
48 static void transcoder_interface_init (XfburnTranscoderInterface *iface, gpointer iface_data);
49
50 /* internals */
51 static const gchar * get_name (XfburnTranscoder *trans);
52 static const gchar * get_description (XfburnTranscoder *trans);
53 static gboolean is_initialized (XfburnTranscoder *trans, GError **error);
54
55 static gboolean get_audio_track (XfburnTranscoder *trans, XfburnAudioTrack *atrack, GError **error);
56 static gboolean has_audio_ext (const gchar *path);
57 static gboolean is_valid_wav (const gchar *path);
58 static gboolean valid_wav_headers (guchar header[44]);
59
60 static struct burn_track * create_burn_track (XfburnTranscoder *trans, XfburnAudioTrack *atrack, GError **error);
61 static gboolean needs_swap (char header[44]);
62
63 enum {
64 LAST_SIGNAL,
65 };
66
67 /* globals */
68
69 static const gchar *errormsg_libburn_setup = N_("An error occurred while setting the burning backend up");
70
71 /*********************/
72 /* class declaration */
73 /*********************/
74 static GObject *parent_class = NULL;
75 //static guint signals[LAST_SIGNAL];
76
77 G_DEFINE_TYPE_EXTENDED(
78 XfburnTranscoderBasic,
79 xfburn_transcoder_basic,
80 G_TYPE_OBJECT,
81 0,
82 G_IMPLEMENT_INTERFACE(XFBURN_TYPE_TRANSCODER, transcoder_interface_init)
83 );
84
85 static void
xfburn_transcoder_basic_class_init(XfburnTranscoderBasicClass * klass)86 xfburn_transcoder_basic_class_init (XfburnTranscoderBasicClass * klass)
87 {
88 GObjectClass *object_class = G_OBJECT_CLASS (klass);
89
90 parent_class = g_type_class_peek_parent (klass);
91
92 object_class->finalize = xfburn_transcoder_basic_finalize;
93
94 /*
95 signals[VOLUME_CHANGED] = g_signal_new ("volume-changed", XFBURN_TYPE_TRANSCODER_BASIC, G_SIGNAL_ACTION,
96 G_STRUCT_OFFSET (XfburnTranscoderBasicClass, volume_changed),
97 NULL, NULL, g_cclosure_marshal_VOID__VOID,
98 G_TYPE_NONE, 0);
99 */
100 }
101
102 static void
xfburn_transcoder_basic_init(XfburnTranscoderBasic * obj)103 xfburn_transcoder_basic_init (XfburnTranscoderBasic * obj)
104 {
105 }
106
107 static void
xfburn_transcoder_basic_finalize(GObject * object)108 xfburn_transcoder_basic_finalize (GObject * object)
109 {
110 G_OBJECT_CLASS (parent_class)->finalize (object);
111 }
112
113 static void
transcoder_interface_init(XfburnTranscoderInterface * iface,gpointer iface_data)114 transcoder_interface_init (XfburnTranscoderInterface *iface, gpointer iface_data)
115 {
116 iface->get_name = get_name;
117 iface->get_description = get_description;
118 iface->is_initialized = is_initialized;
119 iface->get_audio_track = get_audio_track;
120 iface->create_burn_track = create_burn_track;
121 }
122 /* */
123 /* internals */
124 /* */
125
126 static const gchar *
get_name(XfburnTranscoder * trans)127 get_name (XfburnTranscoder *trans)
128 {
129 return _("basic");
130 }
131
132 static const gchar *
get_description(XfburnTranscoder * trans)133 get_description (XfburnTranscoder *trans)
134 {
135 return _("The basic transcoder is built in,\n"
136 "and does not require any library.\n"
137 "But it can only handle uncompressed\n"
138 ".wav files.\n"
139 "If you would like to create audio\n"
140 "compositions from different types of\n"
141 "audio files, please compile with\n"
142 "gstreamer support.");
143 }
144
145 static gboolean
is_initialized(XfburnTranscoder * trans,GError ** error)146 is_initialized (XfburnTranscoder *trans, GError **error)
147 {
148 /* there is nothing to check, because there is nothing to initialize */
149 return TRUE;
150 }
151
152 static gboolean
get_audio_track(XfburnTranscoder * trans,XfburnAudioTrack * atrack,GError ** error)153 get_audio_track (XfburnTranscoder *trans, XfburnAudioTrack *atrack, GError **error)
154 {
155 struct stat s;
156
157 if (!has_audio_ext (atrack->inputfile)) {
158 g_set_error (error, XFBURN_ERROR, XFBURN_ERROR_NOT_AUDIO_EXT,
159 _("File %s does not have a .wav extension"), atrack->inputfile);
160 return FALSE;
161 }
162 if (!is_valid_wav (atrack->inputfile)) {
163 g_set_error (error, XFBURN_ERROR, XFBURN_ERROR_NOT_AUDIO_FORMAT,
164 _("File %s does not contain uncompressed PCM wave audio"), atrack->inputfile);
165 return FALSE;
166 }
167
168 if (stat (atrack->inputfile, &s) != 0) {
169 g_set_error (error, XFBURN_ERROR, XFBURN_ERROR_STAT,
170 _("Could not stat %s: %s"), atrack->inputfile, g_strerror (errno));
171 return FALSE;
172 }
173
174 atrack->length = (s.st_size - 44) / PCM_BYTES_PER_SECS;
175 atrack->sectors = (s.st_size / AUDIO_BYTES_PER_SECTOR);
176 if (s.st_size % AUDIO_BYTES_PER_SECTOR > 0)
177 atrack->sectors++;
178
179 return TRUE;
180 }
181
182 static gboolean
has_audio_ext(const gchar * path)183 has_audio_ext (const gchar *path)
184 {
185 int len = strlen (path);
186 const gchar *ext = path + len - 3;
187
188 return (strcmp (ext, "wav") == 0);
189 }
190
191 static gboolean
is_valid_wav(const gchar * path)192 is_valid_wav (const gchar *path)
193 {
194 int fd;
195 int hread;
196 guchar header[44];
197 gboolean ret;
198
199 fd = open (path, 0);
200
201 if (fd == -1) {
202 xfce_dialog_show_warning(NULL, "", _("Could not open %s."), path);
203 return FALSE;
204 }
205
206 ret = FALSE;
207 hread = read (fd, header, 44);
208 if (hread == 44) {
209 ret = valid_wav_headers (header);
210 }
211
212 close (fd);
213
214 return ret;
215 }
216
217 /*
218 * Simple check of .wav headers, most info from
219 * http://ccrma.stanford.edu/courses/422/projects/WaveFormat/
220 *
221 * I did not take particular care to make sure this check is complete.
222 * As far as I can tell yes, but not much effort was put into verifying it.
223 * FIXME: this works on x86, and does not consider endianness!
224 */
225 static gboolean
valid_wav_headers(guchar header[44])226 valid_wav_headers (guchar header[44])
227 {
228 /* check if first 4 bytes are RIFF or RIFX */
229 if (header[0] == 'R' && header[1] == 'I' && header[2] == 'F') {
230 if (!(header[3] == 'X' || header[3] == 'F')) {
231 g_warning ("File not in riff format");
232 return FALSE;
233 }
234 }
235
236 /* check if bytes 8-11 are WAVE */
237 if (!(header[8] == 'W' && header[9] == 'A' && header[10] == 'V' && header[11] == 'E')) {
238 g_warning ("RIFF file not in WAVE format");
239 return FALSE;
240 }
241
242 /* subchunk starts with 'fmt ' */
243 if (!(header[12] == 'f' && header[13] == 'm' && header[14] == 't' && header[15] == ' ')) {
244 g_warning ("Could not find format subchunk");
245 return FALSE;
246 }
247
248 /* check for PCM format */
249 if (header[16] != 16 || header[20] != 1) {
250 g_warning ("Not in PCM format");
251 return FALSE;
252 }
253
254 /* check for stereo */
255 if (header[22] != 2) {
256 g_warning ("Not in stereo");
257 return FALSE;
258 }
259
260 /* check for 44100 Hz sample rate,
261 * being lazy here and just compare the bytes to what I know they should be */
262 if (!(header[24] == 0x44 && header[25] == 0xAC && header[26] == 0 && header[27] == 0)) {
263 g_warning ("Does not have a sample rate of 44100 Hz");
264 return FALSE;
265 }
266
267 return TRUE;
268 }
269
270
271 static struct burn_track *
create_burn_track(XfburnTranscoder * trans,XfburnAudioTrack * atrack,GError ** error)272 create_burn_track (XfburnTranscoder *trans, XfburnAudioTrack *atrack, GError **error)
273 {
274 char header[44];
275 int thead=0;
276 struct burn_track *track;
277
278 atrack->fd = open (atrack->inputfile, 0);
279 if (atrack->fd == -1) {
280 g_set_error (error, XFBURN_ERROR, XFBURN_ERROR_COULD_NOT_OPEN_FILE,
281 _("Could not open %s: %s"), atrack->inputfile, g_strerror (errno));
282 return NULL;
283 }
284
285 /* advance the fd so that libburn skips the header,
286 * also allows us to check for byte swapping */
287 thead = read (atrack->fd, header, 44);
288 if( thead < 44 ) {
289 g_warning ("Could not read header from %s!", atrack->inputfile);
290 g_set_error (error, XFBURN_ERROR, XFBURN_ERROR_BURN_SOURCE,
291 "%s",
292 _(errormsg_libburn_setup));
293 return NULL;
294 }
295
296
297 atrack->src = burn_fd_source_new (atrack->fd, -1 , 0);
298 if (atrack->src == NULL) {
299 g_warning ("Could not create burn_source from %s!", atrack->inputfile);
300 g_set_error (error, XFBURN_ERROR, XFBURN_ERROR_BURN_SOURCE,
301 "%s",
302 _(errormsg_libburn_setup));
303 return NULL;
304 }
305
306 track = burn_track_create ();
307
308 if (burn_track_set_source (track, atrack->src) != BURN_SOURCE_OK) {
309 g_warning ("Could not add source to track %s!", atrack->inputfile);
310 g_set_error (error, XFBURN_ERROR, XFBURN_ERROR_BURN_SOURCE,
311 "%s",
312 _(errormsg_libburn_setup));
313
314 return NULL;
315 }
316
317 if (needs_swap (header))
318 burn_track_set_byte_swap (track, TRUE);
319
320 burn_track_define_data (track, 0, 0, 1, BURN_AUDIO);
321
322 return track;
323 }
324
325 static gboolean
needs_swap(char header[44])326 needs_swap (char header[44])
327 {
328 if (header[0] == 'R' && header[1] == 'I' && header[2] == 'F' && header[3] == 'X')
329 return TRUE;
330 else
331 return FALSE;
332 }
333
334 /* */
335 /* public */
336 /* */
337
338 GObject *
xfburn_transcoder_basic_new(void)339 xfburn_transcoder_basic_new (void)
340 {
341 return g_object_new (XFBURN_TYPE_TRANSCODER_BASIC, NULL);
342 }
343