diff --git a/kwave/MainWidget.cpp b/kwave/MainWidget.cpp index 9df9d31a..9ce7055f 100644 --- a/kwave/MainWidget.cpp +++ b/kwave/MainWidget.cpp @@ -1,1349 +1,1348 @@ /*************************************************************************** MainWidget.cpp - main widget of the Kwave TopWidget ------------------- begin : 1999 copyright : (C) 1999 by Martin Wilz email : Martin Wilz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include "libkwave/Drag.h" #include "libkwave/FileDrag.h" #include "libkwave/FileInfo.h" #include "libkwave/Label.h" #include "libkwave/LabelList.h" #include "libkwave/Logger.h" #include "libkwave/MessageBox.h" #include "libkwave/Parser.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libkwave/undo/UndoTransactionGuard.h" #include "libgui/FileDialog.h" #include "libgui/LabelPropertiesWidget.h" #include "libgui/OverViewWidget.h" #include "libgui/SignalWidget.h" #include "App.h" #include "FileContext.h" #include "MainWidget.h" /** * useful macro for command parsing */ #define CASE_COMMAND(x) } else if (parser.command() == _(x)) { /** * Limits the zoom to a minimum number of samples visible in one * screen. */ #define MINIMUM_SAMPLES_PER_SCREEN 5 /** * Default widht of the display in seconds when in streaming mode, * where no initial length information is available (guess: 5min) */ #define DEFAULT_DISPLAY_TIME (5 * 60.0) /** * File extension used for label lists */ #define LABEL_LIST_EXT _("*.label") /** * Filter for file dialogs for loading/saving labels */ #define LABEL_LIST_FILTER \ LABEL_LIST_EXT + _("|") + \ i18n("Kwave label list") + \ _(" (") + LABEL_LIST_EXT + _(")") //*************************************************************************** Kwave::MainWidget::MainWidget(QWidget *parent, Kwave::FileContext &context, const QSize &preferred_size) :QWidget(parent), Kwave::CommandHandler(), Kwave::Zoomable(), m_context(context), m_upper_dock(), m_lower_dock(), m_scroll_area(this), m_horizontal_scrollbar(0), m_signal_widget( &m_scroll_area, context.signalManager(), &m_upper_dock, &m_lower_dock ), m_overview(0), m_offset(0), m_zoom(1.0), m_preferred_size(preferred_size), m_delayed_update_timer(this) { // qDebug("MainWidget::MainWidget()"); setAcceptDrops(true); // enable drag&drop Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return; Kwave::PluginManager *plugin_manager = context.pluginManager(); Q_ASSERT(plugin_manager); if (!plugin_manager) return; plugin_manager->registerViewManager(&m_signal_widget); // topLayout: // - upper dock // - view port // - lower dock // - overview // - horizontal scroll bar QVBoxLayout *topLayout = new QVBoxLayout(this); Q_ASSERT(topLayout); if (!topLayout) return; topLayout->setSpacing(0); // -- upper dock -- topLayout->addLayout(&m_upper_dock); // -- view port with signal widget -- m_signal_widget.setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); m_scroll_area.setFrameStyle(QFrame::NoFrame); m_scroll_area.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_scroll_area.setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_scroll_area.setWidgetResizable(true); m_scroll_area.setWidget(&m_signal_widget); topLayout->addWidget(&m_scroll_area); // -- lower dock -- topLayout->addLayout(&m_lower_dock); // -- overview widget -- m_overview = new Kwave::OverViewWidget(*signal_manager, this); Q_ASSERT(m_overview); if (!m_overview) return; m_overview->setMinimumHeight(m_overview->sizeHint().height()); m_overview->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); topLayout->addWidget(m_overview); connect(m_overview, SIGNAL(valueChanged(sample_index_t)), this, SLOT(setOffset(sample_index_t))); connect(m_overview, SIGNAL(sigCommand(QString)), this, SIGNAL(sigCommand(QString))); m_overview->metaDataChanged(signal_manager->metaData()); m_overview->hide(); // -- horizontal scrollbar -- m_horizontal_scrollbar = new QScrollBar(this); Q_ASSERT(m_horizontal_scrollbar); if (!m_horizontal_scrollbar) return; m_horizontal_scrollbar->setOrientation(Qt::Horizontal); topLayout->addWidget(m_horizontal_scrollbar); connect(m_horizontal_scrollbar, SIGNAL(valueChanged(int)), this, SLOT(horizontalScrollBarMoved(int))); m_horizontal_scrollbar->hide(); // -- playback position update -- Kwave::PlaybackController &playback = signal_manager->playbackController(); connect(&playback, SIGNAL(sigPlaybackPos(sample_index_t)), m_overview, SLOT(showCursor(sample_index_t))); connect(&playback, SIGNAL(sigPlaybackStopped()), m_overview, SLOT(showCursor())); // -- connect all signals from/to the signal widget -- connect(&m_signal_widget, SIGNAL(sigCommand(QString)), this, SIGNAL(sigCommand(QString))); connect(&m_signal_widget, SIGNAL(sigCursorChanged(sample_index_t)), m_overview, SLOT(showCursor(sample_index_t))); // -- connect all signals from/to the signal manager -- connect(signal_manager, SIGNAL(sigTrackInserted(uint,Kwave::Track*)), this, SLOT(slotTrackInserted(uint,Kwave::Track*))); connect(signal_manager, SIGNAL(sigTrackDeleted(uint,Kwave::Track*)), this, SLOT(slotTrackDeleted(uint,Kwave::Track*))); connect(signal_manager, SIGNAL(sigMetaDataChanged(Kwave::MetaDataList)), this, SLOT(updateViewRange())); // set up the timer for delayed view range update m_delayed_update_timer.setSingleShot(true); connect(&m_delayed_update_timer, SIGNAL(timeout()), this, SLOT(updateViewRange())); setLayout(topLayout); } //*************************************************************************** bool Kwave::MainWidget::isOK() { return (m_horizontal_scrollbar && m_overview); } //*************************************************************************** Kwave::MainWidget::~MainWidget() { Kwave::PluginManager *plugin_manager = m_context.pluginManager(); Q_ASSERT(plugin_manager); if (plugin_manager) plugin_manager->registerViewManager(0); } //*************************************************************************** void Kwave::MainWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); if (!m_context.isActive() && (m_context.app().guiType() == Kwave::App::GUI_TAB)) { // HACK: this is a workaround for stupid bug in Qt, which sends us a // resize event with bogus size, when we are in tab mode, just // before getting deactivated !? if (!m_delayed_update_timer.isActive()) m_delayed_update_timer.start(0); } else { // update immediately updateViewRange(); } } //*************************************************************************** void Kwave::MainWidget::dragEnterEvent(QDragEnterEvent *event) { if (!event) return; if ((event->proposedAction() != Qt::MoveAction) && (event->proposedAction() != Qt::CopyAction)) return; /* unsupported action */ if (Kwave::FileDrag::canDecode(event->mimeData())) event->acceptProposedAction(); } //*************************************************************************** void Kwave::MainWidget::dropEvent(QDropEvent *event) { if (!event) return; if (!event->mimeData()) return; Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return; if (signal_manager->isEmpty() && Kwave::Drag::canDecode(event->mimeData())) { sample_index_t pos = m_offset + pixels2samples(event->pos().x()); sample_index_t len = 0; if ((len = Kwave::Drag::decode(this, event->mimeData(), *signal_manager, pos))) { // set selection to the new area where the drop was done signal_manager->selectRange(pos, len); event->acceptProposedAction(); } else { QStringList formats = event->mimeData()->formats(); QString fmt = (!formats.isEmpty()) ? formats.first() : QString(); qWarning("MainWidget::dropEvent(%s): failed !", DBG(fmt)); event->ignore(); } } } //*************************************************************************** void Kwave::MainWidget::wheelEvent(QWheelEvent *event) { if (!event || !m_overview) return; // process only wheel events on the signal and overview frame, // not on the channel controls or scrollbars if (!m_scroll_area.geometry().contains(event->pos()) && !m_overview->geometry().contains(event->pos()) ) { event->ignore(); return; } switch (event->modifiers()) { case Qt::NoModifier: { // no modifier + => scroll left/right if (event->delta() > 0) executeCommand(_("view:scroll_left()")); else if (event->delta() < 0) executeCommand(_("view:scroll_right()")); event->accept(); break; } case Qt::ShiftModifier: // + => page up/down if (event->delta() > 0) executeCommand(_("view:scroll_prev()")); else if (event->delta() < 0) executeCommand(_("view:scroll_next()")); event->accept(); break; case Qt::ControlModifier: { // + => zoom in/out int x = qMax(m_signal_widget.mapToViewPort(event->globalPos()), 0); if (event->delta() > 0) executeCommand(_("view:zoom_in(%1)").arg(x)); else if (event->delta() < 0) executeCommand(_("view:zoom_out(%1)").arg(x)); event->accept(); break; } default: event->ignore(); } } //*************************************************************************** void Kwave::MainWidget::closeEvent(QCloseEvent *e) { m_context.closeFile() ? e->accept() : e->ignore(); } //*************************************************************************** void Kwave::MainWidget::slotTrackInserted(unsigned int index, Kwave::Track *track) { // qDebug("MainWidget::slotTrackInserted(%u, %p)", // index, static_cast(track)); Q_UNUSED(index); Q_UNUSED(track); // when the first track has been inserted, set some reasonable zoom Kwave::SignalManager *signal_manager = m_context.signalManager(); bool first_track = (signal_manager && (signal_manager->tracks() == 1)); if (first_track) zoomAll(); else updateViewRange(); } //*************************************************************************** void Kwave::MainWidget::slotTrackDeleted(unsigned int index, Kwave::Track *track) { // qDebug("MainWidget::slotTrackDeleted(%u)", index); Q_UNUSED(index); Q_UNUSED(track); updateViewRange(); } //*************************************************************************** double Kwave::MainWidget::zoom() const { return m_zoom; } //*************************************************************************** int Kwave::MainWidget::executeCommand(const QString &command) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return -EINVAL; if (!command.length()) return -EINVAL; Kwave::Parser parser(command); const sample_index_t visible_samples = visibleSamples(); const sample_index_t signal_length = signal_manager->length(); if (false) { // -- zoom -- CASE_COMMAND("view:zoom_selection") zoomSelection(); CASE_COMMAND("view:zoom_in") int x = parser.hasParams() ? parser.toInt() : -1; zoomIn(x); CASE_COMMAND("view:zoom_out") int x = parser.hasParams() ? parser.toInt() : -1; zoomOut(x); CASE_COMMAND("view:zoom_normal") zoomNormal(); CASE_COMMAND("view:zoom_all") zoomAll(); // -- navigation -- CASE_COMMAND("goto") sample_index_t offset = parser.toSampleIndex(); setOffset((offset > (visible_samples / 2)) ? (offset - (visible_samples / 2)) : 0); signal_manager->selectRange(offset, 0); CASE_COMMAND("view:scroll_right") const sample_index_t step = visible_samples / 10; setOffset(m_offset + step); CASE_COMMAND("view:scroll_left") const sample_index_t step = visible_samples / 10; setOffset((step < m_offset) ? (m_offset - step) : 0); CASE_COMMAND("view:scroll_start") setOffset(0); signal_manager->selectRange(0, 0); CASE_COMMAND("view:scroll_end") if (signal_length >= visible_samples) setOffset(signal_length - visible_samples); CASE_COMMAND("view:scroll_next") setOffset(m_offset + visible_samples); CASE_COMMAND("view:scroll_prev") setOffset((visible_samples < m_offset) ? (m_offset - visible_samples) : 0); CASE_COMMAND("view:scroll_next_label") sample_index_t ofs = Kwave::LabelList(signal_manager->metaData()).nextLabelRight( m_offset + (visible_samples / 2)); if (ofs > signal_length) ofs = signal_length - 1; setOffset((ofs > (visible_samples / 2)) ? (ofs - (visible_samples / 2)) : 0); CASE_COMMAND("view:scroll_prev_label") sample_index_t ofs = Kwave::LabelList(signal_manager->metaData()).nextLabelLeft( m_offset + (visible_samples / 2)); setOffset((ofs > (visible_samples / 2)) ? (ofs - (visible_samples / 2)) : 0); // -- selection -- CASE_COMMAND("selectall") signal_manager->selectRange(0, signal_manager->length()); CASE_COMMAND("selectnext") if (signal_manager->selection().length()) signal_manager->selectRange(signal_manager->selection().last() + 1, signal_manager->selection().length()); else signal_manager->selectRange(signal_manager->length() - 1, 0); CASE_COMMAND("selectprev") sample_index_t ofs = signal_manager->selection().first(); sample_index_t len = signal_manager->selection().length(); if (!len) len = 1; if (len > ofs) len = ofs; signal_manager->selectRange(ofs - len, len); CASE_COMMAND("selecttoleft") signal_manager->selectRange(0, signal_manager->selection().last() + 1); CASE_COMMAND("selecttoright") signal_manager->selectRange(signal_manager->selection().first(), signal_manager->length() - signal_manager->selection().first() ); CASE_COMMAND("selectvisible") signal_manager->selectRange(m_offset, visibleSamples()); CASE_COMMAND("selectnone") signal_manager->selectRange(m_offset, 0); // label handling CASE_COMMAND("label:add") sample_index_t pos = parser.toSampleIndex(); if (!parser.isDone()) { // 2 parameters: position + description QString description = parser.nextParam(); signal_manager->addLabel(pos, description); } else { // 1 parameter only: open dialog for editing the description addLabel(pos, QString()); } CASE_COMMAND("label:edit") int index = parser.toInt(); Kwave::LabelList labels(signal_manager->metaData()); if ((index >= labels.count()) || (index < 0)) return -EINVAL; Kwave::Label label = labels.at(index); labelProperties(label); CASE_COMMAND("label:load") QString filename = parser.nextParam(); return loadLabels(filename); CASE_COMMAND("label:save") QString filename = parser.nextParam(); return saveLabels(filename); // CASE_COMMAND("label:by_intensity") // markSignal(command); // CASE_COMMAND("label:to_pitch") // convertMarkstoPitch(command); // CASE_COMMAND("label:by_period") // markPeriods(command); } else { return (signal_manager) ? signal_manager->executeCommand(command) : -ENOSYS; } return 0; } //*************************************************************************** void Kwave::MainWidget::refreshHorizontalScrollBar() { if (!m_horizontal_scrollbar || !m_context.signalManager()) return; m_horizontal_scrollbar->blockSignals(true); // show/hide the overview widget if (!m_context.signalManager()->isEmpty() && !m_overview->isVisible()) m_overview->show(); if (m_context.signalManager()->isEmpty() && m_overview->isVisible()) m_overview->hide(); // adjust the limits of the horizontal scrollbar if (m_context.signalManager()->length() > 1) { // get the view information in samples sample_index_t length = m_context.signalManager()->length(); sample_index_t visible = visibleSamples(); if (visible > length) visible = length; // calculate the scrollbar ranges in scrollbar's units // // NOTE: we must take care of possible numeric overflows // as the scrollbar internally works with "int" and // the offsets we use for the samples might be bigger! // // [-------------------------------------------##############] // ^ ^ ^ // min max page // // max + page = x | x < INT_MAX (!) // x := length / f // page = x * (visible / length) = visible / f // max = length / f - page // pos = (m_offset / length) * x = m_offset / f const sample_index_t f = qMax( sample_index_t(1), sample_index_t((length + INT_MAX - 1) / INT_MAX) ); int page = Kwave::toInt(visible / f); int min = 0; int max = Kwave::toInt(length / f) - page; int pos = Kwave::toInt(m_offset / f); int single = qMax(1, (page / (10 * qApp->wheelScrollLines()))); if (page < single) page = single; // qDebug("width=%d, max=%d, page=%d, single=%d, pos=%d, visible=%d", // m_width, max, page, single, pos, visible); // qDebug(" last=%d", pos + visible - 1); m_horizontal_scrollbar->setRange(min, max); m_horizontal_scrollbar->setValue(pos); m_horizontal_scrollbar->setSingleStep(single); m_horizontal_scrollbar->setPageStep(page); } else { m_horizontal_scrollbar->setRange(0, 0); } m_horizontal_scrollbar->blockSignals(false); } //*************************************************************************** void Kwave::MainWidget::horizontalScrollBarMoved(int newval) { // new offset = pos * f sample_index_t length = m_context.signalManager()->length(); const sample_index_t f = qMax( sample_index_t(1), sample_index_t((length + INT_MAX - 1) / INT_MAX) ); const sample_index_t pos = newval * f; setOffset(pos); } //*************************************************************************** void Kwave::MainWidget::updateViewRange() { Kwave::SignalManager *signal_manager = m_context.signalManager(); sample_index_t total = (signal_manager) ? signal_manager->length() : 0; if (m_overview) { m_overview->setRange(m_offset, visibleSamples(), total); // show/hide the overview widget and the horizontal scroll bar if (!m_context.signalManager()->isEmpty()) { if (!m_overview->isVisible()) m_overview->show(); if (!m_horizontal_scrollbar->isVisible()) m_horizontal_scrollbar->show(); } if (m_context.signalManager()->isEmpty()) { if (m_overview->isVisible()) m_overview->hide(); if (m_horizontal_scrollbar->isVisible()) m_horizontal_scrollbar->hide(); } } // forward the zoom and offset to the signal widget and overview fixZoomAndOffset(m_zoom, m_offset); m_signal_widget.setZoomAndOffset(m_zoom, m_offset); refreshHorizontalScrollBar(); } //*************************************************************************** sample_index_t Kwave::MainWidget::ms2samples(double ms) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return 0; return static_cast( rint(ms * signal_manager->rate() / 1E3)); } //*************************************************************************** sample_index_t Kwave::MainWidget::pixels2samples(unsigned int pixels) const { if ((pixels == 0) || (m_zoom <= 0.0)) return 0; return static_cast(static_cast(pixels) * m_zoom); } //*************************************************************************** int Kwave::MainWidget::samples2pixels(sample_index_t samples) const { if (m_zoom <= 0.0) return 0; return Kwave::toInt(rint(static_cast(samples) / m_zoom)); } //*************************************************************************** int Kwave::MainWidget::visibleWidth() const { return m_signal_widget.visibleWidth(); } //*************************************************************************** sample_index_t Kwave::MainWidget::visibleSamples() const { return pixels2samples(visibleWidth() - 1) + 1; } //*************************************************************************** double Kwave::MainWidget::fullZoom() const { const int width = visibleWidth(); if (width <= 0) return 0.0; // no zoom info, window is not yet ready Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return 0.0; if (signal_manager->isEmpty()) return 0.0; // no zoom if no signal sample_index_t length = signal_manager->length(); if (!length) { // no length: streaming mode -> try to use "estimated length" // and add some extra, 10% should be ok Kwave::FileInfo info(signal_manager->metaData()); if (info.contains(Kwave::INF_ESTIMATED_LENGTH)) { // estimated length in samples length = info.get(Kwave::INF_ESTIMATED_LENGTH).toULongLong(); length += length / 10; } if (length <= 0) { // fallback: start with some default zoom, // use five minutes (just guessed) length = static_cast(ceil(DEFAULT_DISPLAY_TIME * signal_manager->rate())); } } if (!length) return 0; // still no length !? -> bail out // example: width = 100 [pixels] and length = 3 [samples] // -> samples should be at positions 0, 49.5 and 99 // -> 49.5 [pixels / sample] // -> zoom = 1 / 49.5 [samples / pixel] // => full zoom [samples/pixel] = (length - 1) / (width - 1) if (length < static_cast(width) * 2) { // zoom is small enough, no error compensation needed return (static_cast(length) - 1.0) / static_cast(width - 1); } else { // zoom is big, pre-compensate rounding errors return (static_cast(length) - 0.5) / static_cast(width - 1); } } //*************************************************************************** void Kwave::MainWidget::fixZoomAndOffset(double zoom, sample_index_t offset) { double max_zoom; double min_zoom; sample_index_t length; const double old_zoom = m_zoom; const sample_index_t old_offset = m_offset; const int width = visibleWidth(); m_zoom = zoom; m_offset = offset; Q_ASSERT(m_zoom >= 0.0); Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return; if (!width) return; length = signal_manager->length(); if (!length) { // in streaming mode we have to use a guessed length length = static_cast(ceil(width * fullZoom())); } if (!length) { m_offset = 0; return; }; // ensure that m_offset is [0...length-1] if (m_offset >= length) m_offset = (length - 1); // ensure that the zoom is in a reasonable range max_zoom = fullZoom(); min_zoom = static_cast(MINIMUM_SAMPLES_PER_SCREEN) / static_cast(width); // ensure that pixel coordinates are within signed int range min_zoom = qMax(min_zoom, static_cast(length) / double(INT_MAX)); if (m_zoom < min_zoom) m_zoom = min_zoom; if (m_zoom > max_zoom) m_zoom = max_zoom; // try to correct the offset if there is not enough data to fill // the current window // example: width=100 [pixels], length=3 [samples], // offset=1 [sample], zoom=1/49.5 [samples/pixel] (full) // -> current last displayed sample = length-offset // = 3 - 1 = 2 // -> available space = pixels2samples(width-1) + 1 // = (99/49.5) + 1 = 3 // -> decrease offset by 3 - 2 = 1 Q_ASSERT(length >= m_offset); if ((pixels2samples(width - 1) + 1) > (length - m_offset)) { // there is space after the signal -> move offset right sample_index_t shift = pixels2samples(width - 1) + 1 - (length - m_offset); if (shift >= m_offset) { m_offset = 0; } else { m_offset -= shift; } } // emit change in the zoom factor if (!qFuzzyCompare(m_zoom, old_zoom)) emit sigZoomChanged(m_zoom); if ((m_offset != old_offset) || !qFuzzyCompare(m_zoom, old_zoom)) { emit sigVisibleRangeChanged(m_offset, visibleSamples(), length); updateViewRange(); } } //*************************************************************************** void Kwave::MainWidget::setZoom(double new_zoom) { fixZoomAndOffset(new_zoom, m_offset); } //*************************************************************************** void Kwave::MainWidget::setOffset(sample_index_t new_offset) { fixZoomAndOffset(m_zoom, new_offset); } //*************************************************************************** void Kwave::MainWidget::scrollTo(sample_index_t pos) { sample_index_t visible = visibleSamples(); if ( (pos < (m_offset + (visible / 10))) || (pos > (m_offset + (visible / 2))) ) { // new position is out of range or too close to the border sample_index_t offset = (visible / 2); pos = (pos > offset) ? (pos - offset) : 0; if (pos != m_offset) { // check: is the difference >= 1 pixel ? sample_index_t diff = (pos > m_offset) ? (pos - m_offset) : (m_offset - pos); if (samples2pixels(diff) >= 1) setOffset(pos); } } } //*************************************************************************** void Kwave::MainWidget::zoomSelection() { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return; sample_index_t ofs = signal_manager->selection().offset(); sample_index_t len = signal_manager->selection().length(); if (len) { int width = visibleWidth(); m_offset = ofs; setZoom((static_cast(len)) / static_cast(width - 1)); } } //*************************************************************************** void Kwave::MainWidget::zoomAll() { setZoom(fullZoom()); } //*************************************************************************** void Kwave::MainWidget::zoomNormal() { const sample_index_t shift1 = visibleWidth() / 2; const sample_index_t shift2 = visibleSamples() / 2; m_offset = (m_offset + shift2 >= m_offset) ? (m_offset + shift2) : -shift2; m_offset = (m_offset > shift1) ? (m_offset - shift1) : 0; setZoom(1.0); } //*************************************************************************** void Kwave::MainWidget::zoomIn(int pos) { if (pos >= 0) { // position given, calculate shift: // ofs_old + (pos * m_zoom) := ofs_new + (pos * (m_zoom / 3)) // ofs_new = ofs_old + (pos * m_zoom) - (pos * (m_zoom / 3)) fixZoomAndOffset( m_zoom / 3, m_offset + Kwave::toInt(pos * (m_zoom * (2.0 / 3.0))) ); } else { // no position given, show centered fixZoomAndOffset( m_zoom / 3, m_offset + (visibleSamples() / 3) ); } } //*************************************************************************** void Kwave::MainWidget::zoomOut(int pos) { sample_index_t shift; if (pos >= 0) { // position given, calculate shift // ofs_old + (pos * m_zoom) := ofs_new + (pos * (m_zoom * 3)) // ofs_new = ofs_old + (pos * m_zoom) - (pos * (m_zoom * 3)) shift = Kwave::toInt(m_zoom * 2.0) * pos; } else { // no position given, show centered shift = visibleSamples(); } fixZoomAndOffset( m_zoom * 3, (m_offset > shift) ? (m_offset - shift) : 0 ); } //*************************************************************************** void Kwave::MainWidget::addLabel(sample_index_t pos, const QString &description) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return; // remember the last "modified" state, in case that we abort bool was_modified = signal_manager->isModified(); // create an own undo transaction Kwave::UndoTransactionGuard undo(*signal_manager, i18n("Add Label")); // add a new label, with undo Kwave::Label label = signal_manager->addLabel(pos, description); if (label.isNull()) { signal_manager->abortUndoTransaction(); if (!was_modified) signal_manager->setModified(false); return; } // edit the properties of the new label if (!labelProperties(label)) { // aborted or failed -> delete (without undo) int index = signal_manager->labelIndex(label); signal_manager->deleteLabel(index, false); signal_manager->abortUndoTransaction(); if (!was_modified) signal_manager->setModified(false); } } //**************************************************************************** int Kwave::MainWidget::loadLabels(const QString &filename) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return -1; QUrl url; if (!filename.length()) { QString name(filename); QPointer dlg = new (std::nothrow)Kwave::FileDialog( _("kfiledialog:///kwave_label_dir"), Kwave::FileDialog::OpenFile, LABEL_LIST_FILTER, this, QUrl(), LABEL_LIST_EXT); if (!dlg) return -1; dlg->setWindowTitle(i18n("Load Labels")); if (dlg->exec() != QDialog::Accepted) { delete dlg; return 0; } else { url = dlg->selectedUrl(); delete dlg; } } else { url = QUrl::fromUserInput(filename); } // create an own undo transaction Kwave::UndoTransactionGuard undo(*signal_manager, i18n("Load Labels")); return m_context.loadBatch(url); } //**************************************************************************** int Kwave::MainWidget::saveLabels(const QString &filename) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return false; QUrl url; url = QUrl(m_context.signalName()); if (!filename.length()) { QString name(filename); QPointer dlg = new(std::nothrow)Kwave::FileDialog( _("kfiledialog:///kwave_label_dir"), Kwave::FileDialog::SaveFile, LABEL_LIST_FILTER, this, url, LABEL_LIST_EXT); if (!dlg) return 0; dlg->setWindowTitle(i18n("Save Labels")); if (dlg->exec() != QDialog::Accepted) { delete dlg; return -1; } url = dlg->selectedUrl(); if (url.isEmpty()) { delete dlg; return 0; } delete dlg; // add an extension if necessary name = url.path(); if (!QFileInfo(name).suffix().length()) { url.setPath(name + _(".label")); name = url.path(); } // check if the file exists and ask before overwriting it // if it is not the old filename if (QFileInfo(name).exists()) { if (Kwave::MessageBox::warningYesNo(this, i18n("The file '%1' already exists.\n" "Do you really want to overwrite it?", name)) != KMessageBox::Yes) { return -1; } } } else { url = QUrl::fromUserInput(filename); } // now we have a file name -> save all labels... Kwave::Logger::log(this, Kwave::Logger::Info, _("saving labels to '") + url.toDisplayString() + _("'")); QFile file(url.path()); file.open(QIODevice::WriteOnly); QTextStream out(&file); Kwave::LabelList labels(signal_manager->metaData()); foreach (const Kwave::Label &label, labels) { sample_index_t pos = label.pos(); const QString name = Kwave::Parser::escape(label.name()); out << _("label:add(") << pos; if (name.length()) out << _(", ") << name; out << _(")") << endl; } file.close(); return 0; } //*************************************************************************** bool Kwave::MainWidget::labelProperties(Kwave::Label &label) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return false; if (label.isNull()) return false; int index = signal_manager->labelIndex(label); Q_ASSERT(index >= 0); if (index < 0) return false; // try to modify the label. just in case that the user moves it // to a position where we already have one, catch this situation // and ask if he wants to abort, re-enter the label properties // dialog or just replace (remove) the label at the target position bool accepted; sample_index_t new_pos = label.pos(); QString new_name = label.name(); int old_index = -1; while (true) { // create and prepare the dialog Kwave::LabelPropertiesWidget *dlg = new Kwave::LabelPropertiesWidget(this); Q_ASSERT(dlg); if (!dlg) return false; dlg->setLabelIndex(index); dlg->setLabelPosition(new_pos, signal_manager->length(), signal_manager->rate()); dlg->setLabelName(new_name); // execute the dialog accepted = (dlg->exec() == QDialog::Accepted); if (!accepted) { // user pressed "cancel" delete dlg; break; } // if we get here the user pressed "OK" new_pos = dlg->labelPosition(); new_name = dlg->labelName(); dlg->saveSettings(); delete dlg; // check: if there already is a label at the new position // -> ask the user if he wants to overwrite that one if ((new_pos != label.pos()) && !signal_manager->findLabel(new_pos).isNull()) { int res = Kwave::MessageBox::warningYesNoCancel(this, i18n( "There already is a label at the position you have chosen.\n"\ "Do you want to replace it?")); if (res == KMessageBox::Yes) { // delete the label at the target position (with undo) Kwave::Label old = signal_manager->findLabel(new_pos); old_index = signal_manager->labelIndex(old); break; } if (res == KMessageBox::No) { // make another try -> re-enter the dialog continue; } // cancel -> abort the whole action accepted = false; break; } else { // ok, we can put it there break; } } if (accepted) { // shortcut: abort if nothing has changed if ((new_name == label.name()) && (new_pos == label.pos())) return true; Kwave::UndoTransactionGuard undo(*signal_manager, i18n("Modify Label")); // if there is a label at the target position, remove it first if (old_index >= 0) { signal_manager->deleteLabel(old_index, true); // this might have changed the current index! index = signal_manager->labelIndex(label); } // modify the label through the signal manager if (!signal_manager->modifyLabel(index, new_pos, new_name, true)) { // position is already occupied signal_manager->abortUndoTransaction(); return false; } // reflect the change in the passed label label.moveTo(new_pos); label.rename(new_name); // NOTE: moving might also change the index, so the complete // markers layer has to be refreshed } else signal_manager->abortUndoTransaction(); return accepted; } // ////**************************************************************************** // //void MainWidget::markSignal (const char *str) // //{ // // if (signalmanage) { // // Kwave::Label *newmark; // // // // Kwave::Parser parser (str); // // // // int level = (int) (parser.toDouble() / 100 * (1 << 23)); // // // // int len = signalmanage->getLength(); // // int *sam = signalmanage->getSignal(0)->getSample(); // ### @@@ ### // // LabelType *start = findMarkerType(parser.getNextParam()); // // LabelType *stop = findMarkerType (parser.getNextParam()); // // int time = (int) (parser.toDouble () * signalmanage->getRate() / 1000); // // // // printf ("%d %d\n", level, time); // // printf ("%s %s\n", start->name, stop->name); // // // // ProgressDialog *dialog = // // new ProgressDialog (len, "Searching for Signal portions..."); // // // // if (dialog && start && stop) { // // dialog->show(); // // // // newmark = new Kwave::Label(0, start); //generate initial Kwave::Label // // // // labels->inSort (newmark); // // // // for (int i = 0; i < len; i++) { // // if (qAbs(sam[i]) < level) { // // int j = i; // // while ((i < len) && (qAbs(sam[i]) < level)) i++; // // // // if (i - j > time) { // // //insert labels... // // newmark = new Kwave::Label(i, start); // // labels->inSort (newmark); // // // // if (start != stop) { // // newmark = new Kwave::Label(j, stop); // // labels->inSort (newmark); // // } // // } // // } // // dialog->setProgress (i); // // } // // // // newmark = new Kwave::Label(len - 1, stop); // // labels->inSort (newmark); // // // // refresh (); // // delete dialog; // // } // // } // //} // ////**************************************************************************** // //void MainWidget::markPeriods (const char *str) // //{ // // if (signalmanage) { // // Kwave::Parser parser (str); // // // // int high = signalmanage->getRate() / parser.toInt(); // // int low = signalmanage->getRate() / parser.toInt(); // // int octave = parser.toBool ("true"); // // double adjust = parser.toDouble (); // // // // for (int i = 0; i < AUTOKORRWIN; i++) // // autotable[i] = 1 - (((double)i * i * i) / (AUTOKORRWIN * AUTOKORRWIN * AUTOKORRWIN)); //generate static weighting function // // // // if (octave) for (int i = 0; i < AUTOKORRWIN; i++) weighttable[i] = 1; //initialise moving weight table // // // // Kwave::Label *newmark; // // int next; // // int len = signalmanage->getLength(); // // int *sam = signalmanage->getSignal(0)->getSample(); // ### @@@ ### // // LabelType *start = markertype; // // int cnt = findFirstMark (sam, len); // // // // ProgressDialog *dialog = new ProgressDialog (len - AUTOKORRWIN, "Correlating Signal to find Periods:"); // // if (dialog) { // // dialog->show(); // // // // newmark = new Kwave::Label(cnt, start); // // labels->inSort (newmark); // // // // while (cnt < len - 2*AUTOKORRWIN) { // // if (octave) // // next = findNextRepeatOctave (&sam[cnt], high, adjust); // // else // // next = findNextRepeat (&sam[cnt], high); // // // // if ((next < low) && (next > high)) { // // newmark = new Kwave::Label(cnt, start); // // // // labels->inSort (newmark); // // } // // if (next < AUTOKORRWIN) cnt += next; // // else // // if (cnt < len - AUTOKORRWIN) { // // int a = findFirstMark (&sam[cnt], len - cnt); // // if (a > 0) cnt += a; // // else cnt += high; // // } else cnt = len; // // // // dialog->setProgress (cnt); // // } // // // // delete dialog; // // // // refresh (); // // } // // } // //} // ////***************************************************************************** // //int findNextRepeat (int *sample, int high) // ////autocorellation of a windowed part of the sample // ////returns length of period, if found // //{ // // int i, j; // // double gmax = 0, max, c; // // int maxpos = AUTOKORRWIN; // // int down, up; //flags // // // // max = 0; // // for (j = 0; j < AUTOKORRWIN; j++) // // gmax += ((double)sample[j]) * sample [j]; // // // // //correlate signal with itself for finding maximum integral // // // // down = 0; // // up = 0; // // i = high; // // max = 0; // // while (i < AUTOKORRWIN) { // // c = 0; // // for (j = 0; j < AUTOKORRWIN; j++) c += ((double)sample[j]) * sample [i + j]; // // c = c * autotable[i]; //multiply window with weight for preference of high frequencies // // if (c > max) max = c, maxpos = i; // // i++; // // } // // return maxpos; // //} // ////***************************************************************************** // //int findNextRepeatOctave (int *sample, int high, double adjust = 1.005) // ////autocorellation of a windowed part of the sample // ////same as above only with an adaptive weighting to decrease fast period changes // //{ // // int i, j; // // double gmax = 0, max, c; // // int maxpos = AUTOKORRWIN; // // int down, up; //flags // // // // max = 0; // // for (j = 0; j < AUTOKORRWIN; j++) // // gmax += ((double)sample[j]) * sample [j]; // // // // //correlate signal with itself for finding maximum integral // // // // down = 0; // // up = 0; // // i = high; // // max = 0; // // while (i < AUTOKORRWIN) { // // c = 0; // // for (j = 0; j < AUTOKORRWIN; j++) c += ((double)sample[j]) * sample [i + j]; // // c = c * autotable[i] * weighttable[i]; // // //multiply window with weight for preference of high frequencies // // if (c > max) max = c, maxpos = i; // // i++; // // } // // // // for (int i = 0; i < AUTOKORRWIN; i++) weighttable[i] /= adjust; // // // // weighttable[maxpos] = 1; // // weighttable[maxpos + 1] = .9; // // weighttable[maxpos - 1] = .9; // // weighttable[maxpos + 2] = .8; // // weighttable[maxpos - 2] = .8; // // // // float buf[7]; // // // // for (int i = 0; i < 7; buf[i++] = .1) // // // // //low pass filter // // for (int i = high; i < AUTOKORRWIN - 3; i++) { // // buf[i % 7] = weighttable[i + 3]; // // weighttable[i] = (buf[0] + buf[1] + buf[2] + buf[3] + buf[4] + buf[5] + buf[6]) / 7; // // } // // // // return maxpos; // //} // //***************************************************************************** // //int findFirstMark (int *sample, int len) // ////finds first sample that is non-zero, or one that preceeds a zero crossing // //{ // // int i = 1; // // int last = sample[0]; // // int act = last; // // if ((last < 100) && (last > -100)) i = 0; // // else // // while (i < len) { // // act = sample[i]; // // if ((act < 0) && (last >= 0)) break; // // if ((act > 0) && (last <= 0)) break; // // last = act; // // i++; // // } // // return i; // //} //*************************************************************************** //*************************************************************************** diff --git a/libkwave/CodecManager.h b/libkwave/CodecManager.h index 4f5caf81..a6088465 100644 --- a/libkwave/CodecManager.h +++ b/libkwave/CodecManager.h @@ -1,135 +1,132 @@ /*************************************************************************** CodecManager.h - manager for Kwave's coders and decoders ------------------- begin : Mar 10 2002 copyright : (C) 2002 by Thomas Eschenbacher email : Thomas Eschenbacher ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef CODEC_MANAGER_H #define CODEC_MANAGER_H #include "config.h" #include #include #include #include #include -class QMimeType; -class QMimeData; - namespace Kwave { class Decoder; class Encoder; class Q_DECL_EXPORT CodecManager: public QObject { Q_OBJECT public: /** Constructor */ CodecManager(); /** Destructor */ virtual ~CodecManager(); /** * Registers a new encoder. * @param encoder a KwaveEncoder that will be used as template for * creating new encoder instances (used as factory) */ static void registerEncoder(Kwave::Encoder &encoder); /** * Un-registers an encoder previously registered with registerEncoder. * @param encoder a KwaveEncoder */ static void unregisterEncoder(Kwave::Encoder *encoder); /** * Registers a new decoder. * @param decoder a KwaveDecoder that will be used as template for * creating new decoder instances (used as factory) */ static void registerDecoder(Kwave::Decoder &decoder); /** * Un-registers an decoder previously registered with registerDecoder. * @param decoder a KwaveDecoder */ static void unregisterDecoder(Kwave::Decoder *decoder); /** * Returns true if a decoder for the given mime type is known. * @param mimetype_name name of the mime type * @return true if format is supported, false if not */ static bool canDecode(const QString &mimetype_name); /** * Tries to find a decoder that matches to a given mime type. * @param mimetype_name name of the mime type * @return a new decoder for the mime type or null if none found. */ static Kwave::Decoder *decoder(const QString &mimetype_name); /** * Tries to find an encoder that matches to a given mime type. * @param mimetype_name name of the mime type of the destination * @return a new encoder for the mime type or null if none found. */ static Kwave::Encoder *encoder(const QString &mimetype_name); /** * Returns a string with a list of all file types that can be used * for saving. The string is localized and can be used as a filter * for a KFileDialog. The entries are unique (by file extension) but * not sorted alphabetically. */ static QString encodingFilter(); /** * Returns a string with a list of all file types that can be used * for loading. The string is localized and can be used as a filter * for a KFileDialog. The entries are unique (by file extension) but * not sorted alphabetically. */ static QString decodingFilter(); /** * Tries to find the name of a mime type of a decoder by a URL. * If not found, it returns the default mime type, never an empty string. * @param url a QUrl, only the filename's extension will be inspected * @return name of the mime type or the default mime type */ static QString mimeTypeOf(const QUrl &url); /** Returns a list of supported mime types for encoding */ static QStringList encodingMimeTypes(); private: /** list of all encoders */ static QList m_encoders; /** list of decoders */ static QList m_decoders; }; } #endif /* CODEC_MANAGER_H */ //*************************************************************************** //*************************************************************************** diff --git a/plugins/codec_ascii/AsciiDecoder.cpp b/plugins/codec_ascii/AsciiDecoder.cpp index 99c2a48e..711c22c9 100644 --- a/plugins/codec_ascii/AsciiDecoder.cpp +++ b/plugins/codec_ascii/AsciiDecoder.cpp @@ -1,293 +1,292 @@ /************************************************************************* AsciiDecoder.cpp - decoder for ASCII data ------------------- begin : Sun Dec 03 2006 copyright : (C) 2006 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include -#include #include #include #include "libkwave/Label.h" #include "libkwave/LabelList.h" #include "libkwave/MessageBox.h" #include "libkwave/MultiWriter.h" #include "libkwave/Parser.h" #include "libkwave/Sample.h" #include "libkwave/String.h" #include "libkwave/Writer.h" #include "AsciiCodecPlugin.h" #include "AsciiDecoder.h" #define MAX_LINE_LEN 16384 /**< maximum line length in characters */ //*************************************************************************** Kwave::AsciiDecoder::AsciiDecoder() :Kwave::Decoder(), m_source(), m_dest(0), m_queue_input(), m_line_nr(0) { LOAD_MIME_TYPES REGISTER_COMPRESSION_TYPES m_source.setCodec("UTF-8"); } //*************************************************************************** Kwave::AsciiDecoder::~AsciiDecoder() { if (m_source.device()) close(); } //*************************************************************************** Kwave::Decoder *Kwave::AsciiDecoder::instance() { return new Kwave::AsciiDecoder(); } //*************************************************************************** bool Kwave::AsciiDecoder::open(QWidget *widget, QIODevice &src) { Q_UNUSED(widget); metaData().clear(); Q_ASSERT(!m_source.device()); if (m_source.device()) qWarning("AsciiDecoder::open(), already open !"); // try to open the source if (!src.open(QIODevice::ReadOnly)) { qWarning("failed to open source !"); return false; } // take over the source m_source.setDevice(&src); Kwave::FileInfo info(metaData()); Kwave::LabelList labels; /********** Decoder setup ************/ qDebug("--- AsciiDecoder::open() ---"); // read in all metadata until start of samples, EOF or user cancel qDebug("AsciiDecoder::open(...)"); m_line_nr = 0; while (!m_source.atEnd()) { QString line = m_source.readLine(MAX_LINE_LEN).simplified(); m_line_nr++; if (!line.length()) continue; // skip empty line QRegExp regex(_( "(^##\\s*)" // 1 start of meta data line "([\\'\\\"])?" // 2 property, quote start (' or ") "\\s*(\\w+[\\s\\w]*\\w)\\s*" // 3 property "(\\[\\d*\\])?" // 4 index (optional) "(\\2)" // 5 property, quote end "(\\s*=\\s*)" // 6 assignment '=' "(.*)" // 7 rest, up to end of line )); if (regex.exactMatch(line)) { // meta data entry: "## 'Name' = value" QString name = Kwave::Parser::unescape(regex.cap(3) + regex.cap(4)); QString v = regex.cap(7); QString value; if (v.length()) { // remove quotes from the value bool is_escaped = false; char quote = v[0].toLatin1(); if ((quote != '\'') && (quote != '"')) quote = -1; for (QString::ConstIterator it = v.begin(); it != v.end(); ++it) { const char c = QChar(*it).toLatin1(); if ((c == '\\') && !is_escaped) { is_escaped = true; // next char is escaped continue; } if (is_escaped) { value += *it; // escaped char is_escaped = false; continue; } if (c == quote) { if (!value.length()) continue; // starting quote else break; // ending quote } if ((quote == -1) && (c == '#')) break; // comment in unquoted text // otherwise: normal character, part of text value += *it; } // if the text was unquoted, remove leading/trailing spaces if (quote == -1) value = value.trimmed(); } // handle some well known aliases if (name == _("rate")) name = info.name(INF_SAMPLE_RATE); if (name == _("bits")) name = info.name(INF_BITS_PER_SAMPLE); // handle labels QRegExp regex_label(_("label\\[(\\d*)\\]")); if (regex_label.exactMatch(name)) { bool ok = false; sample_index_t pos = regex_label.cap(1).toULongLong(&ok); if (!ok) { qWarning("line %llu: malformed label position: '%s'", m_line_nr, DBG(name)); continue; // skip it } Kwave::Label label(pos, value); labels.append(label); continue; } bool found = false; foreach (const Kwave::FileProperty &p, info.allKnownProperties()) { if (info.name(p).toLower() == name.toLower()) { found = true; info.set(p, QVariant(value)); } } if (!found) { qWarning("line %llu: unknown meta data entry: '%s' = '%s'", m_line_nr, DBG(name), DBG(value)); } } else if (line.startsWith(QLatin1Char('#'))) { continue; // skip comment lines } else { // reached end of metadata: // -> push back the line into the queue m_queue_input.enqueue(line); break; } } // if the number of channels is not known, but "tracks" is given and // "track" is not present: old syntax has been used if ((info.tracks() < 1) && info.contains(INF_TRACKS) && !info.contains(INF_TRACK)) { info.set(INF_CHANNELS, info.get(INF_TRACKS)); info.set(INF_TRACKS, QVariant()); } metaData().replace(Kwave::MetaDataList(info)); metaData().add(labels.toMetaDataList()); return (info.tracks() >= 1); } //*************************************************************************** bool Kwave::AsciiDecoder::readNextLine() { if (!m_queue_input.isEmpty()) return true; // there is still something in the queue while (!m_source.atEnd()) { QString line = m_source.readLine(MAX_LINE_LEN).simplified(); m_line_nr++; if (!line.length()) { continue; // skip empty line } else if (line.startsWith(QLatin1Char('#'))) { continue; // skip comment lines } else { // -> push back the line into the queue m_queue_input.enqueue(line); return true; } } return false; } //*************************************************************************** bool Kwave::AsciiDecoder::decode(QWidget *widget, Kwave::MultiWriter &dst) { Q_UNUSED(widget); Q_ASSERT(m_source.device()); if (!m_source.device()) return false; m_dest = &dst; // for the moment: use a comma as separator <= TODO const char separators[] = {',', '\0' }; Kwave::FileInfo info(metaData()); unsigned int channels = info.tracks(); QVector frame(channels); // read in all remaining data until EOF or user cancel qDebug("AsciiDecoder::decode(...)"); while (readNextLine() && !dst.isCanceled()) { QByteArray d = m_queue_input.dequeue().toLatin1(); char *line = d.data(); char *saveptr = 0; frame.fill(0); for (unsigned int channel = 0; channel < channels; channel++) { sample_t s = 0; char *token = strtok_r(line, separators, &saveptr); line = 0; if (token) { // skip whitespace at the start while (*token && isspace(*token)) ++token; if (*token) { char *p = token + 1; while (isdigit(*p) || (*p == '+') || (*p == '-')) ++p; *p = 0; if (*token) s = atoi(token); Kwave::Writer *w = dst[channel]; if (w) (*w) << s; } } } } m_dest = 0; info.setLength(dst.last() ? (dst.last() + 1) : 0); metaData().replace(Kwave::MetaDataList(info)); // return with a valid Signal, even if the user pressed cancel ! return true; } //*************************************************************************** void Kwave::AsciiDecoder::close() { m_source.reset(); m_source.setDevice(0); } //*************************************************************************** //*************************************************************************** diff --git a/plugins/codec_ascii/AsciiEncoder.cpp b/plugins/codec_ascii/AsciiEncoder.cpp index 439ff598..e75909b4 100644 --- a/plugins/codec_ascii/AsciiEncoder.cpp +++ b/plugins/codec_ascii/AsciiEncoder.cpp @@ -1,179 +1,178 @@ /************************************************************************* AsciiEncoder.cpp - encoder for ASCII data ------------------- begin : Sun Nov 26 2006 copyright : (C) 2006 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include -#include #include #include #include #include "libkwave/FileInfo.h" #include "libkwave/Label.h" #include "libkwave/LabelList.h" #include "libkwave/MessageBox.h" #include "libkwave/MetaDataList.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/Parser.h" #include "libkwave/Sample.h" #include "libkwave/SampleReader.h" #include "AsciiCodecPlugin.h" #include "AsciiEncoder.h" /***************************************************************************/ Kwave::AsciiEncoder::AsciiEncoder() :Kwave::Encoder(), m_dst() { m_dst.setCodec(QTextCodec::codecForName("UTF-8")); LOAD_MIME_TYPES REGISTER_COMPRESSION_TYPES } /***************************************************************************/ Kwave::AsciiEncoder::~AsciiEncoder() { } /***************************************************************************/ Kwave::Encoder *Kwave::AsciiEncoder::instance() { return new Kwave::AsciiEncoder(); } /***************************************************************************/ QList Kwave::AsciiEncoder::supportedProperties() { // default is to support all known properties Kwave::FileInfo info; return info.allKnownProperties(); } /***************************************************************************/ bool Kwave::AsciiEncoder::encode(QWidget *widget, Kwave::MultiTrackReader &src, QIODevice &dst, const Kwave::MetaDataList &meta_data) { bool result = true; qDebug("AsciiEncoder::encode()"); // get info: tracks, sample rate const Kwave::FileInfo info(meta_data); unsigned int tracks = info.tracks(); unsigned int bits = info.bits(); sample_index_t length = info.length(); do { // open the output device if (!dst.open(QIODevice::ReadWrite | QIODevice::Truncate)) { Kwave::MessageBox::error(widget, i18n("Unable to open the file for saving.")); result = false; break; } // output device successfully opened m_dst.setDevice(&dst); // write out the default properties: // sample rate, bits, tracks, length m_dst << META_PREFIX << "'rate'=" << info.rate() << endl; m_dst << META_PREFIX << "'tracks'=" << tracks << endl; m_dst << META_PREFIX << "'bits'=" << bits << endl; m_dst << META_PREFIX << "'length'=" << length << endl; // write out all other, non-standard properties that we have QMap properties = info.properties(); QMap::Iterator it; QList supported = supportedProperties(); for (it=properties.begin(); it != properties.end(); ++it) { Kwave::FileProperty p = it.key(); QVariant v = it.value(); if (!supported.contains(p)) continue; if (!info.canLoadSave(p)) continue; // write the property m_dst << META_PREFIX << "'" << info.name(p) << "'='" << Kwave::Parser::escape(v.toString()).toUtf8() << "'" << endl; } // write out all labels Kwave::LabelList labels(meta_data); foreach (const Kwave::Label &label, labels) { m_dst << META_PREFIX << "'label[" << QString::number(label.pos()) << "]'='" << Kwave::Parser::escape(label.name()).toUtf8() << "'" << endl; } sample_index_t rest = length; sample_index_t pos = 0; while (rest-- && !src.isCanceled()) { // write out one track per line for (unsigned int track=0; track < tracks; track++) { Kwave::SampleReader *reader = src[track]; Q_ASSERT(reader); if (!reader) break; // read one single sample sample_t sample = 0; if (!reader->eof()) (*reader) >> sample; // print out the sample value m_dst.setFieldWidth(9); m_dst << sample; // comma as separator between the samples if (track != tracks-1) m_dst << ", "; } // as comment: current position [samples] m_dst << " # "; m_dst.setFieldWidth(12); m_dst << pos; pos++; // end of line m_dst << endl; } } while (false); // end of file m_dst << "# EOF " << endl << endl; m_dst.setDevice(0); dst.close(); return result; } /***************************************************************************/ /***************************************************************************/ diff --git a/plugins/codec_audiofile/AudiofileDecoder.cpp b/plugins/codec_audiofile/AudiofileDecoder.cpp index 6ed69453..74e8125d 100644 --- a/plugins/codec_audiofile/AudiofileDecoder.cpp +++ b/plugins/codec_audiofile/AudiofileDecoder.cpp @@ -1,303 +1,302 @@ /************************************************************************* AudiofileDecoder.cpp - import through libaudiofile ------------------- begin : Tue May 28 2002 copyright : (C) 2002 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include -#include #include #include #include "libkwave/Compression.h" #include "libkwave/FileInfo.h" #include "libkwave/MessageBox.h" #include "libkwave/MultiWriter.h" #include "libkwave/Sample.h" #include "libkwave/SampleFormat.h" #include "libkwave/Utils.h" #include "libkwave/VirtualAudioFile.h" #include "libkwave/Writer.h" #include "AudiofileDecoder.h" //*************************************************************************** Kwave::AudiofileDecoder::AudiofileDecoder() :Kwave::Decoder(), m_source(0), m_src_adapter(0) { /* defined in RFC 1521 */ addMimeType("audio/basic", i18n("NeXT, Sun Audio"), "*.au; *.snd"); /* some others, mime types might be wrong (I found no RFC or similar) */ addMimeType("audio/x-8svx", i18n("Amiga IFF/8SVX Sound File Format"), "*.iff; *.8svx"); addMimeType("audio/x-aifc", i18n("Compressed Audio Interchange Format"), "*.aifc"); addMimeType("audio/x-aiff", /* included in KDE */ i18n("Audio Interchange Format"), "*.aif; *.aiff"); addMimeType("audio/x-avr", i18n("Audio Visual Research File Format"), "*.avr"); addMimeType("audio/x-caf", i18n("Core Audio File Format"), "*.caf"); addMimeType("audio/x-ircam", i18n("Berkeley, IRCAM, Carl Sound Format"), "*.sf"); addMimeType("audio/x-nist", i18n("NIST SPHERE Audio File Format"), "*.nist"); addMimeType("audio/x-smp", i18n("Sample Vision Format"), "*.smp"); addMimeType("audio/x-voc", i18n("Creative Voice"), "*.voc"); } //*************************************************************************** Kwave::AudiofileDecoder::~AudiofileDecoder() { if (m_source) close(); if (m_src_adapter) delete m_src_adapter; } //*************************************************************************** Kwave::Decoder *Kwave::AudiofileDecoder::instance() { return new Kwave::AudiofileDecoder(); } //*************************************************************************** bool Kwave::AudiofileDecoder::open(QWidget *widget, QIODevice &src) { metaData().clear(); Q_ASSERT(!m_source); if (m_source) qWarning("AudiofileDecoder::open(), already open !"); // try to open the source if (!src.open(QIODevice::ReadOnly)) { qWarning("AudiofileDecoder::open(), failed to open source !"); return false; } // source successfully opened m_source = &src; m_src_adapter = new Kwave::VirtualAudioFile(*m_source); Q_ASSERT(m_src_adapter); if (!m_src_adapter) return false; m_src_adapter->open(m_src_adapter, 0); AFfilehandle fh = m_src_adapter->handle(); if (!fh || (m_src_adapter->lastError() >= 0)) { QString reason; switch (m_src_adapter->lastError()) { case AF_BAD_NOT_IMPLEMENTED: reason = i18n("Format or function is not implemented"); break; case AF_BAD_MALLOC: reason = i18n("Out of memory"); break; case AF_BAD_HEADER: reason = i18n("File header is damaged"); break; case AF_BAD_CODEC_TYPE: reason = i18n("Invalid codec type"); break; case AF_BAD_OPEN: reason = i18n("Opening the file failed"); break; case AF_BAD_READ: reason = i18n("Read access failed"); break; case AF_BAD_SAMPFMT: reason = i18n("Invalid sample format"); break; default: reason = reason.number(m_src_adapter->lastError()); } QString text= i18n("An error occurred while opening the "\ "file:\n'%1'", reason); Kwave::MessageBox::error(widget, text); return false; } AFframecount length = afGetFrameCount(fh, AF_DEFAULT_TRACK); unsigned int tracks = qMax(afGetVirtualChannels(fh, AF_DEFAULT_TRACK), 0); unsigned int bits = 0; double rate = 0.0; int af_sample_format; afGetVirtualSampleFormat(fh, AF_DEFAULT_TRACK, &af_sample_format, reinterpret_cast(&bits)); Kwave::SampleFormat::Format fmt; switch (af_sample_format) { case AF_SAMPFMT_TWOSCOMP: fmt = Kwave::SampleFormat::Signed; break; case AF_SAMPFMT_UNSIGNED: fmt = Kwave::SampleFormat::Unsigned; break; case AF_SAMPFMT_FLOAT: fmt = Kwave::SampleFormat::Float; break; case AF_SAMPFMT_DOUBLE: fmt = Kwave::SampleFormat::Double; break; default: fmt = Kwave::SampleFormat::Unknown; break; } // get sample rate, with fallback to 8kHz rate = afGetRate(fh, AF_DEFAULT_TRACK); if (rate < 1.0) { qWarning("\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"\ "WARNING: file has no sample rate!\n"\ " => using 8000 samples/sec as fallback\n"\ "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); rate = 8000.0; } Kwave::SampleFormat::Map sf; QString sample_format_name = sf.description(Kwave::SampleFormat(fmt), true); if (static_cast(bits) < 0) bits = 0; int af_compression = afGetCompression(fh, AF_DEFAULT_TRACK); const Kwave::Compression compression( Kwave::Compression::fromAudiofile(af_compression) ); Kwave::FileInfo info(metaData()); info.setRate(rate); info.setBits(bits); info.setTracks(tracks); info.setLength(length); info.set(INF_SAMPLE_FORMAT, Kwave::SampleFormat(fmt).toInt()); info.set(Kwave::INF_COMPRESSION, compression.toInt()); metaData().replace(Kwave::MetaDataList(info)); qDebug("-------------------------"); qDebug("info:"); qDebug("compression = %d", af_compression); qDebug("channels = %d", info.tracks()); qDebug("rate = %0.0f", info.rate()); qDebug("bits/sample = %d", info.bits()); qDebug("length = %lu samples", static_cast(info.length())); qDebug("format = %d (%s)", af_sample_format, DBG(sample_format_name)); qDebug("-------------------------"); // set up libaudiofile to produce Kwave's internal sample format #if Q_BYTE_ORDER == Q_BIG_ENDIAN afSetVirtualByteOrder(fh, AF_DEFAULT_TRACK, AF_BYTEORDER_BIGENDIAN); #else afSetVirtualByteOrder(fh, AF_DEFAULT_TRACK, AF_BYTEORDER_LITTLEENDIAN); #endif afSetVirtualSampleFormat(fh, AF_DEFAULT_TRACK, AF_SAMPFMT_TWOSCOMP, SAMPLE_STORAGE_BITS); return true; } //*************************************************************************** bool Kwave::AudiofileDecoder::decode(QWidget */*widget*/, Kwave::MultiWriter &dst) { Q_ASSERT(m_src_adapter); Q_ASSERT(m_source); if (!m_source) return false; if (!m_src_adapter) return false; AFfilehandle fh = m_src_adapter->handle(); Q_ASSERT(fh); if (!fh) return false; unsigned int frame_size = Kwave::toUint( afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, 1)); // allocate a buffer for input data const unsigned int buffer_frames = (8 * 1024); sample_storage_t *buffer = static_cast(malloc(buffer_frames * frame_size)); Q_ASSERT(buffer); if (!buffer) return false; // read in from the audiofile source const unsigned int tracks = Kwave::FileInfo(metaData()).tracks(); sample_index_t rest = Kwave::FileInfo(metaData()).length(); while (rest) { unsigned int frames = buffer_frames; if (frames > rest) frames = Kwave::toUint(rest); int buffer_used = afReadFrames(fh, AF_DEFAULT_TRACK, reinterpret_cast(buffer), frames); // break if eof reached if (buffer_used <= 0) break; rest -= buffer_used; // split into the tracks sample_storage_t *p = buffer; unsigned int count = buffer_used; while (count) { for (unsigned int track = 0; track < tracks; track++) { sample_storage_t s = *p++; // adjust precision if (SAMPLE_STORAGE_BITS != SAMPLE_BITS) { s /= (1 << (SAMPLE_STORAGE_BITS - SAMPLE_BITS)); } // the following cast is only necessary if // sample_t is not equal to a quint32 *(dst[track]) << static_cast(s); } --count; } // abort if the user pressed cancel if (dst.isCanceled()) break; } // return with a valid Signal, even if the user pressed cancel ! if (buffer) free(buffer); return true; } //*************************************************************************** void Kwave::AudiofileDecoder::close() { if (m_src_adapter) delete m_src_adapter; m_src_adapter = 0; m_source = 0; } //*************************************************************************** //*************************************************************************** diff --git a/plugins/codec_flac/FlacDecoder.cpp b/plugins/codec_flac/FlacDecoder.cpp index 93b55b75..b62be141 100644 --- a/plugins/codec_flac/FlacDecoder.cpp +++ b/plugins/codec_flac/FlacDecoder.cpp @@ -1,331 +1,330 @@ /************************************************************************* FlacDecoder.cpp - decoder for FLAC data ------------------- begin : Tue Feb 28 2004 copyright : (C) 2004 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include -#include #include "libkwave/Compression.h" #include "libkwave/MessageBox.h" #include "libkwave/MultiWriter.h" #include "libkwave/Sample.h" #include "libkwave/String.h" #include "libkwave/Writer.h" #include "FlacCodecPlugin.h" #include "FlacDecoder.h" //*************************************************************************** Kwave::FlacDecoder::FlacDecoder() :Kwave::Decoder(), FLAC::Decoder::Stream(), m_source(0), m_dest(0), m_vorbis_comment_map() { REGISTER_MIME_TYPES REGISTER_COMPRESSION_TYPES } //*************************************************************************** Kwave::FlacDecoder::~FlacDecoder() { if (m_source) close(); } //*************************************************************************** Kwave::Decoder *Kwave::FlacDecoder::instance() { return new Kwave::FlacDecoder(); } //*************************************************************************** ::FLAC__StreamDecoderReadStatus Kwave::FlacDecoder::read_callback( FLAC__byte buffer[], size_t *bytes) { Q_ASSERT(bytes); Q_ASSERT(m_source); if (!bytes || !m_source) return FLAC__STREAM_DECODER_READ_STATUS_ABORT; // check for EOF if (m_source->atEnd()) { *bytes = 0; return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; } // read into application buffer *bytes = static_cast(m_source->read( reinterpret_cast(&(buffer[0])), static_cast(*bytes) )); if (!*bytes) return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; } //*************************************************************************** ::FLAC__StreamDecoderWriteStatus Kwave::FlacDecoder::write_callback( const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]) { Q_ASSERT(buffer); Q_ASSERT(frame); Q_ASSERT(m_dest); if (!buffer || !frame || !m_dest) return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; const unsigned int samples = frame->header.blocksize; const unsigned int tracks = Kwave::FileInfo(metaData()).tracks(); Q_ASSERT(samples); Q_ASSERT(tracks); if (!samples || !tracks) return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; Kwave::SampleArray dst(samples); // expand the samples up to the correct number of bits int shift = SAMPLE_BITS - Kwave::FileInfo(metaData()).bits(); if (shift < 0) shift = 0; unsigned int mul = (1 << shift); // decode the samples into a temporary buffer and // flush it to the Writer(s), track by track for (unsigned int track=0; track < tracks; track++) { Kwave::Writer *writer = (*m_dest)[track]; Q_ASSERT(writer); if (!writer) continue; const FLAC__int32 *src = buffer[track]; sample_t *d = dst.data(); for (unsigned int sample = 0; sample < samples; sample++) { // the following cast is only necessary if // sample_t is not equal to a quint32 sample_t s = static_cast(*src++); // correct precision if (shift) s *= mul; // write to destination buffer *d++ = s; } // flush the temporary buffer (*writer) << dst; } // at this point we check for a user-cancel return (m_dest->isCanceled()) ? FLAC__STREAM_DECODER_WRITE_STATUS_ABORT : FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } //*************************************************************************** void Kwave::FlacDecoder::parseStreamInfo( const FLAC::Metadata::StreamInfo &stream_info) { qDebug("FLAC stream info"); qDebug("\tmin_blocksize = %d", stream_info.get_min_blocksize()); qDebug("\tmax_blocksize = %d", stream_info.get_max_blocksize()); qDebug("\tmin_framesize = %d", stream_info.get_min_framesize()); qDebug("\tmax_framesize = %d", stream_info.get_max_framesize()); Kwave::FileInfo info(metaData()); info.setRate(stream_info.get_sample_rate()); info.setTracks(stream_info.get_channels()); info.setBits(stream_info.get_bits_per_sample()); info.setLength(stream_info.get_total_samples()); metaData().replace(Kwave::MetaDataList(info)); qDebug("Bitstream is %u channel, %uHz", stream_info.get_channels(), stream_info.get_sample_rate()); } //*************************************************************************** void Kwave::FlacDecoder::parseVorbisComments( const FLAC::Metadata::VorbisComment &vorbis_comments) { Kwave::FileInfo info(metaData()); // first of all: the vendor string, specifying the software QString vendor = QString::fromUtf8(reinterpret_cast( vorbis_comments.get_vendor_string())); if (vendor.length()) { info.set(Kwave::INF_SOFTWARE, vendor); qDebug("Encoded by: '%s'\n\n", DBG(vendor)); } // parse all vorbis comments into Kwave file properties for (unsigned int i = 0; i < vorbis_comments.get_num_comments(); i++) { FLAC::Metadata::VorbisComment::Entry comment = vorbis_comments.get_comment(i); Q_ASSERT(comment.is_valid()); if (!comment.is_valid()) continue; QString name = QString::fromUtf8( comment.get_field_name(), comment.get_field_name_length()); QString value = QString::fromUtf8( comment.get_field_value(), comment.get_field_value_length()); if (!m_vorbis_comment_map.contains(name)) continue; // we have a known vorbis tag Kwave::FileProperty prop = m_vorbis_comment_map[name]; info.set(prop, value); } // convert the date property to a QDate if (info.contains(Kwave::INF_CREATION_DATE)) { QString str_date = QVariant(info.get(Kwave::INF_CREATION_DATE)).toString(); QDate date; date = QDate::fromString(str_date, Qt::ISODate); if (!date.isValid()) { int year = str_date.toInt(); date.setDate(year, 1, 1); } if (date.isValid()) info.set(Kwave::INF_CREATION_DATE, date); } metaData().replace(Kwave::MetaDataList(info)); } //*************************************************************************** void Kwave::FlacDecoder::metadata_callback( const ::FLAC__StreamMetadata *metadata) { Q_ASSERT(metadata); if (!metadata) return; switch (metadata->type) { case FLAC__METADATA_TYPE_STREAMINFO: { FLAC::Metadata::StreamInfo stream_info( const_cast< ::FLAC__StreamMetadata * >(metadata), true); parseStreamInfo(stream_info); break; } case FLAC__METADATA_TYPE_PADDING: // -> ignored break; case FLAC__METADATA_TYPE_APPLICATION: qDebug("FLAC metadata: application data"); break; case FLAC__METADATA_TYPE_SEEKTABLE: qDebug("FLAC metadata: seektable - not supported yet"); break; case FLAC__METADATA_TYPE_VORBIS_COMMENT: { FLAC::Metadata::VorbisComment vorbis_comments( const_cast< ::FLAC__StreamMetadata * >(metadata), true); parseVorbisComments(vorbis_comments); break; } case FLAC__METADATA_TYPE_CUESHEET: qDebug("FLAC metadata: cuesheet - not supported yet"); break; case FLAC__METADATA_TYPE_UNDEFINED: default: qDebug("FLAC metadata: unknown/undefined type"); } } //*************************************************************************** void Kwave::FlacDecoder::error_callback(::FLAC__StreamDecoderErrorStatus status) { qDebug("FlacDecoder::error_callback: status=%d", status); } //*************************************************************************** bool Kwave::FlacDecoder::open(QWidget *widget, QIODevice &src) { metaData().clear(); Q_ASSERT(!m_source); if (m_source) qWarning("FlacDecoder::open(), already open !"); // try to open the source if (!src.open(QIODevice::ReadOnly)) { qWarning("failed to open source !"); return false; } // take over the source m_source = &src; /********** Decoder setup ************/ qDebug("--- FlacDecoder::open() ---"); set_metadata_respond_all(); // initialize the stream FLAC__StreamDecoderInitStatus init_state = init(); if (init_state > FLAC__STREAM_DECODER_INIT_STATUS_OK) { Kwave::MessageBox::error(widget, i18n( "Opening the FLAC bitstream failed.")); return false; } // read in all metadata process_until_end_of_metadata(); FLAC::Decoder::Stream::State state = get_state(); if (state >= FLAC__STREAM_DECODER_END_OF_STREAM) { Kwave::MessageBox::error(widget, i18n( "Error while parsing the FLAC metadata. (%s)"), _(state.as_cstring())); return false; } // set some more standard properties Kwave::FileInfo info(metaData()); info.set(Kwave::INF_MIMETYPE, _(DEFAULT_MIME_TYPE)); info.set(Kwave::INF_COMPRESSION, Kwave::Compression::FLAC); metaData().replace(Kwave::MetaDataList(info)); return true; } //*************************************************************************** bool Kwave::FlacDecoder::decode(QWidget * /* widget */, Kwave::MultiWriter &dst) { Q_ASSERT(m_source); if (!m_source) return false; m_dest = &dst; // read in all remaining data qDebug("FlacDecoder::decode(...)"); process_until_end_of_stream(); m_dest = 0; Kwave::FileInfo info(metaData()); info.setLength(dst.last() ? (dst.last() + 1) : 0); metaData().replace(Kwave::MetaDataList(info)); // return with a valid Signal, even if the user pressed cancel ! return true; } //*************************************************************************** void Kwave::FlacDecoder::close() { finish(); m_source = 0; } //*************************************************************************** //*************************************************************************** diff --git a/plugins/codec_flac/FlacEncoder.cpp b/plugins/codec_flac/FlacEncoder.cpp index 28ff4aaa..68f9c36e 100644 --- a/plugins/codec_flac/FlacEncoder.cpp +++ b/plugins/codec_flac/FlacEncoder.cpp @@ -1,338 +1,337 @@ /************************************************************************* FlacEncoder.cpp - encoder for FLAC data ------------------- begin : Tue Feb 28 2004 copyright : (C) 2004 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include -#include #include #include #include #include #include "libkwave/FileInfo.h" #include "libkwave/MessageBox.h" #include "libkwave/MetaDataList.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/Sample.h" #include "libkwave/SampleReader.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "FlacCodecPlugin.h" #include "FlacEncoder.h" /***************************************************************************/ Kwave::FlacEncoder::FlacEncoder() :Kwave::Encoder(), FLAC::Encoder::Stream(), m_vorbis_comment_map(), m_dst(0) { REGISTER_MIME_TYPES REGISTER_COMPRESSION_TYPES } /***************************************************************************/ Kwave::FlacEncoder::~FlacEncoder() { } /***************************************************************************/ Kwave::Encoder *Kwave::FlacEncoder::instance() { return new Kwave::FlacEncoder(); } /***************************************************************************/ QList Kwave::FlacEncoder::supportedProperties() { return m_vorbis_comment_map.values(); } /***************************************************************************/ ::FLAC__StreamEncoderWriteStatus Kwave::FlacEncoder::write_callback( const FLAC__byte buffer[], size_t bytes, unsigned /* samples */, unsigned /* current_frame */) { Q_ASSERT(m_dst); if (!m_dst) return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; qint64 written = m_dst->write( reinterpret_cast(&(buffer[0])), static_cast(bytes) ); return (written == static_cast(bytes)) ? FLAC__STREAM_ENCODER_WRITE_STATUS_OK : FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; } /***************************************************************************/ void Kwave::FlacEncoder::metadata_callback(const ::FLAC__StreamMetadata *) { /* we are not interested in the FLAC metadata */ } /***************************************************************************/ Kwave::FlacEncoder::VorbisCommentContainer::VorbisCommentContainer() :m_vc(0) { m_vc = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); Q_ASSERT(m_vc); } /***************************************************************************/ Kwave::FlacEncoder::VorbisCommentContainer::~VorbisCommentContainer() { if (m_vc) { // clean up all vorbis comments } } /***************************************************************************/ void Kwave::FlacEncoder::VorbisCommentContainer::add(const QString &tag, const QString &value) { Q_ASSERT(m_vc); if (!m_vc) return; QString s; s = tag + _("=") + value; // make a plain C string out of it, containing UTF-8 QByteArray val = s.toUtf8(); // put it into a vorbis_comment_entry structure FLAC__StreamMetadata_VorbisComment_Entry entry; entry.length = val.length(); // in bytes, not characters ! entry.entry = reinterpret_cast(val.data()); // insert the comment into the list unsigned int count = m_vc->data.vorbis_comment.num_comments; bool ok = FLAC__metadata_object_vorbiscomment_insert_comment( m_vc, count, entry, true); Q_ASSERT(ok); Q_UNUSED(ok); } /***************************************************************************/ FLAC__StreamMetadata *Kwave::FlacEncoder::VorbisCommentContainer::data() { return m_vc; } /***************************************************************************/ void Kwave::FlacEncoder::encodeMetaData(const Kwave::FileInfo &info, QVector &flac_metadata) { // encode all Vorbis comments VorbisCommentMap::ConstIterator it; VorbisCommentContainer vc; for (it = m_vorbis_comment_map.constBegin(); it != m_vorbis_comment_map.constEnd(); ++it) { if (!info.contains(it.value())) continue; // not present -> skip QString value = info.get(it.value()).toString(); vc.add(it.key(), value); } flac_metadata.append(vc.data()); // todo: add cue sheet etc here... } /***************************************************************************/ bool Kwave::FlacEncoder::encode(QWidget *widget, Kwave::MultiTrackReader &src, QIODevice &dst, const Kwave::MetaDataList &meta_data) { bool result = true; qDebug("FlacEncoder::encode()"); const Kwave::FileInfo info(meta_data); m_dst = &dst; // get info: tracks, sample rate int tracks = info.tracks(); unsigned int bits = info.bits(); sample_index_t length = info.length(); set_compression_level(5); // @todo make the FLAC compression configurable set_channels(static_cast(tracks)); set_bits_per_sample(static_cast(bits)); set_sample_rate(static_cast(info.rate())); set_total_samples_estimate(static_cast(length)); set_verify(false); // <- set to "true" for debugging // use mid-side stereo encoding if we have two channels set_do_mid_side_stereo(tracks == 2); set_loose_mid_side_stereo(tracks == 2); // encode meta data, most of them as vorbis comments QVector flac_metadata; encodeMetaData(info, flac_metadata); // convert container to a list of pointers unsigned int meta_count = flac_metadata.size(); if (meta_count) { // WARNING: this only stores the pointer, it does not copy! if (!set_metadata(flac_metadata.data(), meta_count)) { qWarning("FlacEncoder: setting meta data failed !?"); } } QVector flac_buffer; do { // open the output device if (!dst.open(QIODevice::ReadWrite | QIODevice::Truncate)) { Kwave::MessageBox::error(widget, i18n("Unable to open the file for saving.")); result = false; break; } // initialize the FLAC stream, this already writes some meta info FLAC__StreamEncoderInitStatus init_state = init(); if (init_state != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { qWarning("state = %d", static_cast(init_state)); Kwave::MessageBox::error(widget, i18n("Unable to open the FLAC encoder.")); result = false; break; } // allocate output buffers, with FLAC 32 bit format unsigned int len = src.blockSize(); // samples for (int track=0; track < tracks; track++) { FLAC__int32 *buffer = static_cast(malloc(sizeof(FLAC__int32) * len)); Q_ASSERT(buffer); if (!buffer) break; flac_buffer.append(buffer); } // allocate input buffer, with Kwave's sample_t Kwave::SampleArray in_buffer(len); Q_ASSERT(in_buffer.size() == len); Q_ASSERT(flac_buffer.size() == tracks); if ((in_buffer.size() < len) || (flac_buffer.size() < tracks)) { Kwave::MessageBox::error(widget, i18n("Out of memory")); result = false; break; } // calculate divisor for reaching the proper resolution int shift = SAMPLE_BITS - bits; if (shift < 0) shift = 0; FLAC__int32 div = (1 << shift); if (div == 1) div = 0; const FLAC__int32 clip_min = -(1 << bits); const FLAC__int32 clip_max = (1 << bits) - 1; sample_index_t rest = length; while (rest && len && !src.isCanceled() && result) { // limit to rest of signal if (len > rest) len = Kwave::toUint(rest); if (!in_buffer.resize(len)) { Kwave::MessageBox::error(widget, i18n("Out of memory")); result = false; break; } // add all samples to one single buffer for (int track = 0; track < tracks; track++) { Kwave::SampleReader *reader = src[track]; Q_ASSERT(reader); if (!reader) break; (*reader) >> in_buffer; // read samples into in_buffer unsigned int l = in_buffer.size();// in_buffer might be empty! if (l < len) { if (!in_buffer.resize(len)) { Kwave::MessageBox::error(widget, i18n("Out of memory")); result = false; break; } while (l < len) in_buffer[l++] = 0; } FLAC__int32 *buf = flac_buffer.at(track); Q_ASSERT(buf); if (!buf) break; const Kwave::SampleArray &in = in_buffer; for (unsigned int in_pos = 0; in_pos < len; in_pos++) { FLAC__int32 s = in[in_pos]; if (div) s /= div; if (s > clip_max) s = clip_max; if (s < clip_min) s = clip_min; *buf = s; buf++; } } if (!result) break; // error occurred? // process all collected samples FLAC__int32 **buffer = flac_buffer.data(); bool processed = process(buffer, static_cast(len)); if (!processed) { result = false; break; } rest -= len; } } while (false); // close the output device and the FLAC stream finish(); // clean up all FLAC metadata while (!flac_metadata.isEmpty()) { FLAC__StreamMetadata *m = flac_metadata[0]; if (m) FLAC__metadata_object_delete(m); flac_metadata.remove(0); } m_dst = 0; dst.close(); while (!flac_buffer.isEmpty()) { FLAC__int32 *buf = flac_buffer.first(); if (buf) free(buf); flac_buffer.remove(0); } return result; } /***************************************************************************/ /***************************************************************************/ diff --git a/plugins/codec_mp3/MP3Encoder.cpp b/plugins/codec_mp3/MP3Encoder.cpp index 130d0a58..355fc75d 100644 --- a/plugins/codec_mp3/MP3Encoder.cpp +++ b/plugins/codec_mp3/MP3Encoder.cpp @@ -1,577 +1,576 @@ /************************************************************************* MP3Encoder.cpp - export of MP3 data via "lame" ------------------- begin : Sat May 19 2012 copyright : (C) 2012 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include -#include #include #include "libkwave/FileInfo.h" #include "libkwave/GenreType.h" #include "libkwave/MessageBox.h" #include "libkwave/MetaDataList.h" #include "libkwave/MixerMatrix.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/Sample.h" #include "libkwave/SampleReader.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "ID3_QIODeviceWriter.h" #include "MP3CodecPlugin.h" #include "MP3Encoder.h" #include "MP3EncoderSettings.h" /***************************************************************************/ Kwave::MP3Encoder::MP3Encoder() :Kwave::Encoder(), m_property_map(), m_lock(), m_dst(0), m_process(this), m_program(), m_params() { REGISTER_MIME_TYPES REGISTER_COMPRESSION_TYPES connect(&m_process, SIGNAL(readyReadStandardOutput()), this, SLOT(dataAvailable())); } /***************************************************************************/ Kwave::MP3Encoder::~MP3Encoder() { } /***************************************************************************/ Kwave::Encoder *Kwave::MP3Encoder::instance() { return new MP3Encoder(); } /***************************************************************************/ QList Kwave::MP3Encoder::supportedProperties() { return m_property_map.properties(); } /***************************************************************************/ void Kwave::MP3Encoder::encodeID3Tags(const Kwave::MetaDataList &meta_data, ID3_Tag &tag) { const Kwave::FileInfo info(meta_data); ID3_FrameInfo frameInfo; const QMap properties(info.properties()); QMap::const_iterator it; for (it = properties.begin(); it != properties.end(); ++it) { const Kwave::FileProperty &property = it.key(); const QVariant &value = it.value(); ID3_FrameID id = m_property_map.findProperty(property); if (id == ID3FID_NOFRAME) continue; if (info.contains(Kwave::INF_CD) && (property == Kwave::INF_CDS)) continue; /* INF_CDS has already been handled by INF_CD */ if (info.contains(Kwave::INF_TRACK) && (property == Kwave::INF_TRACKS)) continue; /* INF_TRACKS has already been handled by INF_TRACK */ ID3_Frame *frame = new ID3_Frame; Q_ASSERT(frame); if (!frame) break; QString str_val = value.toString(); // qDebug("encoding ID3 tag #%02d, property='%s', value='%s'", // static_cast(id), // DBG(info.name(property)), // DBG(str_val) // ); // encode in UCS16 frame->SetID(id); ID3_Field *field = frame->GetField(ID3FN_TEXT); if (!field) { qWarning("no field, frame id=%d", static_cast(id)); delete frame; continue; } ID3_PropertyMap::Encoding encoding = m_property_map.encoding(id); switch (encoding) { case ID3_PropertyMap::ENC_TEXT_PARTINSET: { field->SetEncoding(ID3TE_UTF16); // if "number of CDs is available: append with "/" int cds = info.get(Kwave::INF_CDS).toInt(); if (cds > 0) str_val += _("/%1").arg(cds); field->Set(static_cast(str_val.utf16())); break; } case ID3_PropertyMap::ENC_TRACK_NUM: { // if "number of tracks is available: append with "/" int tracks = info.get(Kwave::INF_TRACKS).toInt(); if (tracks > 0) str_val += _("/%1").arg(tracks); field->SetEncoding(ID3TE_UTF16); field->Set(static_cast(str_val.utf16())); break; } case ID3_PropertyMap::ENC_TERMS_OF_USE: // the same as ENC_COMMENT, but without "Description" /* FALLTHROUGH */ case ID3_PropertyMap::ENC_COMMENT: { // detect language at the start "[xxx] " QString lang; if (str_val.startsWith(QLatin1Char('[')) && (str_val.at(4) == QLatin1Char(']'))) { lang = str_val.mid(1,3); str_val = str_val.mid(5); frame->GetField(ID3FN_DESCRIPTION)->Set(""); frame->GetField(ID3FN_LANGUAGE)->Set( static_cast(lang.toLatin1().data())); } /* frame->GetField(ID3FN_DESCRIPTION)->Set(""); */ field->SetEncoding(ID3TE_UTF16); field->Set(static_cast(str_val.utf16())); break; } case ID3_PropertyMap::ENC_GENRE_TYPE: { int genre = Kwave::GenreType::fromID3(str_val); if (genre >= 0) str_val = Kwave::GenreType::name(genre, false); // else: user defined genre type, take it as it is field->SetEncoding(ID3TE_UTF16); field->Set(static_cast(str_val.utf16())); break; } case ID3_PropertyMap::ENC_LENGTH: { // length in milliseconds const double rate = info.rate(); const sample_index_t samples = info.length(); if ((rate > 0) && samples) { const sample_index_t ms = static_cast( (static_cast(samples) * 1E3) / rate); str_val = QString::number(ms); field->SetEncoding(ID3TE_UTF16); field->Set(static_cast(str_val.utf16())); } else { delete frame; frame = 0; } break; } case ID3_PropertyMap::ENC_TEXT_TIMESTAMP: { // ISO 8601 timestamp: "yyyy-MM-ddTHH:mm:ss" QString s = Kwave::string2date(str_val); // if failed, try "yyyy" format (year only) if (!s.length()) { int year = str_val.toInt(); if ((year > 0) && (year < 9999)) { frame->SetID(ID3FID_YEAR); // -> re-get the field ! // it has become invalid through "SetID()" field = frame->GetField(ID3FN_TEXT); if (!field) { qWarning("no field, frame id=%d", static_cast(id)); break; } s = _("%1").arg(year, 4, 10, QLatin1Char('0')); } } if (s.length()) { field->SetEncoding(ID3TE_UTF16); field->Set(static_cast(s.utf16())); } else { // date is invalid, unknown format qWarning("MP3Encoder::encodeID3Tags(): invalid date: '%s'", DBG(str_val)); delete frame; frame = 0; } break; } case ID3_PropertyMap::ENC_TEXT_SLASH: /* FALLTHROUGH */ case ID3_PropertyMap::ENC_TEXT_URL: /* FALLTHROUGH */ case ID3_PropertyMap::ENC_TEXT: field->SetEncoding(ID3TE_UTF16); field->Set(static_cast(str_val.utf16())); break; case ID3_PropertyMap::ENC_NONE: /* FALLTHROUGH */ default: // ignore delete frame; frame = 0; break; } if (frame) tag.AttachFrame(frame); } tag.Strip(); tag.Update(); } #define OPTION(__field__) \ if (settings.__field__.length()) m_params.append(settings.__field__) #define OPTION_P(__field__, __value__) \ if (settings.__field__.length()) \ m_params.append( \ QString(settings.__field__.arg(__value__)).split(QLatin1Char(' '))) /***************************************************************************/ bool Kwave::MP3Encoder::encode(QWidget *widget, Kwave::MultiTrackReader &src, QIODevice &dst, const Kwave::MetaDataList &meta_data) { bool result = true; ID3_Tag id3_tag; Kwave::MP3EncoderSettings settings; settings.load(); ID3_TagType id3_tag_type = ID3TT_ID3V2; id3_tag.SetSpec(ID3V2_LATEST); const Kwave::FileInfo info(meta_data); // get info: tracks, sample rate const unsigned int tracks = src.tracks(); const sample_index_t length = src.last() - src.first() + 1; unsigned int bits = qBound(8U, ((info.bits() + 7) & ~0x7), 32U); const double rate = info.rate(); const unsigned int out_tracks = qMin(tracks, 2U); // when encoding track count > 2, show a warning that we will mix down // to stereo if (tracks > 2) { if (Kwave::MessageBox::warningContinueCancel( widget, i18n("The file format you have chosen supports only mono or " "stereo. This file will be mixed down to stereo when " "saving."), QString(), QString(), QString(), _("mp3_accept_down_mix_on_export")) != KMessageBox::Continue) { return false; } } // open the output device if (!dst.open(QIODevice::ReadWrite | QIODevice::Truncate)) { Kwave::MessageBox::error(widget, i18n("Unable to open the file for saving!")); return false; } m_dst = &dst; m_params.clear(); // encode meta data into with id3lib ID3_QIODeviceWriter id3_writer(dst); encodeID3Tags(meta_data, id3_tag); OPTION(m_flags.m_prepend); // optional parameters at the very start // mandantory audio input format and encoding options OPTION(m_input.m_raw_format); // input is raw audio OPTION(m_input.m_byte_order); // byte swapping OPTION(m_input.m_signed); // signed sample format // supported sample rates [kHz] // 8 / 11.025 / 12 / 16 / 22.05 / 24 /32 / 44.1 / 48 // if our rate is not supported, lame automatically resamples with the // next higher supported rate if (settings.m_format.m_sample_rate.length()) { QString str = settings.m_format.m_sample_rate; if (str.contains(_("[%khz]"))) { str = str.replace(_("[%khz]"), _("%1")).arg(rate / 1000.0, 1, 'f', 2); m_params.append(str.split(QLatin1Char(' '))); } else { m_params.append(str.arg(rate).split(QLatin1Char(' '))); } } // bits per sample, supported by Kwave are: 8 / 16 / 24 / 32 if (!settings.m_format.m_bits_per_sample.contains(QLatin1Char('%'))) { // bits/sample are not selectable => use default=16bit bits = 16; OPTION(m_format.m_bits_per_sample); } else { OPTION_P(m_format.m_bits_per_sample, bits); } // encode one track as "mono" and two tracks as "joint-stereo" if (tracks == 1) { OPTION(m_format.m_channels.m_mono); } else { OPTION(m_format.m_channels.m_stereo); } // nominal / lower / upper bitrate int bitrate_min = 8; int bitrate_max = 320; int bitrate_nom = 128; if (info.contains(Kwave::INF_BITRATE_NOMINAL)) { // nominal bitrate => use ABR mode bitrate_nom = info.get(Kwave::INF_BITRATE_NOMINAL).toInt() / 1000; bitrate_nom = qBound(bitrate_min, bitrate_nom, bitrate_max); OPTION_P(m_quality.m_bitrate.m_avg, bitrate_nom); } if (info.contains(Kwave::INF_BITRATE_LOWER)) { int bitrate = info.get(Kwave::INF_BITRATE_LOWER).toInt() / 1000; bitrate_min = qBound(bitrate_min, bitrate, bitrate_nom); OPTION_P(m_quality.m_bitrate.m_min, bitrate_min); } if (info.contains(Kwave::INF_BITRATE_UPPER)) { int bitrate = info.get(Kwave::INF_BITRATE_UPPER).toInt() / 1000; bitrate_max = qBound(bitrate_nom, bitrate, bitrate_max); OPTION_P(m_quality.m_bitrate.m_max, bitrate_max); } // Kwave::INF_MPEG_LAYER, /**< MPEG Layer, I/II/III */ // Kwave::INF_MPEG_MODEEXT, /**< MPEG mode extension */ // Kwave::INF_MPEG_VERSION, /**< MPEG version */ /* MPEG emphasis mode */ if (info.contains(Kwave::INF_MPEG_EMPHASIS)) { int emphasis = info.get(Kwave::INF_MPEG_EMPHASIS).toInt(); switch (emphasis) { case 1: OPTION(m_encoding.m_emphasis.m_50_15ms); // 1 = 50/15ms break; case 3: OPTION(m_encoding.m_emphasis.m_ccit_j17); // 3 = CCIT J.17 break; case 0: /* FALLTHROUGH */ default: OPTION(m_encoding.m_emphasis.m_none); // 0 = none break; } } OPTION(m_encoding.m_noise_shaping); // noise shaping settings OPTION(m_encoding.m_compatibility); // compatibility options if (info.contains(Kwave::INF_COPYRIGHTED) && info.get(Kwave::INF_COPYRIGHTED).toBool()) { OPTION(m_flags.m_copyright); // copyrighted } if (info.contains(Kwave::INF_ORIGINAL) && !info.get(Kwave::INF_ORIGINAL).toBool()) { OPTION(m_flags.m_original); // original } OPTION(m_flags.m_protect); // CRC protection OPTION(m_flags.m_append); // optional parameters at the end m_params.append(_("-")); // infile = stdin m_params.append(_("-")); // outfile = stdout m_program = settings.m_path; qDebug("MP3Encoder::encode(): %s %s", DBG(m_program), DBG(m_params.join(_(" "))) ); m_process.setReadChannel(QProcess::StandardOutput); m_process.start(m_program, m_params); QString stdError; if (!m_process.waitForStarted()) { qWarning("cannot start program '%s'", DBG(m_program)); m_process.waitForFinished(); result = false; } // if a ID3v2 tag is requested, the tag comes at the start if (id3_tag_type == ID3TT_ID3V2) id3_tag.Render(id3_writer, id3_tag_type); // MP3 supports only mono and stereo, prepare a mixer matrix // (not used in case of tracks <= 2) Kwave::MixerMatrix mixer(tracks, out_tracks); // read in from the sample readers const unsigned int buf_len = sizeof(m_write_buffer); const int bytes_per_sample = bits / 8; sample_index_t rest = length; Kwave::SampleArray in_samples(tracks); Kwave::SampleArray out_samples(tracks); while (result && rest && (m_process.state() != QProcess::NotRunning)) { unsigned int x; unsigned int y; // merge the tracks into the sample buffer quint8 *dst_buffer = &(m_write_buffer[0]); unsigned int count = buf_len / (bytes_per_sample * tracks); if (rest < count) count = Kwave::toUint(rest); unsigned int written = 0; for (written = 0; written < count; written++) { const sample_t *src_buf = 0; // fill input buffer with samples for (x = 0; x < tracks; ++x) { in_samples[x] = 0; Kwave::SampleReader *stream = src[x]; Q_ASSERT(stream); if (!stream) continue; if (!stream->eof()) (*stream) >> in_samples[x]; } if (tracks > 2) { // multiply matrix with input to get output const Kwave::SampleArray &in = in_samples; for (y = 0; y < out_tracks; ++y) { double sum = 0; for (x = 0; x < tracks; ++x) sum += static_cast(in[x]) * mixer[x][y]; out_samples[y] = static_cast(sum); } // use output of the matrix src_buf = out_samples.constData(); } else { // use input buffer directly src_buf = in_samples.constData(); } // sample conversion from 24bit to raw PCM, native endian for (y = 0; y < out_tracks; ++y) { sample_t s = *(src_buf++); #if Q_BYTE_ORDER == Q_BIG_ENDIAN // big endian if (bits >= 8) *(dst_buffer++) = static_cast(s >> 16); if (bits > 8) *(dst_buffer++) = static_cast(s >> 8); if (bits > 16) *(dst_buffer++) = static_cast(s & 0xFF); if (bits > 24) *(dst_buffer++) = 0x00; #else // little endian if (bits > 24) *(dst_buffer++) = 0x00; if (bits > 16) *(dst_buffer++) = static_cast(s & 0xFF); if (bits > 8) *(dst_buffer++) = static_cast(s >> 8); if (bits >= 8) *(dst_buffer++) = static_cast(s >> 16); #endif } } // write out to the stdin of the external process qint64 bytes_written = m_process.write( reinterpret_cast(&(m_write_buffer[0])), written * (bytes_per_sample * tracks) ); // break if eof reached or disk full if (!bytes_written) break; // wait for write to take all data... m_process.waitForBytesWritten(); // abort if the user pressed cancel // --> this would leave a corrupted file !!! if (src.isCanceled()) break; Q_ASSERT(rest >= written); rest -= written; } // flush and close the write channel m_process.closeWriteChannel(); // wait until the process has finished qDebug("wait for finish of the process"); while (m_process.state() != QProcess::NotRunning) { m_process.waitForFinished(100); if (src.isCanceled()) break; } int exit_code = m_process.exitCode(); qDebug("exit code=%d", exit_code); if (!result || (exit_code != 0)) { result = false; stdError = QString::fromLocal8Bit(m_process.readAllStandardError()); qWarning("stderr output: %s", DBG(stdError)); Kwave::MessageBox::error(widget, i18nc("%1=name of the external program, %2=stderr of the program", "An error occurred while calling the external encoder '%1':\n\n%2", m_program, stdError )); } // if a ID3v1 tag is requested, the tag comes at the end if (id3_tag_type != ID3TT_ID3V2) id3_tag.Render(id3_writer, id3_tag_type); { QMutexLocker _lock(&m_lock); m_dst = 0; dst.close(); } return result; } /***************************************************************************/ void Kwave::MP3Encoder::dataAvailable() { while (m_process.bytesAvailable()) { qint64 len = m_process.read(&(m_read_buffer[0]), sizeof(m_read_buffer)); if (len) { QMutexLocker _lock(&m_lock); if (m_dst) m_dst->write(&(m_read_buffer[0]), len); } } } /***************************************************************************/ /***************************************************************************/ diff --git a/plugins/codec_ogg/OggDecoder.cpp b/plugins/codec_ogg/OggDecoder.cpp index be1b61d8..61dda2a3 100644 --- a/plugins/codec_ogg/OggDecoder.cpp +++ b/plugins/codec_ogg/OggDecoder.cpp @@ -1,284 +1,283 @@ /************************************************************************* OggDecoder.cpp - decoder for Ogg/Vorbis data ------------------- begin : Tue Sep 10 2002 copyright : (C) 2002 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include -#include #include "libkwave/Compression.h" #include "libkwave/MessageBox.h" #include "libkwave/MultiWriter.h" #include "libkwave/Sample.h" #include "libkwave/SampleArray.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libkwave/Writer.h" #include "OggCodecPlugin.h" #include "OggDecoder.h" #include "OggSubDecoder.h" #include "OpusDecoder.h" #include "VorbisDecoder.h" //*************************************************************************** Kwave::OggDecoder::OggDecoder() :Kwave::Decoder(), m_sub_decoder(0), m_source(0) { #ifdef HAVE_OGG_OPUS REGISTER_OGG_OPUS_MIME_TYPES REGISTER_COMPRESSION_TYPE_OGG_OPUS #endif /* HAVE_OGG_OPUS */ #ifdef HAVE_OGG_VORBIS REGISTER_OGG_VORBIS_MIME_TYPES REGISTER_COMPRESSION_TYPE_OGG_VORBIS #endif /* HAVE_OGG_VORBIS */ /* Ogg audio, as per RFC5334 */ addMimeType("audio/ogg", i18n("Ogg audio"), "*.oga"); addMimeType("application/ogg", i18n("Ogg audio"), "*.ogx"); } //*************************************************************************** Kwave::OggDecoder::~OggDecoder() { if (m_source) close(); } //*************************************************************************** Kwave::Decoder *Kwave::OggDecoder::instance() { return new Kwave::OggDecoder(); } //*************************************************************************** int Kwave::OggDecoder::parseHeader(QWidget *widget) { // grab some data at the head of the stream. We want the first page // (which is guaranteed to be small and only contain the Vorbis // stream initial header) We need the first page to get the stream // serialno. // submit a 4k block to libvorbis' Ogg layer char *buffer = ogg_sync_buffer(&m_oy, 4096); Q_ASSERT(buffer); if (!buffer) return -1; long int bytes = static_cast(m_source->read(buffer, 4096)); if ((bytes <= 0) && (!m_source->pos())) { Kwave::MessageBox::error(widget, i18n( "Ogg bitstream has zero-length.")); return -1; } ogg_sync_wrote(&m_oy, bytes); // Get the first page. if (ogg_sync_pageout(&m_oy, &m_og) != 1) { // have we simply run out of data? If so, we're done. if (bytes < 4096) return 0; // error case. seems not be Vorbis data Kwave::MessageBox::error(widget, i18n( "Input does not appear to be an Ogg bitstream.")); return -1; } // Get the serial number and set up the rest of decode. // serialno first; use it to set up a logical stream ogg_stream_init(&m_os, ogg_page_serialno(&m_og)); // get the first packet if (ogg_stream_pagein(&m_os, &m_og) < 0) { // error; stream version mismatch perhaps Kwave::MessageBox::error(widget, i18n( "Error reading first page of the Ogg bitstream data.")); return -1; } if ((ogg_stream_packetout(&m_os, &m_op) != 1) || (m_op.bytes < 8)) { // no page? must not be vorbis Kwave::MessageBox::error(widget, i18n( "Error reading initial header packet.")); return -1; } // get rid of the previous sub decoder if (m_sub_decoder) { delete m_sub_decoder; m_sub_decoder = 0; } Kwave::FileInfo info(metaData()); // --------------------------------- // auto-detect the sub decoder #ifdef HAVE_OGG_OPUS if (memcmp(m_op.packet, "OpusHead", 8) == 0) { qDebug(" OggDecoder: detected Opus codec"); m_sub_decoder = new Kwave::OpusDecoder(m_source, m_oy, m_os, m_og, m_op); info.set(Kwave::INF_MIMETYPE, _("audio/opus")); } #endif /* HAVE_OGG_OPUS */ #ifdef HAVE_OGG_VORBIS if (memcmp(m_op.packet + 1, "vorbis", 6) == 0) { qDebug(" OggDecoder: detected Vorbis codec"); m_sub_decoder = new Kwave::VorbisDecoder(m_source, m_oy, m_os, m_og, m_op); info.set(Kwave::INF_MIMETYPE, _("audio/x-vorbis+ogg")); } #endif /* HAVE_OGG_VORBIS */ if (!m_sub_decoder) { qDebug("--- dump of the first 8 bytes of the packet: ---"); for (unsigned int i = 0; i < 8; i++) qDebug("%2d: 0x%02X - '%c'", i, m_op.packet[i], m_op.packet[i]); Kwave::MessageBox::error(widget, i18n( "Error: Codec not supported")); return -1; } info.setLength(0); // use streaming info.setBits(SAMPLE_BITS); // use Kwave's internal resolution if (m_sub_decoder->open(widget, info) < 0) return -1; metaData().replace(Kwave::MetaDataList(info)); return 1; } //*************************************************************************** bool Kwave::OggDecoder::open(QWidget *widget, QIODevice &src) { metaData().clear(); Q_ASSERT(!m_source); if (m_source) qWarning("OggDecoder::open(), already open !"); // try to open the source if (!src.open(QIODevice::ReadOnly)) { qWarning("failed to open source !"); return false; } // take over the source m_source = &src; /********** Decode setup ************/ qDebug("--- OggDecoder::open() ---"); ogg_sync_init(&m_oy); // Now we can read pages // read the header the first time if (parseHeader(widget) < 0) return false; return true; } //*************************************************************************** bool Kwave::OggDecoder::decode(QWidget *widget, Kwave::MultiWriter &dst) { int eos = 0; Q_ASSERT(m_source); Q_ASSERT(m_sub_decoder); if (!m_source || !m_sub_decoder) return false; // we repeat if the bitstream is chained while (!dst.isCanceled()) { // The rest is just a straight decode loop until end of stream while (!eos) { while (!eos) { int result = ogg_sync_pageout(&m_oy, &m_og); if (result == 0) break; // need more data if (result < 0) { // missing or corrupt data at this page position Kwave::MessageBox::error(widget, i18n( "Corrupt or missing data in bitstream. Continuing." )); } else { // can safely ignore errors at this point ogg_stream_pagein(&m_os, &m_og); while (1) { result = ogg_stream_packetout(&m_os, &m_op); if (result == 0) break; // need more data if (result < 0) { // missing or corrupt data at this page position // no reason to complain; already complained above } else { result = m_sub_decoder->decode(dst); if (result < 0) break; // signal the current position emit sourceProcessed(m_source->pos()); } } if (ogg_page_eos(&m_og) || dst.isCanceled()) eos = 1; } } if (!eos) { char *buffer = ogg_sync_buffer(&m_oy, 4096); int bytes = Kwave::toInt(m_source->read(buffer, 4096)); ogg_sync_wrote(&m_oy, bytes); if (!bytes) eos = 1; } } // clean up this logical bitstream; before exit we see if we're // followed by another [chained] ogg_stream_clear(&m_os); m_sub_decoder->reset(); // parse the next header, maybe we parse a stream or chain... if (eos || (parseHeader(widget) < 1)) break; } // OK, clean up the framer ogg_sync_clear(&m_oy); // signal the current position emit sourceProcessed(m_source->pos()); Kwave::FileInfo info(metaData()); m_sub_decoder->close(info); metaData().replace(Kwave::MetaDataList(info)); // return with a valid Signal, even if the user pressed cancel ! return true; } //*************************************************************************** void Kwave::OggDecoder::close() { m_source = 0; delete m_sub_decoder; m_sub_decoder = 0; } //*************************************************************************** //*************************************************************************** diff --git a/plugins/codec_ogg/OggEncoder.cpp b/plugins/codec_ogg/OggEncoder.cpp index 8c0eaa5d..72590730 100644 --- a/plugins/codec_ogg/OggEncoder.cpp +++ b/plugins/codec_ogg/OggEncoder.cpp @@ -1,139 +1,138 @@ /************************************************************************* OggEncoder.cpp - encoder for Ogg/Vorbis data ------------------- begin : Tue Sep 10 2002 copyright : (C) 2002 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include -#include #include #include #include "libkwave/FileInfo.h" #include "libkwave/MessageBox.h" #include "libkwave/MetaDataList.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/Sample.h" #include "OggCodecPlugin.h" #include "OggEncoder.h" #include "OggSubEncoder.h" #include "OpusEncoder.h" #include "VorbisEncoder.h" /***************************************************************************/ Kwave::OggEncoder::OggEncoder() :Kwave::Encoder(), m_comments_map() { #ifdef HAVE_OGG_OPUS REGISTER_OGG_OPUS_MIME_TYPES REGISTER_COMPRESSION_TYPE_OGG_OPUS #endif /* HAVE_OGG_OPUS */ #ifdef HAVE_OGG_VORBIS REGISTER_OGG_VORBIS_MIME_TYPES REGISTER_COMPRESSION_TYPE_OGG_VORBIS #endif /* HAVE_OGG_VORBIS */ } /***************************************************************************/ Kwave::OggEncoder::~OggEncoder() { } /***************************************************************************/ Kwave::Encoder *Kwave::OggEncoder::instance() { return new Kwave::OggEncoder(); } /***************************************************************************/ QList Kwave::OggEncoder::supportedProperties() { return m_comments_map.values(); } /***************************************************************************/ bool Kwave::OggEncoder::encode(QWidget *widget, Kwave::MultiTrackReader &src, QIODevice &dst, const Kwave::MetaDataList &meta_data) { const Kwave::FileInfo info(meta_data); QSharedPointer sub_encoder; // determine which codec (sub encoder) to use, // by examining the compression type const Kwave::Compression::Type compression = info.contains(Kwave::INF_COMPRESSION) ? Kwave::Compression::fromInt( info.get(Kwave::INF_COMPRESSION).toInt()) : Kwave::Compression::NONE; #ifdef HAVE_OGG_OPUS if (compression == Compression::OGG_OPUS) { qDebug(" OggEncoder: using Opus codec"); sub_encoder = QSharedPointer(new Kwave::OpusEncoder()); } #endif /* HAVE_OGG_OPUS */ #ifdef HAVE_OGG_VORBIS if (compression == Compression::OGG_VORBIS) { qDebug(" OggEncoder: using Vorbis codec"); sub_encoder = QSharedPointer(new Kwave::VorbisEncoder()); } #endif /* HAVE_OGG_VORBIS */ if (!sub_encoder) { qDebug(" OggEncoder: compression='%d'", compression); Kwave::MessageBox::error(widget, i18nc( "error in Ogg encoder, no support for a compression type " "(e.g. opus, vorbis etc)", "Error: No Codec for '%1' available", Kwave::Compression(compression).name() )); return false; } if (!sub_encoder->open(widget, info, src)) return false; // open the output device if (!dst.open(QIODevice::ReadWrite | QIODevice::Truncate)) { Kwave::MessageBox::error(widget, i18n("Unable to open the file for saving.")); return false; } if (!sub_encoder->writeHeader(dst)) return false; if (!sub_encoder->encode(src, dst)) return false; // clean up and exit. sub_encoder->close(); // the sub encoder will be deleted automatically (QSharedPointer) return true; } //*************************************************************************** //*************************************************************************** diff --git a/plugins/codec_wav/WavDecoder.cpp b/plugins/codec_wav/WavDecoder.cpp index f1ed19ef..aaa8e98f 100644 --- a/plugins/codec_wav/WavDecoder.cpp +++ b/plugins/codec_wav/WavDecoder.cpp @@ -1,812 +1,811 @@ /************************************************************************* WavDecoder.cpp - decoder for wav data ------------------- begin : Sun Mar 10 2002 copyright : (C) 2002 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include -#include #include #include #include "libkwave/Compression.h" #include "libkwave/ConfirmCancelProxy.h" #include "libkwave/Label.h" #include "libkwave/LabelList.h" #include "libkwave/MessageBox.h" #include "libkwave/MetaData.h" #include "libkwave/MetaDataList.h" #include "libkwave/MultiWriter.h" #include "libkwave/Sample.h" #include "libkwave/SampleFormat.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libkwave/VirtualAudioFile.h" #include "libkwave/Writer.h" #include "RIFFChunk.h" #include "RIFFParser.h" #include "RecoveryBuffer.h" #include "RecoveryMapping.h" #include "RecoverySource.h" #include "RepairVirtualAudioFile.h" #include "WavDecoder.h" #include "WavFileFormat.h" #include "WavFormatMap.h" #define CHECK(cond) Q_ASSERT(cond); if (!(cond)) { src.close(); return false; } //*************************************************************************** Kwave::WavDecoder::WavDecoder() :Kwave::Decoder(), m_source(0), m_src_adapter(0), m_known_chunks(), m_property_map() { REGISTER_MIME_TYPES REGISTER_COMPRESSION_TYPES // native WAVE chunk names m_known_chunks.append(_("cue ")); /* Markers */ m_known_chunks.append(_("data")); /* Sound Data */ m_known_chunks.append(_("fact")); /* Fact (length in samples) */ m_known_chunks.append(_("fmt ")); /* Format */ m_known_chunks.append(_("inst")); /* Instrument */ m_known_chunks.append(_("labl")); /* label */ m_known_chunks.append(_("ltxt")); /* labeled text */ m_known_chunks.append(_("note")); /* note chunk */ m_known_chunks.append(_("plst")); /* Play List */ m_known_chunks.append(_("smpl")); /* Sampler */ // add all sub-chunks of the LIST chunk (properties) foreach (const QByteArray &name, m_property_map.chunks()) m_known_chunks.append(QLatin1String(name)); // some chunks known from AIFF format m_known_chunks.append(_("FVER")); m_known_chunks.append(_("COMM")); m_known_chunks.append(_("wave")); m_known_chunks.append(_("SSND")); // chunks of .lbm image files, IFF format m_known_chunks.append(_("BMHD")); m_known_chunks.append(_("CMAP")); m_known_chunks.append(_("BODY")); } //*************************************************************************** Kwave::WavDecoder::~WavDecoder() { if (m_source) close(); if (m_src_adapter) delete m_src_adapter; } //*************************************************************************** Kwave::Decoder *Kwave::WavDecoder::instance() { return new Kwave::WavDecoder(); } //*************************************************************************** bool Kwave::WavDecoder::open(QWidget *widget, QIODevice &src) { Kwave::FileInfo info; Kwave::LabelList labels; metaData().clear(); Q_ASSERT(!m_source); bool need_repair = false; if (m_source) qWarning("WavDecoder::open(), already open !"); // try to open the source if (!src.open(QIODevice::ReadOnly)) { qWarning("failed to open source !"); return false; } QStringList main_chunks; main_chunks.append(_("RIFF")); /* RIFF, little-endian */ main_chunks.append(_("RIFX")); /* RIFF, big-endian */ main_chunks.append(_("FORM")); /* used in AIFF, BE or IFF/.lbm */ main_chunks.append(_("LIST")); /* additional information */ main_chunks.append(_("adtl")); /* Associated Data */ Kwave::RIFFParser parser(src, main_chunks, m_known_chunks); // prepare a progress dialog QProgressDialog progress(widget); progress.setWindowTitle(i18n("Auto Repair")); progress.setModal(true); progress.setMinimumDuration(0); progress.setMaximum(100); progress.setAutoClose(true); progress.setValue(0); progress.setLabelText(i18n("Reading...")); connect(&parser, SIGNAL(progress(int)), &progress, SLOT(setValue(int))); connect(&parser, SIGNAL(action(QString)), &progress, SLOT(setLabelText(QString))); Kwave::ConfirmCancelProxy confirm_cancel(widget, &progress, SIGNAL(canceled()), &parser, SLOT(cancel())); // parse, including endianness detection parser.parse(); progress.reset(); if (progress.wasCanceled()) return false; // qDebug("--- RIFF file structure after first pass ---"); // parser.dumpStructure(); // check if there is a RIFF chunk at all... Kwave::RIFFChunk *riff_chunk = parser.findChunk("/RIFF:WAVE"); Kwave::RIFFChunk *fact_chunk = parser.findChunk("/RIFF:WAVE/fact"); Kwave::RIFFChunk *fmt_chunk = parser.findChunk("/RIFF:WAVE/fmt "); Kwave::RIFFChunk *data_chunk = parser.findChunk("/RIFF:WAVE/data"); if (!riff_chunk || !fmt_chunk || !data_chunk || !parser.isSane()) { if (Kwave::MessageBox::warningContinueCancel(widget, i18n("The file has been structurally damaged or " "it is no WAV file.\n" "Should Kwave try to repair it?"), i18n("Kwave Auto Repair"), i18n("&Repair")) != KMessageBox::Continue) { // user didn't let us try :-( return false; } need_repair = true; } // collect all missing chunks if (!riff_chunk) riff_chunk = parser.findMissingChunk("RIFF:WAVE"); if (progress.wasCanceled()) return false; if (!fmt_chunk) { parser.findMissingChunk("fmt "); fmt_chunk = parser.findChunk("/RIFF:WAVE/fmt "); if (progress.wasCanceled()) return false; if (!fmt_chunk) fmt_chunk = parser.findChunk("fmt "); need_repair = true; } if (!data_chunk) { parser.findMissingChunk("data"); data_chunk = parser.findChunk("/RIFF:WAVE/data"); if (progress.wasCanceled()) return false; if (!data_chunk) data_chunk = parser.findChunk("data"); need_repair = true; } // not everything found -> need heavy repair actions ! if (!fmt_chunk || !data_chunk || need_repair) { qDebug("doing heavy repair actions..."); parser.dumpStructure(); parser.repair(); parser.dumpStructure(); if (progress.wasCanceled()) return false; if (!fmt_chunk) fmt_chunk = parser.findChunk("/RIFF:WAVE/fmt "); if (!fmt_chunk) fmt_chunk = parser.findChunk("/RIFF/fmt "); if (!fmt_chunk) fmt_chunk = parser.findChunk("fmt "); if (!data_chunk) data_chunk = parser.findChunk("/RIFF:WAVE/data"); if (!data_chunk) data_chunk = parser.findChunk("/RIFF/data"); if (!data_chunk) data_chunk = parser.findChunk("data"); need_repair = true; } quint32 fmt_offset = 0; if (fmt_chunk) fmt_offset = fmt_chunk->dataStart(); // qDebug("fmt chunk starts at 0x%08X", fmt_offset); // quint32 data_offset = 0; quint32 data_size = 0; if (data_chunk) { // data_offset = data_chunk->dataStart(); data_size = data_chunk->physLength(); // qDebug("data chunk at 0x%08X (%u byte)", data_offset, data_size); } if (!data_size) { // hide progress bar, finally progress.hide(); qApp->processEvents(); // show final error message Kwave::MessageBox::sorry(widget, i18n("The opened file is no WAV file or it is damaged:\n" "There is not enough valid sound data.\n\n" "It makes no sense to continue now.")); return false; } // WORKAROUND: if there is a fact chunk, it must not contain zero // for the moment the only workaround known is to open // such broken files in repair mode if (fact_chunk) { qint64 offset = fact_chunk->dataStart(); union { quint32 len; char bytes[4]; } fact; fact.len = 0; src.seek(offset); src.read(&(fact.bytes[0]), 4); if ((fact.len == 0x00000000) || (fact.len == 0xFFFFFFFF)) { qWarning("WARNING: invalid 'fact' chunk content: 0x%08X " "-> silent repair mode", fact.len); need_repair = true; } } // final check for structural integrity: // we should have exactly one RIFF, fmt and data chunk ! if ((parser.chunkCount("fmt ") != 1) || (parser.chunkCount("data") != 1)) { // hide progress bar, temporarily progress.hide(); qApp->processEvents(); if (Kwave::MessageBox::warningContinueCancel(widget, i18n("The WAV file seems to be damaged:\n" "Some chunks are duplicate or missing.\n\n" "Kwave will only use the first ones and ignore\n" "the rest. This might lead to loss of data.\n" "If you want to get your file repaired completely,\n" "please write an email to the Kwave mailing list\n" "and we will help you."), i18n("Kwave Auto Repair") ) != KMessageBox::Continue) { // user decided to abort and repair on his own // good luck! return false; } need_repair = true; // show progress bar again progress.show(); qApp->processEvents(); } // source successfully opened m_source = &src; // read the wave header Kwave::wav_fmt_header_t header; unsigned int rate = 0; unsigned int bits = 0; src.seek(fmt_offset); // get the encoded block of data from the mime source CHECK(src.size() > static_cast(sizeof(Kwave::wav_header_t) + 8)); // get the header src.read(reinterpret_cast(&header), sizeof(Kwave::wav_fmt_header_t)); header.min.format = qFromLittleEndian(header.min.format); header.min.channels = qFromLittleEndian(header.min.channels); header.min.samplerate = qFromLittleEndian(header.min.samplerate); header.min.bytespersec = qFromLittleEndian(header.min.bytespersec); header.min.blockalign = qFromLittleEndian(header.min.blockalign); header.min.bitwidth = qFromLittleEndian(header.min.bitwidth); unsigned int tracks = header.min.channels; rate = header.min.samplerate; bits = header.min.bitwidth; Kwave::WavFormatMap known_formats; QString format_name = known_formats.findName(header.min.format); // qDebug("-------------------------"); // qDebug("wav header:"); // qDebug("format = 0x%04X, (%s)", header.min.format, // DBG(format_name)); // qDebug("channels = %d", header.min.channels); // qDebug("rate = %u", header.min.samplerate); // qDebug("bytes/s = %u", header.min.bytespersec); // qDebug("block align = %d", header.min.blockalign); // qDebug("bits/sample = %d", header.min.bitwidth); // qDebug("-------------------------"); // open the file through libaudiofile :) if (need_repair) { QList *repair_list = new QList(); Q_ASSERT(repair_list); if (!repair_list) return false; Kwave::RIFFChunk *root = (riff_chunk) ? riff_chunk : parser.findChunk(""); // parser.dumpStructure(); // qDebug("riff chunk = %p, parser.findChunk('')=%p", riff_chunk, // parser.findChunk("")); repair(repair_list, root, fmt_chunk, data_chunk); m_src_adapter = new Kwave::RepairVirtualAudioFile(*m_source, repair_list); } else { m_src_adapter = new Kwave::VirtualAudioFile(*m_source); } Q_ASSERT(m_src_adapter); if (!m_src_adapter) return false; m_src_adapter->open(m_src_adapter, 0); AFfilehandle fh = m_src_adapter->handle(); if (!fh || (m_src_adapter->lastError() >= 0)) { QString reason; switch (m_src_adapter->lastError()) { case AF_BAD_NOT_IMPLEMENTED: reason = i18n("Format or function is not implemented") + _("\n(") + format_name + _(")"); break; case AF_BAD_MALLOC: reason = i18n("Out of memory"); break; case AF_BAD_HEADER: reason = i18n("file header is damaged"); break; case AF_BAD_CODEC_TYPE: reason = i18n("Invalid codec type") + _("\n(") + format_name + _(")"); break; case AF_BAD_OPEN: reason = i18n("Opening the file failed"); break; case AF_BAD_READ: reason = i18n("Read access failed"); break; case AF_BAD_SAMPFMT: reason = i18n("Invalid sample format"); break; default: reason = i18n("internal libaudiofile error #%1: '%2'", m_src_adapter->lastError(), m_src_adapter->lastErrorText() ); } QString text= i18n("An error occurred while opening the file:\n'%1'", reason); Kwave::MessageBox::error(widget, text); return false; } AFframecount length = afGetFrameCount(fh, AF_DEFAULT_TRACK); tracks = afGetVirtualChannels(fh, AF_DEFAULT_TRACK); int af_sample_format; afGetVirtualSampleFormat(fh, AF_DEFAULT_TRACK, &af_sample_format, reinterpret_cast(&bits)); if (static_cast(bits) < 0) bits = 0; Kwave::SampleFormat::Format fmt; switch (af_sample_format) { case AF_SAMPFMT_TWOSCOMP: fmt = Kwave::SampleFormat::Signed; break; case AF_SAMPFMT_UNSIGNED: fmt = Kwave::SampleFormat::Unsigned; break; case AF_SAMPFMT_FLOAT: fmt = Kwave::SampleFormat::Float; break; case AF_SAMPFMT_DOUBLE: fmt = Kwave::SampleFormat::Double; break; default: fmt = Kwave::SampleFormat::Unknown; break; } int af_compression = afGetCompression(fh, AF_DEFAULT_TRACK); const Kwave::Compression compression( Kwave::Compression::fromAudiofile(af_compression) ); info.setRate(rate); info.setBits(bits); info.setTracks(tracks); info.setLength(length); info.set(Kwave::INF_SAMPLE_FORMAT, Kwave::SampleFormat(fmt).toInt()); info.set(Kwave::INF_COMPRESSION, compression.toInt()); // read in all info from the LIST (INFO) chunk Kwave::RIFFChunk *info_chunk = parser.findChunk("/RIFF:WAVE/LIST:INFO"); if (info_chunk) { // found info chunk ! Kwave::RIFFChunkList &list = info_chunk->subChunks(); foreach (Kwave::RIFFChunk *chunk, list) { if (!chunk) continue; if (!m_property_map.containsChunk(chunk->name())) continue; // read the content into a QString Kwave::FileProperty prop = m_property_map.property(chunk->name()); unsigned int ofs = chunk->dataStart(); unsigned int len = chunk->dataLength(); QByteArray buffer(len + 1, 0x00); src.seek(ofs); src.read(buffer.data(), len); buffer[len] = 0; QString value; value = QString::fromUtf8(buffer); info.set(prop, value); } } // read in the Labels (cue list) Kwave::RIFFChunk *cue_chunk = parser.findChunk("/RIFF:WAVE/cue "); if (cue_chunk) { // found a cue list chunk ! quint32 count; src.seek(cue_chunk->dataStart()); src.read(reinterpret_cast(&count), 4); count = qFromLittleEndian(count); // unsigned int length = cue_chunk->dataLength(); // qDebug("cue list found: %u entries, %u bytes (should be: %u)", // count, length, count * (6 * 4) + 4); for (unsigned int i = 0; i < count; i++) { quint32 data, index, position; src.seek(cue_chunk->dataStart() + 4 + ((6 * 4) * i)); /* * typedef struct { * quint32 dwIdentifier; <- index * quint32 dwPosition; <- 0 * quint32 fccChunk; <- 'data' * quint32 dwChunkStart; <- 0 * quint32 dwBlockStart; <- 0 * quint32 dwSampleOffset; <- label.pos() * } cue_list_entry_t; */ /* dwIdentifier */ src.read(reinterpret_cast(&data), 4); index = qFromLittleEndian(data); /* dwPosition (ignored) */ src.read(reinterpret_cast(&data), 4); /* fccChunk */ src.read(reinterpret_cast(&data), 4); /* we currently support only 'data' */ if (qstrncmp(reinterpret_cast(&data), "data", 4)) { qWarning("cue list entry %d refers to '%s', " "which is not supported -> skipped", index, QByteArray( reinterpret_cast(&data), 4).data()); continue; } src.read(reinterpret_cast(&data), 4); /* dwChunkStart (must be 0) */ if (data != 0) { qWarning("cue list entry %d has dwChunkStart != 0 -> skipped", index); continue; } src.read(reinterpret_cast(&data), 4); /* dwBlockStart (must be 0) */ if (data != 0) { qWarning("cue list entry %d has dwBlockStart != 0 -> skipped", index); continue; } src.read(reinterpret_cast(&data), 4); /* dwSampleOffset */ position = qFromLittleEndian(data); // as we now have index and position, find out the name QByteArray name = ""; Kwave::RIFFChunk *adtl_chunk = parser.findChunk("/RIFF:WAVE/LIST:adtl"); if (adtl_chunk) { Kwave::RIFFChunk *labl_chunk = 0; bool found = false; QListIterator it(adtl_chunk->subChunks()); while (it.hasNext()) { quint32 labl_index; labl_chunk = it.next(); /* * typedef struct { * quint32 dwChunkID; <- 'labl' * quint32 dwChunkSize; (without padding !) * quint32 dwIdentifier; <- index * char dwText[]; <- label->name() * } label_list_entry_t; */ if (labl_chunk->name() != "labl") continue; data = 0; src.seek(labl_chunk->dataStart()); src.read(reinterpret_cast(&data), 4); /* dwIdentifier */ labl_index = qFromLittleEndian(data); if (labl_index == index) { found = true; break; /* found it! */ } } if (found) { Q_ASSERT(labl_chunk); unsigned int len = labl_chunk->length(); if (len > 4) { len -= 4; name.resize(len); src.seek(labl_chunk->dataStart() + 4); src.read(static_cast(name.data()), len); if (name[name.count() - 1] != '\0') name += '\0'; } } } if (!name.length()) { // qDebug("cue list entry %d has no name", index); name = ""; } // put a new label into the list QString str = QString::fromUtf8(name); labels.append(Kwave::Label(position, str)); } } labels.sort(); metaData().replace(Kwave::MetaDataList(info)); metaData().replace(labels.toMetaDataList()); // set up libaudiofile to produce Kwave's internal sample format #if Q_BYTE_ORDER == Q_BIG_ENDIAN afSetVirtualByteOrder(fh, AF_DEFAULT_TRACK, AF_BYTEORDER_BIGENDIAN); #else afSetVirtualByteOrder(fh, AF_DEFAULT_TRACK, AF_BYTEORDER_LITTLEENDIAN); #endif afSetVirtualSampleFormat(fh, AF_DEFAULT_TRACK, AF_SAMPFMT_TWOSCOMP, SAMPLE_STORAGE_BITS); return true; } //*************************************************************************** bool Kwave::WavDecoder::decode(QWidget */*widget*/, Kwave::MultiWriter &dst) { Q_ASSERT(m_src_adapter); Q_ASSERT(m_source); if (!m_source) return false; if (!m_src_adapter) return false; AFfilehandle fh = m_src_adapter->handle(); Q_ASSERT(fh); if (!fh) return false; // info().dump(); const unsigned int tracks = dst.tracks(); // allocate an array of Writers, for speeding up QVectorwriters(tracks); Q_ASSERT(writers.count() == Kwave::toInt(dst.tracks())); if (writers.count() != Kwave::toInt(dst.tracks())) return false; for (unsigned int t = 0; t < tracks; t++) writers[t] = dst[t]; Kwave::Writer **writer_fast = writers.data(); unsigned int frame_size = Kwave::toUint( afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, 1)); // allocate a buffer for input data const unsigned int buffer_frames = (8 * 1024); sample_storage_t *buffer = static_cast( malloc(buffer_frames * frame_size)); Q_ASSERT(buffer); if (!buffer) return false; // read in from the audiofile source Q_ASSERT(tracks == Kwave::FileInfo(metaData()).tracks()); sample_index_t rest = Kwave::FileInfo(metaData()).length(); while (rest) { unsigned int frames = buffer_frames; if (frames > rest) frames = Kwave::toUint(rest); unsigned int buffer_used = afReadFrames(fh, AF_DEFAULT_TRACK, reinterpret_cast(buffer), frames); // break if eof reached if (!buffer_used) break; rest -= buffer_used; // split into the tracks sample_storage_t *p = buffer; for (unsigned int count = buffer_used; count; count--) { for (unsigned int track = 0; track < tracks; track++) { sample_storage_t s = *p++; // adjust precision if (SAMPLE_STORAGE_BITS != SAMPLE_BITS) { s /= (1 << (SAMPLE_STORAGE_BITS - SAMPLE_BITS)); } // the following cast is only necessary if // sample_t is not equal to a sample_storage_t Q_ASSERT(writer_fast[track]); *(writer_fast[track]) << static_cast(s); } } // abort if the user pressed cancel if (dst.isCanceled()) break; } // return with a valid Signal, even if the user pressed cancel ! if (buffer) free(buffer); return true; } //*************************************************************************** bool Kwave::WavDecoder::repairChunk( QList *repair_list, Kwave::RIFFChunk *chunk, quint32 &offset) { Q_ASSERT(chunk); Q_ASSERT(m_source); Q_ASSERT(repair_list); if (!chunk) return false; if (!m_source) return false; if (!repair_list) return false; char buffer[16]; quint32 length; Kwave::RecoverySource *repair = 0; // create buffer with header strncpy(buffer, chunk->name().data(), 4); length = (chunk->type() == Kwave::RIFFChunk::Main) ? chunk->physLength() : chunk->dataLength(); buffer[4] = (length ) & 0xFF; buffer[5] = (length >> 8) & 0xFF; buffer[6] = (length >> 16) & 0xFF; buffer[7] = (length >> 24) & 0xFF; if (chunk->type() == Kwave::RIFFChunk::Main) { strncpy(&(buffer[8]), chunk->format().data(), 4); repair = new Kwave::RecoveryBuffer(offset, 12, buffer); qDebug("[0x%08X-0x%08X] - main header '%s' (%s), len=%u", offset, offset+11, chunk->name().data(), chunk->format().data(), length); offset += 12; } else { repair = new Kwave::RecoveryBuffer(offset, 8, buffer); qDebug("[0x%08X-0x%08X] - sub header '%s', len=%u", offset, offset+7, chunk->name().data(), length); offset += 8; } Q_ASSERT(repair); if (!repair) return false; repair_list->append(repair); // map the chunk's data if not main or root if ((chunk->type() != Kwave::RIFFChunk::Root) && (chunk->type() != Kwave::RIFFChunk::Main)) { repair = new Kwave::RecoveryMapping(offset, chunk->physLength(), *m_source, chunk->dataStart()); qDebug("[0x%08X-0x%08X] - restoring from offset 0x%08X (%u)", offset, offset+chunk->physLength()-1, chunk->dataStart(), chunk->physLength()); Q_ASSERT(repair); if (!repair) return false; repair_list->append(repair); offset += chunk->physLength(); } // recursively go over all sub-chunks Kwave::RIFFChunkList &list = chunk->subChunks(); foreach (Kwave::RIFFChunk *c, list) { if (!c) continue; if (!repairChunk(repair_list, c, offset)) return false; } return true; } //*************************************************************************** bool Kwave::WavDecoder::repair(QList *repair_list, Kwave::RIFFChunk *riff_chunk, Kwave::RIFFChunk *fmt_chunk, Kwave::RIFFChunk *data_chunk) { Q_ASSERT(fmt_chunk); Q_ASSERT(data_chunk); if (!fmt_chunk || !data_chunk) return false; // --- first create a chunk tree for the structure --- // make a new "RIFF" chunk as root Kwave::RIFFChunk new_root(0, "RIFF", "WAVE", 0,0,0); new_root.setType(Kwave::RIFFChunk::Main); // create a new "fmt " chunk Kwave::RIFFChunk *new_fmt = new Kwave::RIFFChunk(&new_root, "fmt ",0,0, fmt_chunk->physStart(), fmt_chunk->physLength()); Q_ASSERT(new_fmt); if (!new_fmt) return false; new_root.subChunks().append(new_fmt); // create a new "data" chunk Kwave::RIFFChunk *new_data = new Kwave::RIFFChunk(&new_root, "data",0,0, data_chunk->physStart(), data_chunk->physLength()); Q_ASSERT(new_data); if (!new_data) return false; new_root.subChunks().append(new_data); // if we have a RIFF chunk, add it's subchunks, except "fmt " and "data" // we keep only pointers here, just for getting the right structure, // sizes and start positions. // NOTE: The sizes might be re-assigned and get invalid afterwards!!! if (riff_chunk) { Kwave::RIFFChunkList &list = riff_chunk->subChunks(); foreach (Kwave::RIFFChunk *chunk, list) { if (!chunk) continue; if (chunk->name() == "fmt ") continue; if (chunk->name() == "data") continue; if (chunk->name() == "RIFF") continue; if (chunk->type() == Kwave::RIFFChunk::Empty) continue; if (chunk->type() == Kwave::RIFFChunk::Garbage) continue; new_root.subChunks().append(chunk); } } // fix all node sizes (compress) new_root.fixSize(); // attention: some of the offsets belong to source file, some belong // to reconstructed buffers! only the sizes are correct. // new_root.dumpStructure(); // --- set up the repair list --- // RIFF chunk length quint32 offset = 0; bool repaired = repairChunk(repair_list, &new_root, offset); // clean up... new_root.subChunks().clear(); delete new_fmt; delete new_data; return (repaired); } //*************************************************************************** void Kwave::WavDecoder::close() { if (m_src_adapter) delete m_src_adapter; m_src_adapter = 0; m_source = 0; } //*************************************************************************** //*************************************************************************** diff --git a/plugins/codec_wav/WavEncoder.cpp b/plugins/codec_wav/WavEncoder.cpp index 66ee61ae..0357b762 100644 --- a/plugins/codec_wav/WavEncoder.cpp +++ b/plugins/codec_wav/WavEncoder.cpp @@ -1,550 +1,549 @@ /************************************************************************* WavEncoder.cpp - encoder for wav data ------------------- begin : Sun Mar 10 2002 copyright : (C) 2002 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include -#include #include #include #include "libkwave/Compression.h" #include "libkwave/FileInfo.h" #include "libkwave/LabelList.h" #include "libkwave/MessageBox.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/Sample.h" #include "libkwave/SampleFormat.h" #include "libkwave/SampleReader.h" #include "libkwave/Utils.h" #include "libkwave/VirtualAudioFile.h" #include "WavEncoder.h" #include "WavFileFormat.h" /***************************************************************************/ Kwave::WavEncoder::WavEncoder() :Kwave::Encoder(), m_property_map() { REGISTER_MIME_TYPES REGISTER_COMPRESSION_TYPES } /***************************************************************************/ Kwave::WavEncoder::~WavEncoder() { } /***************************************************************************/ Kwave::Encoder *Kwave::WavEncoder::instance() { return new Kwave::WavEncoder(); } /***************************************************************************/ QList Kwave::WavEncoder::supportedProperties() { return m_property_map.properties(); } /***************************************************************************/ void Kwave::WavEncoder::fixAudiofileBrokenHeaderBug(QIODevice &dst, Kwave::FileInfo &info, unsigned int frame_size) { const unsigned int length = Kwave::toUint(info.length()); quint32 correct_size = length * frame_size; const int compression = info.contains(Kwave::INF_COMPRESSION) ? info.get(Kwave::INF_COMPRESSION).toInt() : Kwave::Compression::NONE; if (compression != Kwave::Compression::NONE) { qWarning("WARNING: libaudiofile might have produced a broken header!"); return; } // just to be sure: at offset 36 we expect the chunk name "data" dst.seek(36); char chunk_name[5]; memset(chunk_name, 0x00, sizeof(chunk_name)); dst.read(&chunk_name[0], 4); if (strncmp("data", chunk_name, sizeof(chunk_name))) { qWarning("WARNING: unexpected wav header format, check disabled"); return; } // read the data chunk size that libaudiofile has written quint32 data_size; dst.seek(40); dst.read(reinterpret_cast(&data_size), 4); data_size = qFromLittleEndian(data_size); if (data_size == length * frame_size) { // qDebug("(data size written by libaudiofile is correct)"); return; } qWarning("WARNING: libaudiofile wrote a wrong 'data' chunk size!"); qWarning(" current=%u, correct=%u", data_size, correct_size); // write the fixed size of the "data" chunk dst.seek(40); data_size = qToLittleEndian(correct_size); dst.write(reinterpret_cast(&data_size), 4); // also fix the "RIFF" size dst.seek(4); quint32 riff_size = static_cast(dst.size()) - 4 - 4; riff_size = qToLittleEndian(riff_size); dst.write(reinterpret_cast(&riff_size), 4); } /***************************************************************************/ void Kwave::WavEncoder::writeInfoChunk(QIODevice &dst, Kwave::FileInfo &info) { // create a list of chunk names and properties for the INFO chunk QMap properties(info.properties()); QMap info_chunks; unsigned int info_size = 0; for (QMap::Iterator it = properties.begin(); it != properties.end(); ++it) { Kwave::FileProperty property = it.key(); if (!m_property_map.containsProperty(property)) continue; QByteArray chunk_id = m_property_map.findProperty(property); if (info_chunks.contains(chunk_id)) continue; // already encoded QByteArray value = QVariant(properties[property]).toString().toUtf8(); info_chunks.insert(chunk_id, value); info_size += 4 + 4 + value.length(); if (value.length() & 0x01) info_size++; } // if there are properties to save, create a LIST chunk if (!info_chunks.isEmpty()) { quint32 size; // enlarge the main RIFF chunk by the size of the LIST chunk info_size += 4 + 4 + 4; // add the size of LIST(INFO) dst.seek(4); dst.read(reinterpret_cast(&size), 4); size = qToLittleEndian( qFromLittleEndian(size) + info_size); dst.seek(4); dst.write(reinterpret_cast(&size), 4); // add the LIST(INFO) chunk itself dst.seek(dst.size()); if (dst.pos() & 1) dst.write("\000", 1); // padding dst.write("LIST", 4); size = qToLittleEndian(info_size - 8); dst.write(reinterpret_cast(&size), 4); dst.write("INFO", 4); // append the chunks to the end of the file for (QMap::Iterator it = info_chunks.begin(); it != info_chunks.end(); ++it) { QByteArray name = it.key(); QByteArray value = it.value(); dst.write(name.data(), 4); // chunk name size = value.length(); // length of the chunk if (size & 0x01) size++; size = qToLittleEndian(size); dst.write(reinterpret_cast(&size), 4); dst.write(value.data(), value.length()); if (value.length() & 0x01) { const char zero = 0; dst.write(&zero, 1); } } } } /***************************************************************************/ void Kwave::WavEncoder::writeLabels(QIODevice &dst, const Kwave::LabelList &labels) { const unsigned int labels_count = labels.count(); quint32 size, additional_size = 0, index, data; // shortcut: nothing to do if no labels present if (!labels_count) return; // easy things first: size of the cue list (has fixed record size) // without chunk name and chunk size const unsigned int size_of_cue_list = 4 + /* number of entries */ labels_count * (6 * 4); /* cue list entry: 6 x 32 bit */ // now the size of the labels unsigned int size_of_labels = 0; foreach (const Kwave::Label &label, labels) { if (label.isNull()) continue; unsigned int name_len = label.name().toUtf8().size(); if (!name_len) continue; // skip zero-length names size_of_labels += (3 * 4); // 3 * 4 byte size_of_labels += name_len; // padding if size is unaligned if (size_of_labels & 1) size_of_labels++; } if (size_of_labels) { size_of_labels += 4; /* header entry: 'adtl' */ // enlarge the main RIFF chunk by the size of the LIST chunk additional_size += 4 + 4 + size_of_labels; // add size of LIST(adtl) } // enlarge the main RIFF chunk by the size of the cue chunks additional_size += 4 + 4 + size_of_cue_list; // add size of 'cue ' dst.seek(4); dst.read(reinterpret_cast(&size), 4); size = qToLittleEndian( qFromLittleEndian(size) + additional_size); dst.seek(4); dst.write(reinterpret_cast(&size), 4); // seek to the end of the file dst.seek(dst.size()); if (dst.pos() & 1) dst.write("\000", 1); // padding // add the 'cue ' list dst.write("cue ", 4); size = qToLittleEndian(size_of_cue_list); dst.write(reinterpret_cast(&size), 4); // number of entries size = qToLittleEndian(labels_count); dst.write(reinterpret_cast(&size), 4); index = 0; foreach (const Kwave::Label &label, labels) { if (label.isNull()) continue; /* * typedef struct { * quint32 dwIdentifier; <- index * quint32 dwPosition; <- 0 * quint32 fccChunk; <- 'data' * quint32 dwChunkStart; <- 0 * quint32 dwBlockStart; <- 0 * quint32 dwSampleOffset; <- label.pos() * } cue_list_entry_t; */ data = qToLittleEndian(index); dst.write(reinterpret_cast(&data), 4); // dwIdentifier data = 0; dst.write(reinterpret_cast(&data), 4); // dwPosition dst.write("data", 4); // fccChunk dst.write(reinterpret_cast(&data), 4); // dwChunkStart dst.write(reinterpret_cast(&data), 4); // dwBlockStart data = qToLittleEndian(Kwave::toUint(label.pos())); dst.write(reinterpret_cast(&data), 4); // dwSampleOffset index++; } // add the LIST(adtl) chunk if (size_of_labels) { dst.write("LIST", 4); size = qToLittleEndian(size_of_labels); dst.write(reinterpret_cast(&size), 4); dst.write("adtl", 4); index = 0; foreach (const Kwave::Label &label, labels) { if (label.isNull()) continue; QByteArray name = label.name().toUtf8(); /* * typedef struct { * quint32 dwChunkID; <- 'labl' * quint32 dwChunkSize; (without padding !) * quint32 dwIdentifier; <- index * char dwText[]; <- label->name() * } label_list_entry_t; */ if (name.size()) { dst.write("labl", 4); // dwChunkID data = qToLittleEndian(name.size() + 4); // dwChunkSize dst.write(reinterpret_cast(&data), 4); data = qToLittleEndian(index); // dwIdentifier dst.write(reinterpret_cast(&data), 4); dst.write(name.data(), name.size()); // dwText if (name.size() & 1) { // padding if necessary data = 0; dst.write(reinterpret_cast(&data), 1); } } index++; } } } /***************************************************************************/ bool Kwave::WavEncoder::encode(QWidget *widget, Kwave::MultiTrackReader &src, QIODevice &dst, const Kwave::MetaDataList &meta_data) { Kwave::FileInfo info(meta_data); /* first get and check some header information */ const unsigned int tracks = info.tracks(); const sample_index_t length = info.length(); unsigned int bits = info.bits(); const double rate = info.rate(); Kwave::SampleFormat format(Kwave::SampleFormat::Signed); if (info.contains(Kwave::INF_SAMPLE_FORMAT)) format.fromInt(info.get(Kwave::INF_SAMPLE_FORMAT).toInt()); Kwave::Compression::Type compression = info.contains(Kwave::INF_COMPRESSION) ? Kwave::Compression::fromInt(info.get(Kwave::INF_COMPRESSION).toInt()) : Kwave::Compression::NONE; // use default bit resolution if missing Q_ASSERT(bits); if (!bits) bits = 16; // check for a valid source if ((!tracks) || (!length)) return false; Q_ASSERT(src.tracks() == tracks); if (src.tracks() != tracks) return false; // check if the chosen compression mode is supported for saving if ((compression != Kwave::Compression::NONE) && (compression != Kwave::Compression::G711_ULAW) && (compression != Kwave::Compression::G711_ALAW)) { qWarning("compression mode %d not supported!", Kwave::Compression(compression).toInt()); int what_now = Kwave::MessageBox::warningYesNoCancel(widget, i18n("Sorry, the currently selected compression type cannot " "be used for saving. Do you want to use " "G711 ULAW compression instead?"), QString(), i18n("&Yes, use G711"), i18n("&No, store uncompressed") ); switch (what_now) { case (KMessageBox::Yes): info.set(Kwave::INF_COMPRESSION, Kwave::Compression::G711_ULAW); compression = Kwave::Compression::G711_ULAW; break; case (KMessageBox::No): info.set(Kwave::INF_COMPRESSION, Kwave::Compression::NONE); compression = Kwave::Compression::NONE; break; default: return false; // bye bye, save later... } } // check for unsupported compression/bits/sample format combinations // G.711 and MSADPCM support only 16 bit signed as input format! if ((compression == Kwave::Compression::G711_ULAW) || (compression == Kwave::Compression::G711_ALAW)) { if ((format != Kwave::SampleFormat::Signed) || (bits != 16)) { format.assign(Kwave::SampleFormat::Signed); bits = 16; info.set(Kwave::INF_SAMPLE_FORMAT, QVariant(format.toInt())); info.setBits(16); qDebug("auto-switching to 16 bit signed format"); } } else if ((bits <= 8) && (format != Kwave::SampleFormat::Unsigned)) { format.assign(Kwave::SampleFormat::Unsigned); info.set(Kwave::INF_SAMPLE_FORMAT, QVariant(format.toInt())); qDebug("auto-switching to unsigned format"); } else if ((bits > 8) && (format != Kwave::SampleFormat::Signed)) { format.assign(Kwave::SampleFormat::Signed); info.set(Kwave::INF_SAMPLE_FORMAT, QVariant(format.toInt())); qDebug("auto-switching to signed format"); } // open the output device if (!dst.open(QIODevice::ReadWrite | QIODevice::Truncate)) { Kwave::MessageBox::error(widget, i18n("Unable to open the file for saving!")); return false; } // check for proper size: WAV supports only 32bit addressing if (length * ((bits + 7) / 8) >= UINT_MAX) { Kwave::MessageBox::error(widget, i18n("File or selection too large")); return false; } int af_sample_format = AF_SAMPFMT_TWOSCOMP; Kwave::SampleFormat fmt(format); switch (fmt) { case Kwave::SampleFormat::Unsigned: af_sample_format = AF_SAMPFMT_UNSIGNED; break; case Kwave::SampleFormat::Float: af_sample_format = AF_SAMPFMT_FLOAT; break; case Kwave::SampleFormat::Double: af_sample_format = AF_SAMPFMT_DOUBLE; break; case Kwave::SampleFormat::Signed: /* FALLTHROUGH */ default: af_sample_format = AF_SAMPFMT_TWOSCOMP; break; } AFfilesetup setup; setup = afNewFileSetup(); afInitFileFormat(setup, AF_FILE_WAVE); afInitChannels(setup, AF_DEFAULT_TRACK, tracks); afInitSampleFormat(setup, AF_DEFAULT_TRACK, af_sample_format, bits); afInitCompression(setup, AF_DEFAULT_TRACK, Kwave::Compression::toAudiofile(compression)); afInitRate(setup, AF_DEFAULT_TRACK, rate); Kwave::VirtualAudioFile outfile(dst); outfile.open(&outfile, setup); AFfilehandle fh = outfile.handle(); if (!fh || (outfile.lastError() >= 0)) { QString reason; switch (outfile.lastError()) { case AF_BAD_NOT_IMPLEMENTED: reason = i18n("Format or function is not implemented") /*+ "\n("+format_name+")"*/; break; case AF_BAD_MALLOC: reason = i18n("Out of memory"); break; case AF_BAD_HEADER: reason = i18n("File header is damaged"); break; case AF_BAD_CODEC_TYPE: reason = i18n("Invalid codec type")/* + "\n("+format_name+")"*/; break; case AF_BAD_OPEN: reason = i18n("Opening the file failed"); break; case AF_BAD_READ: reason = i18n("Read access failed"); break; case AF_BAD_SAMPFMT: reason = i18n("Invalid sample format"); break; default: reason = reason.number(outfile.lastError()); } QString text= i18n("An error occurred while opening the "\ "file:\n'%1'", reason); Kwave::MessageBox::error(widget, text); return false; } // set up libaudiofile to produce Kwave's internal sample format #if Q_BYTE_ORDER == Q_BIG_ENDIAN afSetVirtualByteOrder(fh, AF_DEFAULT_TRACK, AF_BYTEORDER_BIGENDIAN); #else afSetVirtualByteOrder(fh, AF_DEFAULT_TRACK, AF_BYTEORDER_LITTLEENDIAN); #endif afSetVirtualSampleFormat(fh, AF_DEFAULT_TRACK, AF_SAMPFMT_TWOSCOMP, SAMPLE_STORAGE_BITS); // allocate a buffer for input data const unsigned int virtual_frame_size = Kwave::toUint( afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, 1)); const unsigned int buffer_frames = (8 * 1024); sample_storage_t *buffer = static_cast( malloc(buffer_frames * virtual_frame_size)); if (!buffer) return false; // read in from the sample readers sample_index_t rest = length; while (rest) { // merge the tracks into the sample buffer sample_storage_t *p = buffer; unsigned int count = buffer_frames; if (rest < count) count = Kwave::toUint(rest); for (unsigned int pos = 0; pos < count; pos++) { for (unsigned int track = 0; track < tracks; track++) { Kwave::SampleReader *stream = src[track]; sample_t sample = 0; if (!stream->eof()) (*stream) >> sample; // the following cast is only necessary if // sample_t is not equal to sample_storage_t sample_storage_t act = static_cast(sample); act *= (1 << (SAMPLE_STORAGE_BITS - SAMPLE_BITS)); *p = act; p++; } } // write out through libaudiofile count = afWriteFrames(fh, AF_DEFAULT_TRACK, buffer, count); // break if eof reached or disk full Q_ASSERT(count); if (!count) break; Q_ASSERT(rest >= count); rest -= count; // abort if the user pressed cancel // --> this would leave a corrupted file !!! if (src.isCanceled()) break; } // close the audiofile stuff, we need control over the // fixed-up file on our own outfile.close(); // clean up the sample buffer free(buffer); afFreeFileSetup(setup); // due to a buggy implementation of libaudiofile // we have to fix up the length of the "data" and the "RIFF" chunk fixAudiofileBrokenHeaderBug(dst, info, (bits * tracks) >> 3); // put the properties into the INFO chunk writeInfoChunk(dst, info); // write the labels list writeLabels(dst, Kwave::LabelList(meta_data)); return true; } /***************************************************************************/ /***************************************************************************/ diff --git a/plugins/fileinfo/FileInfoDialog.cpp b/plugins/fileinfo/FileInfoDialog.cpp index a91dd36c..83bf6713 100644 --- a/plugins/fileinfo/FileInfoDialog.cpp +++ b/plugins/fileinfo/FileInfoDialog.cpp @@ -1,1128 +1,1127 @@ /*************************************************************************** FileInfoDialog.cpp - dialog for editing file properties ------------------- begin : Sat Jul 20 2002 copyright : (C) 2002 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include "libkwave/CodecManager.h" #include "libkwave/Compression.h" #include "libkwave/Encoder.h" #include "libkwave/FileInfo.h" #include "libkwave/GenreType.h" #include "libkwave/SampleFormat.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "BitrateWidget.h" #include "CompressionWidget.h" #include "FileInfoDialog.h" #include "KeywordWidget.h" #include "SelectDateDialog.h" /** section in the config file for storing default settings */ #define CONFIG_DEFAULT_SECTION "plugin fileinfo - setup dialog" //*************************************************************************** Kwave::FileInfoDialog::FileInfoDialog(QWidget *parent, Kwave::FileInfo &info) :QDialog(parent), Ui::FileInfoDlg(), m_info(info) { setupUi(this); connect(cbCompression, SIGNAL(currentIndexChanged(int)), this, SLOT(compressionChanged())); connect(cbMpegLayer, SIGNAL(currentIndexChanged(int)), this, SLOT(mpegLayerChanged())); connect(chkMpegCopyrighted, SIGNAL(clicked(bool)), this, SLOT(mpegCopyrightedChanged(bool))); connect(chkMpegOriginal, SIGNAL(clicked(bool)), this, SLOT(mpegOriginalChanged(bool))); connect(btHelp->button(QDialogButtonBox::Help), SIGNAL(clicked()), this, SLOT(invokeHelp())); // open config for reading default settings KConfigGroup cfg = KSharedConfig::openConfig()->group(CONFIG_DEFAULT_SECTION); setupFileInfoTab(); setupCompressionTab(cfg); setupMpegTab(); setupContentTab(); setupSourceTab(); setupAuthorCopyrightTab(); setupMiscellaneousTab(); // set the focus onto the "OK" button buttonBox->button(QDialogButtonBox::Ok)->setFocus(); } //*************************************************************************** Kwave::FileInfoDialog::~FileInfoDialog() { } //*************************************************************************** void Kwave::FileInfoDialog::describeWidget(QWidget *widget, const QString &name, const QString &description) { if (!widget) return; widget->setToolTip(description); widget->setWhatsThis(_("") + name + _("
") + description); } //*************************************************************************** void Kwave::FileInfoDialog::initInfo(QLabel *label, QWidget *widget, Kwave::FileProperty property) { if (label) label->setText(i18n(UTF8(m_info.name(property))) + _(":")); if (widget) describeWidget(widget, i18n(UTF8(m_info.name(property))), i18n(UTF8(m_info.description(property)))); } //*************************************************************************** void Kwave::FileInfoDialog::initInfoText(QLabel *label, QLineEdit *edit, Kwave::FileProperty property) { initInfo(label, edit, property); if (edit) edit->setText(QVariant(m_info.get(property)).toString()); } //*************************************************************************** void Kwave::FileInfoDialog::setupFileInfoTab() { /* filename */ initInfo(lblFileName, edFileName, Kwave::INF_FILENAME); QFileInfo fi(QVariant(m_info.get(Kwave::INF_FILENAME)).toString()); QString file_name = fi.fileName(); edFileName->setText(file_name); edFileName->setEnabled(file_name.length() != 0); /* mime type */ QString mimetype = QVariant(m_info.get(Kwave::INF_MIMETYPE)).toString(); if (!mimetype.length()) mimetype = _("audio/x-wav"); // default mimetype qDebug("mimetype = %s", DBG(mimetype)); /* * Check if the file name, mime type and compression match. If not, * we might be in the "SaveAs" mode and the compression belongs to * the old file name */ if (file_name.length()) { QString mt = Kwave::CodecManager::mimeTypeOf(QUrl(file_name)); Kwave::Encoder *encoder = Kwave::CodecManager::encoder(mt); if (encoder) { // encoder does not support the file's mime type -> switch if (!encoder->supports(mt)) { qDebug("switching mime type to '%s'", DBG(mt)); m_info.set(Kwave::INF_MIMETYPE, mt); mimetype = mt; } // encoder does not support compression -> switch QList comps = encoder->compressionTypes(); Kwave::Compression::Type comp = Kwave::Compression::fromInt( QVariant(m_info.get(Kwave::INF_COMPRESSION)).toInt() ); if (!comps.contains(comp)) { Kwave::Compression comp_old(comp); Kwave::Compression comp_new(!comps.isEmpty() ? comps.last() : Kwave::Compression::NONE ); qDebug("compression '%s' not supported: switch to '%s'", DBG(comp_old.name()), DBG(comp_new.name())); m_info.set(Kwave::INF_COMPRESSION, Kwave::Compression(comp).toInt()); } // mime type does not match compression -> switch QList comps_found; foreach (Kwave::Compression::Type c, comps) { Kwave::Compression cmp(c); if ((cmp.preferredMimeType() == mimetype) && comps.contains(c)) { comps_found.append(c); break; } } if (!comps_found.isEmpty() && !comps_found.contains(comp)) { Kwave::Compression::Type cn = (comps_found.isEmpty() && !comps.isEmpty()) ? comps.last() : comps_found.first(); Kwave::Compression comp_old(comp); Kwave::Compression comp_new(cn); qDebug("mime type/compression mismatch: " "switch from '%s' to '%s'", DBG(comp_old.name()), DBG(comp_new.name())); m_info.set(Kwave::INF_COMPRESSION, comp_new.toInt()); } } } edFileFormat->setText(mimetype); /* file size in bytes */ initInfo(lblFileSize, edFileSize, Kwave::INF_FILESIZE); if (m_info.contains(Kwave::INF_FILESIZE)) { unsigned int size = QVariant(m_info.get(Kwave::INF_FILESIZE)).toUInt(); QString dotted = QLocale().toString(size); if (size < 10*1024) { edFileSize->setText(i18n("%1 bytes", dotted)); } else if (size < 10*1024*1024) { edFileSize->setText(i18n("%1 kB (%2 byte)", (size / 1024), dotted)); } else { edFileSize->setText(i18n("%1 MB (%2 byte)", (size / (1024*1024)), dotted)); } } else { edFileSize->setEnabled(false); } /* file format (from mime type) */ // use mimetype instead initInfoText(lblFileFormat, edFileFormat, Kwave::INF_MIMETYPE); /* sample rate */ lblSampleRate->setText(i18n("Sample rate:")); describeWidget(cbSampleRate, lblSampleRate->text().left( lblSampleRate->text().length() - 1), i18n("Here you can select one of the predefined\n" "well-known sample rates or you can enter\n" "any sample rate on your own.")); cbSampleRate->setEditText(QString::number(m_info.rate())); /* bits per sample */ lblResolution->setText(i18n("Resolution:")); describeWidget(sbResolution, lblResolution->text().left( lblResolution->text().length() - 1), i18n("Select a resolution in bits in which the file\n" "will be saved.")); sbResolution->setValue(m_info.bits()); /* number of tracks */ lblChannels->setText(i18n("Tracks:")); describeWidget(sbChannels, lblChannels->text().left( lblChannels->text().length() - 1), i18n("Shows the number of tracks of the signal.\n" "You can add or delete tracks via the Edit menu.")); sbChannels->setMaximum(m_info.tracks()); sbChannels->setMinimum(m_info.tracks()); sbChannels->setValue(m_info.tracks()); connect(sbChannels, SIGNAL(valueChanged(int)), this, SLOT(tracksChanged(int))); tracksChanged(sbChannels->value()); /* length of the signal */ lblLength->setText(i18n("Length:")); describeWidget(txtLength, lblLength->text().left( lblLength->text().length() - 1), i18n("Shows the length of the file in samples\n" "and if possible as time.")); sample_index_t samples = m_info.length(); double rate = m_info.rate(); if (!qFuzzyIsNull(rate)) { double ms = static_cast(samples) * 1E3 / rate; txtLength->setText(i18n("%1 (%2 samples)", Kwave::ms2string(ms), Kwave::samples2string(samples))); } else { txtLength->setText(i18n("%1 samples", Kwave::samples2string(samples))); } /* sample format */ initInfo(lblSampleFormat, cbSampleFormat, Kwave::INF_SAMPLE_FORMAT); cbSampleFormat->clear(); Kwave::SampleFormat::Map sf; const QList formats = sf.keys(); foreach (const int &k, formats) { cbSampleFormat->addItem( sf.description(k, true), QVariant(Kwave::SampleFormat(sf.data(k)).toInt()) ); } int format = QVariant(m_info.get(Kwave::INF_SAMPLE_FORMAT)).toInt(); if (format == 0) format = Kwave::SampleFormat::Signed; // default = signed int cbSampleFormat->setCurrentIndex(cbSampleFormat->findData(format)); } //*************************************************************************** void Kwave::FileInfoDialog::setupCompressionTab(KConfigGroup &cfg) { /* * mime type @ file info -> available compressions * compression @ file info / available compressions -> selected compression * selected compression -> mime type (edit field) * mime type -> mpeg/ogg/flac mode * mgeg layer -> compression */ /* compression */ updateAvailableCompressions(); initInfo(lblCompression, cbCompression, Kwave::INF_COMPRESSION); compressionWidget->init(m_info); compressionWidget->setMode(m_info.contains(Kwave::INF_VBR_QUALITY) ? Kwave::CompressionWidget::VBR_MODE : Kwave::CompressionWidget::ABR_MODE); // ABR bitrate settings int abr_bitrate = m_info.contains(Kwave::INF_BITRATE_NOMINAL) ? QVariant(m_info.get(Kwave::INF_BITRATE_NOMINAL)).toInt() : cfg.readEntry("default_abr_nominal_bitrate", -1); int min_bitrate = m_info.contains(Kwave::INF_BITRATE_LOWER) ? QVariant(m_info.get(Kwave::INF_BITRATE_LOWER)).toInt() : cfg.readEntry("default_abr_lower_bitrate",-1); int max_bitrate = m_info.contains(Kwave::INF_BITRATE_UPPER) ? QVariant(m_info.get(Kwave::INF_BITRATE_UPPER)).toInt() : cfg.readEntry("default_abr_upper_bitrate",-1); compressionWidget->setBitrates(abr_bitrate, min_bitrate, max_bitrate); // VBR base quality int quality = m_info.contains(Kwave::INF_VBR_QUALITY) ? QVariant(m_info.get(Kwave::INF_VBR_QUALITY)).toInt() : cfg.readEntry("default_vbr_quality", -1); compressionWidget->setQuality(quality); compressionChanged(); // // this is not visible, not implemented yet... // InfoTab->setCurrentPage(5); // QWidget *page = InfoTab->currentPage(); // InfoTab->removePage(page); // InfoTab->setCurrentPage(0); // return; } //*************************************************************************** void Kwave::FileInfoDialog::setupMpegTab() { // the whole tab is only enabled in mpeg mode InfoTab->setTabEnabled(2, isMpeg()); /* MPEG layer */ initInfo(lblMpegLayer, cbMpegLayer, Kwave::INF_MPEG_LAYER); int layer = m_info.get(Kwave::INF_MPEG_LAYER).toInt(); if ((layer < 1) || (layer > 3)) layer = 3; // default = layer III cbMpegLayer->setCurrentIndex(layer - 1); /* MPEG version */ initInfo(lblMpegVersion, cbMpegVersion, Kwave::INF_MPEG_VERSION); int ver = Kwave::toInt( (2.0 * QVariant(m_info.get(Kwave::INF_MPEG_VERSION)).toDouble())); // 1, 2, 2.5 -> 2, 4, 5 if ((ver < 1) || (ver > 5)) ver = 4; // default = version 2 if (ver > 3) ver++; // 2, 4, 6 ver >>= 1; // 1, 2, 3 ver--; // 0, 1, 2 if ((ver < 0) || (ver > 2)) ver = 1; // default = version 2 cbMpegVersion->setCurrentIndex(ver); /* Mode extension */ initInfo(lblMpegModeExt, cbMpegModeExt, Kwave::INF_MPEG_MODEEXT); // only in "Joint Stereo" mode, then depends on Layer // // Layer I+II | Layer III // | Intensity stereo MS Stereo //-------------------------------------------------- // 0 - bands 4 to 31 | off off -> 4 // 1 - bands 8 to 31 | on off -> 5 // 2 - bands 12 to 31 | off on -> 6 // 3 - bands 16 to 31 | on on -> 7 int modeext = -1; if (m_info.contains(Kwave::INF_MPEG_MODEEXT)) modeext = QVariant(m_info.get(Kwave::INF_MPEG_MODEEXT)).toInt(); if (modeext < 0) { // find some reasonable default if (m_info.tracks() < 2) { // mono -> -1 } else if (layer < 3) { // Layer I or II -> 0 modeext = 0; } else { // Layer III -> 7 modeext = 7; } } if ((modeext >= 0) && (modeext <= 3)) { cbMpegModeExt->setEnabled(true); cbMpegModeExt->setCurrentIndex(modeext); chkMpegIntensityStereo->setEnabled(false); chkMpegMSStereo->setEnabled(false); } else if ((modeext >= 4) && (modeext <= 7)) { cbMpegModeExt->setEnabled(false); cbMpegModeExt->setCurrentIndex(-1); chkMpegIntensityStereo->setEnabled(true); chkMpegIntensityStereo->setChecked(modeext & 0x01); chkMpegMSStereo->setEnabled(true); chkMpegMSStereo->setChecked(modeext & 0x02); } else { cbMpegModeExt->setEnabled(false); cbMpegModeExt->setCurrentIndex(-1); chkMpegIntensityStereo->setEnabled(false); chkMpegMSStereo->setEnabled(false); } /* Emphasis */ initInfo(lblMpegEmphasis, cbMpegEmphasis, Kwave::INF_MPEG_EMPHASIS); int emphasis = QVariant(m_info.get(Kwave::INF_MPEG_EMPHASIS)).toInt(); switch (emphasis) { case 0: cbMpegEmphasis->setCurrentIndex(0); break; case 1: cbMpegEmphasis->setCurrentIndex(1); break; case 3: cbMpegEmphasis->setCurrentIndex(2); break; default: cbMpegEmphasis->setEnabled(false); break; } /* Copyrighted */ initInfo(lblMpegCopyrighted, chkMpegCopyrighted, Kwave::INF_COPYRIGHTED); bool copyrighted = QVariant(m_info.get(Kwave::INF_COPYRIGHTED)).toBool(); chkMpegCopyrighted->setChecked(copyrighted); mpegCopyrightedChanged(copyrighted); /* Original */ initInfo(lblMpegOriginal, chkMpegOriginal, Kwave::INF_ORIGINAL); bool original = QVariant(m_info.get(Kwave::INF_ORIGINAL)).toBool(); chkMpegOriginal->setChecked(original); mpegOriginalChanged(original); mpegLayerChanged(); } //*************************************************************************** void Kwave::FileInfoDialog::setupContentTab() { /* name, subject, version, genre, title, author, organization, copyright, license */ initInfoText(lblName, edName, Kwave::INF_NAME); initInfoText(lblSubject, edSubject, Kwave::INF_SUBJECT); initInfoText(lblVersion, edVersion, Kwave::INF_VERSION); // genre type cbGenre->addItems(Kwave::GenreType::allTypes()); QString genre = m_info.get(Kwave::INF_GENRE).toString(); int genre_id = Kwave::GenreType::id(genre); if (genre_id >= 0) { // well known genre type genre = Kwave::GenreType::name(genre_id, true); } else { // user defined genre type cbGenre->addItem(genre); } initInfo(lblGenre, cbGenre, Kwave::INF_GENRE); cbGenre->setCurrentIndex(cbGenre->findText(genre)); /* date widget */ initInfo(lblDate, dateEdit, Kwave::INF_CREATION_DATE); QDate date; QString date_str = QVariant(m_info.get(Kwave::INF_CREATION_DATE)).toString(); if (m_info.contains(Kwave::INF_CREATION_DATE)) { if (date_str.length()) date = QDate::fromString(date_str, Qt::ISODate); } if (!date.isValid()) { // fall back to "year only" int year = date_str.toInt(); if ((year > 0) && (year <= 9999)) date = QDate(year, 1, 1); } if (!date.isValid()) { // fall back to "now" date = QDate::currentDate(); } dateEdit->setDate(date); connect(btSelectDate, SIGNAL(clicked()), this, SLOT(selectDate())); connect(btSelectDateNow, SIGNAL(clicked()), this, SLOT(setDateNow())); } //*************************************************************************** void Kwave::FileInfoDialog::setupSourceTab() { /* source, source form */ initInfoText(lblSource, edSource, Kwave::INF_SOURCE); initInfoText(lblSourceForm, edSourceForm, Kwave::INF_SOURCE_FORM); /* Album, CD and track */ initInfoText(lblAlbum, edAlbum, Kwave::INF_ALBUM); initInfo(lblCD, sbCD, Kwave::INF_CD); int cd = (m_info.contains(Kwave::INF_CD)) ? QVariant(m_info.get(Kwave::INF_CD)).toInt() : 0; sbCD->setValue(cd); initInfo(0, sbCDs, Kwave::INF_CDS); int cds = (m_info.contains(Kwave::INF_CDS)) ? QVariant(m_info.get(Kwave::INF_CDS)).toInt() : 0; sbCDs->setValue(cds); initInfo(lblTrack, sbTrack, Kwave::INF_TRACK); int track = (m_info.contains(Kwave::INF_TRACK)) ? QVariant(m_info.get(Kwave::INF_TRACK)).toInt() : 0; sbTrack->setValue(track); initInfo(0, sbTracks, Kwave::INF_TRACKS); int tracks = (m_info.contains(Kwave::INF_TRACKS)) ? QVariant(m_info.get(Kwave::INF_TRACKS)).toInt() : 0; sbTracks->setValue(tracks); /* software, engineer, technician */ initInfoText(lblSoftware, edSoftware, Kwave::INF_SOFTWARE); initInfoText(lblEngineer, edEngineer, Kwave::INF_ENGINEER); initInfoText(lblTechnican, edTechnican, Kwave::INF_TECHNICAN); } //*************************************************************************** void Kwave::FileInfoDialog::setupAuthorCopyrightTab() { /* author organization, copyright, license, ISRC */ initInfoText(lblAuthor, edAuthor, Kwave::INF_AUTHOR); initInfoText(lblOrganization, edOrganization, Kwave::INF_ORGANIZATION); initInfoText(lblCopyright, edCopyright, Kwave::INF_COPYRIGHT); initInfoText(lblLicense, edLicense, Kwave::INF_LICENSE); initInfoText(lblISRC, edISRC, Kwave::INF_ISRC); /* product, archival, contact */ initInfoText(lblProduct, edProduct, Kwave::INF_PRODUCT); initInfoText(lblArchival, edArchival, Kwave::INF_ARCHIVAL); initInfoText(lblContact, edContact, Kwave::INF_CONTACT); } //*************************************************************************** void Kwave::FileInfoDialog::setupMiscellaneousTab() { /* commissioned */ initInfoText(lblCommissioned, edCommissioned, Kwave::INF_COMMISSIONED); /* list of keywords */ lblKeywords->setText(i18n(m_info.name(Kwave::INF_KEYWORDS).toLatin1())); lstKeywords->setWhatsThis(_("") + i18n(m_info.name(Kwave::INF_KEYWORDS).toLatin1()) + _("
") + i18n(m_info.description(Kwave::INF_KEYWORDS).toLatin1())); if (m_info.contains(Kwave::INF_KEYWORDS)) { QString keywords = QVariant(m_info.get(Kwave::INF_KEYWORDS)).toString(); lstKeywords->setKeywords(keywords.split(_(";"))); } connect(lstKeywords, SIGNAL(autoGenerate()), this, SLOT(autoGenerateKeywords())); } //*************************************************************************** void Kwave::FileInfoDialog::selectDate() { QDate date(dateEdit->date()); Kwave::SelectDateDialog date_dialog(this, date); if (date_dialog.exec() == QDialog::Accepted) { date = date_dialog.date(); dateEdit->setDate(date); } } //*************************************************************************** void Kwave::FileInfoDialog::setDateNow() { dateEdit->setDate(QDate::currentDate()); } //*************************************************************************** void Kwave::FileInfoDialog::tracksChanged(int tracks) { switch (tracks) { case 1: lblChannelsVerbose->setText(i18n("(Mono)")); break; case 2: lblChannelsVerbose->setText(i18n("(Stereo)")); break; case 4: lblChannelsVerbose->setText(i18n("(Quadro)")); break; default: lblChannelsVerbose->setText(_("")); break; } } //*************************************************************************** void Kwave::FileInfoDialog::updateAvailableCompressions() { cbCompression->blockSignals(true); QList supported_compressions; QString mime_type = m_info.get(Kwave::INF_MIMETYPE).toString(); // switch by mime type: if (mime_type.length()) { // mime type is present -> offer only matching compressions Kwave::Encoder *encoder = Kwave::CodecManager::encoder(mime_type); if (encoder) supported_compressions = encoder->compressionTypes(); } else { // no mime type -> allow all mimetypes suitable for encoding supported_compressions.append(Kwave::Compression::NONE); QStringList mime_types = Kwave::CodecManager::encodingMimeTypes(); foreach (QString m, mime_types) { Kwave::Encoder *encoder = Kwave::CodecManager::encoder(m); if (!encoder) continue; QList comps = encoder->compressionTypes(); foreach (Kwave::Compression::Type c, comps) if (!supported_compressions.contains(c)) supported_compressions.append(c); } } // if nothing is supported, then use only "none" if (supported_compressions.isEmpty()) supported_compressions.append(Kwave::Compression::NONE); // add supported compressions to the combo box cbCompression->clear(); foreach (Kwave::Compression::Type c, supported_compressions) { Kwave::Compression compression(c); cbCompression->addItem(compression.name(), compression.toInt()); } cbCompression->blockSignals(false); // update the selection of the compression type int c = QVariant(m_info.get(Kwave::INF_COMPRESSION)).toInt(); int new_index = cbCompression->findData(c); // take the highest supported compression if changed to "invalid" // (assuming that the last entry in the list is the best one) if (new_index < 0) new_index = cbCompression->count() - 1; cbCompression->setCurrentIndex(new_index); } //*************************************************************************** void Kwave::FileInfoDialog::compressionChanged() { if (!cbCompression || !edFileFormat) return; Kwave::Compression::Type compression = Kwave::Compression::fromInt(cbCompression->itemData( cbCompression->currentIndex()).toInt() ); const Kwave::Compression comp(compression); const QString preferred_mime_type = comp.preferredMimeType(); // selected compression -> mime type (edit field) if (preferred_mime_type.length()) { // if a compression implies a specific mime type -> select it edFileFormat->setText(preferred_mime_type); } else { // if mime type is given by file info -> keep it // otherwise select one by evaluating the compression <-> encoder QString file_mime_type = m_info.get(Kwave::INF_MIMETYPE).toString(); if (!file_mime_type.length()) { // determine mime type from a matching encoder. // This should work for compression types that are supported by // only one single encoder which also supports only one single // mime type QStringList mime_types = Kwave::CodecManager::encodingMimeTypes(); foreach (const QString &mime_type, mime_types) { Kwave::Encoder *encoder = Kwave::CodecManager::encoder(mime_type); if (!encoder) continue; QList comps = encoder->compressionTypes(); if (comps.contains(compression)) { edFileFormat->setText(mime_type); break; } } } } // if mpeg mode selected -> select mpeg layer int mpeg_layer = -1; switch (compression) { case Kwave::Compression::MPEG_LAYER_I: mpeg_layer = 1; break; case Kwave::Compression::MPEG_LAYER_II: mpeg_layer = 2; break; case Kwave::Compression::MPEG_LAYER_III: mpeg_layer = 3; break; default: break; } InfoTab->setTabEnabled(2, isMpeg()); if ((mpeg_layer > 0) && (cbMpegLayer->currentIndex() != (mpeg_layer - 1))) cbMpegLayer->setCurrentIndex(mpeg_layer - 1); // enable/disable ABR/VBR controls, depending on mime type const bool abr = comp.hasABR(); const bool lower = abr && m_info.contains(Kwave::INF_BITRATE_LOWER); const bool upper = abr && m_info.contains(Kwave::INF_BITRATE_UPPER); const bool vbr = comp.hasVBR(); compressionWidget->enableABR(abr, lower, upper); compressionWidget->enableVBR(vbr); cbSampleFormat->setEnabled(!comp.sampleFormats().isEmpty()); if (abr && !vbr) compressionWidget->setMode(Kwave::CompressionWidget::ABR_MODE); else if (!abr && vbr) compressionWidget->setMode(Kwave::CompressionWidget::VBR_MODE); } //*************************************************************************** bool Kwave::FileInfoDialog::isMpeg() const { int compression = cbCompression->itemData( cbCompression->currentIndex()).toInt(); switch (compression) { case Kwave::Compression::MPEG_LAYER_I: case Kwave::Compression::MPEG_LAYER_II: case Kwave::Compression::MPEG_LAYER_III: return true; default: return false; } } //*************************************************************************** void Kwave::FileInfoDialog::mpegLayerChanged() { if (!cbMpegLayer || !isMpeg()) return; int layer = cbMpegLayer->currentIndex() + 1; int compression = Kwave::Compression::NONE; switch (layer) { case 1: compression = Kwave::Compression::MPEG_LAYER_I; break; case 2: compression = Kwave::Compression::MPEG_LAYER_II; break; case 3: compression = Kwave::Compression::MPEG_LAYER_III; break; } if (compression != Kwave::Compression::NONE) { int index = cbCompression->findData(compression); if (index >= 0) cbCompression->setCurrentIndex(index); } /* Mode extension */ // only in "Joint Stereo" mode, then depends on Layer // // Layer I+II | Layer III // | Intensity stereo MS Stereo //-------------------------------------------------- // 0 - bands 4 to 31 | off off -> 4 // 1 - bands 8 to 31 | on off -> 5 // 2 - bands 12 to 31 | off on -> 6 // 3 - bands 16 to 31 | on on -> 7 if (m_info.tracks() < 2) { // mono cbMpegModeExt->setEnabled(false); cbMpegModeExt->setCurrentIndex(-1); chkMpegIntensityStereo->setEnabled(false); chkMpegIntensityStereo->setChecked(false); chkMpegMSStereo->setEnabled(false); chkMpegMSStereo->setChecked(false); } else if (cbMpegModeExt->isEnabled() && (layer >= 3)) { // switched from layer I or II to layer III cbMpegModeExt->setEnabled(false); int modeext = QVariant(m_info.get(Kwave::INF_MPEG_MODEEXT)).toInt(); if ((modeext < 4) || (modeext > 7)) { modeext = 7; // default to MS stereo + Intensity stereo chkMpegIntensityStereo->setChecked(modeext & 0x01); chkMpegMSStereo->setChecked(modeext & 0x02); } chkMpegIntensityStereo->setEnabled(true); chkMpegMSStereo->setEnabled(true); } else if (!cbMpegModeExt->isEnabled() && (layer <= 2)) { // switched from layer III to layer I or II int modeext = (m_info.contains(Kwave::INF_MPEG_MODEEXT)) ? QVariant(m_info.get(Kwave::INF_MPEG_MODEEXT)).toInt() : -1; if ((modeext < 0) || (modeext > 3)) { modeext = 0; // default bands 4 to 31 cbMpegModeExt->setCurrentIndex(modeext); } cbMpegModeExt->setEnabled(true); chkMpegIntensityStereo->setEnabled(false); chkMpegMSStereo->setEnabled(false); } } //*************************************************************************** void Kwave::FileInfoDialog::mpegCopyrightedChanged(bool checked) { chkMpegCopyrighted->setText((checked) ? i18n("Yes") : i18n("No")); } //*************************************************************************** void Kwave::FileInfoDialog::mpegOriginalChanged(bool checked) { chkMpegOriginal->setText((checked) ? i18n("Yes") : i18n("No")); } //*************************************************************************** void Kwave::FileInfoDialog::autoGenerateKeywords() { // start with the current list QStringList list = lstKeywords->keywords(); // name, subject, version, genre, author, organization, // copyright, license, source, source form, album, // product, archival, contact, software, technician, engineer, // commissioned, ISRC const QString space = _(" "); list += edName->text().split(space); list += edSubject->text().split(space); list += edVersion->text().split(space); list += cbGenre->currentText(); list += edAuthor->text().split(space); list += edOrganization->text().split(space); list += edCopyright->text().split(space); list += edLicense->text().split(space); list += edSource->text().split(space); list += edSourceForm->text().split(space); list += edAlbum->text().split(space); list += edProduct->text().split(space); list += edArchival->text().split(space); list += edContact->text().split(space); list += edSoftware->text().split(space); list += edTechnican->text().split(space); list += edEngineer->text().split(space); list += edCommissioned->text().split(space); list += edISRC->text().split(space); // filter out all useless stuff QMutableStringListIterator it(list); while (it.hasNext()) { QString token = it.next(); // remove punktation characters like '.', ',', '!' from start and end while (token.length()) { QString old_value = token; QChar c = token[token.length()-1]; if (c.isPunct() || c.isMark() || c.isSpace()) token = token.left(token.length()-1); if (!token.length()) break; c = token[0]; if (c.isPunct() || c.isMark() || c.isSpace()) token = token.right(token.length()-1); if (token == old_value) break; // no (more) change(s) } // remove empty entries if (!token.length()) { it.remove(); continue; } // remove simple numbers and too short stuff bool ok = false; token.toInt(&ok); if ((ok) || (token.length() < 3)) { it.remove(); // number or less than 3 characters -> remove continue; } // remove duplicates that differ in case bool is_duplicate = false; QStringListIterator it2(list); while (it2.hasNext()) { QString token2 = it2.next(); if (list.indexOf(token) == list.lastIndexOf(token2)) continue; if (token2.compare(token, Qt::CaseInsensitive) == 0) { // take the one with less uppercase characters unsigned int upper1 = 0; unsigned int upper2 = 0; for (int i=0; i < token.length(); ++i) if (token[i].category() == QChar::Letter_Uppercase) upper1++; for (int i=0; i < token2.length(); ++i) if (token2[i].category() == QChar::Letter_Uppercase) upper2++; if (upper2 < upper1) { is_duplicate = true; break; } } } if (is_duplicate) { it.remove(); continue; } it.setValue(token); } // other stuff like empty strings and duplicates are handled in // the list itself, we don't need to take care of that here :) lstKeywords->setKeywords(list); } //*************************************************************************** void Kwave::FileInfoDialog::acceptEdit(Kwave::FileProperty property, QString value) { value = value.simplified(); if (!m_info.contains(property) && !value.length()) return; if (!value.length()) { m_info.set(property, QVariant()); } else { m_info.set(property, value); } } //*************************************************************************** void Kwave::FileInfoDialog::accept() { // save defaults for next time... KConfigGroup cfg = KSharedConfig::openConfig()->group(CONFIG_DEFAULT_SECTION); cfg.sync(); { int nominal, upper, lower; compressionWidget->getABRrates(nominal, lower, upper); cfg.writeEntry("default_abr_nominal_bitrate", nominal); cfg.writeEntry("default_abr_upper_bitrate", upper); cfg.writeEntry("default_abr_lower_bitrate", lower); int quality = compressionWidget->baseQuality(); cfg.writeEntry("default_vbr_quality", quality); } cfg.sync(); qDebug("FileInfoDialog::accept()"); m_info.dump(); /* mime type */ m_info.set(Kwave::INF_MIMETYPE, edFileFormat->text()); /* bits per sample */ m_info.setBits(sbResolution->value()); /* sample rate */ m_info.setRate(cbSampleRate->currentText().toDouble()); /* sample format */ Kwave::SampleFormat::Map sample_formats; int sample_format = cbSampleFormat->itemData(cbSampleFormat->currentIndex()).toInt(); m_info.set(Kwave::INF_SAMPLE_FORMAT, QVariant(sample_format)); /* compression */ Kwave::Compression::Type compression = Kwave::Compression::fromInt( cbCompression->itemData(cbCompression->currentIndex()).toInt() ); m_info.set(Kwave::INF_COMPRESSION, (compression != Kwave::Compression::NONE) ? QVariant(Kwave::Compression(compression).toInt()) : QVariant()); /* MPEG layer */ if (isMpeg()) { int layer = cbMpegLayer->currentIndex() + 1; m_info.set(Kwave::INF_MPEG_LAYER, layer); // only in "Joint Stereo" mode, then depends on Layer // // Layer I+II | Layer III // | Intensity stereo MS Stereo //-------------------------------------------------- // 0 - bands 4 to 31 | off off -> 4 // 1 - bands 8 to 31 | on off -> 5 // 2 - bands 12 to 31 | off on -> 6 // 3 - bands 16 to 31 | on on -> 7 if (m_info.tracks() < 2) { // mono -> no mode ext. m_info.set(Kwave::INF_MPEG_MODEEXT, QVariant()); } else if (cbMpegModeExt->isEnabled()) { // Layer I+II int modeext = cbMpegModeExt->currentIndex(); m_info.set(Kwave::INF_MPEG_MODEEXT, modeext); } else { // Layer III int modeext = 4; if (chkMpegIntensityStereo->isChecked()) modeext |= 1; if (chkMpegMSStereo->isChecked()) modeext |= 2; m_info.set(Kwave::INF_MPEG_MODEEXT, modeext); } int emphasis = 0;; switch (cbMpegEmphasis->currentIndex()) { case 1: emphasis = 1; break; /* 1 -> 1 */ case 2: emphasis = 3; break; /* 2 -> 3 */ case 0: /* FALLTHROUGH */ default: emphasis = 0; break; /* 0 -> 0 */ } m_info.set(Kwave::INF_MPEG_EMPHASIS, emphasis); bool copyrighted = chkMpegCopyrighted->isChecked(); m_info.set(Kwave::INF_COPYRIGHTED, copyrighted); bool original = chkMpegOriginal->isChecked(); m_info.set(Kwave::INF_ORIGINAL, original); } else { m_info.set(Kwave::INF_MPEG_MODEEXT, QVariant()); m_info.set(Kwave::INF_MPEG_EMPHASIS, QVariant()); m_info.set(Kwave::INF_COPYRIGHTED, QVariant()); m_info.set(Kwave::INF_ORIGINAL, QVariant()); } /* bitrate in Ogg/Vorbis or MPEG mode */ const Kwave::Compression comp(compression); if (comp.hasABR() || comp.hasVBR()) { Kwave::CompressionWidget::Mode mode = compressionWidget->mode(); QVariant del; switch (mode) { case Kwave::CompressionWidget::ABR_MODE: { int nominal, upper, lower; compressionWidget->getABRrates(nominal, lower, upper); bool use_lowest = compressionWidget->lowestEnabled(); bool use_highest = compressionWidget->highestEnabled(); m_info.set(Kwave::INF_BITRATE_NOMINAL, QVariant(nominal)); m_info.set(Kwave::INF_BITRATE_LOWER, (use_lowest) ? QVariant(lower) : del); m_info.set(Kwave::INF_BITRATE_UPPER, (use_highest) ? QVariant(upper) : del); m_info.set(Kwave::INF_VBR_QUALITY, del); break; } case Kwave::CompressionWidget::VBR_MODE: { int quality = compressionWidget->baseQuality(); m_info.set(Kwave::INF_BITRATE_NOMINAL, del); m_info.set(Kwave::INF_BITRATE_LOWER, del); m_info.set(Kwave::INF_BITRATE_UPPER, del); m_info.set(Kwave::INF_VBR_QUALITY, QVariant(quality)); break; } } } /* name, subject, version, genre, title, author, organization, copyright */ acceptEdit(Kwave::INF_NAME, edName->text()); acceptEdit(Kwave::INF_SUBJECT, edSubject->text()); acceptEdit(Kwave::INF_VERSION, edVersion->text()); acceptEdit(Kwave::INF_GENRE, cbGenre->currentText()); acceptEdit(Kwave::INF_AUTHOR, edAuthor->text()); acceptEdit(Kwave::INF_ORGANIZATION, edOrganization->text()); acceptEdit(Kwave::INF_COPYRIGHT, edCopyright->text()); acceptEdit(Kwave::INF_LICENSE, edLicense->text()); /* date */ QDate date = dateEdit->date(); if ( (date != QDate::currentDate()) || m_info.contains(Kwave::INF_CREATION_DATE) ) { m_info.set(Kwave::INF_CREATION_DATE, QVariant(date).toString()); } /* source, source form, album */ acceptEdit(Kwave::INF_SOURCE, edSource->text()); acceptEdit(Kwave::INF_SOURCE_FORM, edSourceForm->text()); acceptEdit(Kwave::INF_ALBUM, edAlbum->text()); /* CD and track */ int cd = sbCD->value(); int cds = sbCDs->value(); int track = sbTrack->value(); int tracks = sbTracks->value(); m_info.set(Kwave::INF_CD, (cd != 0) ? QVariant(cd) : QVariant()); m_info.set(Kwave::INF_CDS, (cds != 0) ? QVariant(cds) : QVariant()); m_info.set(Kwave::INF_TRACK, (track != 0) ? QVariant(track) : QVariant()); m_info.set(Kwave::INF_TRACKS, (tracks != 0) ? QVariant(tracks) : QVariant()); /* product, archival, contact */ acceptEdit(Kwave::INF_PRODUCT, edProduct->text()); acceptEdit(Kwave::INF_ARCHIVAL, edArchival->text()); acceptEdit(Kwave::INF_CONTACT, edContact->text()); /* software, engineer, technician, commissioned, ISRC, keywords */ acceptEdit(Kwave::INF_SOFTWARE, edSoftware->text()); acceptEdit(Kwave::INF_ENGINEER, edEngineer->text()); acceptEdit(Kwave::INF_TECHNICAN, edTechnican->text()); acceptEdit(Kwave::INF_COMMISSIONED,edCommissioned->text()); // acceptEdit(Kwave::INF_ISRC, edISRC->text()); <- READ-ONLY // list of keywords acceptEdit(Kwave::INF_KEYWORDS, lstKeywords->keywords().join(_("; "))); qDebug("FileInfoDialog::accept() [done]"); m_info.dump(); QDialog::accept(); } //*************************************************************************** void Kwave::FileInfoDialog::invokeHelp() { KHelpClient::invokeHelp(_("fileinfo")); } //*************************************************************************** //***************************************************************************