Skip to content
This repository

add a tagging system for movies #1108

Merged
merged 5 commits into from almost 2 years ago

6 participants

Sascha Montellese jmarshallnz Karlson2k Deano316 vicbitter Michal Piechowiak
Sascha Montellese
Collaborator

A few nights ago I had a discussion with @jmarshallnz concerning movie sets and what a pain in the ass they are because they require special handing all over the place. There has been a plan to reduce the set feature to a single set per movie (TheMovieDb only provides one set per movie and will keep it that way) but as users always start screaming when you take something away from them we decided that we should first introduce a more flexible (but also more separated) tagging system for movies (and possibly for other media types).
This PR adds a "tag" and "taglinkmovie" table to the videodb which allows to set as many tags for a movie as the user wants (as it is currently possible with sets). Additionally there's a new "Tags" node in the video library with a "New tag..." button to create a new tag and "Add movies" and "Remove movies" context menu entries in the tags node to allow users to manually add/remove movies from a specific tag. Apart from additional smartplaylist implementation for tags there's no further integration of tags yet i.e. they are not available to skinners in the movie view etc.

I'm throwing this out there to widen the discussion which took place between jmarshall and me. Currently the GUI-side integration of tags into the library is very movie-specific but could (more or less) easily be extended to tvshows and musicvideos (seasons and episodes probably don't make that much sense but I'm sure some users would request it).

For the users the advantage of tags over sets is that they can create/modify them in the GUI and don't need custom NFOs to get them in there in the first place. A disadvantage would be that tags are not scraped but not sure if that would make sense anyway as tags are pretty user-specific IMO.

The advantage for us developers is that we can simplify the code for sets by degrading it to one set per movie (I've already done the necessary work in another branch) which will make both SQL queries and some special handlings in the GUI easier.

EDIT: Forgot to mention that I didn't really integrate tags into the texture cache. All I did was set a (new) default image.

jmarshallnz
Owner

Wow, nice work!

We need to consider carefully before we add the ability to the UI to add tags/delete tags etc.

To me it seems natural that this would be done from the info dialog - i.e. you'd add tags to a movie, and if they're new tags they're added. I guess it depends on how you're using the tag (are you using it to group a bunch of movies together, or using it to add additional lookup info to each movie?)

Either way, I can already hear the demands for removing the "New tag..." item :)

I'm seriously starting to think that we should be removing ability to customise stuff from the UI, at least the clunky way we have things currently, and instead ensure that it can be done well over JSON-RPC and then implementing (or encouraging) a nice web interface/remote app interface for doing it. After all, more and more folk have a touch interface available to them, and it's far more efficient to do things that way. Further, we should be looking at modifying the exiting info window so it can handle this stuff, perhaps via creating a new "Edit" dialog and having that replace the existing info dialog over time?

Sascha Montellese
Collaborator

The reason I put all the "New tag...", "Add movies" and "Remove movies" into the tags node is because I didn't wanna put any more buttons/context menu entries into the movie view. There the context menu is already too full as it is. I agree that the more common use case would be for someone to look through his movie library and then think "Ah this movie could be tagged with foo and bar" and would want to do that right from where he is.
But at the beginning when there are 0 tags it might be faster to be able to create a new tag and get prompted with a list of all movies and then you can quickly go through it and add matching movies. If later on you notice that you missed one you could still add it.

Tags, sets etc can already be manipulated over JSON-RPC but there is one problem (especially for tags): it is not "additive" or "subtractive" i.e. you always have to retrieve the existing list of tags/sets, then add/remove items and pass that to VideoLibrary.SetMovieDetails. While this is easy enough for clients to implement, when done wrong the user will loose all his/her tags/sets/... except the new one(s).

jmarshallnz
Owner

The only problem I have with the context menu is it's quite slow without a keyboard + mouse anyway (or touch equivalently) whereby I wonder whether it might be better to leave it out until we have a better "Edit Movie" dialog (to eventually replace the video info dialog). Doesn't really worry me much either way as it can be removed later if/when a better way to handle it is done.

Sascha Montellese
Collaborator

OK I have re-written the current logic to be more or less media type agnostic i.e. no hardcoded movie support. Both the "tag" (so that tag name uniqueness checks only apply within a certain media type) and the "taglinks" table have a "media_type" column and all the context buttons etc determine the current media type based on the current path.

Sascha Montellese
Collaborator

OK I have updated the code. I removed the media_type column from the "tag" table and replaced it's functionality with sub-queries. Furthermore I have updated the (UNIQUE) INDEX queries and tested them with EXPLAIN QUERY PLAN and they are being used as expected.

jmarshallnz
Owner

Looks ok - just need xcode projects update as well (I can probably add after the fact if you want).

Sascha Montellese
Collaborator

And there are the updates of the xcode project files ;-)

jmarshallnz
Owner

Ready to go then once the minors are taken care of.

Sascha Montellese Montellese merged commit f128546 into from July 08, 2012
Sascha Montellese Montellese closed this July 08, 2012
Karlson2k
Collaborator

Disadvantage: if you have thousands of movies than it will be real pain to mark them as set. It will be a real pain even to find specific "movie set" in tags to make single movie by hands!
Besides TMDB, kinopoisk also supports movie sets.
Why do you want to just to strip movie set support from scrappers?
My suggestion: future more extend movie tag to support different movie tag type. Convert "movie set" to "movie set" tag type.

Sascha Montellese
Collaborator

Nobody talked about removing movie set support so not sure what you are talking about or what your concern is. Movie sets will stay in XBMC but they will most likely be limited to one set per movie so so "Batman" can't be part of both a "Batman" and a "DC Comics" set. Instead you can e.g. put it in a "Batman" set and add a "DC Comics" tag to it. And the TMDB scraper will still scrape the movie set information from TMDB and we talked to the TMDB admin and he assured that TMDB will always only support one set per movie.

Karlson2k
Collaborator

OK, thanks for explanation.
This way is looks good.

Deano316

So we could in theory, once this is fully implemented, have a hotlink from a movie to a tv show/tv show season?

Sascha Montellese
Collaborator

These are not hotlinks, these are tags. You can add as many tags to a movie as you want (although currently it only works the other way around in the GUI because you can only add movies to a tag). Linking between different media items is something completely different.

vicbitter

On MySQL version 5.5.5 and above, the default storage engine is InnoDB. Creating the tag table and index with storage engine InnoDB fails. Can you create the table using ENGINE = MYISAM?

Michal Piechowiak

I wonder if we should set content to "movies" here - this is list of tags - maybe items.SetContent("tags");?

Owner

Yeah, probably should have content type tags for this one.

Michal Piechowiak

why is this visible condition needed?

Owner

copy n paste ftw?

Collaborator

Hehe yes. Thanks for pointing it out.

Tobias Hieta tru referenced this pull request from a commit in RasPlex/plex-home-theatre April 10, 2014
Tobias Hieta Started work on PlayQueues always visible.
Part of #1108
f429884
Tobias Hieta tru referenced this pull request from a commit in RasPlex/plex-home-theatre April 10, 2014
Tobias Hieta Started work on PlayQueues always visible.
Part of #1108
dc9fddb
Tobias Hieta tru referenced this pull request from a commit in RasPlex/plex-home-theatre April 16, 2014
Tobias Hieta Refactored PlayQueueManager.
This makes it possible to handle Local and Remote playQueues with the
same API and also show the PlayQueue on the HomeWindow at all times. It
now also restores the PlayQueue when the sessions ends.

Clicking the PlayQueue from the home window also takes it to
PlexPlayQueue.xml

Related to #1108
Related to #1106
144436f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.

