diff --git a/CMakeLists.txt b/CMakeLists.txt index b120e8e..66b23d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,109 +1,113 @@ project(audiocd) cmake_minimum_required(VERSION 2.8.12) # ECM setup include(FeatureSummary) find_package(ECM 5.24.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(PROJECT_VERSION "16.07.0") set(KF5_DEP_VERSION "5.24.0") # handled by release scripts +set(REQUIRED_QT_VERSION "5.9.0") + +find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Widgets) find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS Config - KDELibs4Support I18n DocTools KIO + KCMUtils ) find_package(KF5Cddb) set_package_properties(KF5Cddb PROPERTIES DESCRIPTION "KF5 branch for CDDB library" URL "http://projects.kde.org/projects/kde/kdemultimedia/libkcddb" TYPE REQUIRED PURPOSE "libkcddb is used to retrieve audio CD meta data from the internet." ) find_package(KF5CompactDisc) set_package_properties(KF5CompactDisc PROPERTIES DESCRIPTION "KCompactDisc library" URL "https://commits.kde.org/libkcompactdisc" TYPE REQUIRED PURPOSE "libkcompactdisc is used to access CD drives." ) include(KDEInstallDirs) include(KDECompilerSettings) include(KDECMakeSettings) include(GenerateExportHeader) include(ECMSetupVersion) include(CMakePushCheckState) include(CheckStructHasMember) include(ECMQtDeclareLoggingCategory) ecm_setup_version("5.0.0" VARIABLE_PREFIX AUDIOCDPLUGINS VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/audiocdplugins_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/AudioCDPluginsConfigVersion.cmake" SOVERSION 5 ) find_package(Cdparanoia REQUIRED) cmake_push_check_state() set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${CDPARANOIA_INCLUDE_DIR}) check_struct_has_member("struct cdrom_drive" "ioctl_device_name" "cdda_interface.h" CDDA_IOCTL_DEVICE_EXISTS) cmake_pop_check_state() if(CDDA_IOCTL_DEVICE_EXISTS) set(HAVE_CDDA_IOCTL_DEVICE 1) else() set(HAVE_CDDA_IOCTL_DEVICE 0) endif() execute_process( COMMAND sed -e "s|cdda_private_data_t \\*private;|cdda_private_data_t \\*private_data;|g" INPUT_FILE ${CDPARANOIA_INCLUDE_DIR}/cdda_interface.h OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/cdda_interface.hpp RESULT_VARIABLE CDDA_INTERFACE_EDIT_ERROR ) if (CDDA_INTERFACE_EDIT_ERROR) message(STATUS "Fixing cdda_interface.h for C++ failed with exit code ${CDDA_INTERFACE_EDIT_ERROR}") endif() configure_file(config-audiocd.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-audiocd.h) ############################# include_directories( ${CDPARANOIA_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) add_subdirectory(plugins) add_subdirectory(kcmaudiocd) add_subdirectory(data) add_subdirectory(doc) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) ########### next target ############### set(kio_audiocd_PART_SRCS audiocd.cpp ) ecm_qt_declare_logging_category(kio_audiocd_PART_SRCS HEADER audiocd_kio_debug.h IDENTIFIER AUDIOCD_KIO_LOG CATEGORY_NAME log_audiocd_kio DEFAULT_SEVERITY Debug) add_library(kio_audiocd ${kio_audiocd_PART_SRCS}) target_link_libraries(kio_audiocd - ${CDPARANOIA_LIBRARIES} + ${CDPARANOIA_LIBRARIES} + Qt5::Widgets KF5::CompactDisc audiocdplugins ) install(TARGETS kio_audiocd DESTINATION ${PLUGIN_INSTALL_DIR}) ########### install files ############### install(FILES audiocd.protocol DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/audiocd.cpp b/audiocd.cpp index 08e0946..83abdcc 100644 --- a/audiocd.cpp +++ b/audiocd.cpp @@ -1,1230 +1,1231 @@ /* * Copyright (C) 2000 Rik Hemsley (rikkus) * Copyright (C) 2000-2002 Michael Matz * Copyright (C) 2001 Carsten Duvenhorst * Copyright (C) 2001 Adrian Schroeter * Copyright (C) 2003 Richard Lärkäng * Copyright (C) 2003 Scott Wheeler * Copyright (C) 2004-2005 Benjamin Meyer * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "audiocd.h" #include #include #if KIO_VERSION >= QT_VERSION_CHECK(5, 48, 0) #define INSERT(A, B) fastInsert(A, B); #else #define INSERT(A, B) insert(A, B); #endif #include extern "C" { //cdda_interface.h in cdparanoia 10.2 has a member called 'private' which the C++ compiler doesn't like //we will thus use a generated local copy which renames that member. #include "cdda_interface.hpp" #include void paranoiaCallback(long, int); KDE_EXPORT int kdemain(int argc, char ** argv); } #include "plugins/audiocdencoder.h" #include #include #include #include #include -#include +#include +#include #include #include #include -#include -#include -#include "audiocd_kio_debug.h" -#include -#include -#include #include +#include +#include + +#include "audiocd_kio_debug.h" + #include +#include + // CDDB #include #include using namespace KIO; #define CDDB_INFORMATION I18N_NOOP("CDDB Information") using namespace AudioCD; -int kdemain(int argc, char ** argv) +extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv) { - KLocalizedString::setApplicationDomain("kio_audiocd"); - // KApplication uses libkcddb which needs a valid kapp pointer - // GUIenabled must be true as libkcddb sometimes wants to communicate - // with the user - qunsetenv("SESSION_MANAGER"); - //KApplication::disableAutoDcopRegistration(); - KCmdLineArgs::init(argc, argv, "kio_audiocd", 0, KLocalizedString(), 0, KLocalizedString()); - - KCmdLineOptions options; - options.add("+protocol", ki18n("Protocol name")); - options.add("+pool", ki18n("Socket name")); - options.add("+app", ki18n("Socket name")); - KCmdLineArgs::addCmdLineOptions(options); - KApplication app(true); - - qCDebug(AUDIOCD_KIO_LOG) << "Starting " << getpid(); - - KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); - AudioCDProtocol slave(args->arg(0).toLocal8Bit(), args->arg(1).toLocal8Bit(), args->arg(2).toLocal8Bit()); - args->clear(); - slave.dispatchLoop(); - - qCDebug(AUDIOCD_KIO_LOG) << "Done"; - return 0; + KLocalizedString::setApplicationDomain("kio_audiocd"); + // QApplication uses libkcddb which needs a valid kapp pointer + // GUIenabled must be true as libkcddb sometimes wants to communicate + // with the user + qunsetenv("SESSION_MANAGER"); + QApplication app(argc, argv); + app.setApplicationName(QStringLiteral("kio_audiocd")); + + if (argc != 4) { + fprintf(stderr, "Usage: kio_audiocd protocol pool app\n"); + exit(-1); + } + + qCDebug(AUDIOCD_KIO_LOG) << "Starting " << getpid(); + + AudioCDProtocol slave(argv[1], argv[2], argv[3]); + slave.dispatchLoop(); + + qCDebug(AUDIOCD_KIO_LOG) << "Done"; + + return 0; } enum Which_dir { Unknown = 0, // Error Info, // CDDB info Root, // The root directory, shows all these :) FullCD, // Show a single file containing all of the data EncoderDir, // The root directory created by an encoder SubDir // A directory created from the Album name configuration }; class AudioCDProtocol::Private { public: Private() : s_info(i18n("Information")), s_fullCD(i18n("Full CD")) { clearURLargs(); } void clearURLargs() { req_allTracks = false; which_dir = Unknown; req_track = -1; cddbUserChoice = -1; } bool tocsAreDifferent(struct cdrom_drive *drive) { if (tracks != (uint)drive->tracks) return true; for (int i = 0; i < drive->tracks; ++i) { if (disc_toc[i].dwStartSector != drive->disc_toc[i].dwStartSector || disc_toc[i].bFlags != drive->disc_toc[i].bFlags || disc_toc[i].bTrack != drive->disc_toc[i].bTrack) return true; } return false; } void setToc(struct cdrom_drive *drive) { for (int i = 0; i < drive->tracks; ++i) { disc_toc[i].dwStartSector = drive->disc_toc[i].dwStartSector; disc_toc[i].bFlags = drive->disc_toc[i].bFlags; disc_toc[i].bTrack = drive->disc_toc[i].bTrack; } } // The type/which of request bool req_allTracks; Which_dir which_dir; int req_track; QString fname; QString child_dir; AudioCDEncoder *encoder_dir_type; // Misc settings QString device; // URL settable int paranoiaLevel; // URL settable bool reportErrors; // Directory strings, never change after init const QString s_info; const QString s_fullCD; // Current CD TOC disc_toc[MAXTRK]; unsigned tracks; bool trackIsAudio[100]; // CDDB items KCDDB::Result cddbResult; CDInfoList cddbList; int cddbUserChoice; // URL settable KCDDB::CDInfo cddbBestChoice; // Template for .. QString fileNameTemplate; // URL settable QString albumNameTemplate; // URL settable QString fileLocationTemplate; // URL settable QString rsearch; QString rreplace; // Current strings for this CD and or cddb selection QStringList templateTitles; QString templateAlbumName; QString templateFileLocation; }; int paranoia_read_limited_error; AudioCDProtocol::AudioCDProtocol(const QByteArray & protocol, const QByteArray & pool, const QByteArray & app) : SlaveBase(protocol, pool, app) { d = new Private; // Add encoders AudioCDEncoder::findAllPlugins(this, encoders); encoderTypeCDA = encoderFromExtension(QLatin1String( ".cda" )); encoderTypeWAV = encoderFromExtension(QLatin1String( ".wav" )); } AudioCDProtocol::~AudioCDProtocol() { while (!encoders.isEmpty()) delete encoders.takeFirst(); delete d; } AudioCDEncoder *AudioCDProtocol::encoderFromExtension(const QString& extension) { AudioCDEncoder *encoder; for (int i = encoders.size()-1; i >= 0; --i) { encoder = encoders.at(i); if(QLatin1String(".")+QLatin1String( encoder->fileType() ) == extension) return encoder; } Q_ASSERT(false); return NULL; } AudioCDEncoder *AudioCDProtocol::determineEncoder(const QString & filename) { int len = filename.length(); int pos = filename.lastIndexOf( QLatin1Char( '.' )); return encoderFromExtension(filename.right(len - pos)); } static void setDeviceToCd(KCompactDisc *cd, struct cdrom_drive *drive) { #if defined(HAVE_CDDA_IOCTL_DEVICE) cd->setDevice(QLatin1String( drive->ioctl_device_name ), 50, false); #elif defined(__FreeBSD__) || defined(__DragonFly__) // FreeBSD's cdparanoia as of january 5th 2006 has rather broken // support for non-SCSI devices. Although it finds ATA cdroms just // fine, there is no straightforward way to discover the device // name associated with the device, which throws the rest of audiocd // for a loop. // if ( !(drive->dev) || (COOKED_IOCTL == drive->interface) ) { // For ATAPI devices, we have no real choice. Use the // user selected value, even if there is none. // qCWarning(AUDIOCD_KIO_LOG) << "Found an ATAPI device, assuming it is the one specified by the user."; cd->setDevice( drive->cdda_device_name ); } else { qCDebug(AUDIOCD_KIO_LOG) << "Found a SCSI or ATAPICAM device."; if ( strlen(drive->dev->device_path) > 0 ) { cd->setDevice( drive->dev->device_path ); } else { // But the device_path can be empty under some // circumstances, so build a representation from // the unit number and SCSI device name. // QString devname = QString::fromLatin1( "/dev/%1%2" ) .arg( drive->dev->given_dev_name ) .arg( drive->dev->given_unit_number ) ; qCDebug(AUDIOCD_KIO_LOG) << " Using derived name " << devname; cd->setDevice( devname ); } } #else #ifdef __GNUC__ #warning audiocd ioslave is not going to work for you #endif #endif } struct cdrom_drive * AudioCDProtocol::initRequest(const QUrl & url) { if (!url.host().isEmpty()) { error(KIO::ERR_UNSUPPORTED_ACTION, i18n("You cannot specify a host with this protocol. " "Please use the audiocd:/ format instead.")); return 0; } // Load OUR Settings. loadSettings(); // Then url parameters can overrule our settings. parseURLArgs(url); struct cdrom_drive * drive = getDrive(); if (0 == drive) return 0; if (d->tocsAreDifferent(drive)) { // Update our knowledge of the disc KCompactDisc cd(KCompactDisc::Asynchronous); setDeviceToCd(&cd, drive); d->setToc(drive); d->tracks = cd.tracks(); for(uint i=0; i< cd.tracks(); i++) d->trackIsAudio[i] = cd.isAudio(i+1); KCDDB::Client c; d->cddbResult = c.lookup(cd.discSignature()); if (d->cddbResult == Success) { d->cddbList = c.lookupResponse(); d->cddbBestChoice = d->cddbList.first(); } generateTemplateTitles(); } // Determine what file or folder that is wanted. QString path = url.path(); if (!path.isEmpty() && path[0] == QLatin1Char( '/' )) path = path.mid(1); d->req_allTracks = false; // See which file and directory they want QString remainingDirPath; d->which_dir = Unknown; if (path.isEmpty()) { d->which_dir = Root; d->encoder_dir_type = encoderTypeWAV; remainingDirPath = d->templateFileLocation; d->fname = QString(); } else { for (int i = encoders.size()-1; i >= 0; --i) { AudioCDEncoder *encoder = encoders.at(i); QString encoderFileLocation = encoder->type(); if (!d->templateFileLocation.isEmpty()) encoderFileLocation = encoderFileLocation + QLatin1String( "/" ) + d->templateFileLocation; if (path == encoder->type()) { d->which_dir = EncoderDir; d->encoder_dir_type = encoder; remainingDirPath = encoderFileLocation.mid(path.length()); d->fname = QString(); break; } else if (encoderFileLocation.startsWith(path)) { d->which_dir = SubDir; d->encoder_dir_type = encoder; remainingDirPath = encoderFileLocation.mid(path.length()); d->fname = QString(); break; } else if (path.startsWith(encoderFileLocation)) { d->which_dir = SubDir; d->encoder_dir_type = encoder; remainingDirPath = QString(); d->fname = path.mid(encoderFileLocation.length() + 1); break; } else if (path.startsWith(encoder->type())) { d->which_dir = EncoderDir; d->encoder_dir_type = encoder; remainingDirPath = QString(); d->fname = path.mid(encoder->type().length() + 1); } } if ( Unknown == d->which_dir ) { if (path.startsWith(d->s_info)) { d->which_dir = Info; d->fname = path.mid(d->s_info.length() + 1); } else if (path.startsWith(d->s_fullCD)) { d->which_dir = FullCD; d->fname = path.mid(d->s_fullCD.length() + 1); d->req_allTracks = true; } else if (d->templateFileLocation.startsWith(path)) { d->which_dir = SubDir; d->encoder_dir_type = encoderTypeWAV; remainingDirPath = d->templateFileLocation.mid(path.length()); d->fname = QString(); } else if (path.startsWith(d->templateFileLocation)) { d->encoder_dir_type = encoderTypeWAV; remainingDirPath = QString(); d->fname = path.mid(d->templateFileLocation.length() + 1); } else { d->encoder_dir_type = encoderTypeWAV; remainingDirPath = QString(); d->fname = path; } } } if (!remainingDirPath.isEmpty() && remainingDirPath[0] == QLatin1Char( '/' )) remainingDirPath = remainingDirPath.mid(1); d->child_dir = remainingDirPath.split(QLatin1String( "/" )).first(); // See if the url is a track d->req_track = -1; if (!d->fname.isEmpty()){ QString name(d->fname); // Remove extension int dot = name.lastIndexOf( QLatin1Char( '.' )); if (dot >= 0) name.truncate(dot); // See if it matches a cddb title uint trackNumber; for (trackNumber = 0; trackNumber < d->tracks; trackNumber++){ if (d->templateTitles[trackNumber] == name) break; } if (trackNumber < d->tracks) d->req_track = trackNumber; else { /* Not found in title list. Try hard to find a number in the string. */ int start = 0; int end = 0; // Find where the numbers start while (start < name.length()){ if (name[start++].isDigit()) break; } // Find where the numbers end for (end = start; end < name.length(); ++end) if (!name[end].isDigit()) break; if (start < name.length()){ bool ok; // The external representation counts from 1 so subtract 1. d->req_track = name.mid(start-1, end - start+2).toInt(&ok) - 1; if (!ok) d->req_track = -1; } } } if (d->req_track >= (int)d->tracks) d->req_track = -1; qCDebug(AUDIOCD_KIO_LOG) << "path=" << path << " file=" << d->fname << " req_track=" << d->req_track << " which_dir=" << d->which_dir << " full CD?=" << d->req_allTracks << endl; return drive; } bool AudioCDProtocol::getSectorsForRequest(struct cdrom_drive * drive, long & firstSector, long & lastSector) const { if (d->req_allTracks) { // we rip all the tracks of the CD firstSector = cdda_track_firstsector(drive, 1); lastSector = cdda_track_lastsector(drive, cdda_tracks(drive)); } else { // we only rip the selected track int trackNumber = d->req_track + 1; if (trackNumber <= 0 || trackNumber > cdda_tracks(drive)) return false; firstSector = cdda_track_firstsector(drive, trackNumber); lastSector = cdda_track_lastsector(drive, trackNumber); } return true; } void AudioCDProtocol::get(const QUrl & url) { struct cdrom_drive * drive = initRequest(url); if (!drive) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } if( d->fname.contains(i18n(CDDB_INFORMATION))){ uint choice = 1; if(d->fname != QString::fromLatin1("%1.txt").arg(i18n(CDDB_INFORMATION))){ choice= d->fname.section(QLatin1Char( '_' ),1,1).section(QLatin1Char( '.' ),0,0).toInt(); } uint count = 1; CDInfoList::iterator it; bool found = false; for ( it = d->cddbList.begin(); it != d->cddbList.end(); ++it ){ if(count == choice){ mimeType(QLatin1String( "text/html" )); data(QByteArray( (*it).toString().toLatin1() )); // send an empty QByteArray to signal end of data. data(QByteArray()); finished(); found = true; break; } count++; } if(!found && d->fname.contains(i18n(CDDB_INFORMATION)+QLatin1Char( ':' ))){ mimeType(QLatin1String( "text/html" )); //data(QCString( d->fname.latin1() )); // send an empty QByteArray to signal end of data. data(QByteArray()); finished(); found = true; } if( !found ) error(KIO::ERR_DOES_NOT_EXIST, url.path()); cdda_close(drive); return; } long firstSector, lastSector; if (!getSectorsForRequest(drive, firstSector, lastSector)) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); cdda_close(drive); return; } AudioCDEncoder *encoder = determineEncoder(d->fname); if(!encoder){ error(KIO::ERR_DOES_NOT_EXIST, url.path()); cdda_close(drive); return; } KCDDB::CDInfo info; if(d->cddbResult == KCDDB::Success){ info = d->cddbBestChoice; int track = d->req_track+1; // hack // do we rip the whole CD? if (d->req_allTracks){ track = 1; // YES => the title of the file is the title of the CD info.track(track-1).set(Title, info.get(Title)); } encoder->fillSongInfo(info, track, QString()); } long totalByteCount = CD_FRAMESIZE_RAW * (lastSector - firstSector + 1); long time_secs = (8 * totalByteCount) / (44100 * 2 * 16); unsigned long size = encoder->size(time_secs); totalSize(size); emit mimeType(QLatin1String(encoder->mimeType())); // Read data (track/disk) from the cd paranoiaRead(drive, firstSector, lastSector, encoder, url.fileName(), size); // send an empty QByteArray to signal end of data. data(QByteArray()); cdda_close(drive); finished(); } void AudioCDProtocol::stat(const QUrl & url) { struct cdrom_drive * drive = initRequest(url); if (!drive && d->device.isEmpty()) { // This is top level directory with CDROM devices const mode_t _umask = ::umask(0); ::umask(_umask); UDSEntry entry; entry.INSERT(KIO::UDSEntry::UDS_NAME, url.fileName().replace(QLatin1Char( '/' ), QLatin1String("%2F"))); entry.INSERT(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.INSERT(KIO::UDSEntry::UDS_ACCESS, (0666 & (~_umask))); entry.INSERT(KIO::UDSEntry::UDS_SIZE, 2+encoders.count()); statEntry(entry); finished(); return; } if (!drive) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } const bool isFile = !d->fname.isEmpty(); // the track number. 0 if ripping // the whole CD. const uint trackNumber = d->req_track + 1; if (!d->req_allTracks) { // we only want to rip one track. // does this track exist? if (isFile && (trackNumber < 1 || trackNumber > d->tracks)) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); cdda_close(drive); return; } } UDSEntry entry; entry.INSERT( KIO::UDSEntry::UDS_NAME, url.fileName().replace(QLatin1Char( '/' ), QLatin1String("%2F") )); entry.INSERT( KIO::UDSEntry::UDS_FILE_TYPE,isFile ? S_IFREG : S_IFDIR); const mode_t _umask = ::umask(0); ::umask(_umask); entry.INSERT(KIO::UDSEntry::UDS_ACCESS, (0666 & (~_umask))); if (!isFile) { entry.INSERT( KIO::UDSEntry::UDS_SIZE, cdda_tracks(drive)); } else { AudioCDEncoder *encoder = determineEncoder(d->fname); - long firstSector, lastSector; + long firstSector = 0, lastSector = 0; getSectorsForRequest(drive, firstSector, lastSector); entry.INSERT( KIO::UDSEntry::UDS_SIZE,fileSize(firstSector, lastSector, encoder)); } statEntry(entry); cdda_close(drive); finished(); } static void app_dir(UDSEntry& e, const QString & n, size_t s) { e.clear(); e.INSERT( KIO::UDSEntry::UDS_NAME, QFile::decodeName(n.toLocal8Bit())); e.INSERT( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); e.INSERT( KIO::UDSEntry::UDS_ACCESS, 0400); e.INSERT( KIO::UDSEntry::UDS_SIZE, s); e.INSERT( KIO::UDSEntry::UDS_MIME_TYPE, QLatin1String("inode/directory")); } static void app_file(UDSEntry& e, const QString & n, size_t s, const QString &mimetype = QString()) { e.clear(); e.INSERT( KIO::UDSEntry::UDS_NAME, QFile::decodeName(n.toLocal8Bit())); e.INSERT( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG); e.INSERT( KIO::UDSEntry::UDS_ACCESS, 0400); e.INSERT( KIO::UDSEntry::UDS_SIZE, s); if (!mimetype.isEmpty()) e.INSERT( KIO::UDSEntry::UDS_MIME_TYPE, mimetype); } void AudioCDProtocol::listDir(const QUrl & url) { struct cdrom_drive * drive = initRequest(url); if (!drive && d->device.isEmpty()) { // List CDROM devices UDSEntry entry; const QStringList &deviceNames = KCompactDisc::cdromDeviceNames(); foreach (const QString &deviceName, deviceNames) { const QString &device = KCompactDisc::urlToDevice(KCompactDisc::cdromDeviceUrl(deviceName)); QUrl targetUrl = url; - targetUrl.addEncodedQueryItem("device", device.toUtf8()); + QUrlQuery targetQuery; + targetQuery.addQueryItem("device", device.toUtf8()); + targetUrl.setQuery(targetQuery); app_dir(entry, device, 2+encoders.count()); entry.INSERT(KIO::UDSEntry::UDS_TARGET_URL, targetUrl.url()); entry.INSERT(KIO::UDSEntry::UDS_DISPLAY_NAME, deviceName); listEntry(entry); } totalSize(entry.count()); finished(); return; } // Some error checking before proceeding if (!drive) { error(KIO::ERR_DOES_NOT_EXIST, url.path()); return; } if (d->which_dir == Unknown){ error(KIO::ERR_DOES_NOT_EXIST, url.path()); cdda_close(drive); return; } if ( !d->fname.isEmpty() ){ error(KIO::ERR_IS_FILE, url.path()); cdda_close(drive); return; } // Generate templated names every time // because the template might have changed. generateTemplateTitles(); UDSEntry entry; if (d->which_dir == Info){ CDInfoList::iterator it; uint count = 1; for ( it = d->cddbList.begin(); it != d->cddbList.end(); ++it ){ (*it).toString(); if(count == 1) app_file(entry, QString::fromLatin1("%1.txt").arg(i18n(CDDB_INFORMATION)), ((*it).toString().length())+1); else app_file(entry, QString::fromLatin1("%1_%2.txt").arg(i18n(CDDB_INFORMATION)).arg(count), ((*it).toString().length())+1); count++; listEntry(entry); } // Error if( count == 1 ) { app_file(entry, QString::fromLatin1("%1: %2.txt").arg(i18n(CDDB_INFORMATION)).arg(KCDDB::resultToString(d->cddbResult)), 0); count++; listEntry(entry); } } if (d->which_dir == Root){ // List virtual directories. app_dir(entry, d->s_fullCD, encoders.count()); listEntry(entry); // Either >0 cddb results or cddb error file app_dir(entry, d->s_info, d->cddbList.count()); listEntry(entry); // List the encoders AudioCDEncoder *encoder; for (int i = encoders.size()-1; i >= 0; --i) { encoder = encoders.at(i); // Skip the directory that is in the root (you can still go in it, just don't show it) if( encoder == encoderTypeWAV ) continue; app_dir(entry, encoder->type(), d->tracks); listEntry(entry); } } // Now fill in the tracks for the current directory if (d->which_dir == FullCD) { AudioCDEncoder *encoder; for (int i = encoders.size()-1; i >= 0; --i) { encoder = encoders.at(i); if (d->cddbResult != KCDDB::Success) addEntry(d->s_fullCD, encoder, drive, -1); else addEntry(d->templateAlbumName, encoder, drive, -1); } } if (d->which_dir == SubDir || d->which_dir == Root || d->which_dir == EncoderDir) { if (d->child_dir.isEmpty()) { // we are at the end of the hierarchy, list the tracks for (uint trackNumber = 1; trackNumber <= d->tracks; trackNumber++) { // Skip data tracks if (!d->trackIsAudio[trackNumber-1]) continue; switch (d->which_dir) { case EncoderDir: case SubDir: case Root: addEntry(d->templateTitles[trackNumber - 1], d->encoder_dir_type, drive, trackNumber); break; case Info: case Unknown: default: error(KIO::ERR_INTERNAL, url.path()); cdda_close(drive); return; } } } else { app_dir(entry, d->child_dir, 1); listEntry(entry); } } totalSize(entry.count()); cdda_close(drive); finished(); } void AudioCDProtocol::addEntry(const QString& trackTitle, AudioCDEncoder *encoder, struct cdrom_drive * drive, int trackNo) { if(!encoder || !drive) return; long theFileSize = 0; if (trackNo == -1) { // adding entry for the full CD theFileSize = fileSize(cdda_track_firstsector(drive, 1), cdda_track_lastsector(drive, cdda_tracks(drive)), encoder); } else { // adding one regular track long firstSector = cdda_track_firstsector(drive, trackNo); long lastSector = cdda_track_lastsector(drive, trackNo); theFileSize = fileSize(firstSector, lastSector, encoder); } UDSEntry entry; app_file(entry, trackTitle + QLatin1String(".")+QLatin1String( encoder->fileType() ), theFileSize, QLatin1String( encoder->mimeType() )); listEntry(entry); } long AudioCDProtocol::fileSize(long firstSector, long lastSector, AudioCDEncoder *encoder) { if(!encoder) return 0; long filesize = CD_FRAMESIZE_RAW * ( lastSector - firstSector + 1 ); long length_seconds = (filesize) / 176400; return encoder->size(length_seconds); } struct cdrom_drive *AudioCDProtocol::getDrive() { const QByteArray device(QFile::encodeName(d->device)); if (device.isEmpty()) return 0; struct cdrom_drive * drive = 0; drive = cdda_identify(device, CDDA_MESSAGE_FORGETIT, 0); if (0 == drive) { qCDebug(AUDIOCD_KIO_LOG) << "Can't find an audio CD on: \"" << d->device << "\""; const QFileInfo fi(d->device); if(!fi.isReadable()) error(KIO::ERR_SLAVE_DEFINED, i18n("Device does not have read permissions for this account. Check the read permissions on the device.")); else if(!fi.isWritable()) error(KIO::ERR_SLAVE_DEFINED, i18n("Device does not have write permissions for this account. Check the write permissions on the device.")); else if(!fi.exists()) error(KIO::ERR_DOES_NOT_EXIST, d->device); else error(KIO::ERR_SLAVE_DEFINED, i18n("Unknown error. If you have a cd in the drive try running cdparanoia -vsQ as yourself (not root). Do you see a track list? If not, make sure you have permission to access the CD device. If you are using SCSI emulation (possible if you have an IDE CD writer) then make sure you check that you have read and write permissions on the generic SCSI device, which is probably /dev/sg0, /dev/sg1, etc.. If it still does not work, try typing audiocd:/?device=/dev/sg0 (or similar) to tell kio_audiocd which device your CD-ROM is.")); return 0; } if (0 != cdda_open(drive)) { qCDebug(AUDIOCD_KIO_LOG) << "cdda_open failed"; error(KIO::ERR_CANNOT_OPEN_FOR_READING, d->device); cdda_close(drive); return 0; } return drive; } void AudioCDProtocol::paranoiaRead( struct cdrom_drive * drive, long firstSector, long lastSector, AudioCDEncoder* encoder, const QString& fileName, unsigned long size ) { if(!encoder || !drive) return; cdrom_paranoia * paranoia = paranoia_init(drive); if (0 == paranoia) { qCDebug(AUDIOCD_KIO_LOG) << "paranoia_init failed"; return; } int paranoiaLevel = PARANOIA_MODE_FULL ^ PARANOIA_MODE_NEVERSKIP; switch (d->paranoiaLevel) { case 0: paranoiaLevel = PARANOIA_MODE_DISABLE; break; case 1: paranoiaLevel |= PARANOIA_MODE_OVERLAP; paranoiaLevel &= ~PARANOIA_MODE_VERIFY; break; case 2: paranoiaLevel |= PARANOIA_MODE_NEVERSKIP; default: break; } paranoia_modeset(paranoia, paranoiaLevel); cdda_verbose_set(drive, CDDA_MESSAGE_PRINTIT, CDDA_MESSAGE_FORGETIT); paranoia_seek(paranoia, firstSector, SEEK_SET); long currentSector(firstSector); unsigned long processed = encoder->readInit(CD_FRAMESIZE_RAW * (lastSector - firstSector + 1)); // TODO test for errors (processed<0)? processedSize(processed); bool ok = true; unsigned long lastSize = size; unsigned long diff = 0; paranoia_read_limited_error = 0; int warned = 0; while (currentSector <= lastSector) { // TODO make the 5 configurable? The default in the lib is 20 fyi qint16 * buf = paranoia_read_limited(paranoia, paranoiaCallback, 5); if( warned == 0 && paranoia_read_limited_error >= 5 && d->reportErrors ){ warning(i18n("AudioCD: Disk damage detected on this track, risk of data corruption.")); warned = 1; } if (0 == buf) { qCDebug(AUDIOCD_KIO_LOG) << "Unrecoverable error in paranoia_read"; ok = false; error( ERR_SLAVE_DEFINED, i18n( "Error reading audio data for %1 from the CD", fileName ) ); break; } ++currentSector; int encoderProcessed = encoder->read(buf, CD_FRAMESAMPLES); if(encoderProcessed == -1){ qCDebug(AUDIOCD_KIO_LOG) << "Encoder processing error, stopping."; ok = false; QString errMsg = i18n( "Could not read %1: encoding failed", fileName ); QString details = encoder->lastErrorMessage(); if ( !details.isEmpty() ) errMsg += QLatin1Char( '\n' ) + details; error( ERR_SLAVE_DEFINED, errMsg ); break; } processed += encoderProcessed; /** * Because compression size is so 'unknown' use some guesswork * * 1) First assume that the reported size is correct and * only change the totalSize if the guess it outside a range of %5. * 2) Only increase in size unless the decrease is %5 of last estimate. * This prevents continues small changes which is just annoying. */ unsigned long end = lastSector - firstSector; unsigned long cur = currentSector - firstSector; unsigned long estSize = (processed / cur ) * end; // If our guess is within 5% of reported // size then use the reported size. unsigned long guess = (long)((100/(float)size)*estSize); if((guess > 97 && guess < 103) || estSize == 0){ if(processed > lastSize){ totalSize(processed+1); lastSize = processed; } } else{ float percentDone = ((float)cur/(float)end); // Calculate estimated amount that will be wrong diff = estSize - lastSize; diff = (diff*(unsigned long)((100/(float)end)*(end-cur)))/2; // Need 1% of data calculated as initial buffer, use %2 to be safe if( percentDone < .02 ){ //qDebug("val: %f, diff: %ld", ((float)cur/(float)end), diff); diff = 0; } // We are growing larger, increase total. if(lastSize < estSize){ //qDebug("lastGuess: %ld, guess: %ld diff: %ld", lastSize, estSize, diff); totalSize(estSize+diff); lastSize = estSize+diff; } else{ int margin = (int)((percentDone)*75); // Don't bother really trying until almost half way done. if( percentDone <= .40 ) margin = 7; unsigned long low = lastSize - lastSize/margin; if(estSize < low){ //qDebug("low: %ld, estSize: %ld, num: %i", low, estSize, margin); totalSize( estSize ); lastSize = estSize; } } } /** * End estimation. */ //qDebug("processed: %d, totalSize: %d", processed, estSize); processedSize(processed); } if(processed > size) totalSize(processed); long encoderProcessed = encoder->readCleanup(); if ( encoderProcessed >= 0 ) { processed += encoderProcessed; if(processed > size) totalSize(processed); processedSize(processed); } else if ( ok ) // i.e. no error message already emitted error( ERR_SLAVE_DEFINED, i18n( "Could not read %1: encoding failed", fileName ) ); paranoia_free(paranoia); paranoia = 0; } /** * Read the settings from the URL * @see loadSettings() */ void AudioCDProtocol::parseURLArgs(const QUrl & url) { d->clearURLargs(); - QString query(QUrl::fromPercentEncoding(url.query().toAscii())); + QString query(QUrl::fromPercentEncoding(url.query().toLatin1())); if (query.isEmpty()) return; const QStringList tokens(query.split(QLatin1Char( '&' ),QString::SkipEmptyParts)); for (QStringList::ConstIterator it(tokens.constBegin()); it != tokens.constEnd(); ++it) { const QString token(*it); int equalsPos = token.indexOf(QLatin1Char( '=' )); if (-1 == equalsPos) continue; const QString attribute(token.left(equalsPos)); const QString value(token.mid(equalsPos + 1)); if (attribute == QLatin1String("device")) d->device = value; else if (attribute == QLatin1String("paranoia_level")) d->paranoiaLevel = value.toInt(); else if (attribute == QLatin1String("fileNameTemplate")) d->fileNameTemplate = value; else if (attribute == QLatin1String("albumNameTemplate")) d->albumNameTemplate = value; else if (attribute == QLatin1String("fileLocationTemplate")) d->fileLocationTemplate = value; else if (attribute == QLatin1String("cddbChoice")) d->cddbUserChoice = value.toInt(); else if (attribute == QLatin1String("niceLevel")){ int niceLevel = value.toInt(); if(setpriority(PRIO_PROCESS, getpid(), niceLevel) != 0) qCDebug(AUDIOCD_KIO_LOG) << "Setting nice level to (" << niceLevel << ") failed."; } } } /** * Read the settings set by the kcm modules * @see parseURLArgs() */ void AudioCDProtocol::loadSettings() { const KConfig *config = new KConfig(QLatin1String( "kcmaudiocdrc"), KConfig::NoGlobals ); const KConfigGroup groupCDDA( config, "CDDA" ); d->device = QString(); // clear device d->paranoiaLevel = 1; // enable paranoia error correction, but allow skipping if (groupCDDA.readEntry("disable_paranoia", false)) { d->paranoiaLevel = 0; // disable all paranoia error correction } if (groupCDDA.readEntry("never_skip", true)) { d->paranoiaLevel = 2; // never skip on errors of the medium, should be default for high quality } d->reportErrors = groupCDDA.readEntry( "report_errors", false ); if(groupCDDA.hasKey("niceLevel")) { int niceLevel = groupCDDA.readEntry("niceLevel", 0); if(setpriority(PRIO_PROCESS, getpid(), niceLevel) != 0) qCDebug(AUDIOCD_KIO_LOG) << "Setting nice level to (" << niceLevel << ") failed."; } // The default track filename template const KConfigGroup groupFileName( config, "FileName" ); d->fileNameTemplate = groupFileName.readEntry("file_name_template", "%{trackartist} - %{number} - %{title}"); d->albumNameTemplate = groupFileName.readEntry("album_name_template", "%{albumartist} - %{albumtitle}"); if (groupFileName.readEntry("show_file_location", false)) d->fileLocationTemplate = groupFileName.readEntry("file_location_template", QString()); else d->fileLocationTemplate = QString(); d->rsearch = groupFileName.readEntry("regexp_search"); d->rreplace = groupFileName.readEntry("regexp_replace"); // if the regular expressions are enclosed in qoutes. remove them // otherwise it is not possible to search for a space " ", since an empty (only spaces) value is not // supported by KConfig, so the space has to be qouted, but then here the regexp searches really for " " // instead of just the space. Alex QRegExp qoutedString( QLatin1String( "^\".*\"$" )); if (qoutedString.exactMatch(d->rsearch)) { d->rsearch=d->rsearch.mid(1, d->rsearch.length()-2); } if (qoutedString.exactMatch(d->rreplace)) { d->rreplace=d->rreplace.mid(1, d->rreplace.length()-2); } // Tell the encoders to load their settings AudioCDEncoder *encoder; for (int i = encoders.size()-1; i >= 0; --i) { encoder = encoders.at(i); if (encoder->init()) encoder->loadSettings(); else encoders.removeAt(i); } delete config; } /** * Generates the track titles from the template using the cddb information. */ void AudioCDProtocol::generateTemplateTitles() { d->templateTitles.clear(); if (d->cddbResult != KCDDB::Success) { for (unsigned int i = 0; i < d->tracks; i++){ QString n; d->templateTitles.append( i18n("Track %1", n.sprintf("%02d", i + 1))); } return; } KCDDB::CDInfo info = d->cddbBestChoice; if(d->cddbUserChoice >= 0 && ((d->cddbUserChoice) < d->cddbList.count())) info = d->cddbList[d->cddbUserChoice]; // Then generate the templates d->templateTitles.clear(); for (uint i = 0; i < d->tracks; i++) { QHash macros; macros[QLatin1String( "albumartist" )] = info.get(Artist).toString(); macros[QLatin1String( "albumtitle" )] = info.get(Title).toString(); macros[QLatin1String( "title" )] = info.track(i).get(Title).toString(); macros[QLatin1String( "trackartist" )] = info.track(i).get(Artist).toString(); QString n; macros[QLatin1String( "number" )] = n.sprintf("%02d", i + 1); //macros["number"] = QString("%1").arg(i+1, 2, 10); macros[QLatin1String( "genre" )] = info.get(Genre).toString(); macros[QLatin1String( "year" )] = info.get(Year).toString(); QString title = KMacroExpander::expandMacros(d->fileNameTemplate, macros, QLatin1Char( '%' )).replace(QLatin1Char( '/' ), QLatin1String("%2F")); title.replace( QRegExp(d->rsearch), d->rreplace ); d->templateTitles.append(title); } QHash macros; macros[QLatin1String( "albumartist" )] = info.get(Artist).toString(); macros[QLatin1String( "albumtitle" )] = info.get(Title).toString(); macros[QLatin1String( "genre" )] = info.get(Genre).toString(); macros[QLatin1String( "year" )] = info.get(Year).toString(); d->templateAlbumName = KMacroExpander::expandMacros(d->albumNameTemplate, macros, QLatin1Char( '%' )).replace(QLatin1Char( '/' ), QLatin1String("%2F")); d->templateAlbumName.replace( QRegExp(d->rsearch), d->rreplace ); d->templateFileLocation = KMacroExpander::expandMacros(d->fileLocationTemplate, macros, QLatin1Char( '%' )); } /** * Based upon the cdparanoia ripping application * Only output BAD stuff * The higher the paranoia_read_limited_error the worse the problem is * FYI: PARANOIA_CB_READ & PARANOIA_CB_VERIFY happen continuously when ripping */ void paranoiaCallback(long, int function) { switch(function){ case PARANOIA_CB_VERIFY: //qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_VERIFY"; break; case PARANOIA_CB_READ: //qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_READ"; break; case PARANOIA_CB_FIXUP_EDGE: //qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_EDGE"; paranoia_read_limited_error = 2; break; case PARANOIA_CB_FIXUP_ATOM: //qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_ATOM"; paranoia_read_limited_error = 6; break; case PARANOIA_CB_READERR: qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_READERR"; paranoia_read_limited_error = 6; break; case PARANOIA_CB_SKIP: qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_SKIP"; paranoia_read_limited_error = 8; break; case PARANOIA_CB_OVERLAP: //qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_OVERLAP"; break; case PARANOIA_CB_SCRATCH: qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_SCRATCH"; paranoia_read_limited_error = 7; break; case PARANOIA_CB_DRIFT: //qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_DRIFT"; paranoia_read_limited_error = 4; break; case PARANOIA_CB_FIXUP_DROPPED: qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_DROPPED"; paranoia_read_limited_error = 5; break; case PARANOIA_CB_FIXUP_DUPED: qCDebug(AUDIOCD_KIO_LOG) << "PARANOIA_CB_FIXUP_DUPED"; paranoia_read_limited_error = 5; break; } } diff --git a/kcmaudiocd/CMakeLists.txt b/kcmaudiocd/CMakeLists.txt index 0a6e11e..2675cdd 100644 --- a/kcmaudiocd/CMakeLists.txt +++ b/kcmaudiocd/CMakeLists.txt @@ -1,27 +1,27 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kcmaudiocd\") include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../plugins ${CMAKE_BINARY_DIR}/kioslave/audiocd/kcmaudiocd ) ########### next target ############### set(kcm_audiocd_PART_SRCS kcmaudiocd.cpp) ki18n_wrap_ui(kcm_audiocd_PART_SRCS audiocdconfig.ui) add_library(kcm_audiocd ${kcm_audiocd_PART_SRCS}) -target_link_libraries(kcm_audiocd +target_link_libraries(kcm_audiocd KF5::I18n - KF5::KDELibs4Support + KF5::KCMUtils audiocdplugins ) install(TARGETS kcm_audiocd DESTINATION ${PLUGIN_INSTALL_DIR}) ########### install files ############### install(FILES audiocd.desktop DESTINATION ${SERVICES_INSTALL_DIR}) diff --git a/kcmaudiocd/audiocdconfig.ui b/kcmaudiocd/audiocdconfig.ui index e44f4ce..c09939b 100644 --- a/kcmaudiocd/audiocdconfig.ui +++ b/kcmaudiocd/audiocdconfig.ui @@ -1,748 +1,737 @@ AudiocdConfig 0 0 848 501 0 0 0 &General Qt::Vertical QSizePolicy::Expanding 20 210 If you uncheck this option, the slave will not try to use error correction which can be useful for reading damaged CDs. However, this feature can be problematic in some cases, so you can switch it off here. Use &error correction when reading the CD true &Skip on errors false Qt::Horizontal QSizePolicy::Fixed 20 20 Encoder Priority -19 19 5 Qt::Horizontal QSlider::NoTicks Highest false Lowest Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false Normal Qt::AlignCenter false &Names File Name (without extension) The following macros will be expanded: false Genre false %{year} false %{title} false Album Title false Year false Track Artist false Track Title false Album Artist false %{albumartist} false %{genre} false %{trackartist} false %{albumtitle} false Track Number false %{number} false - + Album Name The following macros will be expanded: false Year false %{albumartist} false %{year} false Genre false Album Artist false Album Title false %{genre} false %{albumtitle} false Qt::Vertical QSizePolicy::Expanding 20 31 - + This defines where files will appear in relation to the encoder root, you can use / to create subdirectories. E.g: %{albumartist}/%{albumtitle} Files Location true false The following macros will be expanded: false Year false %{albumartist} false %{year} false Genre false Album Artist false Album Title false %{genre} false %{albumtitle} false Qt::Vertical QSizePolicy::Expanding 20 31 - + %{albumtitle}/%{albumartist} Name Regular Expression Replacement Selection: true Regular expression used on all file names. For example using selection " " and replace with "_" would replace all the spaces with underlines. Qt::AlignVCenter true Input: false Output: false Example false Cool artist - example audio file.wav false Cool artist - example audio file.wav Replace with: false QFrame::HLine QFrame::Sunken Qt::Vertical QSizePolicy::Expanding 21 16 - - - KLineEdit - QLineEdit -
klineedit.h
-
-
tabWidget ec_enable_check ec_skip_check niceLevel fileNameLineEdit albumNameLineEdit kcfg_replaceInput kcfg_replaceOutput example - - kcmodule.h - klineedit.h - ec_enable_check toggled(bool) ec_skip_check setEnabled(bool) 20 20 20 20
diff --git a/kcmaudiocd/kcmaudiocd.cpp b/kcmaudiocd/kcmaudiocd.cpp index 1c5208d..4a70fb1 100644 --- a/kcmaudiocd/kcmaudiocd.cpp +++ b/kcmaudiocd/kcmaudiocd.cpp @@ -1,274 +1,270 @@ /* Copyright (C) 2001 Carsten Duvenhorst Copyright (C) 2005 Benjamin Meyer 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcmaudiocd.h" #include "audiocdplugins_version.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 K_PLUGIN_FACTORY(Factory, registerPlugin(); ) KAudiocdModule::KAudiocdModule(QWidget *parent, const QVariantList &lst) : KCModule(parent), configChanged(false) { Q_UNUSED(lst); QVBoxLayout *box = new QVBoxLayout(this); audioConfig = new AudiocdConfig(this); box->addWidget(audioConfig); setButtons(Default|Apply); config = new KConfig( QLatin1String( "kcmaudiocdrc" )); QList encoders; AudioCDEncoder::findAllPlugins(0, encoders); foreach (AudioCDEncoder *encoder, encoders) { if (encoder->init()) { KConfigSkeleton *config = NULL; QWidget *widget = encoder->getConfigureWidget(&config); if(widget && config){ audioConfig->tabWidget->addTab(widget, i18n("%1 Encoder", encoder->type())); KConfigDialogManager *configManager = new KConfigDialogManager(widget, config); encoderSettings.append(configManager); } } } qDeleteAll(encoders); encoders.clear(); for (int i = 0; i < encoderSettings.size(); ++i) { connect( encoderSettings.at( i ),SIGNAL(widgetModified()), this, SLOT(slotModuleChanged())); } //CDDA Options connect(audioConfig->ec_enable_check,SIGNAL(clicked()),this,SLOT(slotEcEnable())); connect(audioConfig->ec_skip_check,SIGNAL(clicked()),SLOT(slotConfigChanged())); connect(audioConfig->niceLevel,SIGNAL(valueChanged(int)),SLOT(slotConfigChanged())); // File Name connect(audioConfig->fileNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotConfigChanged())); connect(audioConfig->albumNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotConfigChanged())); connect(audioConfig->fileLocationLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotConfigChanged())); connect(audioConfig->fileLocationGroupBox, SIGNAL(clicked()), this, SLOT(slotConfigChanged())); connect( audioConfig->kcfg_replaceInput, SIGNAL(textChanged(QString)), this, SLOT(updateExample()) ); connect( audioConfig->kcfg_replaceOutput, SIGNAL(textChanged(QString)), this, SLOT(updateExample()) ); connect( audioConfig->example, SIGNAL(textChanged(QString)), this, SLOT(updateExample()) ); connect( audioConfig->kcfg_replaceInput, SIGNAL(textChanged(QString)), this, SLOT(slotConfigChanged()) ); connect( audioConfig->kcfg_replaceOutput, SIGNAL(textChanged(QString)), this, SLOT(slotConfigChanged()) ); connect( audioConfig->example, SIGNAL(textChanged(QString)), this, SLOT(slotConfigChanged()) ); KAboutData *about = new KAboutData("kcmaudiocd", i18n("KDE Audio CD IO Slave"), AUDIOCDPLUGINS_VERSION_STRING); about->addAuthor(i18n("Benjamin C. Meyer"), i18n("Former Maintainer"), "ben@meyerhome.net"); about->addAuthor(i18n("Carsten Duvenhorst"), i18n("Original Author"), "duvenhorst@duvnet.de"); setAboutData(about); } KAudiocdModule::~KAudiocdModule() { delete config; } QString removeQoutes(const QString& text) { QString deqoutedString=text; QRegExp qoutedStringRegExp( QLatin1String( "^\".*\"$" )); if (qoutedStringRegExp.exactMatch(text)) { deqoutedString=text.mid(1, text.length()-2); } return deqoutedString; } bool needsQoutes(const QString& text) { QRegExp spaceAtTheBeginning( QLatin1String( "^\\s+.*$" )); QRegExp spaceAtTheEnd( QLatin1String( "^.*\\s+$" )); return (spaceAtTheBeginning.exactMatch(text) || spaceAtTheEnd.exactMatch(text)); } void KAudiocdModule::updateExample() { QString text = audioConfig->example->text(); QString deqoutedReplaceInput=removeQoutes(audioConfig->kcfg_replaceInput->text()); QString deqoutedReplaceOutput=removeQoutes(audioConfig->kcfg_replaceOutput->text()); text.replace( QRegExp(deqoutedReplaceInput), deqoutedReplaceOutput ); audioConfig->exampleOutput->setText(text); } void KAudiocdModule::defaults() { audioConfig->ec_enable_check->setChecked(true); audioConfig->ec_skip_check->setChecked(false); audioConfig->niceLevel->setValue(0); audioConfig->kcfg_replaceInput->setText(""); audioConfig->kcfg_replaceOutput->setText(""); audioConfig->example->setText(i18n("Cool artist - example audio file.wav")); for (int i = 0; i < encoderSettings.size(); ++i) { encoderSettings.at( i )->updateWidgetsDefault(); } audioConfig->fileNameLineEdit->setText("%{trackartist} - %{number} - %{title}"); audioConfig->albumNameLineEdit->setText("%{albumartist} - %{albumtitle}"); } void KAudiocdModule::save() { if (!configChanged ) return; { KConfigGroup cg(config, "CDDA"); cg.writeEntry("disable_paranoia",!(audioConfig->ec_enable_check->isChecked())); cg.writeEntry("never_skip",!(audioConfig->ec_skip_check->isChecked())); cg.writeEntry("niceLevel", audioConfig->niceLevel->value()); } { KConfigGroup cg(config, "FileName"); cg.writeEntry("file_name_template", audioConfig->fileNameLineEdit->text()); cg.writeEntry("album_name_template", audioConfig->albumNameLineEdit->text()); cg.writeEntry("show_file_location", audioConfig->fileLocationGroupBox->isChecked()); cg.writeEntry("file_location_template", audioConfig->fileLocationLineEdit->text()); cg.writeEntry("regexp_example", audioConfig->example->text()); // save qouted if required QString replaceInput=audioConfig->kcfg_replaceInput->text(); QString replaceOutput=audioConfig->kcfg_replaceOutput->text(); if (needsQoutes(replaceInput)) { replaceInput=QString("\"")+replaceInput+QString("\""); } if (needsQoutes(replaceOutput)) { replaceOutput=QString("\"")+replaceOutput+QString("\""); } cg.writeEntry("regexp_search", replaceInput); cg.writeEntry("regexp_replace", replaceOutput); } for ( int i = 0;iupdateSettings(); } config->sync(); configChanged = false; } void KAudiocdModule::load() { { KConfigGroup cg(config, "CDDA"); audioConfig->ec_enable_check->setChecked(!(cg.readEntry("disable_paranoia",false))); audioConfig->ec_skip_check->setChecked(!(cg.readEntry("never_skip",true))); audioConfig->niceLevel->setValue(cg.readEntry("niceLevel", 0)); } { KConfigGroup cg(config, "FileName"); audioConfig->fileNameLineEdit->setText(cg.readEntry("file_name_template", "%{trackartist} - %{number} - %{title}")); audioConfig->albumNameLineEdit->setText(cg.readEntry("album_name_template", "%{albumartist} - %{albumtitle}")); audioConfig->fileLocationGroupBox->setChecked(cg.readEntry("show_file_location", false)); audioConfig->fileLocationLineEdit->setText(cg.readEntry("file_location_template", QString())); audioConfig->kcfg_replaceInput->setText(cg.readEntry("regexp_search")); audioConfig->kcfg_replaceOutput->setText(cg.readEntry("regexp_replace")); audioConfig->example->setText(cg.readEntry("example", i18n("Cool artist - example audio file.wav"))); } for ( int i = 0;i updateWidgets(); } } void KAudiocdModule::slotModuleChanged() { for ( int i = 0;ihasChanged() ) { slotConfigChanged(); break; } } } void KAudiocdModule::slotConfigChanged() { configChanged = true; emit changed(true); } /* # slot for the error correction settings */ void KAudiocdModule::slotEcEnable() { if (!(audioConfig->ec_skip_check->isChecked())) { audioConfig->ec_skip_check->setChecked(true); } else { if (audioConfig->ec_skip_check->isEnabled()) { audioConfig->ec_skip_check->setChecked(false); } } slotConfigChanged(); } QString KAudiocdModule::quickHelp() const { return i18n("

Audio CDs

The Audio CD IO-Slave enables you to easily" " create wav, MP3 or Ogg Vorbis files from your audio CD-ROMs or DVDs." " The slave is invoked by typing \"audiocd:/\" in Konqueror's location" " bar. In this module, you can configure" " encoding, and device settings. Note that MP3 and Ogg" " Vorbis encoding are only available if KDE was built with a recent" " version of the LAME or Ogg Vorbis libraries."); } #include "kcmaudiocd.moc" diff --git a/kcmaudiocd/kcmaudiocd.h b/kcmaudiocd/kcmaudiocd.h index 0535bb9..a9b2454 100644 --- a/kcmaudiocd/kcmaudiocd.h +++ b/kcmaudiocd/kcmaudiocd.h @@ -1,78 +1,78 @@ /* Copyright (C) 2001 Carsten Duvenhorst Copyright (C) 2005 Benjamin Meyer 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Permission is also granted to link this program with the Qt library, treating Qt like a library that normally accompanies the operating system kernel, whether or not that is in fact the case. */ #ifndef KCMAUDIOCD_H #define KCMAUDIOCD_H #include -#include -#include +#include +#include class KConfigDialogManager; #include "ui_audiocdconfig.h" class AudiocdConfig : public QWidget, public Ui::AudiocdConfig { public: explicit AudiocdConfig( QWidget *parent ) : QWidget( parent ) { setupUi( this ); } }; class KAudiocdModule : public KCModule { Q_OBJECT public: explicit KAudiocdModule(QWidget *parent=0, const QVariantList &args=QVariantList()); ~KAudiocdModule(); QString quickHelp() const; public slots: void defaults(); void save(); void load(); private slots: void updateExample(); void slotConfigChanged(); void slotEcEnable(); void slotModuleChanged(); private: KConfig *config; bool configChanged; int getBitrateIndex(int value); QList encoderSettings; AudiocdConfig *audioConfig; }; #endif // KCMAUDIOCD_H diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4bbe3f8..46200de 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,42 +1,41 @@ find_package(FLAC) if(FLAC_FOUND) add_subdirectory(flac) endif(FLAC_FOUND) find_package(OggVorbis) if(OGGVORBIS_FOUND AND HAVE_LIBVORBISENC2) add_subdirectory(vorbis) endif(OGGVORBIS_FOUND AND HAVE_LIBVORBISENC2) add_subdirectory(wav) add_subdirectory(lame) ########### next target ############### set(audiocdplugins_LIB_SRCS audiocdencoder.cpp) add_library(audiocdplugins SHARED ${audiocdplugins_LIB_SRCS}) target_link_libraries(audiocdplugins - KF5::KDELibs4Support KF5::Cddb KF5::KIOCore ) generate_export_header(audiocdplugins EXPORT_MACRO_NAME AUDIOCDPLUGINS_EXPORT DEPRECATED_MACRO_NAME AUDIOCDPLUGINS_EXPORT_DEPRECATED EXPORT_FILE_NAME audiocdplugins_export.h ) set_target_properties(audiocdplugins PROPERTIES VERSION ${AUDIOCDPLUGINS_VERSION_STRING} SOVERSION ${AUDIOCDPLUGINS_SOVERSION} ) install(TARGETS audiocdplugins ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install(FILES ${CMAKE_CURRENT_BINARY_DIR}/audiocdplugins_export.h audiocdencoder.h DESTINATION ${INCLUDE_INSTALL_DIR} COMPONENT Devel) diff --git a/plugins/flac/CMakeLists.txt b/plugins/flac/CMakeLists.txt index 453a1ea..34954ff 100644 --- a/plugins/flac/CMakeLists.txt +++ b/plugins/flac/CMakeLists.txt @@ -1,29 +1,30 @@ add_definitions(-DTRANSLATION_DOMAIN=\"audiocd_encoder_flac\") include_directories(${CMAKE_CURRENT_SOURCE_DIR}/.. ${FLAC_INCLUDE_DIR} ) ########### next target ############### # Remove this, and #ifdef in encoderflac when Makefile.am's are removed add_definitions(-DHAVE_LIBFLAC) set(audiocd_encoder_flac_PART_SRCS encoderflac.cpp ) ki18n_wrap_ui(audiocd_encoder_flac_PART_SRCS encoderflacconfig.ui) kconfig_add_kcfg_files(audiocd_encoder_flac_PART_SRCS audiocd_flac_encoder.kcfgc) add_library(audiocd_encoder_flac ${audiocd_encoder_flac_PART_SRCS}) set_target_properties(audiocd_encoder_flac PROPERTIES PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}") target_link_libraries(audiocd_encoder_flac - ${FLAC_LIBRARIES} + ${FLAC_LIBRARIES} + Qt5::Widgets KF5::I18n KF5::Cddb audiocdplugins ) install(TARGETS audiocd_encoder_flac DESTINATION ${PLUGIN_INSTALL_DIR}) install(FILES audiocd_flac_encoder.kcfg DESTINATION ${KCFG_INSTALL_DIR}) diff --git a/plugins/lame/CMakeLists.txt b/plugins/lame/CMakeLists.txt index 87f578c..6008654 100644 --- a/plugins/lame/CMakeLists.txt +++ b/plugins/lame/CMakeLists.txt @@ -1,33 +1,34 @@ add_definitions(-DTRANSLATION_DOMAIN=\"audiocd_encoder_lame\") include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) FIND_PROGRAM(LAME_EXECUTABLE NAMES lame) if(NOT LAME_EXECUTABLE) MESSAGE(STATUS "\"lame\" binary not found. Plugins will be compile but install it under your system to using this plugins") endif(NOT LAME_EXECUTABLE) ########### next target ############### set(audiocd_encoder_lame_PART_SRCS encoderlame.cpp ) ki18n_wrap_ui(audiocd_encoder_lame_PART_SRCS encoderlameconfig.ui) kconfig_add_kcfg_files(audiocd_encoder_lame_PART_SRCS audiocd_lame_encoder.kcfgc) add_library(audiocd_encoder_lame ${audiocd_encoder_lame_PART_SRCS}) set_target_properties(audiocd_encoder_lame PROPERTIES PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}") target_link_libraries(audiocd_encoder_lame + Qt5::Widgets KF5::I18n KF5::Cddb audiocdplugins ) install(TARGETS audiocd_encoder_lame DESTINATION ${PLUGIN_INSTALL_DIR}) ########### install files ############### install(FILES audiocd_lame_encoder.kcfg DESTINATION ${KCFG_INSTALL_DIR}) diff --git a/plugins/lame/encoderlame.cpp b/plugins/lame/encoderlame.cpp index 232d49f..7a16198 100644 --- a/plugins/lame/encoderlame.cpp +++ b/plugins/lame/encoderlame.cpp @@ -1,374 +1,369 @@ /* Copyright (C) 2005 Benjamin Meyer 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "encoderlame.h" #include "audiocd_lame_encoder.h" +#include "audiocd_kio_debug.h" #include -#include "audiocd_kio_debug.h" -#include -#include -#include -#include -#include -#include #include -#include +#include #include +#include +#include // #include "collectingprocess.h" Q_LOGGING_CATEGORY(AUDIOCD_KIO_LOG, "log_audiocd_kio") extern "C" { AUDIOCDPLUGINS_EXPORT void create_audiocd_encoders(KIO::SlaveBase *slave, QList &encoders) { encoders.append(new EncoderLame(slave)); } } static int bitrates[] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 }; class EncoderLame::Private { public: int bitrate; bool waitingForWrite; bool processHasExited; QString lastErrorMessage; QStringList genreList; uint lastSize; KProcess *currentEncodeProcess; QTemporaryFile *tempFile; }; EncoderLame::EncoderLame(KIO::SlaveBase *slave) : QObject(), AudioCDEncoder(slave) { d = new Private(); d->waitingForWrite = false; d->processHasExited = false; d->lastSize = 0; loadSettings(); } EncoderLame::~EncoderLame(){ delete d; } QWidget* EncoderLame::getConfigureWidget(KConfigSkeleton** manager) const { (*manager) = Settings::self(); EncoderLameConfig *config = new EncoderLameConfig(); config->cbr_settings->hide(); return config; } bool EncoderLame::init(){ // Determine if lame is installed on the system or not. if ( QStandardPaths::findExecutable( QStringLiteral( "lame" )).isEmpty() ) return false; // Ask lame for the list of genres it knows; otherwise it barfs when doing // e.g. lame --tg 'Vocal Jazz' KProcess proc; proc.setOutputChannelMode(KProcess::MergedChannels); proc << "lame" << "--genre-list"; proc.execute(); if(proc.exitStatus() != QProcess::NormalExit) return false; QByteArray array = proc.readAll(); QString str = QString::fromLocal8Bit( array ); d->genreList = str.split( '\n', QString::SkipEmptyParts ); // Remove the numbers in front of every genre for( QStringList::Iterator it = d->genreList.begin(); it != d->genreList.end(); ++it ) { QString& genre = *it; int i = 0; while ( i < genre.length() && ( genre[i].isSpace() || genre[i].isDigit() ) ) ++i; genre = genre.mid( i ); } //qCDebug(AUDIOCD_KIO_LOG) << "Available genres:" << d->genreList; return true; } void EncoderLame::loadSettings(){ // Generate the command line arguments for the current settings args.clear(); Settings *settings = Settings::self(); // Should we bitswap? I'm unsure about the proper logic for this. // KDE3 always swapped on little-endian hosts, // while #171065 says we shouldn't always do so. // So... let's make it configurable, at least. bool swap = false; switch (settings->byte_swap()) { case Settings::EnumByte_swap::Yes: swap = true; break; case Settings::EnumByte_swap::No: swap = false; break; default: #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN swap = false; // it looks like lame is now clever enough to do the right thing by default? (#171065) #else swap = false; #endif } if (swap) args << "-x"; int quality = settings->quality(); if (quality < 0 ) quality = quality *-1; if (quality > 9) quality = 9; int method = settings->bitrate_constant() ? 0 : 1 ; if (method == 0) { // Constant Bitrate Encoding args.append("-b"); args.append(QString("%1").arg(bitrates[settings->cbr_bitrate()])); d->bitrate = bitrates[settings->cbr_bitrate()]; args.append("-q"); args.append(QString("%1").arg(quality)); } else { // Variable Bitrate Encoding if (settings->vbr_average_br()) { args.append("--abr"); args.append(QString("%1").arg(bitrates[settings->vbr_mean_brate()])); d->bitrate = bitrates[settings->vbr_mean_brate()]; if (settings->vbr_min_br()){ args.append("-b"); args.append(QString("%1").arg(bitrates[settings->vbr_min_brate()])); } if (settings->vbr_min_hard()) args.append("-F"); if (settings->vbr_max_br()){ args.append("-B"); args.append(QString("%1").arg(bitrates[settings->vbr_max_brate()])); } } else { d->bitrate = 128; args.append("-V"); args.append(QString("%1").arg(quality)); } if ( !settings->vbr_xing_tag() ) args.append("-t"); } args.append("-m"); switch ( settings->stereo() ) { case 0: args.append("s"); break; case 1: args.append("j"); break; case 2: args.append("d"); break; case 3: args.append("m"); break; default: args.append("s"); break; } if(settings->copyright()) args.append("-c"); if(!settings->original()) args.append("-o"); if(settings->iso()) args.append("--strictly-enforce-ISO"); if(settings->crc()) args.append("-p"); if ( settings->enable_lowpass() ) { args.append("--lowpass"); args.append(QString("%1").arg(settings->lowfilterfreq())); if (settings->set_lpf_width()){ args.append("--lowpass-width"); args.append(QString("%1").arg(settings->lowfilterwidth())); } } if ( settings->enable_highpass()) { args.append("--hipass"); args.append(QString("%1").arg(settings->highfilterfreq())); if (settings->set_hpf_width()){ args.append("--hipass-width"); args.append(QString("%1").arg(settings->highfilterwidth())); } } } unsigned long EncoderLame::size(long time_secs) const { return (time_secs * d->bitrate * 1000)/8; } long EncoderLame::readInit(long /*size*/){ // Create KProcess d->currentEncodeProcess = new KProcess(); d->tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/kaudiocd_XXXXXX") + QLatin1String(".mp3")); d->tempFile->open(); d->lastErrorMessage.clear(); d->processHasExited = false; // -r raw/pcm // -s 44.1 (because it is raw you have to specify this) *(d->currentEncodeProcess) << "lame" << "--verbose" << "-r" << "-s" << "44.1"; *(d->currentEncodeProcess) << args; if(Settings::self()->id3_tag()) *d->currentEncodeProcess << trackInfo; // Read in stdin, output to the temp file *d->currentEncodeProcess << "-" << d->tempFile->fileName().toLatin1(); //qCDebug(AUDIOCD_KIO_LOG) << d->currentEncodeProcess->args(); connect(d->currentEncodeProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(receivedStdout())); connect(d->currentEncodeProcess, SIGNAL(readyReadStandardError()), this, SLOT(receivedStderr())); // connect(d->currentEncodeProcess, SIGNAL(bytesWritten()), // this, SLOT(wroteStdin())); connect(d->currentEncodeProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processExited(int,QProcess::ExitStatus))); // Launch! d->currentEncodeProcess->setOutputChannelMode(KProcess::SeparateChannels); d->currentEncodeProcess->start(); return 0; } void EncoderLame::processExited ( int exitCode, QProcess::ExitStatus /*status*/ ){ qCDebug(AUDIOCD_KIO_LOG) << "Lame Encoding process exited with: " << exitCode; d->processHasExited = true; } void EncoderLame::receivedStderr(){ QByteArray error = d->currentEncodeProcess->readAllStandardError(); qCDebug(AUDIOCD_KIO_LOG) << "Lame stderr: " << error; if ( !d->lastErrorMessage.isEmpty() ) d->lastErrorMessage += '\t'; d->lastErrorMessage += QString::fromLocal8Bit( error ); } void EncoderLame::receivedStdout(){ QString output = QString::fromLocal8Bit(d->currentEncodeProcess->readAllStandardOutput()); qCDebug(AUDIOCD_KIO_LOG) << "Lame stdout: " << output; } // void EncoderLame::wroteStdin(){ // d->waitingForWrite = false; // } long EncoderLame::read(qint16 *buf, int frames){ if(!d->currentEncodeProcess) return 0; if (d->processHasExited) return -1; // Pipe the raw data to lame char * cbuf = reinterpret_cast(buf); d->currentEncodeProcess->write(cbuf, frames * 4); // We can't return until the buffer has been written d->currentEncodeProcess->waitForBytesWritten(-1); // Determine the file size increase QFileInfo file(d->tempFile->fileName()); uint change = file.size() - d->lastSize; d->lastSize = file.size(); return change; } long EncoderLame::readCleanup(){ if(!d->currentEncodeProcess) return 0; // Let lame tag the first frame of the mp3 d->currentEncodeProcess->closeWriteChannel(); d->currentEncodeProcess->waitForFinished(-1); // Now copy the file out of the temp into kio QFile file( d->tempFile->fileName() ); if ( file.open( QIODevice::ReadOnly ) ) { char data[1024]; while ( !file.atEnd() ) { uint read = file.read(data, 1024); QByteArray output(data, read); ioslave->data(output); } file.close(); } // cleanup the process and temp delete d->currentEncodeProcess; delete d->tempFile; d->lastSize = 0; return 0; } void EncoderLame::fillSongInfo( KCDDB::CDInfo info, int track, const QString &comment ){ trackInfo.clear(); trackInfo.append("--tt"); trackInfo.append(info.track(track-1).get(Title).toString()); trackInfo.append("--ta"); trackInfo.append(info.track(track-1).get(Artist).toString()); trackInfo.append("--tl"); trackInfo.append(info.get(Title).toString()); trackInfo.append("--ty"); trackInfo.append(QString("%1").arg(info.get(Year).toString())); trackInfo.append("--tc"); trackInfo.append(comment); trackInfo.append("--tn"); trackInfo.append(QString("%1").arg(track)); const QString genre = info.get(Genre).toString(); if ( d->genreList.indexOf( genre ) != -1 ) { trackInfo.append("--tg"); trackInfo.append(genre); } } QString EncoderLame::lastErrorMessage() const { return d->lastErrorMessage; } -//#include "encoderlame.moc" diff --git a/plugins/vorbis/CMakeLists.txt b/plugins/vorbis/CMakeLists.txt index 8a07e93..1c8ecbf 100644 --- a/plugins/vorbis/CMakeLists.txt +++ b/plugins/vorbis/CMakeLists.txt @@ -1,28 +1,29 @@ add_definitions(-DTRANSLATION_DOMAIN=\"audiocd_encoder_vorbis\") include_directories(${CMAKE_CURRENT_SOURCE_DIR}/.. ${OGGVORBIS_INCLUDE_DIR} ${VORBIS_INCLUDE_DIR}) ########### next target ############### set(audiocd_encoder_vorbis_PART_SRCS encodervorbis.cpp) ki18n_wrap_ui(audiocd_encoder_vorbis_PART_SRCS encodervorbisconfig.ui) kconfig_add_kcfg_files(audiocd_encoder_vorbis_PART_SRCS audiocd_vorbis_encoder.kcfgc) add_library(audiocd_encoder_vorbis ${audiocd_encoder_vorbis_PART_SRCS}) set_target_properties(audiocd_encoder_vorbis PROPERTIES PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}") target_link_libraries(audiocd_encoder_vorbis ${OGGVORBIS_LIBRARIES} + Qt5::Widgets KF5::I18n KF5::Cddb audiocdplugins ) install(TARGETS audiocd_encoder_vorbis DESTINATION ${PLUGIN_INSTALL_DIR}) ########### install files ############### install(FILES audiocd_vorbis_encoder.kcfg DESTINATION ${KCFG_INSTALL_DIR}) diff --git a/plugins/vorbis/encodervorbis.cpp b/plugins/vorbis/encodervorbis.cpp index 5de699e..ed0fffa 100644 --- a/plugins/vorbis/encodervorbis.cpp +++ b/plugins/vorbis/encodervorbis.cpp @@ -1,304 +1,302 @@ /* Copyright (C) 2000 Rik Hemsley (rikkus) Copyright (C) 2000, 2001, 2002 Michael Matz Copyright (C) 2001 Carsten Duvenhorst Copyright (C) 2001 Adrian Schroeter Copyright (C) 2003 Richard Lärkäng Copyright (C) 2003 Scott Wheeler Copyright (C) 2004, 2005 Benjamin Meyer 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "encodervorbis.h" #include "audiocd_vorbis_encoder.h" #include #include #include -#include +#include #include #include -#include -#include extern "C" { AUDIOCDPLUGINS_EXPORT void create_audiocd_encoders(KIO::SlaveBase *slave, QList &encoders) { encoders.append(new EncoderVorbis(slave)); } } // these are the approx. bitrates for the current 5 Vorbis modes static const int vorbis_nominal_bitrates[] = { 128, 160, 192, 256, 350 }; static const int vorbis_bitrates[] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 350 }; class EncoderVorbis::Private { public: ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */ ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */ ogg_packet op; /* one raw packet of data for decode */ vorbis_info vi; /* struct that stores all the static vorbis bitstream settings */ vorbis_comment vc; /* struct that stores all the user comments */ vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */ vorbis_block vb; /* local working space for packet->PCM decode */ bool write_vorbis_comments; long vorbis_bitrate_lower; long vorbis_bitrate_upper; long vorbis_bitrate_nominal; int vorbis_encode_method; double vorbis_quality; int vorbis_bitrate; }; EncoderVorbis::EncoderVorbis(KIO::SlaveBase *slave) : AudioCDEncoder(slave) { d = new Private(); } EncoderVorbis::~EncoderVorbis(){ vorbis_info_clear(&d->vi); vorbis_comment_clear(&d->vc); delete d; } QWidget* EncoderVorbis::getConfigureWidget(KConfigSkeleton** manager) const { (*manager) = Settings::self(); EncoderVorbisConfig *config = new EncoderVorbisConfig(); config->kcfg_vorbis_quality->setRange(0.0, 10.0); config->kcfg_vorbis_quality->setSingleStep(0.1); config->vorbis_bitrate_settings->hide(); return config; } bool EncoderVorbis::init(){ vorbis_info_init(&d->vi); vorbis_comment_init(&d->vc); vorbis_comment_add_tag ( &d->vc, const_cast("kde-encoder"), const_cast("kio_audiocd") ); return true; } void EncoderVorbis::loadSettings(){ Settings *settings = Settings::self(); d->vorbis_encode_method = settings->vorbis_enc_method(); d->vorbis_quality = settings->vorbis_quality(); if ( settings->set_vorbis_min_br()) { d->vorbis_bitrate_lower = vorbis_bitrates[settings->vorbis_min_br()] * 1000; } else { d->vorbis_bitrate_lower = -1; } if ( settings->set_vorbis_max_br() ) { d->vorbis_bitrate_upper = vorbis_bitrates[settings->vorbis_max_br()] * 1000; } else { d->vorbis_bitrate_upper = -1; } // this is such a hack! if ( d->vorbis_bitrate_upper != -1 && d->vorbis_bitrate_lower != -1 ) { d->vorbis_bitrate = 104000; // empirically determined ...?! } else { d->vorbis_bitrate = 160 * 1000; } if ( settings->set_vorbis_nominal_br() ) { d->vorbis_bitrate_nominal = vorbis_nominal_bitrates[settings->vorbis_nominal_br()] * 1000; d->vorbis_bitrate = d->vorbis_bitrate_nominal; } else { d->vorbis_bitrate_nominal = -1; } d->write_vorbis_comments = settings->vorbis_comments(); // Now that we have read in the settings apply them to the encoder lib switch (d->vorbis_encode_method) { case 0: vorbis_encode_init_vbr(&d->vi, 2, 44100, d->vorbis_quality/10.0); break; case 1: vorbis_encode_init(&d->vi, 2, 44100, d->vorbis_bitrate_upper, d->vorbis_bitrate_nominal, d->vorbis_bitrate_lower); break; } } long EncoderVorbis::flush_vorbis(void) { long processed(0); while(vorbis_analysis_blockout(&d->vd,&d->vb)==1) { vorbis_analysis(&d->vb,NULL); /* Non-ancient case. */ vorbis_bitrate_addblock(&d->vb); while(vorbis_bitrate_flushpacket(&d->vd, &d->op)) { ogg_stream_packetin(&d->os,&d->op); while(int result=ogg_stream_pageout(&d->os,&d->og)) { if (!result) break; char * oggheader = reinterpret_cast(d->og.header); char * oggbody = reinterpret_cast(d->og.body); if (d->og.header_len) { ioslave->data(QByteArray::fromRawData(oggheader, d->og.header_len)); } if (d->og.body_len) { ioslave->data(QByteArray::fromRawData(oggbody, d->og.body_len)); } processed += d->og.header_len + d->og.body_len; } } } return processed; } unsigned long EncoderVorbis::size(long time_secs) const { long vorbis_size; switch (d->vorbis_encode_method) { case 0: // quality based encoding { // Estimated numbers based on the Vorbis FAQ: // http://www.xiph.org/archives/vorbis-faq/200203/0030.html static long vorbis_q_bitrate[] = { 60, 74, 86, 106, 120, 152, 183, 207, 239, 309, 440 }; long quality = static_cast(d->vorbis_quality); if (quality < 0 || quality > 10) quality = 3; vorbis_size = (time_secs * vorbis_q_bitrate[quality] * 1000) / 8; break; } default: // bitrate based encoding vorbis_size = (time_secs * d->vorbis_bitrate/8); break; } return vorbis_size; } const char * EncoderVorbis::mimeType() const{ return "audio/x-vorbis+ogg"; } long EncoderVorbis::readInit(long /*size*/){ ogg_packet header; ogg_packet header_comm; ogg_packet header_code; vorbis_analysis_init(&d->vd,&d->vi); vorbis_block_init(&d->vd,&d->vb); qsrand(time(NULL)); ogg_stream_init(&d->os,qrand()); vorbis_analysis_headerout(&d->vd,&d->vc,&header,&header_comm,&header_code); ogg_stream_packetin(&d->os,&header); ogg_stream_packetin(&d->os,&header_comm); ogg_stream_packetin(&d->os,&header_code); while (int result = ogg_stream_flush(&d->os,&d->og)) { if (!result) break; char * oggheader = reinterpret_cast(d->og.header); char * oggbody = reinterpret_cast(d->og.body); if (d->og.header_len) { ioslave->data(QByteArray::fromRawData(oggheader, d->og.header_len)); } if (d->og.body_len) { ioslave->data(QByteArray::fromRawData(oggbody, d->og.body_len)); } } return 0; } long EncoderVorbis::read(qint16 * buf, int frames){ int i; float **buffer=vorbis_analysis_buffer(&d->vd,frames); /* uninterleave samples */ for(i=0;ivd,i); return flush_vorbis(); } long EncoderVorbis::readCleanup(){ // send end-of-stream and flush the encoder vorbis_analysis_wrote(&d->vd,0); long processed = flush_vorbis(); ogg_stream_clear(&d->os); vorbis_block_clear(&d->vb); vorbis_dsp_clear(&d->vd); vorbis_info_clear(&d->vi); return processed; } void EncoderVorbis::fillSongInfo( KCDDB::CDInfo info, int track, const QString &comment ) { if( !d->write_vorbis_comments ) return; typedef QPair CommentField; QList commentFields; commentFields.append(CommentField("TITLE", info.track(track-1).get(Title))); commentFields.append(CommentField("ARTIST", info.track(track-1).get(Artist))); commentFields.append(CommentField("ALBUM", info.get(Title))); commentFields.append(CommentField("GENRE", info.get(Genre))); commentFields.append(CommentField("TRACKNUMBER", QString::number(track))); commentFields.append(CommentField("COMMENT", comment)); if (info.get(Year).toInt() > 0) { QDateTime dt( QDate(info.get(Year).toInt(), 1, 1) ); commentFields.append(CommentField("DATE",QLatin1String( dt.toString(Qt::ISODate).toUtf8().data() ))); } for(QList::iterator it = commentFields.begin(); it != commentFields.end(); ++it) { // if the value is not empty if(!(*it).second.toString().isEmpty()) { char *key = qstrdup((*it).first); char *value = qstrdup((*it).second.toString().toUtf8().data()); vorbis_comment_add_tag(&d->vc, key, value); delete [] key; delete [] value; } } }