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 &params)
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 &params)
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 &params)
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 &params)
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"), &params);
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