Showing 30 changed files with 636 additions and 22 deletions. Show diff stats Hide diff stats

  1. 6  XBMC-ATV2.xcodeproj/project.pbxproj
  2. 6  XBMC-IOS.xcodeproj/project.pbxproj
  3. 6  XBMC.xcodeproj/project.pbxproj
  4. 26  language/English/strings.po
  5. 2  project/VS2010Express/XBMC.vcxproj
  6. 6  project/VS2010Express/XBMC.vcxproj.filters
  7. 5  system/library/video/movies/tags.xml
  8. 2  xbmc/dialogs/GUIDialogContextMenu.h
  9. 8  xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
  10. 8  xbmc/filesystem/VideoDatabaseDirectory.cpp
  11. 3  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.cpp
  12. 3  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.h
  13. 1  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.cpp
  14. 60  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTags.cpp
  15. 41  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTags.h
  16. 2  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.cpp
  17. 1  xbmc/filesystem/VideoDatabaseDirectory/Makefile
  18. 4  xbmc/filesystem/VideoDatabaseDirectory/QueryParams.cpp
  19. 2  xbmc/filesystem/VideoDatabaseDirectory/QueryParams.h
  20. 6  xbmc/playlists/SmartPlayList.cpp
  21. 3  xbmc/utils/DatabaseUtils.h
  22. 12  xbmc/video/GUIViewStateVideo.cpp
  23. 178  xbmc/video/VideoDatabase.cpp
  24. 14  xbmc/video/VideoDatabase.h
  25. 6  xbmc/video/VideoInfoTag.cpp
  26. 1  xbmc/video/VideoInfoTag.h
  27. 5  xbmc/video/windows/GUIWindowVideoBase.cpp
  28. 236  xbmc/video/windows/GUIWindowVideoNav.cpp
  29. 3  xbmc/video/windows/GUIWindowVideoNav.h
  30. 2  xbmc/windows/GUIMediaWindow.cpp
6  XBMC-ATV2.xcodeproj/project.pbxproj
@@ -21,6 +21,7 @@
21 21
 		32D6D47C1423A9D8003641AC /* JpegIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32D6D47A1423A9D8003641AC /* JpegIO.cpp */; };
22 22
 		36A9445915821F8300727135 /* DatabaseUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9445715821F8300727135 /* DatabaseUtils.cpp */; };
23 23
 		36A9445D15821FAC00727135 /* SortUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9445B15821FAB00727135 /* SortUtils.cpp */; };
  24
+		36A9465315AA269B00727135 /* DirectoryNodeTags.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9465115AA269B00727135 /* DirectoryNodeTags.cpp */; };
24 25
 		4D5D2E131301753F006ABC13 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D5D2E121301753F006ABC13 /* CFNetwork.framework */; };
25 26
 		7C0A7ECD13A5DBF900AFC2BD /* AppParamParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7ECB13A5DBF900AFC2BD /* AppParamParser.cpp */; };
26 27
 		7C0A7FC813A9E75400AFC2BD /* DirtyRegionSolvers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7FC413A9E75400AFC2BD /* DirtyRegionSolvers.cpp */; };
@@ -1010,6 +1011,8 @@
1010 1011
 		36A9445A15821F9100727135 /* ISortable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ISortable.h; sourceTree = "<group>"; };
1011 1012
 		36A9445B15821FAB00727135 /* SortUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SortUtils.cpp; sourceTree = "<group>"; };
1012 1013
 		36A9445C15821FAB00727135 /* SortUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SortUtils.h; sourceTree = "<group>"; };
  1014
+		36A9465115AA269B00727135 /* DirectoryNodeTags.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DirectoryNodeTags.cpp; sourceTree = "<group>"; };
  1015
+		36A9465215AA269B00727135 /* DirectoryNodeTags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryNodeTags.h; sourceTree = "<group>"; };
1013 1016
 		4D5D2E121301753F006ABC13 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
1014 1017
 		7C0A7ECB13A5DBF900AFC2BD /* AppParamParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AppParamParser.cpp; sourceTree = "<group>"; };
1015 1018
 		7C0A7ECC13A5DBF900AFC2BD /* AppParamParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppParamParser.h; sourceTree = "<group>"; };
@@ -4525,6 +4528,8 @@
4525 4528
 				F56C7481131EC152000AD0F6 /* DirectoryNodeSets.h */,
4526 4529
 				F56C7482131EC152000AD0F6 /* DirectoryNodeStudio.cpp */,
4527 4530
 				F56C7483131EC152000AD0F6 /* DirectoryNodeStudio.h */,
  4531
+				36A9465115AA269B00727135 /* DirectoryNodeTags.cpp */,
  4532
+				36A9465215AA269B00727135 /* DirectoryNodeTags.h */,
4528 4533
 				F56C7484131EC152000AD0F6 /* DirectoryNodeTitleMovies.cpp */,
4529 4534
 				F56C7485131EC152000AD0F6 /* DirectoryNodeTitleMovies.h */,
4530 4535
 				F56C7486131EC152000AD0F6 /* DirectoryNodeTitleMusicVideos.cpp */,
@@ -7219,6 +7224,7 @@
7219 7224
 				36A9445915821F8300727135 /* DatabaseUtils.cpp in Sources */,
7220 7225
 				36A9445D15821FAC00727135 /* SortUtils.cpp in Sources */,
7221 7226
 				DF08E84515829BA600058C77 /* Exception.cpp in Sources */,
  7227
+				36A9465315AA269B00727135 /* DirectoryNodeTags.cpp in Sources */,
7222 7228
 			);
7223 7229
 			runOnlyForDeploymentPostprocessing = 0;
7224 7230
 		};
6  XBMC-IOS.xcodeproj/project.pbxproj
@@ -22,6 +22,7 @@
22 22
 		3291892B1423A9B700E878CD /* JpegIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 329189291423A9B700E878CD /* JpegIO.cpp */; };
23 23
 		36A9444E15821F2C00727135 /* DatabaseUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9444C15821F2C00727135 /* DatabaseUtils.cpp */; };
24 24
 		36A9445215821F5300727135 /* SortUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9445015821F5300727135 /* SortUtils.cpp */; };
  25
+		36A9465B15AA26BC00727135 /* DirectoryNodeTags.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9465915AA26BC00727135 /* DirectoryNodeTags.cpp */; };
25 26
 		4D5D2E1E1301758F006ABC13 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D5D2E1D1301758F006ABC13 /* CFNetwork.framework */; };
26 27
 		7C0A7EDE13A5DC2800AFC2BD /* AppParamParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7EDC13A5DC2800AFC2BD /* AppParamParser.cpp */; };
27 28
 		7C0A7F9D13A9E70800AFC2BD /* GUIWindowDebugInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7F9B13A9E70800AFC2BD /* GUIWindowDebugInfo.cpp */; };
@@ -1010,6 +1011,8 @@
1010 1011
 		36A9444F15821F3B00727135 /* ISortable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ISortable.h; sourceTree = "<group>"; };
1011 1012
 		36A9445015821F5300727135 /* SortUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SortUtils.cpp; sourceTree = "<group>"; };
1012 1013
 		36A9445115821F5300727135 /* SortUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SortUtils.h; sourceTree = "<group>"; };
  1014
+		36A9465915AA26BC00727135 /* DirectoryNodeTags.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DirectoryNodeTags.cpp; sourceTree = "<group>"; };
  1015
+		36A9465A15AA26BC00727135 /* DirectoryNodeTags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryNodeTags.h; sourceTree = "<group>"; };
1013 1016
 		4D5D2E1D1301758F006ABC13 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
1014 1017
 		7C0A7EDC13A5DC2800AFC2BD /* AppParamParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AppParamParser.cpp; sourceTree = "<group>"; };
1015 1018
 		7C0A7EDD13A5DC2800AFC2BD /* AppParamParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppParamParser.h; sourceTree = "<group>"; };
@@ -4881,6 +4884,8 @@
4881 4884
 				F56C8464131F42E8000AD0F6 /* DirectoryNodeSets.h */,
4882 4885
 				F56C8465131F42E8000AD0F6 /* DirectoryNodeStudio.cpp */,
4883 4886
 				F56C8466131F42E8000AD0F6 /* DirectoryNodeStudio.h */,
  4887
+				36A9465915AA26BC00727135 /* DirectoryNodeTags.cpp */,
  4888
+				36A9465A15AA26BC00727135 /* DirectoryNodeTags.h */,
4884 4889
 				F56C8467131F42E8000AD0F6 /* DirectoryNodeTitleMovies.cpp */,
4885 4890
 				F56C8468131F42E8000AD0F6 /* DirectoryNodeTitleMovies.h */,
4886 4891
 				F56C8469131F42E8000AD0F6 /* DirectoryNodeTitleMusicVideos.cpp */,
@@ -7230,6 +7235,7 @@
7230 7235
 				36A9444E15821F2C00727135 /* DatabaseUtils.cpp in Sources */,
7231 7236
 				36A9445215821F5300727135 /* SortUtils.cpp in Sources */,
7232 7237
 				DFC3867E158296EC008AE277 /* Exception.cpp in Sources */,
  7238
