2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "LabelFormatter.h"
13 #include "ServiceBroker.h"
14 #include "StringUtils.h"
18 #include "addons/IAddon.h"
19 #include "guilib/LocalizeStrings.h"
20 #include "music/tags/MusicInfoTag.h"
21 #include "pictures/PictureInfoTag.h"
22 #include "settings/AdvancedSettings.h"
23 #include "settings/Settings.h"
24 #include "settings/SettingsComponent.h"
25 #include "video/VideoInfoTag.h"
31 using namespace MUSIC_INFO
;
36 * The purpose of this class is to parse a mask string of the form
38 * [%N. ][%T] - [%A][ (%Y)]
40 * and provide methods to format up a CFileItem's label(s).
42 * The %N/%A/%B masks are replaced with the corresponding metadata (if available).
44 * Square brackets are treated as a metadata block. Anything inside the block other
45 * than the metadata mask is treated as either a prefix or postfix to the metadata. This
46 * information is only included in the formatted string when the metadata is non-empty.
48 * Any metadata tags not enclosed with square brackets are treated as if it were immediately
49 * enclosed - i.e. with no prefix or postfix.
51 * The special characters %, [, and ] can be produced using %%, %[, and %] respectively.
53 * Any static text outside of the metadata blocks is only shown if the blocks on either side
54 * (or just one side in the case of an end) are both non-empty.
56 * Examples (using the above expression):
58 * Track Title Artist Year Resulting Label
59 * ----- ----- ------ ---- ---------------
60 * 10 "40" U2 1983 10. "40" - U2 (1983)
61 * "40" U2 1983 "40" - U2 (1983)
62 * 10 U2 1983 10. U2 (1983)
63 * 10 "40" 1983 "40" (1983)
64 * 10 "40" U2 10. "40" - U2
67 * Available metadata masks:
76 * %H - season*100+episode
79 * %K - Movie/Game title
81 * %M - number of episodes
84 * %P - production code
96 * %b - Total number of discs
97 * %c - Relevance - Used for actors' appearances
99 * %e - Original release date
104 * *t - Date Taken (suitable for Pictures)
107 #define MASK_CHARS "NSATBGYFLDIJRCKMEPHZOQUVXWabcdefhiprstuv"
109 CLabelFormatter::CLabelFormatter(const std::string
&mask
, const std::string
&mask2
)
111 // assemble our label masks
112 AssembleMask(0, mask
);
113 AssembleMask(1, mask2
);
114 // save a bool for faster lookups
115 m_hideFileExtensions
= !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWEXTENSIONS
);
118 std::string
CLabelFormatter::GetContent(unsigned int label
, const CFileItem
*item
) const
121 assert(m_staticContent
[label
].size() == m_dynamicContent
[label
].size() + 1);
123 if (!item
) return "";
125 std::string strLabel
, dynamicLeft
, dynamicRight
;
126 for (unsigned int i
= 0; i
< m_dynamicContent
[label
].size(); i
++)
128 dynamicRight
= GetMaskContent(m_dynamicContent
[label
][i
], item
);
129 if ((i
== 0 || !dynamicLeft
.empty()) && !dynamicRight
.empty())
130 strLabel
+= m_staticContent
[label
][i
];
131 strLabel
+= dynamicRight
;
132 dynamicLeft
= dynamicRight
;
134 if (!dynamicLeft
.empty())
135 strLabel
+= m_staticContent
[label
][m_dynamicContent
[label
].size()];
140 void CLabelFormatter::FormatLabel(CFileItem
*item
) const
142 std::string maskedLabel
= GetContent(0, item
);
143 if (!maskedLabel
.empty())
144 item
->SetLabel(maskedLabel
);
145 else if (!item
->m_bIsFolder
&& m_hideFileExtensions
)
146 item
->RemoveExtension();
149 void CLabelFormatter::FormatLabel2(CFileItem
*item
) const
151 item
->SetLabel2(GetContent(1, item
));
154 std::string
CLabelFormatter::GetMaskContent(const CMaskString
&mask
, const CFileItem
*item
) const
156 if (!item
) return "";
157 const CMusicInfoTag
*music
= item
->GetMusicInfoTag();
158 const CVideoInfoTag
*movie
= item
->GetVideoInfoTag();
159 const CPictureInfoTag
*pic
= item
->GetPictureInfoTag();
161 switch (mask
.m_content
)
164 if (music
&& music
->GetTrackNumber() > 0)
165 value
= StringUtils::Format("{:02}", music
->GetTrackNumber());
166 if (movie
&& movie
->m_iTrack
> 0)
167 value
= StringUtils::Format("{:02}", movie
->m_iTrack
);
170 if (music
&& music
->GetDiscNumber() > 0)
171 value
= StringUtils::Format("{:02}", music
->GetDiscNumber());
174 if (music
&& music
->GetArtistString().size())
175 value
= music
->GetArtistString();
176 if (movie
&& movie
->m_artist
.size())
177 value
= StringUtils::Join(movie
->m_artist
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
180 if (music
&& music
->GetTitle().size())
181 value
= music
->GetTitle();
182 if (movie
&& movie
->m_strTitle
.size())
183 value
= movie
->m_strTitle
;
186 if (movie
&& !movie
->m_strShowTitle
.empty())
187 value
= movie
->m_strShowTitle
;
190 if (music
&& music
->GetAlbum().size())
191 value
= music
->GetAlbum();
193 value
= movie
->m_strAlbum
;
196 if (music
&& music
->GetGenre().size())
197 value
= StringUtils::Join(music
->GetGenre(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator
);
198 if (movie
&& movie
->m_genre
.size())
199 value
= StringUtils::Join(movie
->m_genre
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
203 value
= music
->GetYearString();
206 if (movie
->m_firstAired
.IsValid())
207 value
= movie
->m_firstAired
.GetAsLocalizedDate();
208 else if (movie
->HasYear())
209 value
= std::to_string(movie
->GetYear());
212 case 'F': // filename
213 value
= CUtil::GetTitleFromPath(item
->GetPath(), item
->m_bIsFolder
&& !item
->IsFileFolder());
216 value
= item
->GetLabel();
217 // is the label the actual file or folder name?
218 if (value
== URIUtils::GetFileName(item
->GetPath()))
219 { // label is the same as filename, clean it up as appropriate
220 value
= CUtil::GetTitleFromPath(item
->GetPath(), item
->m_bIsFolder
&& !item
->IsFileFolder());
227 nDuration
= music
->GetDuration();
229 nDuration
= movie
->GetDuration();
231 value
= StringUtils::SecondsToTimeString(nDuration
, (nDuration
>= 3600) ? TIME_FORMAT_H_MM_SS
: TIME_FORMAT_MM_SS
);
232 else if (item
->m_dwSize
> 0)
233 value
= StringUtils::SizeToString(item
->m_dwSize
);
237 if( (item
->m_bIsFolder
&& item
->m_dwSize
!= 0) || item
->m_dwSize
>= 0 )
238 value
= StringUtils::SizeToString(item
->m_dwSize
);
241 if (item
->m_dateTime
.IsValid())
242 value
= item
->m_dateTime
.GetAsLocalizedDate();
245 if (item
->m_dateTime
.IsValid())
246 value
= item
->m_dateTime
.GetAsLocalizedTime("", false);
249 if (music
&& music
->GetRating() != 0.f
)
250 value
= StringUtils::Format("{:.1f}", music
->GetRating());
251 else if (movie
&& movie
->GetRating().rating
!= 0.f
)
252 value
= StringUtils::Format("{:.1f}", movie
->GetRating().rating
);
254 case 'C': // programs count
255 value
= std::to_string(item
->m_iprogramCount
);
257 case 'c': // relevance
258 value
= std::to_string(movie
->m_relevance
);
261 value
= item
->m_strTitle
;
264 if (movie
&& movie
->m_iEpisode
> 0)
265 value
= StringUtils::Format("{} {}", movie
->m_iEpisode
,
266 g_localizeStrings
.Get(movie
->m_iEpisode
== 1 ? 20452 : 20453));
269 if (movie
&& movie
->m_iEpisode
> 0)
271 if (movie
->m_iSeason
== 0)
272 value
= StringUtils::Format("S{:02}", movie
->m_iEpisode
);
274 value
= StringUtils::Format("{:02}", movie
->m_iEpisode
);
278 if (movie
) // tvshow production code
279 value
= movie
->m_strProductionCode
;
282 if (movie
&& movie
->m_iEpisode
> 0)
283 { // season*100+episode number
284 if (movie
->m_iSeason
== 0)
285 value
= StringUtils::Format("S{:02}", movie
->m_iEpisode
);
287 value
= StringUtils::Format("{}x{:02}", movie
->m_iSeason
, movie
->m_iEpisode
);
293 value
= movie
->m_strMPAARating
;
297 if (movie
&& !movie
->m_studio
.empty())
299 value
= StringUtils::Join(movie
->m_studio
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
302 case 'V': // Playcount
304 value
= std::to_string(music
->GetPlayCount());
306 value
= std::to_string(movie
->GetPlayCount());
309 if( !item
->m_bIsFolder
&& item
->m_dwSize
!= 0 )
310 value
= StringUtils::Format("{} kbps", item
->m_dwSize
);
312 case 'W': // Listeners
313 if( !item
->m_bIsFolder
&& music
&& music
->GetListeners() != 0 )
315 StringUtils::Format("{} {}", music
->GetListeners(),
316 g_localizeStrings
.Get(music
->GetListeners() == 1 ? 20454 : 20455));
318 case 'a': // Date Added
319 if (movie
&& movie
->m_dateAdded
.IsValid())
320 value
= movie
->m_dateAdded
.GetAsLocalizedDate();
321 if (music
&& music
->GetDateAdded().IsValid())
322 value
= music
->GetDateAdded().GetAsLocalizedDate();
324 case 'b': // Total number of discs
326 value
= std::to_string(music
->GetTotalDiscs());
328 case 'e': // Original release date
331 value
= music
->GetOriginalDate();
332 if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bMusicLibraryUseISODates
)
333 value
= StringUtils::ISODateToLocalizedDate(value
);
336 case 'd': // date and time
337 if (item
->m_dateTime
.IsValid())
338 value
= item
->m_dateTime
.GetAsLocalizedDateTime();
340 case 'p': // Last played
341 if (movie
&& movie
->m_lastPlayed
.IsValid())
342 value
= movie
->m_lastPlayed
.GetAsLocalizedDate();
343 if (music
&& music
->GetLastPlayed().IsValid())
344 value
= music
->GetLastPlayed().GetAsLocalizedDate();
346 case 'r': // userrating
347 if (movie
&& movie
->m_iUserRating
!= 0)
348 value
= std::to_string(movie
->m_iUserRating
);
349 if (music
&& music
->GetUserrating() != 0)
350 value
= std::to_string(music
->GetUserrating());
352 case 't': // Date Taken
353 if (pic
&& pic
->GetDateTimeTaken().IsValid())
354 value
= pic
->GetDateTimeTaken().GetAsLocalizedDate();
356 case 's': // Addon status
357 if (item
->HasProperty("Addon.Status"))
358 value
= item
->GetProperty("Addon.Status").asString();
360 case 'i': // Install date
361 if (item
->HasAddonInfo() && item
->GetAddonInfo()->InstallDate().IsValid())
362 value
= item
->GetAddonInfo()->InstallDate().GetAsLocalizedDate();
364 case 'u': // Last used
365 if (item
->HasAddonInfo() && item
->GetAddonInfo()->LastUsed().IsValid())
366 value
= item
->GetAddonInfo()->LastUsed().GetAsLocalizedDate();
368 case 'v': // Last updated
369 if (item
->HasAddonInfo() && item
->GetAddonInfo()->LastUpdated().IsValid())
370 value
= item
->GetAddonInfo()->LastUpdated().GetAsLocalizedDate();
374 value
= std::to_string(music
->GetBPM());
376 case 'h': //plugin label2
377 value
= item
->GetLabel2();
381 return mask
.m_prefix
+ value
+ mask
.m_postfix
;
385 void CLabelFormatter::SplitMask(unsigned int label
, const std::string
&mask
)
389 reg
.RegComp("%([" MASK_CHARS
"])");
390 std::string
work(mask
);
392 while ((findStart
= reg
.RegFind(work
.c_str())) >= 0)
393 { // we've found a match
394 m_staticContent
[label
].push_back(work
.substr(0, findStart
));
395 m_dynamicContent
[label
].emplace_back("", reg
.GetMatch(1)[0], "");
396 work
= work
.substr(findStart
+ reg
.GetFindLen());
398 m_staticContent
[label
].push_back(work
);
401 void CLabelFormatter::AssembleMask(unsigned int label
, const std::string
& mask
)
404 m_staticContent
[label
].clear();
405 m_dynamicContent
[label
].clear();
407 // we want to match [<prefix>%A<postfix]
408 // but allow %%, %[, %] to be in the prefix and postfix. Anything before the first [
409 // could be a mask that's not surrounded with [], so pass to SplitMask.
411 reg
.RegComp("(^|[^%])\\[(([^%]|%%|%\\]|%\\[)*)%([" MASK_CHARS
"])(([^%]|%%|%\\]|%\\[)*)\\]");
412 std::string
work(mask
);
414 while ((findStart
= reg
.RegFind(work
.c_str())) >= 0)
415 { // we've found a match for a pre/postfixed string
417 SplitMask(label
, work
.substr(0, findStart
) + reg
.GetMatch(1));
418 m_dynamicContent
[label
].emplace_back(reg
.GetMatch(2), reg
.GetMatch(4)[0], reg
.GetMatch(5));
419 work
= work
.substr(findStart
+ reg
.GetFindLen());
421 SplitMask(label
, work
);
422 assert(m_staticContent
[label
].size() == m_dynamicContent
[label
].size() + 1);
425 bool CLabelFormatter::FillMusicTag(const std::string
&fileName
, CMusicInfoTag
*tag
) const
427 // run through and find static content to split the string up
428 size_t pos1
= fileName
.find(m_staticContent
[0][0], 0);
429 if (pos1
== std::string::npos
)
431 for (unsigned int i
= 1; i
< m_staticContent
[0].size(); i
++)
433 size_t pos2
= m_staticContent
[0][i
].size() ? fileName
.find(m_staticContent
[0][i
], pos1
) : fileName
.size();
434 if (pos2
== std::string::npos
)
436 // found static content - thus we have the dynamic content surrounded
437 FillMusicMaskContent(m_dynamicContent
[0][i
- 1].m_content
, fileName
.substr(pos1
, pos2
- pos1
), tag
);
438 pos1
= pos2
+ m_staticContent
[0][i
].size();
443 void CLabelFormatter::FillMusicMaskContent(const char mask
, const std::string
&value
, CMusicInfoTag
*tag
) const
449 tag
->SetTrackNumber(atol(value
.c_str()));
452 tag
->SetDiscNumber(atol(value
.c_str()));
455 tag
->SetArtist(value
);
458 tag
->SetTitle(value
);
461 tag
->SetAlbum(value
);
464 tag
->SetGenre(value
);
467 tag
->SetYear(atol(value
.c_str()));
470 tag
->SetDuration(StringUtils::TimeStringToSeconds(value
));
473 tag
->SetRating(value
[0]);
475 case 'r': // userrating
476 tag
->SetUserrating(value
[0]);
478 case 'b': // total discs
479 tag
->SetTotalDiscs(atol(value
.c_str()));