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/AdvancedSettings.h"
23 #include "settings/Settings.h"
26 #include "video/VideoInfoTag.h"
27 #include "music/tags/MusicInfoTag.h"
28 #include "pictures/PictureInfoTag.h"
30 #include "StringUtils.h"
32 #include "guilib/LocalizeStrings.h"
34 using namespace MUSIC_INFO;
39 * The purpose of this class is to parse a mask string of the form
41 * [%N. ][%T] - [%A][ (%Y)]
43 * and provide methods to format up a CFileItem's label(s).
45 * The %N/%A/%B masks are replaced with the corresponding metadata (if available).
47 * Square brackets are treated as a metadata block. Anything inside the block other
48 * than the metadata mask is treated as either a prefix or postfix to the metadata. This
49 * information is only included in the formatted string when the metadata is non-empty.
51 * Any metadata tags not enclosed with square brackets are treated as if it were immediately
52 * enclosed - i.e. with no prefix or postfix.
54 * The special characters %, [, and ] can be produced using %%, %[, and %] respectively.
56 * Any static text outside of the metadata blocks is only shown if the blocks on either side
57 * (or just one side in the case of an end) are both non-empty.
59 * Examples (using the above expression):
61 * Track Title Artist Year Resulting Label
62 * ----- ----- ------ ---- ---------------
63 * 10 "40" U2 1983 10. "40" - U2 (1983)
64 * "40" U2 1983 "40" - U2 (1983)
65 * 10 U2 1983 10. U2 (1983)
66 * 10 "40" 1983 "40" (1983)
67 * 10 "40" U2 10. "40" - U2
70 * Available metadata masks:
79 * %H - season*100+episode
82 * %K - Movie/Game title
84 * %M - number of episodes
87 * %P - production code
100 * *t - Date Taken (suitable for Pictures)
103 #define MASK_CHARS "NSATBGYFLDIJRCKMEPHZOQUVXWapt"
105 CLabelFormatter::CLabelFormatter(const CStdString &mask, const CStdString &mask2)
107 // assemble our label masks
108 AssembleMask(0, mask);
109 AssembleMask(1, mask2);
110 // save a bool for faster lookups
111 m_hideFileExtensions = !CSettings::Get().GetBool("filelists.showextensions");
114 CStdString CLabelFormatter::GetContent(unsigned int label, const CFileItem *item) const
117 assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1);
119 if (!item) return "";
121 CStdString strLabel, dynamicLeft, dynamicRight;
122 for (unsigned int i = 0; i < m_dynamicContent[label].size(); i++)
124 dynamicRight = GetMaskContent(m_dynamicContent[label][i], item);
125 if ((i == 0 || !dynamicLeft.empty()) && !dynamicRight.empty())
126 strLabel += m_staticContent[label][i];
127 strLabel += dynamicRight;
128 dynamicLeft = dynamicRight;
130 if (!dynamicLeft.empty())
131 strLabel += m_staticContent[label][m_dynamicContent[label].size()];
136 void CLabelFormatter::FormatLabel(CFileItem *item) const
138 CStdString maskedLabel = GetContent(0, item);
139 if (!maskedLabel.empty())
140 item->SetLabel(maskedLabel);
141 else if (!item->m_bIsFolder && m_hideFileExtensions)
142 item->RemoveExtension();
145 void CLabelFormatter::FormatLabel2(CFileItem *item) const
147 item->SetLabel2(GetContent(1, item));
150 CStdString CLabelFormatter::GetMaskContent(const CMaskString &mask, const CFileItem *item) const
152 if (!item) return "";
153 const CMusicInfoTag *music = item->GetMusicInfoTag();
154 const CVideoInfoTag *movie = item->GetVideoInfoTag();
155 const CPictureInfoTag *pic = item->GetPictureInfoTag();
157 switch (mask.m_content)
160 if (music && music->GetTrackNumber() > 0)
161 value = StringUtils::Format("%02.2i", music->GetTrackNumber());
162 if (movie&& movie->m_iTrack > 0)
163 value = StringUtils::Format("%02.2i", movie->m_iTrack);
166 if (music && music->GetDiscNumber() > 0)
167 value = StringUtils::Format("%02.2i", music->GetDiscNumber());
170 if (music && music->GetArtist().size())
171 value = StringUtils::Join(music->GetArtist(), g_advancedSettings.m_musicItemSeparator);
172 if (movie && movie->m_artist.size())
173 value = StringUtils::Join(movie->m_artist, g_advancedSettings.m_videoItemSeparator);
176 if (music && music->GetTitle().size())
177 value = music->GetTitle();
178 if (movie && movie->m_strTitle.size())
179 value = movie->m_strTitle;
182 if (movie && !movie->m_strShowTitle.empty())
183 value = movie->m_strShowTitle;
186 if (music && music->GetAlbum().size())
187 value = music->GetAlbum();
189 value = movie->m_strAlbum;
192 if (music && music->GetGenre().size())
193 value = StringUtils::Join(music->GetGenre(), g_advancedSettings.m_musicItemSeparator);
194 if (movie && movie->m_genre.size())
195 value = StringUtils::Join(movie->m_genre, g_advancedSettings.m_videoItemSeparator);
199 value = music->GetYearString();
202 if (movie->m_firstAired.IsValid())
203 value = movie->m_firstAired.GetAsLocalizedDate();
204 else if (movie->m_premiered.IsValid())
205 value = movie->m_premiered.GetAsLocalizedDate();
206 else if (movie->m_iYear > 0)
207 value = StringUtils::Format("%i", movie->m_iYear);
210 case 'F': // filename
211 value = CUtil::GetTitleFromPath(item->GetPath(), item->m_bIsFolder && !item->IsFileFolder());
214 value = item->GetLabel();
215 // is the label the actual file or folder name?
216 if (value == URIUtils::GetFileName(item->GetPath()))
217 { // label is the same as filename, clean it up as appropriate
218 value = CUtil::GetTitleFromPath(item->GetPath(), item->m_bIsFolder && !item->IsFileFolder());
225 nDuration = music->GetDuration();
227 nDuration = movie->GetDuration();
229 value = StringUtils::SecondsToTimeString(nDuration, (nDuration >= 3600) ? TIME_FORMAT_H_MM_SS : TIME_FORMAT_MM_SS);
230 else if (item->m_dwSize > 0)
231 value = StringUtils::SizeToString(item->m_dwSize);
235 if( !item->m_bIsFolder || item->m_dwSize != 0 )
236 value = StringUtils::SizeToString(item->m_dwSize);
239 if (item->m_dateTime.IsValid())
240 value = item->m_dateTime.GetAsLocalizedDate();
243 if (item->m_dateTime.IsValid())
244 value = item->m_dateTime.GetAsLocalizedTime("", false);
247 if (music && music->GetRating() != '0')
248 value = music->GetRating();
249 else if (movie && movie->m_fRating != 0.f)
250 value = StringUtils::Format("%.1f", movie->m_fRating);
252 case 'C': // programs count
253 value = StringUtils::Format("%i", item->m_iprogramCount);
256 value = item->m_strTitle;
259 if (movie && movie->m_iEpisode > 0)
260 value = StringUtils::Format("%i %s",
262 g_localizeStrings.Get(movie->m_iEpisode == 1 ? 20452 : 20453).c_str());
265 if (movie && movie->m_iEpisode > 0)
267 if (movie->m_iSeason == 0)
268 value = StringUtils::Format("S%02.2i", movie->m_iEpisode);
270 value = StringUtils::Format("%02.2i", movie->m_iEpisode);
274 if (movie) // tvshow production code
275 value = movie->m_strProductionCode;
278 if (movie && movie->m_iEpisode > 0)
279 { // season*100+episode number
280 if (movie->m_iSeason == 0)
281 value = StringUtils::Format("S%02.2i", movie->m_iEpisode);
283 value = StringUtils::Format("%ix%02.2i", movie->m_iSeason,movie->m_iEpisode);
287 if (movie && movie->m_strMPAARating)
289 value = movie->m_strMPAARating;
293 if (movie && movie->m_studio.size() > 0)
295 value = StringUtils::Join(movie ->m_studio, g_advancedSettings.m_videoItemSeparator);
298 case 'V': // Playcount
300 value = StringUtils::Format("%i", music->GetPlayCount());
302 value = StringUtils::Format("%i", movie->m_playCount);
305 if( !item->m_bIsFolder && item->m_dwSize != 0 )
306 value = StringUtils::Format("%i kbps", item->m_dwSize);
308 case 'W': // Listeners
309 if( !item->m_bIsFolder && music && music->GetListeners() != 0 )
310 value = StringUtils::Format("%i %s",
311 music->GetListeners(),
312 g_localizeStrings.Get(music->GetListeners() == 1 ? 20454 : 20455).c_str());
314 case 'a': // Date Added
315 if (movie && movie->m_dateAdded.IsValid())
316 value = movie->m_dateAdded.GetAsLocalizedDate();
318 case 'p': // Last played
319 if (movie && movie->m_lastPlayed.IsValid())
320 value = movie->m_lastPlayed.GetAsLocalizedDate();
322 case 't': // Date Taken
323 if (pic && pic->GetDateTimeTaken().IsValid())
324 value = pic->GetDateTimeTaken().GetAsLocalizedDate();
328 return mask.m_prefix + value + mask.m_postfix;
332 void CLabelFormatter::SplitMask(unsigned int label, const CStdString &mask)
336 reg.RegComp("%([" MASK_CHARS "])");
337 CStdString work(mask);
339 while ((findStart = reg.RegFind(work.c_str())) >= 0)
340 { // we've found a match
341 m_staticContent[label].push_back(work.substr(0, findStart));
342 m_dynamicContent[label].push_back(CMaskString("",
343 reg.GetMatch(1)[0], ""));
344 work = work.substr(findStart + reg.GetFindLen());
346 m_staticContent[label].push_back(work);
349 void CLabelFormatter::AssembleMask(unsigned int label, const CStdString& mask)
352 m_staticContent[label].clear();
353 m_dynamicContent[label].clear();
355 // we want to match [<prefix>%A<postfix]
356 // but allow %%, %[, %] to be in the prefix and postfix. Anything before the first [
357 // could be a mask that's not surrounded with [], so pass to SplitMask.
359 reg.RegComp("(^|[^%])\\[(([^%]|%%|%\\]|%\\[)*)%([" MASK_CHARS "])(([^%]|%%|%\\]|%\\[)*)\\]");
360 CStdString work(mask);
362 while ((findStart = reg.RegFind(work.c_str())) >= 0)
363 { // we've found a match for a pre/postfixed string
365 SplitMask(label, work.substr(0, findStart) + reg.GetMatch(1));
366 m_dynamicContent[label].push_back(CMaskString(
370 work = work.substr(findStart + reg.GetFindLen());
372 SplitMask(label, work);
373 assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1);
376 bool CLabelFormatter::FillMusicTag(const CStdString &fileName, CMusicInfoTag *tag) const
378 // run through and find static content to split the string up
379 size_t pos1 = fileName.find(m_staticContent[0][0], 0);
380 if (pos1 == std::string::npos)
382 for (unsigned int i = 1; i < m_staticContent[0].size(); i++)
384 size_t pos2 = m_staticContent[0][i].size() ? fileName.find(m_staticContent[0][i], pos1) : fileName.size();
385 if (pos2 == std::string::npos)
387 // found static content - thus we have the dynamic content surrounded
388 FillMusicMaskContent(m_dynamicContent[0][i - 1].m_content, fileName.substr(pos1, pos2 - pos1), tag);
389 pos1 = pos2 + m_staticContent[0][i].size();
394 void CLabelFormatter::FillMusicMaskContent(const char mask, const CStdString &value, CMusicInfoTag *tag) const
400 tag->SetTrackNumber(atol(value.c_str()));
403 tag->SetPartOfSet(atol(value.c_str()));
406 tag->SetArtist(value);
409 tag->SetTitle(value);
412 tag->SetAlbum(value);
415 tag->SetGenre(value);
418 tag->SetYear(atol(value.c_str()));
421 tag->SetDuration(StringUtils::TimeStringToSeconds(value));
424 tag->SetRating(value[0]);