1 /*************************************************************************
2 * K3BExportPlugin.cpp - export of K3b project files
3 * -------------------
4 * begin : Thu Apr 13 2017
5 * copyright : (C) 2017 by Thomas Eschenbacher
6 * email : Thomas.Eschenbacher@gmx.de
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "config.h"
19
20 #include <errno.h>
21
22 #include <QBuffer>
23 #include <QByteArray>
24 #include <QDir>
25 #include <QDomDocument>
26 #include <QDomElement>
27 #include <QFileInfo>
28 #include <QIODevice>
29 #include <QMap>
30 #include <QPointer>
31 #include <QProcess>
32 #include <QRegExp>
33 #include <QTextStream>
34
35 #include <KLocalizedString> // for the i18n macro
36 #include <KZip>
37
38 #include "libkwave/CodecManager.h"
39 #include "libkwave/Encoder.h"
40 #include "libkwave/FileInfo.h"
41 #include "libkwave/Label.h"
42 #include "libkwave/LabelList.h"
43 #include "libkwave/Logger.h"
44 #include "libkwave/MessageBox.h"
45 #include "libkwave/MetaDataList.h"
46 #include "libkwave/Parser.h"
47 #include "libkwave/Plugin.h"
48 #include "libkwave/PluginManager.h"
49 #include "libkwave/SignalManager.h"
50 #include "libkwave/String.h"
51 #include "libkwave/Utils.h"
52
53 #include "K3BExportDialog.h"
54 #include "K3BExportPlugin.h"
55
KWAVE_PLUGIN(export_k3b,K3BExportPlugin)56 KWAVE_PLUGIN(export_k3b, K3BExportPlugin)
57
58 /** mime type of K3b project files */
59 #define K3B_PROJECT_MIME_TYPE "application/x-k3b"
60
61 /** file suffix of K3b project files */
62 #define K3B_FILE_SUFFIX _("*.k3b")
63
64 /** number of digits to use for out files */
65 #define OUTFILE_DIGITS 4
66
67 /** file name pattern for out files */
68 #define OUTFILE_PATTERN (_("[%0") + _("%1nr]").arg(OUTFILE_DIGITS))
69
70 /** file suffix for out files */
71 #define OUTFILE_SUFFIX _(".wav")
72
73 //***************************************************************************
74 Kwave::K3BExportPlugin::K3BExportPlugin(QObject *parent,
75 const QVariantList &args)
76 :Kwave::Plugin(parent, args),
77 m_url(),
78 m_pattern(),
79 m_selection_only(false),
80 m_export_location(EXPORT_TO_SUB_DIR),
81 m_overwrite_policy(USE_NEW_FILE_NAMES),
82 m_block_info()
83 {
84 }
85
86 //***************************************************************************
~K3BExportPlugin()87 Kwave::K3BExportPlugin::~K3BExportPlugin()
88 {
89 }
90
91 //***************************************************************************
interpreteParameters(QStringList & params)92 int Kwave::K3BExportPlugin::interpreteParameters(QStringList ¶ms)
93 {
94 bool ok;
95 QString param;
96
97 // evaluate the parameter list
98 if (params.count() != 5)
99 return -EINVAL;
100
101 // the selected URL
102 m_url = Kwave::URLfromUserInput(Kwave::Parser::unescape(params[0]));
103 if (!m_url.isValid()) return -EINVAL;
104
105 // label pattern
106 m_pattern = Kwave::Parser::unescape(params[1]);
107
108 // selection only
109 param = params[2];
110 int v = param.toInt(&ok);
111 Q_ASSERT(ok);
112 if (!ok) return -EINVAL;
113 m_selection_only = (v != 0);
114
115 // export location
116 param = params[3];
117 int where = param.toInt(&ok);
118 Q_ASSERT(ok);
119 if (!ok) return -EINVAL;
120 if ((where != EXPORT_TO_SAME_DIR) &&
121 (where != EXPORT_TO_SUB_DIR)) return -EINVAL;
122 m_export_location = static_cast<export_location_t>(where);
123
124 // overwrite policy
125 param = params[4];
126 int overwrite = param.toInt(&ok);
127 Q_ASSERT(ok);
128 if (!ok) return -EINVAL;
129 if ((overwrite != OVERWRITE_EXISTING_FILES) &&
130 (overwrite != USE_NEW_FILE_NAMES)) return -EINVAL;
131 m_overwrite_policy = static_cast<overwrite_policy_t>(overwrite);
132
133 return 0;
134 }
135
136 //***************************************************************************
scanBlocksToSave(const QString & base,sample_index_t selection_left,sample_index_t selection_right)137 void Kwave::K3BExportPlugin::scanBlocksToSave(const QString &base,
138 sample_index_t selection_left,
139 sample_index_t selection_right)
140 {
141 sample_index_t block_start;
142 sample_index_t block_end = 0;
143
144 Kwave::LabelList labels(signalManager().metaData());
145 Kwave::LabelListIterator it(labels);
146 Kwave::Label label = (it.hasNext()) ? it.next() : Kwave::Label();
147
148 // get the title of the whole file, in case that a block does not have
149 // an own title
150 FileInfo info(signalManager().metaData());
151 QString file_title = info.get(INF_NAME).toString();
152 QString file_artist = info.get(INF_AUTHOR).toString();
153
154 // fallback: if there is no INF_NAME either, fall back to the file
155 // name as last resort
156 if (!file_title.length()) file_title = base;
157
158 m_block_info.clear();
159 QString prev_title = file_title;
160 for (unsigned int index = 1; ; ++index) {
161 block_start = block_end;
162 block_end = (label.isNull()) ? signalLength() : label.pos();
163
164 QString block_title = (!label.isNull() && label.name().length()) ?
165 label.name() : prev_title;
166
167 if ((block_end > selection_left) && (block_start <= selection_right)) {
168 BlockInfo block;
169
170 // init and set reasonable defaults
171 block.m_index = index;
172 block.m_filename = QString();
173 block.m_start = block_start;
174 block.m_length = block_end - block_start;
175 block.m_title = prev_title;
176 block.m_artist = file_artist;
177
178 // detect title and artist
179 detectBlockMetaData(prev_title, m_pattern, block);
180 m_block_info.append(block);
181
182 prev_title = block_title;
183
184 // qDebug("#%d [%llu...%llu]", index, block_start, block_end);
185 // qDebug(" title = '%s'", DBG(block.m_title));
186 // qDebug(" artist = '%s'", DBG(block.m_artist));
187 } else {
188 prev_title = block_title;
189 }
190
191 if (label.isNull()) break;
192 label = (it.hasNext()) ? it.next() : Kwave::Label();
193 }
194 }
195
196 //***************************************************************************
createFileName(const QString & pattern,unsigned int index)197 QString Kwave::K3BExportPlugin::createFileName(const QString &pattern,
198 unsigned int index)
199 {
200 QString name = pattern;
201 QString num = _("%1").arg(index, OUTFILE_DIGITS, 10, QLatin1Char('0'));
202 name.replace(OUTFILE_PATTERN, num);
203 name += OUTFILE_SUFFIX;
204 return name;
205 }
206
207 //***************************************************************************
detectBlockMetaData(const QString & text,const QString & pattern,Kwave::K3BExportPlugin::BlockInfo & block)208 bool Kwave::K3BExportPlugin::detectBlockMetaData(
209 const QString &text,
210 const QString &pattern,
211 Kwave::K3BExportPlugin::BlockInfo &block
212 )
213 {
214 if (!pattern.length()) {
215 // auto detect -> try all known patterns
216 foreach (const QString &p, knownPatterns())
217 if (detectBlockMetaData(text, p, block))
218 return true;
219 return false;
220 }
221
222 // list of placeholders and pointers to the resulting strings
223 QMap <QString, QString *> map_patterns;
224 map_patterns.insert(_("[%artist]"), &block.m_artist);
225 map_patterns.insert(_("[%title]"), &block.m_title);
226
227 // try to find the placeholders within the pattern
228 // NOTE: we use a map because it will automatically be sorted (by pos)
229 QString pattern_esc = Kwave::Parser::escape(pattern);
230 QMap <int, QString *> map_result;
231 for (QMap<QString, QString *>::const_iterator
232 it(map_patterns.constBegin());
233 it != map_patterns.constEnd(); ++it)
234 {
235 const QString &placeholder = it.key();
236 QString placeholder_esc;
237 placeholder_esc = Kwave::Parser::escape(placeholder);
238 if (pattern_esc.contains(placeholder_esc)) {
239 const QString rx_string = _("(.+)");
240 int pos = pattern.indexOf(placeholder);
241 pattern_esc.replace(placeholder_esc, rx_string);
242 map_result.insert(pos, map_patterns[placeholder]);
243 }
244 }
245 if (map_result.isEmpty())
246 return false; // no placeholders found in the patterns
247
248 // relax the pattern: turn single whitespace to one or more whitespaces
249 pattern_esc.replace(QRegExp(_("(\\\\\\s)+")), _("\\s+"));
250
251 // try to match the pattern on the given text
252 QRegExp rx(pattern_esc, Qt::CaseInsensitive);
253 if (!rx.exactMatch(text.trimmed()))
254 return false; // does not match :-(
255
256 // we found a match
257 // -> now map the results into the corresponding result strings
258 const QList<int> &result_keys = map_result.keys();
259 for (int index = 0; index < map_result.count(); ++index) {
260 QString value = rx.cap(index + 1).trimmed();
261 if (value.length()) {
262 QString *result = map_result[result_keys[index]];
263 if (result) *result = value;
264 }
265 }
266
267 return true;
268 }
269
270 //***************************************************************************
load(QStringList & params)271 void Kwave::K3BExportPlugin::load(QStringList ¶ms)
272 {
273 Q_UNUSED(params)
274
275 QString menu_path = _("File/Save/%1").arg(_(I18N_NOOP2(
276 "menu: /File/Save/Export to K3b Project...",
277 "Export to K3b Project..."
278 )));
279 emitCommand(_("menu(plugin:setup(export_k3b),%1%2)").arg(
280 menu_path).arg(_("/#group(@SIGNAL)")));
281 emitCommand(_("menu(plugin:setup(export_k3b),%1%2)").arg(
282 menu_path).arg(_("/#icon(application-x-k3b)")));
283 }
284
285 //***************************************************************************
setup(QStringList & params)286 QStringList *Kwave::K3BExportPlugin::setup(QStringList ¶ms)
287 {
288 // try to interpret the previous parameters
289 interpreteParameters(params);
290
291 sample_index_t selection_left = 0;
292 sample_index_t selection_right = 0;
293 selection(Q_NULLPTR, &selection_left, &selection_right, false);
294
295 // enable the "selection only" checkbox only if there is something
296 // selected but not everything
297 bool selected_something = (selection_left != selection_right);
298 bool selected_all = ((selection_left == 0) &&
299 (selection_right + 1 >= signalLength()));
300 bool enable_selection_only = selected_something && !selected_all;
301
302 // show a "File / Save As..." dialog for the *.k3b file
303 QPointer<Kwave::K3BExportDialog> dialog =
304 new(std::nothrow) Kwave::K3BExportDialog(
305 _("kfiledialog:///kwave_export_k3b"),
306 K3B_FILE_SUFFIX + _("|") + i18nc(
307 "file type filter when exporting to K3b",
308 "K3b project file (*.k3b)"
309 ),
310 parentWidget(),
311 Kwave::URLfromUserInput(signalName()),
312 _("*.k3b"),
313 m_pattern,
314 m_selection_only,
315 enable_selection_only,
316 m_export_location,
317 m_overwrite_policy
318 );
319 if (!dialog) return Q_NULLPTR;
320
321 dialog->setWindowTitle(description());
322 if ((dialog->exec() != QDialog::Accepted) || !dialog) {
323 delete dialog;
324 return Q_NULLPTR;
325 }
326
327 QStringList *list = new(std::nothrow) QStringList();
328 Q_ASSERT(list);
329 if (!list) {
330 delete dialog;
331 return Q_NULLPTR;
332 }
333
334 // user has pressed "OK"
335 QUrl url = dialog->selectedUrl();
336 if (url.isEmpty()) {
337 delete dialog;
338 delete list;
339 return Q_NULLPTR;
340 }
341
342 QString name = url.path();
343 QFileInfo path(name);
344
345 // add the correct extension if necessary
346 if (path.suffix() != K3B_FILE_SUFFIX.mid(2))
347 url.setPath(name + K3B_FILE_SUFFIX.mid(1));
348
349 name = Kwave::Parser::escape(url.toString());
350 QString pattern = Kwave::Parser::escape(dialog->pattern());
351 int export_location = static_cast<int>(dialog->exportLocation());
352 int overwrite_policy = static_cast<int>(dialog->overwritePolicy());
353 bool selection_only = (enable_selection_only) ?
354 dialog->selectionOnly() : m_selection_only;
355
356 *list << name; // url
357 *list << pattern; // pattern
358 *list << QString::number(selection_only); // selection only
359 *list << QString::number(export_location); // export location
360 *list << QString::number(overwrite_policy); // overwrite policy
361
362 emitCommand(_("plugin:execute(export_k3b,") +
363 name + _(",") + pattern + _(",") +
364 QString::number(selection_only) + _(",") +
365 QString::number(export_location) + _(",") +
366 QString::number(overwrite_policy) + _(")")
367 );
368
369 if (dialog) delete dialog;
370 return list;
371 }
372
373 //***************************************************************************
374 /*
375 * taken from K3b, libk3b/projects/k3bdoc.cpp
376 *
377 * Copyright (C) 2003-2008 Sebastian Trueg <trueg@k3b.org>
378 *
379 */
saveGeneralDocumentData(QDomElement * part)380 void Kwave::K3BExportPlugin::saveGeneralDocumentData(QDomElement *part)
381 {
382 QDomDocument doc = part->ownerDocument();
383 QDomElement mainElem = doc.createElement(_("general"));
384
385 QDomElement propElem = doc.createElement(_("writing_mode"));
386 propElem.appendChild(doc.createTextNode(_("auto")));
387 mainElem.appendChild(propElem);
388
389 propElem = doc.createElement(_("dummy"));
390 propElem.setAttribute(_("activated"), _("no"));
391 mainElem.appendChild(propElem);
392
393 propElem = doc.createElement(_("on_the_fly"));
394 propElem.setAttribute(_("activated"), _("true"));
395 mainElem.appendChild(propElem);
396
397 propElem = doc.createElement(_("only_create_images"));
398 propElem.setAttribute(_("activated"), _("no"));
399 mainElem.appendChild(propElem);
400
401 propElem = doc.createElement(_("remove_images"));
402 propElem.setAttribute(_("activated"), _("no"));
403 mainElem.appendChild(propElem);
404
405 part->appendChild( mainElem );
406 }
407
408 //***************************************************************************
409 /*
410 * taken from K3b, libk3b/projects/audiocd/k3baudiodoc.cpp
411 *
412 * Copyright (C) 2003-2008 Sebastian Trueg <trueg@k3b.org>
413 * Copyright (C) 2009 Gustavo Pichorim Boiko <gustavo.boiko@kdemail.net>
414 * Copyright (C) 2010 Michal Malek <michalm@jabster.pl>
415 */
saveDocumentData(QDomElement * docElem)416 void Kwave::K3BExportPlugin::saveDocumentData(QDomElement *docElem)
417 {
418 #define GET_INF(inf) doc.createTextNode(info.get(inf).toString())
419
420 const Kwave::FileInfo info(signalManager().metaData());
421
422 QDomDocument doc = docElem->ownerDocument();
423 saveGeneralDocumentData(docElem);
424
425 // add normalize
426 QDomElement normalizeElem = doc.createElement(_("normalize"));
427 normalizeElem.appendChild(doc.createTextNode(_("no")));
428 docElem->appendChild(normalizeElem);
429
430 // add hide track
431 QDomElement hideFirstTrackElem = doc.createElement(_("hide_first_track"));
432 hideFirstTrackElem.appendChild(doc.createTextNode(_("no")));
433 docElem->appendChild(hideFirstTrackElem);
434
435 // save the audio cd ripping settings
436 // paranoia mode, read retries, and ignore read errors
437 // ------------------------------------------------------------
438 QDomElement ripMain = doc.createElement(_("audio_ripping"));
439 docElem->appendChild(ripMain);
440
441 QDomElement ripElem = doc.createElement(_("paranoia_mode"));
442 ripElem.appendChild(doc.createTextNode(_("0")));
443 ripMain.appendChild(ripElem);
444
445 ripElem = doc.createElement(_("read_retries"));
446 ripElem.appendChild(doc.createTextNode(_("0")));
447 ripMain.appendChild(ripElem);
448
449 ripElem = doc.createElement(_("ignore_read_errors"));
450 ripElem.appendChild(doc.createTextNode(_("no")));
451 ripMain.appendChild(ripElem);
452 // ------------------------------------------------------------
453
454 // save disc cd-text
455 // -------------------------------------------------------------
456 QDomElement cdTextMain = doc.createElement(_("cd-text"));
457 cdTextMain.setAttribute(_("activated"), _("yes"));
458
459 QDomElement cdTextElem = doc.createElement(_("title"));
460 cdTextElem.appendChild(GET_INF(INF_NAME));
461 cdTextMain.appendChild(cdTextElem);
462
463 cdTextElem = doc.createElement(_("artist"));
464 cdTextElem.appendChild(GET_INF(INF_AUTHOR));
465 cdTextMain.appendChild(cdTextElem);
466
467 cdTextElem = doc.createElement(_("arranger"));
468 cdTextElem.appendChild(GET_INF(INF_TECHNICAN));
469 cdTextMain.appendChild(cdTextElem);
470
471 cdTextElem = doc.createElement(_("songwriter"));
472 cdTextElem.appendChild(GET_INF(INF_PERFORMER));
473 cdTextMain.appendChild(cdTextElem);
474
475 cdTextElem = doc.createElement(_("composer"));
476 cdTextElem.appendChild(GET_INF(INF_ORGANIZATION));
477 cdTextMain.appendChild(cdTextElem);
478
479 cdTextElem = doc.createElement(_("disc_id"));
480 cdTextElem.appendChild(GET_INF(INF_CD));
481 cdTextMain.appendChild(cdTextElem);
482
483 cdTextElem = doc.createElement(_("upc_ean"));
484 cdTextElem.appendChild(GET_INF(INF_ISRC));
485 cdTextMain.appendChild(cdTextElem);
486
487 cdTextElem = doc.createElement(_("message"));
488 cdTextElem.appendChild(GET_INF(INF_COMMENTS));
489 cdTextMain.appendChild(cdTextElem);
490
491 docElem->appendChild( cdTextMain );
492 // -------------------------------------------------------------
493
494 // save the tracks
495 // -------------------------------------------------------------
496 QDomElement contentsElem = doc.createElement(_("contents"));
497
498 unsigned int index = 1;
499 foreach (const Kwave::K3BExportPlugin::BlockInfo &block, m_block_info) {
500 QString title = block.m_title;
501 QString artist = block.m_artist;
502 QString songwriter;
503 QString url = block.m_filename;
504
505 QDomElement trackElem = doc.createElement(_("track"));
506
507 // add sources
508 QDomElement sourcesParent = doc.createElement(_("sources"));
509 QDomElement sourceElem = doc.createElement(_("file"));
510 sourceElem.setAttribute(_("url"), url);
511 sourceElem.setAttribute(_("start_offset"), _("00:00:00"));
512 sourceElem.setAttribute(_("end_offset"), _("00:00:00"));
513 sourcesParent.appendChild(sourceElem);
514 trackElem.appendChild(sourcesParent);
515
516 // index 0
517 QDomElement index0Elem = doc.createElement(_("index0"));
518 index0Elem.appendChild(doc.createTextNode(QString::number(index)));
519 trackElem.appendChild(index0Elem);
520
521 // add cd-text
522 cdTextMain = doc.createElement(_("cd-text"));
523 cdTextElem = doc.createElement(_("title"));
524 cdTextElem.appendChild(doc.createTextNode(title));
525 cdTextMain.appendChild(cdTextElem);
526
527 cdTextElem = doc.createElement(_("artist"));
528 cdTextElem.appendChild(doc.createTextNode(artist));
529 cdTextMain.appendChild(cdTextElem);
530
531 cdTextElem = doc.createElement(_("arranger"));
532 cdTextElem.appendChild(GET_INF(INF_TECHNICAN));
533 cdTextMain.appendChild(cdTextElem);
534
535 cdTextElem = doc.createElement(_("songwriter"));
536 cdTextElem.appendChild(doc.createTextNode(songwriter));
537 cdTextMain.appendChild(cdTextElem );
538
539 cdTextElem = doc.createElement(_("composer"));
540 cdTextElem.appendChild(GET_INF(INF_ORGANIZATION));
541 cdTextMain.appendChild(cdTextElem);
542
543 cdTextElem = doc.createElement(_("isrc"));
544 cdTextElem.appendChild(GET_INF(INF_ISRC));
545 cdTextMain.appendChild(cdTextElem);
546
547 cdTextElem = doc.createElement(_("message"));
548 cdTextElem.appendChild(GET_INF(INF_COMMENTS));
549 cdTextMain.appendChild(cdTextElem);
550
551 trackElem.appendChild(cdTextMain);
552
553 // add copy protection
554 QDomElement copyElem = doc.createElement(_("copy_protection"));
555 copyElem.appendChild(doc.createTextNode(
556 info.get(INF_COPYRIGHTED).toInt() ? _("yes") : _("no")
557 ));
558 trackElem.appendChild(copyElem);
559
560 // add pre emphasis
561 copyElem = doc.createElement(_("pre_emphasis"));
562 copyElem.appendChild(doc.createTextNode(_("no")));
563 trackElem.appendChild(copyElem);
564
565 contentsElem.appendChild(trackElem);
566 index++;
567 }
568 // -------------------------------------------------------------
569
570 docElem->appendChild(contentsElem);
571 }
572
573 //***************************************************************************
start(QStringList & params)574 int Kwave::K3BExportPlugin::start(QStringList ¶ms)
575 {
576 qDebug("K3BExportPlugin::start()");
577
578 // interpret the parameters
579 int result = interpreteParameters(params);
580 if (result) return result;
581
582 // check the output file
583 if (!m_url.isLocalFile())
584 return -EINVAL; // sorry, KZip supports only local files
585
586 // determine output directory and file name pattern
587 QString k3b_filename = m_url.path();
588 QFileInfo fi(k3b_filename);
589 QString base = fi.completeBaseName();
590 QString out_dir;
591 QString out_pattern;
592 if (m_export_location == Kwave::K3BExportPlugin::EXPORT_TO_SUB_DIR) {
593 // export to a subdir with the name "<filename>.dir"
594 out_dir = fi.absolutePath() + QDir::separator() + base + _(".dir");
595 out_pattern = _("track-") + OUTFILE_PATTERN;
596 } else {
597 // use the same directory as the *.k3b file
598 out_dir = fi.absolutePath();
599 out_pattern = base + _("-track-") + OUTFILE_PATTERN;
600 }
601 qDebug("out_dir = '%s'", DBG(out_dir));
602 qDebug("out_pattern = '%s'", DBG(out_pattern));
603
604 // determine the selection settings
605 sample_index_t selection_left = 0;
606 sample_index_t selection_right = 0;
607 QVector<unsigned int> tracks;
608 selection(&tracks, &selection_left, &selection_right, false);
609
610 // check: only mono or stereo files are supported
611 if ((tracks.count() != 1) && (tracks.count() != 2)) {
612 qWarning("sorry, K3b can not handle %u tracks", tracks.count());
613 Kwave::MessageBox::sorry(parentWidget(), i18n(
614 "Only mono and stereo files can be used for an audio CD. "
615 "You can either deselect some channels or export the file "
616 "in a different file format that supports mono and stereo "
617 "only (for example FLAC) and then try again."
618 ));
619 return -EINVAL;
620 }
621
622 bool selected_something = (selection_left != selection_right);
623 bool selected_all = ( (selection_left == 0) &&
624 ((selection_right + 1) >= signalLength()) );
625 bool enable_selection_only = selected_something && !selected_all;
626 bool selection_only = enable_selection_only && m_selection_only;
627 if (!selection_only) {
628 selection_left = 0;
629 selection_right = signalLength() - 1;
630 }
631
632 // create a list of blocks to save, but not yet the output file names
633 scanBlocksToSave(base, selection_left, selection_right);
634 unsigned int count = m_block_info.count();
635 if (!count)
636 return -EINVAL;
637
638 // find the start index of the file numbering
639 unsigned int first = 1;
640 if (m_overwrite_policy == Kwave::K3BExportPlugin::USE_NEW_FILE_NAMES) {
641 // use new files, find out the highest existing index
642 QString pat = out_pattern;
643 pat.replace(OUTFILE_PATTERN, _("*"));
644 pat += OUTFILE_SUFFIX;
645
646 QDir dir(out_dir, pat);
647 QStringList files;
648 files = dir.entryList();
649
650 for (unsigned int i = first; i < (first + count); ++i) {
651 QString name = createFileName(out_pattern, i);
652 QRegExp rx(_("^(") + name + _(")$"), Qt::CaseInsensitive);
653 QStringList matches = files.filter(rx);
654 if (matches.count() > 0) first = i + 1;
655 }
656 qDebug("found first usable index -> %d", first);
657 } else {
658 // overwrite mode, always start at 1
659 }
660
661 // create the complete file names
662 for (unsigned int i = 0; i < count; ++i) {
663 m_block_info[i].m_filename = out_dir + QDir::separator() +
664 createFileName(out_pattern, first + i);
665 }
666
667 result = saveBlocks(selection_only, out_dir, out_pattern);
668 if (result != 0)
669 return result; // aborted or failed -> do not create a k3b file
670
671 result = saveK3BFile(k3b_filename);
672 if (result != 0)
673 return result; // aborted or failed -> do not ask about starting k3b
674
675 if (Kwave::MessageBox::questionYesNo(parentWidget(), i18n(
676 "A K3b project file has been created and audio files have "
677 "been exported.\n"
678 "Should I start K3b and open the audio CD project now?"
679 )) == KMessageBox::Yes) {
680 // call k3b and pass the project file name (must be full path)
681 QStringList args;
682 args << k3b_filename;
683 if (!QProcess::startDetached(_("k3b"), args)) {
684 return -EIO;
685 }
686 }
687
688 return result;
689 }
690
691 //***************************************************************************
saveBlocks(bool selection_only,const QString & out_dir,const QString & out_pattern)692 int Kwave::K3BExportPlugin::saveBlocks(bool selection_only,
693 const QString &out_dir,
694 const QString &out_pattern)
695 {
696 QString first_filename = Kwave::Parser::escapeForFileName(
697 QUrl::fromLocalFile(createFileName(out_pattern, 1)).toString());
698
699 // remember the original file info remove all unsupported/ properties,
700 // to avoid that the saveblocks plugin complains...
701 const Kwave::FileInfo orig_file_info(signalManager().metaData());
702 Kwave::FileInfo file_info(orig_file_info);
703 QList<Kwave::FileProperty> unsupported_properties;
704 {
705 QString mimetype = Kwave::CodecManager::mimeTypeOf(m_url);
706 Kwave::Encoder *encoder = Kwave::CodecManager::encoder(mimetype);
707 if (encoder) {
708 unsupported_properties = encoder->unsupportedProperties(
709 file_info.properties().keys());
710 delete encoder;
711 }
712 if (!unsupported_properties.isEmpty()) {
713 foreach (const Kwave::FileProperty &p, unsupported_properties) {
714 file_info.set(p, QVariant());
715 }
716 }
717 }
718
719 // make sure that the file uses 16 bits/sample only
720 file_info.setBits(16);
721
722 signalManager().metaData().replace(Kwave::MetaDataList(file_info));
723
724 // call the saveblocks plugin and let it do the main work of exporting
725 // the *.wav files with all the tracks...
726
727 QStringList params;
728 params << out_dir + QDir::separator() + first_filename;
729 params << Kwave::Parser::escape(out_pattern);
730 params << ((m_overwrite_policy == USE_NEW_FILE_NAMES) ? _("0") : _("1"));
731 params << (selection_only ? _("1") : _("0"));
732 int result = manager().executePlugin(_("saveblocks"), ¶ms);
733
734 // restore the original file info
735 signalManager().metaData().replace(Kwave::MetaDataList(orig_file_info));
736
737 return result;
738 }
739
740 //***************************************************************************
saveK3BFile(const QString & k3b_filename)741 int Kwave::K3BExportPlugin::saveK3BFile(const QString &k3b_filename)
742 {
743 // create the K3B file
744 KZip zip(k3b_filename);
745
746 bool ok = zip.open(QIODevice::WriteOnly);
747 if (!ok) return -EIO;
748
749 // write the mime type
750 QByteArray app_type(K3B_PROJECT_MIME_TYPE);
751 zip.setCompression(KZip::NoCompression);
752 zip.setExtraField(KZip::NoExtraField);
753 zip.writeFile(_("mimetype"), app_type);
754
755 // export file global data
756 QByteArray xml;
757 QBuffer out(&xml);
758 out.open(QIODevice::WriteOnly);
759
760 // save the data in the document
761 QDomDocument xmlDoc(_("k3b_audio_project"));
762
763 xmlDoc.appendChild(xmlDoc.createProcessingInstruction(
764 _("xml"), _("version=\"1.0\" encoding=\"UTF-8\"")
765 ));
766 QDomElement docElem = xmlDoc.createElement(_("k3b_audio_project"));
767 xmlDoc.appendChild(docElem);
768 saveDocumentData(&docElem);
769 QTextStream xmlStream(&out);
770 xmlDoc.save(xmlStream, 0);
771
772 out.close();
773
774 zip.setCompression(KZip::NoCompression);
775 zip.setExtraField(KZip::NoExtraField);
776 zip.writeFile(_("maindata.xml"), xml.data());
777 zip.close();
778
779 return 0;
780 }
781
782 //***************************************************************************
knownPatterns()783 QStringList Kwave::K3BExportPlugin::knownPatterns()
784 {
785 // list of all known detection patterns
786 QStringList patterns;
787 patterns << _("[%title] ([%artist])");
788 patterns << _("[%title], [%artist]");
789 patterns << _("[%artist]: [%title]");
790 patterns << _("[%artist] - [%title]");
791 return patterns;
792 }
793
794 //***************************************************************************
795 #include "K3BExportPlugin.moc"
796 //***************************************************************************
797 //***************************************************************************
798