+				36A9465B15AA26BC00727135 /* DirectoryNodeTags.cpp in Sources */,
7233 7239
 			);
7234 7240
 			runOnlyForDeploymentPostprocessing = 0;
7235 7241
 		};
6  XBMC.xcodeproj/project.pbxproj
@@ -194,6 +194,7 @@
194 194
 		32C631281423A90F00F18420 /* JpegIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32C631261423A90F00F18420 /* JpegIO.cpp */; };
195 195
 		36A9443D15821E2800727135 /* DatabaseUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9443B15821E2800727135 /* DatabaseUtils.cpp */; };
196 196
 		36A9444115821E7C00727135 /* SortUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9443F15821E7C00727135 /* SortUtils.cpp */; };
  197
+		36A9464C15AA25FD00727135 /* DirectoryNodeTags.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9464A15AA25FD00727135 /* DirectoryNodeTags.cpp */; };
197 198
 		3802709A13D5A653009493DD /* SystemClock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3802709813D5A653009493DD /* SystemClock.cpp */; };
198 199
 		384718D81325BA04000486D6 /* XBDateTime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 384718D61325BA04000486D6 /* XBDateTime.cpp */; };
199 200
 		38F4E57013CCCB3B00664821 /* Implementation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 38F4E56C13CCCB3B00664821 /* Implementation.cpp */; };
@@ -1376,6 +1377,8 @@
1376 1377
 		36A9443E15821E5400727135 /* ISortable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ISortable.h; sourceTree = "<group>"; };
1377 1378
 		36A9443F15821E7C00727135 /* SortUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SortUtils.cpp; sourceTree = "<group>"; };
1378 1379
 		36A9444015821E7C00727135 /* SortUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SortUtils.h; sourceTree = "<group>"; };
  1380
+		36A9464A15AA25FD00727135 /* DirectoryNodeTags.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DirectoryNodeTags.cpp; sourceTree = "<group>"; };
  1381
+		36A9464B15AA25FD00727135 /* DirectoryNodeTags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryNodeTags.h; sourceTree = "<group>"; };
1379 1382
 		3802709713D5A62D009493DD /* ThreadLocal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadLocal.h; sourceTree = "<group>"; };
1380 1383
 		3802709813D5A653009493DD /* SystemClock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SystemClock.cpp; sourceTree = "<group>"; };
1381 1384
 		3802709913D5A653009493DD /* SystemClock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SystemClock.h; sourceTree = "<group>"; };
@@ -5145,6 +5148,8 @@
5145 5148
 				7CCF7E711067643800992676 /* DirectoryNodeSets.h */,
5146 5149
 				E38E177A0D25F9FA00618676 /* DirectoryNodeStudio.cpp */,
5147 5150
 				E38E177B0D25F9FA00618676 /* DirectoryNodeStudio.h */,
  5151
+				36A9464A15AA25FD00727135 /* DirectoryNodeTags.cpp */,
  5152
+				36A9464B15AA25FD00727135 /* DirectoryNodeTags.h */,
5148 5153
 				E38E177C0D25F9FA00618676 /* DirectoryNodeTitleMovies.cpp */,
5149 5154
 				E38E177D0D25F9FA00618676 /* DirectoryNodeTitleMovies.h */,
5150 5155
 				E38E177E0D25F9FA00618676 /* DirectoryNodeTitleMusicVideos.cpp */,
@@ -7311,6 +7316,7 @@
7311 7316
 				36A9443D15821E2800727135 /* DatabaseUtils.cpp in Sources */,
7312 7317
 				36A9444115821E7C00727135 /* SortUtils.cpp in Sources */,
7313 7318
 				1DE0443515828F4B005DDB4D /* Exception.cpp in Sources */,
  7319
+				36A9464C15AA25FD00727135 /* DirectoryNodeTags.cpp in Sources */,
7314 7320
 			);
7315 7321
 			runOnlyForDeploymentPostprocessing = 0;
7316 7322
 		};
26  language/English/strings.po
@@ -7427,8 +7427,32 @@ msgctxt "#20458"
7427 7427
 msgid "Group movies in sets"
7428 7428
 msgstr ""
7429 7429
 
  7430
+msgctxt "#20459"
  7431
+msgid "Tags"
  7432
+msgstr ""
  7433
+
  7434
+msgctxt "#20460"
  7435
+msgid "Add %s"
  7436
+msgstr ""
  7437
+
  7438
+msgctxt "#20461"
  7439
+msgid "Remove %s"
  7440
+msgstr ""
  7441
+
  7442
+msgctxt "#20462"
  7443
+msgid "New tag..."
  7444
+msgstr ""
  7445
+
  7446
+msgctxt "#20463"
  7447
+msgid "A tag with the name '%s' already exists."
  7448
+msgstr ""
  7449
+
  7450
+msgctxt "#20464"
  7451
+msgid "Select %s"
  7452
+msgstr ""
  7453
+
7430 7454
 #up to 21329 is reserved for the video db !! !
7431  
-#empty strings from id 20459 to 21329
  7455
+#empty strings from id 20465 to 21329
7432 7456
 
7433 7457
 msgctxt "#21330"
7434 7458
 msgid "Show hidden files and directories"
2  project/VS2010Express/XBMC.vcxproj
@@ -449,6 +449,7 @@
449 449
     <ClCompile Include="..\..\xbmc\filesystem\UPnPFile.cpp" />
450 450
     <ClCompile Include="..\..\xbmc\filesystem\VideoDatabaseDirectory.cpp" />
451 451
     <ClCompile Include="..\..\xbmc\FileSystem\VideoDatabaseDirectory\DirectoryNodeCountry.cpp" />
  452
+    <ClCompile Include="..\..\xbmc\filesystem\VideoDatabaseDirectory\DirectoryNodeTags.cpp" />
452 453
     <ClCompile Include="..\..\xbmc\filesystem\windows\WINFileSMB.cpp" />
453 454
     <ClCompile Include="..\..\xbmc\filesystem\windows\WINSMBDirectory.cpp" />
454 455
     <ClCompile Include="..\..\xbmc\filesystem\VirtualDirectory.cpp" />
@@ -926,6 +927,7 @@
926 927
     <ClInclude Include="..\..\xbmc\cores\dvdplayer\DVDCodecs\Audio\DVDAudioCodecPassthrough.h" />
927 928
     <ClInclude Include="..\..\xbmc\cores\paplayer\PCMCodec.h" />
928 929
     <ClInclude Include="..\..\xbmc\filesystem\ImageFile.h" />
  930
+    <ClInclude Include="..\..\xbmc\filesystem\VideoDatabaseDirectory\DirectoryNodeTags.h" />
929 931
     <ClInclude Include="..\..\xbmc\filesystem\windows\WINFileSMB.h" />
930 932
     <ClInclude Include="..\..\xbmc\filesystem\windows\WINSMBDirectory.h" />
931 933
     <ClInclude Include="..\..\xbmc\input\windows\WINJoystick.h" />
6  project/VS2010Express/XBMC.vcxproj.filters
@@ -2586,6 +2586,9 @@
2586 2586
     <ClCompile Include="..\..\xbmc\utils\DatabaseUtils.cpp">
2587 2587
       <Filter>utils</Filter>
2588 2588
     </ClCompile>
  2589
+    <ClCompile Include="..\..\xbmc\filesystem\VideoDatabaseDirectory\DirectoryNodeTags.cpp">
  2590
+      <Filter>filesystem\VideoDatabaseDirectory</Filter>
  2591
+    </ClCompile>
2589 2592
   </ItemGroup>
2590 2593
   <ItemGroup>
2591 2594
     <ClInclude Include="..\..\xbmc\win32\pch.h">
@@ -5216,6 +5219,9 @@
5216 5219
     <ClInclude Include="..\..\xbmc\utils\ISortable.h">
5217 5220
       <Filter>utils</Filter>
5218 5221
     </ClInclude>
  5222
+    <ClInclude Include="..\..\xbmc\filesystem\VideoDatabaseDirectory\DirectoryNodeTags.h">
  5223
+      <Filter>filesystem\VideoDatabaseDirectory</Filter>
  5224
+    </ClInclude>
5219 5225
   </ItemGroup>
5220 5226
   <ItemGroup>
5221 5227
     <ResourceCompile Include="..\..\xbmc\win32\XBMC_PC.rc">
5  system/library/video/movies/tags.xml
... ...
@@ -0,0 +1,5 @@
  1
