1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * Libbrasero-burn
4  * Copyright (C) Philippe Rouquier 2005-2009 <bonfire-app@wanadoo.fr>
5  *
6  * Libbrasero-burn is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * The Libbrasero-burn authors hereby grant permission for non-GPL compatible
12  * GStreamer plugins to be used and distributed together with GStreamer
13  * and Libbrasero-burn. This permission is above and beyond the permissions granted
14  * by the GPL license by which Libbrasero-burn is covered. If you modify this code
15  * you may extend this exception to your version of the code, but you are not
16  * obligated to do so. If you do not wish to do so, delete this exception
17  * statement from your version.
18  *
19  * Libbrasero-burn is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU Library General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to:
26  * 	The Free Software Foundation, Inc.,
27  * 	51 Franklin Street, Fifth Floor
28  * 	Boston, MA  02110-1301, USA.
29  */
30 
31 #ifdef HAVE_CONFIG_H
32 #  include <config.h>
33 #endif
34 
35 #include <stdlib.h>
36 #include <string.h>
37 #include <errno.h>
38 #include <unistd.h>
39 #include <time.h>
40 
41 #include <glib.h>
42 #include <glib-object.h>
43 #include <glib/gi18n-lib.h>
44 #include <glib/gstdio.h>
45 #include <gmodule.h>
46 
47 #include "brasero-units.h"
48 
49 #include "burn-debug.h"
50 #include "burn-job.h"
51 #include "burn-process.h"
52 #include "brasero-plugin-registration.h"
53 #include "burn-cdrtools.h"
54 #include "brasero-track-disc.h"
55 #include "brasero-track-stream.h"
56 
57 #define BRASERO_TYPE_CDDA2WAV         (brasero_cdda2wav_get_type ())
58 #define BRASERO_CDDA2WAV(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), BRASERO_TYPE_CDDA2WAV, BraseroCdda2wav))
59 #define BRASERO_CDDA2WAV_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), BRASERO_TYPE_CDDA2WAV, BraseroCdda2wavClass))
60 #define BRASERO_IS_CDDA2WAV(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), BRASERO_TYPE_CDDA2WAV))
61 #define BRASERO_IS_CDDA2WAV_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), BRASERO_TYPE_CDDA2WAV))
62 #define BRASERO_CDDA2WAV_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), BRASERO_TYPE_CDDA2WAV, BraseroCdda2wavClass))
63 
64 BRASERO_PLUGIN_BOILERPLATE (BraseroCdda2wav, brasero_cdda2wav, BRASERO_TYPE_PROCESS, BraseroProcess);
65 
66 struct _BraseroCdda2wavPrivate {
67 	gchar *file_pattern;
68 
69 	guint track_num;
70 	guint track_nb;
71 
72 	guint is_inf	:1;
73 };
74 typedef struct _BraseroCdda2wavPrivate BraseroCdda2wavPrivate;
75 
76 #define BRASERO_CDDA2WAV_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE ((o), BRASERO_TYPE_CDDA2WAV, BraseroCdda2wavPrivate))
77 static GObjectClass *parent_class = NULL;
78 
79 
80 static BraseroBurnResult
brasero_cdda2wav_post(BraseroJob * job)81 brasero_cdda2wav_post (BraseroJob *job)
82 {
83 	BraseroCdda2wavPrivate *priv;
84 	BraseroMedium *medium;
85 	BraseroJobAction action;
86 	BraseroDrive *drive;
87 	BraseroTrack *track;
88 	int track_num;
89 	int i;
90 
91 	priv = BRASERO_CDDA2WAV_PRIVATE (job);
92 
93 	brasero_job_get_action (job, &action);
94 	if (action == BRASERO_JOB_ACTION_SIZE)
95 		return BRASERO_BURN_OK;
96 
97 	/* we add the tracks */
98 	track = NULL;
99 	brasero_job_get_current_track (job, &track);
100 
101 	drive = brasero_track_disc_get_drive (BRASERO_TRACK_DISC (track));
102 	medium = brasero_drive_get_medium (drive);
103 
104 	track_num = brasero_medium_get_track_num (medium);
105 	for (i = 0; i < track_num; i ++) {
106 		BraseroTrackStream *track_stream;
107 		goffset block_num = 0;
108 
109 		brasero_medium_get_track_space (medium, i + 1, NULL, &block_num);
110 		track_stream = brasero_track_stream_new ();
111 
112 		brasero_track_stream_set_format (track_stream,
113 		                                 BRASERO_AUDIO_FORMAT_RAW|
114 		                                 BRASERO_METADATA_INFO);
115 
116 		BRASERO_JOB_LOG (job, "Adding new audio track of size %" G_GOFFSET_FORMAT, BRASERO_BYTES_TO_DURATION (block_num * 2352));
117 
118 		/* either add .inf or .cdr files */
119 		if (!priv->is_inf) {
120 			gchar *uri;
121 			gchar *filename;
122 
123 			if (track_num == 1)
124 				filename = g_strdup_printf ("%s.cdr", priv->file_pattern);
125 			else
126 				filename = g_strdup_printf ("%s_%02i.cdr", priv->file_pattern, i + 1);
127 
128 			uri = g_filename_to_uri (filename, NULL, NULL);
129 			g_free (filename);
130 
131 			brasero_track_stream_set_source (track_stream, uri);
132 			g_free (uri);
133 
134 			/* signal to cdrecord that we have an .inf file */
135 			if (i != 0)
136 				filename = g_strdup_printf ("%s_%02i.inf", priv->file_pattern, i);
137 			else
138 				filename = g_strdup_printf ("%s.inf", priv->file_pattern);
139 
140 			brasero_track_tag_add_string (BRASERO_TRACK (track_stream),
141 			                              BRASERO_CDRTOOLS_TRACK_INF_FILE,
142 			                              filename);
143 			g_free (filename);
144 		}
145 
146 		/* Always set the boundaries after the source as
147 		 * brasero_track_stream_set_source () resets the length */
148 		brasero_track_stream_set_boundaries (track_stream,
149 		                                     0,
150 		                                     BRASERO_BYTES_TO_DURATION (block_num * 2352),
151 		                                     0);
152 		brasero_job_add_track (job, BRASERO_TRACK (track_stream));
153 		g_object_unref (track_stream);
154 	}
155 
156 	return brasero_job_finished_session (job);
157 }
158 
159 static gboolean
brasero_cdda2wav_get_output_filename_pattern(BraseroCdda2wav * cdda2wav,GError ** error)160 brasero_cdda2wav_get_output_filename_pattern (BraseroCdda2wav *cdda2wav,
161                                               GError **error)
162 {
163 	gchar *path;
164 	gchar *file_pattern;
165 	BraseroCdda2wavPrivate *priv;
166 
167 	priv = BRASERO_CDDA2WAV_PRIVATE (cdda2wav);
168 
169 	if (priv->file_pattern) {
170 		g_free (priv->file_pattern);
171 		priv->file_pattern = NULL;
172 	}
173 
174 	/* Create a tmp directory so cdda2wav can
175 	 * put all its stuff in there */
176 	path = NULL;
177 	brasero_job_get_tmp_dir (BRASERO_JOB (cdda2wav), &path, error);
178 	if (!path)
179 		return FALSE;
180 
181 	file_pattern = g_strdup_printf ("%s/cd_file", path);
182 	g_free (path);
183 
184 	/* NOTE: this file pattern is used to
185 	 * name all wav and inf files. It is followed
186 	 * by underscore/number of the track/extension */
187 
188 	priv->file_pattern = file_pattern;
189 	return TRUE;
190 }
191 
192 static BraseroBurnResult
brasero_cdda2wav_read_stderr(BraseroProcess * process,const gchar * line)193 brasero_cdda2wav_read_stderr (BraseroProcess *process, const gchar *line)
194 {
195 	int num;
196 	BraseroCdda2wav *cdda2wav;
197 	BraseroCdda2wavPrivate *priv;
198 
199 	cdda2wav = BRASERO_CDDA2WAV (process);
200 	priv = BRASERO_CDDA2WAV_PRIVATE (process);
201 
202 	if (sscanf (line, "100%%  track %d '%*s' recorded successfully", &num) == 1) {
203 		gchar *string;
204 
205 		priv->track_nb = num;
206 		string = g_strdup_printf (_("Copying audio track %02d"), priv->track_nb + 1);
207 		brasero_job_set_current_action (BRASERO_JOB (process),
208 		                                BRASERO_BURN_ACTION_DRIVE_COPY,
209 		                                string,
210 		                                TRUE);
211 		g_free (string);
212 	}
213 	else if (strstr (line, "percent_done:")) {
214 		gchar *string;
215 
216 		string = g_strdup_printf (_("Copying audio track %02d"), 1);
217 		brasero_job_set_current_action (BRASERO_JOB (process),
218 		                                BRASERO_BURN_ACTION_DRIVE_COPY,
219 		                                string,
220 		                                TRUE);
221 		g_free (string);
222 	}
223 	/* we have to do this otherwise with sscanf it will
224 	 * match every time it begins with a number */
225 	else if (strchr (line, '%') && sscanf (line, " %d%%", &num) == 1) {
226 		gdouble fraction;
227 
228 		fraction = (gdouble) num / (gdouble) 100.0;
229 		fraction = ((gdouble) priv->track_nb + fraction) / (gdouble) priv->track_num;
230 		brasero_job_set_progress (BRASERO_JOB (cdda2wav), fraction);
231 		brasero_job_start_progress (BRASERO_JOB (process), FALSE);
232 	}
233 
234 	return BRASERO_BURN_OK;
235 }
236 
237 static BraseroBurnResult
brasero_cdda2wav_set_argv_image(BraseroCdda2wav * cdda2wav,GPtrArray * argv,GError ** error)238 brasero_cdda2wav_set_argv_image (BraseroCdda2wav *cdda2wav,
239 				GPtrArray *argv,
240 				GError **error)
241 {
242 	BraseroCdda2wavPrivate *priv;
243 	int fd_out;
244 
245 	priv = BRASERO_CDDA2WAV_PRIVATE (cdda2wav);
246 
247 	/* We want raw output */
248 	g_ptr_array_add (argv, g_strdup ("output-format=cdr"));
249 
250 	/* we want all tracks */
251 	g_ptr_array_add (argv, g_strdup ("-B"));
252 
253 	priv->is_inf = FALSE;
254 
255 	if (brasero_job_get_fd_out (BRASERO_JOB (cdda2wav), &fd_out) == BRASERO_BURN_OK) {
256 		/* On the fly copying */
257 		g_ptr_array_add (argv, g_strdup ("-"));
258 	}
259 	else {
260 		if (!brasero_cdda2wav_get_output_filename_pattern (cdda2wav, error))
261 			return BRASERO_BURN_ERR;
262 
263 		g_ptr_array_add (argv, g_strdup (priv->file_pattern));
264 
265 		brasero_job_set_current_action (BRASERO_JOB (cdda2wav),
266 		                                BRASERO_BURN_ACTION_DRIVE_COPY,
267 		                                _("Preparing to copy audio disc"),
268 		                                FALSE);
269 	}
270 
271 	return BRASERO_BURN_OK;
272 }
273 
274 static BraseroBurnResult
brasero_cdda2wav_set_argv_size(BraseroCdda2wav * cdda2wav,GPtrArray * argv,GError ** error)275 brasero_cdda2wav_set_argv_size (BraseroCdda2wav *cdda2wav,
276                                 GPtrArray *argv,
277                                 GError **error)
278 {
279 	BraseroCdda2wavPrivate *priv;
280 	BraseroMedium *medium;
281 	BraseroTrack *track;
282 	BraseroDrive *drive;
283 	goffset medium_len;
284 	int i;
285 
286 	priv = BRASERO_CDDA2WAV_PRIVATE (cdda2wav);
287 
288 	/* we add the tracks */
289 	medium_len = 0;
290 	track = NULL;
291 	brasero_job_get_current_track (BRASERO_JOB (cdda2wav), &track);
292 
293 	drive = brasero_track_disc_get_drive (BRASERO_TRACK_DISC (track));
294 	medium = brasero_drive_get_medium (drive);
295 
296 	priv->track_num = brasero_medium_get_track_num (medium);
297 	for (i = 0; i < priv->track_num; i ++) {
298 		goffset len = 0;
299 
300 		brasero_medium_get_track_space (medium, i, NULL, &len);
301 		medium_len += len;
302 	}
303 	brasero_job_set_output_size_for_current_track (BRASERO_JOB (cdda2wav), medium_len, medium_len * 2352);
304 
305 	/* if there isn't any output file then that
306 	 * means we have to generate all the
307 	 * .inf files for cdrecord. */
308 	if (brasero_job_get_audio_output (BRASERO_JOB (cdda2wav), NULL) != BRASERO_BURN_OK)
309 		return BRASERO_BURN_NOT_RUNNING;
310 
311 	/* we want all tracks */
312 	g_ptr_array_add (argv, g_strdup ("-B"));
313 
314 	/* since we're running for an on-the-fly burning
315 	 * we only want the .inf files */
316 	g_ptr_array_add (argv, g_strdup ("-J"));
317 
318 	if (!brasero_cdda2wav_get_output_filename_pattern (cdda2wav, error))
319 		return BRASERO_BURN_ERR;
320 
321 	g_ptr_array_add (argv, g_strdup (priv->file_pattern));
322 
323 	priv->is_inf = TRUE;
324 
325 	return BRASERO_BURN_OK;
326 }
327 
328 static BraseroBurnResult
brasero_cdda2wav_set_argv(BraseroProcess * process,GPtrArray * argv,GError ** error)329 brasero_cdda2wav_set_argv (BraseroProcess *process,
330 			  GPtrArray *argv,
331 			  GError **error)
332 {
333 	BraseroDrive *drive;
334 	const gchar *device;
335 	BraseroTrack *track;
336 	BraseroJobAction action;
337 	BraseroBurnResult result;
338 	BraseroCdda2wav *cdda2wav;
339 
340 	cdda2wav = BRASERO_CDDA2WAV (process);
341 
342 	g_ptr_array_add (argv, g_strdup ("cdda2wav"));
343 
344 	/* Add the device path */
345 	track = NULL;
346 	result = brasero_job_get_current_track (BRASERO_JOB (process), &track);
347 	if (!track)
348 		return result;
349 
350 	drive = brasero_track_disc_get_drive (BRASERO_TRACK_DISC (track));
351 	device = brasero_drive_get_device (drive);
352 	g_ptr_array_add (argv, g_strdup_printf ("dev=%s", device));
353 
354 	/* Have it talking */
355 	g_ptr_array_add (argv, g_strdup ("-v255"));
356 
357 	brasero_job_get_action (BRASERO_JOB (cdda2wav), &action);
358 	if (action == BRASERO_JOB_ACTION_SIZE)
359 		result = brasero_cdda2wav_set_argv_size (cdda2wav, argv, error);
360 	else if (action == BRASERO_JOB_ACTION_IMAGE)
361 		result = brasero_cdda2wav_set_argv_image (cdda2wav, argv, error);
362 	else
363 		BRASERO_JOB_NOT_SUPPORTED (cdda2wav);
364 
365 	return result;
366 }
367 
368 static void
brasero_cdda2wav_class_init(BraseroCdda2wavClass * klass)369 brasero_cdda2wav_class_init (BraseroCdda2wavClass *klass)
370 {
371 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
372 	BraseroProcessClass *process_class = BRASERO_PROCESS_CLASS (klass);
373 
374 	g_type_class_add_private (klass, sizeof (BraseroCdda2wavPrivate));
375 
376 	parent_class = g_type_class_peek_parent(klass);
377 	object_class->finalize = brasero_cdda2wav_finalize;
378 
379 	process_class->stderr_func = brasero_cdda2wav_read_stderr;
380 	process_class->set_argv = brasero_cdda2wav_set_argv;
381 	process_class->post = brasero_cdda2wav_post;
382 }
383 
384 static void
brasero_cdda2wav_init(BraseroCdda2wav * obj)385 brasero_cdda2wav_init (BraseroCdda2wav *obj)
386 { }
387 
388 static void
brasero_cdda2wav_finalize(GObject * object)389 brasero_cdda2wav_finalize (GObject *object)
390 {
391 	BraseroCdda2wavPrivate *priv;
392 
393 	priv = BRASERO_CDDA2WAV_PRIVATE (object);
394 
395 	if (priv->file_pattern) {
396 		g_free (priv->file_pattern);
397 		priv->file_pattern = NULL;
398 	}
399 	G_OBJECT_CLASS (parent_class)->finalize (object);
400 }
401 
402 static void
brasero_cdda2wav_export_caps(BraseroPlugin * plugin)403 brasero_cdda2wav_export_caps (BraseroPlugin *plugin)
404 {
405 	GSList *output;
406 	GSList *input;
407 
408 	brasero_plugin_define (plugin,
409 			       "cdda2wav",
410 	                       NULL,
411 			       _("Copy tracks from an audio CD with all associated information"),
412 			       "Philippe Rouquier",
413 			       1);
414 
415 	output = brasero_caps_audio_new (BRASERO_PLUGIN_IO_ACCEPT_FILE /*|BRASERO_PLUGIN_IO_ACCEPT_PIPE*/, /* Keep on the fly on hold until it gets proper testing */
416 					 BRASERO_AUDIO_FORMAT_RAW|
417 	                                 BRASERO_METADATA_INFO);
418 
419 	input = brasero_caps_disc_new (BRASERO_MEDIUM_CDR|
420 	                               BRASERO_MEDIUM_CDRW|
421 	                               BRASERO_MEDIUM_CDROM|
422 	                               BRASERO_MEDIUM_CLOSED|
423 	                               BRASERO_MEDIUM_APPENDABLE|
424 	                               BRASERO_MEDIUM_HAS_AUDIO);
425 
426 	brasero_plugin_link_caps (plugin, output, input);
427 	g_slist_free (output);
428 	g_slist_free (input);
429 
430 	brasero_plugin_register_group (plugin, _(CDRTOOLS_DESCRIPTION));
431 }
432 
433 G_MODULE_EXPORT void
brasero_plugin_check_config(BraseroPlugin * plugin)434 brasero_plugin_check_config (BraseroPlugin *plugin)
435 {
436 	gchar *prog_path;
437 
438 	/* Just check that the program is in the path and executable. */
439 	prog_path = g_find_program_in_path ("cdda2wav");
440 	if (!prog_path) {
441 		brasero_plugin_add_error (plugin,
442 		                          BRASERO_PLUGIN_ERROR_MISSING_APP,
443 		                          "cdda2wav");
444 		return;
445 	}
446 
447 	if (!g_file_test (prog_path, G_FILE_TEST_IS_EXECUTABLE)) {
448 		g_free (prog_path);
449 		brasero_plugin_add_error (plugin,
450 		                          BRASERO_PLUGIN_ERROR_MISSING_APP,
451 		                          "cdda2wav");
452 		return;
453 	}
454 	g_free (prog_path);
455 
456 	/* This is what it should be. Now, as I did not write and icedax plugin
457 	 * the above is enough so that cdda2wav can use a symlink to icedax.
458 	 * As for the version checking, it becomes impossible given that with
459 	 * icedax the string would not start with cdda2wav. So ... */
460 	/*
461 	gint version [3] = { 2, 0, -1};
462 	brasero_plugin_test_app (plugin,
463 	                         "cdda2wav",
464 	                         "--version",
465 	                         "cdda2wav %d.%d",
466 	                         version);
467 	*/
468 }
469