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