+<node order="7" type="folder" visible="Library.HasContent(MovieSets)">
  2
+  <label>20459</label>
  3
+  <path>videodb://1/9</path>
  4
+  <icon>DefaultTags.png</icon>
  5
+</node>
2  xbmc/dialogs/GUIDialogContextMenu.h
@@ -104,6 +104,8 @@ enum CONTEXT_BUTTON { CONTEXT_BUTTON_CANCELLED = 0,
104 104
                       CONTEXT_BUTTON_SET_MOVIESET_FANART,
105 105
                       CONTEXT_BUTTON_DELETE_PLUGIN,
106 106
                       CONTEXT_BUTTON_PLAY_AND_QUEUE,
  107
+                      CONTEXT_BUTTON_TAGS_ADD_ITEMS,
  108
+                      CONTEXT_BUTTON_TAGS_REMOVE_ITEMS,
107 109
                       CONTEXT_BUTTON_USER1,
108 110
                       CONTEXT_BUTTON_USER2,
109 111
                       CONTEXT_BUTTON_USER3,
8  xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
@@ -240,6 +240,14 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
240 240
     videodatabase.GetSetsNav("videodb://1/7/", items, VIDEODB_CONTENT_MOVIES);
241 241
     iLabel = 20434;
242 242
   }
  243
+  else if (m_rule.m_field == FieldTag)
  244
+  {
  245
+    if (m_type == "movies")
  246
+      videodatabase.GetTagsNav("videodb://1/9/", items, VIDEODB_CONTENT_MOVIES);
  247
+    else
  248
+      return;
  249
+    iLabel = 20459;
  250
+  }
243 251
   else
244 252
   { // TODO: Add browseability in here.
245 253
     assert(false);
8  xbmc/filesystem/VideoDatabaseDirectory.cpp
@@ -160,6 +160,10 @@ bool CVideoDatabaseDirectory::GetLabel(const CStdString& strDirectory, CStdStrin
160 160
   if (params.GetSetId() != -1)
161 161
     strLabel += videodatabase.GetSetById(params.GetSetId());
162 162
 
  163
+  // get tag
  164
+  if (params.GetTagId() != -1)
  165
+    strLabel += videodatabase.GetTagById(params.GetTagId());
  166
+
163 167
   // get year
164 168
   if (params.GetYear() != -1)
165 169
   {
@@ -190,6 +194,8 @@ bool CVideoDatabaseDirectory::GetLabel(const CStdString& strDirectory, CStdStrin
190 194
       strLabel = g_localizeStrings.Get(20348); break;
191 195
     case NODE_TYPE_SETS: // Sets
192 196
       strLabel = g_localizeStrings.Get(20434); break;
  197
+    case NODE_TYPE_TAGS: // Tags
  198
+      strLabel = g_localizeStrings.Get(20459); break;
193 199
     case NODE_TYPE_MOVIES_OVERVIEW: // Movies
194 200
       strLabel = g_localizeStrings.Get(342); break;
195 201
     case NODE_TYPE_TVSHOWS_OVERVIEW: // TV Shows
@@ -253,6 +259,8 @@ CStdString CVideoDatabaseDirectory::GetIcon(const CStdString &strDirectory)
253 259
     return "DefaultCountry.png";
254 260
   case NODE_TYPE_SETS: // Sets
255 261
     return "DefaultSets.png";
  262
+  case NODE_TYPE_TAGS: // Tags
  263
+    return "DefaultTags.png";
256 264
   case NODE_TYPE_YEAR: // Year
257 265
     return "DefaultYear.png";
258 266
   case NODE_TYPE_DIRECTOR: // Director
3  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.cpp
@@ -43,6 +43,7 @@
43 43
 #include "DirectoryNodeRecentlyAddedMusicVideos.h"
44 44
 #include "DirectoryNodeTitleMusicVideos.h"
45 45
 #include "DirectoryNodeMusicVideoAlbum.h"
  46
+#include "DirectoryNodeTags.h"
46 47
 #include "video/VideoInfoTag.h"
47 48
 #include "URL.h"
48 49
 #include "settings/AdvancedSettings.h"
@@ -122,6 +123,8 @@ CDirectoryNode* CDirectoryNode::CreateNode(NODE_TYPE Type, const CStdString& str
122 123
     return new CDirectoryNodeCountry(strName, pParent);
123 124
   case NODE_TYPE_SETS:
124 125
     return new CDirectoryNodeSets(strName, pParent);
  126
+  case NODE_TYPE_TAGS:
  127
+    return new CDirectoryNodeTags(strName, pParent);
125 128
   case NODE_TYPE_YEAR:
126 129
     return new CDirectoryNodeYear(strName, pParent);
127 130
   case NODE_TYPE_ACTOR:
3  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.h
@@ -54,7 +54,8 @@ namespace XFILE
54 54
       NODE_TYPE_TITLE_MUSICVIDEOS,
55 55
       NODE_TYPE_MUSICVIDEOS_ALBUM,
56 56
       NODE_TYPE_SETS,
57  
-      NODE_TYPE_COUNTRY
  57
+      NODE_TYPE_COUNTRY,
  58
+      NODE_TYPE_TAGS
58 59
     } NODE_TYPE;
59 60
 
60 61
     typedef struct {
1  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.cpp
@@ -36,6 +36,7 @@ Node MovieChildren[] = {
36 36
                         { NODE_TYPE_STUDIO,       6, 20388 },
37 37
                         { NODE_TYPE_SETS,         7, 20434 },
38 38
                         { NODE_TYPE_COUNTRY,      8, 20451 },
  39
+                        { NODE_TYPE_TAGS,         9, 20459 }
39 40
                        };
40 41
 
41 42
 CDirectoryNodeMoviesOverview::CDirectoryNodeMoviesOverview(const CStdString& strName, CDirectoryNode* pParent)
60  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTags.cpp
... ...
@@ -0,0 +1,60 @@
  1
+/*
  2
+ *      Copyright (C) 2012 Team XBMC
  3
+ *      http://www.xbmc.org
  4
+ *
  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.
  9
+ *
  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.
  14
+ *
  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
  19
+ *
  20
+ */
  21
+
  22
+#include "DirectoryNodeTags.h"
  23
+#include "QueryParams.h"
  24
+#include "video/VideoDatabase.h"
  25
+
  26
+using namespace XFILE::VIDEODATABASEDIRECTORY;
  27
+
  28
+CDirectoryNodeTags::CDirectoryNodeTags(const CStdString& strName, CDirectoryNode* pParent)
  29
+  : CDirectoryNode(NODE_TYPE_TAGS, strName, pParent)
  30
+{
  31
+
  32
+}
  33
+
  34
+NODE_TYPE CDirectoryNodeTags::GetChildType() const
  35
+{
  36
+  return NODE_TYPE_TITLE_MOVIES;
  37
+}
  38
+
  39
+CStdString CDirectoryNodeTags::GetLocalizedName() const
  40
+{
  41
+  CVideoDatabase db;
  42
+  if (db.Open())
  43
+    return db.GetTagById(GetID());
  44
+  return "";
  45
+}
  46
+
  47
+bool CDirectoryNodeTags::GetContent(CFileItemList& items) const
  48
+{
  49
+  CVideoDatabase videodatabase;
  50
+  if (!videodatabase.Open())
  51
+    return false;
  52
+
  53
+  CQueryParams params;
  54
+  CollectQueryParams(params);
  55
+
  56
+  bool bSuccess = videodatabase.GetTagsNav(BuildPath(), items, params.GetContentType());
  57
+  videodatabase.Close();
  58
+
  59
+  return bSuccess;
  60
+}
41  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTags.h
... ...
@@ -0,0 +1,41 @@
  1
+#pragma once
  2
+/*
  3
+ *      Copyright (C) 2012 Team XBMC
  4
+ *      http://www.xbmc.org
  5
+ *
  6
+ *  This Program is free software; you can redistribute it and/or modify
  7
+ *  it under the terms of the GNU General Public License as published by
  8
+ *  the Free Software Foundation; either version 2, or (at your option)
  9
+ *  any later version.
  10
+ *
  11
+ *  This Program is distributed in the hope that it will be useful,
  12
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  13
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14
+ *  GNU General Public License for more details.
  15
+ *
  16
+ *  You should have received a copy of the GNU General Public License
  17
+ *  along with XBMC; see the file COPYING.  If not, write to
  18
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  19
+ *  http://www.gnu.org/copyleft/gpl.html
  20
+ *
  21
+ */
  22
+
  23
+#include "DirectoryNode.h"
  24
+
  25
+namespace XFILE
  26
+{
  27
+  namespace VIDEODATABASEDIRECTORY
  28
+  {
  29
+    class CDirectoryNodeTags : public CDirectoryNode
  30
+    {
  31
+    public:
  32
+      CDirectoryNodeTags(const CStdString& strName, CDirectoryNode* pParent);
  33
+    protected:
  34
+      virtual NODE_TYPE GetChildType() const;
  35
+      virtual bool GetContent(CFileItemList& items) const;
  36
+      virtual CStdString GetLocalizedName() const;
  37
+    };
  38
+  }
  39
+}
  40
+
  41
+
2  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.cpp
@@ -41,7 +41,7 @@ bool CDirectoryNodeTitleMovies::GetContent(CFileItemList& items) const
41 41
   CollectQueryParams(params);
42 42
 
43 43
   CStdString strBaseDir=BuildPath();
44  
-  bool bSuccess=videodatabase.GetMoviesNav(strBaseDir, items, params.GetGenreId(), params.GetYear(), params.GetActorId(), params.GetDirectorId(), params.GetStudioId(), params.GetCountryId(), params.GetSetId());
  44
+  bool bSuccess=videodatabase.GetMoviesNav(strBaseDir, items, params.GetGenreId(), params.GetYear(), params.GetActorId(), params.GetDirectorId(), params.GetStudioId(), params.GetCountryId(), params.GetSetId(), params.GetTagId());
45 45
 
46 46
   videodatabase.Close();
47 47
 
1  xbmc/filesystem/VideoDatabaseDirectory/Makefile
@@ -15,6 +15,7 @@ SRCS=DirectoryNode.cpp \
15 15
      DirectoryNodeSeasons.cpp \
16 16
      DirectoryNodeSets.cpp \
17 17
      DirectoryNodeStudio.cpp \
  18
+     DirectoryNodeTags.cpp \
18 19
      DirectoryNodeTitleMovies.cpp \
19 20
      DirectoryNodeTitleMusicVideos.cpp \
20 21
      DirectoryNodeTitleTvShows.cpp \
4  xbmc/filesystem/VideoDatabaseDirectory/QueryParams.cpp
@@ -39,6 +39,7 @@ CQueryParams::CQueryParams()
39 39
   m_idMVideo = -1;
40 40
   m_idAlbum = -1;
41 41
   m_idSet = -1;
  42
+  m_idTag = -1;
42 43
 }
43 44
 
44 45
 void CQueryParams::SetQueryParam(NODE_TYPE NodeType, const CStdString& strNodeName)
@@ -92,6 +93,9 @@ void CQueryParams::SetQueryParam(NODE_TYPE NodeType, const CStdString& strNodeNa
92 93
   case NODE_TYPE_SETS:
93 94
     m_idSet = idDb;
94 95
     break;
  96
+  case NODE_TYPE_TAGS:
  97
+    m_idTag = idDb;
  98
+    break;
95 99
   default:
96 100
     break;
97 101
   }
2  xbmc/filesystem/VideoDatabaseDirectory/QueryParams.h
@@ -44,6 +44,7 @@ namespace XFILE
44 44
       long GetStudioId() const { return m_idStudio; }
45 45
       long GetMVideoId() const { return m_idMVideo; }
46 46
       long GetSetId() const { return m_idSet; }
  47
+      long GetTagId() const { return m_idTag; }
47 48
 
48 49
     protected:
49 50
       void SetQueryParam(NODE_TYPE NodeType, const CStdString& strNodeName);
@@ -64,6 +65,7 @@ namespace XFILE
64 65
       long m_idMVideo;
65 66
       long m_idAlbum;
66 67
       long m_idSet;
  68
+      long m_idTag;
67 69
     };
68 70
   }
69 71
 }
6  xbmc/playlists/SmartPlayList.cpp
@@ -98,7 +98,8 @@ static const translateField fields[] = {
98 98
   { "audiolanguage",     FieldAudioLanguage,           SortByAudioLanguage,            CSmartPlaylistRule::TEXTIN_FIELD,     21447 },
99 99
   { "subtitlelanguage",  FieldSubtitleLanguage,        SortBySubtitleLanguage,         CSmartPlaylistRule::TEXTIN_FIELD,     21448 },
100 100
   { "random",            FieldRandom,                  SortByRandom,                   CSmartPlaylistRule::TEXT_FIELD,       590 },
101  
-  { "playlist",          FieldPlaylist,                SortByPlaylistOrder,            CSmartPlaylistRule::PLAYLIST_FIELD,   559 }
  101
+  { "playlist",          FieldPlaylist,                SortByPlaylistOrder,            CSmartPlaylistRule::PLAYLIST_FIELD,   559 },
  102
+  { "tag",               FieldTag,                     SortByNone,                     CSmartPlaylistRule::BROWSEABLE_FIELD, 20459 }
102 103
 };
103 104
 
104 105
 #define NUM_FIELDS sizeof(fields) / sizeof(translateField)
@@ -382,6 +383,7 @@ vector<Field> CSmartPlaylistRule::GetFields(const CStdString &type)
382 383
     fields.push_back(FieldFilename);
383 384
     fields.push_back(FieldPath);
384 385
     fields.push_back(FieldSet);
  386
+    fields.push_back(FieldTag);
385 387
     fields.push_back(FieldDateAdded);
386 388
     isVideo = true;
387 389
   }
