Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

add a tagging system for movies #1108

Merged
merged 5 commits into from

6 participants

Sascha Montellese jmarshallnz Karlson2k Deano316 vicbitter Michal Piechowiak
Sascha Montellese
Owner

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
Owner

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
Owner

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
Owner

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
Owner

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

Sascha Montellese Montellese was assigned
jmarshallnz
Owner

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

Sascha Montellese Montellese merged commit f128546 into from
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
Owner

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
Owner

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");?

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

Michal Piechowiak

why is this visible condition needed?

copy n paste ftw?

Hehe yes. Thanks for pointing it out.

Tobias Hieta tru referenced this pull request from a commit in RasPlex/plex-home-theatre
Tobias Hieta tru 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
Tobias Hieta tru 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
Tobias Hieta tru 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 with 636 additions and 22 deletions.
  1. +6 −0 XBMC-ATV2.xcodeproj/project.pbxproj
  2. +6 −0 XBMC-IOS.xcodeproj/project.pbxproj
  3. +6 −0 XBMC.xcodeproj/project.pbxproj
  4. +25 −1 language/English/strings.po
  5. +2 −0  project/VS2010Express/XBMC.vcxproj
  6. +6 −0 project/VS2010Express/XBMC.vcxproj.filters
  7. +5 −0 system/library/video/movies/tags.xml
  8. +2 −0  xbmc/dialogs/GUIDialogContextMenu.h
  9. +8 −0 xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
  10. +8 −0 xbmc/filesystem/VideoDatabaseDirectory.cpp
  11. +3 −0  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.cpp
  12. +2 −1  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.h
  13. +1 −0  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.cpp
  14. +60 −0 xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTags.cpp
  15. +41 −0 xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTags.h
  16. +1 −1  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.cpp
  17. +1 −0  xbmc/filesystem/VideoDatabaseDirectory/Makefile
  18. +4 −0 xbmc/filesystem/VideoDatabaseDirectory/QueryParams.cpp
  19. +2 −0  xbmc/filesystem/VideoDatabaseDirectory/QueryParams.h
  20. +5 −1 xbmc/playlists/SmartPlayList.cpp
  21. +2 −1  xbmc/utils/DatabaseUtils.h
  22. +12 −0 xbmc/video/GUIViewStateVideo.cpp
  23. +173 −5 xbmc/video/VideoDatabase.cpp
  24. +11 −3 xbmc/video/VideoDatabase.h
  25. +6 −0 xbmc/video/VideoInfoTag.cpp
  26. +1 −0  xbmc/video/VideoInfoTag.h
  27. +3 −2 xbmc/video/windows/GUIWindowVideoBase.cpp
  28. +230 −6 xbmc/video/windows/GUIWindowVideoNav.cpp
  29. +3 −0  xbmc/video/windows/GUIWindowVideoNav.h
  30. +1 −1  xbmc/windows/GUIMediaWindow.cpp
