1 /* -*- linux-c -*-
2 Copyright (C) 2004 Tom Szilagyi
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 $Id: export.c 1292 2014-05-02 12:29:01Z tszilagyi $
19 */
20
21 #include <config.h>
22
23 #include <stddef.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #include <fnmatch.h>
30 #include <sys/stat.h>
31 #include <glib.h>
32 #include <glib/gstdio.h>
33 #include <glib-object.h>
34 #include <gdk/gdk.h>
35 #include <gtk/gtk.h>
36
37 #include "athread.h"
38 #include "common.h"
39 #include "i18n.h"
40 #include "utils.h"
41 #include "utils_gui.h"
42 #include "decoder/file_decoder.h"
43 #include "encoder/file_encoder.h"
44 #include "encoder/enc_lame.h"
45 #include "metadata.h"
46 #include "options.h"
47 #include "export.h"
48
49
50 #define BUFSIZE 10240
51
52 extern GtkWidget * main_window;
53 extern options_t options;
54
55 GtkWidget * export_window;
56 int export_slot_count;
57
58 typedef struct {
59
60 char * infile;
61
62 char * artist;
63 char * album;
64 char * title;
65 char * original;
66 int year;
67 int no;
68
69 } export_item_t;
70
71
72 char *
export_compress_str(char * buf,int limit)73 export_compress_str(char * buf, int limit) {
74
75 char * str;
76 char * valid = "abcdefghijklmnopqrstuvwxyz0123456789";
77 int i = 0;
78 int j = 0;
79
80 str = g_ascii_strdown(buf, -1);
81 g_strcanon(str, valid, '_');
82
83 for (i = 0; str[i]; i++) {
84 if (str[i] == '_') {
85 continue;
86 }
87 str[j] = str[i];
88 j++;
89 }
90
91 if (limit < j) {
92 str[limit] = '\0';
93 } else if (j > 0) {
94 str[j] = '\0';
95 } else {
96 str[0] = 'x';
97 str[1] = '\0';
98 }
99
100 return str;
101 }
102
103 int
export_map_has(export_map_t * map,char * val)104 export_map_has(export_map_t * map, char * val) {
105
106 export_map_t * pmap;
107
108 for (pmap = map; pmap; pmap = pmap->next) {
109 if (!strcmp(pmap->val, val)) {
110 return 1;
111 }
112 }
113
114 return 0;
115 }
116
117 export_map_t *
export_map_new(export_map_t * _map,char * key,int limit)118 export_map_new(export_map_t * _map, char * key, int limit) {
119
120 export_map_t * map;
121 char * tmp;
122 int i = '2';
123
124 if ((map = (export_map_t *)malloc(sizeof(export_map_t))) == NULL) {
125 fprintf(stderr, "export_map_new(): malloc error\n");
126 return NULL;
127 }
128
129 map->next = NULL;
130 map->key = g_utf8_casefold(key, -1);
131
132 tmp = export_compress_str(key, limit);
133
134 while (export_map_has(_map, tmp) && i <= 'z') {
135
136 tmp[strlen(tmp)-1] = i;
137
138 if ((i >= '2' && i < '9') || (i >= 'a' && i <= 'z')) {
139 ++i;
140 } else {
141 i = 'a';
142 }
143 }
144
145 map->val = strdup(tmp);
146
147 g_free(tmp);
148 return map;
149 }
150
151 char *
export_map_put(export_map_t ** map,char * key,int limit)152 export_map_put(export_map_t ** map, char * key, int limit) {
153
154 export_map_t * pmap;
155 export_map_t * _pmap;
156
157 if (key == NULL || key[0] == '\0') {
158 return NULL;
159 }
160
161 if (*map == NULL) {
162 *map = export_map_new(NULL, key, limit);
163 return (*map)->val;
164 } else {
165 char * key1 = g_utf8_casefold(key, -1);
166
167 for (_pmap = pmap = *map; pmap; _pmap = pmap, pmap = pmap->next) {
168
169 if (!g_utf8_collate(key1, pmap->key)) {
170 g_free(key1);
171 return pmap->val;
172 }
173 }
174
175 g_free(key1);
176
177 _pmap->next = export_map_new(*map, key, limit);
178 return _pmap->next->val;
179 }
180 }
181
182 void
export_map_free(export_map_t * map)183 export_map_free(export_map_t * map) {
184
185 export_map_t * pmap;
186
187 for (pmap = map; pmap; map = pmap) {
188 pmap = map->next;
189 g_free(map->key);
190 free(map->val);
191 free(map);
192 }
193 }
194
195
196 export_t *
export_new(void)197 export_new(void) {
198
199 export_t * export;
200
201 if ((export = (export_t *)calloc(1, sizeof(export_t))) == NULL) {
202 fprintf(stderr, "export_new: calloc error\n");
203 return NULL;
204 }
205
206 #ifndef HAVE_LIBPTHREAD
207 export->mutex = g_mutex_new();
208 #endif /* !HAVE_LIBPTHREAD */
209
210 return export;
211 }
212
213 void
export_item_free(export_item_t * item)214 export_item_free(export_item_t * item) {
215
216 free(item->infile);
217 free(item->artist);
218 free(item->album);
219 free(item->title);
220 free(item->original);
221 free(item);
222 }
223
224 void
export_free(export_t * export)225 export_free(export_t * export) {
226
227 GSList * node;
228
229 #ifndef HAVE_LIBPTHREAD
230 g_mutex_free(export->mutex);
231 #endif /* !HAVE_LIBPTHREAD */
232
233 for (node = export->slist; node; node = node->next) {
234 export_item_free((export_item_t *)node->data);
235 }
236
237 g_slist_free(export->slist);
238 g_strfreev(export->excl_patternv);
239
240 export_map_free(export->artist_map);
241 export_map_free(export->record_map);
242 free(export);
243 }
244
245 void
export_append_item(export_t * export,char * infile,char * artist,char * album,char * title,int year,int no)246 export_append_item(export_t * export, char * infile,
247 char * artist, char * album, char * title, int year, int no) {
248
249 export_item_t * item;
250 char * basename;
251 char * ext;
252
253 if ((item = (export_item_t *)calloc(1, sizeof(export_item_t))) == NULL) {
254 fprintf(stderr, "export_append_item: calloc error\n");
255 return;
256 }
257
258 item->infile = strdup(infile);
259 basename = g_path_get_basename(infile);
260 if ((ext = g_strrstr(basename, ".")) != NULL) {
261 *ext = '\0';
262 }
263
264 item->artist = (artist && artist[0] != '\0') ? strdup(artist) : strdup(_("Unknown Artist"));
265 item->album = (album && album[0] != '\0') ? strdup(album) : strdup(_("Unknown Album"));
266 item->title = (title && title[0] != '\0') ? strdup(title) : strdup(_("Unknown Track"));
267 item->original = basename;
268 item->year = year;
269 item->no = no;
270
271 export->slist = g_slist_append(export->slist, item);
272 }
273
274 gboolean
export_finish(gpointer user_data)275 export_finish(gpointer user_data) {
276
277 export_t * export = (export_t *)user_data;
278
279 gtk_window_resize(GTK_WINDOW(export_window),
280 export_window->allocation.width,
281 export_window->allocation.height - export->slot->allocation.height);
282
283 gtk_widget_destroy(export->slot);
284 export->slot = NULL;
285
286 g_source_remove(export->progbar_tag);
287
288 export_free(export);
289
290 --export_slot_count;
291
292 if (export_slot_count == 0) {
293 unregister_toplevel_window(export_window);
294 gtk_widget_destroy(export_window);
295 export_window = NULL;
296 }
297
298 return FALSE;
299 }
300
301 int
export_item_set_path(export_t * export,export_item_t * item,char * path,char * ext,int index)302 export_item_set_path(export_t * export, export_item_t * item, char * path, char * ext, int index) {
303
304 char track[MAXLEN];
305 char buf[3*MAXLEN];
306 char str_no[16];
307 char str_index[16];
308
309 track[0] = '\0';
310 buf[0] = '\0';
311
312 strcat(buf, export->outdir);
313
314 if (export->dir_for_artist) {
315 strcat(buf, "/");
316 strcat(buf, export_map_put(&export->artist_map, item->artist, export->dir_len_limit));
317 if (!is_dir(buf) && mkdir(buf, S_IRUSR | S_IWUSR | S_IXUSR) < 0) {
318 fprintf(stderr, "mkdir: %s: %s\n", buf, strerror(errno));
319 return -1;
320 }
321 }
322
323 if (export->dir_for_album) {
324 strcat(buf, "/");
325 strcat(buf, export_map_put(&export->record_map, item->album, export->dir_len_limit));
326 if (!is_dir(buf) && mkdir(buf, S_IRUSR | S_IWUSR | S_IXUSR) < 0) {
327 fprintf(stderr, "mkdir: %s: %s\n", buf, strerror(errno));
328 return -1;
329 }
330 }
331
332 snprintf(str_no, 15, "%02d", item->no);
333 snprintf(str_index, 15, "%04d", index);
334
335 make_string_va(track, export->template, 'o', item->original, 'a', item->artist,
336 'r', item->album, 't', item->title, 'n', str_no, 'x', ext, 'i', str_index, 0);
337
338 snprintf(path, MAXLEN-1, "%s/%s", buf, track);
339 return 0;
340 }
341
342 gboolean
update_progbar_ratio(gpointer user_data)343 update_progbar_ratio(gpointer user_data) {
344
345 export_t * export = (export_t *)user_data;
346
347 if (export->slot) {
348
349 char tmp[16];
350
351 AQUALUNG_MUTEX_LOCK(export->mutex);
352 snprintf(tmp, 15, "%d%%", (int)(export->ratio * 100));
353 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(export->progbar), export->ratio);
354 AQUALUNG_MUTEX_UNLOCK(export->mutex);
355 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(export->progbar), tmp);
356 }
357
358 return TRUE;
359 }
360
361 gboolean
set_prog_src_file_entry_idle(gpointer user_data)362 set_prog_src_file_entry_idle(gpointer user_data) {
363
364 export_t * export = (export_t *)user_data;
365
366 if (export->slot) {
367
368 char * utf8 = NULL;
369
370 AQUALUNG_MUTEX_LOCK(export->mutex);
371 utf8 = g_filename_display_name(export->file1);
372 AQUALUNG_MUTEX_UNLOCK(export->mutex);
373
374 gtk_entry_set_text(GTK_ENTRY(export->prog_file_entry1), utf8);
375 gtk_widget_grab_focus(export->prog_cancel_button);
376 g_free(utf8);
377 }
378
379 return FALSE;
380 }
381
382 gboolean
set_prog_trg_file_entry_idle(gpointer user_data)383 set_prog_trg_file_entry_idle(gpointer user_data) {
384
385 export_t * export = (export_t *)user_data;
386
387 if (export->slot) {
388
389 char * utf8 = NULL;
390
391 AQUALUNG_MUTEX_LOCK(export->mutex);
392 utf8 = g_filename_display_name(export->file2);
393 AQUALUNG_MUTEX_UNLOCK(export->mutex);
394
395 gtk_entry_set_text(GTK_ENTRY(export->prog_file_entry2), utf8);
396 gtk_widget_grab_focus(export->prog_cancel_button);
397 g_free(utf8);
398 }
399
400 return FALSE;
401 }
402
403 void
set_prog_src_file_entry(export_t * export,char * file)404 set_prog_src_file_entry(export_t * export, char * file) {
405
406 AQUALUNG_MUTEX_LOCK(export->mutex);
407 strncpy(export->file1, file, MAXLEN-1);
408 AQUALUNG_MUTEX_UNLOCK(export->mutex);
409
410 aqualung_idle_add(set_prog_src_file_entry_idle, export);
411 }
412
413 void
set_prog_trg_file_entry(export_t * export,char * file)414 set_prog_trg_file_entry(export_t * export, char * file) {
415
416 AQUALUNG_MUTEX_LOCK(export->mutex);
417 strncpy(export->file2, file, MAXLEN-1);
418 AQUALUNG_MUTEX_UNLOCK(export->mutex);
419
420 aqualung_idle_add(set_prog_trg_file_entry_idle, export);
421 }
422
423
424 void
export_meta_amend_frame(metadata_t * meta,int tag,int type,export_item_t * item)425 export_meta_amend_frame(metadata_t * meta, int tag, int type, export_item_t * item) {
426
427 char * str;
428 meta_frame_t * frame;
429
430 /* see whether particular frame type is available in this tag */
431 if (!meta_get_fieldname_embedded(tag, type, &str)) {
432 return;
433 }
434
435 /* if yes, check for existence */
436 frame = metadata_get_frame_by_tag_and_type(meta, tag, type, NULL);
437 if (frame != NULL) {
438 return;
439 }
440
441 /* not found, add it with content from export item */
442 frame = meta_frame_new();
443 frame->tag = tag;
444 frame->type = type;
445 switch (type) {
446 case META_FIELD_TITLE:
447 frame->field_val = strdup(item->title);
448 break;
449 case META_FIELD_ARTIST:
450 frame->field_val = strdup(item->artist);
451 break;
452 case META_FIELD_ALBUM:
453 frame->field_val = strdup(item->album);
454 break;
455 case META_FIELD_DATE:
456 {
457 char str_year[6];
458 snprintf(str_year, 5, "%d", item->year);
459 frame->field_val = strdup(str_year);
460 }
461 break;
462 case META_FIELD_TRACKNO:
463 frame->int_val = item->no;
464 break;
465 }
466
467 metadata_add_frame(meta, frame);
468 }
469
470
471 /* Add metadata fields stored in Music Store / Playlist
472 * if they were not transferred from source file metadata.
473 */
474 void
export_meta_amend_stored_fields(metadata_t * meta,int tags,export_item_t * item)475 export_meta_amend_stored_fields(metadata_t * meta, int tags, export_item_t * item) {
476
477 int tag = META_TAG_MAX;
478
479 /* iterate on possible output tags */
480 while (tag) {
481
482 if ((tags & tag) == 0) {
483 tag >>= 1;
484 continue;
485 }
486
487 if (strcmp(item->title, _("Unknown Track")) != 0) {
488 export_meta_amend_frame(meta, tag, META_FIELD_TITLE, item);
489 }
490 if (strcmp(item->artist, _("Unknown Artist")) != 0) {
491 export_meta_amend_frame(meta, tag, META_FIELD_ARTIST, item);
492 }
493 if (strcmp(item->album, _("Unknown Album")) != 0) {
494 export_meta_amend_frame(meta, tag, META_FIELD_ALBUM, item);
495 }
496 if (item->year != 0) {
497 export_meta_amend_frame(meta, tag, META_FIELD_DATE, item);
498 }
499 if (item->no != 0) {
500 export_meta_amend_frame(meta, tag, META_FIELD_TRACKNO, item);
501 }
502
503 tag >>= 1;
504 }
505 }
506
507
508 void
export_item(export_t * export,export_item_t * item,int index)509 export_item(export_t * export, export_item_t * item, int index) {
510
511 file_decoder_t * fdec;
512 file_encoder_t * fenc;
513 encoder_mode_t mode;
514 char * ext = "raw";
515 char filename[MAXLEN];
516 int tags = 0;
517 int force_copy = 0;
518
519 float buf[2*BUFSIZE];
520 int n_read;
521 long long samples_read = 0;
522
523 memset(&mode, 0, sizeof(encoder_mode_t));
524
525 switch (export->format) {
526 case ENC_SNDFILE_LIB:
527 ext = "wav";
528 tags = 0;
529 break;
530 case ENC_FLAC_LIB:
531 ext = "flac";
532 tags = META_TAG_OXC | META_TAG_FLAC_APIC;
533 break;
534 case ENC_VORBIS_LIB:
535 ext = "ogg";
536 tags = META_TAG_OXC;
537 break;
538 case ENC_LAME_LIB:
539 ext = "mp3";
540 tags = META_TAG_ID3v1 | META_TAG_ID3v2 | META_TAG_APE;
541 break;
542 }
543
544
545 fdec = file_decoder_new();
546
547 if (file_decoder_open(fdec, item->infile)) {
548 return;
549 }
550
551 if (export->filter_same) {
552 if ((fdec->file_lib == FLAC_LIB && export->format == ENC_FLAC_LIB) ||
553 (fdec->file_lib == VORBIS_LIB && export->format == ENC_VORBIS_LIB) ||
554 (fdec->file_lib == MAD_LIB && export->format == ENC_LAME_LIB)) {
555 force_copy = 1;
556 }
557 }
558
559 if (export->excl_enabled) {
560 char * utf8 = g_filename_display_name(item->infile);
561 int i;
562
563 for (i = 0; export->excl_patternv[i]; i++) {
564
565 if (*(export->excl_patternv[i]) == '\0') {
566 continue;
567 }
568
569 if (fnmatch(export->excl_patternv[i], utf8, FNM_CASEFOLD) == 0) {
570 force_copy = 1;
571 break;
572 }
573 }
574
575 g_free(utf8);
576 }
577
578 if (force_copy || export->format == ENC_COPY) {
579 if ((ext = strrchr(item->infile, '.')) == NULL) {
580 ext = "";
581 } else {
582 ++ext;
583 }
584 }
585
586 if (export_item_set_path(export, item, filename, ext, index) < 0) {
587 file_decoder_close(fdec);
588 file_decoder_delete(fdec);
589 return;
590 }
591
592 set_prog_src_file_entry(export, item->infile);
593 set_prog_trg_file_entry(export, filename);
594
595 if (force_copy || export->format == ENC_COPY) {
596
597 char buf[BUFSIZE];
598 size_t n_read;
599 struct stat statbuf;
600 unsigned long pos = 0;
601 int n = 0;
602 FILE * fi;
603 FILE * fo;
604
605 file_decoder_close(fdec);
606 file_decoder_delete(fdec);
607
608 if (g_stat(item->infile, &statbuf) != -1) {
609 if (statbuf.st_size == 0) {
610 return;
611 }
612 }
613
614 if ((fi = fopen(item->infile, "rb")) == NULL) {
615 fprintf(stdout, "export_item: unable to open file %s\n", item->infile);
616 return;
617 }
618
619 if ((fo = fopen(filename, "wb")) == NULL) {
620 fclose(fi);
621 fprintf(stdout, "export_item: unable to open file %s\n", filename);
622 return;
623 }
624
625 while ((n_read = fread(buf, sizeof(char), sizeof(buf), fi)) > 0 && !export->cancelled) {
626 fwrite(buf, sizeof(char), n_read, fo);
627 pos += n_read;
628
629 if (n-- == 0) {
630 n = 100;
631 AQUALUNG_MUTEX_LOCK(export->mutex);
632 export->ratio = (double)pos / statbuf.st_size;
633 AQUALUNG_MUTEX_UNLOCK(export->mutex);
634 }
635 }
636
637 fclose(fi);
638 fclose(fo);
639 return;
640 }
641
642
643 strncpy(mode.filename, filename, MAXLEN-1);
644 mode.file_lib = export->format;
645 mode.sample_rate = fdec->fileinfo.sample_rate;
646 mode.channels = fdec->fileinfo.channels;
647
648 if (mode.file_lib == ENC_FLAC_LIB) {
649 mode.clevel = export->bitrate;
650 } else if (mode.file_lib == ENC_VORBIS_LIB) {
651 mode.bps = export->bitrate * 1000;
652 } else if (mode.file_lib == ENC_LAME_LIB) {
653 mode.bps = export->bitrate * 1000;
654 mode.vbr = export->vbr;
655 }
656
657 mode.write_meta = export->write_meta;
658 if (mode.write_meta) {
659 if (fdec->meta == NULL) {
660 mode.meta = metadata_new();
661 } else {
662 mode.meta = metadata_clone(fdec->meta, tags);
663 }
664 export_meta_amend_stored_fields(mode.meta, tags, item);
665 }
666
667 fenc = file_encoder_new();
668
669 if (file_encoder_open(fenc, &mode)) {
670 return;
671 }
672
673 while (!export->cancelled) {
674
675 n_read = file_decoder_read(fdec, buf, BUFSIZE);
676 file_encoder_write(fenc, buf, n_read);
677
678 samples_read += n_read;
679
680 AQUALUNG_MUTEX_LOCK(export->mutex);
681 export->ratio = (double)samples_read / fdec->fileinfo.total_samples;
682 AQUALUNG_MUTEX_UNLOCK(export->mutex);
683
684 if (n_read < BUFSIZE) {
685 break;
686 }
687 }
688
689 file_decoder_close(fdec);
690 file_encoder_close(fenc);
691 file_decoder_delete(fdec);
692 file_encoder_delete(fenc);
693 if (mode.meta != NULL) {
694 metadata_free(mode.meta);
695 }
696 }
697
698 void *
export_thread(void * arg)699 export_thread(void * arg) {
700
701 export_t * export = (export_t *)arg;
702 GSList * node;
703 int index = 0;
704
705 AQUALUNG_THREAD_DETACH();
706
707 for (node = export->slist; node; node = node->next) {
708
709 if (export->cancelled) {
710 break;
711 }
712
713 ++index;
714 export_item(export, (export_item_t *)node->data, index);
715 }
716
717 aqualung_idle_add(export_finish, export);
718
719 return NULL;
720 }
721
722 void
export_browse_cb(GtkButton * button,gpointer data)723 export_browse_cb(GtkButton * button, gpointer data) {
724
725 file_chooser_with_entry(_("Please select the directory for exported files."),
726 main_window,
727 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
728 FILE_CHOOSER_FILTER_NONE,
729 (GtkWidget *)data,
730 options.exportdir);
731 }
732
733 GtkWidget *
export_create_format_combo(void)734 export_create_format_combo(void) {
735
736 GtkWidget * combo = gtk_combo_box_new_text();
737 int n = -1;
738
739 #ifdef HAVE_SNDFILE_ENC
740 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "WAV");
741 ++n;
742 #endif /* HAVE_SNDFILE_ENC */
743 #ifdef HAVE_FLAC_ENC
744 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "FLAC");
745 ++n;
746 #endif /* HAVE_FLAC_ENC */
747 #ifdef HAVE_VORBISENC
748 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "Ogg Vorbis");
749 ++n;
750 #endif /* HAVE_VORBISENC */
751 #ifdef HAVE_LAME
752 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "MP3");
753 ++n;
754 #endif /* HAVE_LAME */
755 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Copy"));
756 ++n;
757
758 if (n >= options.export_file_format) {
759 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), options.export_file_format);
760 } else {
761 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
762 }
763
764 return combo;
765 }
766
767
768 /* returns file_lib value */
769 int
export_get_format_from_combo(GtkWidget * combo)770 export_get_format_from_combo(GtkWidget * combo) {
771
772 int file_lib = -1;
773 gchar * text = gtk_combo_box_get_active_text(GTK_COMBO_BOX(combo));
774 if (strcmp(text, "WAV") == 0) {
775 file_lib = ENC_SNDFILE_LIB;
776 }
777 if (strcmp(text, "FLAC") == 0) {
778 file_lib = ENC_FLAC_LIB;
779 }
780 if (strcmp(text, "Ogg Vorbis") == 0) {
781 file_lib = ENC_VORBIS_LIB;
782 }
783 if (strcmp(text, "MP3") == 0) {
784 file_lib = ENC_LAME_LIB;
785 }
786 if (strcmp(text, _("Copy")) == 0) {
787 file_lib = ENC_COPY;
788 }
789 g_free(text);
790 return file_lib;
791 }
792
793 void
export_format_combo_changed(GtkWidget * widget,gpointer data)794 export_format_combo_changed(GtkWidget * widget, gpointer data) {
795
796 export_t * export = (export_t *)data;
797 gchar * text = gtk_combo_box_get_active_text(GTK_COMBO_BOX(widget));
798
799 if (strcmp(text, "WAV") == 0 || strcmp(text, _("Copy")) == 0) {
800 gtk_widget_hide(export->bitrate_scale);
801 gtk_widget_hide(export->bitrate_label);
802 gtk_widget_hide(export->bitrate_value_label);
803 gtk_widget_hide(export->vbr_check);
804 gtk_widget_hide(export->meta_check);
805 }
806 if (strcmp(text, "FLAC") == 0) {
807 gtk_widget_show(export->bitrate_scale);
808 gtk_widget_show(export->bitrate_label);
809 gtk_label_set_text(GTK_LABEL(export->bitrate_label), _("Compression level:"));
810 gtk_widget_show(export->bitrate_value_label);
811 gtk_widget_hide(export->vbr_check);
812 gtk_widget_show(export->meta_check);
813
814 gtk_range_set_range(GTK_RANGE(export->bitrate_scale), 0, 8);
815 gtk_range_set_value(GTK_RANGE(export->bitrate_scale), options.export_bitrate);
816 }
817 if (strcmp(text, "Ogg Vorbis") == 0) {
818 gtk_widget_show(export->bitrate_scale);
819 gtk_widget_show(export->bitrate_label);
820 gtk_label_set_text(GTK_LABEL(export->bitrate_label), _("Bitrate [kbps]:"));
821 gtk_widget_show(export->bitrate_value_label);
822 gtk_widget_hide(export->vbr_check);
823 gtk_widget_show(export->meta_check);
824
825 gtk_range_set_range(GTK_RANGE(export->bitrate_scale), 32, 320);
826 gtk_range_set_value(GTK_RANGE(export->bitrate_scale), options.export_bitrate);
827 }
828 if (strcmp(text, "MP3") == 0) {
829 gtk_widget_show(export->bitrate_scale);
830 gtk_widget_show(export->bitrate_label);
831 gtk_label_set_text(GTK_LABEL(export->bitrate_label), _("Bitrate [kbps]:"));
832 gtk_widget_show(export->bitrate_value_label);
833 gtk_widget_show(export->vbr_check);
834 gtk_widget_show(export->meta_check);
835
836 gtk_range_set_range(GTK_RANGE(export->bitrate_scale), 32, 320);
837 gtk_range_set_value(GTK_RANGE(export->bitrate_scale), options.export_bitrate);
838 }
839
840 options.export_file_format = export_get_format_from_combo(widget);
841
842 g_free(text);
843 }
844
845 void
export_bitrate_changed(GtkRange * range,gpointer data)846 export_bitrate_changed(GtkRange * range, gpointer data) {
847
848 export_t * export = (export_t *)data;
849 float val = gtk_range_get_value(range);
850 gchar * text = gtk_combo_box_get_active_text(GTK_COMBO_BOX(export->format_combo));
851
852 if (strcmp(text, "FLAC") == 0) {
853 int i = (int)val;
854 char str[256];
855 switch (i) {
856 case 0:
857 case 8:
858 snprintf(str, 255, "%d (%s)", i, (i == 0) ? _("fast") : _("best"));
859 gtk_label_set_text(GTK_LABEL(export->bitrate_value_label), str);
860 break;
861 default:
862 snprintf(str, 255, "%d", i);
863 gtk_label_set_text(GTK_LABEL(export->bitrate_value_label), str);
864 break;
865 }
866 }
867 if (strcmp(text, "Ogg Vorbis") == 0) {
868 int i = (int)val;
869 char str[256];
870 snprintf(str, 255, "%d", i);
871 gtk_label_set_text(GTK_LABEL(export->bitrate_value_label), str);
872 }
873 if (strcmp(text, "MP3") == 0) {
874 int i = (int)val;
875 char str[256];
876 #ifdef HAVE_LAME
877 i = lame_encoder_validate_bitrate(i, 0);
878 #endif /* HAVE_LAME */
879 snprintf(str, 255, "%d", i);
880 gtk_label_set_text(GTK_LABEL(export->bitrate_value_label), str);
881 }
882 g_free(text);
883 }
884
885 void
check_dir_limit_toggled(GtkToggleButton * toggle,gpointer data)886 check_dir_limit_toggled(GtkToggleButton * toggle, gpointer data) {
887
888 export_t * export = (export_t *)data;
889
890 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(export->check_dir_artist)) ||
891 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(export->check_dir_album))) {
892 gtk_widget_set_sensitive(export->dirlen_spin, TRUE);
893 } else {
894 gtk_widget_set_sensitive(export->dirlen_spin, FALSE);
895 }
896 }
897
898 void
export_format_help_cb(GtkButton * button,gpointer user_data)899 export_format_help_cb(GtkButton * button, gpointer user_data) {
900
901 message_dialog(_("Help"),
902 ((export_t *)user_data)->dialog,
903 GTK_MESSAGE_INFO,
904 GTK_BUTTONS_OK,
905 NULL,
906 _("\nThe template string you enter here will be used to "
907 "construct the filename of the exported files. The Original "
908 "filename, Artist, Record and Track names are denoted by %%o, "
909 "%%a, %%r and %%t. The track number and format-dependent "
910 "file extension are denoted by %%n and %%x, respectively. "
911 "%%i gives an identifier which is unique within an export "
912 "session."));
913 }
914
915 void
export_check_excl_toggled(GtkWidget * widget,gpointer data)916 export_check_excl_toggled(GtkWidget * widget, gpointer data) {
917
918 gboolean state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
919 gtk_widget_set_sensitive((GtkWidget *)data, state);
920 }
921
922 gboolean
export_window_event(GtkWidget * widget,GdkEvent * event,gpointer * data)923 export_window_event(GtkWidget * widget, GdkEvent * event, gpointer * data) {
924
925 if (event->type == GDK_DELETE) {
926 gtk_window_iconify(GTK_WINDOW(export_window));
927 return TRUE;
928 }
929
930 return FALSE;
931 }
932
933 void
export_cancel_event(GtkButton * button,gpointer data)934 export_cancel_event(GtkButton * button, gpointer data) {
935
936 export_t * export = (export_t *)data;
937
938 export->cancelled = 1;
939 }
940
941 void
create_export_window()942 create_export_window() {
943
944 GtkWidget * vbox;
945
946 export_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
947 register_toplevel_window(export_window, TOP_WIN_SKIN | TOP_WIN_TRAY);
948 gtk_window_set_title(GTK_WINDOW(export_window), _("Exporting files"));
949 gtk_window_set_position(GTK_WINDOW(export_window), GTK_WIN_POS_CENTER);
950 gtk_window_set_transient_for(GTK_WINDOW(export_window), GTK_WINDOW(main_window));
951 gtk_window_set_type_hint(GTK_WINDOW(export_window), GDK_WINDOW_TYPE_HINT_DIALOG);
952
953 gtk_window_resize(GTK_WINDOW(export_window), 480, 110);
954 g_signal_connect(G_OBJECT(export_window), "event",
955 G_CALLBACK(export_window_event), NULL);
956
957 gtk_container_set_border_width(GTK_CONTAINER(export_window), 5);
958
959 vbox = gtk_vbox_new(FALSE, 0);
960 gtk_container_add(GTK_CONTAINER(export_window), vbox);
961
962 gtk_widget_show_all(export_window);
963 }
964
965 void
export_progress_window(export_t * export)966 export_progress_window(export_t * export) {
967
968 GtkWidget * vbox;
969
970 ++export_slot_count;
971
972 if (export_window == NULL) {
973 create_export_window();
974 }
975
976 vbox = gtk_bin_get_child(GTK_BIN(export_window));
977
978 export->slot = gtk_table_new(5, 2, FALSE);
979 gtk_box_pack_start(GTK_BOX(vbox), export->slot, FALSE, FALSE, 0);
980
981 gtk_table_attach(GTK_TABLE(export->slot), gtk_hseparator_new(), 0, 2, 0, 1,
982 GTK_FILL, GTK_FILL, 5, 5);
983
984 insert_label_entry(export->slot, _("Source file:"), &export->prog_file_entry1, NULL, 1, 2, FALSE);
985 insert_label_entry(export->slot, _("Target file:"), &export->prog_file_entry2, NULL, 2, 3, FALSE);
986
987 export->prog_cancel_button = gui_stock_label_button(_("Abort"), GTK_STOCK_CANCEL);
988 g_signal_connect(export->prog_cancel_button, "clicked", G_CALLBACK(export_cancel_event), export);
989 insert_label_progbar_button(export->slot, _("Progress:"), &export->progbar, export->prog_cancel_button, 3, 4);
990
991 gtk_table_attach(GTK_TABLE(export->slot), gtk_hseparator_new(), 0, 2, 4, 5,
992 GTK_FILL, GTK_FILL, 5, 5);
993
994 gtk_widget_grab_focus(export->prog_cancel_button);
995
996 gtk_widget_show_all(export->slot);
997 }
998
999 gboolean
export_dialog_close(GtkWidget * widget,GdkEvent * event,gpointer ex)1000 export_dialog_close(GtkWidget * widget, GdkEvent * event, gpointer ex) {
1001 export_t * export = ex;
1002 gtk_widget_destroy(export->dialog);
1003 return TRUE;
1004 }
1005
1006 gboolean
export_dialog_response(GtkDialog * dialog,gint response_id,gpointer ex)1007 export_dialog_response(GtkDialog * dialog, gint response_id, gpointer ex) {
1008 export_t * export = ex;
1009 char * poutdir = g_filename_from_utf8(gtk_entry_get_text(GTK_ENTRY(export->outdir_entry)), -1, NULL, NULL, NULL);
1010
1011 if (response_id != GTK_RESPONSE_ACCEPT) {
1012 gtk_widget_destroy(export->dialog);
1013 return TRUE;
1014 }
1015
1016 if ((poutdir == NULL) || (poutdir[0] == '\0')) {
1017 gtk_widget_grab_focus(export->outdir_entry);
1018 g_free(poutdir);
1019 return FALSE;
1020 }
1021
1022 normalize_filename(poutdir, export->outdir);
1023 g_free(poutdir);
1024
1025 if (strlen(gtk_entry_get_text(GTK_ENTRY(export->templ_entry))) == 0) {
1026 gtk_widget_grab_focus(export->templ_entry);
1027 return FALSE;
1028 } else {
1029 int ret;
1030 char buf[MAXLEN];
1031 char * format = (char *)gtk_entry_get_text(GTK_ENTRY(export->templ_entry));
1032 if ((ret = make_string_va(buf, format,
1033 'o', "o", 'a', "a", 'r', "r", 't', "t", 'n', "n", 'x', "x", 'i', "i", 0)) != 0) {
1034 make_string_strerror(ret, buf);
1035 message_dialog(_("Error in format string"),
1036 export->dialog,
1037 GTK_MESSAGE_ERROR,
1038 GTK_BUTTONS_OK,
1039 NULL,
1040 buf);
1041 return FALSE;
1042 }
1043 }
1044
1045 if (access(export->outdir, R_OK | W_OK) != 0) {
1046 message_dialog(_("Error"),
1047 export->dialog,
1048 GTK_MESSAGE_ERROR,
1049 GTK_BUTTONS_OK,
1050 NULL,
1051 _("\nDestination directory is not read-write accessible!"));
1052
1053 gtk_widget_grab_focus(export->outdir_entry);
1054 return FALSE;
1055 }
1056
1057 strncpy(options.exportdir, export->outdir, MAXLEN-1);
1058 set_option_from_entry(export->templ_entry, export->template, MAXLEN);
1059 set_option_from_entry(export->templ_entry, options.export_template, MAXLEN);
1060 options.export_file_format = export->format = export_get_format_from_combo(export->format_combo);
1061 options.export_bitrate = export->bitrate = gtk_range_get_value(GTK_RANGE(export->bitrate_scale));
1062 set_option_from_toggle(export->check_dir_artist, &options.export_subdir_artist);
1063 set_option_from_toggle(export->check_dir_album, &options.export_subdir_album);
1064 set_option_from_spin(export->dirlen_spin, &options.export_subdir_limit);
1065 set_option_from_toggle(export->vbr_check, &export->vbr);
1066 options.export_vbr = export->vbr;
1067 set_option_from_toggle(export->meta_check, &export->write_meta);
1068 options.export_metadata = export->write_meta;
1069 set_option_from_toggle(export->check_dir_artist, &export->dir_for_artist);
1070 set_option_from_toggle(export->check_dir_album, &export->dir_for_album);
1071
1072 set_option_from_toggle(export->check_filter_same, &export->filter_same);
1073 options.export_filter_same = export->filter_same;
1074 set_option_from_toggle(export->check_excl_enabled, &export->excl_enabled);
1075 options.export_excl_enabled = export->excl_enabled;
1076
1077 if (export->excl_enabled) {
1078 set_option_from_entry(export->excl_entry, options.export_excl_pattern, MAXLEN);
1079 export->excl_patternv =
1080 g_strsplit(gtk_entry_get_text(GTK_ENTRY(export->excl_entry)), ",", 0);
1081 }
1082
1083 if (export->dir_for_artist || export->dir_for_album) {
1084 set_option_from_spin(export->dirlen_spin, &export->dir_len_limit);
1085 } else {
1086 export->dir_len_limit = MAXLEN-1;
1087 }
1088
1089 gtk_widget_destroy(export->dialog);
1090
1091 export_progress_window(export);
1092 export->progbar_tag = aqualung_timeout_add(250, update_progbar_ratio, export);
1093 AQUALUNG_THREAD_CREATE(export->thread_id, NULL, export_thread, export);
1094
1095 return TRUE;
1096 }
1097
1098 void
export_start(export_t * export)1099 export_start(export_t * export) {
1100
1101 GtkWidget * content_area;
1102
1103 GtkWidget * help_button;
1104
1105 GtkWidget * table;
1106 GtkWidget * hbox;
1107 GtkWidget * frame;
1108
1109 export->dialog = gtk_dialog_new_with_buttons(_("Export files"),
1110 GTK_WINDOW(main_window),
1111 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
1112 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1113 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1114 NULL);
1115
1116 gtk_window_set_position(GTK_WINDOW(export->dialog), GTK_WIN_POS_CENTER);
1117 gtk_window_set_default_size(GTK_WINDOW(export->dialog), 400, -1);
1118 gtk_dialog_set_default_response(GTK_DIALOG(export->dialog), GTK_RESPONSE_REJECT);
1119
1120 content_area = gtk_dialog_get_content_area(GTK_DIALOG(export->dialog));
1121
1122 g_signal_connect(G_OBJECT(export->dialog), "response",
1123 G_CALLBACK(export_dialog_response), (gpointer)export);
1124 g_signal_connect(G_OBJECT(export->dialog), "delete_event",
1125 G_CALLBACK(export_dialog_close), (gpointer)export);
1126
1127 /* Location and filename */
1128 frame = gtk_frame_new(_("Location and filename"));
1129 gtk_box_pack_start(GTK_BOX(content_area), frame, FALSE, FALSE, 2);
1130 gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
1131
1132 table = gtk_table_new(5, 2, FALSE);
1133 gtk_container_add(GTK_CONTAINER(frame), table);
1134
1135 insert_label_entry_browse(table, _("Target directory:"), &export->outdir_entry, options.exportdir, 0, 1, export_browse_cb);
1136
1137 export->check_dir_artist = gtk_check_button_new_with_label(_("Create subdirectories for artists"));
1138 gtk_widget_set_name(export->check_dir_artist, "check_on_notebook");
1139 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(export->check_dir_artist), options.export_subdir_artist);
1140 gtk_table_attach(GTK_TABLE(table), export->check_dir_artist, 0, 3, 1, 2, GTK_FILL, GTK_FILL, 5, 5);
1141 g_signal_connect(G_OBJECT(export->check_dir_artist), "toggled",
1142 G_CALLBACK(check_dir_limit_toggled), export);
1143
1144 export->check_dir_album = gtk_check_button_new_with_label(_("Create subdirectories for albums"));
1145 gtk_widget_set_name(export->check_dir_album, "check_on_notebook");
1146 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(export->check_dir_album), options.export_subdir_album);
1147 gtk_table_attach(GTK_TABLE(table), export->check_dir_album, 0, 3, 2, 3, GTK_FILL, GTK_FILL, 5, 5);
1148 g_signal_connect(G_OBJECT(export->check_dir_album), "toggled",
1149 G_CALLBACK(check_dir_limit_toggled), export);
1150
1151 insert_label_spin_with_limits(table, _("Subdirectory name\nlength limit:"), &export->dirlen_spin, options.export_subdir_limit, 4, 64, 3, 4);
1152 gtk_widget_set_sensitive(export->dirlen_spin, options.export_subdir_artist || options.export_subdir_album);
1153
1154 help_button = gtk_button_new_from_stock(GTK_STOCK_HELP);
1155 g_signal_connect(help_button, "clicked", G_CALLBACK(export_format_help_cb), export);
1156 insert_label_entry_button(table, _("Filename template:"), &export->templ_entry, options.export_template, help_button, 4, 5);
1157
1158 /* Format */
1159 frame = gtk_frame_new(_("Format"));
1160 gtk_box_pack_start(GTK_BOX(content_area), frame, FALSE, FALSE, 2);
1161 gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
1162
1163 table = gtk_table_new(4, 2, FALSE);
1164 gtk_container_add(GTK_CONTAINER(frame), table);
1165
1166 hbox = gtk_hbox_new(FALSE, 0);
1167 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_("File format:")), FALSE, FALSE, 0);
1168 gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 5, 5);
1169
1170 export->format_combo = export_create_format_combo();
1171 g_signal_connect(G_OBJECT(export->format_combo), "changed",
1172 G_CALLBACK(export_format_combo_changed), export);
1173 gtk_table_attach(GTK_TABLE(table), export->format_combo, 1, 2, 0, 1,
1174 GTK_EXPAND | GTK_FILL, GTK_FILL, 5, 5);
1175
1176 hbox = gtk_hbox_new(FALSE, 0);
1177 export->bitrate_label = gtk_label_new(_("Compression level:"));
1178 gtk_box_pack_start(GTK_BOX(hbox), export->bitrate_label, FALSE, FALSE, 0);
1179 gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 5, 5);
1180
1181 export->bitrate_scale = gtk_hscale_new_with_range(0, 8, 1);
1182 g_signal_connect(G_OBJECT(export->bitrate_scale), "value-changed",
1183 G_CALLBACK(export_bitrate_changed), export);
1184 gtk_scale_set_draw_value(GTK_SCALE(export->bitrate_scale), FALSE);
1185 gtk_scale_set_digits(GTK_SCALE(export->bitrate_scale), 0);
1186 gtk_widget_set_size_request(export->bitrate_scale, 180, -1);
1187 gtk_table_attach(GTK_TABLE(table), export->bitrate_scale, 1, 2, 1, 2,
1188 GTK_EXPAND | GTK_FILL, GTK_FILL, 5, 5);
1189
1190 export->bitrate_value_label = gtk_label_new("0 (fast)");
1191 gtk_table_attach(GTK_TABLE(table), export->bitrate_value_label, 1, 2, 2, 3,
1192 GTK_EXPAND | GTK_FILL, GTK_FILL, 5, 5);
1193
1194 export->vbr_check = gtk_check_button_new_with_label(_("VBR encoding"));
1195 gtk_widget_set_name(export->vbr_check, "check_on_notebook");
1196 gtk_table_attach(GTK_TABLE(table), export->vbr_check, 0, 1, 2, 3, GTK_FILL, GTK_FILL, 5, 5);
1197 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(export->vbr_check), options.export_vbr);
1198
1199 export->meta_check = gtk_check_button_new_with_label(_("Tag files with metadata"));
1200 gtk_widget_set_name(export->meta_check, "check_on_notebook");
1201 gtk_table_attach(GTK_TABLE(table), export->meta_check, 0, 2, 3, 4,
1202 GTK_FILL, GTK_FILL, 5, 5);
1203 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(export->meta_check), options.export_metadata);
1204
1205
1206 /* Filter */
1207 frame = gtk_frame_new(_("Filter"));
1208 gtk_box_pack_start(GTK_BOX(content_area), frame, FALSE, FALSE, 2);
1209 gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
1210
1211 table = gtk_table_new(1, 2, FALSE);
1212 gtk_container_add(GTK_CONTAINER(frame), table);
1213
1214 export->check_filter_same = gtk_check_button_new_with_label(_("Do not reencode files already being in the target format"));
1215 gtk_widget_set_name(export->check_filter_same, "check_on_notebook");
1216 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(export->check_filter_same), options.export_filter_same);
1217 gtk_table_attach(GTK_TABLE(table), export->check_filter_same, 0, 2, 0, 1, GTK_FILL, GTK_FILL, 5, 5);
1218
1219 export->check_excl_enabled = gtk_check_button_new_with_label(_("Do not reencode files\nmatching wildcard:"));
1220 gtk_widget_set_name(export->check_excl_enabled, "check_on_notebook");
1221 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(export->check_excl_enabled), options.export_excl_enabled);
1222 gtk_table_attach(GTK_TABLE(table), export->check_excl_enabled, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 5, 5);
1223
1224 export->excl_entry = gtk_entry_new();
1225 gtk_entry_set_max_length(GTK_ENTRY(export->excl_entry), MAXLEN-1);
1226 gtk_entry_set_text(GTK_ENTRY(export->excl_entry), options.export_excl_pattern);
1227 gtk_table_attach(GTK_TABLE(table), export->excl_entry, 1, 2, 1, 2, GTK_FILL | GTK_EXPAND, GTK_FILL, 5, 5);
1228 gtk_widget_set_sensitive(export->excl_entry, options.export_excl_enabled);
1229
1230 g_signal_connect(G_OBJECT(export->check_excl_enabled), "toggled",
1231 G_CALLBACK(export_check_excl_toggled), export->excl_entry);
1232
1233
1234 gtk_widget_show_all(export->dialog);
1235 export_format_combo_changed(export->format_combo, export);
1236
1237 export->outdir[0] = '\0';
1238 return;
1239 }
1240
1241 // vim: shiftwidth=8:tabstop=8:softtabstop=8 :
1242
1243