@@ -716,6 +718,8 @@ CStdString CSmartPlaylistRule::GetWhereClause(CDatabase &db, const CStdString& s
716 718
         query = table + ".idFile " + negate + " IN (SELECT idFile FROM bookmark WHERE type = 1)";
717 719
       else if (m_field == FieldSet)
718 720
         query = GetField(FieldId, strType) + negate + " IN (SELECT idMovie FROM setlinkmovie JOIN sets ON sets.idSet=setlinkmovie.idSet WHERE sets.strSet" + parameter + ")";
  721
+      else if (m_field == FieldTag)
  722
+        query = GetField(FieldId, strType) + negate + " IN (SELECT idMedia FROM taglinks JOIN tag ON tag.idTag = taglinks.idTag WHERE tag.strTag" + parameter + " AND taglinks.media_type = 'movie')";
719 723
     }
720 724
     else if (strType == "musicvideos")
721 725
     {
3  xbmc/utils/DatabaseUtils.h
@@ -109,7 +109,8 @@ typedef enum {
109 109
   FieldAudioCodec,
110 110
   FieldAudioLanguage,
111 111
   FieldSubtitleLanguage,
112  
-  FieldProductionCode
  112
+  FieldProductionCode,
  113
+  FieldTag
113 114
 } Field;
114 115
 
115 116
 typedef std::set<Field> Fields;
12  xbmc/video/GUIViewStateVideo.cpp
@@ -211,6 +211,18 @@ CGUIViewStateWindowVideoNav::CGUIViewStateWindowVideoNav(const CFileItemList& it
211 211
         SetSortOrder(g_settings.m_viewStateVideoNavGenres.m_sortOrder);
212 212
       }
213 213
       break;
  214
+    case NODE_TYPE_TAGS:
  215
+      {
  216
+        SORT_METHOD method = SORT_METHOD_LABEL_IGNORE_THE;
  217
+        if (!g_guiSettings.GetBool("filelists.ignorethewhensorting"))
  218
+          method = SORT_METHOD_LABEL;
  219
+
  220
+        AddSortMethod(method, 551, LABEL_MASKS("%T","", "%T",""));  // Title, empty | Title, empty
  221
+        SetSortMethod(method);
  222
+        SetViewAsControl(g_settings.m_viewStateVideoNavGenres.m_viewMode);
  223
+        SetSortOrder(g_settings.m_viewStateVideoNavGenres.m_sortOrder);
  224
+      }
  225
+      break;
214 226
     case NODE_TYPE_EPISODES:
215 227
       {
216 228
         if (params.GetSeason() > -1)
178  xbmc/video/VideoDatabase.cpp
@@ -321,6 +321,16 @@ bool CVideoDatabase::CreateTables()
321 321
     m_pDS->exec("CREATE TRIGGER delete_set AFTER DELETE ON sets FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idSet AND media_type='set'; END");
322 322
     m_pDS->exec("CREATE TRIGGER delete_person AFTER DELETE ON actors FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idActor AND media_type IN ('actor','artist','writer','director'); END");
323 323
 
  324
+    CLog::Log(LOGINFO, "create tag table");
  325
+    m_pDS->exec("CREATE TABLE tag (idTag integer primary key, strTag text)");
  326
+    m_pDS->exec("CREATE UNIQUE INDEX ix_tag_1 ON tag (strTag(256))");
  327
+
  328
+    CLog::Log(LOGINFO, "create taglinks table");
  329
+    m_pDS->exec("CREATE TABLE taglinks (idTag integer, idMedia integer, media_type TEXT)");
  330
+    m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_1 ON taglinks (idTag, media_type(20), idMedia)");
  331
+    m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_2 ON taglinks (idMedia, media_type(20), idTag)");
  332
+    m_pDS->exec("CREATE INDEX ix_taglinks_3 ON taglinks (media_type(20))");
  333
+
324 334
     // we create views last to ensure all indexes are rolled in
325 335
     CreateViews();
326 336
   }
@@ -1240,7 +1250,7 @@ int CVideoDatabase::AddToTable(const CStdString& table, const CStdString& firstF
1240 1250
     {
1241 1251
       m_pDS->close();
1242 1252
       // doesnt exists, add it
1243  
-      strSQL = PrepareSQL("insert into %s (%s, %s) values( NULL, '%s')", table.c_str(), firstField.c_str(), secondField.c_str(), value.c_str());
  1253
+      strSQL = PrepareSQL("insert into %s (%s, %s) values(NULL, '%s')", table.c_str(), firstField.c_str(), secondField.c_str(), value.c_str());      
1244 1254
       m_pDS->exec(strSQL.c_str());
1245 1255
       int id = (int)m_pDS->lastinsertid();
1246 1256
       return id;
@@ -1265,6 +1275,14 @@ int CVideoDatabase::AddSet(const CStdString& strSet)
1265 1275
   return AddToTable("sets", "idSet", "strSet", strSet);
1266 1276
 }
1267 1277
 
  1278
+int CVideoDatabase::AddTag(const std::string& tag)
  1279
+{
  1280
+  if (tag.empty())
  1281
+    return -1;
  1282
+
  1283
+  return AddToTable("tag", "idTag", "strTag", tag);
  1284
+}
  1285
+
1268 1286
 int CVideoDatabase::AddGenre(const CStdString& strGenre)
1269 1287
 {
1270 1288
   return AddToTable("genre", "idGenre", "strGenre", strGenre);
@@ -1346,19 +1364,24 @@ void CVideoDatabase::AddLinkToActor(const char *table, int actorID, const char *
1346 1364
   }
1347 1365
 }
1348 1366
 
1349  
-void CVideoDatabase::AddToLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID)
  1367
+void CVideoDatabase::AddToLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID, const char *typeField /* = NULL */, const char *type /* = NULL */)
1350 1368
 {
1351 1369
   try
1352 1370
   {
1353 1371
     if (NULL == m_pDB.get()) return ;
1354 1372
     if (NULL == m_pDS.get()) return ;
1355 1373
 
1356  
-    CStdString strSQL=PrepareSQL("select * from %s where %s=%i and %s=%i", table, firstField, firstID, secondField, secondID);
  1374
+    CStdString strSQL = PrepareSQL("select * from %s where %s=%i and %s=%i", table, firstField, firstID, secondField, secondID);
  1375
+    if (typeField != NULL && type != NULL)
  1376
+      strSQL += PrepareSQL(" and %s='%s'", typeField, type);
1357 1377
     m_pDS->query(strSQL.c_str());
1358 1378
     if (m_pDS->num_rows() == 0)
1359 1379
     {
1360 1380
       // doesnt exists, add it
1361  
-      strSQL=PrepareSQL("insert into %s (%s,%s) values(%i,%i)", table, firstField, secondField, firstID, secondID);
  1381
+      if (typeField == NULL || type == NULL)
  1382
+        strSQL = PrepareSQL("insert into %s (%s,%s) values(%i,%i)", table, firstField, secondField, firstID, secondID);
  1383
+      else
  1384
+        strSQL = PrepareSQL("insert into %s (%s,%s,%s) values(%i,%i,'%s')", table, firstField, secondField, typeField, firstID, secondID, type);
1362 1385
       m_pDS->exec(strSQL.c_str());
1363 1386
     }
1364 1387
     m_pDS->close();
@@ -1369,11 +1392,45 @@ void CVideoDatabase::AddToLinkTable(const char *table, const char *firstField, i
1369 1392
   }
1370 1393
 }
1371 1394
 
  1395
+void CVideoDatabase::RemoveFromLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID, const char *typeField /* = NULL */, const char *type /* = NULL */)
  1396
