changed: update version strings for beta4
[xbmc.git] / xbmc / utils / LabelFormatter.cpp
blob02a796b768f1da12906f595742353c43afffa525
1 /*
2 * Copyright (C) 2005-2008 Team XBMC
3 * http://www.xbmc.org
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)
8 * any later version.
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, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
22 #include "LabelFormatter.h"
23 #include "GUISettings.h"
24 #include "RegExp.h"
25 #include "Util.h"
26 #include "VideoInfoTag.h"
27 #include "MusicInfoTag.h"
28 #include "FileItem.h"
29 #include "StringUtils.h"
30 #include "LocalizeStrings.h"
32 using namespace MUSIC_INFO;
34 /* LabelFormatter
35 * ==============
37 * The purpose of this class is to parse a mask string of the form
39 * [%N. ][%T] - [%A][ (%Y)]
41 * and provide methods to format up a CFileItem's label(s).
43 * The %N/%A/%B masks are replaced with the corresponding metadata (if available).
45 * Square brackets are treated as a metadata block. Anything inside the block other
46 * than the metadata mask is treated as either a prefix or postfix to the metadata. This
47 * information is only included in the formatted string when the metadata is non-empty.
49 * Any metadata tags not enclosed with square brackets are treated as if it were immediately
50 * enclosed - i.e. with no prefix or postfix.
52 * The special characters %, [, and ] can be produced using %%, %[, and %] respectively.
54 * Any static text outside of the metadata blocks is only shown if the blocks on either side
55 * (or just one side in the case of an end) are both non-empty.
57 * Examples (using the above expression):
59 * Track Title Artist Year Resulting Label
60 * ----- ----- ------ ---- ---------------
61 * 10 "40" U2 1983 10. "40" - U2 (1983)
62 * "40" U2 1983 "40" - U2 (1983)
63 * 10 U2 1983 10. U2 (1983)
64 * 10 "40" 1983 "40" (1983)
65 * 10 "40" U2 10. "40" - U2
66 * 10 "40" 10. "40"
68 * Available metadata masks:
70 * %N - Track Number
71 * %S - Disc Number
72 * %A - Artist
73 * %T - Title
74 * %B - Album
75 * %G - Genre
76 * %Y - Year
77 * %F - FileName
78 * %L - existing Label
79 * %D - Duration
80 * %I - Size
81 * %J - Date
82 * %R - Movie rating
83 * %C - Programs count
84 * %K - Movie/Game title
85 * %M - number of episodes
86 * %E - episode number
87 * %P - production code
88 * %H - season*100+episode
89 * %Z - tvshow title
90 * %O - mpaa rating
91 * %Q - file time
92 * %U - studio
93 * %X - Bitrate
96 #define MASK_CHARS "NSATBGYFLDIJRCKMEPHZOQUX"
98 CLabelFormatter::CLabelFormatter(const CStdString &mask, const CStdString &mask2)
100 // assemble our label masks
101 AssembleMask(0, mask);
102 AssembleMask(1, mask2);
103 // save a bool for faster lookups
104 m_hideFileExtensions = !g_guiSettings.GetBool("filelists.showextensions");
107 CStdString CLabelFormatter::GetContent(unsigned int label, const CFileItem *item) const
109 assert(label < 2);
110 assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1);
112 if (!item) return "";
114 CStdString strLabel, dynamicLeft, dynamicRight;
115 for (unsigned int i = 0; i < m_dynamicContent[label].size(); i++)
117 dynamicRight = GetMaskContent(m_dynamicContent[label][i], item);
118 if ((i == 0 || !dynamicLeft.IsEmpty()) && !dynamicRight.IsEmpty())
119 strLabel += m_staticContent[label][i];
120 strLabel += dynamicRight;
121 dynamicLeft = dynamicRight;
123 if (!dynamicLeft.IsEmpty())
124 strLabel += m_staticContent[label][m_dynamicContent[label].size()];
126 return strLabel;
129 void CLabelFormatter::FormatLabel(CFileItem *item) const
131 CStdString maskedLabel = GetContent(0, item);
132 if (!maskedLabel.IsEmpty())
133 item->SetLabel(maskedLabel);
134 else if (!item->m_bIsFolder && m_hideFileExtensions)
135 item->RemoveExtension();
138 void CLabelFormatter::FormatLabel2(CFileItem *item) const
140 item->SetLabel2(GetContent(1, item));
143 CStdString CLabelFormatter::GetMaskContent(const CMaskString &mask, const CFileItem *item) const
145 if (!item) return "";
146 const CMusicInfoTag *music = item->GetMusicInfoTag();
147 const CVideoInfoTag *movie = item->GetVideoInfoTag();
148 CStdString value;
149 switch (mask.m_content)
151 case 'N':
152 if (music && music->GetTrackNumber() > 0)
153 value.Format("%02.2i", music->GetTrackNumber());
154 if (movie&& movie->m_iTrack > 0)
155 value.Format("%02.2i", movie->m_iTrack);
156 break;
157 case 'S':
158 if (music && music->GetDiscNumber() > 0)
159 value.Format("%02.2i", music->GetDiscNumber());
160 break;
161 case 'A':
162 if (music && music->GetArtist().size())
163 value = music->GetArtist();
164 if (movie && movie->m_strArtist.size())
165 value = movie->m_strArtist;
166 break;
167 case 'T':
168 if (music && music->GetTitle().size())
169 value = music->GetTitle();
170 if (movie && movie->m_strTitle.size())
171 value = movie->m_strTitle;
172 break;
173 case 'Z':
174 if (movie && !movie->m_strShowTitle.IsEmpty())
175 value = movie->m_strShowTitle;
176 break;
177 case 'B':
178 if (music && music->GetAlbum().size())
179 value = music->GetAlbum();
180 else if (movie)
181 value = movie->m_strAlbum;
182 break;
183 case 'G':
184 if (music && music->GetGenre().size())
185 value = music->GetGenre();
186 if (movie && movie->m_strGenre.size())
187 value = movie->m_strGenre;
188 break;
189 case 'Y':
190 if (music)
191 value = music->GetYearString();
192 if (movie)
194 if (!movie->m_strFirstAired.IsEmpty())
195 value = movie->m_strFirstAired;
196 else if (!movie->m_strPremiered.IsEmpty())
197 value = movie->m_strPremiered;
198 else if (movie->m_iYear > 0)
199 value.Format("%i",movie->m_iYear);
201 break;
202 case 'F': // filename
203 value = CUtil::GetTitleFromPath(item->m_strPath, item->m_bIsFolder && !item->IsFileFolder());
204 break;
205 case 'L':
206 value = item->GetLabel();
207 // is the label the actual file or folder name?
208 if (value == CUtil::GetFileName(item->m_strPath))
209 { // label is the same as filename, clean it up as appropriate
210 value = CUtil::GetTitleFromPath(item->m_strPath, item->m_bIsFolder && !item->IsFileFolder());
212 break;
213 case 'D':
214 { // duration
215 int nDuration=0;
216 if (music)
217 nDuration = music->GetDuration();
218 if (movie)
220 if (movie->m_streamDetails.GetVideoDuration() > 0)
221 nDuration = movie->m_streamDetails.GetVideoDuration();
222 else if (!movie->m_strRuntime.IsEmpty())
223 nDuration = StringUtils::TimeStringToSeconds(movie->m_strRuntime);
225 if (nDuration > 0)
226 value = StringUtils::SecondsToTimeString(nDuration);
227 else if (item->m_dwSize > 0)
228 value = StringUtils::SizeToString(item->m_dwSize);
230 break;
231 case 'I': // size
232 if( !item->m_bIsFolder || item->m_dwSize != 0 )
233 value = StringUtils::SizeToString(item->m_dwSize);
234 break;
235 case 'J': // date
236 if (item->m_dateTime.IsValid())
237 value = item->m_dateTime.GetAsLocalizedDate();
238 break;
239 case 'Q': // time
240 if (item->m_dateTime.IsValid())
241 value = item->m_dateTime.GetAsLocalizedTime("", false);
242 break;
243 case 'R': // rating
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);
248 break;
249 case 'C': // programs count
250 value.Format("%i", item->m_iprogramCount);
251 break;
252 case 'K':
253 value = item->m_strTitle;
254 break;
255 case 'M':
256 if (movie && movie->m_iEpisode > 0)
257 value.Format("%i %s", movie->m_iEpisode,g_localizeStrings.Get(movie->m_iEpisode == 1 ? 20452 : 20453));
258 break;
259 case 'E':
260 if (movie && movie->m_iEpisode > 0)
261 { // episode number
262 if (movie->m_iSpecialSortEpisode > 0)
263 value.Format("S%02.2i", movie->m_iEpisode);
264 else
265 value.Format("%02.2i", movie->m_iEpisode);
267 break;
268 case 'P':
269 if (movie) // tvshow production code
270 value = movie->m_strProductionCode;
271 break;
272 case 'H':
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);
277 else
278 value.Format("%ix%02.2i", movie->m_iSeason,movie->m_iEpisode);
280 break;
281 case 'O':
282 if (movie && movie->m_strMPAARating)
283 {// MPAA Rating
284 value = movie->m_strMPAARating;
286 break;
287 case 'U':
288 if (movie && movie->m_strStudio)
289 {// MPAA Rating
290 value = movie ->m_strStudio;
292 break;
293 case 'X': // Bitrate
294 if( !item->m_bIsFolder || item->m_dwSize != 0 )
295 value.Format("%i kbps", item->m_dwSize);
296 break;
298 if (!value.IsEmpty())
299 return mask.m_prefix + value + mask.m_postfix;
300 return "";
303 void CLabelFormatter::SplitMask(unsigned int label, const CStdString &mask)
305 assert(label < 2);
306 CRegExp reg;
307 reg.RegComp("%([" MASK_CHARS "])");
308 CStdString work(mask);
309 int findStart = -1;
310 while ((findStart = reg.RegFind(work.c_str())) >= 0)
311 { // we've found a match
312 m_staticContent[label].push_back(work.Left(findStart));
313 char* lp_tmp = reg.GetReplaceString("\\1");
314 m_dynamicContent[label].push_back(CMaskString("", *lp_tmp, ""));
315 free(lp_tmp);
316 work = work.Mid(findStart + reg.GetFindLen());
318 m_staticContent[label].push_back(work);
321 void CLabelFormatter::AssembleMask(unsigned int label, const CStdString& mask)
323 assert(label < 2);
324 m_staticContent[label].clear();
325 m_dynamicContent[label].clear();
327 // we want to match [<prefix>%A<postfix]
328 // but allow %%, %[, %] to be in the prefix and postfix. Anything before the first [
329 // could be a mask that's not surrounded with [], so pass to SplitMask.
330 CRegExp reg;
331 reg.RegComp("(^|[^%])\\[(([^%]|%%|%\\]|%\\[)*)%([" MASK_CHARS "])(([^%]|%%|%\\]|%\\[)*)\\]");
332 CStdString work(mask);
333 int findStart = -1;
334 while ((findStart = reg.RegFind(work.c_str())) >= 0)
335 { // we've found a match for a pre/postfixed string
336 // send anything
337 char *s1 = reg.GetReplaceString("\\1");
338 char *s2 = reg.GetReplaceString("\\2");
339 char *s4 = reg.GetReplaceString("\\4");
340 char *s5 = reg.GetReplaceString("\\5");
341 SplitMask(label, work.Left(findStart) + s1);
342 m_dynamicContent[label].push_back(CMaskString(s2, *s4, s5));
343 free(s1);
344 free(s2);
345 free(s4);
346 free(s5);
347 work = work.Mid(findStart + reg.GetFindLen());
349 SplitMask(label, work);
350 assert(m_staticContent[label].size() == m_dynamicContent[label].size() + 1);
353 bool CLabelFormatter::FillMusicTag(const CStdString &fileName, CMusicInfoTag *tag) const
355 // run through and find static content to split the string up
356 int pos1 = fileName.Find(m_staticContent[0][0], 0);
357 if (pos1 == (int)CStdString::npos)
358 return false;
359 for (unsigned int i = 1; i < m_staticContent[0].size(); i++)
361 int pos2 = m_staticContent[0][i].size() ? fileName.Find(m_staticContent[0][i], pos1) : fileName.size();
362 if (pos2 == (int)CStdString::npos)
363 return false;
364 // found static content - thus we have the dynamic content surrounded
365 FillMusicMaskContent(m_dynamicContent[0][i - 1].m_content, fileName.Mid(pos1, pos2 - pos1), tag);
366 pos1 = pos2 + m_staticContent[0][i].size();
368 return true;
371 void CLabelFormatter::FillMusicMaskContent(const char mask, const CStdString &value, CMusicInfoTag *tag) const
373 if (!tag) return;
374 switch (mask)
376 case 'N':
377 tag->SetTrackNumber(atol(value.c_str()));
378 break;
379 case 'S':
380 tag->SetPartOfSet(atol(value.c_str()));
381 break;
382 case 'A':
383 tag->SetArtist(value);
384 break;
385 case 'T':
386 tag->SetTitle(value);
387 break;
388 case 'B':
389 tag->SetAlbum(value);
390 break;
391 case 'G':
392 tag->SetGenre(value);
393 break;
394 case 'Y':
395 tag->SetYear(atol(value.c_str()));
396 break;
397 case 'D':
398 tag->SetDuration(StringUtils::TimeStringToSeconds(value));
399 break;
400 case 'R': // rating
401 tag->SetRating(value[0]);
402 break;