2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "LabelFormatter.h"
22 #include "settings/GUISettings.h"
23 #include "settings/AdvancedSettings.h"
26 #include "video/VideoInfoTag.h"
27 #include "music/tags/MusicInfoTag.h"
29 #include "StringUtils.h"
31 #include "guilib/LocalizeStrings.h"
33 using namespace MUSIC_INFO;
38 * The purpose of this class is to parse a mask string of the form
40 * [%N. ][%T] - [%A][ (%Y)]
42 * and provide methods to format up a CFileItem's label(s).
44 * The %N/%A/%B masks are replaced with the corresponding metadata (if available).
46 * Square brackets are treated as a metadata block. Anything inside the block other
47 * than the metadata mask is treated as either a prefix or postfix to the metadata. This
48 * information is only included in the formatted string when the metadata is non-empty.
50 * Any metadata tags not enclosed with square brackets are treated as if it were immediately
51 * enclosed - i.e. with no prefix or postfix.
53 * The special characters %, [, and ] can be produced using %%, %[, and %] respectively.
55 * Any static text outside of the metadata blocks is only shown if the blocks on either side
56 * (or just one side in the case of an end) are both non-empty.
58 * Examples (using the above expression):
60 * Track Title Artist Year Resulting Label
61 * ----- ----- ------ ---- ---------------
62 * 10 "40" U2 1983 10. "40" - U2 (1983)
63 * "40" U2 1983 "40" - U2 (1983)
64 * 10 U2 1983 10. U2 (1983)
65 * 10 "40" 1983 "40" (1983)
66 * 10 "40" U2 10. "40" - U2
69 * Available metadata masks:
78 * %H - season*100+episode
81 * %K - Movie/Game title
83 * %M - number of episodes
86 * %P - production code
101 #define MASK_CHARS "NSATBGYFLDIJRCKMEPHZOQUVXWap"
103 CLabelFormatter::CLabelFormatter(const CStdString &mask, const CStdString &mask2)
105 // assemble our label masks
106 AssembleMask(0, mask);
107 AssembleMask(1, mask2);
108 // save a bool for faster lookups
109 m_hideFileExtensions = !g_guiSettings.GetBool("filelists.showextensions");
112 CStdString CLabelFormatter::GetContent(unsigned int label, const CFileItem *item) const
115 assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1);
117 if (!item) return "";
119 CStdString strLabel, dynamicLeft, dynamicRight;
120 for (unsigned int i = 0; i < m_dynamicContent[label].size(); i++)
122 dynamicRight = GetMaskContent(m_dynamicContent[label][i], item);
123 if ((i == 0 || !dynamicLeft.IsEmpty()) && !dynamicRight.IsEmpty())
124 strLabel += m_staticContent[label][i];
125 strLabel += dynamicRight;
126 dynamicLeft = dynamicRight;
128 if (!dynamicLeft.IsEmpty())
129 strLabel += m_staticContent[label][m_dynamicContent[label].size()];
134 void CLabelFormatter::FormatLabel(CFileItem *item) const
136 CStdString maskedLabel = GetContent(0, item);
137 if (!maskedLabel.IsEmpty())
138 item->SetLabel(maskedLabel);
139 else if (!item->m_bIsFolder && m_hideFileExtensions)
140 item->RemoveExtension();
143 void CLabelFormatter::FormatLabel2(CFileItem *item) const
145 item->SetLabel2(GetContent(1, item));
148 CStdString CLabelFormatter::GetMaskContent(const CMaskString &mask, const CFileItem *item) const
150 if (!item) return "";
151 const CMusicInfoTag *music = item->GetMusicInfoTag();
152 const CVideoInfoTag *movie = item->GetVideoInfoTag();
154 switch (mask.m_content)
157 if (music && music->GetTrackNumber() > 0)
158 value.Format("%02.2i", music->GetTrackNumber());
159 if (movie&& movie->m_iTrack > 0)
160 value.Format("%02.2i", movie->m_iTrack);
163 if (music && music->GetDiscNumber() > 0)
164 value.Format("%02.2i", music->GetDiscNumber());
167 if (music && music->GetArtist().size())
168 value = StringUtils::Join(music->GetArtist(), g_advancedSettings.m_musicItemSeparator);
169 if (movie && movie->m_artist.size())
170 value = StringUtils::Join(movie->m_artist, g_advancedSettings.m_videoItemSeparator);
173 if (music && music->GetTitle().size())
174 value = music->GetTitle();
175 if (movie && movie->m_strTitle.size())
176 value = movie->m_strTitle;
179 if (movie && !movie->m_strShowTitle.IsEmpty())
180 value = movie->m_strShowTitle;
183 if (music && music->GetAlbum().size())
184 value = music->GetAlbum();
186 value = movie->m_strAlbum;
189 if (music && music->GetGenre().size())
190 value = StringUtils::Join(music->GetGenre(), g_advancedSettings.m_musicItemSeparator);
191 if (movie && movie->m_genre.size())
192 value = StringUtils::Join(movie->m_genre, g_advancedSettings.m_videoItemSeparator);
196 value = music->GetYearString();
199 if (movie->m_firstAired.IsValid())
200 value = movie->m_firstAired.GetAsLocalizedDate();
201 else if (movie->m_premiered.IsValid())
202 value = movie->m_premiered.GetAsLocalizedDate();
203 else if (movie->m_iYear > 0)
204 value.Format("%i",movie->m_iYear);
207 case 'F': // filename
208 value = CUtil::GetTitleFromPath(item->GetPath(), item->m_bIsFolder && !item->IsFileFolder());
211 value = item->GetLabel();
212 // is the label the actual file or folder name?
213 if (value == URIUtils::GetFileName(item->GetPath()))
214 { // label is the same as filename, clean it up as appropriate
215 value = CUtil::GetTitleFromPath(item->GetPath(), item->m_bIsFolder && !item->IsFileFolder());
222 nDuration = music->GetDuration();
224 nDuration = movie->GetDuration();
226 value = StringUtils::SecondsToTimeString(nDuration, (nDuration >= 3600) ? TIME_FORMAT_H_MM_SS : TIME_FORMAT_MM_SS);
227 else if (item->m_dwSize > 0)
228 value = StringUtils::SizeToString(item->m_dwSize);
232 if( !item->m_bIsFolder || item->m_dwSize != 0 )
233 value = StringUtils::SizeToString(item->m_dwSize);
236 if (item->m_dateTime.IsValid())
237 value = item->m_dateTime.GetAsLocalizedDate();
240 if (item->m_dateTime.IsValid())
241 value = item->m_dateTime.GetAsLocalizedTime("", false);
244 if (music && music->GetRating() != '0')
245 value = music->GetRating();
246 else if (movie && movie->m_fRating != 0.f)
247 value.Format("%.1f", movie->m_fRating);
249 case 'C': // programs count
250 value.Format("%i", item->m_iprogramCount);
253 value = item->m_strTitle;
256 if (movie && movie->m_iEpisode > 0)
257 value.Format("%i %s", movie->m_iEpisode,g_localizeStrings.Get(movie->m_iEpisode == 1 ? 20452 : 20453));
260 if (movie && movie->m_iEpisode > 0)
262 if (movie->m_iSpecialSortEpisode > 0)
263 value.Format("S%02.2i", movie->m_iEpisode);
265 value.Format("%02.2i", movie->m_iEpisode);
269 if (movie) // tvshow production code
270 value = movie->m_strProductionCode;
273 if (movie && movie->m_iEpisode > 0)
274 { // season*100+episode number
275 if (movie->m_iSpecialSortSeason > 0)
276 value.Format("Sx%02.2i", movie->m_iEpisode);
278 value.Format("%ix%02.2i", movie->m_iSeason,movie->m_iEpisode);
282 if (movie && movie->m_strMPAARating)
284 value = movie->m_strMPAARating;
288 if (movie && movie->m_studio.size() > 0)
290 value = StringUtils::Join(movie ->m_studio, g_advancedSettings.m_videoItemSeparator);
293 case 'V': // Playcount
295 value.Format("%i", music->GetPlayCount());
297 value.Format("%i", movie->m_playCount);
300 if( !item->m_bIsFolder && item->m_dwSize != 0 )
301 value.Format("%i kbps", item->m_dwSize);
303 case 'W': // Listeners
304 if( !item->m_bIsFolder && music && music->GetListeners() != 0 )
305 value.Format("%i %s", music->GetListeners(), g_localizeStrings.Get(music->GetListeners() == 1 ? 20454 : 20455));
307 case 'a': // Date Added
308 if (movie && movie->m_dateAdded.IsValid())
309 value = movie->m_dateAdded.GetAsLocalizedDate();
311 case 'p': // Last played
312 if (movie && movie->m_lastPlayed.IsValid())
313 value = movie->m_lastPlayed.GetAsLocalizedDate();
316 if (!value.IsEmpty())
317 return mask.m_prefix + value + mask.m_postfix;
321 void CLabelFormatter::SplitMask(unsigned int label, const CStdString &mask)
325 reg.RegComp("%([" MASK_CHARS "])");
326 CStdString work(mask);
328 while ((findStart = reg.RegFind(work.c_str())) >= 0)
329 { // we've found a match
330 m_staticContent[label].push_back(work.Left(findStart));
331 m_dynamicContent[label].push_back(CMaskString("",
332 reg.GetReplaceString("\\1")[0], ""));
333 work = work.Mid(findStart + reg.GetFindLen());
335 m_staticContent[label].push_back(work);
338 void CLabelFormatter::AssembleMask(unsigned int label, const CStdString& mask)
341 m_staticContent[label].clear();
342 m_dynamicContent[label].clear();
344 // we want to match [<prefix>%A<postfix]
345 // but allow %%, %[, %] to be in the prefix and postfix. Anything before the first [
346 // could be a mask that's not surrounded with [], so pass to SplitMask.
348 reg.RegComp("(^|[^%])\\[(([^%]|%%|%\\]|%\\[)*)%([" MASK_CHARS "])(([^%]|%%|%\\]|%\\[)*)\\]");
349 CStdString work(mask);
351 while ((findStart = reg.RegFind(work.c_str())) >= 0)
352 { // we've found a match for a pre/postfixed string
354 SplitMask(label, work.Left(findStart) + reg.GetReplaceString("\\1").c_str());
355 m_dynamicContent[label].push_back(CMaskString(
356 reg.GetReplaceString("\\2"),
357 reg.GetReplaceString("\\4")[0],
358 reg.GetReplaceString("\\5")));
359 work = work.Mid(findStart + reg.GetFindLen());
361 SplitMask(label, work);
362 assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1);
365 bool CLabelFormatter::FillMusicTag(const CStdString &fileName, CMusicInfoTag *tag) const
367 // run through and find static content to split the string up
368 int pos1 = fileName.Find(m_staticContent[0][0], 0);
369 if (pos1 == (int)CStdString::npos)
371 for (unsigned int i = 1; i < m_staticContent[0].size(); i++)
373 int pos2 = m_staticContent[0][i].size() ? fileName.Find(m_staticContent[0][i], pos1) : fileName.size();
374 if (pos2 == (int)CStdString::npos)
376 // found static content - thus we have the dynamic content surrounded
377 FillMusicMaskContent(m_dynamicContent[0][i - 1].m_content, fileName.Mid(pos1, pos2 - pos1), tag);
378 pos1 = pos2 + m_staticContent[0][i].size();
383 void CLabelFormatter::FillMusicMaskContent(const char mask, const CStdString &value, CMusicInfoTag *tag) const
389 tag->SetTrackNumber(atol(value.c_str()));
392 tag->SetPartOfSet(atol(value.c_str()));
395 tag->SetArtist(value);
398 tag->SetTitle(value);
401 tag->SetAlbum(value);
404 tag->SetGenre(value);
407 tag->SetYear(atol(value.c_str()));
410 tag->SetDuration(StringUtils::TimeStringToSeconds(value));
413 tag->SetRating(value[0]);