+{
  1397
+  try
  1398
+  {
  1399
+    if (NULL == m_pDB.get()) return ;
  1400
+    if (NULL == m_pDS.get()) return ;
  1401
+
  1402
+    CStdString strSQL = PrepareSQL("DELETE FROM %s WHERE %s = %i AND %s = %i", table, firstField, firstID, secondField, secondID);
  1403
+    if (typeField != NULL && type != NULL)
  1404
+      strSQL += PrepareSQL(" AND %s='%s'", typeField, type);
  1405
+    m_pDS->exec(strSQL.c_str());
  1406
+  }
  1407
+  catch (...)
  1408
+  {
  1409
+    CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  1410
+  }
  1411
+}
  1412
+
1372 1413
 //****Sets****
1373 1414
 void CVideoDatabase::AddSetToMovie(int idMovie, int idSet)
1374 1415
 {
1375 1416
   AddToLinkTable("setlinkmovie", "idSet", idSet, "idMovie", idMovie);
1376 1417
 }
  1418
+//****Tags****
  1419
+void CVideoDatabase::AddTagToItem(int idMovie, int idTag, const std::string &type)
  1420
+{
  1421
+  if (type.empty())
  1422
+    return;
  1423
+
  1424
+  AddToLinkTable("taglinks", "idTag", idTag, "idMedia", idMovie, "media_type", type.c_str());
  1425
+}
  1426
+
  1427
+void CVideoDatabase::RemoveTagFromItem(int idMovie, int idTag, const std::string &type)
  1428
+{
  1429
+  if (type.empty())
  1430
+    return;
  1431
+
  1432
+  RemoveFromLinkTable("taglinks", "idTag", idTag, "idMedia", idMovie, "media_type", type.c_str());
  1433
+}
1377 1434
 
1378 1435
 //****Actors****
1379 1436
 void CVideoDatabase::AddActorToMovie(int idMovie, int idActor, const CStdString& strRole, int order)
@@ -1898,6 +1955,13 @@ int CVideoDatabase::SetDetailsForMovie(const CStdString& strFilenameAndPath, con
1898 1955
       AddSetToMovie(idMovie, idSet);
1899 1956
     }
1900 1957
 
  1958
+    // add tags...
  1959
+    for (unsigned int i = 0; i < details.m_tags.size(); i++)
  1960
+    {
  1961
+      int idTag = AddTag(details.m_tags[i]);
  1962
+      AddTagToItem(idMovie, idTag, "movie");
  1963
+    }
  1964
+
1901 1965
     // add countries...
1902 1966
     for (unsigned int i = 0; i < details.m_country.size(); i++)
1903 1967
       AddCountryToMovie(idMovie, AddCountry(details.m_country[i]));
@@ -2838,6 +2902,31 @@ void CVideoDatabase::DeleteSet(int idSet)
2838 2902
   }
2839 2903
 }
2840 2904
 
  2905
+void CVideoDatabase::DeleteTag(int idTag, const std::string &mediaType)
  2906
+{
  2907
+  try
  2908
+  {
  2909
+    if (m_pDB.get() == NULL || m_pDS.get() == NULL)
  2910
+      return;
  2911
+
  2912
+    CStdString strSQL;
  2913
+    strSQL = PrepareSQL("DELETE FROM taglinks WHERE idTag = %i AND media_type = '%s'", idTag, mediaType.c_str());
  2914
+    m_pDS->exec(strSQL.c_str());
  2915
+
  2916
+    // check if the tag is used for another media type as well before deleting it completely
  2917
+    strSQL = PrepareSQL("SELECT 1 FROM taglinks WHERE idTag = %i", idTag);
  2918
+    if (RunQuery(strSQL) <= 0)
  2919
+    {
  2920
+      strSQL = PrepareSQL("DELETE FROM tag WHERE idTag = %i", idTag);
  2921
+      m_pDS->exec(strSQL.c_str());
  2922
+    }
  2923
+  }
  2924
+  catch (...)
  2925
+  {
  2926
+    CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idTag);
  2927
+  }
  2928
