/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "CDDARipper.h" #include "CDDARipJob.h" #include "FileItem.h" #include "ServiceBroker.h" #include "Util.h" #include "addons/AddonManager.h" #include "addons/addoninfo/AddonInfo.h" #include "addons/addoninfo/AddonType.h" #include "filesystem/CDDADirectory.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" #include "messaging/helpers/DialogOKHelper.h" #include "music/MusicDatabase.h" #include "music/MusicDbUrl.h" #include "music/MusicLibraryQueue.h" #include "music/infoscanner/MusicInfoScanner.h" #include "music/tags/MusicInfoTag.h" #include "music/tags/MusicInfoTagLoaderFactory.h" #include "settings/AdvancedSettings.h" #include "settings/MediaSourceSettings.h" #include "settings/SettingPath.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "settings/windows/GUIControlSettings.h" #include "storage/MediaManager.h" #include "utils/LabelFormatter.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/Variant.h" #include "utils/log.h" using namespace ADDON; using namespace XFILE; using namespace MUSIC_INFO; using namespace KODI::MESSAGING; using namespace KODI::CDRIP; CCDDARipper& CCDDARipper::GetInstance() { static CCDDARipper sRipper; return sRipper; } CCDDARipper::CCDDARipper() : CJobQueue(false, 1) //enforce fifo and non-parallel processing { } CCDDARipper::~CCDDARipper() = default; // rip a single track from cd bool CCDDARipper::RipTrack(CFileItem* pItem) { // don't rip non cdda items if (!URIUtils::HasExtension(pItem->GetPath(), ".cdda")) { CLog::Log(LOGDEBUG, "CCDDARipper::{} - File '{}' is not a cdda track", __func__, pItem->GetPath()); return false; } // construct directory where the track is stored std::string strDirectory; int legalType; if (!CreateAlbumDir(*pItem->GetMusicInfoTag(), strDirectory, legalType)) return false; std::string strFile = URIUtils::AddFileToFolder( strDirectory, CUtil::MakeLegalFileName(GetTrackName(pItem), legalType)); AddJob(new CCDDARipJob(pItem->GetPath(), strFile, *pItem->GetMusicInfoTag(), CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( CSettings::SETTING_AUDIOCDS_ENCODER))); return true; } bool CCDDARipper::RipCD() { // return here if cd is not a CDDA disc MEDIA_DETECT::CCdInfo* pInfo = CServiceBroker::GetMediaManager().GetCdInfo(); if (pInfo == nullptr || !pInfo->IsAudio(1)) { CLog::Log(LOGDEBUG, "CCDDARipper::{} - CD is not an audio cd", __func__); return false; } // get cd cdda contents CFileItemList vecItems; XFILE::CCDDADirectory directory; directory.GetDirectory(CURL("cdda://local/"), vecItems); // get cddb info for (int i = 0; i < vecItems.Size(); ++i) { CFileItemPtr pItem = vecItems[i]; CMusicInfoTagLoaderFactory factory; std::unique_ptr pLoader(factory.CreateLoader(*pItem)); if (nullptr != pLoader) { pLoader->Load(pItem->GetPath(), *pItem->GetMusicInfoTag()); // get tag from file if (!pItem->GetMusicInfoTag()->Loaded()) break; // No CDDB info available } } // construct directory where the tracks are stored std::string strDirectory; int legalType; if (!CreateAlbumDir(*vecItems[0]->GetMusicInfoTag(), strDirectory, legalType)) return false; // rip all tracks one by one const std::shared_ptr settings = CServiceBroker::GetSettingsComponent()->GetSettings(); for (int i = 0; i < vecItems.Size(); i++) { CFileItemPtr item = vecItems[i]; // construct filename std::string strFile = URIUtils::AddFileToFolder( strDirectory, CUtil::MakeLegalFileName(GetTrackName(item.get()), legalType)); // don't rip non cdda items if (item->GetPath().find(".cdda") == std::string::npos) continue; bool eject = settings->GetBool(CSettings::SETTING_AUDIOCDS_EJECTONRIP) && i == vecItems.Size() - 1; AddJob(new CCDDARipJob(item->GetPath(), strFile, *item->GetMusicInfoTag(), settings->GetInt(CSettings::SETTING_AUDIOCDS_ENCODER), eject)); } return true; } bool CCDDARipper::CreateAlbumDir(const MUSIC_INFO::CMusicInfoTag& infoTag, std::string& strDirectory, int& legalType) { std::shared_ptr recordingpathSetting = std::static_pointer_cast( CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting( CSettings::SETTING_AUDIOCDS_RECORDINGPATH)); if (recordingpathSetting != nullptr) { strDirectory = recordingpathSetting->GetValue(); if (strDirectory.empty()) { if (CGUIControlButtonSetting::GetPath(recordingpathSetting, &g_localizeStrings)) strDirectory = recordingpathSetting->GetValue(); } } URIUtils::AddSlashAtEnd(strDirectory); if (strDirectory.size() < 3) { // no rip path has been set, show error CLog::Log(LOGERROR, "CCDDARipper::{} - Required path has not been set", __func__); HELPERS::ShowOKDialogText(CVariant{257}, CVariant{608}); return false; } legalType = LEGAL_NONE; CFileItem ripPath(strDirectory, true); if (ripPath.IsSmb()) legalType = LEGAL_WIN32_COMPAT; #ifdef TARGET_WINDOWS if (ripPath.IsHD()) legalType = LEGAL_WIN32_COMPAT; #endif std::string strAlbumDir = GetAlbumDirName(infoTag); if (!strAlbumDir.empty()) { strDirectory = URIUtils::AddFileToFolder(strDirectory, strAlbumDir); URIUtils::AddSlashAtEnd(strDirectory); } strDirectory = CUtil::MakeLegalPath(strDirectory, legalType); // Create directory if it doesn't exist if (!CUtil::CreateDirectoryEx(strDirectory)) { CLog::Log(LOGERROR, "CCDDARipper::{} - Unable to create directory '{}'", __func__, strDirectory); return false; } return true; } std::string CCDDARipper::GetAlbumDirName(const MUSIC_INFO::CMusicInfoTag& infoTag) { std::string strAlbumDir; // use audiocds.trackpathformat setting to format // directory name where CD tracks will be stored, // use only format part ending at the last '/' strAlbumDir = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( CSettings::SETTING_AUDIOCDS_TRACKPATHFORMAT); size_t pos = strAlbumDir.find_last_of("/\\"); if (pos == std::string::npos) return ""; // no directory strAlbumDir = strAlbumDir.substr(0, pos); // replace %A with album artist name if (strAlbumDir.find("%A") != std::string::npos) { std::string strAlbumArtist = infoTag.GetAlbumArtistString(); if (strAlbumArtist.empty()) strAlbumArtist = infoTag.GetArtistString(); if (strAlbumArtist.empty()) strAlbumArtist = "Unknown Artist"; else StringUtils::Replace(strAlbumArtist, '/', '_'); StringUtils::Replace(strAlbumDir, "%A", strAlbumArtist); } // replace %B with album title if (strAlbumDir.find("%B") != std::string::npos) { std::string strAlbum = infoTag.GetAlbum(); if (strAlbum.empty()) strAlbum = StringUtils::Format("Unknown Album {}", CDateTime::GetCurrentDateTime().GetAsLocalizedDateTime()); else StringUtils::Replace(strAlbum, '/', '_'); StringUtils::Replace(strAlbumDir, "%B", strAlbum); } // replace %G with genre if (strAlbumDir.find("%G") != std::string::npos) { std::string strGenre = StringUtils::Join( infoTag.GetGenre(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); if (strGenre.empty()) strGenre = "Unknown Genre"; else StringUtils::Replace(strGenre, '/', '_'); StringUtils::Replace(strAlbumDir, "%G", strGenre); } // replace %Y with year if (strAlbumDir.find("%Y") != std::string::npos) { std::string strYear = infoTag.GetYearString(); if (strYear.empty()) strYear = "Unknown Year"; else StringUtils::Replace(strYear, '/', '_'); StringUtils::Replace(strAlbumDir, "%Y", strYear); } return strAlbumDir; } std::string CCDDARipper::GetTrackName(CFileItem* item) { // get track number from "cdda://local/01.cdda" int trackNumber = atoi(item->GetPath().substr(13, item->GetPath().size() - 13 - 5).c_str()); // Format up our ripped file label CFileItem destItem(*item); destItem.SetLabel(""); // get track file name format from audiocds.trackpathformat setting, // use only format part starting from the last '/' std::string strFormat = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( CSettings::SETTING_AUDIOCDS_TRACKPATHFORMAT); size_t pos = strFormat.find_last_of("/\\"); if (pos != std::string::npos) strFormat.erase(0, pos + 1); CLabelFormatter formatter(strFormat, ""); formatter.FormatLabel(&destItem); // grab the label to use it as our ripped filename std::string track = destItem.GetLabel(); if (track.empty()) track = StringUtils::Format("{}{:02}", "Track-", trackNumber); const std::string encoder = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( CSettings::SETTING_AUDIOCDS_ENCODER); const AddonInfoPtr addonInfo = CServiceBroker::GetAddonMgr().GetAddonInfo(encoder, AddonType::AUDIOENCODER); if (addonInfo) track += addonInfo->Type(AddonType::AUDIOENCODER)->GetValue("@extension").asString(); return track; } void CCDDARipper::OnJobComplete(unsigned int jobID, bool success, CJob* job) { if (success) { if (CJobQueue::QueueEmpty()) { std::string dir = URIUtils::GetDirectory(static_cast(job)->GetOutput()); bool unimportant; int source = CUtil::GetMatchingSource( dir, *CMediaSourceSettings::GetInstance().CMediaSourceSettings::GetSources("music"), unimportant); CMusicDatabase database; database.Open(); if (source >= 0 && database.InsideScannedPath(dir)) CMusicLibraryQueue::GetInstance().ScanLibrary( dir, MUSIC_INFO::CMusicInfoScanner::SCAN_NORMAL, false); database.Close(); } return CJobQueue::OnJobComplete(jobID, success, job); } CancelJobs(); }