File indexing completed on 2024-05-12 04:49:59
0001 /* 0002 Copyright (C) 2004 Allan Sandfeld Jensen <kde@carewolf.com> 0003 Copyright (C) 2005 Benjamin Meyer <ben at meyerhome dot net> 0004 0005 This library is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Library General Public 0007 License as published by the Free Software Foundation; either 0008 version 2 of the License, or (at your option) any later version. 0009 0010 This library is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 Library General Public License for more details. 0014 0015 You should have received a copy of the GNU Library General Public License 0016 along with this library; see the file COPYING.LIB. If not, write to 0017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "encoderflac.h" 0022 0023 #include <FLAC/format.h> 0024 #include <FLAC/metadata.h> 0025 #include <FLAC/stream_encoder.h> 0026 0027 #if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 0028 #include "audiocd_flac_encoder.h" 0029 #endif 0030 0031 #include "audiocd_kio_debug.h" 0032 #include <KConfig> 0033 0034 #include <QDateTime> 0035 #include <QPair> 0036 0037 Q_LOGGING_CATEGORY(AUDIOCD_KIO_LOG, "kf.kio.workers.audiocd") 0038 0039 extern "C" { 0040 AUDIOCDPLUGINS_EXPORT void create_audiocd_encoders(KIO::WorkerBase *worker, QList<AudioCDEncoder *> &encoders) 0041 { 0042 encoders.append(new EncoderFLAC(worker)); 0043 } 0044 } 0045 0046 class EncoderFLAC::Private { 0047 0048 public: 0049 FLAC__StreamEncoder *encoder; 0050 FLAC__StreamMetadata **metadata; 0051 KIO::WorkerBase *ioWorker; 0052 unsigned long data; 0053 #if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 0054 unsigned compression_level; 0055 #endif 0056 }; 0057 0058 static FLAC__StreamEncoderWriteStatus WriteCallback(const FLAC__StreamEncoder *encoder, 0059 const FLAC__byte buffer[], 0060 #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 0061 unsigned bytes, 0062 #else 0063 size_t bytes, 0064 #endif 0065 unsigned samples, 0066 unsigned current_frame, 0067 void *client_data) 0068 { 0069 Q_UNUSED(encoder) 0070 Q_UNUSED(samples) 0071 Q_UNUSED(current_frame) 0072 0073 auto *d = (EncoderFLAC::Private *)client_data; 0074 0075 d->data += bytes; 0076 0077 QByteArray output; 0078 0079 if (bytes) { 0080 output = QByteArray::fromRawData((const char *)buffer, bytes); 0081 d->ioWorker->data(output); 0082 output.clear(); 0083 } 0084 0085 return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; 0086 } 0087 0088 static void MetadataCallback(const FLAC__StreamEncoder *encoder, const FLAC__StreamMetadata *metadata, void *client_data) 0089 { 0090 // We do not have seekable writeback so we just discard the updated metadata 0091 Q_UNUSED(encoder) 0092 Q_UNUSED(metadata) 0093 Q_UNUSED(client_data) 0094 } 0095 0096 /* 0097 static FLAC__SeekableStreamEncoderSeekStatus SeekCallback(const 0098 FLAC__SeekableStreamEncoder *encoder, FLAC__uint64 absolute_byte_offset, void 0099 *client_data) 0100 {} ; */ 0101 0102 EncoderFLAC::EncoderFLAC(KIO::WorkerBase *worker) 0103 : AudioCDEncoder(worker) 0104 { 0105 d = new Private(); 0106 d->ioWorker = worker; 0107 d->encoder = nullptr; 0108 #if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 0109 d->compression_level = 5; 0110 #endif 0111 } 0112 0113 EncoderFLAC::~EncoderFLAC() { 0114 if (d->encoder) 0115 FLAC__stream_encoder_delete(d->encoder); 0116 delete d; 0117 } 0118 0119 QWidget *EncoderFLAC::getConfigureWidget(KConfigSkeleton **manager) const 0120 { 0121 #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 0122 Q_UNUSED(manager); 0123 return NULL; 0124 #else 0125 (*manager) = Settings::self(); 0126 return new EncoderFLACConfig(); 0127 #endif 0128 } 0129 0130 bool EncoderFLAC::init() 0131 { 0132 d->encoder = FLAC__stream_encoder_new(); 0133 d->metadata = nullptr; 0134 d->data = 0; 0135 return true; 0136 } 0137 0138 void EncoderFLAC::loadSettings() { 0139 #if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 0140 Settings *settings = Settings::self(); 0141 d->compression_level = settings->flac_compression_level(); 0142 if (d->compression_level > 8) 0143 d->compression_level = 5; 0144 #endif 0145 } 0146 0147 // Estimate size to be 5/8 of uncompressed size. 0148 unsigned long EncoderFLAC::size(long time_secs) const { 0149 long uncompressed = (time_secs * (44100 * 2 * 2)); 0150 return (uncompressed / 8) * 5 + 1000; 0151 } 0152 0153 long EncoderFLAC::readInit(long size) { 0154 qCDebug(AUDIOCD_KIO_LOG) << "EncoderFLAC::readInit() called"; 0155 d->data = 0; 0156 #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 0157 FLAC__stream_encoder_set_write_callback(d->encoder, WriteCallback); 0158 FLAC__stream_encoder_set_metadata_callback(d->encoder, MetadataCallback); 0159 FLAC__stream_encoder_set_client_data(d->encoder, d); 0160 #endif 0161 0162 #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 0163 // The options match approximately those of flac compression-level-5 0164 FLAC__stream_encoder_set_do_mid_side_stereo(d->encoder, true); 0165 FLAC__stream_encoder_set_max_lpc_order(d->encoder, 8); // flac -l8 0166 FLAC__stream_encoder_set_min_residual_partition_order(d->encoder, 3); 0167 FLAC__stream_encoder_set_max_residual_partition_order(d->encoder, 0168 3); // flac -r3,3 0169 #else 0170 FLAC__stream_encoder_set_compression_level(d->encoder, d->compression_level); 0171 #endif 0172 0173 FLAC__stream_encoder_set_streamable_subset(d->encoder, true); 0174 if (size > 0) 0175 FLAC__stream_encoder_set_total_samples_estimate(d->encoder, size / 4); 0176 0177 #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 0178 FLAC__stream_encoder_init(d->encoder); 0179 #else 0180 FLAC__stream_encoder_init_stream(d->encoder, WriteCallback, nullptr, nullptr, MetadataCallback, d); 0181 #endif 0182 return d->data; 0183 } 0184 0185 long EncoderFLAC::read(qint16 *buf, int frames) 0186 { 0187 unsigned long olddata = d->data; 0188 auto buffer = new FLAC__int32[frames * 2]; 0189 for (int i = 0; i < frames * 2; i++) { 0190 buffer[i] = (FLAC__int32)buf[i]; 0191 } 0192 0193 FLAC__stream_encoder_process_interleaved(d->encoder, buffer, frames); 0194 delete[] buffer; 0195 return d->data - olddata; 0196 } 0197 0198 long EncoderFLAC::readCleanup() 0199 { 0200 FLAC__stream_encoder_finish(d->encoder); 0201 // FLAC__stream_encoder_delete(d->encoder); 0202 if (d->metadata) { 0203 FLAC__metadata_object_delete(d->metadata[0]); 0204 delete[] d->metadata; 0205 d->metadata = nullptr; 0206 } 0207 return 0; 0208 } 0209 0210 void EncoderFLAC::fillSongInfo(KCDDB::CDInfo info, int track, const QString &comment) 0211 { 0212 d->metadata = new FLAC__StreamMetadata *[1]; 0213 d->metadata[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); 0214 // d->metadata[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING); 0215 // d->metadata[2] = 0216 // FLAC__metadata_object_new(FLAC__METADATA_TYPE_SEEKTABLE) 0217 0218 using Comment = QPair<QString, QVariant>; 0219 Comment comments[7] = {Comment(QLatin1String("TITLE"), info.track(track - 1).get(Title)), 0220 Comment(QLatin1String("ARTIST"), info.track(track - 1).get(Artist)), 0221 Comment(QLatin1String("ALBUM"), info.get(Title)), 0222 Comment(QLatin1String("GENRE"), info.get(Genre)), 0223 Comment(QLatin1String("TRACKNUMBER"), QString::number(track)), 0224 Comment(QLatin1String("COMMENT"), comment), 0225 Comment(QLatin1String("DATE"), QVariant(QString()))}; 0226 if (info.get(Year).toInt() > 0) { 0227 const QDateTime dt = QDate(info.get(Year).toInt(), 1, 1).startOfDay(); 0228 comments[6] = Comment(QStringLiteral("DATE"), dt.toString(Qt::ISODate)); 0229 } 0230 0231 FLAC__StreamMetadata_VorbisComment_Entry entry; 0232 QString field; 0233 int num_comments = 0; 0234 0235 for (int i = 0; i < 7; i++) { 0236 if (!comments[i].second.toString().isEmpty()) { 0237 field = comments[i].first + QLatin1Char('=') + comments[i].second.toString(); 0238 const QByteArray cfield = field.toUtf8(); 0239 entry.entry = (FLAC__byte *)qstrdup(cfield.data()); 0240 entry.length = cfield.length(); 0241 // Insert in vorbiscomment and assign ownership of pointers to FLAC 0242 FLAC__metadata_object_vorbiscomment_insert_comment(d->metadata[0], num_comments, entry, false); 0243 num_comments++; 0244 } 0245 } 0246 0247 FLAC__stream_encoder_set_metadata(d->encoder, d->metadata, 1); 0248 }