6 XBMC-ATV2.xcodeproj/project.pbxproj
View
@@ -21,6 +21,7 @@
32D6D47C1423A9D8003641AC /* JpegIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32D6D47A1423A9D8003641AC /* JpegIO.cpp */; };
36A9445915821F8300727135 /* DatabaseUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9445715821F8300727135 /* DatabaseUtils.cpp */; };
36A9445D15821FAC00727135 /* SortUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9445B15821FAB00727135 /* SortUtils.cpp */; };
+ 36A9465315AA269B00727135 /* DirectoryNodeTags.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9465115AA269B00727135 /* DirectoryNodeTags.cpp */; };
4D5D2E131301753F006ABC13 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D5D2E121301753F006ABC13 /* CFNetwork.framework */; };
7C0A7ECD13A5DBF900AFC2BD /* AppParamParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7ECB13A5DBF900AFC2BD /* AppParamParser.cpp */; };
7C0A7FC813A9E75400AFC2BD /* DirtyRegionSolvers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7FC413A9E75400AFC2BD /* DirtyRegionSolvers.cpp */; };
@@ -1010,6 +1011,8 @@
36A9445A15821F9100727135 /* ISortable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ISortable.h; sourceTree = "<group>"; };
36A9445B15821FAB00727135 /* SortUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SortUtils.cpp; sourceTree = "<group>"; };
36A9445C15821FAB00727135 /* SortUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SortUtils.h; sourceTree = "<group>"; };
+ 36A9465115AA269B00727135 /* DirectoryNodeTags.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DirectoryNodeTags.cpp; sourceTree = "<group>"; };
+ 36A9465215AA269B00727135 /* DirectoryNodeTags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryNodeTags.h; sourceTree = "<group>"; };
4D5D2E121301753F006ABC13 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
7C0A7ECB13A5DBF900AFC2BD /* AppParamParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AppParamParser.cpp; sourceTree = "<group>"; };
7C0A7ECC13A5DBF900AFC2BD /* AppParamParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppParamParser.h; sourceTree = "<group>"; };
@@ -4525,6 +4528,8 @@
F56C7481131EC152000AD0F6 /* DirectoryNodeSets.h */,
F56C7482131EC152000AD0F6 /* DirectoryNodeStudio.cpp */,
F56C7483131EC152000AD0F6 /* DirectoryNodeStudio.h */,
+ 36A9465115AA269B00727135 /* DirectoryNodeTags.cpp */,
+ 36A9465215AA269B00727135 /* DirectoryNodeTags.h */,
F56C7484131EC152000AD0F6 /* DirectoryNodeTitleMovies.cpp */,
F56C7485131EC152000AD0F6 /* DirectoryNodeTitleMovies.h */,
F56C7486131EC152000AD0F6 /* DirectoryNodeTitleMusicVideos.cpp */,
@@ -7219,6 +7224,7 @@
36A9445915821F8300727135 /* DatabaseUtils.cpp in Sources */,
36A9445D15821FAC00727135 /* SortUtils.cpp in Sources */,
DF08E84515829BA600058C77 /* Exception.cpp in Sources */,
+ 36A9465315AA269B00727135 /* DirectoryNodeTags.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
6 XBMC-IOS.xcodeproj/project.pbxproj
View
@@ -22,6 +22,7 @@
3291892B1423A9B700E878CD /* JpegIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 329189291423A9B700E878CD /* JpegIO.cpp */; };
36A9444E15821F2C00727135 /* DatabaseUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9444C15821F2C00727135 /* DatabaseUtils.cpp */; };
36A9445215821F5300727135 /* SortUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9445015821F5300727135 /* SortUtils.cpp */; };
+ 36A9465B15AA26BC00727135 /* DirectoryNodeTags.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9465915AA26BC00727135 /* DirectoryNodeTags.cpp */; };
4D5D2E1E1301758F006ABC13 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D5D2E1D1301758F006ABC13 /* CFNetwork.framework */; };
7C0A7EDE13A5DC2800AFC2BD /* AppParamParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7EDC13A5DC2800AFC2BD /* AppParamParser.cpp */; };
7C0A7F9D13A9E70800AFC2BD /* GUIWindowDebugInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C0A7F9B13A9E70800AFC2BD /* GUIWindowDebugInfo.cpp */; };
@@ -1010,6 +1011,8 @@
36A9444F15821F3B00727135 /* ISortable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ISortable.h; sourceTree = "<group>"; };
36A9445015821F5300727135 /* SortUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SortUtils.cpp; sourceTree = "<group>"; };
36A9445115821F5300727135 /* SortUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SortUtils.h; sourceTree = "<group>"; };
+ 36A9465915AA26BC00727135 /* DirectoryNodeTags.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DirectoryNodeTags.cpp; sourceTree = "<group>"; };
+ 36A9465A15AA26BC00727135 /* DirectoryNodeTags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryNodeTags.h; sourceTree = "<group>"; };
4D5D2E1D1301758F006ABC13 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
7C0A7EDC13A5DC2800AFC2BD /* AppParamParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AppParamParser.cpp; sourceTree = "<group>"; };
7C0A7EDD13A5DC2800AFC2BD /* AppParamParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppParamParser.h; sourceTree = "<group>"; };
@@ -4881,6 +4884,8 @@
F56C8464131F42E8000AD0F6 /* DirectoryNodeSets.h */,
F56C8465131F42E8000AD0F6 /* DirectoryNodeStudio.cpp */,
F56C8466131F42E8000AD0F6 /* DirectoryNodeStudio.h */,
+ 36A9465915AA26BC00727135 /* DirectoryNodeTags.cpp */,
+ 36A9465A15AA26BC00727135 /* DirectoryNodeTags.h */,
F56C8467131F42E8000AD0F6 /* DirectoryNodeTitleMovies.cpp */,
F56C8468131F42E8000AD0F6 /* DirectoryNodeTitleMovies.h */,
F56C8469131F42E8000AD0F6 /* DirectoryNodeTitleMusicVideos.cpp */,
@@ -7230,6 +7235,7 @@
36A9444E15821F2C00727135 /* DatabaseUtils.cpp in Sources */,
36A9445215821F5300727135 /* SortUtils.cpp in Sources */,
DFC3867E158296EC008AE277 /* Exception.cpp in Sources */,
+ 36A9465B15AA26BC00727135 /* DirectoryNodeTags.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
6 XBMC.xcodeproj/project.pbxproj
View
@@ -194,6 +194,7 @@
32C631281423A90F00F18420 /* JpegIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 32C631261423A90F00F18420 /* JpegIO.cpp */; };
36A9443D15821E2800727135 /* DatabaseUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9443B15821E2800727135 /* DatabaseUtils.cpp */; };
36A9444115821E7C00727135 /* SortUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9443F15821E7C00727135 /* SortUtils.cpp */; };
+ 36A9464C15AA25FD00727135 /* DirectoryNodeTags.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36A9464A15AA25FD00727135 /* DirectoryNodeTags.cpp */; };
3802709A13D5A653009493DD /* SystemClock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3802709813D5A653009493DD /* SystemClock.cpp */; };
384718D81325BA04000486D6 /* XBDateTime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 384718D61325BA04000486D6 /* XBDateTime.cpp */; };
38F4E57013CCCB3B00664821 /* Implementation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 38F4E56C13CCCB3B00664821 /* Implementation.cpp */; };
@@ -1376,6 +1377,8 @@
36A9443E15821E5400727135 /* ISortable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ISortable.h; sourceTree = "<group>"; };
36A9443F15821E7C00727135 /* SortUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SortUtils.cpp; sourceTree = "<group>"; };
36A9444015821E7C00727135 /* SortUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SortUtils.h; sourceTree = "<group>"; };
+ 36A9464A15AA25FD00727135 /* DirectoryNodeTags.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DirectoryNodeTags.cpp; sourceTree = "<group>"; };
+ 36A9464B15AA25FD00727135 /* DirectoryNodeTags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryNodeTags.h; sourceTree = "<group>"; };
3802709713D5A62D009493DD /* ThreadLocal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadLocal.h; sourceTree = "<group>"; };
3802709813D5A653009493DD /* SystemClock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SystemClock.cpp; sourceTree = "<group>"; };
3802709913D5A653009493DD /* SystemClock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SystemClock.h; sourceTree = "<group>"; };
@@ -5145,6 +5148,8 @@
7CCF7E711067643800992676 /* DirectoryNodeSets.h */,
E38E177A0D25F9FA00618676 /* DirectoryNodeStudio.cpp */,
E38E177B0D25F9FA00618676 /* DirectoryNodeStudio.h */,
+ 36A9464A15AA25FD00727135 /* DirectoryNodeTags.cpp */,
+ 36A9464B15AA25FD00727135 /* DirectoryNodeTags.h */,
E38E177C0D25F9FA00618676 /* DirectoryNodeTitleMovies.cpp */,
E38E177D0D25F9FA00618676 /* DirectoryNodeTitleMovies.h */,
E38E177E0D25F9FA00618676 /* DirectoryNodeTitleMusicVideos.cpp */,
@@ -7311,6 +7316,7 @@
36A9443D15821E2800727135 /* DatabaseUtils.cpp in Sources */,
36A9444115821E7C00727135 /* SortUtils.cpp in Sources */,
1DE0443515828F4B005DDB4D /* Exception.cpp in Sources */,
+ 36A9464C15AA25FD00727135 /* DirectoryNodeTags.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
26 language/English/strings.po
View
@@ -7427,8 +7427,32 @@ msgctxt "#20458"
msgid "Group movies in sets"
msgstr ""
+msgctxt "#20459"
+msgid "Tags"
+msgstr ""
+
+msgctxt "#20460"
+msgid "Add %s"
+msgstr ""
+
+msgctxt "#20461"
+msgid "Remove %s"
+msgstr ""
+
+msgctxt "#20462"
+msgid "New tag..."
+msgstr ""
+
+msgctxt "#20463"
+msgid "A tag with the name '%s' already exists."
+msgstr ""
+
+msgctxt "#20464"
+msgid "Select %s"
+msgstr ""
+
#up to 21329 is reserved for the video db !! !
-#empty strings from id 20459 to 21329
+#empty strings from id 20465 to 21329
msgctxt "#21330"
msgid "Show hidden files and directories"
2  project/VS2010Express/XBMC.vcxproj
View
@@ -449,6 +449,7 @@
<ClCompile Include="..\..\xbmc\filesystem\UPnPFile.cpp" />
<ClCompile Include="..\..\xbmc\filesystem\VideoDatabaseDirectory.cpp" />
<ClCompile Include="..\..\xbmc\FileSystem\VideoDatabaseDirectory\DirectoryNodeCountry.cpp" />
+ <ClCompile Include="..\..\xbmc\filesystem\VideoDatabaseDirectory\DirectoryNodeTags.cpp" />
<ClCompile Include="..\..\xbmc\filesystem\windows\WINFileSMB.cpp" />
<ClCompile Include="..\..\xbmc\filesystem\windows\WINSMBDirectory.cpp" />
<ClCompile Include="..\..\xbmc\filesystem\VirtualDirectory.cpp" />
@@ -926,6 +927,7 @@
<ClInclude Include="..\..\xbmc\cores\dvdplayer\DVDCodecs\Audio\DVDAudioCodecPassthrough.h" />
<ClInclude Include="..\..\xbmc\cores\paplayer\PCMCodec.h" />
<ClInclude Include="..\..\xbmc\filesystem\ImageFile.h" />
+ <ClInclude Include="..\..\xbmc\filesystem\VideoDatabaseDirectory\DirectoryNodeTags.h" />
<ClInclude Include="..\..\xbmc\filesystem\windows\WINFileSMB.h" />
<ClInclude Include="..\..\xbmc\filesystem\windows\WINSMBDirectory.h" />
<ClInclude Include="..\..\xbmc\input\windows\WINJoystick.h" />
6 project/VS2010Express/XBMC.vcxproj.filters
View
@@ -2586,6 +2586,9 @@
<ClCompile Include="..\..\xbmc\utils\DatabaseUtils.cpp">
<Filter>utils</Filter>
</ClCompile>
+ <ClCompile Include="..\..\xbmc\filesystem\VideoDatabaseDirectory\DirectoryNodeTags.cpp">
+ <Filter>filesystem\VideoDatabaseDirectory</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\xbmc\win32\pch.h">
@@ -5216,6 +5219,9 @@
<ClInclude Include="..\..\xbmc\utils\ISortable.h">
<Filter>utils</Filter>
</ClInclude>
+ <ClInclude Include="..\..\xbmc\filesystem\VideoDatabaseDirectory\DirectoryNodeTags.h">
+ <Filter>filesystem\VideoDatabaseDirectory</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\..\xbmc\win32\XBMC_PC.rc">
5 system/library/video/movies/tags.xml
View
@@ -0,0 +1,5 @@
+<node order="7" type="folder" visible="Library.HasContent(MovieSets)">
+ <label>20459</label>
+ <path>videodb://1/9</path>
+ <icon>DefaultTags.png</icon>
+</node>
2  xbmc/dialogs/GUIDialogContextMenu.h
View
@@ -104,6 +104,8 @@ enum CONTEXT_BUTTON { CONTEXT_BUTTON_CANCELLED = 0,
CONTEXT_BUTTON_SET_MOVIESET_FANART,
CONTEXT_BUTTON_DELETE_PLUGIN,
CONTEXT_BUTTON_PLAY_AND_QUEUE,
+ CONTEXT_BUTTON_TAGS_ADD_ITEMS,
+ CONTEXT_BUTTON_TAGS_REMOVE_ITEMS,
CONTEXT_BUTTON_USER1,
CONTEXT_BUTTON_USER2,
CONTEXT_BUTTON_USER3,
8 xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp
View
@@ -240,6 +240,14 @@ void CGUIDialogSmartPlaylistRule::OnBrowse()
videodatabase.GetSetsNav("videodb://1/7/", items, VIDEODB_CONTENT_MOVIES);
iLabel = 20434;
}
+ else if (m_rule.m_field == FieldTag)
+ {
+ if (m_type == "movies")
+ videodatabase.GetTagsNav("videodb://1/9/", items, VIDEODB_CONTENT_MOVIES);
+ else
+ return;
+ iLabel = 20459;
+ }
else
{ // TODO: Add browseability in here.
assert(false);
8 xbmc/filesystem/VideoDatabaseDirectory.cpp
View
@@ -160,6 +160,10 @@ bool CVideoDatabaseDirectory::GetLabel(const CStdString& strDirectory, CStdStrin
if (params.GetSetId() != -1)
strLabel += videodatabase.GetSetById(params.GetSetId());
+ // get tag
+ if (params.GetTagId() != -1)
+ strLabel += videodatabase.GetTagById(params.GetTagId());
+
// get year
if (params.GetYear() != -1)
{
@@ -190,6 +194,8 @@ bool CVideoDatabaseDirectory::GetLabel(const CStdString& strDirectory, CStdStrin
strLabel = g_localizeStrings.Get(20348); break;
case NODE_TYPE_SETS: // Sets
strLabel = g_localizeStrings.Get(20434); break;
+ case NODE_TYPE_TAGS: // Tags
+ strLabel = g_localizeStrings.Get(20459); break;
case NODE_TYPE_MOVIES_OVERVIEW: // Movies
strLabel = g_localizeStrings.Get(342); break;
case NODE_TYPE_TVSHOWS_OVERVIEW: // TV Shows
@@ -253,6 +259,8 @@ CStdString CVideoDatabaseDirectory::GetIcon(const CStdString &strDirectory)
return "DefaultCountry.png";
case NODE_TYPE_SETS: // Sets
return "DefaultSets.png";
+ case NODE_TYPE_TAGS: // Tags
+ return "DefaultTags.png";
case NODE_TYPE_YEAR: // Year
return "DefaultYear.png";
case NODE_TYPE_DIRECTOR: // Director
3  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.cpp
View
@@ -43,6 +43,7 @@
#include "DirectoryNodeRecentlyAddedMusicVideos.h"
#include "DirectoryNodeTitleMusicVideos.h"
#include "DirectoryNodeMusicVideoAlbum.h"
+#include "DirectoryNodeTags.h"
#include "video/VideoInfoTag.h"
#include "URL.h"
#include "settings/AdvancedSettings.h"
@@ -122,6 +123,8 @@ CDirectoryNode* CDirectoryNode::CreateNode(NODE_TYPE Type, const CStdString& str
return new CDirectoryNodeCountry(strName, pParent);
case NODE_TYPE_SETS:
return new CDirectoryNodeSets(strName, pParent);
+ case NODE_TYPE_TAGS:
+ return new CDirectoryNodeTags(strName, pParent);
case NODE_TYPE_YEAR:
return new CDirectoryNodeYear(strName, pParent);
case NODE_TYPE_ACTOR:
3  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNode.h
View
@@ -54,7 +54,8 @@ namespace XFILE
NODE_TYPE_TITLE_MUSICVIDEOS,
NODE_TYPE_MUSICVIDEOS_ALBUM,
NODE_TYPE_SETS,
- NODE_TYPE_COUNTRY
+ NODE_TYPE_COUNTRY,
+ NODE_TYPE_TAGS
} NODE_TYPE;
typedef struct {
1  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeMoviesOverview.cpp
View
@@ -36,6 +36,7 @@ Node MovieChildren[] = {
{ NODE_TYPE_STUDIO, 6, 20388 },
{ NODE_TYPE_SETS, 7, 20434 },
{ NODE_TYPE_COUNTRY, 8, 20451 },
+ { NODE_TYPE_TAGS, 9, 20459 }
};
CDirectoryNodeMoviesOverview::CDirectoryNodeMoviesOverview(const CStdString& strName, CDirectoryNode* pParent)
60 xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTags.cpp
View
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 Team XBMC
+ * http://www.xbmc.org
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include "DirectoryNodeTags.h"
+#include "QueryParams.h"
+#include "video/VideoDatabase.h"
+
+using namespace XFILE::VIDEODATABASEDIRECTORY;
+
+CDirectoryNodeTags::CDirectoryNodeTags(const CStdString& strName, CDirectoryNode* pParent)
+ : CDirectoryNode(NODE_TYPE_TAGS, strName, pParent)
+{
+
+}
+
+NODE_TYPE CDirectoryNodeTags::GetChildType() const
+{
+ return NODE_TYPE_TITLE_MOVIES;
+}
+
+CStdString CDirectoryNodeTags::GetLocalizedName() const
+{
+ CVideoDatabase db;
+ if (db.Open())
+ return db.GetTagById(GetID());
+ return "";
+}
+
+bool CDirectoryNodeTags::GetContent(CFileItemList& items) const
+{
+ CVideoDatabase videodatabase;
+ if (!videodatabase.Open())
+ return false;
+
+ CQueryParams params;
+ CollectQueryParams(params);
+
+ bool bSuccess = videodatabase.GetTagsNav(BuildPath(), items, params.GetContentType());
+ videodatabase.Close();
+
+ return bSuccess;
+}
41 xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTags.h
View
@@ -0,0 +1,41 @@
+#pragma once
+/*
+ * Copyright (C) 2012 Team XBMC
+ * http://www.xbmc.org
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include "DirectoryNode.h"
+
+namespace XFILE
+{
+ namespace VIDEODATABASEDIRECTORY
+ {
+ class CDirectoryNodeTags : public CDirectoryNode
+ {
+ public:
+ CDirectoryNodeTags(const CStdString& strName, CDirectoryNode* pParent);
+ protected:
+ virtual NODE_TYPE GetChildType() const;
+ virtual bool GetContent(CFileItemList& items) const;
+ virtual CStdString GetLocalizedName() const;
+ };
+ }
+}
+
+
2  xbmc/filesystem/VideoDatabaseDirectory/DirectoryNodeTitleMovies.cpp
View
@@ -41,7 +41,7 @@ bool CDirectoryNodeTitleMovies::GetContent(CFileItemList& items) const
CollectQueryParams(params);
CStdString strBaseDir=BuildPath();
- bool bSuccess=videodatabase.GetMoviesNav(strBaseDir, items, params.GetGenreId(), params.GetYear(), params.GetActorId(), params.GetDirectorId(), params.GetStudioId(), params.GetCountryId(), params.GetSetId());
+ bool bSuccess=videodatabase.GetMoviesNav(strBaseDir, items, params.GetGenreId(), params.GetYear(), params.GetActorId(), params.GetDirectorId(), params.GetStudioId(), params.GetCountryId(), params.GetSetId(), params.GetTagId());
videodatabase.Close();
1  xbmc/filesystem/VideoDatabaseDirectory/Makefile
View
@@ -15,6 +15,7 @@ SRCS=DirectoryNode.cpp \
DirectoryNodeSeasons.cpp \
DirectoryNodeSets.cpp \
DirectoryNodeStudio.cpp \
+ DirectoryNodeTags.cpp \
DirectoryNodeTitleMovies.cpp \
DirectoryNodeTitleMusicVideos.cpp \
DirectoryNodeTitleTvShows.cpp \
4 xbmc/filesystem/VideoDatabaseDirectory/QueryParams.cpp
View
@@ -39,6 +39,7 @@ CQueryParams::CQueryParams()
m_idMVideo = -1;
m_idAlbum = -1;
m_idSet = -1;
+ m_idTag = -1;
}
void CQueryParams::SetQueryParam(NODE_TYPE NodeType, const CStdString& strNodeName)
@@ -92,6 +93,9 @@ void CQueryParams::SetQueryParam(NODE_TYPE NodeType, const CStdString& strNodeNa
case NODE_TYPE_SETS:
m_idSet = idDb;
break;
+ case NODE_TYPE_TAGS:
+ m_idTag = idDb;
+ break;
default:
break;
}
2  xbmc/filesystem/VideoDatabaseDirectory/QueryParams.h
View
@@ -44,6 +44,7 @@ namespace XFILE
long GetStudioId() const { return m_idStudio; }
long GetMVideoId() const { return m_idMVideo; }
long GetSetId() const { return m_idSet; }
+ long GetTagId() const { return m_idTag; }
protected:
void SetQueryParam(NODE_TYPE NodeType, const CStdString& strNodeName);
@@ -64,6 +65,7 @@ namespace XFILE
long m_idMVideo;
long m_idAlbum;
long m_idSet;
+ long m_idTag;
};
}
}
6 xbmc/playlists/SmartPlayList.cpp
View
@@ -98,7 +98,8 @@ static const translateField fields[] = {
{ "audiolanguage", FieldAudioLanguage, SortByAudioLanguage, CSmartPlaylistRule::TEXTIN_FIELD, 21447 },
{ "subtitlelanguage", FieldSubtitleLanguage, SortBySubtitleLanguage, CSmartPlaylistRule::TEXTIN_FIELD, 21448 },
{ "random", FieldRandom, SortByRandom, CSmartPlaylistRule::TEXT_FIELD, 590 },
- { "playlist", FieldPlaylist, SortByPlaylistOrder, CSmartPlaylistRule::PLAYLIST_FIELD, 559 }
+ { "playlist", FieldPlaylist, SortByPlaylistOrder, CSmartPlaylistRule::PLAYLIST_FIELD, 559 },
+ { "tag", FieldTag, SortByNone, CSmartPlaylistRule::BROWSEABLE_FIELD, 20459 }
};
#define NUM_FIELDS sizeof(fields) / sizeof(translateField)
@@ -382,6 +383,7 @@ vector<Field> CSmartPlaylistRule::GetFields(const CStdString &type)
fields.push_back(FieldFilename);
fields.push_back(FieldPath);
fields.push_back(FieldSet);
+ fields.push_back(FieldTag);
fields.push_back(FieldDateAdded);
isVideo = true;
}
@@ -716,6 +718,8 @@ CStdString CSmartPlaylistRule::GetWhereClause(CDatabase &db, const CStdString& s
query = table + ".idFile " + negate + " IN (SELECT idFile FROM bookmark WHERE type = 1)";
else if (m_field == FieldSet)
query = GetField(FieldId, strType) + negate + " IN (SELECT idMovie FROM setlinkmovie JOIN sets ON sets.idSet=setlinkmovie.idSet WHERE sets.strSet" + parameter + ")";
+ else if (m_field == FieldTag)
+ 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')";
}
else if (strType == "musicvideos")
{
3  xbmc/utils/DatabaseUtils.h
View
@@ -109,7 +109,8 @@ typedef enum {
FieldAudioCodec,
FieldAudioLanguage,
FieldSubtitleLanguage,
- FieldProductionCode
+ FieldProductionCode,
+ FieldTag
} Field;
typedef std::set<Field> Fields;
12 xbmc/video/GUIViewStateVideo.cpp
View
@@ -211,6 +211,18 @@ CGUIViewStateWindowVideoNav::CGUIViewStateWindowVideoNav(const CFileItemList& it
SetSortOrder(g_settings.m_viewStateVideoNavGenres.m_sortOrder);
}
break;
+ case NODE_TYPE_TAGS:
+ {
+ SORT_METHOD method = SORT_METHOD_LABEL_IGNORE_THE;
+ if (!g_guiSettings.GetBool("filelists.ignorethewhensorting"))
+ method = SORT_METHOD_LABEL;
+
+ AddSortMethod(method, 551, LABEL_MASKS("%T","", "%T","")); // Title, empty | Title, empty
+ SetSortMethod(method);
+ SetViewAsControl(g_settings.m_viewStateVideoNavGenres.m_viewMode);
+ SetSortOrder(g_settings.m_viewStateVideoNavGenres.m_sortOrder);
+ }
+ break;
case NODE_TYPE_EPISODES:
{
if (params.GetSeason() > -1)
178 xbmc/video/VideoDatabase.cpp
View
@@ -321,6 +321,16 @@ bool CVideoDatabase::CreateTables()
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");
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");
+ CLog::Log(LOGINFO, "create tag table");
+ m_pDS->exec("CREATE TABLE tag (idTag integer primary key, strTag text)");
+ m_pDS->exec("CREATE UNIQUE INDEX ix_tag_1 ON tag (strTag(256))");
+
+ CLog::Log(LOGINFO, "create taglinks table");
+ m_pDS->exec("CREATE TABLE taglinks (idTag integer, idMedia integer, media_type TEXT)");
+ m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_1 ON taglinks (idTag, media_type(20), idMedia)");
+ m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_2 ON taglinks (idMedia, media_type(20), idTag)");
+ m_pDS->exec("CREATE INDEX ix_taglinks_3 ON taglinks (media_type(20))");
+
// we create views last to ensure all indexes are rolled in
CreateViews();
}
@@ -1240,7 +1250,7 @@ int CVideoDatabase::AddToTable(const CStdString& table, const CStdString& firstF
{
m_pDS->close();
// doesnt exists, add it
- strSQL = PrepareSQL("insert into %s (%s, %s) values( NULL, '%s')", table.c_str(), firstField.c_str(), secondField.c_str(), value.c_str());
+ strSQL = PrepareSQL("insert into %s (%s, %s) values(NULL, '%s')", table.c_str(), firstField.c_str(), secondField.c_str(), value.c_str());
m_pDS->exec(strSQL.c_str());
int id = (int)m_pDS->lastinsertid();
return id;
@@ -1265,6 +1275,14 @@ int CVideoDatabase::AddSet(const CStdString& strSet)
return AddToTable("sets", "idSet", "strSet", strSet);
}
+int CVideoDatabase::AddTag(const std::string& tag)
+{
+ if (tag.empty())
+ return -1;
+
+ return AddToTable("tag", "idTag", "strTag", tag);
+}
+
int CVideoDatabase::AddGenre(const CStdString& strGenre)
{
return AddToTable("genre", "idGenre", "strGenre", strGenre);
@@ -1346,19 +1364,24 @@ void CVideoDatabase::AddLinkToActor(const char *table, int actorID, const char *
}
}
-void CVideoDatabase::AddToLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID)
+void CVideoDatabase::AddToLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID, const char *typeField /* = NULL */, const char *type /* = NULL */)
{
try
{
if (NULL == m_pDB.get()) return ;
if (NULL == m_pDS.get()) return ;
- CStdString strSQL=PrepareSQL("select * from %s where %s=%i and %s=%i", table, firstField, firstID, secondField, secondID);
+ CStdString strSQL = PrepareSQL("select * from %s where %s=%i and %s=%i", table, firstField, firstID, secondField, secondID);
+ if (typeField != NULL && type != NULL)
+ strSQL += PrepareSQL(" and %s='%s'", typeField, type);
m_pDS->query(strSQL.c_str());
if (m_pDS->num_rows() == 0)
{
// doesnt exists, add it
- strSQL=PrepareSQL("insert into %s (%s,%s) values(%i,%i)", table, firstField, secondField, firstID, secondID);
+ if (typeField == NULL || type == NULL)
+ strSQL = PrepareSQL("insert into %s (%s,%s) values(%i,%i)", table, firstField, secondField, firstID, secondID);
+ else
+ strSQL = PrepareSQL("insert into %s (%s,%s,%s) values(%i,%i,'%s')", table, firstField, secondField, typeField, firstID, secondID, type);
m_pDS->exec(strSQL.c_str());
}
m_pDS->close();
@@ -1369,11 +1392,45 @@ void CVideoDatabase::AddToLinkTable(const char *table, const char *firstField, i
}
}
+void CVideoDatabase::RemoveFromLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID, const char *typeField /* = NULL */, const char *type /* = NULL */)
+{
+ try
+ {
+ if (NULL == m_pDB.get()) return ;
+ if (NULL == m_pDS.get()) return ;
+
+ CStdString strSQL = PrepareSQL("DELETE FROM %s WHERE %s = %i AND %s = %i", table, firstField, firstID, secondField, secondID);
+ if (typeField != NULL && type != NULL)
+ strSQL += PrepareSQL(" AND %s='%s'", typeField, type);
+ m_pDS->exec(strSQL.c_str());
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
+ }
+}
+
//****Sets****
void CVideoDatabase::AddSetToMovie(int idMovie, int idSet)
{
AddToLinkTable("setlinkmovie", "idSet", idSet, "idMovie", idMovie);
}
+//****Tags****
+void CVideoDatabase::AddTagToItem(int idMovie, int idTag, const std::string &type)
+{
+ if (type.empty())
+ return;
+
+ AddToLinkTable("taglinks", "idTag", idTag, "idMedia", idMovie, "media_type", type.c_str());
+}
+
+void CVideoDatabase::RemoveTagFromItem(int idMovie, int idTag, const std::string &type)
+{
+ if (type.empty())
+ return;
+
+ RemoveFromLinkTable("taglinks", "idTag", idTag, "idMedia", idMovie, "media_type", type.c_str());
+}
//****Actors****
void CVideoDatabase::AddActorToMovie(int idMovie, int idActor, const CStdString& strRole, int order)
@@ -1898,6 +1955,13 @@ int CVideoDatabase::SetDetailsForMovie(const CStdString& strFilenameAndPath, con
AddSetToMovie(idMovie, idSet);
}
+ // add tags...
+ for (unsigned int i = 0; i < details.m_tags.size(); i++)
+ {
+ int idTag = AddTag(details.m_tags[i]);
+ AddTagToItem(idMovie, idTag, "movie");
+ }
+
// add countries...
for (unsigned int i = 0; i < details.m_country.size(); i++)
AddCountryToMovie(idMovie, AddCountry(details.m_country[i]));
@@ -2838,6 +2902,31 @@ void CVideoDatabase::DeleteSet(int idSet)
}
}
+void CVideoDatabase::DeleteTag(int idTag, const std::string &mediaType)
+{
+ try
+ {
+ if (m_pDB.get() == NULL || m_pDS.get() == NULL)
+ return;
+
+ CStdString strSQL;
+ strSQL = PrepareSQL("DELETE FROM taglinks WHERE idTag = %i AND media_type = '%s'", idTag, mediaType.c_str());
+ m_pDS->exec(strSQL.c_str());
+
+ // check if the tag is used for another media type as well before deleting it completely
+ strSQL = PrepareSQL("SELECT 1 FROM taglinks WHERE idTag = %i", idTag);
+ if (RunQuery(strSQL) <= 0)
+ {
+ strSQL = PrepareSQL("DELETE FROM tag WHERE idTag = %i", idTag);
+ m_pDS->exec(strSQL.c_str());
+ }
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idTag);
+ }
+}
+
void CVideoDatabase::GetDetailsFromDB(auto_ptr<Dataset> &pDS, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
{
GetDetailsFromDB(pDS->get_sql_record(), min, max, offsets, details, idxOffset);
@@ -3058,6 +3147,15 @@ CVideoInfoTag CVideoDatabase::GetDetailsForMovie(const dbiplus::sql_record* cons
m_pDS2->next();
}
+ // get tags
+ 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);
+ m_pDS2->query(strSQL.c_str());
+ while (!m_pDS2->eof())
+ {
+ details.m_tags.push_back(m_pDS2->fv("tag.strTag").get_asString());
+ m_pDS2->next();
+ }
+
// create tvshowlink string
vector<int> links;
GetLinksToTvShow(idMovie,links);
@@ -3900,6 +3998,16 @@ bool CVideoDatabase::UpdateOldVersion(int iVersion)
m_pDS->exec("CREATE INDEX ix_episode_show1 on episode(idEpisode,idShow)");
m_pDS->exec("CREATE INDEX ix_episode_show2 on episode(idShow,idEpisode)");
}
+ if (iVersion < 67)
+ {
+ m_pDS->exec("CREATE TABLE tag (idTag integer primary key, strTag text)");
+ m_pDS->exec("CREATE UNIQUE INDEX ix_tag_1 ON tag (strTag(256))");
+
+ m_pDS->exec("CREATE TABLE taglinks (idTag integer, idMedia integer, media_type TEXT)");
+ m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_1 ON taglinks (idTag, media_type(20), idMedia)");
+ m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_2 ON taglinks (idMedia, media_type(20), idTag)");
+ m_pDS->exec("CREATE INDEX ix_taglinks_3 ON taglinks (media_type(20))");
+ }
// always recreate the view after any table change
CreateViews();
return true;
@@ -4351,6 +4459,54 @@ bool CVideoDatabase::GetNavCommon(const CStdString& strBaseDir, CFileItemList& i
return false;
}
+bool CVideoDatabase::GetTagsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent)
+{
+ CStdString mediaType;
+ if (idContent == VIDEODB_CONTENT_MOVIES)
+ mediaType = "movie";
+ else
+ return false;
+
+ try
+ {
+ if (NULL == m_pDB.get()) return false;
+ if (NULL == m_pDS.get()) return false;
+
+ 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()));
+ if (iRowsFound <= 0)
+ return iRowsFound == 0;
+
+ while (!m_pDS->eof())
+ {
+ int idTag = m_pDS->fv(0).get_asInt();
+
+ CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
+ pItem->m_bIsFolder = true;
+ pItem->GetVideoInfoTag()->m_iDbId = idTag;
+ pItem->GetVideoInfoTag()->m_type = "tag";
+
+ CStdString strDir; strDir.Format("%ld/", idTag);
+ pItem->SetPath(strBaseDir + strDir);
+
+ if (!items.Contains(pItem->GetPath()))
+ {
+ pItem->SetLabelPreformated(true);
+ items.Add(pItem);
+ }
+
+ m_pDS->next();
+ }
+ m_pDS->close();
+
+ return true;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
+ }
+ return false;
+}
+
bool CVideoDatabase::GetSetsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent)
{
if (idContent != VIDEODB_CONTENT_MOVIES)
@@ -5149,7 +5305,9 @@ bool CVideoDatabase::GetSortedVideos(MediaType mediaType, const CStdString& strB
return success;
}
-bool CVideoDatabase::GetMoviesNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre, int idYear, int idActor, int idDirector, int idStudio, int idCountry, int idSet)
+bool CVideoDatabase::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 */)
{
Filter filter;
if (idGenre != -1)
@@ -5184,6 +5342,11 @@ bool CVideoDatabase::GetMoviesNav(const CStdString& strBaseDir, CFileItemList& i
filter.join = PrepareSQL("join setlinkmovie on setlinkmovie.idMovie=movieview.idMovie");
filter.where = PrepareSQL("setlinkmovie.idSet=%u",idSet);
}
+ else if (idTag != -1)
+ {
+ filter.join = PrepareSQL("join taglinks on taglinks.idMedia = movieview.idMovie AND taglinks.media_type = 'movie'");
+ filter.where = PrepareSQL("taglinks.idTag = %u", idTag);
+ }
return GetMoviesByWhere(strBaseDir, filter, items, idSet == -1);
}
@@ -5842,6 +6005,11 @@ CStdString CVideoDatabase::GetSetById(int id)
return GetSingleValue("sets", "strSet", PrepareSQL("idSet=%i", id));
}
+CStdString CVideoDatabase::GetTagById(int id)
+{
+ return GetSingleValue("tag", "strTag", PrepareSQL("idTag = %i", id));
+}
+
CStdString CVideoDatabase::GetPersonById(int id)
{
return GetSingleValue("actors", "strActor", PrepareSQL("idActor=%i", id));
14 xbmc/video/VideoDatabase.h
View
@@ -419,6 +419,7 @@ class CVideoDatabase : public CDatabase
CStdString GetGenreById(int id);
CStdString GetCountryById(int id);
CStdString GetSetById(int id);
+ CStdString GetTagById(int id);
CStdString GetPersonById(int id);
CStdString GetStudioById(int id);
CStdString GetTvShowTitleById(int id);
@@ -459,6 +460,7 @@ class CVideoDatabase : public CDatabase
void RemoveContentForPath(const CStdString& strPath,CGUIDialogProgress *progress = NULL);
void UpdateFanart(const CFileItem &item, VIDEODB_CONTENT_TYPE type);
void DeleteSet(int idSet);
+ void DeleteTag(int idTag, const std::string &mediaType);
// per-file video settings
bool GetVideoSettings(const CStdString &strFilenameAndPath, CVideoSettings &settings);
@@ -584,9 +586,10 @@ class CVideoDatabase : public CDatabase
bool GetDirectorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1);
bool GetWritersNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1);
bool GetSetsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1);
+ bool GetTagsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent=-1);
bool GetMusicVideoAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idArtist);
- 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);
+ 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);
bool GetTvShowsNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre=-1, int idYear=-1, int idActor=-1, int idDirector=-1, int idStudio=-1);
bool GetSeasonsNav(const CStdString& strBaseDir, CFileItemList& items, int idActor=-1, int idDirector=-1, int idGenre=-1, int idYear=-1, int idShow=-1);
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
std::string GetArtForItem(int mediaId, const std::string &mediaType, const std::string &artType);
bool GetTvShowSeasonArt(int mediaId, std::map<int, std::string> &seasonArt);
+ int AddTag(const std::string &tag);
+ void AddTagToItem(int idItem, int idTag, const std::string &type);
+ void RemoveTagFromItem(int idItem, int idTag, const std::string &type);
+
protected:
int GetMovieId(const CStdString& strFilenameAndPath);
int GetMusicVideoId(const CStdString& strFilenameAndPath);
@@ -710,7 +717,8 @@ class CVideoDatabase : public CDatabase
// link functions - these two do all the work
void AddLinkToActor(const char *table, int actorID, const char *secondField, int secondID, const CStdString &role, int order);
- void AddToLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID);
+ void AddToLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID, const char *typeField = NULL, const char *type = NULL);
+ void RemoveFromLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID, const char *typeField = NULL, const char *type = NULL);
void AddSetToMovie(int idMovie, int idSet);
@@ -799,7 +807,7 @@ class CVideoDatabase : public CDatabase
*/
bool LookupByFolders(const CStdString &path, bool shows = false);
- virtual int GetMinVersion() const { return 66; };
+ virtual int GetMinVersion() const { return 67; };
virtual int GetExportVersion() const { return 1; };
const char *GetBaseDBName() const { return "MyVideos"; };
6 xbmc/video/VideoInfoTag.cpp
View
@@ -53,6 +53,7 @@ void CVideoInfoTag::Reset()
m_cast.clear();
m_set.clear();
m_setId.clear();
+ m_tags.clear();
m_strFile.clear();
m_strPath.clear();
m_strIMDBNumber.clear();
@@ -174,6 +175,7 @@ bool CVideoInfoTag::Save(TiXmlNode *node, const CStdString &tag, bool savePathIn
XMLUtils::SetStringArray(movie, "genre", m_genre);
XMLUtils::SetStringArray(movie, "country", m_country);
XMLUtils::SetStringArray(movie, "set", m_set);
+ XMLUtils::SetStringArray(movie, "tag", m_tags);
XMLUtils::SetStringArray(movie, "credits", m_writingCredits);
XMLUtils::SetStringArray(movie, "director", m_director);
XMLUtils::SetDate(movie, "premiered", m_premiered);
@@ -291,6 +293,7 @@ void CVideoInfoTag::Archive(CArchive& ar)
ar << m_set;
ar << m_setId;
+ ar << m_tags;
ar << m_strRuntime;
ar << m_strFile;
ar << m_strPath;
@@ -367,6 +370,7 @@ void CVideoInfoTag::Archive(CArchive& ar)
ar >> m_set;
ar >> m_setId;
+ ar >> m_tags;
ar >> m_strRuntime;
ar >> m_strFile;
ar >> m_strPath;
@@ -443,6 +447,7 @@ void CVideoInfoTag::Serialize(CVariant& value)
value["setid"] = CVariant(CVariant::VariantTypeArray);
for (unsigned int i = 0; i < m_setId.size(); i++)
value["setid"].push_back(m_setId[i]);
+ value["tags"] = m_tags;
value["runtime"] = m_strRuntime;
value["file"] = m_strFile;
value["path"] = m_strPath;
@@ -656,6 +661,7 @@ void CVideoInfoTag::ParseNative(const TiXmlElement* movie, bool prioritise)
}
XMLUtils::GetStringArray(movie, "set", m_set, prioritise, g_advancedSettings.m_videoItemSeparator);
+ XMLUtils::GetStringArray(movie, "tag", m_tags, prioritise, g_advancedSettings.m_videoItemSeparator);
XMLUtils::GetStringArray(movie, "studio", m_studio, prioritise, g_advancedSettings.m_videoItemSeparator);
// artists
node = movie->FirstChildElement("artist");
1  xbmc/video/VideoInfoTag.h
View
@@ -97,6 +97,7 @@ class CVideoInfoTag : public IArchivable, public ISerializable, public ISortable
typedef std::vector< SActorInfo >::const_iterator iCast;
std::vector<std::string> m_set;
std::vector<int> m_setId;
+ std::vector<std::string> m_tags;
CStdString m_strRuntime;
CStdString m_strFile;
CStdString m_strPath;
5 xbmc/video/windows/GUIWindowVideoBase.cpp
View
@@ -875,7 +875,8 @@ bool CGUIWindowVideoBase::OnSelect(int iItem)
CFileItemPtr item = m_vecItems->Get(iItem);
CStdString path = item->GetPath();
- if (!item->m_bIsFolder && path != "add" && path != "addons://more/video" && path.Left(19) != "newsmartplaylist://" && path.Left(14) != "newplaylist://")
+ if (!item->m_bIsFolder && path != "add" && path != "addons://more/video" &&
+ path.Left(19) != "newsmartplaylist://" && path.Left(14) != "newplaylist://" && path.Left(9) != "newtag://")
return OnFileAction(iItem, g_guiSettings.GetInt("myvideos.selectaction"));
return CGUIMediaWindow::OnSelect(iItem);
@@ -1187,7 +1188,7 @@ void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &but
buttons.Add(CONTEXT_BUTTON_PLAY_PART, 20324);
}
- if (!m_vecItems->GetPath().IsEmpty() && !item->GetPath().Left(19).Equals("newsmartplaylist://")
+ if (!m_vecItems->GetPath().IsEmpty() && !item->GetPath().Left(19).Equals("newsmartplaylist://") && !item->GetPath().Left(9).Equals("newtag://")
&& !m_vecItems->IsSourcesPath())
{
buttons.Add(CONTEXT_BUTTON_QUEUE_ITEM, 13347); // Add to Playlist
236 xbmc/video/windows/GUIWindowVideoNav.cpp
View
@@ -51,6 +51,7 @@
#include "utils/URIUtils.h"
#include "utils/StringUtils.h"
#include "TextureCache.h"
+#include "dialogs/GUIDialogKeyboard.h"
using namespace XFILE;
using namespace VIDEODATABASEDIRECTORY;
@@ -207,6 +208,14 @@ CStdString CGUIWindowVideoNav::GetQuickpathName(const CStdString& strPath) const
return "MovieActors";
else if (strPath.Equals("videodb://1/5/"))
return "MovieDirectors";
+ else if (strPath.Equals("videodb://1/6/"))
+ return "MovieStudios";
+ else if (strPath.Equals("videodb://1/7/"))
+ return "MovieSets";
+ else if (strPath.Equals("videodb://1/8/"))
+ return "MovieCountries";
+ else if (strPath.Equals("videodb://1/9/"))
+ return "MovieTags";
else if (strPath.Equals("videodb://1/"))
return "Movies";
else if (strPath.Equals("videodb://2/1/"))
@@ -315,6 +324,7 @@ bool CGUIWindowVideoNav::GetDirectory(const CStdString &strDirectory, CFileItemL
}
else if (node == NODE_TYPE_TITLE_MOVIES ||
node == NODE_TYPE_SETS ||
+ node == NODE_TYPE_TAGS ||
node == NODE_TYPE_RECENTLY_ADDED_MOVIES)
items.SetContent("movies");
else if (node == NODE_TYPE_TITLE_TVSHOWS)
@@ -352,6 +362,15 @@ bool CGUIWindowVideoNav::GetDirectory(const CStdString &strDirectory, CFileItemL
if (!items.IsSourcesPath())
LoadVideoInfo(items);
}
+
+ if (items.GetPath() == "videodb://1/9/" && !items.Contains("newtag://movie"))
+ {
+ CFileItemPtr newTag(new CFileItem("newtag://movie", false));
+ newTag->SetLabel(g_localizeStrings.Get(20462));
+ newTag->SetLabelPreformated(true);
+ newTag->SetSpecialSort(SortSpecialOnTop);
+ items.Add(newTag);
+ }
}
return bResult;
}
@@ -624,7 +643,8 @@ void CGUIWindowVideoNav::OnDeleteItem(CFileItemPtr pItem)
{
if (!pItem->GetPath().Equals("newsmartplaylist://video") &&
!pItem->GetPath().Equals("special://videoplaylists/") &&
- !pItem->GetPath().Equals("sources://video/"))
+ !pItem->GetPath().Equals("sources://video/") &&
+ !pItem->GetPath().Left(9).Equals("newtag://"))
CGUIWindowVideoBase::OnDeleteItem(pItem);
}
else if (pItem->GetPath().Left(14).Equals("videodb://1/7/") &&
@@ -644,10 +664,28 @@ void CGUIWindowVideoNav::OnDeleteItem(CFileItemPtr pItem)
for (int i=0;i<items.Size();++i)
OnDeleteItem(items[i]);
- CVideoDatabaseDirectory dir;
- CQueryParams params;
- dir.GetQueryParams(pItem->GetPath(),params);
- m_database.DeleteSet(params.GetSetId());
+ CVideoDatabaseDirectory dir;
+ CQueryParams params;
+ dir.GetQueryParams(pItem->GetPath(),params);
+ m_database.DeleteSet(params.GetSetId());
+ }
+ }
+ else if (pItem->GetPath().Left(14).Equals("videodb://1/9/") &&
+ pItem->GetPath().size() > 14 && pItem->m_bIsFolder)
+ {
+ CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO);
+ pDialog->SetHeading(432);
+ CStdString strLabel;
+ strLabel.Format(g_localizeStrings.Get(433),pItem->GetLabel());
+ pDialog->SetLine(1, strLabel);
+ pDialog->SetLine(2, "");;
+ pDialog->DoModal();
+ if (pDialog->IsConfirmed())
+ {
+ CVideoDatabaseDirectory dir;
+ CQueryParams params;
+ dir.GetQueryParams(pItem->GetPath(), params);
+ m_database.DeleteTag(params.GetTagId(), "movie");
}
}
else if (m_vecItems->GetPath().Equals(CUtil::VideoPlaylistsLocation()) ||
@@ -883,7 +921,9 @@ void CGUIWindowVideoNav::GetContextButtons(int itemNumber, CContextButtons &butt
buttons.Add(CONTEXT_BUTTON_UPDATE_TVSHOW, 13349);
}
if (!item->IsPlugin() && !item->IsScript() && !item->IsLiveTV() && !item->IsAddonsPath() &&
- item->GetPath() != "sources://video/" && item->GetPath() != "special://videoplaylists/")
+ item->GetPath() != "sources://video/" && item->GetPath() != "special://videoplaylists/" &&
+ item->GetPath().Left(19) != "newsmartplaylist://" && item->GetPath().Left(14) != "newplaylist://" &&
+ item->GetPath().Left(9) != "newtag://")
{
if (item->m_bIsFolder)
{
@@ -924,6 +964,15 @@ void CGUIWindowVideoNav::GetContextButtons(int itemNumber, CContextButtons &butt
buttons.Add(CONTEXT_BUTTON_DELETE, 646);
}
+ if (item->GetPath().Left(14).Equals("videodb://1/9/") && item->GetPath().size() > 14 && item->m_bIsFolder) // tags
+ {
+ CStdString strLabelAdd; strLabelAdd.Format(g_localizeStrings.Get(20460), g_localizeStrings.Get(20342).c_str());
+ CStdString strLabelRemove; strLabelRemove.Format(g_localizeStrings.Get(20461), g_localizeStrings.Get(20342).c_str());
+ buttons.Add(CONTEXT_BUTTON_TAGS_ADD_ITEMS, strLabelAdd);
+ buttons.Add(CONTEXT_BUTTON_TAGS_REMOVE_ITEMS, strLabelRemove);
+ buttons.Add(CONTEXT_BUTTON_DELETE, 646);
+ }
+
if (node == NODE_TYPE_ACTOR && !dir.IsAllItem(item->GetPath()) && item->m_bIsFolder)
{
if (m_vecItems->GetPath().Left(11).Equals("videodb://3")) // mvids
@@ -1195,6 +1244,74 @@ bool CGUIWindowVideoNav::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
Update(m_vecItems->GetPath());
return true;
}
+ case CONTEXT_BUTTON_TAGS_ADD_ITEMS:
+ {
+ if (!item->GetPath().Left(10).Equals("videodb://"))
+ return false;
+
+ std::string mediaType;
+ if (item->GetPath().Mid(9, 3).Equals("/1/"))
+ mediaType = "movie";
+ else
+ return false;
+
+ CFileItemList items;
+ CStdString localizedType = GetLocalizedType(mediaType);
+ CStdString strLabel; strLabel.Format(g_localizeStrings.Get(20464), localizedType.c_str());
+ if (!GetItemsForTag(strLabel, mediaType, items, item->GetVideoInfoTag()->m_iDbId))
+ return true;
+
+ CVideoDatabase videodb;
+ if (!videodb.Open())
+ return true;
+
+ for (int index = 0; index < items.Size(); index++)
+ {
+ if (!items[index]->HasVideoInfoTag() || items[index]->GetVideoInfoTag()->m_iDbId <= 0)
+ continue;
+
+ videodb.AddTagToItem(items[index]->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_iDbId, mediaType);
+ }
+
+ // we need to clear any cached version of this tag's listing
+ items.SetPath(item->GetPath());
+ items.RemoveDiscCache(GetID());
+ return true;
+ }
+ case CONTEXT_BUTTON_TAGS_REMOVE_ITEMS:
+ {
+ if (!item->GetPath().Left(10).Equals("videodb://"))
+ return false;
+
+ std::string mediaType;
+ if (item->GetPath().Mid(9, 3).Equals("/1/"))
+ mediaType = "movie";
+ else
+ return false;
+
+ CFileItemList items;
+ CStdString localizedType = GetLocalizedType(mediaType);
+ CStdString strLabel; strLabel.Format(g_localizeStrings.Get(20464), localizedType.c_str());
+ if (!GetItemsForTag(strLabel, mediaType, items, item->GetVideoInfoTag()->m_iDbId, false))
+ return true;
+
+ CVideoDatabase videodb;
+ if (!videodb.Open())
+ return true;
+
+ for (int index = 0; index < items.Size(); index++)
+ {
+ if (!items[index]->HasVideoInfoTag() || items[index]->GetVideoInfoTag()->m_iDbId <= 0)
+ continue;
+
+ videodb.RemoveTagFromItem(items[index]->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_iDbId, mediaType);
+ }
+
+ // we need to clear any cached version of this tag's listing
+ items.SetPath(item->GetPath());
+ items.RemoveDiscCache(GetID());
+ return true;
+ }
case CONTEXT_BUTTON_UPDATE_LIBRARY:
{
OnScan("");
@@ -1332,6 +1449,54 @@ bool CGUIWindowVideoNav::OnClick(int iItem)
m_viewControl.SetSelectedItem(iItem);
return true;
}
+ else if (item->GetPath().Left(9).Equals("newtag://"))
+ {
+ // dont allow update while scanning
+ if (g_application.IsVideoScanning())
+ {
+ CGUIDialogOK::ShowAndGetInput(257, 0, 14057, 0);
+ return true;
+ }
+
+ //Get the new title
+ CStdString strTag;
+ if (!CGUIDialogKeyboard::ShowAndGetInput(strTag, g_localizeStrings.Get(20462), false))
+ return true;
+
+ CVideoDatabase videodb;
+ if (!videodb.Open())
+ return true;
+
+ CStdString mediaType = item->GetPath().Mid(9);
+ CStdString localizedType = GetLocalizedType(mediaType);
+ if (localizedType.empty())
+ return true;
+
+ if (!videodb.GetSingleValue("tag", "tag.idTag", videodb.PrepareSQL("tag.strTag = '%s' AND tag.idTag IN (SELECT taglinks.idTag FROM taglinks WHERE taglinks.media_type = '%s')", strTag.c_str(), mediaType.c_str())).empty())
+ {
+ CStdString strError; strError.Format(g_localizeStrings.Get(20463), strTag.c_str());
+ CGUIDialogOK::ShowAndGetInput(20462, strError, "", "");
+ return true;
+ }
+
+ int idTag = videodb.AddTag(strTag);
+ CFileItemList items;
+ CStdString strLabel; strLabel.Format(g_localizeStrings.Get(20464), localizedType.c_str());
+ if (GetItemsForTag(strLabel, mediaType, items, idTag))
+ {
+ for (int index = 0; index < items.Size(); index++)
+ {
+ if (!items[index]->HasVideoInfoTag() || items[index]->GetVideoInfoTag()->m_iDbId <= 0)
+ continue;
+
+ videodb.AddTagToItem(items[index]->GetVideoInfoTag()->m_iDbId, idTag, mediaType);
+ }
+ }
+
+ m_vecItems->RemoveDiscCache(GetID());
+ Update(m_vecItems->GetPath());
+ return true;
+ }
return CGUIWindowVideoBase::OnClick(iItem);
}
@@ -1354,6 +1519,8 @@ CStdString CGUIWindowVideoNav::GetStartFolder(const CStdString &dir)
return "videodb://1/7/";
else if (dir.Equals("MovieCountries"))
return "videodb://1/8/";
+ else if (dir.Equals("MovieTags"))
+ return "videodb://1/9/";
else if (dir.Equals("Movies"))
return "videodb://1/";
else if (dir.Equals("TvShowGenres"))
@@ -1405,6 +1572,7 @@ void CGUIWindowVideoNav::ApplyWatchedFilter(CFileItemList &items)
if (node == NODE_TYPE_EPISODES
|| node == NODE_TYPE_SEASONS
|| node == NODE_TYPE_SETS
+ || node == NODE_TYPE_TAGS
|| node == NODE_TYPE_TITLE_MOVIES
|| node == NODE_TYPE_TITLE_TVSHOWS
|| node == NODE_TYPE_TITLE_MUSICVIDEOS
@@ -1443,3 +1611,59 @@ void CGUIWindowVideoNav::ApplyWatchedFilter(CFileItemList &items)
}
}
}
+
+bool CGUIWindowVideoNav::GetItemsForTag(const CStdString &strHeading, const std::string &type, CFileItemList &items, int idTag /* = -1 */, bool showAll /* = true */)
+{
+ CVideoDatabase videodb;
+ if (!videodb.Open())
+ return false;
+
+ CFileItemList listItems;
+ bool result = false;
+ if (idTag <= 0)
+ result = videodb.GetMoviesNav("videodb://1/2/", listItems);
+ else
+ {
+ if (showAll)
+ {
+ CVideoDatabase::Filter filter;
+ filter.where = videodb.PrepareSQL("movieview.idMovie NOT IN (SELECT taglinks.idMedia FROM taglinks WHERE taglinks.idTag = %d AND taglinks.media_type = '%s')", idTag, type.c_str());
+ result = videodb.GetMoviesByWhere("videodb://1/2/", filter, listItems);
+ }
+ else
+ result = videodb.GetMoviesNav("videodb://1/9/", listItems, -1, -1, -1, -1, -1, -1, -1, idTag);
+ }
+
+ if (!result || listItems.Size() <= 0)
+ return false;
+
+ CGUIDialogSelect *dialog = (CGUIDialogSelect *)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
+ if (dialog == NULL)
+ return false;
+
+ listItems.Sort(SORT_METHOD_LABEL_IGNORE_THE, SortOrderAscending);
+
+ dialog->Reset();
+ dialog->SetMultiSelection(true);
+ dialog->SetHeading(strHeading);
+ dialog->SetItems(&listItems);
+ dialog->EnableButton(true, 186);
+ dialog->DoModal();
+
+ items.Copy(dialog->GetSelectedItems());
+ return items.Size() > 0;
+}
+
+CStdString CGUIWindowVideoNav::GetLocalizedType(const std::string &strType)
+{
+ if (strType == "movie")
+ return g_localizeStrings.Get(20342);
+ else if (strType == "tvshow")
+ return g_localizeStrings.Get(20343);
+ else if (strType == "episode")
+ return g_localizeStrings.Get(20359);
+ else if (strType == "musicvideo")
+ return g_localizeStrings.Get(20391);
+ else
+ return "";
+}
3  xbmc/video/windows/GUIWindowVideoNav.h
View
@@ -67,5 +67,8 @@ class CGUIWindowVideoNav : public CGUIWindowVideoBase
virtual CStdString GetQuickpathName(const CStdString& strPath) const;
+ bool GetItemsForTag(const CStdString &strHeading, const std::string &type, CFileItemList &items, int idTag = -1, bool showAll = true);
+ static CStdString GetLocalizedType(const std::string &strType);
+
VECSOURCES m_shares;
};
2  xbmc/windows/GUIMediaWindow.cpp
View
@@ -1425,7 +1425,7 @@ void CGUIMediaWindow::GetContextButtons(int itemNumber, CContextButtons &buttons
// TODO: FAVOURITES Conditions on masterlock and localisation
if (!item->IsParentFolder() && !item->GetPath().Equals("add") && !item->GetPath().Equals("newplaylist://") &&
- !item->GetPath().Left(19).Equals("newsmartplaylist://"))
+ !item->GetPath().Left(19).Equals("newsmartplaylist://") && !item->GetPath().Left(9).Equals("newtag://"))
{
if (CFavourites::IsFavourite(item.get(), GetID()))
buttons.Add(CONTEXT_BUTTON_ADD_FAVOURITE, 14077); // Remove Favourite
Something went wrong with that request. Please try again.