+}
  2929
+
2841 2930
 void CVideoDatabase::GetDetailsFromDB(auto_ptr<Dataset> &pDS, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
2842 2931
 {
2843 2932
   GetDetailsFromDB(pDS->get_sql_record(), min, max, offsets, details, idxOffset);
@@ -3058,6 +3147,15 @@ CVideoInfoTag CVideoDatabase::GetDetailsForMovie(const dbiplus::sql_record* cons
3058 3147
       m_pDS2->next();
3059 3148
     }
3060 3149
 
  3150
+    // get tags
  3151
+    strSQL = PrepareSQL("SELECT tag.strTag FROM tag, taglinks WHERE taglinks.idMedia = %i AND taglinks.media_type = 'movie' AND taglinks.idTag = tag.idTag ORDER BY tag.idTag", idMovie);
  3152
+    m_pDS2->query(strSQL.c_str());
  3153
+    while (!m_pDS2->eof())
  3154
+    {
  3155
+      details.m_tags.push_back(m_pDS2->fv("tag.strTag").get_asString());
  3156
+      m_pDS2->next();
  3157
+    }
  3158
+
3061 3159
     // create tvshowlink string
3062 3160
     vector<int> links;
3063 3161
     GetLinksToTvShow(idMovie,links);
@@ -3900,6 +3998,16 @@ bool CVideoDatabase::UpdateOldVersion(int iVersion)
3900 3998
     m_pDS->exec("CREATE INDEX ix_episode_show1 on episode(idEpisode,idShow)");
3901 3999
     m_pDS->exec("CREATE INDEX ix_episode_show2 on episode(idShow,idEpisode)");
3902 4000
   }
  4001
+  if (iVersion < 67)
  4002
+  {
  4003
+    m_pDS->exec("CREATE TABLE tag (idTag integer primary key, strTag text)");
  4004
+    m_pDS->exec("CREATE UNIQUE INDEX ix_tag_1 ON tag (strTag(256))");
  4005
+
  4006
+    m_pDS->exec("CREATE TABLE taglinks (idTag integer, idMedia integer, media_type TEXT)");
  4007
+    m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_1 ON taglinks (idTag, media_type(20), idMedia)");
  4008
+    m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_2 ON taglinks (idMedia, media_type(20), idTag)");
  4009
+    m_pDS->exec("CREATE INDEX ix_taglinks_3 ON taglinks (media_type(20))");
  4010
+  }
3903 4011
   // always recreate the view after any table change
3904 4012
   CreateViews();
3905 4013
   return true;
@@ -4351,6 +4459,54 @@ bool CVideoDatabase::GetNavCommon(const CStdString& strBaseDir, CFileItemList& i
4351 4459
   return false;
4352 4460
 }
4353 4461
 
  4462
+bool CVideoDatabase::GetTagsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent)
  4463
+{
  4464
+  CStdString mediaType;
  4465
+  if (idContent == VIDEODB_CONTENT_MOVIES)
  4466
+    mediaType = "movie";
  4467
+  else
  4468
+    return false;
  4469
+
  4470
+  try
  4471
+  {
  4472
+    if (NULL == m_pDB.get()) return false;
  4473
+    if (NULL == m_pDS.get()) return false;
  4474
+
  4475
+    int iRowsFound = RunQuery(PrepareSQL("SELECT tag.idTag, tag.strTag FROM taglinks JOIN tag ON tag.idTag = taglinks.idTag WHERE taglinks.media_type = '%s' GROUP BY taglinks.idTag", mediaType.c_str()));
  4476
+    if (iRowsFound <= 0)
  4477
+      return iRowsFound == 0;
  4478
+
  4479
+    while (!m_pDS->eof())
  4480
+    {
  4481
+      int idTag = m_pDS->fv(0).get_asInt();
  4482
+
  4483
+      CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
  4484
+      pItem->m_bIsFolder = true;
  4485
+      pItem->GetVideoInfoTag()->m_iDbId = idTag;
  4486
+      pItem->GetVideoInfoTag()->m_type = "tag";
  4487
+
  4488
+      CStdString strDir; strDir.Format("%ld/", idTag);
  4489
+      pItem->SetPath(strBaseDir + strDir);
  4490
+
  4491
+      if (!items.Contains(pItem->GetPath()))
  4492
+      {
  4493
+        pItem->SetLabelPreformated(true);
  4494
+        items.Add(pItem);
  4495
+      }
  4496
+
  4497
+      m_pDS->next();
  4498
+    }
  4499
+    m_pDS->close();
  4500
+
  4501
+    return true;
  4502
+  }
  4503
+  catch (...)
  4504
+  {
  4505
+    CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
  4506
+  }
  4507
+  return false;
  4508
+}
  4509
+
4354 4510
 bool CVideoDatabase::GetSetsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent)
4355 4511
 {
4356 4512
   if (idContent != VIDEODB_CONTENT_MOVIES)
@@ -5149,7 +5305,9 @@ bool CVideoDatabase::GetSortedVideos(MediaType mediaType, const CStdString& strB
5149 5305
   return success;
5150 5306
 }
5151 5307
 
5152  
-bool CVideoDatabase::GetMoviesNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre, int idYear, int idActor, int idDirector, int idStudio, int idCountry, int idSet)
  5308
+bool CVideoDatabase::GetMoviesNav(const CStdString& strBaseDir, CFileItemList& items,
  5309
+                                  int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */,
  5310
+                                  int idStudio /* = -1 */, int idCountry /* = -1 */, int idSet /* = -1 */, int idTag /* = -1 */)
5153 5311
 {
5154 5312
   Filter filter;
5155 5313
   if (idGenre != -1)
@@ -5184,6 +5342,11 @@ bool CVideoDatabase::GetMoviesNav(const CStdString& strBaseDir, CFileItemList& i
5184 5342
     filter.join  = PrepareSQL("join setlinkmovie on setlinkmovie.idMovie=movieview.idMovie");
5185 5343
     filter.where = PrepareSQL("setlinkmovie.idSet=%u",idSet);
5186 5344
   }
  5345
+  else if (idTag != -1)
  5346
+  {
  5347
+    filter.join  = PrepareSQL("join taglinks on taglinks.idMedia = movieview.idMovie AND taglinks.media_type = 'movie'");
  5348
+    filter.where = PrepareSQL("taglinks.idTag = %u", idTag);
  5349
+  }
5187 5350
 
5188 5351
   return GetMoviesByWhere(strBaseDir, filter, items, idSet == -1);
5189 5352
 }
@@ -5842,6 +6005,11 @@ CStdString CVideoDatabase::GetSetById(int id)
5842 6005
   return GetSingleValue("sets", "strSet", PrepareSQL("idSet=%i", id));
5843 6006
 }
5844 6007
 
  6008
+CStdString CVideoDatabase::GetTagById(int id)
  6009
+{
  6010
+  return GetSingleValue("tag", "strTag", PrepareSQL("idTag = %i", id));
  6011
+}
  6012
+
5845 6013
 CStdString CVideoDatabase::GetPersonById(int id)
5846 6014
 {
5847 6015
   return GetSingleValue("actors", "strActor", PrepareSQL("idActor=%i", id));
14  xbmc/video/VideoDatabase.h
@@ -419,6 +419,7 @@ class CVideoDatabase : public CDatabase
419 419
   CStdString GetGenreById(int id);
420 420
   CStdString GetCountryById(int id);
421 421
   CStdString GetSetById(int id);
  422
+  CStdString GetTagById(int id);
422 423
   CStdString GetPersonById(int id);
423 424
   CStdString GetStudioById(int id);
424 425
   CStdString GetTvShowTitleById(int id);
@@ -459,6 +460,7 @@ class CVideoDatabase : public CDatabase
459 460
   void RemoveContentForPath(const CStdString& strPath,CGUIDialogProgress *progress = NULL);
460 461
   void UpdateFanart(const CFileItem &item, VIDEODB_CONTENT_TYPE type);
461 462
   void DeleteSet(int idSet);
  463
+  void DeleteTag(int idTag, const std::string &mediaType);
462 464
 
463 465
   // per-file video settings
464 466
   bool GetVideoSettings(const CStdString &strFilenameAndPath, CVideoSettings &settings);
@@ -584,9 +586,10 @@ class CVideoDatabase : public CDatabase
584 586
   bool GetDirectorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1);
