summaryrefslogtreecommitdiffstats
path: root/xbmc/utils/LabelFormatter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/utils/LabelFormatter.cpp')
-rw-r--r--xbmc/utils/LabelFormatter.cpp479
1 files changed, 479 insertions, 0 deletions
diff --git a/xbmc/utils/LabelFormatter.cpp b/xbmc/utils/LabelFormatter.cpp
new file mode 100644
index 0000000..d66cc63
--- /dev/null
+++ b/xbmc/utils/LabelFormatter.cpp
@@ -0,0 +1,479 @@
+/*
+ * 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 "LabelFormatter.h"
+
+#include "FileItem.h"
+#include "RegExp.h"
+#include "ServiceBroker.h"
+#include "StringUtils.h"
+#include "URIUtils.h"
+#include "Util.h"
+#include "Variant.h"
+#include "addons/IAddon.h"
+#include "guilib/LocalizeStrings.h"
+#include "music/tags/MusicInfoTag.h"
+#include "pictures/PictureInfoTag.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "video/VideoInfoTag.h"
+
+#include <cassert>
+#include <cstdlib>
+#include <inttypes.h>
+
+using namespace MUSIC_INFO;
+
+/* LabelFormatter
+ * ==============
+ *
+ * The purpose of this class is to parse a mask string of the form
+ *
+ * [%N. ][%T] - [%A][ (%Y)]
+ *
+ * and provide methods to format up a CFileItem's label(s).
+ *
+ * The %N/%A/%B masks are replaced with the corresponding metadata (if available).
+ *
+ * Square brackets are treated as a metadata block. Anything inside the block other
+ * than the metadata mask is treated as either a prefix or postfix to the metadata. This
+ * information is only included in the formatted string when the metadata is non-empty.
+ *
+ * Any metadata tags not enclosed with square brackets are treated as if it were immediately
+ * enclosed - i.e. with no prefix or postfix.
+ *
+ * The special characters %, [, and ] can be produced using %%, %[, and %] respectively.
+ *
+ * Any static text outside of the metadata blocks is only shown if the blocks on either side
+ * (or just one side in the case of an end) are both non-empty.
+ *
+ * Examples (using the above expression):
+ *
+ * Track Title Artist Year Resulting Label
+ * ----- ----- ------ ---- ---------------
+ * 10 "40" U2 1983 10. "40" - U2 (1983)
+ * "40" U2 1983 "40" - U2 (1983)
+ * 10 U2 1983 10. U2 (1983)
+ * 10 "40" 1983 "40" (1983)
+ * 10 "40" U2 10. "40" - U2
+ * 10 "40" 10. "40"
+ *
+ * Available metadata masks:
+ *
+ * %A - Artist
+ * %B - Album
+ * %C - Programs count
+ * %D - Duration
+ * %E - episode number
+ * %F - FileName
+ * %G - Genre
+ * %H - season*100+episode
+ * %I - Size
+ * %J - Date
+ * %K - Movie/Game title
+ * %L - existing Label
+ * %M - number of episodes
+ * %N - Track Number
+ * %O - mpaa rating
+ * %P - production code
+ * %Q - file time
+ * %R - Movie rating
+ * %S - Disc Number
+ * %T - Title
+ * %U - studio
+ * %V - Playcount
+ * %W - Listeners
+ * %X - Bitrate
+ * %Y - Year
+ * %Z - tvshow title
+ * %a - Date Added
+ * %b - Total number of discs
+ * %c - Relevance - Used for actors' appearances
+ * %d - Date and Time
+ * %e - Original release date
+ * %f - bpm
+ * %p - Last Played
+ * %r - User Rating
+ * *t - Date Taken (suitable for Pictures)
+ */
+
+#define MASK_CHARS "NSATBGYFLDIJRCKMEPHZOQUVXWabcdefiprstuv"
+
+CLabelFormatter::CLabelFormatter(const std::string &mask, const std::string &mask2)
+{
+ // assemble our label masks
+ AssembleMask(0, mask);
+ AssembleMask(1, mask2);
+ // save a bool for faster lookups
+ m_hideFileExtensions = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWEXTENSIONS);
+}
+
+std::string CLabelFormatter::GetContent(unsigned int label, const CFileItem *item) const
+{
+ assert(label < 2);
+ assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1);
+
+ if (!item) return "";
+
+ std::string strLabel, dynamicLeft, dynamicRight;
+ for (unsigned int i = 0; i < m_dynamicContent[label].size(); i++)
+ {
+ dynamicRight = GetMaskContent(m_dynamicContent[label][i], item);
+ if ((i == 0 || !dynamicLeft.empty()) && !dynamicRight.empty())
+ strLabel += m_staticContent[label][i];
+ strLabel += dynamicRight;
+ dynamicLeft = dynamicRight;
+ }
+ if (!dynamicLeft.empty())
+ strLabel += m_staticContent[label][m_dynamicContent[label].size()];
+
+ return strLabel;
+}
+
+void CLabelFormatter::FormatLabel(CFileItem *item) const
+{
+ std::string maskedLabel = GetContent(0, item);
+ if (!maskedLabel.empty())
+ item->SetLabel(maskedLabel);
+ else if (!item->m_bIsFolder && m_hideFileExtensions)
+ item->RemoveExtension();
+}
+
+void CLabelFormatter::FormatLabel2(CFileItem *item) const
+{
+ item->SetLabel2(GetContent(1, item));
+}
+
+std::string CLabelFormatter::GetMaskContent(const CMaskString &mask, const CFileItem *item) const
+{
+ if (!item) return "";
+ const CMusicInfoTag *music = item->GetMusicInfoTag();
+ const CVideoInfoTag *movie = item->GetVideoInfoTag();
+ const CPictureInfoTag *pic = item->GetPictureInfoTag();
+ std::string value;
+ switch (mask.m_content)
+ {
+ case 'N':
+ if (music && music->GetTrackNumber() > 0)
+ value = StringUtils::Format("{:02}", music->GetTrackNumber());
+ if (movie&& movie->m_iTrack > 0)
+ value = StringUtils::Format("{:02}", movie->m_iTrack);
+ break;
+ case 'S':
+ if (music && music->GetDiscNumber() > 0)
+ value = StringUtils::Format("{:02}", music->GetDiscNumber());
+ break;
+ case 'A':
+ if (music && music->GetArtistString().size())
+ value = music->GetArtistString();
+ if (movie && movie->m_artist.size())
+ value = StringUtils::Join(movie->m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ break;
+ case 'T':
+ if (music && music->GetTitle().size())
+ value = music->GetTitle();
+ if (movie && movie->m_strTitle.size())
+ value = movie->m_strTitle;
+ break;
+ case 'Z':
+ if (movie && !movie->m_strShowTitle.empty())
+ value = movie->m_strShowTitle;
+ break;
+ case 'B':
+ if (music && music->GetAlbum().size())
+ value = music->GetAlbum();
+ else if (movie)
+ value = movie->m_strAlbum;
+ break;
+ case 'G':
+ if (music && music->GetGenre().size())
+ value = StringUtils::Join(music->GetGenre(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
+ if (movie && movie->m_genre.size())
+ value = StringUtils::Join(movie->m_genre, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ break;
+ case 'Y':
+ if (music)
+ value = music->GetYearString();
+ if (movie)
+ {
+ if (movie->m_firstAired.IsValid())
+ value = movie->m_firstAired.GetAsLocalizedDate();
+ else if (movie->HasYear())
+ value = std::to_string(movie->GetYear());
+ }
+ break;
+ case 'F': // filename
+ value = CUtil::GetTitleFromPath(item->GetPath(), item->m_bIsFolder && !item->IsFileFolder());
+ break;
+ case 'L':
+ value = item->GetLabel();
+ // is the label the actual file or folder name?
+ if (value == URIUtils::GetFileName(item->GetPath()))
+ { // label is the same as filename, clean it up as appropriate
+ value = CUtil::GetTitleFromPath(item->GetPath(), item->m_bIsFolder && !item->IsFileFolder());
+ }
+ break;
+ case 'D':
+ { // duration
+ int nDuration=0;
+ if (music)
+ nDuration = music->GetDuration();
+ if (movie)
+ nDuration = movie->GetDuration();
+ if (nDuration > 0)
+ value = StringUtils::SecondsToTimeString(nDuration, (nDuration >= 3600) ? TIME_FORMAT_H_MM_SS : TIME_FORMAT_MM_SS);
+ else if (item->m_dwSize > 0)
+ value = StringUtils::SizeToString(item->m_dwSize);
+ }
+ break;
+ case 'I': // size
+ if( (item->m_bIsFolder && item->m_dwSize != 0) || item->m_dwSize >= 0 )
+ value = StringUtils::SizeToString(item->m_dwSize);
+ break;
+ case 'J': // date
+ if (item->m_dateTime.IsValid())
+ value = item->m_dateTime.GetAsLocalizedDate();
+ break;
+ case 'Q': // time
+ if (item->m_dateTime.IsValid())
+ value = item->m_dateTime.GetAsLocalizedTime("", false);
+ break;
+ case 'R': // rating
+ if (music && music->GetRating() != 0.f)
+ value = StringUtils::Format("{:.1f}", music->GetRating());
+ else if (movie && movie->GetRating().rating != 0.f)
+ value = StringUtils::Format("{:.1f}", movie->GetRating().rating);
+ break;
+ case 'C': // programs count
+ value = std::to_string(item->m_iprogramCount);
+ break;
+ case 'c': // relevance
+ value = std::to_string(movie->m_relevance);
+ break;
+ case 'K':
+ value = item->m_strTitle;
+ break;
+ case 'M':
+ if (movie && movie->m_iEpisode > 0)
+ value = StringUtils::Format("{} {}", movie->m_iEpisode,
+ g_localizeStrings.Get(movie->m_iEpisode == 1 ? 20452 : 20453));
+ break;
+ case 'E':
+ if (movie && movie->m_iEpisode > 0)
+ { // episode number
+ if (movie->m_iSeason == 0)
+ value = StringUtils::Format("S{:02}", movie->m_iEpisode);
+ else
+ value = StringUtils::Format("{:02}", movie->m_iEpisode);
+ }
+ break;
+ case 'P':
+ if (movie) // tvshow production code
+ value = movie->m_strProductionCode;
+ break;
+ case 'H':
+ if (movie && movie->m_iEpisode > 0)
+ { // season*100+episode number
+ if (movie->m_iSeason == 0)
+ value = StringUtils::Format("S{:02}", movie->m_iEpisode);
+ else
+ value = StringUtils::Format("{}x{:02}", movie->m_iSeason, movie->m_iEpisode);
+ }
+ break;
+ case 'O':
+ if (movie)
+ {// MPAA Rating
+ value = movie->m_strMPAARating;
+ }
+ break;
+ case 'U':
+ if (movie && !movie->m_studio.empty())
+ {// Studios
+ value = StringUtils::Join(movie ->m_studio, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
+ }
+ break;
+ case 'V': // Playcount
+ if (music)
+ value = std::to_string(music->GetPlayCount());
+ if (movie)
+ value = std::to_string(movie->GetPlayCount());
+ break;
+ case 'X': // Bitrate
+ if( !item->m_bIsFolder && item->m_dwSize != 0 )
+ value = StringUtils::Format("{} kbps", item->m_dwSize);
+ break;
+ case 'W': // Listeners
+ if( !item->m_bIsFolder && music && music->GetListeners() != 0 )
+ value =
+ StringUtils::Format("{} {}", music->GetListeners(),
+ g_localizeStrings.Get(music->GetListeners() == 1 ? 20454 : 20455));
+ break;
+ case 'a': // Date Added
+ if (movie && movie->m_dateAdded.IsValid())
+ value = movie->m_dateAdded.GetAsLocalizedDate();
+ if (music && music->GetDateAdded().IsValid())
+ value = music->GetDateAdded().GetAsLocalizedDate();
+ break;
+ case 'b': // Total number of discs
+ if (music)
+ value = std::to_string(music->GetTotalDiscs());
+ break;
+ case 'e': // Original release date
+ if (music)
+ {
+ value = music->GetOriginalDate();
+ if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bMusicLibraryUseISODates)
+ value = StringUtils::ISODateToLocalizedDate(value);
+ }
+ break;
+ case 'd': // date and time
+ if (item->m_dateTime.IsValid())
+ value = item->m_dateTime.GetAsLocalizedDateTime();
+ break;
+ case 'p': // Last played
+ if (movie && movie->m_lastPlayed.IsValid())
+ value = movie->m_lastPlayed.GetAsLocalizedDate();
+ if (music && music->GetLastPlayed().IsValid())
+ value = music->GetLastPlayed().GetAsLocalizedDate();
+ break;
+ case 'r': // userrating
+ if (movie && movie->m_iUserRating != 0)
+ value = std::to_string(movie->m_iUserRating);
+ if (music && music->GetUserrating() != 0)
+ value = std::to_string(music->GetUserrating());
+ break;
+ case 't': // Date Taken
+ if (pic && pic->GetDateTimeTaken().IsValid())
+ value = pic->GetDateTimeTaken().GetAsLocalizedDate();
+ break;
+ case 's': // Addon status
+ if (item->HasProperty("Addon.Status"))
+ value = item->GetProperty("Addon.Status").asString();
+ break;
+ case 'i': // Install date
+ if (item->HasAddonInfo() && item->GetAddonInfo()->InstallDate().IsValid())
+ value = item->GetAddonInfo()->InstallDate().GetAsLocalizedDate();
+ break;
+ case 'u': // Last used
+ if (item->HasAddonInfo() && item->GetAddonInfo()->LastUsed().IsValid())
+ value = item->GetAddonInfo()->LastUsed().GetAsLocalizedDate();
+ break;
+ case 'v': // Last updated
+ if (item->HasAddonInfo() && item->GetAddonInfo()->LastUpdated().IsValid())
+ value = item->GetAddonInfo()->LastUpdated().GetAsLocalizedDate();
+ break;
+ case 'f': // BPM
+ if (music)
+ value = std::to_string(music->GetBPM());
+ break;
+ }
+ if (!value.empty())
+ return mask.m_prefix + value + mask.m_postfix;
+ return "";
+}
+
+void CLabelFormatter::SplitMask(unsigned int label, const std::string &mask)
+{
+ assert(label < 2);
+ CRegExp reg;
+ reg.RegComp("%([" MASK_CHARS "])");
+ std::string work(mask);
+ int findStart = -1;
+ while ((findStart = reg.RegFind(work.c_str())) >= 0)
+ { // we've found a match
+ m_staticContent[label].push_back(work.substr(0, findStart));
+ m_dynamicContent[label].emplace_back("", reg.GetMatch(1)[0], "");
+ work = work.substr(findStart + reg.GetFindLen());
+ }
+ m_staticContent[label].push_back(work);
+}
+
+void CLabelFormatter::AssembleMask(unsigned int label, const std::string& mask)
+{
+ assert(label < 2);
+ m_staticContent[label].clear();
+ m_dynamicContent[label].clear();
+
+ // we want to match [<prefix>%A<postfix]
+ // but allow %%, %[, %] to be in the prefix and postfix. Anything before the first [
+ // could be a mask that's not surrounded with [], so pass to SplitMask.
+ CRegExp reg;
+ reg.RegComp("(^|[^%])\\[(([^%]|%%|%\\]|%\\[)*)%([" MASK_CHARS "])(([^%]|%%|%\\]|%\\[)*)\\]");
+ std::string work(mask);
+ int findStart = -1;
+ while ((findStart = reg.RegFind(work.c_str())) >= 0)
+ { // we've found a match for a pre/postfixed string
+ // send anything
+ SplitMask(label, work.substr(0, findStart) + reg.GetMatch(1));
+ m_dynamicContent[label].emplace_back(reg.GetMatch(2), reg.GetMatch(4)[0], reg.GetMatch(5));
+ work = work.substr(findStart + reg.GetFindLen());
+ }
+ SplitMask(label, work);
+ assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1);
+}
+
+bool CLabelFormatter::FillMusicTag(const std::string &fileName, CMusicInfoTag *tag) const
+{
+ // run through and find static content to split the string up
+ size_t pos1 = fileName.find(m_staticContent[0][0], 0);
+ if (pos1 == std::string::npos)
+ return false;
+ for (unsigned int i = 1; i < m_staticContent[0].size(); i++)
+ {
+ size_t pos2 = m_staticContent[0][i].size() ? fileName.find(m_staticContent[0][i], pos1) : fileName.size();
+ if (pos2 == std::string::npos)
+ return false;
+ // found static content - thus we have the dynamic content surrounded
+ FillMusicMaskContent(m_dynamicContent[0][i - 1].m_content, fileName.substr(pos1, pos2 - pos1), tag);
+ pos1 = pos2 + m_staticContent[0][i].size();
+ }
+ return true;
+}
+
+void CLabelFormatter::FillMusicMaskContent(const char mask, const std::string &value, CMusicInfoTag *tag) const
+{
+ if (!tag) return;
+ switch (mask)
+ {
+ case 'N':
+ tag->SetTrackNumber(atol(value.c_str()));
+ break;
+ case 'S':
+ tag->SetDiscNumber(atol(value.c_str()));
+ break;
+ case 'A':
+ tag->SetArtist(value);
+ break;
+ case 'T':
+ tag->SetTitle(value);
+ break;
+ case 'B':
+ tag->SetAlbum(value);
+ break;
+ case 'G':
+ tag->SetGenre(value);
+ break;
+ case 'Y':
+ tag->SetYear(atol(value.c_str()));
+ break;
+ case 'D':
+ tag->SetDuration(StringUtils::TimeStringToSeconds(value));
+ break;
+ case 'R': // rating
+ tag->SetRating(value[0]);
+ break;
+ case 'r': // userrating
+ tag->SetUserrating(value[0]);
+ break;
+ case 'b': // total discs
+ tag->SetTotalDiscs(atol(value.c_str()));
+ break;
+ }
+}
+