comparison src/filewriter/filewriter.c @ 984:df7b09989aee trunk

[svn] - We got a new plugin, captain! - FileWriter is the ultimate plugin for dumping audio to files. It should be the successor of Disk Writer and Out-Lame, as it supports the same output formats as those (WAVE and MP3). The main advantage of having only one file dumping plugin for many formats is that not every plugin has to think about file handling (where to write files to, how to call them etc.) that much anymore. - FileWriter is also very extensible - adding new output formats should be very easy.
author mf0102
date Mon, 30 Apr 2007 14:16:32 -0700
parents
children 029056fb9f9d
comparison
equal deleted inserted replaced
983:cc0c5c9ad2b9 984:df7b09989aee
1 /* FileWriter-Plugin
2 * (C) copyright 2007 merging of Disk Writer and Out-Lame by Michael Färber
3 *
4 * Original Out-Lame-Plugin:
5 * (C) copyright 2002 Lars Siebold <khandha5@gmx.net>
6 * (C) copyright 2006-2007 porting to audacious by Yoshiki Yazawa <yaz@cc.rim.or.jp>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23 #include "filewriter.h"
24 #include "plugins.h"
25
26 static GtkWidget *configure_win = NULL, *configure_vbox;
27 static GtkWidget *path_hbox, *path_label, *path_dirbrowser;
28 static GtkWidget *configure_bbox, *configure_ok, *configure_cancel;
29
30 static GtkWidget *fileext_hbox, *fileext_label, *fileext_combo, *plugin_button;
31 enum fileext_t { WAV = 0, MP3, FILEEXT_MAX } ;
32 static gint fileext = WAV;
33 static gchar *fileext_str[] = { "wav", "mp3" } ;
34 static FileWriter plugin;
35
36 static GtkWidget *saveplace_hbox, *saveplace;
37 static gboolean save_original = TRUE;
38
39 static GtkWidget *filenamefrom_hbox, *filenamefrom_label, *filenamefrom_toggle;
40 static gboolean filenamefromtags = TRUE;
41
42 static GtkWidget *use_suffix_toggle = NULL;
43 static gboolean use_suffix = FALSE;
44
45 static GtkWidget *prependnumber_toggle;
46 static gboolean prependnumber = FALSE;
47
48 static gchar *file_path = NULL;
49
50 gint ctrlsocket_get_session_id(void); /* FIXME */
51
52 extern TitleInput *input_get_song_tuple(const gchar *filename);
53
54 static void file_init(void);
55 static void file_about(void);
56 static gint file_open(AFormat fmt, gint rate, gint nch);
57 static void file_write(void *ptr, gint length);
58 static void file_close(void);
59 static void file_flush(gint time);
60 static void file_pause(short p);
61 static gint file_free(void);
62 static gint file_playing(void);
63 static gint file_get_written_time(void);
64 static gint file_get_output_time(void);
65 static void file_configure(void);
66
67 OutputPlugin file_op =
68 {
69 NULL,
70 NULL,
71 NULL,
72 file_init,
73 NULL,
74 file_about,
75 file_configure,
76 NULL,
77 NULL,
78 file_open,
79 file_write,
80 file_close,
81 file_flush,
82 file_pause,
83 file_free,
84 file_playing,
85 file_get_output_time,
86 file_get_written_time,
87 NULL
88 };
89
90 OutputPlugin *get_oplugin_info(void)
91 {
92 file_op.description = g_strdup_printf("FileWriter %s", VERSION);
93 return &file_op;
94 }
95
96 static void set_plugin(void)
97 {
98 if (fileext < 0 || fileext >= FILEEXT_MAX)
99 fileext = 0;
100
101 if (fileext == WAV)
102 plugin = wav_plugin;
103 if (fileext == MP3)
104 plugin = mp3_plugin;
105 }
106
107 static void file_init(void)
108 {
109 ConfigDb *db;
110
111 db = bmp_cfg_db_open();
112 bmp_cfg_db_get_int(db, "filewriter", "fileext", &fileext);
113 bmp_cfg_db_get_string(db, "filewriter", "file_path", &file_path);
114 bmp_cfg_db_get_bool(db, "filewriter", "save_original", &save_original);
115 bmp_cfg_db_get_bool(db, "filewriter", "use_suffix", &use_suffix);
116 bmp_cfg_db_get_bool(db, "filewriter", "filenamefromtags", &filenamefromtags);
117 bmp_cfg_db_get_bool(db, "filewriter", "prependnumber", &prependnumber);
118 bmp_cfg_db_close(db);
119
120 if (!file_path)
121 file_path = g_strdup(g_get_home_dir());
122
123 set_plugin();
124 if (plugin.init)
125 plugin.init();
126 }
127
128 void file_about(void)
129 {
130 static GtkWidget *dialog;
131
132 if (dialog != NULL)
133 return;
134
135 dialog = xmms_show_message("About FileWriter-Plugin",
136 "FileWriter-Plugin\n\n"
137 "This program is free software; you can redistribute it and/or modify\n"
138 "it under the terms of the GNU General Public License as published by\n"
139 "the Free Software Foundation; either version 2 of the License, or\n"
140 "(at your option) any later version.\n"
141 "\n"
142 "This program is distributed in the hope that it will be useful,\n"
143 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
144 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
145 "GNU General Public License for more details.\n"
146 "\n"
147 "You should have received a copy of the GNU General Public License\n"
148 "along with this program; if not, write to the Free Software\n"
149 "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,\n"
150 "USA.", "Ok", FALSE, NULL, NULL);
151 gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
152 GTK_SIGNAL_FUNC(gtk_widget_destroyed), &dialog);
153 }
154
155 static gint file_open(AFormat fmt, gint rate, gint nch)
156 {
157 gchar *origfilename = NULL, *filename = NULL, *temp = NULL;
158 gint pos;
159
160 if (xmms_check_realtime_priority())
161 {
162 xmms_show_message(_("Error"),
163 _("You cannot use the FileWriter plugin\n"
164 "when you're running in realtime mode."),
165 _("OK"), FALSE, NULL, NULL);
166 return 0;
167 }
168
169 input.format = fmt;
170 input.frequency = rate;
171 input.channels = nch;
172
173 pos = xmms_remote_get_playlist_pos(ctrlsocket_get_session_id());
174
175 origfilename = xmms_remote_get_playlist_file(ctrlsocket_get_session_id(),
176 pos);
177
178 tuple = input_get_song_tuple(origfilename);
179
180 if (filenamefromtags)
181 {
182 gchar *utf8 =
183 xmms_remote_get_playlist_title(ctrlsocket_get_session_id(), pos);
184 g_strchomp(utf8); //chop trailing ^J --yaz
185
186 filename = g_locale_from_utf8(utf8, -1, NULL, NULL, NULL);
187 g_free(utf8);
188 while (filename != NULL && (temp = strchr(filename, '/')) != NULL)
189 *temp = '-';
190 }
191 if (filename == NULL)
192 {
193 filename = g_path_get_basename(origfilename);
194 if (!use_suffix)
195 if ((temp = strrchr(filename, '.')) != NULL)
196 *temp = '\0';
197 }
198 if (filename == NULL)
199 filename = g_strdup_printf("aud-%d", pos);
200
201
202 if (prependnumber)
203 {
204 gint number;
205 if (tuple && tuple->track_number)
206 number = tuple->track_number;
207 else
208 number = pos + 1;
209
210 temp = g_strdup_printf("%.02d %s", number, filename);
211 g_free(filename);
212 filename = temp;
213 }
214
215 gchar *directory;
216 if (save_original)
217 directory = g_path_get_dirname(origfilename);
218 else
219 directory = g_strdup(file_path);
220
221 g_free(origfilename);
222
223 temp = g_strdup_printf("%s/%s.%s",
224 directory, filename, fileext_str[fileext]);
225 g_free(directory);
226 g_free(filename);
227 filename = temp;
228
229 output_file = vfs_fopen(filename, "w");
230 g_free(filename);
231
232 if (!output_file)
233 return 0;
234
235 return plugin.open();
236 }
237
238 static void convert_buffer(gpointer buffer, gint length)
239 {
240 gint i;
241
242 if (input.format == FMT_S8)
243 {
244 guint8 *ptr1 = buffer;
245 gint8 *ptr2 = buffer;
246
247 for (i = 0; i < length; i++)
248 *(ptr1++) = *(ptr2++) ^ 128;
249 }
250 if (input.format == FMT_S16_BE)
251 {
252 gint16 *ptr = buffer;
253
254 for (i = 0; i < length >> 1; i++, ptr++)
255 *ptr = GUINT16_SWAP_LE_BE(*ptr);
256 }
257 if (input.format == FMT_S16_NE)
258 {
259 gint16 *ptr = buffer;
260
261 for (i = 0; i < length >> 1; i++, ptr++)
262 *ptr = GINT16_TO_LE(*ptr);
263 }
264 if (input.format == FMT_U16_BE)
265 {
266 gint16 *ptr1 = buffer;
267 guint16 *ptr2 = buffer;
268
269 for (i = 0; i < length >> 1; i++, ptr2++)
270 *(ptr1++) = GINT16_TO_LE(GUINT16_FROM_BE(*ptr2) ^ 32768);
271 }
272 if (input.format == FMT_U16_LE)
273 {
274 gint16 *ptr1 = buffer;
275 guint16 *ptr2 = buffer;
276
277 for (i = 0; i < length >> 1; i++, ptr2++)
278 *(ptr1++) = GINT16_TO_LE(GUINT16_FROM_LE(*ptr2) ^ 32768);
279 }
280 if (input.format == FMT_U16_NE)
281 {
282 gint16 *ptr1 = buffer;
283 guint16 *ptr2 = buffer;
284
285 for (i = 0; i < length >> 1; i++, ptr2++)
286 *(ptr1++) = GINT16_TO_LE((*ptr2) ^ 32768);
287 }
288 }
289
290 static void file_write(void *ptr, gint length)
291 {
292 AFormat new_format;
293 int new_frequency, new_channels;
294 EffectPlugin *ep;
295
296 new_format = input.format;
297 new_frequency = input.frequency;
298 new_channels = input.channels;
299
300 ep = get_current_effect_plugin();
301 if ( effects_enabled() && ep && ep->query_format ) {
302 ep->query_format(&new_format,&new_frequency,&new_channels);
303 }
304
305 if ( effects_enabled() && ep && ep->mod_samples ) {
306 length = ep->mod_samples(&ptr,length,
307 input.format,
308 input.frequency,
309 input.channels );
310 }
311
312 if (input.format == FMT_S8 || input.format == FMT_S16_BE ||
313 input.format == FMT_U16_LE || input.format == FMT_U16_BE ||
314 input.format == FMT_U16_NE)
315 convert_buffer(ptr, length);
316 #ifdef WORDS_BIGENDIAN
317 if (input.format == FMT_S16_NE)
318 convert_buffer(ptr, length);
319 #endif
320
321 plugin.write(ptr, length);
322 }
323
324 static void file_close(void)
325 {
326 plugin.close();
327
328 if (output_file)
329 {
330 written = 0;
331 vfs_fclose(output_file);
332 }
333 output_file = NULL;
334 }
335
336 static void file_flush(gint time)
337 {
338 if (time < 0)
339 return;
340
341 file_close();
342 file_open(input.format, input.frequency, input.channels);
343
344 offset = time;
345 }
346
347 static void file_pause(short p)
348 {
349 }
350
351 static gint file_free(void)
352 {
353 return plugin.free();
354 }
355
356 static gint file_playing(void)
357 {
358 return plugin.playing();
359 }
360
361 static gint file_get_written_time(void)
362 {
363 return plugin.get_written_time();
364 }
365
366 static gint file_get_output_time(void)
367 {
368 return file_get_written_time();
369 }
370
371 static void configure_ok_cb(gpointer data)
372 {
373 ConfigDb *db;
374
375 fileext = gtk_combo_box_get_active(GTK_COMBO_BOX(fileext_combo));
376
377 g_free(file_path);
378 file_path = g_strdup(gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(path_dirbrowser)));
379
380 use_suffix =
381 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(use_suffix_toggle));
382
383 prependnumber =
384 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(prependnumber_toggle));
385
386 db = bmp_cfg_db_open();
387 bmp_cfg_db_set_int(db, "filewriter", "fileext", fileext);
388 bmp_cfg_db_set_string(db, "filewriter", "file_path", file_path);
389 bmp_cfg_db_set_bool(db, "filewriter", "save_original", save_original);
390 bmp_cfg_db_set_bool(db, "filewriter", "filenamefromtags", filenamefromtags);
391 bmp_cfg_db_set_bool(db, "filewriter", "use_suffix", use_suffix);
392 bmp_cfg_db_set_bool(db, "filewriter", "prependnumber", prependnumber);
393
394 bmp_cfg_db_close(db);
395
396 gtk_widget_destroy(configure_win);
397 if (path_dirbrowser)
398 gtk_widget_destroy(path_dirbrowser);
399 }
400
401 static void fileext_cb(GtkWidget *combo, gpointer data)
402 {
403 fileext = gtk_combo_box_get_active(GTK_COMBO_BOX(fileext_combo));
404 set_plugin();
405
406 gtk_widget_set_sensitive(plugin_button, plugin.configure != NULL);
407 }
408
409 static void plugin_configure_cb(GtkWidget *button, gpointer data)
410 {
411 if (plugin.configure)
412 plugin.configure();
413 }
414
415
416 static void saveplace_original_cb(GtkWidget *button, gpointer data)
417 {
418 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
419 {
420 gtk_widget_set_sensitive(path_hbox, FALSE);
421 save_original = TRUE;
422 }
423 }
424
425 static void saveplace_custom_cb(GtkWidget *button, gpointer data)
426 {
427 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
428 {
429 gtk_widget_set_sensitive(path_hbox, TRUE);
430 save_original = FALSE;
431 }
432 }
433
434 static void filenamefromtags_cb(GtkWidget *button, gpointer data)
435 {
436 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
437 {
438 gtk_widget_set_sensitive(use_suffix_toggle, FALSE);
439 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(use_suffix_toggle), FALSE);
440 use_suffix = FALSE;
441 filenamefromtags = TRUE;
442 }
443 }
444
445 static void filenamefromfilename_cb(GtkWidget *button, gpointer data)
446 {
447 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
448 {
449 gtk_widget_set_sensitive(use_suffix_toggle, TRUE);
450 filenamefromtags = FALSE;
451 }
452 }
453
454
455 static void configure_destroy(void)
456 {
457 if (path_dirbrowser)
458 gtk_widget_destroy(path_dirbrowser);
459 }
460
461 static void file_configure(void)
462 {
463 GtkTooltips *use_suffix_tooltips;
464
465 if (!configure_win)
466 {
467 configure_win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
468
469 gtk_signal_connect(GTK_OBJECT(configure_win), "destroy",
470 GTK_SIGNAL_FUNC(configure_destroy), NULL);
471 gtk_signal_connect(GTK_OBJECT(configure_win), "destroy",
472 GTK_SIGNAL_FUNC(gtk_widget_destroyed),
473 &configure_win);
474
475 gtk_window_set_title(GTK_WINDOW(configure_win),
476 _("Disk Writer Configuration"));
477 gtk_window_set_position(GTK_WINDOW(configure_win), GTK_WIN_POS_MOUSE);
478
479 gtk_container_set_border_width(GTK_CONTAINER(configure_win), 10);
480
481 configure_vbox = gtk_vbox_new(FALSE, 10);
482 gtk_container_add(GTK_CONTAINER(configure_win), configure_vbox);
483
484
485 fileext_hbox = gtk_hbox_new(FALSE, 5);
486 gtk_box_pack_start(GTK_BOX(configure_vbox), fileext_hbox, FALSE, FALSE, 0);
487
488 fileext_label = gtk_label_new(_("Output file extension:"));
489 gtk_box_pack_start(GTK_BOX(fileext_hbox), fileext_label, FALSE, FALSE, 0);
490
491 fileext_combo = gtk_combo_box_new_text();
492 gtk_combo_box_append_text(GTK_COMBO_BOX(fileext_combo), "WAV");
493 gtk_combo_box_append_text(GTK_COMBO_BOX(fileext_combo), "MP3");
494 gtk_box_pack_start(GTK_BOX(fileext_hbox), fileext_combo, FALSE, FALSE, 0);
495 gtk_combo_box_set_active(GTK_COMBO_BOX(fileext_combo), fileext);
496 g_signal_connect(G_OBJECT(fileext_combo), "changed", G_CALLBACK(fileext_cb), NULL);
497
498 plugin_button = gtk_button_new_with_label(_("Configure"));
499 gtk_widget_set_sensitive(plugin_button, plugin.configure != NULL);
500 g_signal_connect(G_OBJECT(plugin_button), "clicked", G_CALLBACK(plugin_configure_cb), NULL);
501 gtk_box_pack_end(GTK_BOX(fileext_hbox), plugin_button, FALSE, FALSE, 0);
502
503
504
505
506 gtk_box_pack_start(GTK_BOX(configure_vbox), gtk_hseparator_new(), FALSE, FALSE, 0);
507
508
509
510 saveplace_hbox = gtk_hbox_new(FALSE, 5);
511 gtk_container_add(GTK_CONTAINER(configure_vbox), saveplace_hbox);
512
513 saveplace = gtk_radio_button_new_with_label(NULL, _("Save into original directory"));
514 g_signal_connect(G_OBJECT(saveplace), "toggled", G_CALLBACK(saveplace_original_cb), NULL);
515 gtk_box_pack_start(GTK_BOX(saveplace_hbox), saveplace, FALSE, FALSE, 0);
516
517 saveplace = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(saveplace),
518 _("Save into custom directory"));
519 g_signal_connect(G_OBJECT(saveplace), "toggled", G_CALLBACK(saveplace_custom_cb), NULL);
520 gtk_box_pack_start(GTK_BOX(saveplace_hbox), saveplace, FALSE, FALSE, 0);
521
522 if (!save_original)
523 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(saveplace), TRUE);
524
525
526
527 path_hbox = gtk_hbox_new(FALSE, 5);
528 gtk_box_pack_start(GTK_BOX(configure_vbox), path_hbox, FALSE, FALSE, 0);
529
530 path_label = gtk_label_new(_("Output file folder:"));
531 gtk_box_pack_start(GTK_BOX(path_hbox), path_label, FALSE, FALSE, 0);
532
533 path_dirbrowser =
534 gtk_file_chooser_button_new ("Pick a folder",
535 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
536 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(path_dirbrowser),
537 file_path);
538 gtk_box_pack_start(GTK_BOX(path_hbox), path_dirbrowser, TRUE, TRUE, 0);
539
540 if (save_original)
541 gtk_widget_set_sensitive(path_hbox, FALSE);
542
543
544
545
546 gtk_box_pack_start(GTK_BOX(configure_vbox), gtk_hseparator_new(), FALSE, FALSE, 0);
547
548
549
550
551 filenamefrom_hbox = gtk_hbox_new(FALSE, 5);
552 gtk_container_add(GTK_CONTAINER(configure_vbox), filenamefrom_hbox);
553
554 filenamefrom_label = gtk_label_new(_("Get filename from:"));
555 gtk_box_pack_start(GTK_BOX(filenamefrom_hbox), filenamefrom_label, FALSE, FALSE, 0);
556
557 filenamefrom_toggle = gtk_radio_button_new_with_label(NULL, _("original file tags"));
558 g_signal_connect(G_OBJECT(filenamefrom_toggle), "toggled", G_CALLBACK(filenamefromtags_cb), NULL);
559 gtk_box_pack_start(GTK_BOX(filenamefrom_hbox), filenamefrom_toggle, FALSE, FALSE, 0);
560
561 filenamefrom_toggle =
562 gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(filenamefrom_toggle),
563 _("original filename"));
564 g_signal_connect(G_OBJECT(filenamefrom_toggle), "toggled", G_CALLBACK(filenamefromfilename_cb), NULL);
565 gtk_box_pack_start(GTK_BOX(filenamefrom_hbox), filenamefrom_toggle, FALSE, FALSE, 0);
566
567 if (!filenamefromtags)
568 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(filenamefrom_toggle), TRUE);
569
570
571
572
573 use_suffix_toggle = gtk_check_button_new_with_label(_("Don't strip file name extension"));
574 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(use_suffix_toggle), use_suffix);
575 gtk_box_pack_start(GTK_BOX(configure_vbox), use_suffix_toggle, FALSE, FALSE, 0);
576 use_suffix_tooltips = gtk_tooltips_new();
577 gtk_tooltips_set_tip(use_suffix_tooltips, use_suffix_toggle, "If enabled, the extension from the original filename will not be stripped before adding the .wav extension to the end.", NULL);
578 gtk_tooltips_enable(use_suffix_tooltips);
579
580 if (filenamefromtags)
581 gtk_widget_set_sensitive(use_suffix_toggle, FALSE);
582
583
584
585
586 gtk_box_pack_start(GTK_BOX(configure_vbox), gtk_hseparator_new(), FALSE, FALSE, 0);
587
588
589
590
591 prependnumber_toggle = gtk_check_button_new_with_label(_("Prepend track number to filename"));
592 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prependnumber_toggle), prependnumber);
593 gtk_box_pack_start(GTK_BOX(configure_vbox), prependnumber_toggle, FALSE, FALSE, 0);
594
595
596
597 configure_bbox = gtk_hbutton_box_new();
598 gtk_button_box_set_layout(GTK_BUTTON_BOX(configure_bbox),
599 GTK_BUTTONBOX_END);
600 gtk_button_box_set_spacing(GTK_BUTTON_BOX(configure_bbox), 5);
601 gtk_box_pack_start(GTK_BOX(configure_vbox), configure_bbox,
602 FALSE, FALSE, 0);
603
604 configure_cancel = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
605 gtk_signal_connect_object(GTK_OBJECT(configure_cancel), "clicked",
606 GTK_SIGNAL_FUNC(gtk_widget_destroy),
607 GTK_OBJECT(configure_win));
608 gtk_box_pack_start(GTK_BOX(configure_bbox), configure_cancel,
609 TRUE, TRUE, 0);
610
611 configure_ok = gtk_button_new_from_stock(GTK_STOCK_OK);
612 gtk_signal_connect(GTK_OBJECT(configure_ok), "clicked",
613 GTK_SIGNAL_FUNC(configure_ok_cb), NULL);
614 gtk_box_pack_start(GTK_BOX(configure_bbox), configure_ok,
615 TRUE, TRUE, 0);
616
617 gtk_widget_show_all(configure_win);
618 }
619 }
620
621 VFSFile *output_file = NULL;
622 guint64 written = 0;
623 guint64 offset = 0;
624 TitleInput *tuple = NULL;