585 587
   bool GetWritersNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1);
586 588
   bool GetSetsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1);
  589
+  bool GetTagsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1);
587 590
   bool GetMusicVideoAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idArtist);
588 591
 
589  
-  bool GetMoviesNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre=-1, int idYear=-1, int idActor=-1, int idDirector=-1, int idStudio=-1, int idCountry=-1, int idSet=-1);
  592
+  bool GetMoviesNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre=-1, int idYear=-1, int idActor=-1, int idDirector=-1, int idStudio=-1, int idCountry=-1, int idSet=-1, int idTag=-1);
590 593
   bool GetTvShowsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre=-1, int idYear=-1, int idActor=-1, int idDirector=-1, int idStudio=-1);
591 594
   bool GetSeasonsNav(const CStdString& strBaseDir, CFileItemList& items, int idActor=-1, int idDirector=-1, int idGenre=-1, int idYear=-1, int idShow=-1);
592 595
   bool GetEpisodesNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre=-1, int idYear=-1, int idActor=-1, int idDirector=-1, int idShow=-1, int idSeason=-1, const SortDescription &sortDescription = SortDescription());
@@ -680,6 +683,10 @@ class CVideoDatabase : public CDatabase
680 683
   std::string GetArtForItem(int mediaId, const std::string &mediaType, const std::string &artType);
681 684
   bool GetTvShowSeasonArt(int mediaId, std::map<int, std::string> &seasonArt);
682 685
 
  686
+  int AddTag(const std::string &tag);
  687
+  void AddTagToItem(int idItem, int idTag, const std::string &type);
  688
+  void RemoveTagFromItem(int idItem, int idTag, const std::string &type);
  689
+
683 690
 protected:
684 691
   int GetMovieId(const CStdString& strFilenameAndPath);
685 692
   int GetMusicVideoId(const CStdString& strFilenameAndPath);
@@ -710,7 +717,8 @@ class CVideoDatabase : public CDatabase
710 717
 
711 718
   // link functions - these two do all the work
712 719
   void AddLinkToActor(const char *table, int actorID, const char *secondField, int secondID, const CStdString &role, int order);
713  
-  void AddToLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID);
  720
+  void AddToLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID, const char *typeField = NULL, const char *type = NULL);
  721
+  void RemoveFromLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID, const char *typeField = NULL, const char *type = NULL);
714 722
 
715 723
   void AddSetToMovie(int idMovie, int idSet);
716 724
 
@@ -799,7 +807,7 @@ class CVideoDatabase : public CDatabase
799 807
    */
800 808
   bool LookupByFolders(const CStdString &path, bool shows = false);
801 809
 
802  
-  virtual int GetMinVersion() const { return 66; };
  810
+  virtual int GetMinVersion() const { return 67; };
803 811
   virtual int GetExportVersion() const { return 1; };
804 812
   const char *GetBaseDBName() const { return "MyVideos"; };
805 813
 
6  xbmc/video/VideoInfoTag.cpp
@@ -53,6 +53,7 @@ void CVideoInfoTag::Reset()
53 53
   m_cast.clear();
54 54
   m_set.clear();
55 55
   m_setId.clear();
  56
+  m_tags.clear();
56 57
   m_strFile.clear();
57 58
   m_strPath.clear();
58 59
   m_strIMDBNumber.clear();
@@ -174,6 +175,7 @@ bool CVideoInfoTag::Save(TiXmlNode *node, const CStdString &tag, bool savePathIn
174 175
   XMLUtils::SetStringArray(movie, "genre", m_genre);
175 176
   XMLUtils::SetStringArray(movie, "country", m_country);
176 177
   XMLUtils::SetStringArray(movie, "set", m_set);
  178
+  XMLUtils::SetStringArray(movie, "tag", m_tags);
177 179
   XMLUtils::SetStringArray(movie, "credits", m_writingCredits);
178 180
   XMLUtils::SetStringArray(movie, "director", m_director);
179 181
   XMLUtils::SetDate(movie, "premiered", m_premiered);
@@ -291,6 +293,7 @@ void CVideoInfoTag::Archive(CArchive& ar)
291 293
 
292 294
     ar << m_set;
293 295
     ar << m_setId;
  296
+    ar << m_tags;
294 297
     ar << m_strRuntime;
295 298
     ar << m_strFile;
296 299
     ar << m_strPath;
@@ -367,6 +370,7 @@ void CVideoInfoTag::Archive(CArchive& ar)
367 370
 
368 371
     ar >> m_set;
369 372
     ar >> m_setId;
  373
+    ar >> m_tags;
370 374
     ar >> m_strRuntime;
371 375
     ar >> m_strFile;
372 376
     ar >> m_strPath;
@@ -443,6 +447,7 @@ void CVideoInfoTag::Serialize(CVariant& value)
443 447
   value["setid"] = CVariant(CVariant::VariantTypeArray);
444 448
   for (unsigned int i = 0; i < m_setId.size(); i++)
445 449
     value["setid"].push_back(m_setId[i]);
  450
+  value["tags"] = m_tags;
446 451
   value["runtime"] = m_strRuntime;
447 452
   value["file"] = m_strFile;
448 453
   value["path"] = m_strPath;
@@ -656,6 +661,7 @@ void CVideoInfoTag::ParseNative(const TiXmlElement* movie, bool prioritise)
656 661
   }
657 662
 
658 663
   XMLUtils::GetStringArray(movie, "set", m_set, prioritise, g_advancedSettings.m_videoItemSeparator);
  664
+  XMLUtils::GetStringArray(movie, "tag", m_tags, prioritise, g_advancedSettings.m_videoItemSeparator);
659 665
   XMLUtils::GetStringArray(movie, "studio", m_studio, prioritise, g_advancedSettings.m_videoItemSeparator);
660 666
   // artists
661 667
   node = movie->FirstChildElement("artist");
1  xbmc/video/VideoInfoTag.h
@@ -97,6 +97,7 @@ class CVideoInfoTag : public IArchivable, public ISerializable, public ISortable
97 97
   typedef std::vector< SActorInfo >::const_iterator iCast;
98 98
   std::vector<std::string> m_set;
99 99
   std::vector<int> m_setId;
  100
+  std::vector<std::string> m_tags;
100 101
   CStdString m_strRuntime;
101 102
   CStdString m_strFile;
102 103
   CStdString m_strPath;
5  xbmc/video/windows/GUIWindowVideoBase.cpp
@@ -875,7 +875,8 @@ bool CGUIWindowVideoBase::OnSelect(int iItem)
875 875
   CFileItemPtr item = m_vecItems->Get(iItem);
876 876
 
877 877
   CStdString path = item->GetPath();
878  
-  if (!item->m_bIsFolder && path != "add" && path != "addons://more/video" && path.Left(19) != "newsmartplaylist://" && path.Left(14) != "newplaylist://")
  878
+  if (!item->m_bIsFolder && path != "add" && path != "addons://more/video" &&
  879
+      path.Left(19) != "newsmartplaylist://" && path.Left(14) != "newplaylist://" && path.Left(9) != "newtag://")
879 880
     return OnFileAction(iItem, g_guiSettings.GetInt("myvideos.selectaction"));
880 881
 
881 882
   return CGUIMediaWindow::OnSelect(iItem);
@@ -1187,7 +1188,7 @@ void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &but
1187 1188
             buttons.Add(CONTEXT_BUTTON_PLAY_PART, 20324);
1188 1189
         }
1189 1190
 
1190  
-        if (!m_vecItems->GetPath().IsEmpty() && !item->GetPath().Left(19).Equals("newsmartplaylist://")
  1191
+        if (!m_vecItems->GetPath().IsEmpty() && !item->GetPath().Left(19).Equals("newsmartplaylist://") && !item->GetPath().Left(9).Equals("newtag://")
1191 1192
             && !m_vecItems->IsSourcesPath())
1192 1193
         {
1193 1194
           buttons.Add(CONTEXT_BUTTON_QUEUE_ITEM, 13347);      // Add to Playlist
236  xbmc/video/windows/GUIWindowVideoNav.cpp
@@ -51,6 +51,7 @@
51 51
 #include "utils/URIUtils.h"
52 52
 #include "utils/StringUtils.h"
53 53
 #include "TextureCache.h"