[cosmetic] AddVideoAsset function cleanup
[xbmc.git] / xbmc / video / VideoDatabase.cpp
blob177eae333dfab9f1c0665137845d214e3fc1c7bf
1 /*
2 * Copyright (C) 2016-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "VideoDatabase.h"
11 #include "FileItem.h"
12 #include "FileItemList.h"
13 #include "GUIInfoManager.h"
14 #include "GUIPassword.h"
15 #include "ServiceBroker.h"
16 #include "TextureCache.h"
17 #include "URL.h"
18 #include "Util.h"
19 #include "VideoInfoScanner.h"
20 #include "XBDateTime.h"
21 #include "addons/AddonManager.h"
22 #include "dbwrappers/dataset.h"
23 #include "dialogs/GUIDialogExtendedProgressBar.h"
24 #include "dialogs/GUIDialogKaiToast.h"
25 #include "dialogs/GUIDialogProgress.h"
26 #include "dialogs/GUIDialogYesNo.h"
27 #include "filesystem/Directory.h"
28 #include "filesystem/File.h"
29 #include "filesystem/MultiPathDirectory.h"
30 #include "filesystem/PluginDirectory.h"
31 #include "filesystem/StackDirectory.h"
32 #include "guilib/GUIComponent.h"
33 #include "guilib/GUIWindowManager.h"
34 #include "guilib/LocalizeStrings.h"
35 #include "guilib/guiinfo/GUIInfoLabels.h"
36 #include "imagefiles/ImageFileURL.h"
37 #include "interfaces/AnnouncementManager.h"
38 #include "messaging/helpers/DialogOKHelper.h"
39 #include "music/Artist.h"
40 #include "playlists/SmartPlayList.h"
41 #include "profiles/ProfileManager.h"
42 #include "settings/AdvancedSettings.h"
43 #include "settings/MediaSettings.h"
44 #include "settings/MediaSourceSettings.h"
45 #include "settings/Settings.h"
46 #include "settings/SettingsComponent.h"
47 #include "storage/MediaManager.h"
48 #include "utils/ArtUtils.h"
49 #include "utils/FileUtils.h"
50 #include "utils/GroupUtils.h"
51 #include "utils/LabelFormatter.h"
52 #include "utils/StringUtils.h"
53 #include "utils/URIUtils.h"
54 #include "utils/Variant.h"
55 #include "utils/XMLUtils.h"
56 #include "utils/log.h"
57 #include "video/VideoDbUrl.h"
58 #include "video/VideoFileItemClassify.h"
59 #include "video/VideoInfoTag.h"
60 #include "video/VideoLibraryQueue.h"
61 #include "video/VideoManagerTypes.h"
62 #include "video/VideoThumbLoader.h"
64 #include <algorithm>
65 #include <map>
66 #include <memory>
67 #include <string>
68 #include <unordered_set>
69 #include <vector>
71 using namespace dbiplus;
72 using namespace XFILE;
73 using namespace ADDON;
74 using namespace KODI;
75 using namespace KODI::MESSAGING;
76 using namespace KODI::GUILIB;
77 using namespace KODI::VIDEO;
79 //********************************************************************************************************************************
80 CVideoDatabase::CVideoDatabase(void) = default;
82 //********************************************************************************************************************************
83 CVideoDatabase::~CVideoDatabase(void) = default;
85 //********************************************************************************************************************************
86 bool CVideoDatabase::Open()
88 return CDatabase::Open(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_databaseVideo);
91 void CVideoDatabase::CreateTables()
93 CLog::Log(LOGINFO, "create bookmark table");
94 m_pDS->exec("CREATE TABLE bookmark ( idBookmark integer primary key, idFile integer, timeInSeconds double, totalTimeInSeconds double, thumbNailImage text, player text, playerState text, type integer)\n");
96 CLog::Log(LOGINFO, "create settings table");
97 m_pDS->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
98 "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
99 "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
100 "VolumeAmplification float, AudioDelay float, ResumeTime integer,"
101 "Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
102 "ScalingMethod integer, DeinterlaceMode integer, StereoMode integer, StereoInvert bool, VideoStream integer,"
103 "TonemapMethod integer, TonemapParam float, Orientation integer, CenterMixLevel integer)\n");
105 CLog::Log(LOGINFO, "create stacktimes table");
106 m_pDS->exec("CREATE TABLE stacktimes (idFile integer, times text)\n");
108 CLog::Log(LOGINFO, "create genre table");
109 m_pDS->exec("CREATE TABLE genre ( genre_id integer primary key, name TEXT)\n");
110 m_pDS->exec("CREATE TABLE genre_link (genre_id integer, media_id integer, media_type TEXT)");
112 CLog::Log(LOGINFO, "create country table");
113 m_pDS->exec("CREATE TABLE country ( country_id integer primary key, name TEXT)");
114 m_pDS->exec("CREATE TABLE country_link (country_id integer, media_id integer, media_type TEXT)");
116 CLog::Log(LOGINFO, "create movie table");
117 std::string columns = "CREATE TABLE movie ( idMovie integer primary key, idFile integer";
119 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
120 columns += StringUtils::Format(",c{:02} text", i);
122 columns += ", idSet integer, userrating integer, premiered text)";
123 m_pDS->exec(columns);
125 CLog::Log(LOGINFO, "create actor table");
126 m_pDS->exec("CREATE TABLE actor ( actor_id INTEGER PRIMARY KEY, name TEXT, art_urls TEXT )");
127 m_pDS->exec("CREATE TABLE actor_link(actor_id INTEGER, media_id INTEGER, media_type TEXT, role TEXT, cast_order INTEGER)");
128 m_pDS->exec("CREATE TABLE director_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
129 m_pDS->exec("CREATE TABLE writer_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
131 CLog::Log(LOGINFO, "create path table");
132 m_pDS->exec(
133 "CREATE TABLE path ( idPath integer primary key, strPath text, strContent text, strScraper "
134 "text, strHash text, scanRecursive integer, useFolderNames bool, strSettings text, noUpdate "
135 "bool, exclude bool, allAudio bool, dateAdded text, idParentPath integer)");
137 CLog::Log(LOGINFO, "create files table");
138 m_pDS->exec("CREATE TABLE files ( idFile integer primary key, idPath integer, strFilename text, playCount integer, lastPlayed text, dateAdded text)");
140 CLog::Log(LOGINFO, "create tvshow table");
141 columns = "CREATE TABLE tvshow ( idShow integer primary key";
143 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
144 columns += StringUtils::Format(",c{:02} text", i);
146 columns += ", userrating integer, duration INTEGER)";
147 m_pDS->exec(columns);
149 CLog::Log(LOGINFO, "create episode table");
150 columns = "CREATE TABLE episode ( idEpisode integer primary key, idFile integer";
151 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
153 std::string column;
154 if ( i == VIDEODB_ID_EPISODE_SEASON || i == VIDEODB_ID_EPISODE_EPISODE || i == VIDEODB_ID_EPISODE_BOOKMARK)
155 column = StringUtils::Format(",c{:02} varchar(24)", i);
156 else
157 column = StringUtils::Format(",c{:02} text", i);
159 columns += column;
161 columns += ", idShow integer, userrating integer, idSeason integer)";
162 m_pDS->exec(columns);
164 CLog::Log(LOGINFO, "create tvshowlinkpath table");
165 m_pDS->exec("CREATE TABLE tvshowlinkpath (idShow integer, idPath integer)\n");
167 CLog::Log(LOGINFO, "create movielinktvshow table");
168 m_pDS->exec("CREATE TABLE movielinktvshow ( idMovie integer, IdShow integer)\n");
170 CLog::Log(LOGINFO, "create studio table");
171 m_pDS->exec("CREATE TABLE studio ( studio_id integer primary key, name TEXT)\n");
172 m_pDS->exec("CREATE TABLE studio_link (studio_id integer, media_id integer, media_type TEXT)");
174 CLog::Log(LOGINFO, "create musicvideo table");
175 columns = "CREATE TABLE musicvideo ( idMVideo integer primary key, idFile integer";
176 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
177 columns += StringUtils::Format(",c{:02} text", i);
179 columns += ", userrating integer, premiered text)";
180 m_pDS->exec(columns);
182 CLog::Log(LOGINFO, "create streaminfo table");
183 m_pDS->exec("CREATE TABLE streamdetails (idFile integer, iStreamType integer, "
184 "strVideoCodec text, fVideoAspect float, iVideoWidth integer, iVideoHeight integer, "
185 "strAudioCodec text, iAudioChannels integer, strAudioLanguage text, "
186 "strSubtitleLanguage text, iVideoDuration integer, strStereoMode text, strVideoLanguage text, "
187 "strHdrType text)");
189 CLog::Log(LOGINFO, "create sets table");
190 m_pDS->exec("CREATE TABLE sets ( idSet integer primary key, strSet text, strOverview text)");
192 CLog::Log(LOGINFO, "create seasons table");
193 m_pDS->exec("CREATE TABLE seasons ( idSeason integer primary key, idShow integer, season integer, name text, userrating integer)");
195 CLog::Log(LOGINFO, "create art table");
196 m_pDS->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
198 CLog::Log(LOGINFO, "create tag table");
199 m_pDS->exec("CREATE TABLE tag (tag_id integer primary key, name TEXT)");
200 m_pDS->exec("CREATE TABLE tag_link (tag_id integer, media_id integer, media_type TEXT)");
202 CLog::Log(LOGINFO, "create rating table");
203 m_pDS->exec("CREATE TABLE rating (rating_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, rating_type TEXT, rating FLOAT, votes INTEGER)");
205 CLog::Log(LOGINFO, "create uniqueid table");
206 m_pDS->exec("CREATE TABLE uniqueid (uniqueid_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, value TEXT, type TEXT)");
208 CLog::Log(LOGINFO, "create videoversiontype table");
209 m_pDS->exec("CREATE TABLE videoversiontype (id INTEGER PRIMARY KEY, name TEXT, owner INTEGER, "
210 "itemType INTEGER)");
211 CLog::Log(LOGINFO, "populate videoversiontype table");
212 InitializeVideoVersionTypeTable(GetSchemaVersion());
214 CLog::Log(LOGINFO, "create videoversion table");
215 m_pDS->exec("CREATE TABLE videoversion (idFile INTEGER PRIMARY KEY, idMedia INTEGER, media_type "
216 "TEXT, itemType INTEGER, idType INTEGER)");
219 void CVideoDatabase::CreateLinkIndex(const char *table)
221 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_1 ON %s (name(255))", table, table));
222 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_1 ON %s_link (%s_id, media_type(20), media_id)", table, table, table));
223 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_2 ON %s_link (media_id, media_type(20), %s_id)", table, table, table));
224 m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s_link_3 ON %s_link (media_type(20))", table, table));
227 void CVideoDatabase::CreateForeignLinkIndex(const char *table, const char *foreignkey)
229 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_1 ON %s_link (%s_id, media_type(20), media_id)", table, table, foreignkey));
230 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_2 ON %s_link (media_id, media_type(20), %s_id)", table, table, foreignkey));
231 m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s_link_3 ON %s_link (media_type(20))", table, table));
234 void CVideoDatabase::CreateAnalytics()
236 /* indexes should be added on any columns that are used in */
237 /* a where or a join. primary key on a column is the same as a */
238 /* unique index on that column, so there is no need to add any */
239 /* index if no other columns are referred */
241 /* order of indexes are important, for an index to be considered all */
242 /* columns up to the column in question have to have been specified */
243 /* select * from foolink where foo_id = 1, can not take */
244 /* advantage of a index that has been created on ( bar_id, foo_id ) */
245 /* however an index on ( foo_id, bar_id ) will be considered for use */
247 CLog::Log(LOGINFO, "{} - creating indices", __FUNCTION__);
248 m_pDS->exec("CREATE INDEX ix_bookmark ON bookmark (idFile, type)");
249 m_pDS->exec("CREATE UNIQUE INDEX ix_settings ON settings ( idFile )\n");
250 m_pDS->exec("CREATE UNIQUE INDEX ix_stacktimes ON stacktimes ( idFile )\n");
251 m_pDS->exec("CREATE INDEX ix_path ON path ( strPath(255) )");
252 m_pDS->exec("CREATE INDEX ix_path2 ON path ( idParentPath )");
253 m_pDS->exec("CREATE INDEX ix_files ON files ( idPath, strFilename(255) )");
255 m_pDS->exec("CREATE UNIQUE INDEX ix_movie_file_1 ON movie (idFile, idMovie)");
256 m_pDS->exec("CREATE UNIQUE INDEX ix_movie_file_2 ON movie (idMovie, idFile)");
258 m_pDS->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_1 ON tvshowlinkpath ( idShow, idPath )\n");
259 m_pDS->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_2 ON tvshowlinkpath ( idPath, idShow )\n");
260 m_pDS->exec("CREATE UNIQUE INDEX ix_movielinktvshow_1 ON movielinktvshow ( idShow, idMovie)\n");
261 m_pDS->exec("CREATE UNIQUE INDEX ix_movielinktvshow_2 ON movielinktvshow ( idMovie, idShow)\n");
263 m_pDS->exec("CREATE UNIQUE INDEX ix_episode_file_1 on episode (idEpisode, idFile)");
264 m_pDS->exec("CREATE UNIQUE INDEX id_episode_file_2 on episode (idFile, idEpisode)");
265 std::string createColIndex =
266 StringUtils::Format("CREATE INDEX ix_episode_season_episode on episode (c{:02}, c{:02})",
267 VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_EPISODE);
268 m_pDS->exec(createColIndex);
269 createColIndex = StringUtils::Format("CREATE INDEX ix_episode_bookmark on episode (c{:02})",
270 VIDEODB_ID_EPISODE_BOOKMARK);
271 m_pDS->exec(createColIndex);
272 m_pDS->exec("CREATE INDEX ix_episode_show1 on episode(idEpisode,idShow)");
273 m_pDS->exec("CREATE INDEX ix_episode_show2 on episode(idShow,idEpisode)");
275 m_pDS->exec("CREATE UNIQUE INDEX ix_musicvideo_file_1 on musicvideo (idMVideo, idFile)");
276 m_pDS->exec("CREATE UNIQUE INDEX ix_musicvideo_file_2 on musicvideo (idFile, idMVideo)");
278 m_pDS->exec("CREATE INDEX ixMovieBasePath ON movie ( c23(12) )");
279 m_pDS->exec("CREATE INDEX ixMusicVideoBasePath ON musicvideo ( c14(12) )");
280 m_pDS->exec("CREATE INDEX ixEpisodeBasePath ON episode ( c19(12) )");
282 m_pDS->exec("CREATE INDEX ix_streamdetails ON streamdetails (idFile)");
283 m_pDS->exec("CREATE INDEX ix_seasons ON seasons (idShow, season)");
284 m_pDS->exec("CREATE INDEX ix_art ON art(media_id, media_type(20), type(20))");
286 m_pDS->exec("CREATE INDEX ix_rating ON rating(media_id, media_type(20))");
288 m_pDS->exec("CREATE INDEX ix_uniqueid1 ON uniqueid(media_id, media_type(20), type(20))");
289 m_pDS->exec("CREATE INDEX ix_uniqueid2 ON uniqueid(media_type(20), value(20))");
291 m_pDS->exec("CREATE UNIQUE INDEX ix_actor_1 ON actor (name(255))");
292 m_pDS->exec("CREATE UNIQUE INDEX ix_actor_link_1 ON "
293 "actor_link (actor_id, media_type(20), media_id, role(255))");
294 m_pDS->exec("CREATE INDEX ix_actor_link_2 ON "
295 "actor_link (media_id, media_type(20), actor_id)");
296 m_pDS->exec("CREATE INDEX ix_actor_link_3 ON actor_link (media_type(20))");
298 m_pDS->exec("CREATE INDEX ix_videoversion ON videoversion (idMedia, media_type(20))");
300 m_pDS->exec(PrepareSQL("CREATE INDEX ix_movie_title ON movie (c%02d(255))", VIDEODB_ID_TITLE));
302 CreateLinkIndex("tag");
303 CreateForeignLinkIndex("director", "actor");
304 CreateForeignLinkIndex("writer", "actor");
305 CreateLinkIndex("studio");
306 CreateLinkIndex("genre");
307 CreateLinkIndex("country");
309 CLog::Log(LOGINFO, "{} - creating triggers", __FUNCTION__);
310 m_pDS->exec("CREATE TRIGGER delete_movie AFTER DELETE ON movie FOR EACH ROW BEGIN "
311 "DELETE FROM genre_link WHERE media_id=old.idMovie AND media_type='movie'; "
312 "DELETE FROM actor_link WHERE media_id=old.idMovie AND media_type='movie'; "
313 "DELETE FROM director_link WHERE media_id=old.idMovie AND media_type='movie'; "
314 "DELETE FROM studio_link WHERE media_id=old.idMovie AND media_type='movie'; "
315 "DELETE FROM country_link WHERE media_id=old.idMovie AND media_type='movie'; "
316 "DELETE FROM writer_link WHERE media_id=old.idMovie AND media_type='movie'; "
317 "DELETE FROM movielinktvshow WHERE idMovie=old.idMovie; "
318 "DELETE FROM art WHERE media_id=old.idMovie AND media_type='movie'; "
319 "DELETE FROM tag_link WHERE media_id=old.idMovie AND media_type='movie'; "
320 "DELETE FROM rating WHERE media_id=old.idMovie AND media_type='movie'; "
321 "DELETE FROM uniqueid WHERE media_id=old.idMovie AND media_type='movie'; "
322 "DELETE FROM videoversion "
323 "WHERE idFile=old.idFile AND idMedia=old.idMovie AND media_type='movie'; "
324 "END");
325 m_pDS->exec("CREATE TRIGGER delete_tvshow AFTER DELETE ON tvshow FOR EACH ROW BEGIN "
326 "DELETE FROM actor_link WHERE media_id=old.idShow AND media_type='tvshow'; "
327 "DELETE FROM director_link WHERE media_id=old.idShow AND media_type='tvshow'; "
328 "DELETE FROM studio_link WHERE media_id=old.idShow AND media_type='tvshow'; "
329 "DELETE FROM tvshowlinkpath WHERE idShow=old.idShow; "
330 "DELETE FROM genre_link WHERE media_id=old.idShow AND media_type='tvshow'; "
331 "DELETE FROM movielinktvshow WHERE idShow=old.idShow; "
332 "DELETE FROM seasons WHERE idShow=old.idShow; "
333 "DELETE FROM art WHERE media_id=old.idShow AND media_type='tvshow'; "
334 "DELETE FROM tag_link WHERE media_id=old.idShow AND media_type='tvshow'; "
335 "DELETE FROM rating WHERE media_id=old.idShow AND media_type='tvshow'; "
336 "DELETE FROM uniqueid WHERE media_id=old.idShow AND media_type='tvshow'; "
337 "END");
338 m_pDS->exec("CREATE TRIGGER delete_musicvideo AFTER DELETE ON musicvideo FOR EACH ROW BEGIN "
339 "DELETE FROM actor_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
340 "DELETE FROM director_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
341 "DELETE FROM genre_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
342 "DELETE FROM studio_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
343 "DELETE FROM art WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
344 "DELETE FROM tag_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
345 "DELETE FROM uniqueid WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
346 "END");
347 m_pDS->exec("CREATE TRIGGER delete_episode AFTER DELETE ON episode FOR EACH ROW BEGIN "
348 "DELETE FROM actor_link WHERE media_id=old.idEpisode AND media_type='episode'; "
349 "DELETE FROM director_link WHERE media_id=old.idEpisode AND media_type='episode'; "
350 "DELETE FROM writer_link WHERE media_id=old.idEpisode AND media_type='episode'; "
351 "DELETE FROM art WHERE media_id=old.idEpisode AND media_type='episode'; "
352 "DELETE FROM rating WHERE media_id=old.idEpisode AND media_type='episode'; "
353 "DELETE FROM uniqueid WHERE media_id=old.idEpisode AND media_type='episode'; "
354 "END");
355 m_pDS->exec("CREATE TRIGGER delete_season AFTER DELETE ON seasons FOR EACH ROW BEGIN "
356 "DELETE FROM art WHERE media_id=old.idSeason AND media_type='season'; "
357 "END");
358 m_pDS->exec("CREATE TRIGGER delete_set AFTER DELETE ON sets FOR EACH ROW BEGIN "
359 "DELETE FROM art WHERE media_id=old.idSet AND media_type='set'; "
360 "END");
361 m_pDS->exec("CREATE TRIGGER delete_person AFTER DELETE ON actor FOR EACH ROW BEGIN "
362 "DELETE FROM art WHERE media_id=old.actor_id AND media_type IN ('actor','artist','writer','director'); "
363 "END");
364 m_pDS->exec("CREATE TRIGGER delete_tag AFTER DELETE ON tag_link FOR EACH ROW BEGIN "
365 "DELETE FROM tag WHERE tag_id=old.tag_id AND tag_id NOT IN (SELECT DISTINCT tag_id FROM tag_link); "
366 "END");
367 m_pDS->exec("CREATE TRIGGER delete_file AFTER DELETE ON files FOR EACH ROW BEGIN "
368 "DELETE FROM bookmark WHERE idFile=old.idFile; "
369 "DELETE FROM settings WHERE idFile=old.idFile; "
370 "DELETE FROM stacktimes WHERE idFile=old.idFile; "
371 "DELETE FROM streamdetails WHERE idFile=old.idFile; "
372 "DELETE FROM videoversion WHERE idFile=old.idFile; "
373 "DELETE FROM art WHERE media_id=old.idFile AND media_type='videoversion'; "
374 "END");
375 m_pDS->exec("CREATE TRIGGER delete_videoversion AFTER DELETE ON videoversion FOR EACH ROW BEGIN "
376 "DELETE FROM art WHERE media_id=old.idFile AND media_type='videoversion'; "
377 "DELETE FROM streamdetails WHERE idFile=old.idFile; "
378 "END");
380 CreateViews();
383 void CVideoDatabase::CreateViews()
385 CLog::Log(LOGINFO, "create episode_view");
386 std::string episodeview = PrepareSQL(
387 "CREATE VIEW episode_view AS SELECT "
388 " episode.*,"
389 " files.strFileName AS strFileName,"
390 " path.strPath AS strPath,"
391 " files.playCount AS playCount,"
392 " files.lastPlayed AS lastPlayed,"
393 " files.dateAdded AS dateAdded,"
394 " tvshow.c%02d AS strTitle,"
395 " tvshow.c%02d AS genre,"
396 " tvshow.c%02d AS studio,"
397 " tvshow.c%02d AS premiered,"
398 " tvshow.c%02d AS mpaa,"
399 " bookmark.timeInSeconds AS resumeTimeInSeconds, "
400 " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
401 " bookmark.playerState AS playerState, "
402 " rating.rating AS rating, "
403 " rating.votes AS votes, "
404 " rating.rating_type AS rating_type, "
405 " uniqueid.value AS uniqueid_value, "
406 " uniqueid.type AS uniqueid_type "
407 "FROM episode"
408 " JOIN files ON"
409 " files.idFile=episode.idFile"
410 " JOIN tvshow ON"
411 " tvshow.idShow=episode.idShow"
412 " JOIN path ON"
413 " files.idPath=path.idPath"
414 " LEFT JOIN bookmark ON"
415 " bookmark.idFile=episode.idFile AND bookmark.type=1"
416 " LEFT JOIN rating ON"
417 " rating.rating_id=episode.c%02d"
418 " LEFT JOIN uniqueid ON"
419 " uniqueid.uniqueid_id=episode.c%02d",
420 VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_GENRE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_PREMIERED,
421 VIDEODB_ID_TV_MPAA, VIDEODB_ID_EPISODE_RATING_ID, VIDEODB_ID_EPISODE_IDENT_ID);
422 m_pDS->exec(episodeview);
424 CLog::Log(LOGINFO, "create tvshowcounts");
425 // clang-format off
426 std::string tvshowcounts = PrepareSQL("CREATE VIEW tvshowcounts AS SELECT "
427 " tvshow.idShow AS idShow,"
428 " MAX(files.lastPlayed) AS lastPlayed,"
429 " NULLIF(COUNT(episode.c12), 0) AS totalCount,"
430 " COUNT(files.playCount) AS watchedcount,"
431 " NULLIF(COUNT(DISTINCT(episode.c12)), 0) AS totalSeasons, "
432 " MAX(files.dateAdded) as dateAdded, "
433 " COUNT(bookmark.type) AS inProgressCount "
434 " FROM tvshow"
435 " LEFT JOIN episode ON"
436 " episode.idShow=tvshow.idShow"
437 " LEFT JOIN files ON"
438 " files.idFile=episode.idFile "
439 " LEFT JOIN bookmark ON"
440 " bookmark.idFile=files.idFile AND bookmark.type=1 "
441 "GROUP BY tvshow.idShow");
442 // clang-format on
443 m_pDS->exec(tvshowcounts);
445 CLog::Log(LOGINFO, "create tvshowlinkpath_minview");
446 // This view only exists to workaround a limitation in MySQL <5.7 which is not able to
447 // perform subqueries in joins.
448 // Also, the correct solution is to remove the path information altogether, since a
449 // TV series can always have multiple paths. It is used in the GUI at the moment, but
450 // such usage should be removed together with this view and the path columns in tvshow_view.
451 //!@todo Remove the hacky selection of a semi-random path for tvshows from the queries and UI
452 std::string tvshowlinkpathview = PrepareSQL("CREATE VIEW tvshowlinkpath_minview AS SELECT "
453 " idShow, "
454 " min(idPath) AS idPath "
455 "FROM tvshowlinkpath "
456 "GROUP BY idShow");
457 m_pDS->exec(tvshowlinkpathview);
459 CLog::Log(LOGINFO, "create tvshow_view");
460 // clang-format off
461 std::string tvshowview = PrepareSQL("CREATE VIEW tvshow_view AS SELECT "
462 " tvshow.*,"
463 " path.idParentPath AS idParentPath,"
464 " path.strPath AS strPath,"
465 " tvshowcounts.dateAdded AS dateAdded,"
466 " lastPlayed, totalCount, watchedcount, totalSeasons, "
467 " rating.rating AS rating, "
468 " rating.votes AS votes, "
469 " rating.rating_type AS rating_type, "
470 " uniqueid.value AS uniqueid_value, "
471 " uniqueid.type AS uniqueid_type, "
472 " tvshowcounts.inProgressCount AS inProgressCount "
473 "FROM tvshow"
474 " LEFT JOIN tvshowlinkpath_minview ON "
475 " tvshowlinkpath_minview.idShow=tvshow.idShow"
476 " LEFT JOIN path ON"
477 " path.idPath=tvshowlinkpath_minview.idPath"
478 " INNER JOIN tvshowcounts ON"
479 " tvshow.idShow = tvshowcounts.idShow "
480 " LEFT JOIN rating ON"
481 " rating.rating_id=tvshow.c%02d "
482 " LEFT JOIN uniqueid ON"
483 " uniqueid.uniqueid_id=tvshow.c%02d ",
484 VIDEODB_ID_TV_RATING_ID, VIDEODB_ID_TV_IDENT_ID);
485 // clang-format on
486 m_pDS->exec(tvshowview);
488 CLog::Log(LOGINFO, "create season_view");
489 // clang-format off
490 std::string seasonview = PrepareSQL("CREATE VIEW season_view AS SELECT "
491 " seasons.idSeason AS idSeason,"
492 " seasons.idShow AS idShow,"
493 " seasons.season AS season,"
494 " seasons.name AS name,"
495 " seasons.userrating AS userrating,"
496 " tvshow_view.strPath AS strPath,"
497 " tvshow_view.c%02d AS showTitle,"
498 " tvshow_view.c%02d AS plot,"
499 " tvshow_view.c%02d AS premiered,"
500 " tvshow_view.c%02d AS genre,"
501 " tvshow_view.c%02d AS studio,"
502 " tvshow_view.c%02d AS mpaa,"
503 " count(DISTINCT episode.idEpisode) AS episodes,"
504 " count(files.playCount) AS playCount,"
505 " min(episode.c%02d) AS aired, "
506 " count(bookmark.type) AS inProgressCount "
507 "FROM seasons"
508 " JOIN tvshow_view ON"
509 " tvshow_view.idShow = seasons.idShow"
510 " JOIN episode ON"
511 " episode.idShow = seasons.idShow AND episode.c%02d = seasons.season"
512 " JOIN files ON"
513 " files.idFile = episode.idFile "
514 " LEFT JOIN bookmark ON"
515 " bookmark.idFile = files.idFile AND bookmark.type = 1 "
516 "GROUP BY seasons.idSeason,"
517 " seasons.idShow,"
518 " seasons.season,"
519 " seasons.name,"
520 " seasons.userrating,"
521 " tvshow_view.strPath,"
522 " tvshow_view.c%02d,"
523 " tvshow_view.c%02d,"
524 " tvshow_view.c%02d,"
525 " tvshow_view.c%02d,"
526 " tvshow_view.c%02d,"
527 " tvshow_view.c%02d ",
528 VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_PLOT, VIDEODB_ID_TV_PREMIERED,
529 VIDEODB_ID_TV_GENRE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_MPAA,
530 VIDEODB_ID_EPISODE_AIRED, VIDEODB_ID_EPISODE_SEASON,
531 VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_PLOT, VIDEODB_ID_TV_PREMIERED,
532 VIDEODB_ID_TV_GENRE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_MPAA);
533 // clang-format on
534 m_pDS->exec(seasonview);
536 CLog::Log(LOGINFO, "create musicvideo_view");
537 m_pDS->exec(PrepareSQL(
538 "CREATE VIEW musicvideo_view AS SELECT"
539 " musicvideo.*,"
540 " files.strFileName as strFileName,"
541 " path.strPath as strPath,"
542 " files.playCount as playCount,"
543 " files.lastPlayed as lastPlayed,"
544 " files.dateAdded as dateAdded, "
545 " bookmark.timeInSeconds AS resumeTimeInSeconds, "
546 " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
547 " bookmark.playerState AS playerState, "
548 " uniqueid.value AS uniqueid_value, "
549 " uniqueid.type AS uniqueid_type "
550 "FROM musicvideo"
551 " JOIN files ON"
552 " files.idFile=musicvideo.idFile"
553 " JOIN path ON"
554 " path.idPath=files.idPath"
555 " LEFT JOIN bookmark ON"
556 " bookmark.idFile=musicvideo.idFile AND bookmark.type=1"
557 " LEFT JOIN uniqueid ON"
558 " uniqueid.uniqueid_id=musicvideo.c%02d",
559 VIDEODB_ID_MUSICVIDEO_IDENT_ID));
561 CLog::Log(LOGINFO, "create movie_view");
563 std::string movieview =
564 PrepareSQL("CREATE VIEW movie_view AS SELECT"
565 " movie.*,"
566 " sets.strSet AS strSet,"
567 " sets.strOverview AS strSetOverview,"
568 " files.strFileName AS strFileName,"
569 " path.strPath AS strPath,"
570 " files.playCount AS playCount,"
571 " files.lastPlayed AS lastPlayed, "
572 " files.dateAdded AS dateAdded, "
573 " bookmark.timeInSeconds AS resumeTimeInSeconds, "
574 " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
575 " bookmark.playerState AS playerState, "
576 " rating.rating AS rating, "
577 " rating.votes AS votes, "
578 " rating.rating_type AS rating_type, "
579 " uniqueid.value AS uniqueid_value, "
580 " uniqueid.type AS uniqueid_type, "
581 " EXISTS( "
582 " SELECT 1 "
583 " FROM videoversion vv "
584 " WHERE vv.idMedia = movie.idMovie "
585 " AND vv.media_type = '%s' "
586 " AND vv.itemType = %i "
587 " AND vv.idFile <> movie.idFile "
588 " ) AS hasVideoVersions, "
589 " EXISTS( "
590 " SELECT 1 "
591 " FROM videoversion vv "
592 " WHERE vv.idMedia = movie.idMovie "
593 " AND vv.media_type = '%s' "
594 " AND vv.itemType = %i "
595 " ) AS hasVideoExtras, "
596 " CASE "
597 " WHEN vv.idFile = movie.idFile AND vv.itemType = %i THEN 1 "
598 " ELSE 0 "
599 " END AS isDefaultVersion, "
600 " vv.idFile AS videoVersionIdFile, "
601 " vvt.id AS videoVersionTypeId,"
602 " vvt.name AS videoVersionTypeName,"
603 " vvt.itemType AS videoVersionTypeItemType "
604 "FROM movie"
605 " LEFT JOIN sets ON"
606 " sets.idSet = movie.idSet"
607 " LEFT JOIN rating ON"
608 " rating.rating_id = movie.c%02d"
609 " LEFT JOIN uniqueid ON"
610 " uniqueid.uniqueid_id = movie.c%02d"
611 " LEFT JOIN videoversion vv ON"
612 " vv.idMedia = movie.idMovie AND vv.media_type = '%s' AND vv.itemType = %i"
613 " JOIN videoversiontype vvt ON"
614 " vvt.id = vv.idType AND vvt.itemType = vv.itemType"
615 " JOIN files ON"
616 " files.idFile = vv.idFile"
617 " JOIN path ON"
618 " path.idPath = files.idPath"
619 " LEFT JOIN bookmark ON"
620 " bookmark.idFile = vv.idFile AND bookmark.type = 1",
621 MediaTypeMovie, VideoAssetType::VERSION, MediaTypeMovie, VideoAssetType::EXTRA,
622 VideoAssetType::VERSION, VIDEODB_ID_RATING_ID, VIDEODB_ID_IDENT_ID, MediaTypeMovie,
623 VideoAssetType::VERSION);
624 m_pDS->exec(movieview);
627 //********************************************************************************************************************************
628 int CVideoDatabase::GetPathId(const std::string& strPath)
630 std::string strSQL;
633 int idPath=-1;
634 if (nullptr == m_pDB)
635 return -1;
636 if (nullptr == m_pDS)
637 return -1;
639 std::string strPath1(strPath);
640 if (URIUtils::IsStack(strPath) || StringUtils::StartsWithNoCase(strPath, "rar://") || StringUtils::StartsWithNoCase(strPath, "zip://"))
641 URIUtils::GetParentPath(strPath,strPath1);
643 URIUtils::AddSlashAtEnd(strPath1);
645 strSQL=PrepareSQL("select idPath from path where strPath='%s'",strPath1.c_str());
646 m_pDS->query(strSQL);
647 if (!m_pDS->eof())
648 idPath = m_pDS->fv("path.idPath").get_asInt();
650 m_pDS->close();
651 return idPath;
653 catch (...)
655 CLog::Log(LOGERROR, "{} unable to getpath ({})", __FUNCTION__, strSQL);
657 return -1;
660 bool CVideoDatabase::GetPaths(std::set<std::string> &paths)
664 if (nullptr == m_pDB)
665 return false;
666 if (nullptr == m_pDS)
667 return false;
669 paths.clear();
671 // grab all paths with movie content set
672 if (!m_pDS->query("select strPath,noUpdate from path"
673 " where (strContent = 'movies' or strContent = 'musicvideos')"
674 " and strPath NOT like 'multipath://%%'"
675 " order by strPath"))
676 return false;
678 while (!m_pDS->eof())
680 if (!m_pDS->fv("noUpdate").get_asBool())
681 paths.insert(m_pDS->fv("strPath").get_asString());
682 m_pDS->next();
684 m_pDS->close();
686 // then grab all tvshow paths
687 if (!m_pDS->query("select strPath,noUpdate from path"
688 " where ( strContent = 'tvshows'"
689 " or idPath in (select idPath from tvshowlinkpath))"
690 " and strPath NOT like 'multipath://%%'"
691 " order by strPath"))
692 return false;
694 while (!m_pDS->eof())
696 if (!m_pDS->fv("noUpdate").get_asBool())
697 paths.insert(m_pDS->fv("strPath").get_asString());
698 m_pDS->next();
700 m_pDS->close();
702 // finally grab all other paths holding a movie which is not a stack or a rar archive
703 // - this isnt perfect but it should do fine in most situations.
704 // reason we need it to hold a movie is stacks from different directories (cdx folders for instance)
705 // not making mistakes must take priority
706 if (!m_pDS->query("select strPath,noUpdate from path"
707 " where idPath in (select idPath from files join movie on movie.idFile=files.idFile)"
708 " and idPath NOT in (select idPath from tvshowlinkpath)"
709 " and idPath NOT in (select idPath from files where strFileName like 'video_ts.ifo')" // dvd folders get stacked to a single item in parent folder
710 " and idPath NOT in (select idPath from files where strFileName like 'index.bdmv')" // bluray folders get stacked to a single item in parent folder
711 " and strPath NOT like 'multipath://%%'"
712 " and strContent NOT in ('movies', 'tvshows', 'None')" // these have been added above
713 " order by strPath"))
715 return false;
716 while (!m_pDS->eof())
718 if (!m_pDS->fv("noUpdate").get_asBool())
719 paths.insert(m_pDS->fv("strPath").get_asString());
720 m_pDS->next();
722 m_pDS->close();
723 return true;
725 catch (...)
727 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
729 return false;
732 bool CVideoDatabase::GetPathsLinkedToTvShow(int idShow, std::vector<std::string> &paths)
734 std::string sql;
737 sql = PrepareSQL("SELECT strPath FROM path JOIN tvshowlinkpath ON tvshowlinkpath.idPath=path.idPath WHERE idShow=%i", idShow);
738 m_pDS->query(sql);
739 while (!m_pDS->eof())
741 paths.emplace_back(m_pDS->fv(0).get_asString());
742 m_pDS->next();
744 return true;
746 catch (...)
748 CLog::Log(LOGERROR, "{} error during query: {}", __FUNCTION__, sql);
750 return false;
753 bool CVideoDatabase::GetPathsForTvShow(int idShow, std::set<int>& paths)
755 std::string strSQL;
758 if (nullptr == m_pDB)
759 return false;
760 if (nullptr == m_pDS)
761 return false;
763 // add base path
764 strSQL = PrepareSQL("SELECT strPath FROM tvshow_view WHERE idShow=%i", idShow);
765 if (m_pDS->query(strSQL))
766 paths.insert(GetPathId(m_pDS->fv(0).get_asString()));
768 // add all other known paths
769 strSQL = PrepareSQL("SELECT DISTINCT idPath FROM files JOIN episode ON episode.idFile=files.idFile WHERE episode.idShow=%i",idShow);
770 m_pDS->query(strSQL);
771 while (!m_pDS->eof())
773 paths.insert(m_pDS->fv(0).get_asInt());
774 m_pDS->next();
776 m_pDS->close();
777 return true;
779 catch (...)
781 CLog::Log(LOGERROR, "{} error during query: {}", __FUNCTION__, strSQL);
783 return false;
786 int CVideoDatabase::RunQuery(const std::string &sql)
788 auto start = std::chrono::steady_clock::now();
790 int rows = -1;
791 if (m_pDS->query(sql))
793 rows = m_pDS->num_rows();
794 if (rows == 0)
795 m_pDS->close();
798 auto end = std::chrono::steady_clock::now();
799 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
801 CLog::Log(LOGDEBUG, LOGDATABASE, "{} took {} ms for {} items query: {}", __FUNCTION__,
802 duration.count(), rows, sql);
804 return rows;
807 bool CVideoDatabase::GetSubPaths(const std::string &basepath, std::vector<std::pair<int, std::string>>& subpaths)
809 std::string sql;
812 if (!m_pDB || !m_pDS)
813 return false;
815 std::string path(basepath);
816 URIUtils::AddSlashAtEnd(path);
817 sql = PrepareSQL("SELECT idPath,strPath FROM path WHERE SUBSTR(strPath,1,%i)='%s'"
818 " AND idPath NOT IN (SELECT idPath FROM files WHERE strFileName LIKE 'video_ts.ifo')"
819 " AND idPath NOT IN (SELECT idPath FROM files WHERE strFileName LIKE 'index.bdmv')"
820 , StringUtils::utf8_strlen(path.c_str()), path.c_str());
822 m_pDS->query(sql);
823 while (!m_pDS->eof())
825 subpaths.emplace_back(m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asString());
826 m_pDS->next();
828 m_pDS->close();
829 return true;
831 catch (...)
833 CLog::Log(LOGERROR, "{} error during query: {}", __FUNCTION__, sql);
835 return false;
838 int CVideoDatabase::AddPath(const std::string& strPath, const std::string &parentPath /*= "" */, const CDateTime& dateAdded /* = CDateTime() */)
840 std::string strSQL;
843 int idPath = GetPathId(strPath);
844 if (idPath >= 0)
845 return idPath; // already have the path
847 if (nullptr == m_pDB)
848 return -1;
849 if (nullptr == m_pDS)
850 return -1;
852 std::string strPath1(strPath);
853 if (URIUtils::IsStack(strPath) || StringUtils::StartsWithNoCase(strPath, "rar://") || StringUtils::StartsWithNoCase(strPath, "zip://"))
854 URIUtils::GetParentPath(strPath,strPath1);
856 URIUtils::AddSlashAtEnd(strPath1);
858 int idParentPath = GetPathId(parentPath.empty() ? URIUtils::GetParentPath(strPath1) : parentPath);
860 // add the path
861 if (idParentPath < 0)
863 if (dateAdded.IsValid())
864 strSQL=PrepareSQL("insert into path (idPath, strPath, dateAdded) values (NULL, '%s', '%s')", strPath1.c_str(), dateAdded.GetAsDBDateTime().c_str());
865 else
866 strSQL=PrepareSQL("insert into path (idPath, strPath) values (NULL, '%s')", strPath1.c_str());
868 else
870 if (dateAdded.IsValid())
871 strSQL = PrepareSQL("insert into path (idPath, strPath, dateAdded, idParentPath) values (NULL, '%s', '%s', %i)", strPath1.c_str(), dateAdded.GetAsDBDateTime().c_str(), idParentPath);
872 else
873 strSQL=PrepareSQL("insert into path (idPath, strPath, idParentPath) values (NULL, '%s', %i)", strPath1.c_str(), idParentPath);
875 m_pDS->exec(strSQL);
876 idPath = (int)m_pDS->lastinsertid();
877 return idPath;
879 catch (...)
881 CLog::Log(LOGERROR, "{} unable to addpath ({})", __FUNCTION__, strSQL);
883 return -1;
886 bool CVideoDatabase::GetPathHash(const std::string &path, std::string &hash)
890 if (nullptr == m_pDB)
891 return false;
892 if (nullptr == m_pDS)
893 return false;
895 std::string strSQL=PrepareSQL("select strHash from path where strPath='%s'", path.c_str());
896 m_pDS->query(strSQL);
897 if (m_pDS->num_rows() == 0)
898 return false;
899 hash = m_pDS->fv("strHash").get_asString();
900 return true;
902 catch (...)
904 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, path);
907 return false;
910 bool CVideoDatabase::GetSourcePath(const std::string &path, std::string &sourcePath)
912 SScanSettings dummy;
913 return GetSourcePath(path, sourcePath, dummy);
916 bool CVideoDatabase::GetSourcePath(const std::string &path, std::string &sourcePath, SScanSettings& settings)
920 if (path.empty() || m_pDB == nullptr || m_pDS == nullptr)
921 return false;
923 std::string strPath2;
925 if (URIUtils::IsMultiPath(path))
926 strPath2 = CMultiPathDirectory::GetFirstPath(path);
927 else
928 strPath2 = path;
930 std::string strPath1 = URIUtils::GetDirectory(strPath2);
931 int idPath = GetPathId(strPath1);
933 if (idPath > -1)
935 // check if the given path already is a source itself
936 std::string strSQL = PrepareSQL("SELECT path.useFolderNames, path.scanRecursive, path.noUpdate, path.exclude FROM path WHERE "
937 "path.idPath = %i AND "
938 "path.strContent IS NOT NULL AND path.strContent != '' AND "
939 "path.strScraper IS NOT NULL AND path.strScraper != ''", idPath);
940 if (m_pDS->query(strSQL) && !m_pDS->eof())
942 settings.parent_name_root = settings.parent_name = m_pDS->fv(0).get_asBool();
943 settings.recurse = m_pDS->fv(1).get_asInt();
944 settings.noupdate = m_pDS->fv(2).get_asBool();
945 settings.exclude = m_pDS->fv(3).get_asBool();
947 m_pDS->close();
948 sourcePath = path;
949 return true;
953 // look for parent paths until there is one which is a source
954 std::string strParent;
955 bool found = false;
956 while (URIUtils::GetParentPath(strPath1, strParent))
958 std::string strSQL = PrepareSQL("SELECT path.strContent, path.strScraper, path.scanRecursive, path.useFolderNames, path.noUpdate, path.exclude FROM path WHERE strPath = '%s'", strParent.c_str());
959 if (m_pDS->query(strSQL) && !m_pDS->eof())
961 std::string strContent = m_pDS->fv(0).get_asString();
962 std::string strScraper = m_pDS->fv(1).get_asString();
963 if (!strContent.empty() && !strScraper.empty())
965 settings.parent_name_root = settings.parent_name = m_pDS->fv(2).get_asBool();
966 settings.recurse = m_pDS->fv(3).get_asInt();
967 settings.noupdate = m_pDS->fv(4).get_asBool();
968 settings.exclude = m_pDS->fv(5).get_asBool();
969 found = true;
970 break;
974 strPath1 = strParent;
976 m_pDS->close();
978 if (found)
980 sourcePath = strParent;
981 return true;
984 catch (...)
986 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
988 return false;
991 //********************************************************************************************************************************
992 int CVideoDatabase::AddFile(const std::string& strFileNameAndPath,
993 const std::string& parentPath /* = "" */,
994 const CDateTime& dateAdded /* = CDateTime() */,
995 int playcount /* = 0 */,
996 const CDateTime& lastPlayed /* = CDateTime() */)
998 std::string strSQL = "";
1001 int idFile;
1002 if (nullptr == m_pDB)
1003 return -1;
1004 if (nullptr == m_pDS)
1005 return -1;
1007 const auto finalDateAdded = GetDateAdded(strFileNameAndPath, dateAdded);
1009 std::string strFileName, strPath;
1010 SplitPath(strFileNameAndPath,strPath,strFileName);
1012 int idPath = AddPath(strPath, parentPath, finalDateAdded);
1013 if (idPath < 0)
1014 return -1;
1016 std::string strSQL=PrepareSQL("select idFile from files where strFileName='%s' and idPath=%i", strFileName.c_str(),idPath);
1018 m_pDS->query(strSQL);
1019 if (m_pDS->num_rows() > 0)
1021 idFile = m_pDS->fv("idFile").get_asInt() ;
1022 m_pDS->close();
1023 return idFile;
1025 m_pDS->close();
1027 std::string strPlaycount = "NULL";
1028 if (playcount > 0)
1029 strPlaycount = std::to_string(playcount);
1030 std::string strLastPlayed = "NULL";
1031 if (lastPlayed.IsValid())
1032 strLastPlayed = "'" + lastPlayed.GetAsDBDateTime() + "'";
1034 strSQL = PrepareSQL("INSERT INTO files (idFile, idPath, strFileName, playCount, lastPlayed, dateAdded) "
1035 "VALUES(NULL, %i, '%s', " + strPlaycount + ", " + strLastPlayed + ", '%s')",
1036 idPath, strFileName.c_str(), finalDateAdded.GetAsDBDateTime().c_str());
1037 m_pDS->exec(strSQL);
1038 idFile = (int)m_pDS->lastinsertid();
1039 return idFile;
1041 catch (...)
1043 CLog::Log(LOGERROR, "{} unable to addfile ({})", __FUNCTION__, strSQL);
1045 return -1;
1048 int CVideoDatabase::AddFile(const CFileItem& item)
1050 if (IsVideoDb(item) && item.HasVideoInfoTag())
1052 const auto videoInfoTag = item.GetVideoInfoTag();
1053 if (videoInfoTag->m_iFileId != -1)
1054 return videoInfoTag->m_iFileId;
1055 else
1056 return AddFile(*videoInfoTag);
1058 return AddFile(item.GetPath());
1061 int CVideoDatabase::AddFile(const CVideoInfoTag& details, const std::string& parentPath /* = "" */)
1063 return AddFile(details.GetPath(), parentPath, details.m_dateAdded, details.GetPlayCount(),
1064 details.m_lastPlayed);
1067 void CVideoDatabase::UpdateFileDateAdded(CVideoInfoTag& details)
1069 if (details.GetPath().empty() || GetAndFillFileId(details) <= 0)
1070 return;
1072 CDateTime finalDateAdded;
1075 if (nullptr == m_pDB)
1076 return;
1077 if (nullptr == m_pDS)
1078 return;
1080 finalDateAdded = GetDateAdded(details.GetPath(), details.m_dateAdded);
1082 m_pDS->exec(PrepareSQL("UPDATE files SET dateAdded='%s' WHERE idFile=%d",
1083 finalDateAdded.GetAsDBDateTime().c_str(), details.m_iFileId));
1085 catch (...)
1087 CLog::Log(LOGERROR, "{}({}, {}) failed", __FUNCTION__, CURL::GetRedacted(details.GetPath()),
1088 finalDateAdded.GetAsDBDateTime());
1092 bool CVideoDatabase::SetPathHash(const std::string &path, const std::string &hash)
1096 if (nullptr == m_pDB)
1097 return false;
1098 if (nullptr == m_pDS)
1099 return false;
1101 int idPath = AddPath(path);
1102 if (idPath < 0) return false;
1104 std::string strSQL=PrepareSQL("update path set strHash='%s' where idPath=%ld", hash.c_str(), idPath);
1105 m_pDS->exec(strSQL);
1107 return true;
1109 catch (...)
1111 CLog::Log(LOGERROR, "{} ({}, {}) failed", __FUNCTION__, path, hash);
1114 return false;
1117 bool CVideoDatabase::LinkMovieToTvshow(int idMovie, int idShow, bool bRemove)
1121 if (nullptr == m_pDB)
1122 return false;
1123 if (nullptr == m_pDS)
1124 return false;
1126 if (bRemove) // delete link
1128 std::string strSQL=PrepareSQL("delete from movielinktvshow where idMovie=%i and idShow=%i", idMovie, idShow);
1129 m_pDS->exec(strSQL);
1130 return true;
1133 std::string strSQL=PrepareSQL("insert into movielinktvshow (idShow,idMovie) values (%i,%i)", idShow,idMovie);
1134 m_pDS->exec(strSQL);
1136 return true;
1138 catch (...)
1140 CLog::Log(LOGERROR, "{} ({}, {}) failed", __FUNCTION__, idMovie, idShow);
1143 return false;
1146 bool CVideoDatabase::IsLinkedToTvshow(int idMovie)
1150 if (nullptr == m_pDB)
1151 return false;
1152 if (nullptr == m_pDS)
1153 return false;
1155 std::string strSQL=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie);
1156 m_pDS->query(strSQL);
1157 if (m_pDS->eof())
1159 m_pDS->close();
1160 return false;
1163 m_pDS->close();
1164 return true;
1166 catch (...)
1168 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idMovie);
1171 return false;
1174 bool CVideoDatabase::GetLinksToTvShow(int idMovie, std::vector<int>& ids)
1178 if (nullptr == m_pDB)
1179 return false;
1180 if (nullptr == m_pDS)
1181 return false;
1183 std::string strSQL=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie);
1184 m_pDS2->query(strSQL);
1185 while (!m_pDS2->eof())
1187 ids.push_back(m_pDS2->fv(1).get_asInt());
1188 m_pDS2->next();
1191 m_pDS2->close();
1192 return true;
1194 catch (...)
1196 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idMovie);
1199 return false;
1203 //********************************************************************************************************************************
1204 int CVideoDatabase::GetFileId(const std::string& strFilenameAndPath)
1208 if (nullptr == m_pDB)
1209 return -1;
1210 if (nullptr == m_pDS)
1211 return -1;
1212 std::string strPath, strFileName;
1213 SplitPath(strFilenameAndPath,strPath,strFileName);
1215 int idPath = GetPathId(strPath);
1216 if (idPath >= 0)
1218 std::string strSQL;
1219 strSQL=PrepareSQL("select idFile from files where strFileName='%s' and idPath=%i", strFileName.c_str(),idPath);
1220 m_pDS->query(strSQL);
1221 if (m_pDS->num_rows() > 0)
1223 int idFile = m_pDS->fv("files.idFile").get_asInt();
1224 m_pDS->close();
1225 return idFile;
1229 catch (...)
1231 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
1233 return -1;
1236 int CVideoDatabase::GetFileId(const CFileItem &item)
1238 int fileId = -1;
1239 if (item.HasVideoInfoTag())
1240 fileId = GetFileId(*item.GetVideoInfoTag());
1242 if (fileId == -1)
1243 fileId = GetFileId(item.GetPath());
1245 return fileId;
1248 int CVideoDatabase::GetFileId(const CVideoInfoTag& details)
1250 if (details.m_iFileId > 0)
1251 return details.m_iFileId;
1253 const auto& filePath = details.GetPath();
1254 if (filePath.empty())
1255 return -1;
1257 return GetFileId(filePath);
1260 int CVideoDatabase::GetAndFillFileId(CVideoInfoTag& details)
1262 details.m_iFileId = GetFileId(details);
1263 return details.m_iFileId;
1266 //********************************************************************************************************************************
1267 int CVideoDatabase::GetMovieId(const std::string& strFilenameAndPath)
1271 if (nullptr == m_pDB)
1272 return -1;
1273 if (nullptr == m_pDS)
1274 return -1;
1275 int idMovie = -1;
1277 // needed for query parameters
1278 int idFile = GetFileId(strFilenameAndPath);
1279 int idPath=-1;
1280 std::string strPath;
1281 if (idFile < 0)
1283 std::string strFile;
1284 SplitPath(strFilenameAndPath,strPath,strFile);
1286 // have to join movieinfo table for correct results
1287 idPath = GetPathId(strPath);
1288 if (idPath < 0 && strPath != strFilenameAndPath)
1289 return -1;
1292 if (idFile == -1 && strPath != strFilenameAndPath)
1293 return -1;
1295 std::string strSQL;
1296 if (idFile == -1)
1297 strSQL = PrepareSQL("SELECT idMovie FROM movie "
1298 " JOIN files ON files.idFile=movie.idFile "
1299 "WHERE files.idPath=%i",
1300 idPath);
1301 else
1302 strSQL = PrepareSQL("SELECT idMedia FROM videoversion "
1303 "WHERE idFile = %i AND media_type = '%s' AND itemType = %i",
1304 idFile, MediaTypeMovie, VideoAssetType::VERSION);
1306 CLog::Log(LOGDEBUG, LOGDATABASE, "{} ({}), query = {}", __FUNCTION__,
1307 CURL::GetRedacted(strFilenameAndPath), strSQL);
1308 m_pDS->query(strSQL);
1309 if (m_pDS->num_rows() > 0)
1310 idMovie = m_pDS->fv(0).get_asInt();
1311 m_pDS->close();
1313 return idMovie;
1315 catch (...)
1317 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
1319 return -1;
1322 int CVideoDatabase::GetTvShowId(const std::string& strPath)
1326 if (nullptr == m_pDB)
1327 return -1;
1328 if (nullptr == m_pDS)
1329 return -1;
1330 int idTvShow = -1;
1332 // have to join movieinfo table for correct results
1333 int idPath = GetPathId(strPath);
1334 if (idPath < 0)
1335 return -1;
1337 std::string strSQL;
1338 std::string strPath1=strPath;
1339 std::string strParent;
1340 int iFound=0;
1342 strSQL=PrepareSQL("select idShow from tvshowlinkpath where tvshowlinkpath.idPath=%i",idPath);
1343 m_pDS->query(strSQL);
1344 if (!m_pDS->eof())
1345 iFound = 1;
1347 while (iFound == 0 && URIUtils::GetParentPath(strPath1, strParent))
1349 strSQL=PrepareSQL("SELECT idShow FROM path INNER JOIN tvshowlinkpath ON tvshowlinkpath.idPath=path.idPath WHERE strPath='%s'",strParent.c_str());
1350 m_pDS->query(strSQL);
1351 if (!m_pDS->eof())
1353 int idShow = m_pDS->fv("idShow").get_asInt();
1354 if (idShow != -1)
1355 iFound = 2;
1357 strPath1 = strParent;
1360 if (m_pDS->num_rows() > 0)
1361 idTvShow = m_pDS->fv("idShow").get_asInt();
1362 m_pDS->close();
1364 return idTvShow;
1366 catch (...)
1368 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
1370 return -1;
1373 int CVideoDatabase::GetEpisodeId(const std::string& strFilenameAndPath, int idEpisode, int idSeason) // input value is episode/season number hint - for multiparters
1377 if (nullptr == m_pDB)
1378 return -1;
1379 if (nullptr == m_pDS)
1380 return -1;
1382 // need this due to the nested GetEpisodeInfo query
1383 std::unique_ptr<Dataset> pDS;
1384 pDS.reset(m_pDB->CreateDataset());
1385 if (nullptr == pDS)
1386 return -1;
1388 int idFile = GetFileId(strFilenameAndPath);
1389 if (idFile < 0)
1390 return -1;
1392 std::string strSQL=PrepareSQL("select idEpisode from episode where idFile=%i", idFile);
1394 CLog::Log(LOGDEBUG, LOGDATABASE, "{} ({}), query = {}", __FUNCTION__,
1395 CURL::GetRedacted(strFilenameAndPath), strSQL);
1396 pDS->query(strSQL);
1397 if (pDS->num_rows() > 0)
1399 if (idEpisode == -1)
1400 idEpisode = pDS->fv("episode.idEpisode").get_asInt();
1401 else // use the hint!
1403 while (!pDS->eof())
1405 CVideoInfoTag tag;
1406 int idTmpEpisode = pDS->fv("episode.idEpisode").get_asInt();
1407 GetEpisodeBasicInfo(strFilenameAndPath, tag, idTmpEpisode);
1408 if (tag.m_iEpisode == idEpisode && (idSeason == -1 || tag.m_iSeason == idSeason)) {
1409 // match on the episode hint, and there's no season hint or a season hint match
1410 idEpisode = idTmpEpisode;
1411 break;
1413 pDS->next();
1415 if (pDS->eof())
1416 idEpisode = -1;
1419 else
1420 idEpisode = -1;
1422 pDS->close();
1424 return idEpisode;
1426 catch (...)
1428 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
1430 return -1;
1433 int CVideoDatabase::GetMusicVideoId(const std::string& strFilenameAndPath)
1437 if (nullptr == m_pDB)
1438 return -1;
1439 if (nullptr == m_pDS)
1440 return -1;
1442 int idFile = GetFileId(strFilenameAndPath);
1443 if (idFile < 0)
1444 return -1;
1446 std::string strSQL=PrepareSQL("select idMVideo from musicvideo where idFile=%i", idFile);
1448 CLog::Log(LOGDEBUG, LOGDATABASE, "{} ({}), query = {}", __FUNCTION__,
1449 CURL::GetRedacted(strFilenameAndPath), strSQL);
1450 m_pDS->query(strSQL);
1451 int idMVideo=-1;
1452 if (m_pDS->num_rows() > 0)
1453 idMVideo = m_pDS->fv("idMVideo").get_asInt();
1454 m_pDS->close();
1456 return idMVideo;
1458 catch (...)
1460 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
1462 return -1;
1465 //********************************************************************************************************************************
1466 int CVideoDatabase::AddNewMovie(CVideoInfoTag& details)
1468 assert(m_pDB->in_transaction());
1470 const auto filePath = details.GetPath();
1474 if (nullptr == m_pDB)
1475 return -1;
1476 if (nullptr == m_pDS)
1477 return -1;
1479 if (details.m_iFileId <= 0)
1481 details.m_iFileId = AddFile(details);
1482 if (details.m_iFileId <= 0)
1483 return -1;
1486 m_pDS->exec(
1487 PrepareSQL("INSERT INTO movie (idMovie, idFile) VALUES (NULL, %i)", details.m_iFileId));
1488 details.m_iDbId = static_cast<int>(m_pDS->lastinsertid());
1489 m_pDS->exec(
1490 PrepareSQL("INSERT INTO videoversion (idFile, idMedia, media_type, itemType, idType) "
1491 "VALUES(%i, %i, '%s', %i, %i)",
1492 details.m_iFileId, details.m_iDbId, MediaTypeMovie, VideoAssetType::VERSION,
1493 VIDEO_VERSION_ID_DEFAULT));
1495 return details.m_iDbId;
1497 catch (...)
1499 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
1501 return -1;
1504 bool CVideoDatabase::AddPathToTvShow(int idShow, const std::string &path, const std::string &parentPath, const CDateTime& dateAdded /* = CDateTime() */)
1506 // Check if this path is already added
1507 int idPath = GetPathId(path);
1508 if (idPath < 0)
1509 idPath = AddPath(path, parentPath, GetDateAdded(path, dateAdded));
1511 return ExecuteQuery(PrepareSQL("REPLACE INTO tvshowlinkpath(idShow, idPath) VALUES (%i,%i)", idShow, idPath));
1514 int CVideoDatabase::AddTvShow()
1516 if (ExecuteQuery("INSERT INTO tvshow(idShow) VALUES(NULL)"))
1517 return (int)m_pDS->lastinsertid();
1518 return -1;
1521 //********************************************************************************************************************************
1522 int CVideoDatabase::AddNewEpisode(int idShow, CVideoInfoTag& details)
1524 const auto filePath = details.GetPath();
1528 if (nullptr == m_pDB || nullptr == m_pDS)
1529 return -1;
1531 if (details.m_iFileId <= 0)
1533 details.m_iFileId = AddFile(details);
1534 if (details.m_iFileId <= 0)
1535 return -1;
1538 std::string strSQL =
1539 PrepareSQL("INSERT INTO episode (idEpisode, idFile, idShow) VALUES (NULL, %i, %i)",
1540 details.m_iFileId, idShow);
1541 m_pDS->exec(strSQL);
1542 details.m_iDbId = static_cast<int>(m_pDS->lastinsertid());
1544 return details.m_iDbId;
1546 catch (...)
1548 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
1550 return -1;
1553 int CVideoDatabase::AddNewMusicVideo(CVideoInfoTag& details)
1555 const auto filePath = details.GetPath();
1559 if (nullptr == m_pDB)
1560 return -1;
1561 if (nullptr == m_pDS)
1562 return -1;
1564 if (details.m_iFileId <= 0)
1566 details.m_iFileId = AddFile(details);
1567 if (details.m_iFileId <= 0)
1568 return -1;
1571 std::string strSQL = PrepareSQL("INSERT INTO musicvideo (idMVideo, idFile) VALUES (NULL, %i)",
1572 details.m_iFileId);
1573 m_pDS->exec(strSQL);
1574 details.m_iDbId = static_cast<int>(m_pDS->lastinsertid());
1576 return details.m_iDbId;
1578 catch (...)
1580 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
1582 return -1;
1585 //********************************************************************************************************************************
1586 int CVideoDatabase::AddToTable(const std::string& table, const std::string& firstField, const std::string& secondField, const std::string& value)
1590 if (nullptr == m_pDB)
1591 return -1;
1592 if (nullptr == m_pDS)
1593 return -1;
1595 std::string strSQL = PrepareSQL("select %s from %s where %s like '%s'", firstField.c_str(), table.c_str(), secondField.c_str(), value.substr(0, 255).c_str());
1596 m_pDS->query(strSQL);
1597 if (m_pDS->num_rows() == 0)
1599 m_pDS->close();
1600 // doesn't exists, add it
1601 strSQL = PrepareSQL("insert into %s (%s, %s) values(NULL, '%s')", table.c_str(), firstField.c_str(), secondField.c_str(), value.substr(0, 255).c_str());
1602 m_pDS->exec(strSQL);
1603 int id = (int)m_pDS->lastinsertid();
1604 return id;
1606 else
1608 int id = m_pDS->fv(firstField.c_str()).get_asInt();
1609 m_pDS->close();
1610 return id;
1613 catch (...)
1615 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, value);
1618 return -1;
1621 int CVideoDatabase::UpdateRatings(int mediaId, const char *mediaType, const RatingMap& values, const std::string& defaultRating)
1625 if (nullptr == m_pDB)
1626 return -1;
1627 if (nullptr == m_pDS)
1628 return -1;
1630 std::string sql = PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='%s'", mediaId, mediaType);
1631 m_pDS->exec(sql);
1633 return AddRatings(mediaId, mediaType, values, defaultRating);
1635 catch (...)
1637 CLog::Log(LOGERROR, "{} unable to update ratings of ({})", __FUNCTION__, mediaType);
1639 return -1;
1642 int CVideoDatabase::AddRatings(int mediaId, const char *mediaType, const RatingMap& values, const std::string& defaultRating)
1644 int ratingid = -1;
1647 if (nullptr == m_pDB)
1648 return -1;
1649 if (nullptr == m_pDS)
1650 return -1;
1652 for (const auto& i : values)
1654 int id;
1655 std::string strSQL = PrepareSQL("SELECT rating_id FROM rating WHERE media_id=%i AND media_type='%s' AND rating_type = '%s'", mediaId, mediaType, i.first.c_str());
1656 m_pDS->query(strSQL);
1657 if (m_pDS->num_rows() == 0)
1659 m_pDS->close();
1660 // doesn't exists, add it
1661 strSQL = PrepareSQL("INSERT INTO rating (media_id, media_type, rating_type, rating, votes) "
1662 "VALUES (%i, '%s', '%s', %f, %i)",
1663 mediaId, mediaType, i.first.c_str(),
1664 static_cast<double>(i.second.rating), i.second.votes);
1665 m_pDS->exec(strSQL);
1666 id = (int)m_pDS->lastinsertid();
1668 else
1670 id = m_pDS->fv(0).get_asInt();
1671 m_pDS->close();
1672 strSQL = PrepareSQL("UPDATE rating SET rating = %f, votes = %i WHERE rating_id = %i",
1673 static_cast<double>(i.second.rating), i.second.votes, id);
1674 m_pDS->exec(strSQL);
1676 if (i.first == defaultRating)
1677 ratingid = id;
1679 return ratingid;
1682 catch (...)
1684 CLog::Log(LOGERROR, "{} ({} - {}) failed", __FUNCTION__, mediaId, mediaType);
1687 return ratingid;
1690 int CVideoDatabase::UpdateUniqueIDs(int mediaId, const char *mediaType, const CVideoInfoTag& details)
1694 if (nullptr == m_pDB)
1695 return -1;
1696 if (nullptr == m_pDS)
1697 return -1;
1699 std::string sql = PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='%s'", mediaId, mediaType);
1700 m_pDS->exec(sql);
1702 return AddUniqueIDs(mediaId, mediaType, details);
1704 catch (...)
1706 CLog::Log(LOGERROR, "{} unable to update unique ids of ({})", __FUNCTION__, mediaType);
1708 return -1;
1711 int CVideoDatabase::AddUniqueIDs(int mediaId, const char *mediaType, const CVideoInfoTag& details)
1713 int uniqueid = -1;
1716 if (nullptr == m_pDB)
1717 return -1;
1718 if (nullptr == m_pDS)
1719 return -1;
1721 for (const auto& i : details.GetUniqueIDs())
1723 int id;
1724 std::string strSQL = PrepareSQL("SELECT uniqueid_id FROM uniqueid WHERE media_id=%i AND media_type='%s' AND type = '%s'", mediaId, mediaType, i.first.c_str());
1725 m_pDS->query(strSQL);
1726 if (m_pDS->num_rows() == 0)
1728 m_pDS->close();
1729 // doesn't exists, add it
1730 strSQL = PrepareSQL("INSERT INTO uniqueid (media_id, media_type, value, type) VALUES (%i, '%s', '%s', '%s')", mediaId, mediaType, i.second.c_str(), i.first.c_str());
1731 m_pDS->exec(strSQL);
1732 id = (int)m_pDS->lastinsertid();
1734 else
1736 id = m_pDS->fv(0).get_asInt();
1737 m_pDS->close();
1738 strSQL = PrepareSQL("UPDATE uniqueid SET value = '%s', type = '%s' WHERE uniqueid_id = %i", i.second.c_str(), i.first.c_str(), id);
1739 m_pDS->exec(strSQL);
1741 if (i.first == details.GetDefaultUniqueID())
1742 uniqueid = id;
1744 return uniqueid;
1747 catch (...)
1749 CLog::Log(LOGERROR, "{} ({} - {}) failed", __FUNCTION__, mediaId, mediaType);
1752 return uniqueid;
1755 int CVideoDatabase::AddSet(const std::string& strSet,
1756 const std::string& strOverview /* = "" */,
1757 const bool updateOverview /* = true */)
1759 if (strSet.empty())
1760 return -1;
1764 if (m_pDB == nullptr || m_pDS == nullptr)
1765 return -1;
1767 std::string strSQL = PrepareSQL("SELECT idSet FROM sets WHERE strSet LIKE '%s'", strSet.c_str());
1768 m_pDS->query(strSQL);
1769 if (m_pDS->num_rows() == 0)
1771 m_pDS->close();
1772 strSQL = PrepareSQL("INSERT INTO sets (idSet, strSet, strOverview) VALUES(NULL, '%s', '%s')", strSet.c_str(), strOverview.c_str());
1773 m_pDS->exec(strSQL);
1774 int id = static_cast<int>(m_pDS->lastinsertid());
1775 return id;
1777 else
1779 int id = m_pDS->fv("idSet").get_asInt();
1780 m_pDS->close();
1782 // update set data
1783 if (updateOverview)
1785 strSQL = PrepareSQL("UPDATE sets SET strOverview = '%s' WHERE idSet = %i",
1786 strOverview.c_str(), id);
1787 m_pDS->exec(strSQL);
1790 return id;
1793 catch (...)
1795 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSet);
1798 return -1;
1801 int CVideoDatabase::AddTag(const std::string& name)
1803 if (name.empty())
1804 return -1;
1806 return AddToTable("tag", "tag_id", "name", name);
1809 int CVideoDatabase::AddActor(const std::string& name, const std::string& thumbURLs, const std::string &thumb)
1813 if (nullptr == m_pDB)
1814 return -1;
1815 if (nullptr == m_pDS)
1816 return -1;
1817 int idActor = -1;
1819 // ATTENTION: the trimming of actor names should really not be done here but after the scraping / NFO-parsing
1820 std::string trimmedName = name;
1821 StringUtils::Trim(trimmedName);
1823 std::string strSQL=PrepareSQL("select actor_id from actor where name like '%s'", trimmedName.substr(0, 255).c_str());
1824 m_pDS->query(strSQL);
1825 if (m_pDS->num_rows() == 0)
1827 m_pDS->close();
1828 // doesn't exists, add it
1829 strSQL=PrepareSQL("insert into actor (actor_id, name, art_urls) values(NULL, '%s', '%s')", trimmedName.substr(0,255).c_str(), thumbURLs.c_str());
1830 m_pDS->exec(strSQL);
1831 idActor = (int)m_pDS->lastinsertid();
1833 else
1835 idActor = m_pDS->fv(0).get_asInt();
1836 m_pDS->close();
1837 // update the thumb url's
1838 if (!thumbURLs.empty())
1840 strSQL=PrepareSQL("update actor set art_urls = '%s' where actor_id = %i", thumbURLs.c_str(), idActor);
1841 m_pDS->exec(strSQL);
1844 // add artwork
1845 if (!thumb.empty())
1846 SetArtForItem(idActor, "actor", "thumb", thumb);
1847 return idActor;
1849 catch (...)
1851 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, name);
1853 return -1;
1858 void CVideoDatabase::AddLinkToActor(int mediaId, const char *mediaType, int actorId, const std::string &role, int order)
1860 std::string sql = PrepareSQL("SELECT 1 FROM actor_link WHERE actor_id=%i AND "
1861 "media_id=%i AND media_type='%s' AND role='%s'",
1862 actorId, mediaId, mediaType, role.c_str());
1864 if (GetSingleValue(sql).empty())
1865 { // doesn't exists, add it
1866 sql = PrepareSQL("INSERT INTO actor_link (actor_id, media_id, media_type, role, cast_order) VALUES(%i,%i,'%s','%s',%i)", actorId, mediaId, mediaType, role.c_str(), order);
1867 ExecuteQuery(sql);
1871 void CVideoDatabase::AddToLinkTable(int mediaId, const std::string& mediaType, const std::string& table, int valueId, const char *foreignKey)
1873 const char *key = foreignKey ? foreignKey : table.c_str();
1874 std::string sql = PrepareSQL("SELECT 1 FROM %s_link WHERE %s_id=%i AND media_id=%i AND media_type='%s'", table.c_str(), key, valueId, mediaId, mediaType.c_str());
1876 if (GetSingleValue(sql).empty())
1877 { // doesn't exists, add it
1878 sql = PrepareSQL("INSERT INTO %s_link (%s_id,media_id,media_type) VALUES(%i,%i,'%s')", table.c_str(), key, valueId, mediaId, mediaType.c_str());
1879 ExecuteQuery(sql);
1883 void CVideoDatabase::RemoveFromLinkTable(int mediaId, const std::string& mediaType, const std::string& table, int valueId, const char *foreignKey)
1885 const char *key = foreignKey ? foreignKey : table.c_str();
1886 std::string sql = PrepareSQL("DELETE FROM %s_link WHERE %s_id=%i AND media_id=%i AND media_type='%s'", table.c_str(), key, valueId, mediaId, mediaType.c_str());
1888 ExecuteQuery(sql);
1891 void CVideoDatabase::AddLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1893 for (const auto &i : values)
1895 if (!i.empty())
1897 int idValue = AddToTable(field, field + "_id", "name", i);
1898 if (idValue > -1)
1899 AddToLinkTable(mediaId, mediaType, field, idValue);
1904 void CVideoDatabase::UpdateLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1906 std::string sql = PrepareSQL("DELETE FROM %s_link WHERE media_id=%i AND media_type='%s'", field.c_str(), mediaId, mediaType.c_str());
1907 m_pDS->exec(sql);
1909 AddLinksToItem(mediaId, mediaType, field, values);
1912 void CVideoDatabase::AddActorLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1914 for (const auto &i : values)
1916 if (!i.empty())
1918 int idValue = AddActor(i, "");
1919 if (idValue > -1)
1920 AddToLinkTable(mediaId, mediaType, field, idValue, "actor");
1925 void CVideoDatabase::UpdateActorLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1927 std::string sql = PrepareSQL("DELETE FROM %s_link WHERE media_id=%i AND media_type='%s'", field.c_str(), mediaId, mediaType.c_str());
1928 m_pDS->exec(sql);
1930 AddActorLinksToItem(mediaId, mediaType, field, values);
1933 //****Tags****
1934 void CVideoDatabase::AddTagToItem(int media_id, int tag_id, const std::string &type)
1936 if (type.empty())
1937 return;
1939 AddToLinkTable(media_id, type, "tag", tag_id);
1942 void CVideoDatabase::RemoveTagFromItem(int media_id, int tag_id, const std::string &type)
1944 if (type.empty())
1945 return;
1947 RemoveFromLinkTable(media_id, type, "tag", tag_id);
1950 void CVideoDatabase::RemoveTagsFromItem(int media_id, const std::string &type)
1952 if (type.empty())
1953 return;
1955 m_pDS2->exec(PrepareSQL("DELETE FROM tag_link WHERE media_id=%d AND media_type='%s'", media_id, type.c_str()));
1958 //****Actors****
1959 void CVideoDatabase::AddCast(int mediaId, const char *mediaType, const std::vector< SActorInfo > &cast)
1961 if (cast.empty())
1962 return;
1964 int order = std::max_element(cast.begin(), cast.end())->order;
1965 for (const auto &i : cast)
1967 int idActor = AddActor(i.strName, i.thumbUrl.GetData(), i.thumb);
1968 AddLinkToActor(mediaId, mediaType, idActor, i.strRole, i.order >= 0 ? i.order : ++order);
1972 //********************************************************************************************************************************
1973 bool CVideoDatabase::LoadVideoInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int getDetails /* = VideoDbDetailsAll */)
1975 if (GetMovieInfo(strFilenameAndPath, details))
1976 return true;
1977 if (GetEpisodeInfo(strFilenameAndPath, details))
1978 return true;
1979 if (GetMusicVideoInfo(strFilenameAndPath, details))
1980 return true;
1981 if (GetFileInfo(strFilenameAndPath, details))
1982 return true;
1984 return false;
1987 bool CVideoDatabase::HasMovieInfo(const std::string& strFilenameAndPath)
1991 if (nullptr == m_pDB)
1992 return false;
1993 if (nullptr == m_pDS)
1994 return false;
1995 int idMovie = GetMovieId(strFilenameAndPath);
1996 return (idMovie > 0); // index of zero is also invalid
1998 catch (...)
2000 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2002 return false;
2005 bool CVideoDatabase::HasTvShowInfo(const std::string& strPath)
2009 if (nullptr == m_pDB)
2010 return false;
2011 if (nullptr == m_pDS)
2012 return false;
2013 int idTvShow = GetTvShowId(strPath);
2014 return (idTvShow > 0); // index of zero is also invalid
2016 catch (...)
2018 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
2020 return false;
2023 bool CVideoDatabase::HasEpisodeInfo(const std::string& strFilenameAndPath)
2027 if (nullptr == m_pDB)
2028 return false;
2029 if (nullptr == m_pDS)
2030 return false;
2031 int idEpisode = GetEpisodeId(strFilenameAndPath);
2032 return (idEpisode > 0); // index of zero is also invalid
2034 catch (...)
2036 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2038 return false;
2041 bool CVideoDatabase::HasMusicVideoInfo(const std::string& strFilenameAndPath)
2045 if (nullptr == m_pDB)
2046 return false;
2047 if (nullptr == m_pDS)
2048 return false;
2049 int idMVideo = GetMusicVideoId(strFilenameAndPath);
2050 return (idMVideo > 0); // index of zero is also invalid
2052 catch (...)
2054 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2056 return false;
2059 void CVideoDatabase::DeleteDetailsForTvShow(int idTvShow)
2063 if (nullptr == m_pDB)
2064 return;
2065 if (nullptr == m_pDS)
2066 return;
2068 std::string strSQL;
2069 strSQL=PrepareSQL("DELETE from genre_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2070 m_pDS->exec(strSQL);
2072 strSQL=PrepareSQL("DELETE FROM actor_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2073 m_pDS->exec(strSQL);
2075 strSQL=PrepareSQL("DELETE FROM director_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2076 m_pDS->exec(strSQL);
2078 strSQL=PrepareSQL("DELETE FROM studio_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2079 m_pDS->exec(strSQL);
2081 strSQL = PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2082 m_pDS->exec(strSQL);
2084 strSQL = PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2085 m_pDS->exec(strSQL);
2087 // remove all info other than the id
2088 // we do this due to the way we have the link between the file + movie tables.
2090 std::vector<std::string> ids;
2091 for (int iType = VIDEODB_ID_TV_MIN + 1; iType < VIDEODB_ID_TV_MAX; iType++)
2092 ids.emplace_back(StringUtils::Format("c{:02}=NULL", iType));
2094 strSQL = "update tvshow set ";
2095 strSQL += StringUtils::Join(ids, ", ");
2096 strSQL += PrepareSQL(" where idShow=%i", idTvShow);
2097 m_pDS->exec(strSQL);
2099 catch (...)
2101 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idTvShow);
2105 //********************************************************************************************************************************
2106 void CVideoDatabase::GetMoviesByActor(const std::string& name, CFileItemList& items)
2108 Filter filter;
2109 filter.join = "LEFT JOIN actor_link ON actor_link.media_id=movie_view.idMovie AND actor_link.media_type='movie' "
2110 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2111 "LEFT JOIN director_link ON director_link.media_id=movie_view.idMovie AND director_link.media_type='movie' "
2112 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2113 filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
2114 filter.group = "movie_view.idMovie";
2115 GetMoviesByWhere("videodb://movies/titles/", filter, items);
2118 void CVideoDatabase::GetTvShowsByActor(const std::string& name, CFileItemList& items)
2120 Filter filter;
2121 filter.join = "LEFT JOIN actor_link ON actor_link.media_id=tvshow_view.idShow AND actor_link.media_type='tvshow' "
2122 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2123 "LEFT JOIN director_link ON director_link.media_id=tvshow_view.idShow AND director_link.media_type='tvshow' "
2124 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2125 filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
2126 GetTvShowsByWhere("videodb://tvshows/titles/", filter, items);
2129 void CVideoDatabase::GetEpisodesByActor(const std::string& name, CFileItemList& items)
2131 Filter filter;
2132 filter.join = "LEFT JOIN actor_link ON actor_link.media_id=episode_view.idEpisode AND actor_link.media_type='episode' "
2133 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2134 "LEFT JOIN director_link ON director_link.media_id=episode_view.idEpisode AND director_link.media_type='episode' "
2135 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2136 filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
2137 filter.group = "episode_view.idEpisode";
2138 GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
2141 void CVideoDatabase::GetMusicVideosByArtist(const std::string& strArtist, CFileItemList& items)
2145 items.Clear();
2146 if (nullptr == m_pDB)
2147 return;
2148 if (nullptr == m_pDS)
2149 return;
2151 std::string strSQL;
2152 if (strArtist.empty()) //! @todo SMARTPLAYLISTS what is this here for???
2153 strSQL=PrepareSQL("select distinct * from musicvideo_view join actor_link on actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo' join actor on actor.actor_id=actor_link.actor_id");
2154 else // same artist OR same director
2155 strSQL = PrepareSQL(
2156 "select * from musicvideo_view join actor_link on "
2157 "actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo' "
2158 "join actor on actor.actor_id=actor_link.actor_id where actor.name='%s' OR "
2159 "musicvideo_view.c05='%s' GROUP BY idMVideo",
2160 strArtist.c_str(), strArtist.c_str());
2161 m_pDS->query( strSQL );
2163 while (!m_pDS->eof())
2165 CVideoInfoTag tag = GetDetailsForMusicVideo(m_pDS);
2166 CFileItemPtr pItem(new CFileItem(tag));
2167 pItem->SetLabel(StringUtils::Join(tag.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator));
2168 items.Add(pItem);
2169 m_pDS->next();
2171 m_pDS->close();
2173 catch (...)
2175 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strArtist);
2179 //********************************************************************************************************************************
2180 bool CVideoDatabase::GetMovieInfo(const std::string& strFilenameAndPath,
2181 CVideoInfoTag& details,
2182 int idMovie /* = -1 */,
2183 int idVersion /* = -1 */,
2184 int getDetails /* = VideoDbDetailsAll */)
2188 if (m_pDB == nullptr || m_pDS == nullptr)
2189 return false;
2191 if (idMovie < 0)
2192 idMovie = GetMovieId(strFilenameAndPath);
2194 if (idMovie < 0)
2195 return false;
2197 std::string sql;
2198 if (idVersion >= 0)
2200 //! @todo get rid of "videos with versions as folder" hack!
2201 if (idVersion != VIDEO_VERSION_ID_ALL)
2202 sql = PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND videoVersionTypeId = %i",
2203 idMovie, idVersion);
2205 else if (!strFilenameAndPath.empty())
2207 const int idFile{GetFileId(strFilenameAndPath)};
2208 if (idFile != -1)
2209 sql = PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND videoVersionIdFile = %i",
2210 idMovie, idFile);
2213 if (sql.empty())
2214 sql = PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND isDefaultVersion = 1",
2215 idMovie);
2217 if (!m_pDS->query(sql))
2218 return false;
2220 details = GetDetailsForMovie(m_pDS, getDetails);
2221 return !details.IsEmpty();
2223 catch (...)
2225 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2227 return false;
2230 std::string CVideoDatabase::GetMovieTitle(int idMovie)
2232 if (!m_pDB || !m_pDS)
2233 return "";
2235 m_pDS->query(PrepareSQL("SELECT c%02d from movie where idMovie = %i", VIDEODB_ID_TITLE, idMovie));
2237 if (!m_pDS->eof())
2238 return m_pDS->fv(0).get_asString();
2239 else
2240 return "";
2243 //********************************************************************************************************************************
2244 bool CVideoDatabase::GetTvShowInfo(const std::string& strPath, CVideoInfoTag& details, int idTvShow /* = -1 */, CFileItem *item /* = NULL */, int getDetails /* = VideoDbDetailsAll */)
2248 if (m_pDB == nullptr || m_pDS == nullptr)
2249 return false;
2251 if (idTvShow < 0)
2252 idTvShow = GetTvShowId(strPath);
2253 if (idTvShow < 0) return false;
2255 std::string sql = PrepareSQL("SELECT * FROM tvshow_view WHERE idShow=%i GROUP BY idShow", idTvShow);
2256 if (!m_pDS->query(sql))
2257 return false;
2258 details = GetDetailsForTvShow(m_pDS, getDetails, item);
2259 return !details.IsEmpty();
2261 catch (...)
2263 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
2265 return false;
2268 bool CVideoDatabase::GetSeasonInfo(const std::string& path,
2269 int season,
2270 CVideoInfoTag& details,
2271 CFileItem* item)
2275 const std::string sql = PrepareSQL("strPath='%s' AND season=%i", path.c_str(), season);
2276 const std::string id = GetSingleValue("season_view", "idSeason", sql);
2277 if (id.empty())
2279 CLog::LogF(LOGERROR, "Failed to obtain seasonId for path={}, season={}", path, season);
2281 else
2283 const int idSeason = static_cast<int>(std::strtol(id.c_str(), nullptr, 10));
2284 return GetSeasonInfo(idSeason, details, item);
2287 catch (...)
2289 CLog::LogF(LOGERROR, "Exception while trying to to obtain seasonId for path={}, season={}",
2290 path, season);
2292 return false;
2295 bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, bool allDetails /* = true */)
2297 return GetSeasonInfo(idSeason, details, allDetails, nullptr);
2300 bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, CFileItem* item)
2302 return GetSeasonInfo(idSeason, details, true, item);
2305 bool CVideoDatabase::GetSeasonInfo(int idSeason,
2306 CVideoInfoTag& details,
2307 bool allDetails,
2308 CFileItem* item)
2310 if (idSeason < 0)
2311 return false;
2315 if (!m_pDB || !m_pDS)
2316 return false;
2318 std::string sql = PrepareSQL("SELECT idSeason, idShow, season, name, userrating FROM seasons WHERE idSeason=%i", idSeason);
2319 if (!m_pDS->query(sql))
2320 return false;
2322 if (m_pDS->num_rows() != 1)
2323 return false;
2325 if (allDetails)
2327 int idShow = m_pDS->fv(1).get_asInt();
2329 // close the current result because we are going to query the season view for all details
2330 m_pDS->close();
2332 if (idShow < 0)
2333 return false;
2335 CFileItemList seasons;
2336 if (!GetSeasonsNav(StringUtils::Format("videodb://tvshows/titles/{}/", idShow), seasons, -1,
2337 -1, -1, -1, idShow, false) ||
2338 seasons.Size() <= 0)
2339 return false;
2341 for (int index = 0; index < seasons.Size(); index++)
2343 const CFileItemPtr season = seasons.Get(index);
2344 if (season->HasVideoInfoTag() && season->GetVideoInfoTag()->m_iDbId == idSeason && season->GetVideoInfoTag()->m_iIdShow == idShow)
2346 details = *season->GetVideoInfoTag();
2347 if (item)
2348 *item = *season;
2349 return true;
2353 return false;
2356 const int season = m_pDS->fv(2).get_asInt();
2357 std::string name = m_pDS->fv(3).get_asString();
2359 if (name.empty())
2361 if (season == 0)
2362 name = g_localizeStrings.Get(20381);
2363 else
2364 name = StringUtils::Format(g_localizeStrings.Get(20358), season);
2367 details.m_strTitle = name;
2368 if (!name.empty())
2369 details.m_strSortTitle = name;
2370 details.m_iSeason = season;
2371 details.m_iDbId = m_pDS->fv(0).get_asInt();
2372 details.m_iIdSeason = details.m_iDbId;
2373 details.m_type = MediaTypeSeason;
2374 details.m_iUserRating = m_pDS->fv(4).get_asInt();
2375 details.m_iIdShow = m_pDS->fv(1).get_asInt();
2377 return true;
2379 catch (...)
2381 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSeason);
2383 return false;
2386 bool CVideoDatabase::GetEpisodeBasicInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idEpisode /* = -1 */)
2390 if (idEpisode < 0)
2391 idEpisode = GetEpisodeId(strFilenameAndPath);
2393 if (idEpisode < 0)
2394 return false;
2396 std::string sql = PrepareSQL("select * from episode where idEpisode=%i",idEpisode);
2397 if (!m_pDS->query(sql))
2398 return false;
2399 details = GetBasicDetailsForEpisode(m_pDS);
2400 return !details.IsEmpty();
2402 catch (...)
2404 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2406 return false;
2409 bool CVideoDatabase::GetEpisodeInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idEpisode /* = -1 */, int getDetails /* = VideoDbDetailsAll */)
2413 if (m_pDB == nullptr || m_pDS == nullptr)
2414 return false;
2416 if (idEpisode < 0)
2417 idEpisode = GetEpisodeId(strFilenameAndPath, details.m_iEpisode, details.m_iSeason);
2418 if (idEpisode < 0) return false;
2420 std::string sql = PrepareSQL("select * from episode_view where idEpisode=%i",idEpisode);
2421 if (!m_pDS->query(sql))
2422 return false;
2423 details = GetDetailsForEpisode(m_pDS, getDetails);
2424 return !details.IsEmpty();
2426 catch (...)
2428 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2430 return false;
2433 bool CVideoDatabase::GetMusicVideoInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idMVideo /* = -1 */, int getDetails /* = VideoDbDetailsAll */)
2437 if (m_pDB == nullptr || m_pDS == nullptr)
2438 return false;
2440 if (idMVideo < 0)
2441 idMVideo = GetMusicVideoId(strFilenameAndPath);
2442 if (idMVideo < 0) return false;
2444 std::string sql = PrepareSQL("select * from musicvideo_view where idMVideo=%i", idMVideo);
2445 if (!m_pDS->query(sql))
2446 return false;
2447 details = GetDetailsForMusicVideo(m_pDS, getDetails);
2448 return !details.IsEmpty();
2450 catch (...)
2452 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2454 return false;
2457 bool CVideoDatabase::GetSetInfo(int idSet, CVideoInfoTag& details, CFileItem* item /* = nullptr */)
2461 if (idSet < 0)
2462 return false;
2464 Filter filter;
2465 filter.where = PrepareSQL("sets.idSet=%d", idSet);
2466 CFileItemList items;
2467 if (!GetSetsByWhere("videodb://movies/sets/", filter, items) ||
2468 items.Size() != 1 ||
2469 !items[0]->HasVideoInfoTag())
2470 return false;
2472 details = *(items[0]->GetVideoInfoTag());
2473 if (item)
2474 *item = *items[0];
2475 return !details.IsEmpty();
2477 catch (...)
2479 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSet);
2481 return false;
2484 bool CVideoDatabase::GetFileInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idFile /* = -1 */)
2488 if (idFile < 0)
2489 idFile = GetFileId(strFilenameAndPath);
2490 if (idFile < 0)
2491 return false;
2493 std::string sql = PrepareSQL("SELECT * FROM files "
2494 "JOIN path ON path.idPath = files.idPath "
2495 "LEFT JOIN bookmark ON bookmark.idFile = files.idFile AND bookmark.type = %i "
2496 "WHERE files.idFile = %i", CBookmark::RESUME, idFile);
2497 if (!m_pDS->query(sql))
2498 return false;
2500 details.m_iFileId = m_pDS->fv("files.idFile").get_asInt();
2501 details.m_strPath = m_pDS->fv("path.strPath").get_asString();
2502 std::string strFileName = m_pDS->fv("files.strFilename").get_asString();
2503 ConstructPath(details.m_strFileNameAndPath, details.m_strPath, strFileName);
2504 details.SetPlayCount(std::max(details.GetPlayCount(), m_pDS->fv("files.playCount").get_asInt()));
2505 if (!details.m_lastPlayed.IsValid())
2506 details.m_lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
2507 if (!details.m_dateAdded.IsValid())
2508 details.m_dateAdded.SetFromDBDateTime(m_pDS->fv("files.dateAdded").get_asString());
2509 if (!details.GetResumePoint().IsSet() ||
2510 (!details.GetResumePoint().HasSavedPlayerState() &&
2511 !m_pDS->fv("bookmark.playerState").get_asString().empty()))
2513 details.SetResumePoint(m_pDS->fv("bookmark.timeInSeconds").get_asDouble(),
2514 m_pDS->fv("bookmark.totalTimeInSeconds").get_asDouble(),
2515 m_pDS->fv("bookmark.playerState").get_asString());
2518 // get streamdetails
2519 GetStreamDetails(details);
2521 return !details.IsEmpty();
2523 catch (...)
2525 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2527 return false;
2530 std::string CVideoDatabase::GetValueString(const CVideoInfoTag &details, int min, int max, const SDbTableOffsets *offsets) const
2532 std::vector<std::string> conditions;
2533 for (int i = min + 1; i < max; ++i)
2535 switch (offsets[i].type)
2537 case VIDEODB_TYPE_STRING:
2538 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const std::string*)(((const char*)&details)+offsets[i].offset))->c_str()));
2539 break;
2540 case VIDEODB_TYPE_INT:
2541 conditions.emplace_back(PrepareSQL("c%02d='%i'", i, *(const int*)(((const char*)&details)+offsets[i].offset)));
2542 break;
2543 case VIDEODB_TYPE_COUNT:
2545 int value = *(const int*)(((const char*)&details)+offsets[i].offset);
2546 if (value)
2547 conditions.emplace_back(PrepareSQL("c%02d=%i", i, value));
2548 else
2549 conditions.emplace_back(PrepareSQL("c%02d=NULL", i));
2551 break;
2552 case VIDEODB_TYPE_BOOL:
2553 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, *(const bool*)(((const char*)&details)+offsets[i].offset)?"true":"false"));
2554 break;
2555 case VIDEODB_TYPE_FLOAT:
2556 conditions.emplace_back(PrepareSQL(
2557 "c%02d='%f'", i, *(const double*)(((const char*)&details) + offsets[i].offset)));
2558 break;
2559 case VIDEODB_TYPE_STRINGARRAY:
2560 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, StringUtils::Join(*((const std::vector<std::string>*)(((const char*)&details)+offsets[i].offset)),
2561 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator).c_str()));
2562 break;
2563 case VIDEODB_TYPE_DATE:
2564 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const CDateTime*)(((const char*)&details)+offsets[i].offset))->GetAsDBDate().c_str()));
2565 break;
2566 case VIDEODB_TYPE_DATETIME:
2567 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const CDateTime*)(((const char*)&details)+offsets[i].offset))->GetAsDBDateTime().c_str()));
2568 break;
2569 case VIDEODB_TYPE_UNUSED: // Skip the unused field to avoid populating unused data
2570 continue;
2573 return StringUtils::Join(conditions, ",");
2576 //********************************************************************************************************************************
2577 int CVideoDatabase::SetDetailsForItem(CVideoInfoTag& details, const std::map<std::string, std::string> &artwork)
2579 return SetDetailsForItem(details.m_iDbId, details.m_type, details, artwork);
2582 int CVideoDatabase::SetDetailsForItem(int id, const MediaType& mediaType, CVideoInfoTag& details, const std::map<std::string, std::string> &artwork)
2584 if (mediaType == MediaTypeNone)
2585 return -1;
2587 if (mediaType == MediaTypeMovie)
2588 return SetDetailsForMovie(details, artwork, id);
2589 else if (mediaType == MediaTypeVideoCollection)
2590 return SetDetailsForMovieSet(details, artwork, id);
2591 else if (mediaType == MediaTypeTvShow)
2593 std::map<int, std::map<std::string, std::string> > seasonArtwork;
2594 if (!UpdateDetailsForTvShow(id, details, artwork, seasonArtwork))
2595 return -1;
2597 return id;
2599 else if (mediaType == MediaTypeSeason)
2600 return SetDetailsForSeason(details, artwork, details.m_iIdShow, id);
2601 else if (mediaType == MediaTypeEpisode)
2602 return SetDetailsForEpisode(details, artwork, details.m_iIdShow, id);
2603 else if (mediaType == MediaTypeMusicVideo)
2604 return SetDetailsForMusicVideo(details, artwork, id);
2606 return -1;
2609 int CVideoDatabase::SetDetailsForMovie(CVideoInfoTag& details,
2610 const std::map<std::string, std::string>& artwork,
2611 int idMovie /* = -1 */)
2613 const auto filePath = details.GetPath();
2617 BeginTransaction();
2619 if (idMovie < 0)
2620 idMovie = GetMovieId(filePath);
2622 if (idMovie == -1)
2624 // only add a new movie if we don't already have a valid idMovie
2625 idMovie = AddNewMovie(details);
2626 if (idMovie < 0)
2628 RollbackTransaction();
2629 return idMovie;
2633 // update dateadded if it's set
2634 if (details.m_dateAdded.IsValid())
2635 UpdateFileDateAdded(details);
2637 AddCast(idMovie, "movie", details.m_cast);
2638 AddLinksToItem(idMovie, MediaTypeMovie, "genre", details.m_genre);
2639 AddLinksToItem(idMovie, MediaTypeMovie, "studio", details.m_studio);
2640 AddLinksToItem(idMovie, MediaTypeMovie, "country", details.m_country);
2641 AddLinksToItem(idMovie, MediaTypeMovie, "tag", details.m_tags);
2642 AddActorLinksToItem(idMovie, MediaTypeMovie, "director", details.m_director);
2643 AddActorLinksToItem(idMovie, MediaTypeMovie, "writer", details.m_writingCredits);
2645 // add ratingsu
2646 details.m_iIdRating = AddRatings(idMovie, MediaTypeMovie, details.m_ratings, details.GetDefaultRating());
2648 // add unique ids
2649 details.m_iIdUniqueID = AddUniqueIDs(idMovie, MediaTypeMovie, details);
2651 // add set...
2652 int idSet = -1;
2653 if (!details.m_set.title.empty())
2655 idSet = AddSet(details.m_set.title, details.m_set.overview, details.GetUpdateSetOverview());
2656 // add art if not available
2657 if (!HasArtForItem(idSet, MediaTypeVideoCollection))
2659 for (const auto &it : artwork)
2661 if (StringUtils::StartsWith(it.first, "set."))
2662 SetArtForItem(idSet, MediaTypeVideoCollection, it.first.substr(4), it.second);
2667 if (details.HasStreamDetails())
2668 SetStreamDetailsForFileId(details.m_streamDetails, GetAndFillFileId(details));
2670 SetArtForItem(idMovie, MediaTypeMovie, artwork);
2672 if (!details.HasUniqueID() && details.HasYear())
2673 { // query DB for any movies matching online id and year
2674 std::string strSQL = PrepareSQL("SELECT files.playCount, files.lastPlayed "
2675 "FROM movie "
2676 " INNER JOIN files "
2677 " ON files.idFile=movie.idFile "
2678 " JOIN uniqueid "
2679 " ON movie.idMovie=uniqueid.media_id AND uniqueid.media_type='movie' AND uniqueid.value='%s'"
2680 "WHERE movie.premiered LIKE '%i%%' AND movie.idMovie!=%i AND files.playCount > 0",
2681 details.GetUniqueID().c_str(), details.GetYear(), idMovie);
2682 m_pDS->query(strSQL);
2684 if (!m_pDS->eof())
2686 int playCount = m_pDS->fv("files.playCount").get_asInt();
2688 CDateTime lastPlayed;
2689 lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
2691 // update with playCount and lastPlayed
2692 strSQL =
2693 PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", playCount,
2694 lastPlayed.GetAsDBDateTime().c_str(), GetAndFillFileId(details));
2695 m_pDS->exec(strSQL);
2698 m_pDS->close();
2700 // update our movie table (we know it was added already above)
2701 // and insert the new row
2702 std::string sql = "UPDATE movie SET " + GetValueString(details, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets);
2703 if (idSet > 0)
2704 sql += PrepareSQL(", idSet = %i", idSet);
2705 else
2706 sql += ", idSet = NULL";
2707 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2708 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2709 else
2710 sql += ", userrating = NULL";
2711 if (details.HasPremiered())
2712 sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
2713 else
2714 sql += PrepareSQL(", premiered = '%i'", details.GetYear());
2715 sql += PrepareSQL(" where idMovie=%i", idMovie);
2716 m_pDS->exec(sql);
2717 CommitTransaction();
2719 return idMovie;
2721 catch (...)
2723 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
2725 RollbackTransaction();
2726 return -1;
2729 int CVideoDatabase::UpdateDetailsForMovie(int idMovie, CVideoInfoTag& details, const std::map<std::string, std::string> &artwork, const std::set<std::string> &updatedDetails)
2731 if (idMovie < 0)
2732 return idMovie;
2736 CLog::Log(LOGINFO, "{}: Starting updates for movie {}", __FUNCTION__, idMovie);
2738 BeginTransaction();
2740 // process the link table updates
2741 if (updatedDetails.find("genre") != updatedDetails.end())
2742 UpdateLinksToItem(idMovie, MediaTypeMovie, "genre", details.m_genre);
2743 if (updatedDetails.find("studio") != updatedDetails.end())
2744 UpdateLinksToItem(idMovie, MediaTypeMovie, "studio", details.m_studio);
2745 if (updatedDetails.find("country") != updatedDetails.end())
2746 UpdateLinksToItem(idMovie, MediaTypeMovie, "country", details.m_country);
2747 if (updatedDetails.find("tag") != updatedDetails.end())
2748 UpdateLinksToItem(idMovie, MediaTypeMovie, "tag", details.m_tags);
2749 if (updatedDetails.find("director") != updatedDetails.end())
2750 UpdateActorLinksToItem(idMovie, MediaTypeMovie, "director", details.m_director);
2751 if (updatedDetails.find("writer") != updatedDetails.end())
2752 UpdateActorLinksToItem(idMovie, MediaTypeMovie, "writer", details.m_writingCredits);
2753 if (updatedDetails.find("art.altered") != updatedDetails.end())
2754 SetArtForItem(idMovie, MediaTypeMovie, artwork);
2755 if (updatedDetails.find("ratings") != updatedDetails.end())
2756 details.m_iIdRating = UpdateRatings(idMovie, MediaTypeMovie, details.m_ratings, details.GetDefaultRating());
2757 if (updatedDetails.find("uniqueid") != updatedDetails.end())
2758 details.m_iIdUniqueID = UpdateUniqueIDs(idMovie, MediaTypeMovie, details);
2759 if (updatedDetails.find("dateadded") != updatedDetails.end() && details.m_dateAdded.IsValid())
2760 UpdateFileDateAdded(details);
2762 // track if the set was updated
2763 int idSet = 0;
2764 if (updatedDetails.find("set") != updatedDetails.end())
2765 { // set
2766 idSet = -1;
2767 if (!details.m_set.title.empty())
2769 idSet = AddSet(details.m_set.title, details.m_set.overview);
2773 if (updatedDetails.find("showlink") != updatedDetails.end())
2775 // remove existing links
2776 std::vector<int> tvShowIds;
2777 GetLinksToTvShow(idMovie, tvShowIds);
2778 for (const auto& idTVShow : tvShowIds)
2779 LinkMovieToTvshow(idMovie, idTVShow, true);
2781 // setup links to shows if the linked shows are in the db
2782 for (const auto& showLink : details.m_showLink)
2784 CFileItemList items;
2785 GetTvShowsByName(showLink, items);
2786 if (!items.IsEmpty())
2787 LinkMovieToTvshow(idMovie, items[0]->GetVideoInfoTag()->m_iDbId, false);
2788 else
2789 CLog::Log(LOGWARNING, "{}: Failed to link movie {} to show {}", __FUNCTION__,
2790 details.m_strTitle, showLink);
2794 // and update the movie table
2795 std::string sql = "UPDATE movie SET " + GetValueString(details, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets);
2796 if (idSet > 0)
2797 sql += PrepareSQL(", idSet = %i", idSet);
2798 else if (idSet < 0)
2799 sql += ", idSet = NULL";
2800 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2801 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2802 else
2803 sql += ", userrating = NULL";
2804 if (details.HasPremiered())
2805 sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
2806 else
2807 sql += PrepareSQL(", premiered = '%i'", details.GetYear());
2808 sql += PrepareSQL(" where idMovie=%i", idMovie);
2809 m_pDS->exec(sql);
2811 CommitTransaction();
2813 CLog::Log(LOGINFO, "{}: Finished updates for movie {}", __FUNCTION__, idMovie);
2815 return idMovie;
2817 catch (...)
2819 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idMovie);
2821 RollbackTransaction();
2822 return -1;
2825 int CVideoDatabase::SetDetailsForMovieSet(const CVideoInfoTag& details, const std::map<std::string, std::string> &artwork, int idSet /* = -1 */)
2827 if (details.m_strTitle.empty())
2828 return -1;
2832 BeginTransaction();
2833 if (idSet < 0)
2835 idSet = AddSet(details.m_strTitle, details.m_strPlot);
2836 if (idSet < 0)
2838 RollbackTransaction();
2839 return -1;
2843 SetArtForItem(idSet, MediaTypeVideoCollection, artwork);
2845 // and insert the new row
2846 std::string sql = PrepareSQL("UPDATE sets SET strSet='%s', strOverview='%s' WHERE idSet=%i", details.m_strTitle.c_str(), details.m_strPlot.c_str(), idSet);
2847 m_pDS->exec(sql);
2848 CommitTransaction();
2850 return idSet;
2852 catch (...)
2854 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSet);
2856 RollbackTransaction();
2857 return -1;
2860 int CVideoDatabase::GetMatchingTvShow(const CVideoInfoTag &details)
2862 // first try matching on uniqueid, then on title + year
2863 int id = -1;
2864 if (!details.HasUniqueID())
2865 id = GetDbId(PrepareSQL("SELECT idShow FROM tvshow JOIN uniqueid ON uniqueid.media_id=tvshow.idShow AND uniqueid.media_type='tvshow' WHERE uniqueid.value='%s'", details.GetUniqueID().c_str()));
2866 if (id < 0)
2867 id = GetDbId(PrepareSQL("SELECT idShow FROM tvshow WHERE c%02d='%s' AND c%02d='%s'", VIDEODB_ID_TV_TITLE, details.m_strTitle.c_str(), VIDEODB_ID_TV_PREMIERED, details.GetPremiered().GetAsDBDate().c_str()));
2868 return id;
2871 int CVideoDatabase::SetDetailsForTvShow(const std::vector<std::pair<std::string, std::string> > &paths,
2872 CVideoInfoTag& details, const std::map<std::string, std::string> &artwork,
2873 const std::map<int, std::map<std::string, std::string> > &seasonArt, int idTvShow /*= -1 */)
2877 The steps are as follows.
2878 1. Check if the tvshow is found on any of the given paths. If found, we have the show id.
2879 2. Search for a matching show. If found, we have the show id.
2880 3. If we don't have the id, add a new show.
2881 4. Add the paths to the show.
2882 5. Add details for the show.
2885 if (idTvShow < 0)
2887 for (const auto &i : paths)
2889 idTvShow = GetTvShowId(i.first);
2890 if (idTvShow > -1)
2891 break;
2894 if (idTvShow < 0)
2895 idTvShow = GetMatchingTvShow(details);
2896 if (idTvShow < 0)
2898 idTvShow = AddTvShow();
2899 if (idTvShow < 0)
2900 return -1;
2903 // add any paths to the tvshow
2904 for (const auto &i : paths)
2905 AddPathToTvShow(idTvShow, i.first, i.second, details.m_dateAdded);
2907 UpdateDetailsForTvShow(idTvShow, details, artwork, seasonArt);
2909 return idTvShow;
2912 bool CVideoDatabase::UpdateDetailsForTvShow(int idTvShow, CVideoInfoTag &details,
2913 const std::map<std::string, std::string> &artwork, const std::map<int, std::map<std::string, std::string>> &seasonArt)
2915 BeginTransaction();
2917 DeleteDetailsForTvShow(idTvShow);
2919 AddCast(idTvShow, "tvshow", details.m_cast);
2920 AddLinksToItem(idTvShow, MediaTypeTvShow, "genre", details.m_genre);
2921 AddLinksToItem(idTvShow, MediaTypeTvShow, "studio", details.m_studio);
2922 AddLinksToItem(idTvShow, MediaTypeTvShow, "tag", details.m_tags);
2923 AddActorLinksToItem(idTvShow, MediaTypeTvShow, "director", details.m_director);
2925 // add ratings
2926 details.m_iIdRating = AddRatings(idTvShow, MediaTypeTvShow, details.m_ratings, details.GetDefaultRating());
2928 // add unique ids
2929 details.m_iIdUniqueID = AddUniqueIDs(idTvShow, MediaTypeTvShow, details);
2931 // add "all seasons" - the rest are added in SetDetailsForEpisode
2932 AddSeason(idTvShow, -1);
2934 // add any named seasons
2935 for (const auto& namedSeason : details.m_namedSeasons)
2937 // make sure the named season exists
2938 int seasonId = AddSeason(idTvShow, namedSeason.first, namedSeason.second);
2940 // get any existing details for the named season
2941 CVideoInfoTag season;
2942 if (!GetSeasonInfo(seasonId, season, false) || season.m_strSortTitle == namedSeason.second)
2943 continue;
2945 season.SetSortTitle(namedSeason.second);
2946 SetDetailsForSeason(season, std::map<std::string, std::string>(), idTvShow, seasonId);
2949 SetArtForItem(idTvShow, MediaTypeTvShow, artwork);
2950 for (const auto &i : seasonArt)
2952 int idSeason = AddSeason(idTvShow, i.first);
2953 if (idSeason > -1)
2954 SetArtForItem(idSeason, MediaTypeSeason, i.second);
2957 // and insert the new row
2958 std::string sql = "UPDATE tvshow SET " + GetValueString(details, VIDEODB_ID_TV_MIN, VIDEODB_ID_TV_MAX, DbTvShowOffsets);
2959 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2960 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2961 else
2962 sql += ", userrating = NULL";
2963 if (details.GetDuration() > 0)
2964 sql += PrepareSQL(", duration = %i", details.GetDuration());
2965 else
2966 sql += ", duration = NULL";
2967 sql += PrepareSQL(" WHERE idShow=%i", idTvShow);
2968 if (ExecuteQuery(sql))
2970 CommitTransaction();
2971 return true;
2973 RollbackTransaction();
2974 return false;
2977 int CVideoDatabase::SetDetailsForSeason(const CVideoInfoTag& details, const std::map<std::string,
2978 std::string> &artwork, int idShow, int idSeason /* = -1 */)
2980 if (idShow < 0 || details.m_iSeason < -1)
2981 return -1;
2985 BeginTransaction();
2986 if (idSeason < 0)
2988 idSeason = AddSeason(idShow, details.m_iSeason);
2989 if (idSeason < 0)
2991 RollbackTransaction();
2992 return -1;
2996 SetArtForItem(idSeason, MediaTypeSeason, artwork);
2998 // and insert the new row
2999 std::string sql = PrepareSQL("UPDATE seasons SET season=%i", details.m_iSeason);
3000 if (!details.m_strSortTitle.empty())
3001 sql += PrepareSQL(", name='%s'", details.m_strSortTitle.c_str());
3002 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
3003 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
3004 else
3005 sql += ", userrating = NULL";
3006 sql += PrepareSQL(" WHERE idSeason=%i", idSeason);
3007 m_pDS->exec(sql.c_str());
3008 CommitTransaction();
3010 return idSeason;
3012 catch (...)
3014 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSeason);
3016 RollbackTransaction();
3017 return -1;
3020 bool CVideoDatabase::SetFileForEpisode(const std::string& fileAndPath, int idEpisode, int idFile)
3024 std::string sql = PrepareSQL("UPDATE episode SET c18='%s', idFile=%i WHERE idEpisode=%i",
3025 fileAndPath.c_str(), idFile, idEpisode);
3026 m_pDS->exec(sql);
3028 return true;
3030 catch (...)
3032 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idEpisode);
3034 return false;
3037 bool CVideoDatabase::SetFileForMovie(const std::string& fileAndPath, int idMovie, int idFile)
3041 assert(m_pDB->in_transaction());
3043 std::string sql = PrepareSQL("UPDATE movie SET c22='%s', idFile=%i WHERE idMovie=%i",
3044 fileAndPath.c_str(), idFile, idMovie);
3045 m_pDS->exec(sql);
3046 sql = PrepareSQL("UPDATE videoversion SET idFile=%i WHERE idMedia=%i AND media_type='movie'",
3047 idFile, idMovie);
3048 m_pDS->exec(sql);
3050 return true;
3052 catch (...)
3054 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idMovie);
3056 return false;
3059 int CVideoDatabase::SetDetailsForEpisode(CVideoInfoTag& details,
3060 const std::map<std::string, std::string>& artwork,
3061 int idShow,
3062 int idEpisode /* = -1 */)
3064 const auto filePath = details.GetPath();
3068 BeginTransaction();
3069 if (idEpisode < 0)
3070 idEpisode = GetEpisodeId(filePath);
3072 if (idEpisode > 0)
3073 DeleteEpisode(idEpisode, true); // true to keep the table entry
3074 else
3076 // only add a new episode if we don't already have a valid idEpisode
3077 // (DeleteEpisode is called with bKeepId == true so the episode won't
3078 // be removed from the episode table)
3079 idEpisode = AddNewEpisode(idShow, details);
3080 if (idEpisode < 0)
3082 RollbackTransaction();
3083 return -1;
3087 // update dateadded if it's set
3088 if (details.m_dateAdded.IsValid())
3089 UpdateFileDateAdded(details);
3091 AddCast(idEpisode, "episode", details.m_cast);
3092 AddActorLinksToItem(idEpisode, MediaTypeEpisode, "director", details.m_director);
3093 AddActorLinksToItem(idEpisode, MediaTypeEpisode, "writer", details.m_writingCredits);
3095 // add ratings
3096 details.m_iIdRating = AddRatings(idEpisode, MediaTypeEpisode, details.m_ratings, details.GetDefaultRating());
3098 // add unique ids
3099 details.m_iIdUniqueID = AddUniqueIDs(idEpisode, MediaTypeEpisode, details);
3101 if (details.HasStreamDetails())
3102 SetStreamDetailsForFileId(details.m_streamDetails, GetAndFillFileId(details));
3104 // ensure we have this season already added
3105 int idSeason = AddSeason(idShow, details.m_iSeason);
3107 SetArtForItem(idEpisode, MediaTypeEpisode, artwork);
3109 if (details.m_iEpisode != -1 && details.m_iSeason != -1)
3110 { // query DB for any episodes matching idShow, Season and Episode
3111 std::string strSQL = PrepareSQL("SELECT files.playCount, files.lastPlayed "
3112 "FROM episode INNER JOIN files ON files.idFile=episode.idFile "
3113 "WHERE episode.c%02d=%i AND episode.c%02d=%i AND episode.idShow=%i "
3114 "AND episode.idEpisode!=%i AND files.playCount > 0",
3115 VIDEODB_ID_EPISODE_SEASON, details.m_iSeason, VIDEODB_ID_EPISODE_EPISODE,
3116 details.m_iEpisode, idShow, idEpisode);
3117 m_pDS->query(strSQL);
3119 if (!m_pDS->eof())
3121 int playCount = m_pDS->fv("files.playCount").get_asInt();
3123 CDateTime lastPlayed;
3124 lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
3126 // update with playCount and lastPlayed
3127 strSQL =
3128 PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", playCount,
3129 lastPlayed.GetAsDBDateTime().c_str(), GetAndFillFileId(details));
3130 m_pDS->exec(strSQL);
3133 m_pDS->close();
3135 // and insert the new row
3136 std::string sql = "UPDATE episode SET " + GetValueString(details, VIDEODB_ID_EPISODE_MIN, VIDEODB_ID_EPISODE_MAX, DbEpisodeOffsets);
3137 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
3138 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
3139 else
3140 sql += ", userrating = NULL";
3141 sql += PrepareSQL(", idSeason = %i", idSeason);
3142 sql += PrepareSQL(" where idEpisode=%i", idEpisode);
3143 m_pDS->exec(sql);
3144 CommitTransaction();
3146 return idEpisode;
3148 catch (...)
3150 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
3152 RollbackTransaction();
3153 return -1;
3156 int CVideoDatabase::GetSeasonId(int showID, int season)
3158 std::string sql = PrepareSQL("idShow=%i AND season=%i", showID, season);
3159 std::string id = GetSingleValue("seasons", "idSeason", sql);
3160 if (id.empty())
3161 return -1;
3162 return strtol(id.c_str(), NULL, 10);
3165 int CVideoDatabase::AddSeason(int showID, int season, const std::string& name /* = "" */)
3167 int seasonId = GetSeasonId(showID, season);
3168 if (seasonId < 0)
3170 if (ExecuteQuery(PrepareSQL("INSERT INTO seasons (idShow, season, name) VALUES(%i, %i, '%s')", showID, season, name.c_str())))
3171 seasonId = (int)m_pDS->lastinsertid();
3173 return seasonId;
3176 int CVideoDatabase::SetDetailsForMusicVideo(CVideoInfoTag& details,
3177 const std::map<std::string, std::string>& artwork,
3178 int idMVideo /* = -1 */)
3180 const auto filePath = details.GetPath();
3184 BeginTransaction();
3186 if (idMVideo < 0)
3187 idMVideo = GetMusicVideoId(filePath);
3189 if (idMVideo > -1)
3190 DeleteMusicVideo(idMVideo, true); // Keep id
3191 else
3193 // only add a new musicvideo if we don't already have a valid idMVideo
3194 // (DeleteMusicVideo is called with bKeepId == true so the musicvideo won't
3195 // be removed from the musicvideo table)
3196 idMVideo = AddNewMusicVideo(details);
3197 if (idMVideo < 0)
3199 RollbackTransaction();
3200 return -1;
3204 // update dateadded if it's set
3205 if (details.m_dateAdded.IsValid())
3206 UpdateFileDateAdded(details);
3208 AddCast(idMVideo, MediaTypeMusicVideo, details.m_cast);
3209 AddActorLinksToItem(idMVideo, MediaTypeMusicVideo, "actor", details.m_artist);
3210 AddActorLinksToItem(idMVideo, MediaTypeMusicVideo, "director", details.m_director);
3211 AddLinksToItem(idMVideo, MediaTypeMusicVideo, "genre", details.m_genre);
3212 AddLinksToItem(idMVideo, MediaTypeMusicVideo, "studio", details.m_studio);
3213 AddLinksToItem(idMVideo, MediaTypeMusicVideo, "tag", details.m_tags);
3215 // add unique ids
3216 details.m_iIdUniqueID = UpdateUniqueIDs(idMVideo, MediaTypeMusicVideo, details);
3218 if (details.HasStreamDetails())
3219 SetStreamDetailsForFileId(details.m_streamDetails, GetAndFillFileId(details));
3221 SetArtForItem(idMVideo, MediaTypeMusicVideo, artwork);
3223 // update our movie table (we know it was added already above)
3224 // and insert the new row
3225 std::string sql = "UPDATE musicvideo SET " + GetValueString(details, VIDEODB_ID_MUSICVIDEO_MIN, VIDEODB_ID_MUSICVIDEO_MAX, DbMusicVideoOffsets);
3226 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
3227 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
3228 else
3229 sql += ", userrating = NULL";
3230 if (details.HasPremiered())
3231 sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
3232 else
3233 sql += PrepareSQL(", premiered = '%i'", details.GetYear());
3234 sql += PrepareSQL(" where idMVideo=%i", idMVideo);
3235 m_pDS->exec(sql);
3236 CommitTransaction();
3238 return idMVideo;
3240 catch (...)
3242 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
3244 RollbackTransaction();
3245 return -1;
3248 int CVideoDatabase::SetStreamDetailsForFile(const CStreamDetails& details,
3249 const std::string& strFileNameAndPath)
3251 // AddFile checks to make sure the file isn't already in the DB first
3252 int idFile = AddFile(strFileNameAndPath);
3253 if (idFile < 0)
3254 return -1;
3256 //! @todo ugly error return mechanism, fixme
3257 if (SetStreamDetailsForFileId(details, idFile))
3258 return idFile;
3259 else
3260 return -2;
3263 bool CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails& details, int idFile)
3265 if (idFile < 0)
3266 return false;
3270 m_pDS->exec(PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", idFile));
3272 for (int i=1; i<=details.GetVideoStreamCount(); i++)
3274 m_pDS->exec(PrepareSQL(
3275 "INSERT INTO streamdetails "
3276 "(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth, iVideoHeight, "
3277 "iVideoDuration, strStereoMode, strVideoLanguage, strHdrType) "
3278 "VALUES (%i,%i,'%s',%f,%i,%i,%i,'%s','%s','%s')",
3279 idFile, static_cast<int>(CStreamDetail::VIDEO), details.GetVideoCodec(i).c_str(),
3280 static_cast<double>(details.GetVideoAspect(i)), details.GetVideoWidth(i),
3281 details.GetVideoHeight(i), details.GetVideoDuration(i), details.GetStereoMode(i).c_str(),
3282 details.GetVideoLanguage(i).c_str(), details.GetVideoHdrType(i).c_str()));
3284 for (int i=1; i<=details.GetAudioStreamCount(); i++)
3286 m_pDS->exec(PrepareSQL(
3287 "INSERT INTO streamdetails "
3288 "(idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) "
3289 "VALUES (%i,%i,'%s',%i,'%s')",
3290 idFile, static_cast<int>(CStreamDetail::AUDIO), details.GetAudioCodec(i).c_str(),
3291 details.GetAudioChannels(i), details.GetAudioLanguage(i).c_str()));
3293 for (int i=1; i<=details.GetSubtitleStreamCount(); i++)
3295 m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
3296 "(idFile, iStreamType, strSubtitleLanguage) "
3297 "VALUES (%i,%i,'%s')",
3298 idFile, static_cast<int>(CStreamDetail::SUBTITLE),
3299 details.GetSubtitleLanguage(i).c_str()));
3302 // update the runtime information, if empty
3303 if (details.GetVideoDuration())
3305 std::vector<std::pair<std::string, int> > tables;
3306 tables.emplace_back("movie", VIDEODB_ID_RUNTIME);
3307 tables.emplace_back("episode", VIDEODB_ID_EPISODE_RUNTIME);
3308 tables.emplace_back("musicvideo", VIDEODB_ID_MUSICVIDEO_RUNTIME);
3309 for (const auto &i : tables)
3311 std::string sql = PrepareSQL("update %s set c%02d=%d where idFile=%d and c%02d=''",
3312 i.first.c_str(), i.second, details.GetVideoDuration(), idFile, i.second);
3313 m_pDS->exec(sql);
3316 return true;
3318 catch (...)
3320 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idFile);
3322 return false;
3325 //********************************************************************************************************************************
3326 void CVideoDatabase::GetFilePathById(int idMovie, std::string& filePath, VideoDbContentType iType)
3330 if (nullptr == m_pDB)
3331 return;
3332 if (nullptr == m_pDS)
3333 return;
3335 if (idMovie < 0) return ;
3337 std::string strSQL;
3338 if (iType == VideoDbContentType::MOVIES)
3339 strSQL=PrepareSQL("SELECT path.strPath, files.strFileName FROM path INNER JOIN files ON path.idPath=files.idPath INNER JOIN movie ON files.idFile=movie.idFile WHERE movie.idMovie=%i ORDER BY strFilename", idMovie );
3340 if (iType == VideoDbContentType::EPISODES)
3341 strSQL=PrepareSQL("SELECT path.strPath, files.strFileName FROM path INNER JOIN files ON path.idPath=files.idPath INNER JOIN episode ON files.idFile=episode.idFile WHERE episode.idEpisode=%i ORDER BY strFilename", idMovie );
3342 if (iType == VideoDbContentType::TVSHOWS)
3343 strSQL=PrepareSQL("SELECT path.strPath FROM path INNER JOIN tvshowlinkpath ON path.idPath=tvshowlinkpath.idPath WHERE tvshowlinkpath.idShow=%i", idMovie );
3344 if (iType == VideoDbContentType::MUSICVIDEOS)
3345 strSQL=PrepareSQL("SELECT path.strPath, files.strFileName FROM path INNER JOIN files ON path.idPath=files.idPath INNER JOIN musicvideo ON files.idFile=musicvideo.idFile WHERE musicvideo.idMVideo=%i ORDER BY strFilename", idMovie );
3347 m_pDS->query( strSQL );
3348 if (!m_pDS->eof())
3350 if (iType != VideoDbContentType::TVSHOWS)
3352 std::string fileName = m_pDS->fv("files.strFilename").get_asString();
3353 ConstructPath(filePath,m_pDS->fv("path.strPath").get_asString(),fileName);
3355 else
3356 filePath = m_pDS->fv("path.strPath").get_asString();
3358 m_pDS->close();
3360 catch (...)
3362 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
3366 //********************************************************************************************************************************
3367 void CVideoDatabase::GetBookMarksForFile(const std::string& strFilenameAndPath, VECBOOKMARKS& bookmarks, CBookmark::EType type /*= CBookmark::STANDARD*/, bool bAppend, long partNumber)
3371 if (URIUtils::IsDiscImageStack(strFilenameAndPath))
3373 CStackDirectory dir;
3374 CFileItemList fileList;
3375 const CURL pathToUrl(strFilenameAndPath);
3376 dir.GetDirectory(pathToUrl, fileList);
3377 if (!bAppend)
3378 bookmarks.clear();
3379 for (int i = fileList.Size() - 1; i >= 0; i--) // put the bookmarks of the highest part first in the list
3380 GetBookMarksForFile(fileList[i]->GetPath(), bookmarks, type, true, (i+1));
3382 else
3384 int idFile = GetFileId(strFilenameAndPath);
3385 if (idFile < 0) return ;
3386 if (!bAppend)
3387 bookmarks.erase(bookmarks.begin(), bookmarks.end());
3388 if (nullptr == m_pDB)
3389 return;
3390 if (nullptr == m_pDS)
3391 return;
3393 std::string strSQL=PrepareSQL("select * from bookmark where idFile=%i and type=%i order by timeInSeconds", idFile, (int)type);
3394 m_pDS->query( strSQL );
3395 while (!m_pDS->eof())
3397 CBookmark bookmark;
3398 bookmark.timeInSeconds = m_pDS->fv("timeInSeconds").get_asDouble();
3399 bookmark.partNumber = partNumber;
3400 bookmark.totalTimeInSeconds = m_pDS->fv("totalTimeInSeconds").get_asDouble();
3401 bookmark.thumbNailImage = m_pDS->fv("thumbNailImage").get_asString();
3402 bookmark.playerState = m_pDS->fv("playerState").get_asString();
3403 bookmark.player = m_pDS->fv("player").get_asString();
3404 bookmark.type = type;
3405 if (type == CBookmark::EPISODE)
3407 std::string strSQL2=PrepareSQL("select c%02d, c%02d from episode where c%02d=%i order by c%02d, c%02d", VIDEODB_ID_EPISODE_EPISODE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_BOOKMARK, m_pDS->fv("idBookmark").get_asInt(), VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTEPISODE);
3408 m_pDS2->query(strSQL2);
3409 bookmark.episodeNumber = m_pDS2->fv(0).get_asInt();
3410 bookmark.seasonNumber = m_pDS2->fv(1).get_asInt();
3411 m_pDS2->close();
3413 bookmarks.push_back(bookmark);
3414 m_pDS->next();
3416 //sort(bookmarks.begin(), bookmarks.end(), SortBookmarks);
3417 m_pDS->close();
3420 catch (...)
3422 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3426 bool CVideoDatabase::GetResumeBookMark(const std::string& strFilenameAndPath, CBookmark &bookmark)
3428 VECBOOKMARKS bookmarks;
3429 GetBookMarksForFile(strFilenameAndPath, bookmarks, CBookmark::RESUME);
3430 if (!bookmarks.empty())
3432 bookmark = bookmarks[0];
3433 return true;
3435 return false;
3438 void CVideoDatabase::DeleteResumeBookMark(const CFileItem& item)
3440 if (!m_pDB || !m_pDS)
3441 return;
3443 int fileID = item.GetVideoInfoTag()->m_iFileId;
3444 if (fileID < 0)
3446 fileID = GetFileId(item.GetPath());
3447 if (fileID < 0)
3448 return;
3453 std::string sql = PrepareSQL("delete from bookmark where idFile=%i and type=%i", fileID, CBookmark::RESUME);
3454 m_pDS->exec(sql);
3456 VideoDbContentType iType = static_cast<VideoDbContentType>(item.GetVideoContentType());
3457 std::string content;
3458 switch (iType)
3460 case VideoDbContentType::MOVIES:
3461 content = MediaTypeMovie;
3462 break;
3463 case VideoDbContentType::EPISODES:
3464 content = MediaTypeEpisode;
3465 break;
3466 case VideoDbContentType::TVSHOWS:
3467 content = MediaTypeTvShow;
3468 break;
3469 case VideoDbContentType::MUSICVIDEOS:
3470 content = MediaTypeMusicVideo;
3471 break;
3472 default:
3473 break;
3476 if (!content.empty())
3478 AnnounceUpdate(content, item.GetVideoInfoTag()->m_iDbId);
3482 catch(...)
3484 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__,
3485 item.GetVideoInfoTag()->m_strFileNameAndPath);
3489 void CVideoDatabase::GetEpisodesByFile(const std::string& strFilenameAndPath, std::vector<CVideoInfoTag>& episodes)
3493 std::string strSQL = PrepareSQL("select * from episode_view where idFile=%i order by c%02d, c%02d asc", GetFileId(strFilenameAndPath), VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTEPISODE);
3494 m_pDS->query(strSQL);
3495 while (!m_pDS->eof())
3497 episodes.emplace_back(GetDetailsForEpisode(m_pDS));
3498 m_pDS->next();
3500 m_pDS->close();
3502 catch (...)
3504 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3508 //********************************************************************************************************************************
3509 void CVideoDatabase::AddBookMarkToFile(const std::string& strFilenameAndPath, const CBookmark &bookmark, CBookmark::EType type /*= CBookmark::STANDARD*/)
3513 int idFile = AddFile(strFilenameAndPath);
3514 if (idFile < 0)
3515 return;
3516 if (nullptr == m_pDB)
3517 return;
3518 if (nullptr == m_pDS)
3519 return;
3521 std::string strSQL;
3522 int idBookmark=-1;
3523 if (type == CBookmark::RESUME) // get the same resume mark bookmark each time type
3525 strSQL=PrepareSQL("select idBookmark from bookmark where idFile=%i and type=1", idFile);
3527 else if (type == CBookmark::STANDARD) // get the same bookmark again, and update. not sure here as a dvd can have same time in multiple places, state will differ thou
3529 /* get a bookmark within the same time as previous */
3530 double mintime = bookmark.timeInSeconds - 0.5;
3531 double maxtime = bookmark.timeInSeconds + 0.5;
3532 strSQL=PrepareSQL("select idBookmark from bookmark where idFile=%i and type=%i and (timeInSeconds between %f and %f) and playerState='%s'", idFile, (int)type, mintime, maxtime, bookmark.playerState.c_str());
3535 if (type != CBookmark::EPISODE)
3537 // get current id
3538 m_pDS->query( strSQL );
3539 if (m_pDS->num_rows() != 0)
3540 idBookmark = m_pDS->get_field_value("idBookmark").get_asInt();
3541 m_pDS->close();
3543 // update or insert depending if it existed before
3544 if (idBookmark >= 0 )
3545 strSQL=PrepareSQL("update bookmark set timeInSeconds = %f, totalTimeInSeconds = %f, thumbNailImage = '%s', player = '%s', playerState = '%s' where idBookmark = %i", bookmark.timeInSeconds, bookmark.totalTimeInSeconds, bookmark.thumbNailImage.c_str(), bookmark.player.c_str(), bookmark.playerState.c_str(), idBookmark);
3546 else
3547 strSQL=PrepareSQL("insert into bookmark (idBookmark, idFile, timeInSeconds, totalTimeInSeconds, thumbNailImage, player, playerState, type) values(NULL,%i,%f,%f,'%s','%s','%s', %i)", idFile, bookmark.timeInSeconds, bookmark.totalTimeInSeconds, bookmark.thumbNailImage.c_str(), bookmark.player.c_str(), bookmark.playerState.c_str(), (int)type);
3549 m_pDS->exec(strSQL);
3551 catch (...)
3553 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3557 void CVideoDatabase::ClearBookMarkOfFile(const std::string& strFilenameAndPath, CBookmark& bookmark, CBookmark::EType type /*= CBookmark::STANDARD*/)
3561 int idFile = GetFileId(strFilenameAndPath);
3562 if (idFile < 0) return ;
3563 if (nullptr == m_pDB)
3564 return;
3565 if (nullptr == m_pDS)
3566 return;
3568 /* a little bit ugly, we clear first bookmark that is within one second of given */
3569 /* should be no problem since we never add bookmarks that are closer than that */
3570 double mintime = bookmark.timeInSeconds - 0.5;
3571 double maxtime = bookmark.timeInSeconds + 0.5;
3572 std::string strSQL = PrepareSQL("select idBookmark from bookmark where idFile=%i and type=%i and playerState like '%s' and player like '%s' and (timeInSeconds between %f and %f)", idFile, type, bookmark.playerState.c_str(), bookmark.player.c_str(), mintime, maxtime);
3574 m_pDS->query( strSQL );
3575 if (m_pDS->num_rows() != 0)
3577 int idBookmark = m_pDS->get_field_value("idBookmark").get_asInt();
3578 strSQL=PrepareSQL("delete from bookmark where idBookmark=%i",idBookmark);
3579 m_pDS->exec(strSQL);
3580 if (type == CBookmark::EPISODE)
3582 strSQL=PrepareSQL("update episode set c%02d=-1 where idFile=%i and c%02d=%i", VIDEODB_ID_EPISODE_BOOKMARK, idFile, VIDEODB_ID_EPISODE_BOOKMARK, idBookmark);
3583 m_pDS->exec(strSQL);
3587 m_pDS->close();
3589 catch (...)
3591 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3595 //********************************************************************************************************************************
3596 void CVideoDatabase::ClearBookMarksOfFile(const std::string& strFilenameAndPath, CBookmark::EType type /*= CBookmark::STANDARD*/)
3598 int idFile = GetFileId(strFilenameAndPath);
3599 if (idFile >= 0)
3600 return ClearBookMarksOfFile(idFile, type);
3603 void CVideoDatabase::ClearBookMarksOfFile(int idFile, CBookmark::EType type /*= CBookmark::STANDARD*/)
3605 if (idFile < 0)
3606 return;
3610 if (nullptr == m_pDB)
3611 return;
3612 if (nullptr == m_pDS)
3613 return;
3615 std::string strSQL=PrepareSQL("delete from bookmark where idFile=%i and type=%i", idFile, (int)type);
3616 m_pDS->exec(strSQL);
3617 if (type == CBookmark::EPISODE)
3619 strSQL=PrepareSQL("update episode set c%02d=-1 where idFile=%i", VIDEODB_ID_EPISODE_BOOKMARK, idFile);
3620 m_pDS->exec(strSQL);
3623 catch (...)
3625 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idFile);
3630 bool CVideoDatabase::GetBookMarkForEpisode(const CVideoInfoTag& tag, CBookmark& bookmark)
3634 std::string strSQL = PrepareSQL("select bookmark.* from bookmark join episode on episode.c%02d=bookmark.idBookmark where episode.idEpisode=%i", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
3635 m_pDS2->query( strSQL );
3636 if (!m_pDS2->eof())
3638 bookmark.timeInSeconds = m_pDS2->fv("timeInSeconds").get_asDouble();
3639 bookmark.totalTimeInSeconds = m_pDS2->fv("totalTimeInSeconds").get_asDouble();
3640 bookmark.thumbNailImage = m_pDS2->fv("thumbNailImage").get_asString();
3641 bookmark.playerState = m_pDS2->fv("playerState").get_asString();
3642 bookmark.player = m_pDS2->fv("player").get_asString();
3643 bookmark.type = (CBookmark::EType)m_pDS2->fv("type").get_asInt();
3645 else
3647 m_pDS2->close();
3648 return false;
3650 m_pDS2->close();
3652 catch (...)
3654 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
3655 return false;
3657 return true;
3660 void CVideoDatabase::AddBookMarkForEpisode(const CVideoInfoTag& tag, const CBookmark& bookmark)
3664 int idFile = GetFileId(tag.m_strFileNameAndPath);
3665 // delete the current episode for the selected episode number
3666 std::string strSQL = PrepareSQL("delete from bookmark where idBookmark in (select c%02d from episode where c%02d=%i and c%02d=%i and idFile=%i)", VIDEODB_ID_EPISODE_BOOKMARK, VIDEODB_ID_EPISODE_SEASON, tag.m_iSeason, VIDEODB_ID_EPISODE_EPISODE, tag.m_iEpisode, idFile);
3667 m_pDS->exec(strSQL);
3669 AddBookMarkToFile(tag.m_strFileNameAndPath, bookmark, CBookmark::EPISODE);
3670 int idBookmark = (int)m_pDS->lastinsertid();
3671 strSQL = PrepareSQL("update episode set c%02d=%i where c%02d=%i and c%02d=%i and idFile=%i", VIDEODB_ID_EPISODE_BOOKMARK, idBookmark, VIDEODB_ID_EPISODE_SEASON, tag.m_iSeason, VIDEODB_ID_EPISODE_EPISODE, tag.m_iEpisode, idFile);
3672 m_pDS->exec(strSQL);
3674 catch (...)
3676 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, tag.m_iDbId);
3680 void CVideoDatabase::DeleteBookMarkForEpisode(const CVideoInfoTag& tag)
3684 std::string strSQL = PrepareSQL("delete from bookmark where idBookmark in (select c%02d from episode where idEpisode=%i)", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
3685 m_pDS->exec(strSQL);
3686 strSQL = PrepareSQL("update episode set c%02d=-1 where idEpisode=%i", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
3687 m_pDS->exec(strSQL);
3689 catch (...)
3691 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, tag.m_iDbId);
3695 //********************************************************************************************************************************
3696 void CVideoDatabase::DeleteMovie(int idMovie,
3697 DeleteMovieCascadeAction ca /* = ALL_ASSETS */,
3698 DeleteMovieHashAction hashAction /* = HASH_DELETE */)
3700 if (idMovie < 0)
3701 return;
3705 if (nullptr == m_pDB)
3706 return;
3707 if (nullptr == m_pDS)
3708 return;
3710 BeginTransaction();
3712 const int idFile{GetDbId(PrepareSQL("SELECT idFile FROM movie WHERE idMovie=%i", idMovie))};
3713 DeleteStreamDetails(idFile);
3715 if (hashAction == DeleteMovieHashAction::HASH_DELETE)
3717 const std::string path = GetSingleValue(PrepareSQL(
3718 "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i",
3719 idFile));
3720 if (!path.empty())
3721 InvalidatePathHash(path);
3724 const std::string strSQL{PrepareSQL("DELETE FROM movie WHERE idMovie=%i", idMovie)};
3725 m_pDS->exec(strSQL);
3727 if (ca == DeleteMovieCascadeAction::ALL_ASSETS)
3729 // The default version of the movie was removed by a delete trigger.
3730 // Clean up the other assets attached to the movie, if any.
3732 // need local dataset due to nested DeleteVideoAsset query
3733 const std::unique_ptr<Dataset> pDS{m_pDB->CreateDataset()};
3735 pDS->query(PrepareSQL("SELECT idFile FROM videoversion WHERE idMedia=%i AND media_type='%s'",
3736 idMovie, MediaTypeMovie));
3738 while (!pDS->eof())
3740 if (!DeleteVideoAsset(pDS->fv(0).get_asInt()))
3742 RollbackTransaction();
3743 pDS->close();
3744 return;
3746 pDS->next();
3748 pDS->close();
3751 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3752 AnnounceRemove(MediaTypeMovie, idMovie);
3754 CommitTransaction();
3756 catch (...)
3758 CLog::LogF(LOGERROR, "failed");
3759 RollbackTransaction();
3763 void CVideoDatabase::DeleteTvShow(const std::string& strPath)
3765 int idTvShow = GetTvShowId(strPath);
3766 if (idTvShow >= 0)
3767 DeleteTvShow(idTvShow);
3770 void CVideoDatabase::DeleteTvShow(int idTvShow, bool bKeepId /* = false */)
3772 if (idTvShow < 0)
3773 return;
3777 if (nullptr == m_pDB)
3778 return;
3779 if (nullptr == m_pDS)
3780 return;
3782 BeginTransaction();
3784 std::set<int> paths;
3785 GetPathsForTvShow(idTvShow, paths);
3787 std::string strSQL=PrepareSQL("SELECT episode.idEpisode FROM episode WHERE episode.idShow=%i",idTvShow);
3788 m_pDS2->query(strSQL);
3789 while (!m_pDS2->eof())
3791 DeleteEpisode(m_pDS2->fv(0).get_asInt(), bKeepId);
3792 m_pDS2->next();
3795 DeleteDetailsForTvShow(idTvShow);
3797 strSQL=PrepareSQL("delete from seasons where idShow=%i", idTvShow);
3798 m_pDS->exec(strSQL);
3800 // keep tvshow table and movielink table so we can update data in place
3801 if (!bKeepId)
3803 strSQL=PrepareSQL("delete from tvshow where idShow=%i", idTvShow);
3804 m_pDS->exec(strSQL);
3806 for (const auto &i : paths)
3808 std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path WHERE idPath=%i", i));
3809 if (!path.empty())
3810 InvalidatePathHash(path);
3814 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3815 if (!bKeepId)
3816 AnnounceRemove(MediaTypeTvShow, idTvShow);
3818 CommitTransaction();
3821 catch (...)
3823 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idTvShow);
3824 RollbackTransaction();
3828 void CVideoDatabase::DeleteSeason(int idSeason, bool bKeepId /* = false */)
3830 if (idSeason < 0)
3831 return;
3835 if (m_pDB == nullptr || m_pDS == nullptr || m_pDS2 == nullptr)
3836 return;
3838 BeginTransaction();
3840 std::string strSQL = PrepareSQL("SELECT episode.idEpisode FROM episode "
3841 "JOIN seasons ON seasons.idSeason = %d AND episode.idShow = seasons.idShow AND episode.c%02d = seasons.season ",
3842 idSeason, VIDEODB_ID_EPISODE_SEASON);
3843 m_pDS2->query(strSQL);
3844 while (!m_pDS2->eof())
3846 DeleteEpisode(m_pDS2->fv(0).get_asInt(), bKeepId);
3847 m_pDS2->next();
3850 ExecuteQuery(PrepareSQL("DELETE FROM seasons WHERE idSeason = %i", idSeason));
3852 CommitTransaction();
3854 catch (...)
3856 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSeason);
3857 RollbackTransaction();
3861 void CVideoDatabase::DeleteEpisode(int idEpisode, bool bKeepId /* = false */)
3863 if (idEpisode < 0)
3864 return;
3868 if (nullptr == m_pDB)
3869 return;
3870 if (nullptr == m_pDS)
3871 return;
3873 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3874 if (!bKeepId)
3875 AnnounceRemove(MediaTypeEpisode, idEpisode);
3877 int idFile = GetDbId(PrepareSQL("SELECT idFile FROM episode WHERE idEpisode=%i", idEpisode));
3878 DeleteStreamDetails(idFile);
3880 // keep episode table entry and bookmarks so we can update the data in place
3881 // the ancillary tables are still purged
3882 if (!bKeepId)
3884 std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile));
3885 if (!path.empty())
3886 InvalidatePathHash(path);
3888 std::string strSQL = PrepareSQL("delete from episode where idEpisode=%i", idEpisode);
3889 m_pDS->exec(strSQL);
3893 catch (...)
3895 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idEpisode);
3899 void CVideoDatabase::DeleteMusicVideo(int idMVideo, bool bKeepId /* = false */)
3901 if (idMVideo < 0)
3902 return;
3906 if (nullptr == m_pDB)
3907 return;
3908 if (nullptr == m_pDS)
3909 return;
3911 BeginTransaction();
3913 int idFile = GetDbId(PrepareSQL("SELECT idFile FROM musicvideo WHERE idMVideo=%i", idMVideo));
3914 DeleteStreamDetails(idFile);
3916 // keep the music video table entry and bookmarks so we can update data in place
3917 // the ancillary tables are still purged
3918 if (!bKeepId)
3920 std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile));
3921 if (!path.empty())
3922 InvalidatePathHash(path);
3924 std::string strSQL = PrepareSQL("delete from musicvideo where idMVideo=%i", idMVideo);
3925 m_pDS->exec(strSQL);
3928 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3929 if (!bKeepId)
3930 AnnounceRemove(MediaTypeMusicVideo, idMVideo);
3932 CommitTransaction();
3935 catch (...)
3937 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
3938 RollbackTransaction();
3942 int CVideoDatabase::GetDbId(const std::string &query)
3944 std::string result = GetSingleValue(query);
3945 if (!result.empty())
3947 int idDb = strtol(result.c_str(), NULL, 10);
3948 if (idDb > 0)
3949 return idDb;
3951 return -1;
3954 void CVideoDatabase::DeleteStreamDetails(int idFile)
3956 m_pDS->exec(PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", idFile));
3959 void CVideoDatabase::DeleteSet(int idSet)
3963 if (nullptr == m_pDB)
3964 return;
3965 if (nullptr == m_pDS)
3966 return;
3968 std::string strSQL;
3969 strSQL=PrepareSQL("delete from sets where idSet = %i", idSet);
3970 m_pDS->exec(strSQL);
3971 strSQL=PrepareSQL("update movie set idSet = null where idSet = %i", idSet);
3972 m_pDS->exec(strSQL);
3974 catch (...)
3976 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSet);
3980 void CVideoDatabase::ClearMovieSet(int idMovie)
3982 SetMovieSet(idMovie, -1);
3985 void CVideoDatabase::SetMovieSet(int idMovie, int idSet)
3987 if (idSet >= 0)
3988 ExecuteQuery(PrepareSQL("update movie set idSet = %i where idMovie = %i", idSet, idMovie));
3989 else
3990 ExecuteQuery(PrepareSQL("update movie set idSet = null where idMovie = %i", idMovie));
3993 std::string CVideoDatabase::GetFileBasePathById(int idFile)
3995 if (!m_pDB || !m_pDS)
3996 return "";
4000 m_pDS->query(PrepareSQL(
4001 "SELECT strPath FROM path JOIN files ON path.idPath = files.idPath WHERE idFile = %i",
4002 idFile));
4004 if (!m_pDS->eof())
4006 return m_pDS->fv("strPath").get_asString();
4008 m_pDS->close();
4010 catch (...)
4012 CLog::Log(LOGERROR, "{} failed for file {}", __FUNCTION__, idFile);
4015 return "";
4018 int CVideoDatabase::GetFileIdByMovie(int idMovie)
4020 if (!m_pDB || !m_pDS)
4021 return -1;
4023 int idFile = -1;
4027 m_pDS->query(PrepareSQL("SELECT idFile FROM movie WHERE idMovie = %i", idMovie));
4029 if (!m_pDS->eof())
4031 idFile = m_pDS->fv("idFile").get_asInt();
4034 m_pDS->close();
4036 catch (...)
4038 CLog::Log(LOGERROR, "{} failed for movie {}", __FUNCTION__, idMovie);
4041 return idFile;
4044 void CVideoDatabase::GetSameVideoItems(const CFileItem& item, CFileItemList& items)
4046 if (!m_pDB || !m_pDS)
4047 return;
4049 std::vector<int> itemIds;
4051 int dbId = item.GetVideoInfoTag()->m_iDbId;
4052 MediaType mediaType = item.GetVideoInfoTag()->m_type;
4056 // get items with same unique ids (imdb, tmdb, etc.) as the specified item, these are
4057 // the different versions of the item
4058 // note: old records may have the type 'unknown'
4059 // note 2: for type 'tmdb' the same value may be used for a movie and a tv episode, only
4060 // distinguished by media_type.
4061 // @todo make the (value,type) pairs truly unique
4062 m_pDS->query(PrepareSQL("SELECT DISTINCT media_id "
4063 "FROM uniqueid "
4064 "WHERE (media_type, value, type) IN "
4065 " (SELECT media_type, value, type "
4066 " FROM uniqueid WHERE media_id = %i AND media_type = '%s') ",
4067 dbId, mediaType.c_str()));
4069 while (!m_pDS->eof())
4071 itemIds.push_back(m_pDS->fv("media_id").get_asInt());
4072 m_pDS->next();
4075 m_pDS->close();
4077 VideoDbContentType itemType = item.GetVideoContentType();
4079 // get items with same title (and year if exists) as the specified item, these are
4080 // potentially different versions of the item
4081 if (itemType == VideoDbContentType::MOVIES)
4083 if (item.GetVideoInfoTag()->HasYear())
4084 m_pDS->query(
4085 PrepareSQL("SELECT idMovie FROM movie WHERE c%02d = '%s' AND premiered LIKE '%i%%'",
4086 VIDEODB_ID_TITLE, item.GetVideoInfoTag()->GetTitle().c_str(),
4087 item.GetVideoInfoTag()->GetYear()));
4088 else
4089 m_pDS->query(
4090 PrepareSQL("SELECT idMovie FROM movie WHERE c%02d = '%s' AND LENGTH(premiered) < 4",
4091 VIDEODB_ID_TITLE, item.GetVideoInfoTag()->GetTitle().c_str()));
4093 while (!m_pDS->eof())
4095 int movieId = m_pDS->fv("idMovie").get_asInt();
4097 // add movieId if not already in itemIds
4098 if (std::find(itemIds.begin(), itemIds.end(), movieId) == itemIds.end())
4099 itemIds.push_back(movieId);
4101 m_pDS->next();
4104 m_pDS->close();
4107 // get video item details
4108 for (const auto id : itemIds)
4110 auto current = std::make_shared<CFileItem>();
4111 if (GetDetailsByTypeAndId(*current.get(), itemType, id))
4112 items.Add(current);
4115 catch (...)
4117 CLog::Log(LOGERROR, "{} failed for {} {}", __FUNCTION__, mediaType, dbId);
4121 void CVideoDatabase::DeleteTag(int idTag, VideoDbContentType mediaType)
4125 if (m_pDB == nullptr || m_pDS == nullptr)
4126 return;
4128 std::string type;
4129 if (mediaType == VideoDbContentType::MOVIES)
4130 type = MediaTypeMovie;
4131 else if (mediaType == VideoDbContentType::TVSHOWS)
4132 type = MediaTypeTvShow;
4133 else if (mediaType == VideoDbContentType::MUSICVIDEOS)
4134 type = MediaTypeMusicVideo;
4135 else
4136 return;
4138 std::string strSQL = PrepareSQL("DELETE FROM tag_link WHERE tag_id = %i AND media_type = '%s'", idTag, type.c_str());
4139 m_pDS->exec(strSQL);
4141 catch (...)
4143 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idTag);
4147 void CVideoDatabase::GetDetailsFromDB(std::unique_ptr<Dataset> &pDS, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
4149 GetDetailsFromDB(pDS->get_sql_record(), min, max, offsets, details, idxOffset);
4152 void CVideoDatabase::GetDetailsFromDB(const dbiplus::sql_record* const record, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
4154 for (int i = min + 1; i < max; i++)
4156 switch (offsets[i].type)
4158 case VIDEODB_TYPE_STRING:
4159 *(std::string*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asString();
4160 break;
4161 case VIDEODB_TYPE_INT:
4162 case VIDEODB_TYPE_COUNT:
4163 *(int*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asInt();
4164 break;
4165 case VIDEODB_TYPE_BOOL:
4166 *(bool*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asBool();
4167 break;
4168 case VIDEODB_TYPE_FLOAT:
4169 *(float*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asFloat();
4170 break;
4171 case VIDEODB_TYPE_STRINGARRAY:
4173 std::string value = record->at(i+idxOffset).get_asString();
4174 if (!value.empty())
4175 *(std::vector<std::string>*)(((char*)&details)+offsets[i].offset) = StringUtils::Split(value, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4176 break;
4178 case VIDEODB_TYPE_DATE:
4179 ((CDateTime*)(((char*)&details)+offsets[i].offset))->SetFromDBDate(record->at(i+idxOffset).get_asString());
4180 break;
4181 case VIDEODB_TYPE_DATETIME:
4182 ((CDateTime*)(((char*)&details)+offsets[i].offset))->SetFromDBDateTime(record->at(i+idxOffset).get_asString());
4183 break;
4184 case VIDEODB_TYPE_UNUSED: // Skip the unused field to avoid populating unused data
4185 continue;
4190 bool CVideoDatabase::GetDetailsByTypeAndId(CFileItem& item, VideoDbContentType type, int id)
4192 CVideoInfoTag details;
4193 details.Reset();
4195 switch (type)
4197 case VideoDbContentType::MOVIES:
4198 GetMovieInfo("", details, id);
4199 break;
4200 case VideoDbContentType::TVSHOWS:
4201 GetTvShowInfo("", details, id, &item);
4202 break;
4203 case VideoDbContentType::EPISODES:
4204 GetEpisodeInfo("", details, id);
4205 break;
4206 case VideoDbContentType::MUSICVIDEOS:
4207 GetMusicVideoInfo("", details, id);
4208 break;
4209 default:
4210 return false;
4213 item.SetFromVideoInfoTag(details);
4214 return true;
4217 CVideoInfoTag CVideoDatabase::GetDetailsByTypeAndId(VideoDbContentType type, int id)
4219 CFileItem item;
4220 if (GetDetailsByTypeAndId(item, type, id))
4221 return CVideoInfoTag(*item.GetVideoInfoTag());
4223 return {};
4226 bool CVideoDatabase::GetStreamDetails(const std::string& filenameAndPath, CStreamDetails& details)
4228 CVideoInfoTag tag;
4229 tag.m_iFileId = GetFileId(filenameAndPath);
4230 if (GetStreamDetails(tag))
4232 details = tag.m_streamDetails;
4233 return true;
4235 return false;
4238 bool CVideoDatabase::GetStreamDetails(CFileItem& item)
4240 // Note that this function (possibly) creates VideoInfoTags for items that don't have one yet!
4241 int fileId = -1;
4243 if (item.HasVideoInfoTag())
4244 fileId = item.GetVideoInfoTag()->m_iFileId;
4245 if (fileId < 0)
4246 fileId = GetFileId(item);
4248 if (fileId < 0)
4249 return false;
4251 // Have a file id, get stream details if available (creates tag either way)
4252 item.GetVideoInfoTag()->m_iFileId = fileId;
4253 return GetStreamDetails(*item.GetVideoInfoTag());
4256 bool CVideoDatabase::GetStreamDetails(CVideoInfoTag& tag)
4259 const std::string path = tag.m_strFileNameAndPath;
4260 int fileId{-1};
4261 if (URIUtils::GetExtension(path) == ".mpls")
4262 fileId = GetFileId(path);
4263 else
4264 fileId = tag.m_iFileId;
4266 if (fileId < 0)
4267 return false;
4269 bool retVal = false;
4271 CStreamDetails& details = tag.m_streamDetails;
4272 details.Reset();
4274 std::unique_ptr<Dataset> pDS(m_pDB->CreateDataset());
4277 std::string strSQL = PrepareSQL("SELECT * FROM streamdetails WHERE idFile = %i", fileId);
4278 pDS->query(strSQL);
4280 while (!pDS->eof())
4282 CStreamDetail::StreamType e = (CStreamDetail::StreamType)pDS->fv(1).get_asInt();
4283 switch (e)
4285 case CStreamDetail::VIDEO:
4287 CStreamDetailVideo *p = new CStreamDetailVideo();
4288 p->m_strCodec = pDS->fv(2).get_asString();
4289 p->m_fAspect = pDS->fv(3).get_asFloat();
4290 p->m_iWidth = pDS->fv(4).get_asInt();
4291 p->m_iHeight = pDS->fv(5).get_asInt();
4292 p->m_iDuration = pDS->fv(10).get_asInt();
4293 p->m_strStereoMode = pDS->fv(11).get_asString();
4294 p->m_strLanguage = pDS->fv(12).get_asString();
4295 p->m_strHdrType = pDS->fv(13).get_asString();
4296 details.AddStream(p);
4297 retVal = true;
4298 break;
4300 case CStreamDetail::AUDIO:
4302 CStreamDetailAudio *p = new CStreamDetailAudio();
4303 p->m_strCodec = pDS->fv(6).get_asString();
4304 if (pDS->fv(7).get_isNull())
4305 p->m_iChannels = -1;
4306 else
4307 p->m_iChannels = pDS->fv(7).get_asInt();
4308 p->m_strLanguage = pDS->fv(8).get_asString();
4309 details.AddStream(p);
4310 retVal = true;
4311 break;
4313 case CStreamDetail::SUBTITLE:
4315 CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
4316 p->m_strLanguage = pDS->fv(9).get_asString();
4317 details.AddStream(p);
4318 retVal = true;
4319 break;
4323 pDS->next();
4326 pDS->close();
4328 catch (...)
4330 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, tag.m_iFileId);
4332 details.DetermineBestStreams();
4334 if (details.GetVideoDuration() > 0)
4335 tag.SetDuration(details.GetVideoDuration());
4337 return retVal;
4340 bool CVideoDatabase::GetResumePoint(CVideoInfoTag& tag)
4342 if (tag.m_iFileId < 0)
4343 return false;
4345 bool match = false;
4349 if (URIUtils::IsDiscImageStack(tag.m_strFileNameAndPath))
4351 CStackDirectory dir;
4352 CFileItemList fileList;
4353 const CURL pathToUrl(tag.m_strFileNameAndPath);
4354 dir.GetDirectory(pathToUrl, fileList);
4355 tag.SetResumePoint(CBookmark());
4356 for (int i = fileList.Size() - 1; i >= 0; i--)
4358 CBookmark bookmark;
4359 if (GetResumeBookMark(fileList[i]->GetPath(), bookmark))
4361 bookmark.partNumber = (i+1); /* store part number in here */
4362 tag.SetResumePoint(bookmark);
4363 match = true;
4364 break;
4368 else
4370 std::string strSQL=PrepareSQL("select timeInSeconds, totalTimeInSeconds from bookmark where idFile=%i and type=%i order by timeInSeconds", tag.m_iFileId, CBookmark::RESUME);
4371 m_pDS2->query( strSQL );
4372 if (!m_pDS2->eof())
4374 tag.SetResumePoint(m_pDS2->fv(0).get_asDouble(), m_pDS2->fv(1).get_asDouble(), "");
4375 match = true;
4377 m_pDS2->close();
4380 catch (...)
4382 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, tag.m_strFileNameAndPath);
4385 return match;
4388 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4390 return GetDetailsForMovie(pDS->get_sql_record(), getDetails);
4393 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4395 CVideoInfoTag details;
4397 if (record == NULL)
4398 return details;
4400 int idMovie = record->at(0).get_asInt();
4402 GetDetailsFromDB(record, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets, details);
4404 details.m_iDbId = idMovie;
4405 details.m_type = MediaTypeMovie;
4406 details.SetHasVideoVersions(record->at(VIDEODB_DETAILS_MOVIE_HASVERSIONS).get_asBool());
4407 details.SetHasVideoExtras(record->at(VIDEODB_DETAILS_MOVIE_HASEXTRAS).get_asBool());
4408 details.SetIsDefaultVideoVersion(record->at(VIDEODB_DETAILS_MOVIE_ISDEFAULTVERSION).get_asBool());
4409 auto& versionInfo = details.GetAssetInfo();
4410 versionInfo.SetId(record->at(VIDEODB_DETAILS_MOVIE_VERSION_TYPEID).get_asInt());
4411 versionInfo.SetTitle(record->at(VIDEODB_DETAILS_MOVIE_VERSION_TYPENAME).get_asString());
4412 versionInfo.SetType(
4413 static_cast<VideoAssetType>(record->at(VIDEODB_DETAILS_MOVIE_VERSION_ITEMTYPE).get_asInt()));
4414 details.m_set.id = record->at(VIDEODB_DETAILS_MOVIE_SET_ID).get_asInt();
4415 details.m_set.title = record->at(VIDEODB_DETAILS_MOVIE_SET_NAME).get_asString();
4416 details.m_set.overview = record->at(VIDEODB_DETAILS_MOVIE_SET_OVERVIEW).get_asString();
4417 details.m_iFileId = record->at(VIDEODB_DETAILS_MOVIE_VERSION_FILEID).get_asInt();
4418 details.m_strPath = record->at(VIDEODB_DETAILS_MOVIE_PATH).get_asString();
4419 std::string strFileName = record->at(VIDEODB_DETAILS_MOVIE_FILE).get_asString();
4420 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4421 details.SetPlayCount(record->at(VIDEODB_DETAILS_MOVIE_PLAYCOUNT).get_asInt());
4422 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_LASTPLAYED).get_asString());
4423 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_DATEADDED).get_asString());
4424 details.SetResumePoint(record->at(VIDEODB_DETAILS_MOVIE_RESUME_TIME).get_asInt(),
4425 record->at(VIDEODB_DETAILS_MOVIE_TOTAL_TIME).get_asInt(),
4426 record->at(VIDEODB_DETAILS_MOVIE_PLAYER_STATE).get_asString());
4427 details.m_iUserRating = record->at(VIDEODB_DETAILS_MOVIE_USER_RATING).get_asInt();
4428 details.SetRating(record->at(VIDEODB_DETAILS_MOVIE_RATING).get_asFloat(),
4429 record->at(VIDEODB_DETAILS_MOVIE_VOTES).get_asInt(),
4430 record->at(VIDEODB_DETAILS_MOVIE_RATING_TYPE).get_asString(), true);
4431 details.SetUniqueID(record->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_TYPE).get_asString() ,true);
4432 std::string premieredString = record->at(VIDEODB_DETAILS_MOVIE_PREMIERED).get_asString();
4433 if (premieredString.size() == 4)
4434 details.SetYear(record->at(VIDEODB_DETAILS_MOVIE_PREMIERED).get_asInt());
4435 else
4436 details.SetPremieredFromDBDate(premieredString);
4438 if (getDetails)
4440 if (getDetails & VideoDbDetailsCast)
4441 GetCast(details.m_iDbId, MediaTypeMovie, details.m_cast);
4443 if (getDetails & VideoDbDetailsTag)
4444 GetTags(details.m_iDbId, MediaTypeMovie, details.m_tags);
4446 if (getDetails & VideoDbDetailsRating)
4447 GetRatings(details.m_iDbId, MediaTypeMovie, details.m_ratings);
4449 if (getDetails & VideoDbDetailsUniqueID)
4450 GetUniqueIDs(details.m_iDbId, MediaTypeMovie, details);
4452 if (getDetails & VideoDbDetailsShowLink)
4454 // create tvshowlink string
4455 std::vector<int> links;
4456 GetLinksToTvShow(idMovie, links);
4457 for (unsigned int i = 0; i < links.size(); ++i)
4459 std::string strSQL = PrepareSQL("select c%02d from tvshow where idShow=%i",
4460 VIDEODB_ID_TV_TITLE, links[i]);
4461 m_pDS2->query(strSQL);
4462 if (!m_pDS2->eof())
4463 details.m_showLink.emplace_back(m_pDS2->fv(0).get_asString());
4465 m_pDS2->close();
4468 if (getDetails & VideoDbDetailsStream)
4469 GetStreamDetails(details);
4471 details.m_parsedDetails = getDetails;
4473 return details;
4476 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */, CFileItem* item /* = NULL */)
4478 return GetDetailsForTvShow(pDS->get_sql_record(), getDetails, item);
4481 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */, CFileItem* item /* = NULL */)
4483 CVideoInfoTag details;
4485 if (record == NULL)
4486 return details;
4488 int idTvShow = record->at(0).get_asInt();
4490 GetDetailsFromDB(record, VIDEODB_ID_TV_MIN, VIDEODB_ID_TV_MAX, DbTvShowOffsets, details, 1);
4491 details.m_bHasPremiered = details.m_premiered.IsValid();
4492 details.m_iDbId = idTvShow;
4493 details.m_type = MediaTypeTvShow;
4494 details.m_strPath = record->at(VIDEODB_DETAILS_TVSHOW_PATH).get_asString();
4495 details.m_basePath = details.m_strPath;
4496 details.m_parentPathID = record->at(VIDEODB_DETAILS_TVSHOW_PARENTPATHID).get_asInt();
4497 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_DATEADDED).get_asString());
4498 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_LASTPLAYED).get_asString());
4499 details.m_iSeason = record->at(VIDEODB_DETAILS_TVSHOW_NUM_SEASONS).get_asInt();
4500 details.m_iEpisode = record->at(VIDEODB_DETAILS_TVSHOW_NUM_EPISODES).get_asInt();
4501 details.SetPlayCount(record->at(VIDEODB_DETAILS_TVSHOW_NUM_WATCHED).get_asInt());
4502 details.m_strShowTitle = details.m_strTitle;
4503 details.m_iUserRating = record->at(VIDEODB_DETAILS_TVSHOW_USER_RATING).get_asInt();
4504 details.SetRating(record->at(VIDEODB_DETAILS_TVSHOW_RATING).get_asFloat(),
4505 record->at(VIDEODB_DETAILS_TVSHOW_VOTES).get_asInt(),
4506 record->at(VIDEODB_DETAILS_TVSHOW_RATING_TYPE).get_asString(), true);
4507 details.SetUniqueID(record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_TYPE).get_asString(), true);
4508 details.SetDuration(record->at(VIDEODB_DETAILS_TVSHOW_DURATION).get_asInt());
4510 //! @todo videotag member + guiinfo int needed?
4511 //! -- Currently not needed; having it available as item prop seems sufficient for skinning
4512 const int inProgressEpisodes = record->at(VIDEODB_DETAILS_TVSHOW_NUM_INPROGRESS).get_asInt();
4514 if (getDetails)
4516 if (getDetails & VideoDbDetailsCast)
4518 GetCast(details.m_iDbId, "tvshow", details.m_cast);
4521 if (getDetails & VideoDbDetailsTag)
4522 GetTags(details.m_iDbId, MediaTypeTvShow, details.m_tags);
4524 if (getDetails & VideoDbDetailsRating)
4525 GetRatings(details.m_iDbId, MediaTypeTvShow, details.m_ratings);
4527 if (getDetails & VideoDbDetailsUniqueID)
4528 GetUniqueIDs(details.m_iDbId, MediaTypeTvShow, details);
4530 details.m_parsedDetails = getDetails;
4533 if (item != NULL)
4535 item->m_dateTime = details.GetPremiered();
4536 item->SetProperty("totalseasons", details.m_iSeason);
4537 item->SetProperty("totalepisodes", details.m_iEpisode);
4538 item->SetProperty("numepisodes", details.m_iEpisode); // will be changed later to reflect watchmode setting
4539 item->SetProperty("watchedepisodes", details.GetPlayCount());
4540 item->SetProperty("unwatchedepisodes", details.m_iEpisode - details.GetPlayCount());
4541 item->SetProperty("inprogressepisodes", inProgressEpisodes);
4542 item->SetProperty("watchedepisodepercent",
4543 details.m_iEpisode > 0 ? (details.GetPlayCount() * 100 / details.m_iEpisode)
4544 : 0);
4546 details.SetPlayCount((details.m_iEpisode <= details.GetPlayCount()) ? 1 : 0);
4548 return details;
4551 CVideoInfoTag CVideoDatabase::GetBasicDetailsForEpisode(std::unique_ptr<Dataset> &pDS)
4553 return GetBasicDetailsForEpisode(pDS->get_sql_record());
4556 CVideoInfoTag CVideoDatabase::GetBasicDetailsForEpisode(const dbiplus::sql_record* const record)
4558 CVideoInfoTag details;
4560 if (record == nullptr)
4561 return details;
4563 int idEpisode = record->at(0).get_asInt();
4565 GetDetailsFromDB(record, VIDEODB_ID_EPISODE_MIN, VIDEODB_ID_EPISODE_MAX, DbEpisodeOffsets, details);
4566 details.m_iDbId = idEpisode;
4567 details.m_type = MediaTypeEpisode;
4568 details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4569 details.m_iIdShow = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt();
4570 details.m_iIdSeason = record->at(VIDEODB_DETAILS_EPISODE_SEASON_ID).get_asInt();
4571 details.m_iUserRating = record->at(VIDEODB_DETAILS_EPISODE_USER_RATING).get_asInt();
4573 return details;
4576 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4578 return GetDetailsForEpisode(pDS->get_sql_record(), getDetails);
4581 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4583 CVideoInfoTag details;
4585 if (record == nullptr)
4586 return details;
4588 details = GetBasicDetailsForEpisode(record);
4590 details.m_strPath = record->at(VIDEODB_DETAILS_EPISODE_PATH).get_asString();
4591 std::string strFileName = record->at(VIDEODB_DETAILS_EPISODE_FILE).get_asString();
4592 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4593 details.SetPlayCount(record->at(VIDEODB_DETAILS_EPISODE_PLAYCOUNT).get_asInt());
4594 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_LASTPLAYED).get_asString());
4595 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_DATEADDED).get_asString());
4596 details.m_strMPAARating = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_MPAA).get_asString();
4597 details.m_strShowTitle = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_NAME).get_asString();
4598 details.m_genre = StringUtils::Split(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_GENRE).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4599 details.m_studio = StringUtils::Split(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_STUDIO).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4600 details.SetPremieredFromDBDate(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_AIRED).get_asString());
4602 details.SetResumePoint(record->at(VIDEODB_DETAILS_EPISODE_RESUME_TIME).get_asInt(),
4603 record->at(VIDEODB_DETAILS_EPISODE_TOTAL_TIME).get_asInt(),
4604 record->at(VIDEODB_DETAILS_EPISODE_PLAYER_STATE).get_asString());
4606 details.SetRating(record->at(VIDEODB_DETAILS_EPISODE_RATING).get_asFloat(),
4607 record->at(VIDEODB_DETAILS_EPISODE_VOTES).get_asInt(),
4608 record->at(VIDEODB_DETAILS_EPISODE_RATING_TYPE).get_asString(), true);
4609 details.SetUniqueID(record->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_TYPE).get_asString(), true);
4611 if (getDetails)
4613 if (getDetails & VideoDbDetailsCast)
4615 GetCast(details.m_iDbId, MediaTypeEpisode, details.m_cast);
4616 GetCast(details.m_iIdShow, MediaTypeTvShow, details.m_cast);
4619 if (getDetails & VideoDbDetailsRating)
4620 GetRatings(details.m_iDbId, MediaTypeEpisode, details.m_ratings);
4622 if (getDetails & VideoDbDetailsUniqueID)
4623 GetUniqueIDs(details.m_iDbId, MediaTypeEpisode, details);
4625 if (getDetails & VideoDbDetailsBookmark)
4626 GetBookMarkForEpisode(details, details.m_EpBookmark);
4628 if (getDetails & VideoDbDetailsStream)
4629 GetStreamDetails(details);
4631 details.m_parsedDetails = getDetails;
4633 return details;
4636 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4638 return GetDetailsForMusicVideo(pDS->get_sql_record(), getDetails);
4641 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4643 CVideoInfoTag details;
4644 CArtist artist;
4646 if (record == nullptr)
4647 return details;
4649 int idMVideo = record->at(0).get_asInt();
4651 GetDetailsFromDB(record, VIDEODB_ID_MUSICVIDEO_MIN, VIDEODB_ID_MUSICVIDEO_MAX, DbMusicVideoOffsets, details);
4652 details.m_iDbId = idMVideo;
4653 details.m_type = MediaTypeMusicVideo;
4655 details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4656 details.m_strPath = record->at(VIDEODB_DETAILS_MUSICVIDEO_PATH).get_asString();
4657 std::string strFileName = record->at(VIDEODB_DETAILS_MUSICVIDEO_FILE).get_asString();
4658 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4659 details.SetPlayCount(record->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYCOUNT).get_asInt());
4660 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_LASTPLAYED).get_asString());
4661 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_DATEADDED).get_asString());
4662 details.SetResumePoint(record->at(VIDEODB_DETAILS_MUSICVIDEO_RESUME_TIME).get_asInt(),
4663 record->at(VIDEODB_DETAILS_MUSICVIDEO_TOTAL_TIME).get_asInt(),
4664 record->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYER_STATE).get_asString());
4665 details.m_iUserRating = record->at(VIDEODB_DETAILS_MUSICVIDEO_USER_RATING).get_asInt();
4666 details.SetUniqueID(record->at(VIDEODB_DETAILS_MUSICVIDEO_UNIQUEID_VALUE).get_asString(),
4667 record->at(VIDEODB_DETAILS_MUSICVIDEO_UNIQUEID_TYPE).get_asString(), true);
4668 std::string premieredString = record->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED).get_asString();
4669 if (premieredString.size() == 4)
4670 details.SetYear(record->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED).get_asInt());
4671 else
4672 details.SetPremieredFromDBDate(premieredString);
4674 if (getDetails)
4676 if (getDetails & VideoDbDetailsTag)
4677 GetTags(details.m_iDbId, MediaTypeMusicVideo, details.m_tags);
4679 if (getDetails & VideoDbDetailsUniqueID)
4680 GetUniqueIDs(details.m_iDbId, MediaTypeMusicVideo, details);
4682 if (getDetails & VideoDbDetailsStream)
4683 GetStreamDetails(details);
4685 if (getDetails & VideoDbDetailsAll)
4687 GetCast(details.m_iDbId, "musicvideo", details.m_cast);
4690 details.m_parsedDetails = getDetails;
4692 return details;
4695 void CVideoDatabase::GetCast(int media_id, const std::string &media_type, std::vector<SActorInfo> &cast)
4699 if (!m_pDB)
4700 return;
4701 if (!m_pDS2)
4702 return;
4704 std::string sql = PrepareSQL("SELECT actor.name,"
4705 " actor_link.role,"
4706 " actor_link.cast_order,"
4707 " actor.art_urls,"
4708 " art.url "
4709 "FROM actor_link"
4710 " JOIN actor ON"
4711 " actor_link.actor_id=actor.actor_id"
4712 " LEFT JOIN art ON"
4713 " art.media_id=actor.actor_id AND art.media_type='actor' AND art.type='thumb' "
4714 "WHERE actor_link.media_id=%i AND actor_link.media_type='%s'"
4715 "ORDER BY actor_link.cast_order", media_id, media_type.c_str());
4716 m_pDS2->query(sql);
4717 while (!m_pDS2->eof())
4719 SActorInfo info;
4720 info.strName = m_pDS2->fv(0).get_asString();
4721 info.strRole = m_pDS2->fv(1).get_asString();
4723 // ignore identical actors (since cast might already be prefilled)
4724 if (std::none_of(cast.begin(), cast.end(), [info](const SActorInfo& actor) {
4725 return actor.strName == info.strName && actor.strRole == info.strRole;
4728 info.order = m_pDS2->fv(2).get_asInt();
4729 info.thumbUrl.ParseFromData(m_pDS2->fv(3).get_asString());
4730 info.thumb = m_pDS2->fv(4).get_asString();
4731 cast.emplace_back(std::move(info));
4734 m_pDS2->next();
4736 m_pDS2->close();
4738 catch (...)
4740 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4744 void CVideoDatabase::GetTags(int media_id, const std::string &media_type, std::vector<std::string> &tags)
4748 if (!m_pDB)
4749 return;
4750 if (!m_pDS2)
4751 return;
4753 std::string sql = PrepareSQL("SELECT tag.name FROM tag INNER JOIN tag_link ON tag_link.tag_id = tag.tag_id WHERE tag_link.media_id = %i AND tag_link.media_type = '%s' ORDER BY tag.tag_id", media_id, media_type.c_str());
4754 m_pDS2->query(sql);
4755 while (!m_pDS2->eof())
4757 tags.emplace_back(m_pDS2->fv(0).get_asString());
4758 m_pDS2->next();
4760 m_pDS2->close();
4762 catch (...)
4764 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4768 void CVideoDatabase::GetRatings(int media_id, const std::string &media_type, RatingMap &ratings)
4772 if (!m_pDB)
4773 return;
4774 if (!m_pDS2)
4775 return;
4777 std::string sql = PrepareSQL("SELECT rating.rating_type, rating.rating, rating.votes FROM rating WHERE rating.media_id = %i AND rating.media_type = '%s'", media_id, media_type.c_str());
4778 m_pDS2->query(sql);
4779 while (!m_pDS2->eof())
4781 ratings[m_pDS2->fv(0).get_asString()] = CRating(m_pDS2->fv(1).get_asFloat(), m_pDS2->fv(2).get_asInt());
4782 m_pDS2->next();
4784 m_pDS2->close();
4786 catch (...)
4788 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4792 void CVideoDatabase::GetUniqueIDs(int media_id, const std::string &media_type, CVideoInfoTag& details)
4796 if (!m_pDB)
4797 return;
4798 if (!m_pDS2)
4799 return;
4801 std::string sql = PrepareSQL("SELECT type, value FROM uniqueid WHERE media_id = %i AND media_type = '%s'", media_id, media_type.c_str());
4802 m_pDS2->query(sql);
4803 while (!m_pDS2->eof())
4805 details.SetUniqueID(m_pDS2->fv(1).get_asString(), m_pDS2->fv(0).get_asString());
4806 m_pDS2->next();
4808 m_pDS2->close();
4810 catch (...)
4812 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4816 bool CVideoDatabase::GetVideoSettings(const CFileItem &item, CVideoSettings &settings)
4818 return GetVideoSettings(GetFileId(item), settings);
4821 /// \brief GetVideoSettings() obtains any saved video settings for the current file.
4822 /// \retval Returns true if the settings exist, false otherwise.
4823 bool CVideoDatabase::GetVideoSettings(const std::string &filePath, CVideoSettings &settings)
4825 return GetVideoSettings(GetFileId(filePath), settings);
4828 bool CVideoDatabase::GetVideoSettings(int idFile, CVideoSettings &settings)
4832 if (idFile < 0) return false;
4833 if (nullptr == m_pDB)
4834 return false;
4835 if (nullptr == m_pDS)
4836 return false;
4838 std::string strSQL=PrepareSQL("select * from settings where settings.idFile = '%i'", idFile);
4839 m_pDS->query( strSQL );
4841 if (m_pDS->num_rows() > 0)
4842 { // get the video settings info
4843 settings.m_AudioDelay = m_pDS->fv("AudioDelay").get_asFloat();
4844 settings.m_AudioStream = m_pDS->fv("AudioStream").get_asInt();
4845 settings.m_Brightness = m_pDS->fv("Brightness").get_asFloat();
4846 settings.m_Contrast = m_pDS->fv("Contrast").get_asFloat();
4847 settings.m_CustomPixelRatio = m_pDS->fv("PixelRatio").get_asFloat();
4848 settings.m_CustomNonLinStretch = m_pDS->fv("NonLinStretch").get_asBool();
4849 settings.m_NoiseReduction = m_pDS->fv("NoiseReduction").get_asFloat();
4850 settings.m_PostProcess = m_pDS->fv("PostProcess").get_asBool();
4851 settings.m_Sharpness = m_pDS->fv("Sharpness").get_asFloat();
4852 settings.m_CustomZoomAmount = m_pDS->fv("ZoomAmount").get_asFloat();
4853 settings.m_CustomVerticalShift = m_pDS->fv("VerticalShift").get_asFloat();
4854 settings.m_Gamma = m_pDS->fv("Gamma").get_asFloat();
4855 settings.m_SubtitleDelay = m_pDS->fv("SubtitleDelay").get_asFloat();
4856 settings.m_SubtitleOn = m_pDS->fv("SubtitlesOn").get_asBool();
4857 settings.m_SubtitleStream = m_pDS->fv("SubtitleStream").get_asInt();
4858 settings.m_ViewMode = m_pDS->fv("ViewMode").get_asInt();
4859 settings.m_ResumeTime = m_pDS->fv("ResumeTime").get_asInt();
4860 settings.m_InterlaceMethod = (EINTERLACEMETHOD)m_pDS->fv("Deinterlace").get_asInt();
4861 settings.m_VolumeAmplification = m_pDS->fv("VolumeAmplification").get_asFloat();
4862 settings.m_ScalingMethod = (ESCALINGMETHOD)m_pDS->fv("ScalingMethod").get_asInt();
4863 settings.m_StereoMode = m_pDS->fv("StereoMode").get_asInt();
4864 settings.m_StereoInvert = m_pDS->fv("StereoInvert").get_asBool();
4865 settings.m_VideoStream = m_pDS->fv("VideoStream").get_asInt();
4866 settings.m_ToneMapMethod =
4867 static_cast<ETONEMAPMETHOD>(m_pDS->fv("TonemapMethod").get_asInt());
4868 settings.m_ToneMapParam = m_pDS->fv("TonemapParam").get_asFloat();
4869 settings.m_Orientation = m_pDS->fv("Orientation").get_asInt();
4870 settings.m_CenterMixLevel = m_pDS->fv("CenterMixLevel").get_asInt();
4871 m_pDS->close();
4872 return true;
4874 m_pDS->close();
4876 catch (...)
4878 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
4880 return false;
4883 void CVideoDatabase::SetVideoSettings(const CFileItem &item, const CVideoSettings &settings)
4885 int idFile = AddFile(item);
4886 SetVideoSettings(idFile, settings);
4889 /// \brief Sets the settings for a particular video file
4890 void CVideoDatabase::SetVideoSettings(int idFile, const CVideoSettings &setting)
4894 if (nullptr == m_pDB)
4895 return;
4896 if (nullptr == m_pDS)
4897 return;
4898 if (idFile < 0)
4899 return;
4900 std::string strSQL = PrepareSQL("select * from settings where idFile=%i", idFile);
4901 m_pDS->query( strSQL );
4902 if (m_pDS->num_rows() > 0)
4904 m_pDS->close();
4905 // update the item
4906 strSQL = PrepareSQL(
4907 "update settings set "
4908 "Deinterlace=%i,ViewMode=%i,ZoomAmount=%f,PixelRatio=%f,VerticalShift=%f,"
4909 "AudioStream=%i,SubtitleStream=%i,SubtitleDelay=%f,SubtitlesOn=%i,Brightness=%f,Contrast="
4910 "%f,Gamma=%f,"
4911 "VolumeAmplification=%f,AudioDelay=%f,Sharpness=%f,NoiseReduction=%f,NonLinStretch=%i,"
4912 "PostProcess=%i,ScalingMethod=%i,",
4913 setting.m_InterlaceMethod, setting.m_ViewMode,
4914 static_cast<double>(setting.m_CustomZoomAmount),
4915 static_cast<double>(setting.m_CustomPixelRatio),
4916 static_cast<double>(setting.m_CustomVerticalShift), setting.m_AudioStream,
4917 setting.m_SubtitleStream, static_cast<double>(setting.m_SubtitleDelay),
4918 setting.m_SubtitleOn, static_cast<double>(setting.m_Brightness),
4919 static_cast<double>(setting.m_Contrast), static_cast<double>(setting.m_Gamma),
4920 static_cast<double>(setting.m_VolumeAmplification),
4921 static_cast<double>(setting.m_AudioDelay), static_cast<double>(setting.m_Sharpness),
4922 static_cast<double>(setting.m_NoiseReduction), setting.m_CustomNonLinStretch,
4923 setting.m_PostProcess, setting.m_ScalingMethod);
4924 std::string strSQL2;
4926 strSQL2 = PrepareSQL("ResumeTime=%i,StereoMode=%i,StereoInvert=%i,VideoStream=%i,"
4927 "TonemapMethod=%i,TonemapParam=%f where idFile=%i\n",
4928 setting.m_ResumeTime, setting.m_StereoMode, setting.m_StereoInvert,
4929 setting.m_VideoStream, setting.m_ToneMapMethod,
4930 static_cast<double>(setting.m_ToneMapParam), idFile);
4931 strSQL += strSQL2;
4932 m_pDS->exec(strSQL);
4933 return ;
4935 else
4936 { // add the items
4937 m_pDS->close();
4938 strSQL= "INSERT INTO settings (idFile,Deinterlace,ViewMode,ZoomAmount,PixelRatio, VerticalShift, "
4939 "AudioStream,SubtitleStream,SubtitleDelay,SubtitlesOn,Brightness,"
4940 "Contrast,Gamma,VolumeAmplification,AudioDelay,"
4941 "ResumeTime,"
4942 "Sharpness,NoiseReduction,NonLinStretch,PostProcess,ScalingMethod,StereoMode,StereoInvert,VideoStream,TonemapMethod,TonemapParam,Orientation,CenterMixLevel) "
4943 "VALUES ";
4944 strSQL += PrepareSQL(
4945 "(%i,%i,%i,%f,%f,%f,%i,%i,%f,%i,%f,%f,%f,%f,%f,%i,%f,%f,%i,%i,%i,%i,%i,%i,%i,%f,%i,%i)",
4946 idFile, setting.m_InterlaceMethod, setting.m_ViewMode,
4947 static_cast<double>(setting.m_CustomZoomAmount),
4948 static_cast<double>(setting.m_CustomPixelRatio),
4949 static_cast<double>(setting.m_CustomVerticalShift), setting.m_AudioStream,
4950 setting.m_SubtitleStream, static_cast<double>(setting.m_SubtitleDelay),
4951 setting.m_SubtitleOn, static_cast<double>(setting.m_Brightness),
4952 static_cast<double>(setting.m_Contrast), static_cast<double>(setting.m_Gamma),
4953 static_cast<double>(setting.m_VolumeAmplification),
4954 static_cast<double>(setting.m_AudioDelay), setting.m_ResumeTime,
4955 static_cast<double>(setting.m_Sharpness), static_cast<double>(setting.m_NoiseReduction),
4956 setting.m_CustomNonLinStretch, setting.m_PostProcess, setting.m_ScalingMethod,
4957 setting.m_StereoMode, setting.m_StereoInvert, setting.m_VideoStream,
4958 setting.m_ToneMapMethod, static_cast<double>(setting.m_ToneMapParam),
4959 setting.m_Orientation, setting.m_CenterMixLevel);
4960 m_pDS->exec(strSQL);
4963 catch (...)
4965 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idFile);
4969 void CVideoDatabase::UpdateArtForItem(int mediaId, const MediaType& mediaType)
4971 AnnounceUpdate(mediaType, mediaId);
4974 void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, const std::map<std::string, std::string> &art)
4976 for (const auto &i : art)
4977 SetArtForItem(mediaId, mediaType, i.first, i.second);
4980 void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType, const std::string &url)
4984 if (nullptr == m_pDB)
4985 return;
4986 if (nullptr == m_pDS)
4987 return;
4989 // don't set <foo>.<bar> art types - these are derivative types from parent items
4990 if (artType.find('.') != std::string::npos)
4991 return;
4993 std::string sql = PrepareSQL("SELECT art_id,url FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str());
4994 m_pDS->query(sql);
4995 if (!m_pDS->eof())
4996 { // update
4997 int artId = m_pDS->fv(0).get_asInt();
4998 std::string oldUrl = m_pDS->fv(1).get_asString();
4999 m_pDS->close();
5000 if (oldUrl != url)
5002 sql = PrepareSQL("UPDATE art SET url='%s' where art_id=%d", url.c_str(), artId);
5003 m_pDS->exec(sql);
5006 else
5007 { // insert
5008 m_pDS->close();
5009 sql = PrepareSQL("INSERT INTO art(media_id, media_type, type, url) VALUES (%d, '%s', '%s', '%s')", mediaId, mediaType.c_str(), artType.c_str(), url.c_str());
5010 m_pDS->exec(sql);
5013 catch (...)
5015 CLog::Log(LOGERROR, "{}({}, '{}', '{}', '{}') failed", __FUNCTION__, mediaId, mediaType,
5016 artType, url);
5020 bool CVideoDatabase::GetArtForItem(int mediaId, const MediaType &mediaType, std::map<std::string, std::string> &art)
5024 if (nullptr == m_pDB)
5025 return false;
5026 if (nullptr == m_pDS2)
5027 return false; // using dataset 2 as we're likely called in loops on dataset 1
5029 std::string sql = PrepareSQL("SELECT type,url FROM art WHERE media_id=%i AND media_type='%s'", mediaId, mediaType.c_str());
5031 m_pDS2->query(sql);
5032 while (!m_pDS2->eof())
5034 art.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString()));
5035 m_pDS2->next();
5037 m_pDS2->close();
5038 return !art.empty();
5040 catch (...)
5042 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaId);
5044 return false;
5047 bool CVideoDatabase::GetArtForAsset(int assetId,
5048 ArtFallbackOptions fallback,
5049 std::map<std::string, std::string>& art)
5053 if (nullptr == m_pDB)
5054 return false;
5055 if (nullptr == m_pDS2)
5056 return false; // using dataset 2 as we're likely called in loops on dataset 1
5058 std::string sql{PrepareSQL("SELECT art.media_type, art.type, art.url "
5059 "FROM art "
5060 "WHERE media_id = %i AND media_type = '%s' ",
5061 assetId, MediaTypeVideoVersion)};
5063 if (fallback == ArtFallbackOptions::PARENT)
5064 sql.append(PrepareSQL("UNION "
5065 "SELECT art.media_type, art.type, art.url "
5066 "FROM art "
5067 " JOIN videoversion as vv "
5068 " ON art.media_id = vv.idMedia AND art.media_type = vv.media_type "
5069 "WHERE idFile = %i",
5070 assetId));
5072 m_pDS2->query(sql);
5073 while (!m_pDS2->eof())
5075 if (m_pDS2->fv(0).get_asString() == MediaTypeVideoVersion)
5077 // version data has priority over owner's data
5078 art[m_pDS2->fv(1).get_asString()] = m_pDS2->fv(2).get_asString();
5080 else if (fallback == ArtFallbackOptions::PARENT)
5082 // insert if not yet present
5083 art.insert(make_pair(m_pDS2->fv(1).get_asString(), m_pDS2->fv(2).get_asString()));
5085 m_pDS2->next();
5087 m_pDS2->close();
5088 return !art.empty();
5090 catch (...)
5092 CLog::LogF(LOGERROR, "retrieval failed ({})", assetId);
5094 return false;
5097 std::string CVideoDatabase::GetArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType)
5099 std::string query = PrepareSQL("SELECT url FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str());
5100 return GetSingleValue(query, m_pDS2);
5103 bool CVideoDatabase::RemoveArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType)
5105 return ExecuteQuery(PrepareSQL("DELETE FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str()));
5108 bool CVideoDatabase::RemoveArtForItem(int mediaId, const MediaType &mediaType, const std::set<std::string> &artTypes)
5110 bool result = true;
5111 for (const auto &i : artTypes)
5112 result &= RemoveArtForItem(mediaId, mediaType, i);
5114 return result;
5117 bool CVideoDatabase::HasArtForItem(int mediaId, const MediaType &mediaType)
5121 if (nullptr == m_pDB)
5122 return false;
5123 if (nullptr == m_pDS2)
5124 return false; // using dataset 2 as we're likely called in loops on dataset 1
5126 std::string sql = PrepareSQL("SELECT 1 FROM art WHERE media_id=%i AND media_type='%s' LIMIT 1", mediaId, mediaType.c_str());
5127 m_pDS2->query(sql);
5128 bool result = !m_pDS2->eof();
5129 m_pDS2->close();
5130 return result;
5132 catch (...)
5134 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaId);
5136 return false;
5139 bool CVideoDatabase::GetTvShowSeasons(int showId, std::map<int, int> &seasons)
5143 if (nullptr == m_pDB)
5144 return false;
5145 if (nullptr == m_pDS2)
5146 return false; // using dataset 2 as we're likely called in loops on dataset 1
5148 // get all seasons for this show
5149 std::string sql = PrepareSQL("select idSeason,season from seasons where idShow=%i", showId);
5150 m_pDS2->query(sql);
5152 seasons.clear();
5153 while (!m_pDS2->eof())
5155 seasons.insert(std::make_pair(m_pDS2->fv(1).get_asInt(), m_pDS2->fv(0).get_asInt()));
5156 m_pDS2->next();
5158 m_pDS2->close();
5159 return true;
5161 catch (...)
5163 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
5165 return false;
5168 bool CVideoDatabase::GetTvShowNamedSeasons(int showId, std::map<int, std::string> &seasons)
5172 if (nullptr == m_pDB)
5173 return false;
5174 if (nullptr == m_pDS2)
5175 return false; // using dataset 2 as we're likely called in loops on dataset 1
5177 // get all named seasons for this show
5178 std::string sql = PrepareSQL("select season, name from seasons where season > 0 and name is not null and name <> '' and idShow = %i", showId);
5179 m_pDS2->query(sql);
5181 seasons.clear();
5182 while (!m_pDS2->eof())
5184 seasons.insert(std::make_pair(m_pDS2->fv(0).get_asInt(), m_pDS2->fv(1).get_asString()));
5185 m_pDS2->next();
5187 m_pDS2->close();
5188 return true;
5190 catch (...)
5192 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
5194 return false;
5197 std::string CVideoDatabase::GetTvShowNamedSeasonById(int tvshowId, int seasonId)
5199 return GetSingleValue("seasons", "name",
5200 PrepareSQL("season=%i AND idShow=%i", seasonId, tvshowId));
5203 bool CVideoDatabase::GetTvShowSeasonArt(int showId, std::map<int, std::map<std::string, std::string> > &seasonArt)
5207 if (nullptr == m_pDB)
5208 return false;
5209 if (nullptr == m_pDS2)
5210 return false; // using dataset 2 as we're likely called in loops on dataset 1
5212 std::map<int, int> seasons;
5213 GetTvShowSeasons(showId, seasons);
5215 for (const auto &i : seasons)
5217 std::map<std::string, std::string> art;
5218 GetArtForItem(i.second, MediaTypeSeason, art);
5219 seasonArt.insert(std::make_pair(i.first,art));
5221 return true;
5223 catch (...)
5225 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
5227 return false;
5230 bool CVideoDatabase::GetArtTypes(const MediaType &mediaType, std::vector<std::string> &artTypes)
5234 if (nullptr == m_pDB)
5235 return false;
5236 if (nullptr == m_pDS)
5237 return false;
5239 std::string sql = PrepareSQL("SELECT DISTINCT type FROM art WHERE media_type='%s'", mediaType.c_str());
5240 int numRows = RunQuery(sql);
5241 if (numRows <= 0)
5242 return numRows == 0;
5244 while (!m_pDS->eof())
5246 artTypes.emplace_back(m_pDS->fv(0).get_asString());
5247 m_pDS->next();
5249 m_pDS->close();
5250 return true;
5252 catch (...)
5254 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaType);
5256 return false;
5259 namespace
5261 std::vector<std::string> GetBasicItemAvailableArtTypes(int mediaId,
5262 VideoDbContentType dbType,
5263 CVideoDatabase& db)
5265 std::vector<std::string> result;
5266 CVideoInfoTag tag = db.GetDetailsByTypeAndId(dbType, mediaId);
5268 //! @todo artwork: fanart stored separately, doesn't need to be
5269 tag.m_fanart.Unpack();
5270 if (tag.m_fanart.GetNumFanarts() && std::find(result.cbegin(), result.cend(), "fanart") == result.cend())
5271 result.emplace_back("fanart");
5273 // all other images
5274 tag.m_strPictureURL.Parse();
5275 for (const auto& urlEntry : tag.m_strPictureURL.GetUrls())
5277 std::string artType = urlEntry.m_aspect;
5278 if (artType.empty())
5279 artType = tag.m_type == MediaTypeEpisode ? "thumb" : "poster";
5280 if (urlEntry.m_type == CScraperUrl::UrlType::General && // exclude season artwork for TV shows
5281 !StringUtils::StartsWith(artType, "set.") && // exclude movie set artwork for movies
5282 std::find(result.cbegin(), result.cend(), artType) == result.cend())
5284 result.push_back(artType);
5287 return result;
5290 std::vector<std::string> GetSeasonAvailableArtTypes(int mediaId, CVideoDatabase& db)
5292 CVideoInfoTag tag;
5293 db.GetSeasonInfo(mediaId, tag);
5295 std::vector<std::string> result;
5297 CVideoInfoTag sourceShow;
5298 db.GetTvShowInfo("", sourceShow, tag.m_iIdShow);
5299 sourceShow.m_strPictureURL.Parse();
5300 for (const auto& urlEntry : sourceShow.m_strPictureURL.GetUrls())
5302 std::string artType = urlEntry.m_aspect;
5303 if (artType.empty())
5304 artType = "poster";
5305 if (urlEntry.m_type == CScraperUrl::UrlType::Season && urlEntry.m_season == tag.m_iSeason &&
5306 std::find(result.cbegin(), result.cend(), artType) == result.cend())
5308 result.push_back(artType);
5311 return result;
5314 std::vector<std::string> GetMovieSetAvailableArtTypes(int mediaId, CVideoDatabase& db)
5316 std::vector<std::string> result;
5317 CFileItemList items;
5318 std::string baseDir = StringUtils::Format("videodb://movies/sets/{}", mediaId);
5319 if (db.GetMoviesNav(baseDir, items))
5321 for (const auto& item : items)
5323 CVideoInfoTag* pTag = item->GetVideoInfoTag();
5324 pTag->m_strPictureURL.Parse();
5326 for (const auto& urlEntry : pTag->m_strPictureURL.GetUrls())
5328 if (!StringUtils::StartsWith(urlEntry.m_aspect, "set."))
5329 continue;
5331 std::string artType = urlEntry.m_aspect.substr(4);
5332 if (std::find(result.cbegin(), result.cend(), artType) == result.cend())
5333 result.push_back(artType);
5337 return result;
5340 std::vector<CScraperUrl::SUrlEntry> GetBasicItemAvailableArt(int mediaId,
5341 VideoDbContentType dbType,
5342 const std::string& artType,
5343 CVideoDatabase& db)
5345 std::vector<CScraperUrl::SUrlEntry> result;
5346 CVideoInfoTag tag = db.GetDetailsByTypeAndId(dbType, mediaId);
5348 if (artType.empty() || artType == "fanart")
5350 tag.m_fanart.Unpack();
5351 for (unsigned int i = 0; i < tag.m_fanart.GetNumFanarts(); i++)
5353 CScraperUrl::SUrlEntry url(tag.m_fanart.GetImageURL(i));
5354 url.m_preview = tag.m_fanart.GetPreviewURL(i);
5355 url.m_aspect = "fanart";
5356 result.push_back(url);
5359 tag.m_strPictureURL.Parse();
5360 for (auto urlEntry : tag.m_strPictureURL.GetUrls())
5362 if (urlEntry.m_aspect.empty())
5363 urlEntry.m_aspect = tag.m_type == MediaTypeEpisode ? "thumb" : "poster";
5364 if ((urlEntry.m_aspect == artType ||
5365 (artType.empty() && !StringUtils::StartsWith(urlEntry.m_aspect, "set."))) &&
5366 urlEntry.m_type == CScraperUrl::UrlType::General)
5368 result.push_back(urlEntry);
5372 return result;
5375 std::vector<CScraperUrl::SUrlEntry> GetSeasonAvailableArt(
5376 int mediaId, const std::string& artType, CVideoDatabase& db)
5378 CVideoInfoTag tag;
5379 db.GetSeasonInfo(mediaId, tag);
5381 std::vector<CScraperUrl::SUrlEntry> result;
5383 CVideoInfoTag sourceShow;
5384 db.GetTvShowInfo("", sourceShow, tag.m_iIdShow);
5385 sourceShow.m_strPictureURL.Parse();
5386 for (auto urlEntry : sourceShow.m_strPictureURL.GetUrls())
5388 if (urlEntry.m_aspect.empty())
5389 urlEntry.m_aspect = "poster";
5390 if ((artType.empty() || urlEntry.m_aspect == artType) &&
5391 urlEntry.m_type == CScraperUrl::UrlType::Season &&
5392 urlEntry.m_season == tag.m_iSeason)
5394 result.push_back(urlEntry);
5397 return result;
5400 std::vector<CScraperUrl::SUrlEntry> GetMovieSetAvailableArt(
5401 int mediaId, const std::string& artType, CVideoDatabase& db)
5403 std::vector<CScraperUrl::SUrlEntry> result;
5404 CFileItemList items;
5405 std::string baseDir = StringUtils::Format("videodb://movies/sets/{}", mediaId);
5406 std::unordered_set<std::string> addedURLs;
5407 if (db.GetMoviesNav(baseDir, items))
5409 for (const auto& item : items)
5411 CVideoInfoTag* pTag = item->GetVideoInfoTag();
5412 pTag->m_strPictureURL.Parse();
5414 for (auto urlEntry : pTag->m_strPictureURL.GetUrls())
5416 bool isSetArt = !artType.empty() ? urlEntry.m_aspect == "set." + artType :
5417 StringUtils::StartsWith(urlEntry.m_aspect, "set.");
5418 if (isSetArt && addedURLs.insert(urlEntry.m_url).second)
5420 urlEntry.m_aspect = urlEntry.m_aspect.substr(4);
5421 result.push_back(urlEntry);
5426 return result;
5429 VideoDbContentType CovertMediaTypeToContentType(const MediaType& mediaType)
5431 VideoDbContentType dbType{VideoDbContentType::UNKNOWN};
5432 if (mediaType == MediaTypeTvShow)
5433 dbType = VideoDbContentType::TVSHOWS;
5434 else if (mediaType == MediaTypeMovie)
5435 dbType = VideoDbContentType::MOVIES;
5436 else if (mediaType == MediaTypeEpisode)
5437 dbType = VideoDbContentType::EPISODES;
5438 else if (mediaType == MediaTypeMusicVideo)
5439 dbType = VideoDbContentType::MUSICVIDEOS;
5441 return dbType;
5443 } // namespace
5445 std::vector<CScraperUrl::SUrlEntry> CVideoDatabase::GetAvailableArtForItem(
5446 int mediaId, const MediaType& mediaType, const std::string& artType)
5448 VideoDbContentType dbType = CovertMediaTypeToContentType(mediaType);
5450 if (dbType != VideoDbContentType::UNKNOWN)
5451 return GetBasicItemAvailableArt(mediaId, dbType, artType, *this);
5452 if (mediaType == MediaTypeSeason)
5453 return GetSeasonAvailableArt(mediaId, artType, *this);
5454 if (mediaType == MediaTypeVideoCollection)
5455 return GetMovieSetAvailableArt(mediaId, artType, *this);
5456 return {};
5459 std::vector<std::string> CVideoDatabase::GetAvailableArtTypesForItem(int mediaId,
5460 const MediaType& mediaType)
5462 VideoDbContentType dbType = CovertMediaTypeToContentType(mediaType);
5464 if (dbType != VideoDbContentType::UNKNOWN)
5465 return GetBasicItemAvailableArtTypes(mediaId, dbType, *this);
5466 if (mediaType == MediaTypeSeason)
5467 return GetSeasonAvailableArtTypes(mediaId, *this);
5468 if (mediaType == MediaTypeVideoCollection)
5469 return GetMovieSetAvailableArtTypes(mediaId, *this);
5470 return {};
5473 /// \brief GetStackTimes() obtains any saved video times for the stacked file
5474 /// \retval Returns true if the stack times exist, false otherwise.
5475 bool CVideoDatabase::GetStackTimes(const std::string &filePath, std::vector<uint64_t> &times)
5479 // obtain the FileID (if it exists)
5480 int idFile = GetFileId(filePath);
5481 if (idFile < 0) return false;
5482 if (nullptr == m_pDB)
5483 return false;
5484 if (nullptr == m_pDS)
5485 return false;
5486 // ok, now obtain the settings for this file
5487 std::string strSQL=PrepareSQL("select times from stacktimes where idFile=%i\n", idFile);
5488 m_pDS->query( strSQL );
5489 if (m_pDS->num_rows() > 0)
5490 { // get the video settings info
5491 uint64_t timeTotal = 0;
5492 std::vector<std::string> timeString = StringUtils::Split(m_pDS->fv("times").get_asString(), ",");
5493 times.clear();
5494 for (const auto &i : timeString)
5496 uint64_t partTime = static_cast<uint64_t>(atof(i.c_str()) * 1000.0);
5497 times.push_back(partTime); // db stores in secs, convert to msecs
5498 timeTotal += partTime;
5500 m_pDS->close();
5501 return (timeTotal > 0);
5503 m_pDS->close();
5505 catch (...)
5507 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
5509 return false;
5512 /// \brief Sets the stack times for a particular video file
5513 void CVideoDatabase::SetStackTimes(const std::string& filePath, const std::vector<uint64_t> &times)
5517 if (nullptr == m_pDB)
5518 return;
5519 if (nullptr == m_pDS)
5520 return;
5521 int idFile = AddFile(filePath);
5522 if (idFile < 0)
5523 return;
5525 // delete any existing items
5526 m_pDS->exec( PrepareSQL("delete from stacktimes where idFile=%i", idFile) );
5528 // add the items
5529 std::string timeString = StringUtils::Format("{:.3f}", times[0] / 1000.0f);
5530 for (unsigned int i = 1; i < times.size(); i++)
5531 timeString += StringUtils::Format(",{:.3f}", times[i] / 1000.0f);
5533 m_pDS->exec( PrepareSQL("insert into stacktimes (idFile,times) values (%i,'%s')\n", idFile, timeString.c_str()) );
5535 catch (...)
5537 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
5541 void CVideoDatabase::RemoveContentForPath(const std::string& strPath, CGUIDialogProgress *progress /* = NULL */)
5543 if(URIUtils::IsMultiPath(strPath))
5545 std::vector<std::string> paths;
5546 CMultiPathDirectory::GetPaths(strPath, paths);
5548 for(unsigned i=0;i<paths.size();i++)
5549 RemoveContentForPath(paths[i], progress);
5554 if (nullptr == m_pDB)
5555 return;
5556 if (nullptr == m_pDS)
5557 return;
5559 if (progress)
5561 progress->SetHeading(CVariant{700});
5562 progress->SetLine(0, CVariant{""});
5563 progress->SetLine(1, CVariant{313});
5564 progress->SetLine(2, CVariant{330});
5565 progress->SetPercentage(0);
5566 progress->Open();
5567 progress->ShowProgressBar(true);
5569 std::vector<std::pair<int, std::string> > paths;
5570 GetSubPaths(strPath, paths);
5571 int iCurr = 0;
5572 for (const auto &i : paths)
5574 bool bMvidsChecked=false;
5575 if (progress)
5577 progress->SetPercentage((int)((float)(iCurr++)/paths.size()*100.f));
5578 progress->Progress();
5581 const auto tvshowId = GetTvShowId(i.second);
5582 if (tvshowId > 0)
5583 DeleteTvShow(tvshowId);
5584 else
5586 std::string strSQL = PrepareSQL("select files.strFilename from files join movie on movie.idFile=files.idFile where files.idPath=%i", i.first);
5587 m_pDS2->query(strSQL);
5588 if (m_pDS2->eof())
5590 strSQL = PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i.first);
5591 m_pDS2->query(strSQL);
5592 bMvidsChecked = true;
5594 while (!m_pDS2->eof())
5596 std::string strMoviePath;
5597 std::string strFileName = m_pDS2->fv("files.strFilename").get_asString();
5598 ConstructPath(strMoviePath, i.second, strFileName);
5599 const auto movieId = GetMovieId(strMoviePath);
5600 if (movieId > 0)
5601 DeleteMovie(movieId);
5602 else
5604 const auto musicvideoId = GetMusicVideoId(strMoviePath);
5605 if (musicvideoId > 0)
5606 DeleteMusicVideo(musicvideoId);
5608 m_pDS2->next();
5609 if (m_pDS2->eof() && !bMvidsChecked)
5611 strSQL =PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i.first);
5612 m_pDS2->query(strSQL);
5613 bMvidsChecked = true;
5616 m_pDS2->close();
5617 m_pDS2->exec(PrepareSQL("update path set strContent='', strScraper='', strHash='',strSettings='',useFolderNames=0,scanRecursive=0 where idPath=%i", i.first));
5621 catch (...)
5623 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
5625 if (progress)
5626 progress->Close();
5629 void CVideoDatabase::SetScraperForPath(const std::string& filePath, const ScraperPtr& scraper, const VIDEO::SScanSettings& settings)
5631 // if we have a multipath, set scraper for all contained paths
5632 if(URIUtils::IsMultiPath(filePath))
5634 std::vector<std::string> paths;
5635 CMultiPathDirectory::GetPaths(filePath, paths);
5637 for(unsigned i=0;i<paths.size();i++)
5638 SetScraperForPath(paths[i],scraper,settings);
5640 return;
5645 if (nullptr == m_pDB)
5646 return;
5647 if (nullptr == m_pDS)
5648 return;
5650 int idPath = AddPath(filePath);
5651 if (idPath < 0)
5652 return;
5654 // Update
5655 std::string strSQL;
5656 if (settings.exclude)
5657 { //NB See note in ::GetScraperForPath about strContent=='none'
5658 strSQL = PrepareSQL(
5659 "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5660 "strSettings='', noUpdate=0, exclude=1, allAudio=%i WHERE idPath=%i",
5661 settings.m_allExtAudio, idPath);
5663 else if(!scraper)
5664 { // catch clearing content, but not excluding
5665 strSQL = PrepareSQL(
5666 "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5667 "strSettings='', noUpdate=0, exclude=0, allAudio=%i WHERE idPath=%i",
5668 settings.m_allExtAudio, idPath);
5670 else
5672 std::string content = TranslateContent(scraper->Content());
5673 strSQL = PrepareSQL(
5674 "UPDATE path SET strContent='%s', strScraper='%s', scanRecursive=%i, useFolderNames=%i, "
5675 "strSettings='%s', noUpdate=%i, exclude=0, allAudio=%i WHERE idPath=%i",
5676 content.c_str(), scraper->ID().c_str(), settings.recurse, settings.parent_name,
5677 scraper->GetPathSettings().c_str(), settings.noupdate, settings.m_allExtAudio, idPath);
5679 m_pDS->exec(strSQL);
5681 catch (...)
5683 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
5687 bool CVideoDatabase::ScraperInUse(const std::string &scraperID) const
5691 if (nullptr == m_pDB)
5692 return false;
5693 if (nullptr == m_pDS)
5694 return false;
5696 std::string sql = PrepareSQL("select count(1) from path where strScraper='%s'", scraperID.c_str());
5697 if (!m_pDS->query(sql) || m_pDS->num_rows() == 0)
5698 return false;
5699 bool found = m_pDS->fv(0).get_asInt() > 0;
5700 m_pDS->close();
5701 return found;
5703 catch (...)
5705 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, scraperID);
5707 return false;
5710 class CArtItem
5712 public:
5713 CArtItem() { art_id = 0; media_id = 0; };
5714 int art_id;
5715 std::string art_type;
5716 std::string art_url;
5717 int media_id;
5718 std::string media_type;
5721 // used for database update to v83
5722 class CShowItem
5724 public:
5725 bool operator==(const CShowItem &r) const
5727 return (!ident.empty() && ident == r.ident) || (title == r.title && year == r.year);
5729 int id;
5730 int path;
5731 std::string title;
5732 std::string year;
5733 std::string ident;
5736 // used for database update to v84
5737 class CShowLink
5739 public:
5740 int show;
5741 int pathId;
5742 std::string path;
5745 void CVideoDatabase::UpdateTables(int iVersion)
5747 // Important: DO NOT use CREATE TABLE [...] AS SELECT [...] - it does not work on MySQL with GTID consistency enforced
5749 if (iVersion < 76)
5751 m_pDS->exec("ALTER TABLE settings ADD StereoMode integer");
5752 m_pDS->exec("ALTER TABLE settings ADD StereoInvert bool");
5754 if (iVersion < 77)
5755 m_pDS->exec("ALTER TABLE streamdetails ADD strStereoMode text");
5757 if (iVersion < 81)
5758 { // add idParentPath to path table
5759 m_pDS->exec("ALTER TABLE path ADD idParentPath integer");
5760 std::map<std::string, int> paths;
5761 m_pDS->query("select idPath,strPath from path");
5762 while (!m_pDS->eof())
5764 paths.insert(make_pair(m_pDS->fv(1).get_asString(), m_pDS->fv(0).get_asInt()));
5765 m_pDS->next();
5767 m_pDS->close();
5768 // run through these paths figuring out the parent path, and add to the table if found
5769 for (const auto &i : paths)
5771 std::string parent = URIUtils::GetParentPath(i.first);
5772 auto j = paths.find(parent);
5773 if (j != paths.end())
5774 m_pDS->exec(PrepareSQL("UPDATE path SET idParentPath=%i WHERE idPath=%i", j->second, i.second));
5777 if (iVersion < 82)
5779 // drop parent path id and basePath from tvshow table
5780 m_pDS->exec("UPDATE tvshow SET c16=NULL,c17=NULL");
5782 if (iVersion < 83)
5784 // drop duplicates in tvshow table, and update tvshowlinkpath accordingly
5785 std::string sql = PrepareSQL("SELECT tvshow.idShow,idPath,c%02d,c%02d,c%02d FROM tvshow JOIN tvshowlinkpath ON tvshow.idShow = tvshowlinkpath.idShow", VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_PREMIERED, VIDEODB_ID_TV_IDENT_ID);
5786 m_pDS->query(sql);
5787 std::vector<CShowItem> shows;
5788 while (!m_pDS->eof())
5790 CShowItem show;
5791 show.id = m_pDS->fv(0).get_asInt();
5792 show.path = m_pDS->fv(1).get_asInt();
5793 show.title = m_pDS->fv(2).get_asString();
5794 show.year = m_pDS->fv(3).get_asString();
5795 show.ident = m_pDS->fv(4).get_asString();
5796 shows.emplace_back(std::move(show));
5797 m_pDS->next();
5799 m_pDS->close();
5800 if (!shows.empty())
5802 for (auto i = shows.begin() + 1; i != shows.end(); ++i)
5804 // has this show been found before?
5805 auto j = find(shows.begin(), i, *i);
5806 if (j != i)
5807 { // this is a duplicate
5808 // update the tvshowlinkpath table
5809 m_pDS->exec(PrepareSQL("UPDATE tvshowlinkpath SET idShow = %d WHERE idShow = %d AND idPath = %d", j->id, i->id, i->path));
5810 // update episodes, seasons, movie links
5811 m_pDS->exec(PrepareSQL("UPDATE episode SET idShow = %d WHERE idShow = %d", j->id, i->id));
5812 m_pDS->exec(PrepareSQL("UPDATE seasons SET idShow = %d WHERE idShow = %d", j->id, i->id));
5813 m_pDS->exec(PrepareSQL("UPDATE movielinktvshow SET idShow = %d WHERE idShow = %d", j->id, i->id));
5814 // delete tvshow
5815 m_pDS->exec(PrepareSQL("DELETE FROM genrelinktvshow WHERE idShow=%i", i->id));
5816 m_pDS->exec(PrepareSQL("DELETE FROM actorlinktvshow WHERE idShow=%i", i->id));
5817 m_pDS->exec(PrepareSQL("DELETE FROM directorlinktvshow WHERE idShow=%i", i->id));
5818 m_pDS->exec(PrepareSQL("DELETE FROM studiolinktvshow WHERE idShow=%i", i->id));
5819 m_pDS->exec(PrepareSQL("DELETE FROM tvshow WHERE idShow = %d", i->id));
5822 // cleanup duplicate seasons
5823 m_pDS->exec("DELETE FROM seasons WHERE idSeason NOT IN (SELECT idSeason FROM (SELECT min(idSeason) as idSeason FROM seasons GROUP BY idShow,season) AS sub)");
5826 if (iVersion < 84)
5827 { // replace any multipaths in tvshowlinkpath table
5828 m_pDS->query("SELECT idShow, tvshowlinkpath.idPath, strPath FROM tvshowlinkpath JOIN path ON tvshowlinkpath.idPath=path.idPath WHERE path.strPath LIKE 'multipath://%'");
5829 std::vector<CShowLink> shows;
5830 while (!m_pDS->eof())
5832 CShowLink link;
5833 link.show = m_pDS->fv(0).get_asInt();
5834 link.pathId = m_pDS->fv(1).get_asInt();
5835 link.path = m_pDS->fv(2).get_asString();
5836 shows.emplace_back(std::move(link));
5837 m_pDS->next();
5839 m_pDS->close();
5840 // update these
5841 for (auto i = shows.begin(); i != shows.end(); ++i)
5843 std::vector<std::string> paths;
5844 CMultiPathDirectory::GetPaths(i->path, paths);
5845 for (auto j = paths.begin(); j != paths.end(); ++j)
5847 int idPath = AddPath(*j, URIUtils::GetParentPath(*j));
5848 /* we can't rely on REPLACE INTO here as analytics (indices) aren't online yet */
5849 if (GetSingleValue(PrepareSQL("SELECT 1 FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i->show, idPath)).empty())
5850 m_pDS->exec(PrepareSQL("INSERT INTO tvshowlinkpath(idShow, idPath) VALUES(%i,%i)", i->show, idPath));
5852 m_pDS->exec(PrepareSQL("DELETE FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i->show, i->pathId));
5855 if (iVersion < 85)
5857 // drop multipaths from the path table - they're not needed for anything at all
5858 m_pDS->exec("DELETE FROM path WHERE strPath LIKE 'multipath://%'");
5860 if (iVersion < 87)
5861 { // due to the tvshow merging above, there could be orphaned season or show art
5862 m_pDS->exec("DELETE from art WHERE media_type='tvshow' AND NOT EXISTS (SELECT 1 FROM tvshow WHERE tvshow.idShow = art.media_id)");
5863 m_pDS->exec("DELETE from art WHERE media_type='season' AND NOT EXISTS (SELECT 1 FROM seasons WHERE seasons.idSeason = art.media_id)");
5865 if (iVersion < 91)
5867 // create actor link table
5868 m_pDS->exec("CREATE TABLE actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
5869 m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type, role, cast_order) SELECT DISTINCT idActor, idMovie, 'movie', strRole, iOrder from actorlinkmovie");
5870 m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type, role, cast_order) SELECT DISTINCT idActor, idShow, 'tvshow', strRole, iOrder from actorlinktvshow");
5871 m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type, role, cast_order) SELECT DISTINCT idActor, idEpisode, 'episode', strRole, iOrder from actorlinkepisode");
5872 m_pDS->exec("DROP TABLE IF EXISTS actorlinkmovie");
5873 m_pDS->exec("DROP TABLE IF EXISTS actorlinktvshow");
5874 m_pDS->exec("DROP TABLE IF EXISTS actorlinkepisode");
5875 m_pDS->exec("CREATE TABLE actor(actor_id INTEGER PRIMARY KEY, name TEXT, art_urls TEXT)");
5876 m_pDS->exec("INSERT INTO actor(actor_id, name, art_urls) SELECT idActor,strActor,strThumb FROM actors");
5877 m_pDS->exec("DROP TABLE IF EXISTS actors");
5879 // directors
5880 m_pDS->exec("CREATE TABLE director_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5881 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMovie, 'movie' FROM directorlinkmovie");
5882 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idShow, 'tvshow' FROM directorlinktvshow");
5883 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idEpisode, 'episode' FROM directorlinkepisode");
5884 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMVideo, 'musicvideo' FROM directorlinkmusicvideo");
5885 m_pDS->exec("DROP TABLE IF EXISTS directorlinkmovie");
5886 m_pDS->exec("DROP TABLE IF EXISTS directorlinktvshow");
5887 m_pDS->exec("DROP TABLE IF EXISTS directorlinkepisode");
5888 m_pDS->exec("DROP TABLE IF EXISTS directorlinkmusicvideo");
5890 // writers
5891 m_pDS->exec("CREATE TABLE writer_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5892 m_pDS->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idMovie, 'movie' FROM writerlinkmovie");
5893 m_pDS->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idEpisode, 'episode' FROM writerlinkepisode");
5894 m_pDS->exec("DROP TABLE IF EXISTS writerlinkmovie");
5895 m_pDS->exec("DROP TABLE IF EXISTS writerlinkepisode");
5897 // music artist
5898 m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type) SELECT DISTINCT idArtist, idMVideo, 'musicvideo' FROM artistlinkmusicvideo");
5899 m_pDS->exec("DROP TABLE IF EXISTS artistlinkmusicvideo");
5901 // studios
5902 m_pDS->exec("CREATE TABLE studio_link(studio_id INTEGER, media_id INTEGER, media_type TEXT)");
5903 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMovie, 'movie' FROM studiolinkmovie");
5904 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idShow, 'tvshow' FROM studiolinktvshow");
5905 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMVideo, 'musicvideo' FROM studiolinkmusicvideo");
5906 m_pDS->exec("DROP TABLE IF EXISTS studiolinkmovie");
5907 m_pDS->exec("DROP TABLE IF EXISTS studiolinktvshow");
5908 m_pDS->exec("DROP TABLE IF EXISTS studiolinkmusicvideo");
5909 m_pDS->exec("CREATE TABLE studionew(studio_id INTEGER PRIMARY KEY, name TEXT)");
5910 m_pDS->exec("INSERT INTO studionew(studio_id, name) SELECT idStudio,strStudio FROM studio");
5911 m_pDS->exec("DROP TABLE IF EXISTS studio");
5912 m_pDS->exec("ALTER TABLE studionew RENAME TO studio");
5914 // genres
5915 m_pDS->exec("CREATE TABLE genre_link(genre_id INTEGER, media_id INTEGER, media_type TEXT)");
5916 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMovie, 'movie' FROM genrelinkmovie");
5917 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idShow, 'tvshow' FROM genrelinktvshow");
5918 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMVideo, 'musicvideo' FROM genrelinkmusicvideo");
5919 m_pDS->exec("DROP TABLE IF EXISTS genrelinkmovie");
5920 m_pDS->exec("DROP TABLE IF EXISTS genrelinktvshow");
5921 m_pDS->exec("DROP TABLE IF EXISTS genrelinkmusicvideo");
5922 m_pDS->exec("CREATE TABLE genrenew(genre_id INTEGER PRIMARY KEY, name TEXT)");
5923 m_pDS->exec("INSERT INTO genrenew(genre_id, name) SELECT idGenre,strGenre FROM genre");
5924 m_pDS->exec("DROP TABLE IF EXISTS genre");
5925 m_pDS->exec("ALTER TABLE genrenew RENAME TO genre");
5927 // country
5928 m_pDS->exec("CREATE TABLE country_link(country_id INTEGER, media_id INTEGER, media_type TEXT)");
5929 m_pDS->exec("INSERT INTO country_link(country_id, media_id, media_type) SELECT DISTINCT idCountry, idMovie, 'movie' FROM countrylinkmovie");
5930 m_pDS->exec("DROP TABLE IF EXISTS countrylinkmovie");
5931 m_pDS->exec("CREATE TABLE countrynew(country_id INTEGER PRIMARY KEY, name TEXT)");
5932 m_pDS->exec("INSERT INTO countrynew(country_id, name) SELECT idCountry,strCountry FROM country");
5933 m_pDS->exec("DROP TABLE IF EXISTS country");
5934 m_pDS->exec("ALTER TABLE countrynew RENAME TO country");
5936 // tags
5937 m_pDS->exec("CREATE TABLE tag_link(tag_id INTEGER, media_id INTEGER, media_type TEXT)");
5938 m_pDS->exec("INSERT INTO tag_link(tag_id, media_id, media_type) SELECT DISTINCT idTag, idMedia, media_type FROM taglinks");
5939 m_pDS->exec("DROP TABLE IF EXISTS taglinks");
5940 m_pDS->exec("CREATE TABLE tagnew(tag_id INTEGER PRIMARY KEY, name TEXT)");
5941 m_pDS->exec("INSERT INTO tagnew(tag_id, name) SELECT idTag,strTag FROM tag");
5942 m_pDS->exec("DROP TABLE IF EXISTS tag");
5943 m_pDS->exec("ALTER TABLE tagnew RENAME TO tag");
5946 if (iVersion < 93)
5948 // cleanup main tables
5949 std::string valuesSql;
5950 for(int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
5952 valuesSql += StringUtils::Format("c{:02} = TRIM(c{:02})", i, i);
5953 if (i < VIDEODB_MAX_COLUMNS - 1)
5954 valuesSql += ",";
5956 m_pDS->exec("UPDATE episode SET " + valuesSql);
5957 m_pDS->exec("UPDATE movie SET " + valuesSql);
5958 m_pDS->exec("UPDATE musicvideo SET " + valuesSql);
5959 m_pDS->exec("UPDATE tvshow SET " + valuesSql);
5961 // cleanup additional tables
5962 std::map<std::string, std::vector<std::string>> additionalTablesMap = {
5963 {"actor", {"actor_link", "director_link", "writer_link"}},
5964 {"studio", {"studio_link"}},
5965 {"genre", {"genre_link"}},
5966 {"country", {"country_link"}},
5967 {"tag", {"tag_link"}}
5969 for (const auto& additionalTableEntry : additionalTablesMap)
5971 std::string table = additionalTableEntry.first;
5972 std::string tablePk = additionalTableEntry.first + "_id";
5973 std::map<int, std::string> duplicatesMinMap;
5974 std::map<int, std::string> duplicatesMap;
5976 // cleanup name
5977 m_pDS->exec(PrepareSQL("UPDATE %s SET name = TRIM(name)",
5978 table.c_str()));
5980 // shrink name to length 255
5981 m_pDS->exec(PrepareSQL("UPDATE %s SET name = SUBSTR(name, 1, 255) WHERE LENGTH(name) > 255",
5982 table.c_str()));
5984 // fetch main entries
5985 m_pDS->query(PrepareSQL("SELECT MIN(%s), name FROM %s GROUP BY name HAVING COUNT(1) > 1",
5986 tablePk.c_str(), table.c_str()));
5988 while (!m_pDS->eof())
5990 duplicatesMinMap.insert(std::make_pair(m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asString()));
5991 m_pDS->next();
5993 m_pDS->close();
5995 // fetch duplicate entries
5996 for (const auto& entry : duplicatesMinMap)
5998 m_pDS->query(PrepareSQL("SELECT %s FROM %s WHERE name = '%s' AND %s <> %i",
5999 tablePk.c_str(), table.c_str(),
6000 entry.second.c_str(), tablePk.c_str(), entry.first));
6002 std::stringstream ids;
6003 while (!m_pDS->eof())
6005 int id = m_pDS->fv(0).get_asInt();
6006 m_pDS->next();
6008 ids << id;
6009 if (!m_pDS->eof())
6010 ids << ",";
6012 m_pDS->close();
6014 duplicatesMap.insert(std::make_pair(entry.first, ids.str()));
6017 // cleanup duplicates in link tables
6018 for (const auto& subTable : additionalTableEntry.second)
6020 // create indexes to speed up things
6021 m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s ON %s (%s)",
6022 subTable.c_str(), subTable.c_str(), tablePk.c_str()));
6024 // migrate every duplicate entry to the main entry
6025 for (const auto& entry : duplicatesMap)
6027 m_pDS->exec(PrepareSQL("UPDATE %s SET %s = %i WHERE %s IN (%s) ",
6028 subTable.c_str(), tablePk.c_str(), entry.first,
6029 tablePk.c_str(), entry.second.c_str()));
6032 // clear all duplicates in the link tables
6033 if (subTable == "actor_link")
6035 // as a distinct won't work because of role and cast_order and a group by kills a
6036 // low powered mysql, we de-dupe it with REPLACE INTO while using the real unique index
6037 m_pDS->exec("CREATE TABLE temp_actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
6038 m_pDS->exec("CREATE UNIQUE INDEX ix_temp_actor_link ON temp_actor_link (actor_id, media_type(20), media_id)");
6039 m_pDS->exec("REPLACE INTO temp_actor_link SELECT * FROM actor_link");
6040 m_pDS->exec("DROP INDEX ix_temp_actor_link ON temp_actor_link");
6042 else
6044 m_pDS->exec(PrepareSQL("CREATE TABLE temp_%s AS SELECT DISTINCT * FROM %s",
6045 subTable.c_str(), subTable.c_str()));
6048 m_pDS->exec(PrepareSQL("DROP TABLE IF EXISTS %s",
6049 subTable.c_str()));
6051 m_pDS->exec(PrepareSQL("ALTER TABLE temp_%s RENAME TO %s",
6052 subTable.c_str(), subTable.c_str()));
6055 // delete duplicates in main table
6056 for (const auto& entry : duplicatesMap)
6058 m_pDS->exec(PrepareSQL("DELETE FROM %s WHERE %s IN (%s)",
6059 table.c_str(), tablePk.c_str(), entry.second.c_str()));
6064 if (iVersion < 96)
6066 m_pDS->exec("ALTER TABLE movie ADD userrating integer");
6067 m_pDS->exec("ALTER TABLE episode ADD userrating integer");
6068 m_pDS->exec("ALTER TABLE tvshow ADD userrating integer");
6069 m_pDS->exec("ALTER TABLE musicvideo ADD userrating integer");
6072 if (iVersion < 97)
6073 m_pDS->exec("ALTER TABLE sets ADD strOverview TEXT");
6075 if (iVersion < 98)
6076 m_pDS->exec("ALTER TABLE seasons ADD name text");
6078 if (iVersion < 99)
6080 // Add idSeason to episode table, so we don't have to join via idShow and season in the future
6081 m_pDS->exec("ALTER TABLE episode ADD idSeason integer");
6083 m_pDS->query("SELECT idSeason, idShow, season FROM seasons");
6084 while (!m_pDS->eof())
6086 m_pDS2->exec(PrepareSQL("UPDATE episode "
6087 "SET idSeason = %d "
6088 "WHERE "
6089 "episode.idShow = %d AND "
6090 "episode.c%02d = %d",
6091 m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asInt(),
6092 VIDEODB_ID_EPISODE_SEASON, m_pDS->fv(2).get_asInt()));
6094 m_pDS->next();
6097 if (iVersion < 101)
6098 m_pDS->exec("ALTER TABLE seasons ADD userrating INTEGER");
6100 if (iVersion < 102)
6102 m_pDS->exec("CREATE TABLE rating (rating_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, rating_type TEXT, rating FLOAT, votes INTEGER)");
6104 std::string sql = PrepareSQL("SELECT DISTINCT idMovie, c%02d, c%02d FROM movie", VIDEODB_ID_RATING_ID, VIDEODB_ID_VOTES);
6105 m_pDS->query(sql);
6106 while (!m_pDS->eof())
6108 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6109 "votes) VALUES (%i, 'movie', 'default', %f, %i)",
6110 m_pDS->fv(0).get_asInt(),
6111 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
6112 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
6113 int idRating = (int)m_pDS2->lastinsertid();
6114 m_pDS2->exec(PrepareSQL("UPDATE movie SET c%02d=%i WHERE idMovie=%i", VIDEODB_ID_RATING_ID, idRating, m_pDS->fv(0).get_asInt()));
6115 m_pDS->next();
6117 m_pDS->close();
6119 sql = PrepareSQL("SELECT DISTINCT idShow, c%02d, c%02d FROM tvshow", VIDEODB_ID_TV_RATING_ID, VIDEODB_ID_TV_VOTES);
6120 m_pDS->query(sql);
6121 while (!m_pDS->eof())
6123 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6124 "votes) VALUES (%i, 'tvshow', 'default', %f, %i)",
6125 m_pDS->fv(0).get_asInt(),
6126 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
6127 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
6128 int idRating = (int)m_pDS2->lastinsertid();
6129 m_pDS2->exec(PrepareSQL("UPDATE tvshow SET c%02d=%i WHERE idShow=%i", VIDEODB_ID_TV_RATING_ID, idRating, m_pDS->fv(0).get_asInt()));
6130 m_pDS->next();
6132 m_pDS->close();
6134 sql = PrepareSQL("SELECT DISTINCT idEpisode, c%02d, c%02d FROM episode", VIDEODB_ID_EPISODE_RATING_ID, VIDEODB_ID_EPISODE_VOTES);
6135 m_pDS->query(sql);
6136 while (!m_pDS->eof())
6138 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6139 "votes) VALUES (%i, 'episode', 'default', %f, %i)",
6140 m_pDS->fv(0).get_asInt(),
6141 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
6142 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
6143 int idRating = (int)m_pDS2->lastinsertid();
6144 m_pDS2->exec(PrepareSQL("UPDATE episode SET c%02d=%i WHERE idEpisode=%i", VIDEODB_ID_EPISODE_RATING_ID, idRating, m_pDS->fv(0).get_asInt()));
6145 m_pDS->next();
6147 m_pDS->close();
6150 if (iVersion < 103)
6152 m_pDS->exec("ALTER TABLE settings ADD VideoStream integer");
6153 m_pDS->exec("ALTER TABLE streamdetails ADD strVideoLanguage text");
6156 if (iVersion < 104)
6158 m_pDS->exec("ALTER TABLE tvshow ADD duration INTEGER");
6160 std::string sql = PrepareSQL( "SELECT episode.idShow, MAX(episode.c%02d) "
6161 "FROM episode "
6163 "LEFT JOIN streamdetails "
6164 "ON streamdetails.idFile = episode.idFile "
6165 "AND streamdetails.iStreamType = 0 " // only grab video streams
6167 "WHERE episode.c%02d <> streamdetails.iVideoDuration "
6168 "OR streamdetails.iVideoDuration IS NULL "
6169 "GROUP BY episode.idShow", VIDEODB_ID_EPISODE_RUNTIME, VIDEODB_ID_EPISODE_RUNTIME);
6171 m_pDS->query(sql);
6172 while (!m_pDS->eof())
6174 m_pDS2->exec(PrepareSQL("UPDATE tvshow SET duration=%i WHERE idShow=%i", m_pDS->fv(1).get_asInt(), m_pDS->fv(0).get_asInt()));
6175 m_pDS->next();
6177 m_pDS->close();
6180 if (iVersion < 105)
6182 m_pDS->exec("ALTER TABLE movie ADD premiered TEXT");
6183 m_pDS->exec(PrepareSQL("UPDATE movie SET premiered=c%02d", VIDEODB_ID_YEAR));
6184 m_pDS->exec("ALTER TABLE musicvideo ADD premiered TEXT");
6185 m_pDS->exec(PrepareSQL("UPDATE musicvideo SET premiered=c%02d", VIDEODB_ID_MUSICVIDEO_YEAR));
6188 if (iVersion < 107)
6190 // need this due to the nested GetScraperPath query
6191 std::unique_ptr<Dataset> pDS;
6192 pDS.reset(m_pDB->CreateDataset());
6193 if (nullptr == pDS)
6194 return;
6196 pDS->exec("CREATE TABLE uniqueid (uniqueid_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, value TEXT, type TEXT)");
6198 for (int i = 0; i < 3; ++i)
6200 std::string mediatype, columnID;
6201 int columnUniqueID;
6202 switch (i)
6204 case (0):
6205 mediatype = "movie";
6206 columnID = "idMovie";
6207 columnUniqueID = VIDEODB_ID_IDENT_ID;
6208 break;
6209 case (1):
6210 mediatype = "tvshow";
6211 columnID = "idShow";
6212 columnUniqueID = VIDEODB_ID_TV_IDENT_ID;
6213 break;
6214 case (2):
6215 mediatype = "episode";
6216 columnID = "idEpisode";
6217 columnUniqueID = VIDEODB_ID_EPISODE_IDENT_ID;
6218 break;
6219 default:
6220 continue;
6222 pDS->query(PrepareSQL("SELECT %s, c%02d FROM %s", columnID.c_str(), columnUniqueID, mediatype.c_str()));
6223 while (!pDS->eof())
6225 std::string uniqueid = pDS->fv(1).get_asString();
6226 if (!uniqueid.empty())
6228 int mediaid = pDS->fv(0).get_asInt();
6229 if (StringUtils::StartsWith(uniqueid, "tt"))
6230 m_pDS2->exec(PrepareSQL("INSERT INTO uniqueid(media_id, media_type, type, value) VALUES (%i, '%s', 'imdb', '%s')", mediaid, mediatype.c_str(), uniqueid.c_str()));
6231 else
6232 m_pDS2->exec(PrepareSQL("INSERT INTO uniqueid(media_id, media_type, type, value) VALUES (%i, '%s', 'unknown', '%s')", mediaid, mediatype.c_str(), uniqueid.c_str()));
6233 m_pDS2->exec(PrepareSQL("UPDATE %s SET c%02d='%i' WHERE %s=%i", mediatype.c_str(), columnUniqueID, (int)m_pDS2->lastinsertid(), columnID.c_str(), mediaid));
6235 pDS->next();
6237 pDS->close();
6241 if (iVersion < 109)
6243 m_pDS->exec("ALTER TABLE settings RENAME TO settingsold");
6244 m_pDS->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
6245 "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
6246 "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
6247 "VolumeAmplification float, AudioDelay float, ResumeTime integer,"
6248 "Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
6249 "ScalingMethod integer, DeinterlaceMode integer, StereoMode integer, StereoInvert bool, VideoStream integer)");
6250 m_pDS->exec("INSERT INTO settings SELECT idFile, Deinterlace, ViewMode, ZoomAmount, PixelRatio, VerticalShift, AudioStream, SubtitleStream, SubtitleDelay, SubtitlesOn, Brightness, Contrast, Gamma, VolumeAmplification, AudioDelay, ResumeTime, Sharpness, NoiseReduction, NonLinStretch, PostProcess, ScalingMethod, DeinterlaceMode, StereoMode, StereoInvert, VideoStream FROM settingsold");
6251 m_pDS->exec("DROP TABLE settingsold");
6254 if (iVersion < 110)
6256 m_pDS->exec("ALTER TABLE settings ADD TonemapMethod integer");
6257 m_pDS->exec("ALTER TABLE settings ADD TonemapParam float");
6260 if (iVersion < 111)
6261 m_pDS->exec("ALTER TABLE settings ADD Orientation integer");
6263 if (iVersion < 112)
6264 m_pDS->exec("ALTER TABLE settings ADD CenterMixLevel integer");
6266 if (iVersion < 113)
6268 // fb9c25f5 and e5f6d204 changed the behavior of path splitting for plugin URIs (previously it would only use the root)
6269 // Re-split paths for plugin files in order to maintain watched state etc.
6270 m_pDS->query("SELECT files.idFile, files.strFilename, path.strPath FROM files LEFT JOIN path ON files.idPath = path.idPath WHERE files.strFilename LIKE 'plugin://%'");
6271 while (!m_pDS->eof())
6273 std::string path, fn;
6274 SplitPath(m_pDS->fv(1).get_asString(), path, fn);
6275 if (path != m_pDS->fv(2).get_asString())
6277 int pathid = -1;
6278 m_pDS2->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", path.c_str()));
6279 if (!m_pDS2->eof())
6280 pathid = m_pDS2->fv(0).get_asInt();
6281 m_pDS2->close();
6282 if (pathid < 0)
6284 std::string parent = URIUtils::GetParentPath(path);
6285 int parentid = -1;
6286 m_pDS2->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", parent.c_str()));
6287 if (!m_pDS2->eof())
6288 parentid = m_pDS2->fv(0).get_asInt();
6289 m_pDS2->close();
6290 if (parentid < 0)
6292 m_pDS2->exec(PrepareSQL("INSERT INTO path (strPath) VALUES ('%s')", parent.c_str()));
6293 parentid = (int)m_pDS2->lastinsertid();
6295 m_pDS2->exec(PrepareSQL("INSERT INTO path (strPath, idParentPath) VALUES ('%s', %i)", path.c_str(), parentid));
6296 pathid = (int)m_pDS2->lastinsertid();
6298 m_pDS2->query(PrepareSQL("SELECT idFile FROM files WHERE strFileName='%s' AND idPath=%i", fn.c_str(), pathid));
6299 bool exists = !m_pDS2->eof();
6300 m_pDS2->close();
6301 if (exists)
6302 m_pDS2->exec(PrepareSQL("DELETE FROM files WHERE idFile=%i", m_pDS->fv(0).get_asInt()));
6303 else
6304 m_pDS2->exec(PrepareSQL("UPDATE files SET idPath=%i WHERE idFile=%i", pathid, m_pDS->fv(0).get_asInt()));
6306 m_pDS->next();
6308 m_pDS->close();
6311 if (iVersion < 119)
6312 m_pDS->exec("ALTER TABLE path ADD allAudio bool");
6314 if (iVersion < 120)
6315 m_pDS->exec("ALTER TABLE streamdetails ADD strHdrType text");
6317 if (iVersion < 121)
6319 // https://github.com/xbmc/xbmc/issues/21253 - Kodi picks up wrong "year" for PVR recording.
6321 m_pDS->query("SELECT idFile, strFilename FROM files WHERE strFilename LIKE '% (1969)%.pvr' OR "
6322 "strFilename LIKE '% (1601)%.pvr'");
6323 while (!m_pDS->eof())
6325 std::string fixedFileName = m_pDS->fv(1).get_asString();
6326 size_t pos = fixedFileName.find(" (1969)");
6327 if (pos == std::string::npos)
6328 pos = fixedFileName.find(" (1601)");
6330 if (pos != std::string::npos)
6332 fixedFileName.erase(pos, 7);
6334 m_pDS2->exec(PrepareSQL("UPDATE files SET strFilename='%s' WHERE idFile=%i",
6335 fixedFileName.c_str(), m_pDS->fv(0).get_asInt()));
6337 m_pDS->next();
6339 m_pDS->close();
6342 if (iVersion < 123)
6344 // create videoversiontype table
6345 m_pDS->exec("CREATE TABLE videoversiontype (id INTEGER PRIMARY KEY, name TEXT, owner INTEGER)");
6346 InitializeVideoVersionTypeTable(iVersion);
6348 // create videoversion table
6349 m_pDS->exec("CREATE TABLE videoversion (idFile INTEGER PRIMARY KEY, idMedia INTEGER, mediaType "
6350 "TEXT, itemType INTEGER, idType INTEGER)");
6351 m_pDS->exec(PrepareSQL(
6352 "INSERT INTO videoversion SELECT idFile, idMovie, 'movie', '%i', '%i' FROM movie",
6353 VideoAssetType::VERSION, VIDEO_VERSION_ID_DEFAULT));
6356 if (iVersion < 127)
6358 m_pDS->exec("ALTER TABLE videoversiontype ADD itemType INTEGER");
6360 // First, assume all types are video version types
6361 m_pDS->exec(PrepareSQL("UPDATE videoversiontype SET itemType = %i", VideoAssetType::VERSION));
6363 // Then, check current extras entries and their assigned item type and migrate it
6365 // get all assets with extras item type
6366 m_pDS->query("SELECT DISTINCT idType FROM videoversion WHERE itemType = 1");
6367 while (!m_pDS->eof())
6369 const int idType{m_pDS->fv(0).get_asInt()};
6370 if (idType > VIDEO_VERSION_ID_END)
6372 // user-added type for extras. change its item type to extras
6373 m_pDS2->exec(PrepareSQL("UPDATE videoversiontype SET itemType = %i WHERE id = %i",
6374 VideoAssetType::EXTRA, idType));
6376 else
6378 // system type used for an extra. copy as extras item type.
6379 m_pDS2->query(
6380 PrepareSQL("SELECT itemType, name FROM videoversiontype WHERE id = %i", idType));
6381 if (m_pDS2->fv(0).get_asInt() == 0)
6383 // currently a versions type, create a corresponding user-added type for extras
6384 m_pDS2->exec(PrepareSQL(
6385 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(NULL, '%s', %i, %i)",
6386 m_pDS2->fv(1).get_asString().c_str(), VideoAssetTypeOwner::USER,
6387 VideoAssetType::EXTRA));
6389 // update the respective extras to use the new extras type
6390 const int newId{static_cast<int>(m_pDS2->lastinsertid())};
6391 m_pDS2->exec(
6392 PrepareSQL("UPDATE videoversion SET idType = %i WHERE itemType = 1 AND idType = %i",
6393 newId, idType));
6396 m_pDS->next();
6398 m_pDS->close();
6401 if (iVersion < 128)
6403 m_pDS->exec("CREATE TABLE videoversion_new "
6404 "(idFile INTEGER PRIMARY KEY, idMedia INTEGER, media_type TEXT, "
6405 " itemType INTEGER, idType INTEGER)");
6406 m_pDS->exec("INSERT INTO videoversion_new "
6407 " (idFile, idMedia, media_type, itemType, idType) "
6408 "SELECT idFile, idMedia, mediaType, itemType, idType FROM videoversion");
6409 m_pDS->exec("DROP TABLE videoversion");
6410 m_pDS->exec("ALTER TABLE videoversion_new RENAME TO videoversion");
6412 // Fix gap in the migration to videodb v127 for unused user-defined video version types.
6413 // Unfortunately due to original design we cannot tell which ones were movie versions or
6414 // extras and now they're all displayed in the version type selection for movies.
6415 // Remove them all as the better fix of providing a GUI to manage version types will not be
6416 // available in Omega v21. That implies the loss of the unused user-defined version names
6417 // created since v21 beta 2.
6418 m_pDS2->exec(PrepareSQL("DELETE FROM videoversiontype "
6419 "WHERE id NOT IN (SELECT idType FROM videoversion) "
6420 "AND owner = %i "
6421 "AND itemType = %i",
6422 VideoAssetTypeOwner::USER, VideoAssetType::VERSION));
6425 if (iVersion < 131)
6427 // Remove quality-like predefined version types
6429 // Retrieve current utilization per type
6430 m_pDS->query("SELECT vvt.id, vvt.name, count(vv.idType) "
6431 "FROM videoversiontype vvt "
6432 " LEFT JOIN videoversion vv ON vvt.id = vv.idType "
6433 "WHERE vvt.id = 40405 OR vvt.id BETWEEN 40418 AND 40430 "
6434 "GROUP BY vvt.id");
6436 while (!m_pDS->eof())
6438 const int typeId{m_pDS->fv(0).get_asInt()};
6439 const std::string typeName{m_pDS->fv(1).get_asString()};
6440 const int versionsCount{m_pDS->fv(2).get_asInt()};
6442 if (versionsCount > 0)
6444 // type used by some versions, recreate as user type and link the versions to the new id
6445 m_pDS2->exec(PrepareSQL(
6446 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(NULL, '%s', %i, %i)",
6447 typeName.c_str(), VideoAssetTypeOwner::USER, VideoAssetType::VERSION));
6449 const int newId{static_cast<int>(m_pDS2->lastinsertid())};
6451 m_pDS2->exec(
6452 PrepareSQL("UPDATE videoversion SET idType = %i WHERE idType = %i", newId, typeId));
6454 m_pDS2->exec(PrepareSQL("DELETE FROM videoversiontype WHERE id = %i", typeId));
6455 m_pDS->next();
6457 m_pDS->close();
6460 if (iVersion < 133)
6462 // Remove episodes with invalid idSeason values.
6463 // Since 2015 they were masked from episode_view and are not going to be missed.
6464 // Those records would be misses in database converted in 2015 (see videodb version 99).
6466 m_pDS->exec("DELETE FROM episode WHERE idSeason NOT IN (SELECT idSeason from seasons)");
6470 int CVideoDatabase::GetSchemaVersion() const
6472 return 133;
6475 bool CVideoDatabase::LookupByFolders(const std::string &path, bool shows)
6477 SScanSettings settings;
6478 bool foundDirectly = false;
6479 ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
6480 if (scraper && scraper->Content() == CONTENT_TVSHOWS && !shows)
6481 return false; // episodes
6482 return settings.parent_name_root; // shows, movies, musicvids
6485 bool CVideoDatabase::GetPlayCounts(const std::string &strPath, CFileItemList &items)
6487 if(URIUtils::IsMultiPath(strPath))
6489 std::vector<std::string> paths;
6490 CMultiPathDirectory::GetPaths(strPath, paths);
6492 bool ret = false;
6493 for(unsigned i=0;i<paths.size();i++)
6494 ret |= GetPlayCounts(paths[i], items);
6496 return ret;
6498 int pathID = -1;
6499 if (!URIUtils::IsPlugin(strPath))
6501 pathID = GetPathId(strPath);
6502 if (pathID < 0)
6503 return false; // path (and thus files) aren't in the database
6508 // error!
6509 if (nullptr == m_pDB)
6510 return false;
6511 if (nullptr == m_pDS)
6512 return false;
6514 std::string sql =
6515 "SELECT"
6516 " files.strFilename, files.playCount,"
6517 " bookmark.timeInSeconds, bookmark.totalTimeInSeconds "
6518 "FROM files"
6519 " LEFT JOIN bookmark ON"
6520 " files.idFile = bookmark.idFile AND bookmark.type = %i ";
6522 if (URIUtils::IsPlugin(strPath))
6524 for (auto& item : items)
6526 if (!item || item->m_bIsFolder || !item->GetProperty("IsPlayable").asBoolean())
6527 continue;
6529 std::string path, filename;
6530 SplitPath(item->GetPath(), path, filename);
6531 m_pDS->query(PrepareSQL(sql +
6532 "INNER JOIN path ON files.idPath = path.idPath "
6533 "WHERE files.strFilename='%s' AND path.strPath='%s'",
6534 (int)CBookmark::RESUME, filename.c_str(), path.c_str()));
6536 if (!m_pDS->eof())
6538 if (!item->GetVideoInfoTag()->IsPlayCountSet())
6539 item->GetVideoInfoTag()->SetPlayCount(m_pDS->fv(1).get_asInt());
6540 if (!item->GetVideoInfoTag()->GetResumePoint().IsSet())
6541 item->GetVideoInfoTag()->SetResumePoint(m_pDS->fv(2).get_asInt(), m_pDS->fv(3).get_asInt(), "");
6543 m_pDS->close();
6546 else
6548 //! @todo also test a single query for the above and below
6549 sql = PrepareSQL(sql + "WHERE files.idPath=%i", (int)CBookmark::RESUME, pathID);
6551 if (RunQuery(sql) <= 0)
6552 return false;
6554 items.SetFastLookup(true); // note: it's possibly quicker the other way around (map on db returned items)?
6555 while (!m_pDS->eof())
6557 std::string path;
6558 ConstructPath(path, strPath, m_pDS->fv(0).get_asString());
6559 CFileItemPtr item = items.Get(path);
6560 if (item)
6562 if (!items.IsPlugin() || !item->GetVideoInfoTag()->IsPlayCountSet())
6563 item->GetVideoInfoTag()->SetPlayCount(m_pDS->fv(1).get_asInt());
6565 if (!item->GetVideoInfoTag()->GetResumePoint().IsSet())
6567 item->GetVideoInfoTag()->SetResumePoint(m_pDS->fv(2).get_asInt(), m_pDS->fv(3).get_asInt(), "");
6570 m_pDS->next();
6574 return true;
6576 catch (...)
6578 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6580 return false;
6583 int CVideoDatabase::GetPlayCount(int iFileId)
6585 if (iFileId < 0)
6586 return 0; // not in db, so not watched
6590 // error!
6591 if (nullptr == m_pDB)
6592 return -1;
6593 if (nullptr == m_pDS)
6594 return -1;
6596 std::string strSQL = PrepareSQL("select playCount from files WHERE idFile=%i", iFileId);
6597 int count = 0;
6598 if (m_pDS->query(strSQL))
6600 // there should only ever be one row returned
6601 if (m_pDS->num_rows() == 1)
6602 count = m_pDS->fv(0).get_asInt();
6603 m_pDS->close();
6605 return count;
6607 catch (...)
6609 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6611 return -1;
6614 int CVideoDatabase::GetPlayCount(const std::string& strFilenameAndPath)
6616 return GetPlayCount(GetFileId(strFilenameAndPath));
6619 int CVideoDatabase::GetPlayCount(const CFileItem &item)
6621 if (IsBlurayPlaylist(item))
6622 return GetPlayCount(GetFileId(item.GetDynPath()));
6623 else
6624 return GetPlayCount(GetFileId(item));
6627 CDateTime CVideoDatabase::GetLastPlayed(int iFileId)
6629 if (iFileId < 0)
6630 return {}; // not in db, so not watched
6634 // error!
6635 if (nullptr == m_pDB)
6636 return {};
6637 if (nullptr == m_pDS)
6638 return {};
6640 std::string strSQL = PrepareSQL("select lastPlayed from files WHERE idFile=%i", iFileId);
6641 CDateTime lastPlayed;
6642 if (m_pDS->query(strSQL))
6644 // there should only ever be one row returned
6645 if (m_pDS->num_rows() == 1)
6646 lastPlayed.SetFromDBDateTime(m_pDS->fv(0).get_asString());
6647 m_pDS->close();
6649 return lastPlayed;
6651 catch (...)
6653 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6655 return {};
6658 CDateTime CVideoDatabase::GetLastPlayed(const std::string& strFilenameAndPath)
6660 return GetLastPlayed(GetFileId(strFilenameAndPath));
6663 void CVideoDatabase::UpdateFanart(const CFileItem& item, VideoDbContentType type)
6665 if (nullptr == m_pDB)
6666 return;
6667 if (nullptr == m_pDS)
6668 return;
6669 if (!item.HasVideoInfoTag() || item.GetVideoInfoTag()->m_iDbId < 0) return;
6671 std::string exec;
6672 if (type == VideoDbContentType::TVSHOWS)
6673 exec = PrepareSQL("UPDATE tvshow set c%02d='%s' WHERE idShow=%i", VIDEODB_ID_TV_FANART, item.GetVideoInfoTag()->m_fanart.m_xml.c_str(), item.GetVideoInfoTag()->m_iDbId);
6674 else if (type == VideoDbContentType::MOVIES)
6675 exec = PrepareSQL("UPDATE movie set c%02d='%s' WHERE idMovie=%i", VIDEODB_ID_FANART, item.GetVideoInfoTag()->m_fanart.m_xml.c_str(), item.GetVideoInfoTag()->m_iDbId);
6679 m_pDS->exec(exec);
6681 if (type == VideoDbContentType::TVSHOWS)
6682 AnnounceUpdate(MediaTypeTvShow, item.GetVideoInfoTag()->m_iDbId);
6683 else if (type == VideoDbContentType::MOVIES)
6684 AnnounceUpdate(MediaTypeMovie, item.GetVideoInfoTag()->m_iDbId);
6686 catch (...)
6688 CLog::Log(LOGERROR, "{} - error updating fanart for {}", __FUNCTION__, item.GetPath());
6692 CDateTime CVideoDatabase::SetPlayCount(const CFileItem& item, int count, const CDateTime& date)
6694 int id{-1};
6695 if (IsBlurayPlaylist(item))
6696 id = AddFile(item.GetDynPath());
6697 else if (item.HasProperty("original_listitem_url") &&
6698 URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString()))
6700 CFileItem item2(item);
6701 item2.SetPath(item.GetProperty("original_listitem_url").asString());
6702 id = AddFile(item2);
6704 else
6705 id = AddFile(item);
6706 if (id < 0)
6707 return {};
6709 // and mark as watched
6712 const CDateTime lastPlayed(date.IsValid() ? date : CDateTime::GetCurrentDateTime());
6714 if (nullptr == m_pDB)
6715 return {};
6716 if (nullptr == m_pDS)
6717 return {};
6719 std::string strSQL;
6720 if (count)
6722 strSQL = PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", count,
6723 lastPlayed.GetAsDBDateTime().c_str(), id);
6725 else
6727 if (!date.IsValid())
6728 strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed=NULL where idFile=%i", id);
6729 else
6730 strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed='%s' where idFile=%i",
6731 lastPlayed.GetAsDBDateTime().c_str(), id);
6734 m_pDS->exec(strSQL);
6736 // We only need to announce changes to video items in the library
6737 if (item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId > 0)
6739 CVariant data;
6740 if (CVideoLibraryQueue::GetInstance().IsScanningLibrary())
6741 data["transaction"] = true;
6742 // Only provide the "playcount" value if it has actually changed
6743 if (item.GetVideoInfoTag()->GetPlayCount() != count)
6744 data["playcount"] = count;
6745 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnUpdate",
6746 std::make_shared<CFileItem>(item), data);
6749 return lastPlayed;
6751 catch (...)
6753 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6756 return {};
6759 CDateTime CVideoDatabase::IncrementPlayCount(const CFileItem& item)
6761 return SetPlayCount(item, GetPlayCount(item) + 1);
6764 CDateTime CVideoDatabase::UpdateLastPlayed(const CFileItem& item)
6766 return SetPlayCount(item, GetPlayCount(item), CDateTime::GetCurrentDateTime());
6769 void CVideoDatabase::UpdateMovieTitle(int idMovie,
6770 const std::string& strNewMovieTitle,
6771 VideoDbContentType iType)
6775 if (nullptr == m_pDB)
6776 return;
6777 if (nullptr == m_pDS)
6778 return;
6779 std::string content;
6780 if (iType == VideoDbContentType::MOVIES)
6782 CLog::Log(LOGINFO, "Changing Movie:id:{} New Title:{}", idMovie, strNewMovieTitle);
6783 content = MediaTypeMovie;
6785 else if (iType == VideoDbContentType::EPISODES)
6787 CLog::Log(LOGINFO, "Changing Episode:id:{} New Title:{}", idMovie, strNewMovieTitle);
6788 content = MediaTypeEpisode;
6790 else if (iType == VideoDbContentType::TVSHOWS)
6792 CLog::Log(LOGINFO, "Changing TvShow:id:{} New Title:{}", idMovie, strNewMovieTitle);
6793 content = MediaTypeTvShow;
6795 else if (iType == VideoDbContentType::MUSICVIDEOS)
6797 CLog::Log(LOGINFO, "Changing MusicVideo:id:{} New Title:{}", idMovie, strNewMovieTitle);
6798 content = MediaTypeMusicVideo;
6800 else if (iType == VideoDbContentType::MOVIE_SETS)
6802 CLog::Log(LOGINFO, "Changing Movie set:id:{} New Title:{}", idMovie, strNewMovieTitle);
6803 std::string strSQL = PrepareSQL("UPDATE sets SET strSet='%s' WHERE idSet=%i", strNewMovieTitle.c_str(), idMovie );
6804 m_pDS->exec(strSQL);
6807 if (!content.empty())
6809 SetSingleValue(iType, idMovie, FieldTitle, strNewMovieTitle);
6810 AnnounceUpdate(content, idMovie);
6813 catch (...)
6815 CLog::Log(
6816 LOGERROR,
6817 "{} (int idMovie, const std::string& strNewMovieTitle) failed on MovieID:{} and Title:{}",
6818 __FUNCTION__, idMovie, strNewMovieTitle);
6822 bool CVideoDatabase::UpdateVideoSortTitle(int idDb,
6823 const std::string& strNewSortTitle,
6824 VideoDbContentType iType /* = MOVIES */)
6828 if (nullptr == m_pDB || nullptr == m_pDS)
6829 return false;
6830 if (iType != VideoDbContentType::MOVIES && iType != VideoDbContentType::TVSHOWS)
6831 return false;
6833 std::string content = MediaTypeMovie;
6834 if (iType == VideoDbContentType::TVSHOWS)
6835 content = MediaTypeTvShow;
6837 if (SetSingleValue(iType, idDb, FieldSortTitle, strNewSortTitle))
6839 AnnounceUpdate(content, idDb);
6840 return true;
6843 catch (...)
6845 CLog::Log(LOGERROR,
6846 "{} (int idDb, const std::string& strNewSortTitle, VIDEODB_CONTENT_TYPE iType) "
6847 "failed on ID: {} and Sort Title: {}",
6848 __FUNCTION__, idDb, strNewSortTitle);
6851 return false;
6854 /// \brief EraseVideoSettings() Erases the videoSettings table and reconstructs it
6855 void CVideoDatabase::EraseVideoSettings(const CFileItem &item)
6857 int idFile = GetFileId(item);
6858 if (idFile < 0)
6859 return;
6863 std::string sql = PrepareSQL("DELETE FROM settings WHERE idFile=%i", idFile);
6865 CLog::Log(LOGINFO, "Deleting settings information for files {}",
6866 CURL::GetRedacted(item.GetPath()));
6867 m_pDS->exec(sql);
6869 catch (...)
6871 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6875 void CVideoDatabase::EraseAllVideoSettings()
6879 std::string sql = "DELETE FROM settings";
6881 CLog::Log(LOGINFO, "Deleting all video settings");
6882 m_pDS->exec(sql);
6884 catch (...)
6886 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6890 void CVideoDatabase::EraseAllVideoSettings(const std::string& path)
6892 std::string itemsToDelete;
6896 std::string sql = PrepareSQL("SELECT files.idFile FROM files WHERE idFile IN (SELECT idFile FROM files INNER JOIN path ON path.idPath = files.idPath AND path.strPath LIKE \"%s%%\")", path.c_str());
6897 m_pDS->query(sql);
6898 while (!m_pDS->eof())
6900 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
6901 itemsToDelete += file;
6902 m_pDS->next();
6904 m_pDS->close();
6906 if (!itemsToDelete.empty())
6908 itemsToDelete = "(" + StringUtils::TrimRight(itemsToDelete, ",") + ")";
6910 sql = "DELETE FROM settings WHERE idFile IN " + itemsToDelete;
6911 m_pDS->exec(sql);
6914 catch (...)
6916 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6920 bool CVideoDatabase::GetGenresNav(const std::string& strBaseDir,
6921 CFileItemList& items,
6922 VideoDbContentType idContent /* = UNKNOWN */,
6923 const Filter& filter /* = Filter() */,
6924 bool countOnly /* = false */)
6926 return GetNavCommon(strBaseDir, items, "genre", idContent, filter, countOnly);
6929 bool CVideoDatabase::GetCountriesNav(const std::string& strBaseDir,
6930 CFileItemList& items,
6931 VideoDbContentType idContent /* = UNKNOWN */,
6932 const Filter& filter /* = Filter() */,
6933 bool countOnly /* = false */)
6935 return GetNavCommon(strBaseDir, items, "country", idContent, filter, countOnly);
6938 bool CVideoDatabase::GetStudiosNav(const std::string& strBaseDir,
6939 CFileItemList& items,
6940 VideoDbContentType idContent /* = UNKNOWN */,
6941 const Filter& filter /* = Filter() */,
6942 bool countOnly /* = false */)
6944 return GetNavCommon(strBaseDir, items, "studio", idContent, filter, countOnly);
6947 bool CVideoDatabase::GetNavCommon(const std::string& strBaseDir,
6948 CFileItemList& items,
6949 const char* type,
6950 VideoDbContentType idContent /* = UNKNOWN */,
6951 const Filter& filter /* = Filter() */,
6952 bool countOnly /* = false */)
6956 if (nullptr == m_pDB)
6957 return false;
6958 if (nullptr == m_pDS)
6959 return false;
6961 std::string strSQL;
6962 Filter extFilter = filter;
6963 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6965 std::string view, view_id, media_type, extraField, extraJoin;
6966 if (idContent == VideoDbContentType::MOVIES)
6968 view = MediaTypeMovie;
6969 view_id = "idMovie";
6970 media_type = MediaTypeMovie;
6971 extraField = "files.playCount";
6973 else if (idContent == VideoDbContentType::TVSHOWS) //this will not get tvshows with 0 episodes
6975 view = MediaTypeEpisode;
6976 view_id = "idShow";
6977 media_type = MediaTypeTvShow;
6978 // in order to make use of FieldPlaycount in smart playlists we need an extra join
6979 if (StringUtils::EqualsNoCase(type, "tag"))
6980 extraJoin = PrepareSQL("JOIN tvshow_view ON tvshow_view.idShow = tag_link.media_id AND tag_link.media_type='tvshow'");
6982 else if (idContent == VideoDbContentType::MUSICVIDEOS)
6984 view = MediaTypeMusicVideo;
6985 view_id = "idMVideo";
6986 media_type = MediaTypeMusicVideo;
6987 extraField = "files.playCount";
6989 else
6990 return false;
6992 strSQL = "SELECT {} " + PrepareSQL("FROM %s ", type);
6993 extFilter.fields = PrepareSQL("%s.%s_id, %s.name, path.strPath", type, type, type);
6994 extFilter.AppendField(extraField);
6995 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type, type, type, type, type));
6996 extFilter.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'", view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
6997 extFilter.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str()));
6998 extFilter.AppendJoin("JOIN path ON path.idPath = files.idPath");
6999 extFilter.AppendJoin(extraJoin);
7001 else
7003 std::string view, view_id, media_type, extraField, extraJoin;
7004 if (idContent == VideoDbContentType::MOVIES)
7006 view = MediaTypeMovie;
7007 view_id = "idMovie";
7008 media_type = MediaTypeMovie;
7009 extraField = "count(1), count(files.playCount)";
7010 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
7012 else if (idContent == VideoDbContentType::TVSHOWS)
7014 view = MediaTypeTvShow;
7015 view_id = "idShow";
7016 media_type = MediaTypeTvShow;
7018 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7020 view = MediaTypeMusicVideo;
7021 view_id = "idMVideo";
7022 media_type = MediaTypeMusicVideo;
7023 extraField = "count(1), count(files.playCount)";
7024 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
7026 else
7027 return false;
7029 strSQL = "SELECT {} " + PrepareSQL("FROM %s ", type);
7030 extFilter.fields = PrepareSQL("%s.%s_id, %s.name", type, type, type);
7031 extFilter.AppendField(extraField);
7032 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type, type, type, type, type));
7033 extFilter.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'",
7034 view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
7035 extFilter.AppendJoin(extraJoin);
7036 extFilter.AppendGroup(PrepareSQL("%s.%s_id", type, type));
7039 if (countOnly)
7041 extFilter.fields = PrepareSQL("COUNT(DISTINCT %s.%s_id)", type, type);
7042 extFilter.group.clear();
7043 extFilter.order.clear();
7045 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
7047 CVideoDbUrl videoUrl;
7048 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
7049 return false;
7051 int iRowsFound = RunQuery(strSQL);
7052 if (iRowsFound <= 0)
7053 return iRowsFound == 0;
7055 if (countOnly)
7057 CFileItemPtr pItem(new CFileItem());
7058 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
7059 items.Add(pItem);
7061 m_pDS->close();
7062 return true;
7065 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7067 std::map<int, std::pair<std::string,int> > mapItems;
7068 while (!m_pDS->eof())
7070 int id = m_pDS->fv(0).get_asInt();
7071 std::string str = m_pDS->fv(1).get_asString();
7073 // was this already found?
7074 auto it = mapItems.find(id);
7075 if (it == mapItems.end())
7077 // check path
7078 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7080 if (idContent == VideoDbContentType::MOVIES ||
7081 idContent == VideoDbContentType::MUSICVIDEOS)
7082 mapItems.insert(std::pair<int, std::pair<std::string,int> >(id, std::pair<std::string, int>(str,m_pDS->fv(3).get_asInt()))); //fv(3) is file.playCount
7083 else if (idContent == VideoDbContentType::TVSHOWS)
7084 mapItems.insert(std::pair<int, std::pair<std::string,int> >(id, std::pair<std::string,int>(str,0)));
7087 m_pDS->next();
7089 m_pDS->close();
7091 for (const auto &i : mapItems)
7093 CFileItemPtr pItem(new CFileItem(i.second.first));
7094 pItem->GetVideoInfoTag()->m_iDbId = i.first;
7095 pItem->GetVideoInfoTag()->m_type = type;
7097 CVideoDbUrl itemUrl = videoUrl;
7098 std::string path = StringUtils::Format("{}/", i.first);
7099 itemUrl.AppendPath(path);
7100 pItem->SetPath(itemUrl.ToString());
7102 pItem->m_bIsFolder = true;
7103 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7104 pItem->GetVideoInfoTag()->SetPlayCount(i.second.second);
7105 if (!items.Contains(pItem->GetPath()))
7107 pItem->SetLabelPreformatted(true);
7108 items.Add(pItem);
7112 else
7114 while (!m_pDS->eof())
7116 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
7117 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
7118 pItem->GetVideoInfoTag()->m_type = type;
7120 CVideoDbUrl itemUrl = videoUrl;
7121 std::string path = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
7122 itemUrl.AppendPath(path);
7123 pItem->SetPath(itemUrl.ToString());
7125 pItem->m_bIsFolder = true;
7126 pItem->SetLabelPreformatted(true);
7127 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7128 { // fv(3) is the number of videos watched, fv(2) is the total number. We set the playcount
7129 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7130 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(3).get_asInt() == m_pDS->fv(2).get_asInt()) ? 1 : 0);
7132 items.Add(pItem);
7133 m_pDS->next();
7135 m_pDS->close();
7137 return true;
7139 catch (...)
7141 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7143 return false;
7146 bool CVideoDatabase::GetTagsNav(const std::string& strBaseDir,
7147 CFileItemList& items,
7148 VideoDbContentType idContent /* = UNKNOWN */,
7149 const Filter& filter /* = Filter() */,
7150 bool countOnly /* = false */)
7152 return GetNavCommon(strBaseDir, items, "tag", idContent, filter, countOnly);
7155 bool CVideoDatabase::GetSetsNav(const std::string& strBaseDir,
7156 CFileItemList& items,
7157 VideoDbContentType idContent /* = UNKNOWN */,
7158 const Filter& filter /* = Filter() */,
7159 bool ignoreSingleMovieSets /* = false */)
7161 if (idContent != VideoDbContentType::MOVIES)
7162 return false;
7164 return GetSetsByWhere(strBaseDir, filter, items, ignoreSingleMovieSets);
7167 bool CVideoDatabase::GetSetsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool ignoreSingleMovieSets /* = false */)
7171 if (nullptr == m_pDB)
7172 return false;
7173 if (nullptr == m_pDS)
7174 return false;
7176 CVideoDbUrl videoUrl;
7177 if (!videoUrl.FromString(strBaseDir))
7178 return false;
7180 Filter setFilter = filter;
7181 setFilter.join += " JOIN sets ON movie_view.idSet = sets.idSet";
7182 if (!setFilter.order.empty())
7183 setFilter.order += ",";
7184 setFilter.order += "sets.idSet";
7186 if (!GetMoviesByWhere(strBaseDir, setFilter, items))
7187 return false;
7189 CFileItemList sets;
7190 GroupAttribute groupingAttributes;
7191 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
7192 auto option = options.find("ignoreSingleMovieSets");
7194 if (option != options.end())
7196 groupingAttributes =
7197 option->second.asBoolean() ? GroupAttributeIgnoreSingleItems : GroupAttributeNone;
7199 else
7201 groupingAttributes =
7202 ignoreSingleMovieSets ? GroupAttributeIgnoreSingleItems : GroupAttributeNone;
7205 if (!GroupUtils::Group(GroupBySet, strBaseDir, items, sets, groupingAttributes))
7206 return false;
7208 items.ClearItems();
7209 items.Append(sets);
7211 return true;
7213 catch (...)
7215 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7217 return false;
7220 bool CVideoDatabase::GetMusicVideoAlbumsNav(const std::string& strBaseDir, CFileItemList& items, int idArtist /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
7224 if (nullptr == m_pDB)
7225 return false;
7226 if (nullptr == m_pDS)
7227 return false;
7229 CVideoDbUrl videoUrl;
7230 if (!videoUrl.FromString(strBaseDir))
7231 return false;
7233 std::string strSQL = "select {} from musicvideo_view ";
7234 Filter extFilter = filter;
7235 extFilter.fields = PrepareSQL("musicvideo_view.c%02d, musicvideo_view.idMVideo, actor.name, "
7236 "musicvideo_view.c%02d, musicvideo_view.c%02d, musicvideo_view.c%02d ",
7237 VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_TITLE,
7238 VIDEODB_ID_MUSICVIDEO_PLOT, VIDEODB_ID_MUSICVIDEO_ARTIST);
7239 extFilter.AppendJoin(
7240 PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo "));
7241 extFilter.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id = actor_link.actor_id"));
7242 extFilter.fields += ", path.strPath";
7243 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on path.idPath = files.idPath");
7245 if (StringUtils::EndsWith(strBaseDir,"albums/"))
7246 extFilter.AppendWhere(PrepareSQL("musicvideo_view.c%02d != ''", VIDEODB_ID_MUSICVIDEO_ALBUM));
7248 if (idArtist > -1)
7249 videoUrl.AddOption("artistid", idArtist);
7251 extFilter.AppendGroup(PrepareSQL(" CASE WHEN musicvideo_view.c09 !='' THEN musicvideo_view.c09 "
7252 "ELSE musicvideo_view.c00 END"));
7254 if (countOnly)
7256 extFilter.fields = "COUNT(1)";
7257 extFilter.group.clear();
7258 extFilter.order.clear();
7260 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
7262 if (!BuildSQL(videoUrl.ToString(), strSQL, extFilter, strSQL, videoUrl))
7263 return false;
7265 int iRowsFound = RunQuery(strSQL);
7266 /* fields returned by query are :-
7267 (0) - Album title (if any)
7268 (1) - idMVideo
7269 (2) - Artist name
7270 (3) - Music video title
7271 (4) - Music video plot
7272 (5) - Music Video artist
7273 (6) - Path to video
7275 if (iRowsFound <= 0)
7276 return iRowsFound == 0;
7278 std::string strArtist;
7279 if (idArtist> -1)
7280 strArtist = m_pDS->fv("actor.name").get_asString();
7282 if (countOnly)
7284 CFileItemPtr pItem(new CFileItem());
7285 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
7286 items.Add(pItem);
7288 m_pDS->close();
7289 return true;
7292 std::list <int> idMVideoList;
7293 std::list <std::pair<std::string, std::string>> idData;
7295 while (!m_pDS->eof())
7297 bool isAlbum = true;
7298 std::string strAlbum = m_pDS->fv(0).get_asString(); //Album title
7299 int idMVideo = m_pDS->fv(1).get_asInt();
7300 if (strAlbum.empty())
7302 strAlbum = m_pDS->fv(3).get_asString(); // video title if not an album
7303 isAlbum = false;
7306 CFileItemPtr pItem(new CFileItem(strAlbum));
7308 CVideoDbUrl itemUrl = videoUrl;
7309 std::string path = StringUtils::Format("{}/", idMVideo);
7310 if (!isAlbum)
7312 itemUrl.AddOption("albumid", idMVideo);
7313 path += std::to_string(idMVideo);
7315 strSQL = PrepareSQL(
7316 "SELECT type, url FROM art WHERE media_id = %i AND media_type = 'musicvideo'",
7317 idMVideo);
7318 m_pDS2->query(strSQL);
7319 while (!m_pDS2->eof())
7321 pItem->SetArt(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString());
7322 m_pDS2->next();
7324 m_pDS2->close();
7326 itemUrl.AppendPath(path);
7327 pItem->SetPath(itemUrl.ToString());
7328 pItem->m_bIsFolder = isAlbum;
7329 pItem->SetLabelPreformatted(true);
7331 if (!items.Contains(pItem->GetPath()))
7332 if (g_passwordManager.IsDatabasePathUnlocked(
7333 m_pDS->fv("path.strPath").get_asString(),
7334 *CMediaSourceSettings::GetInstance().GetSources("video")))
7336 pItem->GetVideoInfoTag()->m_artist.emplace_back(strArtist);
7337 pItem->GetVideoInfoTag()->m_iDbId = idMVideo;
7338 items.Add(pItem);
7339 idMVideoList.push_back(idMVideo);
7340 idData.emplace_back(m_pDS->fv(0).get_asString(), m_pDS->fv(5).get_asString());
7342 m_pDS->next();
7344 m_pDS->close();
7346 for (int i = 0; i < items.Size(); i++)
7348 CVideoInfoTag details;
7350 if (items[i]->m_bIsFolder)
7352 details.SetPath(items[i]->GetPath());
7353 details.m_strAlbum = idData.front().first;
7354 details.m_type = MediaTypeAlbum;
7355 details.m_artist.emplace_back(idData.front().second);
7356 details.m_iDbId = idMVideoList.front();
7357 items[i]->SetProperty("musicvideomediatype", MediaTypeAlbum);
7358 items[i]->SetLabel(idData.front().first);
7359 items[i]->SetFromVideoInfoTag(details);
7361 idMVideoList.pop_front();
7362 idData.pop_front();
7363 continue;
7365 else
7367 GetMusicVideoInfo("", details, idMVideoList.front());
7368 items[i]->SetFromVideoInfoTag(details);
7369 idMVideoList.pop_front();
7370 idData.pop_front();
7374 if (!strArtist.empty())
7375 items.SetProperty("customtitle",strArtist); // change displayed path from eg /23 to /Artist
7377 return true;
7379 catch (...)
7381 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7383 return false;
7386 bool CVideoDatabase::GetWritersNav(const std::string& strBaseDir,
7387 CFileItemList& items,
7388 VideoDbContentType idContent /* = UNKNOWN */,
7389 const Filter& filter /* = Filter() */,
7390 bool countOnly /* = false */)
7392 return GetPeopleNav(strBaseDir, items, "writer", idContent, filter, countOnly);
7395 bool CVideoDatabase::GetDirectorsNav(const std::string& strBaseDir,
7396 CFileItemList& items,
7397 VideoDbContentType idContent /* = UNKNOWN */,
7398 const Filter& filter /* = Filter() */,
7399 bool countOnly /* = false */)
7401 return GetPeopleNav(strBaseDir, items, "director", idContent, filter, countOnly);
7404 bool CVideoDatabase::GetActorsNav(const std::string& strBaseDir,
7405 CFileItemList& items,
7406 VideoDbContentType idContent /* = UNKNOWN */,
7407 const Filter& filter /* = Filter() */,
7408 bool countOnly /* = false */)
7410 if (GetPeopleNav(strBaseDir, items, "actor", idContent, filter, countOnly))
7411 { // set thumbs - ideally this should be in the normal thumb setting routines
7412 for (int i = 0; i < items.Size() && !countOnly; i++)
7414 CFileItemPtr pItem = items[i];
7415 if (idContent == VideoDbContentType::MUSICVIDEOS)
7416 pItem->SetArt("icon", "DefaultArtist.png");
7417 else
7418 pItem->SetArt("icon", "DefaultActor.png");
7420 return true;
7422 return false;
7425 bool CVideoDatabase::GetPeopleNav(const std::string& strBaseDir,
7426 CFileItemList& items,
7427 const char* type,
7428 VideoDbContentType idContent /* = UNKNOWN */,
7429 const Filter& filter /* = Filter() */,
7430 bool countOnly /* = false */)
7432 if (nullptr == m_pDB)
7433 return false;
7434 if (nullptr == m_pDS)
7435 return false;
7439 //! @todo This routine (and probably others at this same level) use playcount as a reference to filter on at a later
7440 //! point. This means that we *MUST* filter these levels as you'll get double ups. Ideally we'd allow playcount
7441 //! to filter through as we normally do for tvshows to save this happening.
7442 //! Also, we apply this same filtering logic to the locked or unlocked paths to prevent these from showing.
7443 //! Whether or not this should happen is a tricky one - it complicates all the high level categories (everything
7444 //! above titles).
7446 // General routine that the other actor/director/writer routines call
7448 // get primary genres for movies
7449 std::string strSQL;
7450 bool bMainArtistOnly = false;
7451 Filter extFilter = filter;
7452 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7454 std::string view, view_id, media_type, extraField, extraJoin, group;
7455 if (idContent == VideoDbContentType::MOVIES)
7457 view = MediaTypeMovie;
7458 view_id = "idMovie";
7459 media_type = MediaTypeMovie;
7460 extraField = "files.playCount";
7462 else if (idContent == VideoDbContentType::TVSHOWS)
7464 view = MediaTypeEpisode;
7465 view_id = "idShow";
7466 media_type = MediaTypeTvShow;
7467 extraField = "count(DISTINCT idShow)";
7468 group = "actor.actor_id";
7470 else if (idContent == VideoDbContentType::EPISODES)
7472 view = MediaTypeEpisode;
7473 view_id = "idEpisode";
7474 media_type = MediaTypeEpisode;
7475 extraField = "files.playCount";
7477 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7479 bMainArtistOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
7480 CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS);
7481 if (StringUtils::EndsWith(strBaseDir, "directors/"))
7482 // only set this to true if getting artists and show all performers is false
7483 bMainArtistOnly = false;
7484 view = MediaTypeMusicVideo;
7485 view_id = "idMVideo";
7486 media_type = MediaTypeMusicVideo;
7487 extraField = "count(1), count(files.playCount)";
7488 if (bMainArtistOnly)
7489 extraJoin =
7490 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
7491 group = "actor.actor_id";
7493 else
7494 return false;
7496 strSQL = "SELECT {} FROM actor ";
7497 extFilter.fields = "actor.actor_id, actor.name, actor.art_urls, path.strPath";
7498 extFilter.AppendField(extraField);
7499 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON actor.actor_id = %s_link.actor_id", type, type));
7500 extFilter.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'", view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
7501 extFilter.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str()));
7502 extFilter.AppendJoin("JOIN path ON path.idPath = files.idPath");
7503 extFilter.AppendJoin(extraJoin);
7504 extFilter.AppendGroup(group);
7506 else
7508 std::string view, view_id, media_type, extraField, extraJoin;
7509 if (idContent == VideoDbContentType::MOVIES)
7511 view = MediaTypeMovie;
7512 view_id = "idMovie";
7513 media_type = MediaTypeMovie;
7514 extraField = "count(1), count(files.playCount)";
7515 extraJoin = PrepareSQL(" JOIN files ON files.idFile=%s_view.idFile", view.c_str());
7517 else if (idContent == VideoDbContentType::TVSHOWS)
7519 view = MediaTypeTvShow;
7520 view_id = "idShow";
7521 media_type = MediaTypeTvShow;
7522 extraField = "count(idShow)";
7524 else if (idContent == VideoDbContentType::EPISODES)
7526 view = MediaTypeEpisode;
7527 view_id = "idEpisode";
7528 media_type = MediaTypeEpisode;
7529 extraField = "count(1), count(files.playCount)";
7530 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
7532 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7534 bMainArtistOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
7535 CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS);
7536 if (StringUtils::EndsWith(strBaseDir, "directors/"))
7537 // only set this to true if getting artists and show all performers is false
7538 bMainArtistOnly = false;
7539 view = MediaTypeMusicVideo;
7540 view_id = "idMVideo";
7541 media_type = MediaTypeMusicVideo;
7542 extraField = "count(1), count(files.playCount)";
7543 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
7544 if (bMainArtistOnly)
7545 extraJoin =
7546 extraJoin +
7547 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
7549 else
7550 return false;
7552 strSQL = "SELECT {} FROM actor ";
7553 extFilter.fields = "actor.actor_id, actor.name, actor.art_urls";
7554 extFilter.AppendField(extraField);
7555 extFilter.AppendJoin(PrepareSQL("JOIN %s_link on actor.actor_id = %s_link.actor_id", type, type));
7556 extFilter.AppendJoin(PrepareSQL("JOIN %s_view on %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'", view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
7557 extFilter.AppendJoin(extraJoin);
7558 extFilter.AppendGroup("actor.actor_id");
7561 if (countOnly)
7563 extFilter.fields = "COUNT(1)";
7564 extFilter.group.clear();
7565 extFilter.order.clear();
7567 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
7569 CVideoDbUrl videoUrl;
7570 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
7571 return false;
7573 // run query
7574 auto start = std::chrono::steady_clock::now();
7576 if (!m_pDS->query(strSQL)) return false;
7578 auto end = std::chrono::steady_clock::now();
7579 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
7581 CLog::Log(LOGDEBUG, LOGDATABASE, "{} - query took {} ms", __FUNCTION__, duration.count());
7583 start = std::chrono::steady_clock::now();
7585 int iRowsFound = m_pDS->num_rows();
7586 if (iRowsFound == 0)
7588 m_pDS->close();
7589 return true;
7592 if (countOnly)
7594 CFileItemPtr pItem(new CFileItem());
7595 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
7596 items.Add(pItem);
7598 m_pDS->close();
7599 return true;
7602 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7604 std::map<int, CActor> mapActors;
7606 while (!m_pDS->eof())
7608 int idActor = m_pDS->fv(0).get_asInt();
7609 CActor actor;
7610 actor.name = m_pDS->fv(1).get_asString();
7611 actor.thumb = m_pDS->fv(2).get_asString();
7612 if (idContent != VideoDbContentType::TVSHOWS &&
7613 idContent != VideoDbContentType::MUSICVIDEOS)
7615 actor.playcount = m_pDS->fv(3).get_asInt();
7616 actor.appearances = 1;
7618 else actor.appearances = m_pDS->fv(4).get_asInt();
7619 auto it = mapActors.find(idActor);
7620 // is this actor already known?
7621 if (it == mapActors.end())
7623 // check path
7624 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7625 mapActors.insert(std::pair<int, CActor>(idActor, actor));
7627 else if (idContent != VideoDbContentType::TVSHOWS &&
7628 idContent != VideoDbContentType::MUSICVIDEOS)
7629 it->second.appearances++;
7630 m_pDS->next();
7632 m_pDS->close();
7634 for (const auto &i : mapActors)
7636 CFileItemPtr pItem(new CFileItem(i.second.name));
7638 CVideoDbUrl itemUrl = videoUrl;
7639 std::string path = StringUtils::Format("{}/", i.first);
7640 itemUrl.AppendPath(path);
7641 pItem->SetPath(itemUrl.ToString());
7643 pItem->m_bIsFolder=true;
7644 pItem->GetVideoInfoTag()->SetPlayCount(i.second.playcount);
7645 pItem->GetVideoInfoTag()->m_strPictureURL.ParseFromData(i.second.thumb);
7646 pItem->GetVideoInfoTag()->m_iDbId = i.first;
7647 pItem->GetVideoInfoTag()->m_type = type;
7648 pItem->GetVideoInfoTag()->m_relevance = i.second.appearances;
7649 if (idContent == VideoDbContentType::MUSICVIDEOS)
7651 // Get artist bio from music db later if available
7652 pItem->GetVideoInfoTag()->m_artist.emplace_back(i.second.name);
7653 pItem->SetProperty("musicvideomediatype", MediaTypeArtist);
7655 items.Add(pItem);
7658 else
7660 while (!m_pDS->eof())
7664 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
7666 CVideoDbUrl itemUrl = videoUrl;
7667 std::string path = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
7668 itemUrl.AppendPath(path);
7669 pItem->SetPath(itemUrl.ToString());
7671 pItem->m_bIsFolder=true;
7672 pItem->GetVideoInfoTag()->m_strPictureURL.ParseFromData(m_pDS->fv(2).get_asString());
7673 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
7674 pItem->GetVideoInfoTag()->m_type = type;
7675 if (idContent != VideoDbContentType::TVSHOWS)
7677 // fv(4) is the number of videos watched, fv(3) is the total number. We set the playcount
7678 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7679 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(4).get_asInt() == m_pDS->fv(3).get_asInt()) ? 1 : 0);
7681 pItem->GetVideoInfoTag()->m_relevance = m_pDS->fv(3).get_asInt();
7682 if (idContent == VideoDbContentType::MUSICVIDEOS)
7684 pItem->GetVideoInfoTag()->m_artist.emplace_back(pItem->GetLabel());
7685 pItem->SetProperty("musicvideomediatype", MediaTypeArtist);
7687 items.Add(pItem);
7688 m_pDS->next();
7690 catch (...)
7692 m_pDS->close();
7693 CLog::Log(LOGERROR, "{}: out of memory - retrieved {} items", __FUNCTION__, items.Size());
7694 return items.Size() > 0;
7697 m_pDS->close();
7700 end = std::chrono::steady_clock::now();
7701 duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
7703 CLog::Log(LOGDEBUG, LOGDATABASE, "{} item retrieval took {} ms", __FUNCTION__,
7704 duration.count());
7706 return true;
7708 catch (...)
7710 m_pDS->close();
7711 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7713 return false;
7716 bool CVideoDatabase::GetYearsNav(const std::string& strBaseDir,
7717 CFileItemList& items,
7718 VideoDbContentType idContent /* = UNKNOWN */,
7719 const Filter& filter /* = Filter() */)
7723 if (nullptr == m_pDB)
7724 return false;
7725 if (nullptr == m_pDS)
7726 return false;
7728 std::string strSQL;
7729 Filter extFilter = filter;
7730 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7732 if (idContent == VideoDbContentType::MOVIES)
7734 strSQL = "select movie_view.premiered, path.strPath, files.playCount from movie_view ";
7735 extFilter.AppendJoin("join files on files.idFile = movie_view.idFile join path on files.idPath = path.idPath");
7737 else if (idContent == VideoDbContentType::TVSHOWS)
7739 strSQL = PrepareSQL("select tvshow_view.c%02d, path.strPath from tvshow_view ", VIDEODB_ID_TV_PREMIERED);
7740 extFilter.AppendJoin("join episode_view on episode_view.idShow = tvshow_view.idShow join files on files.idFile = episode_view.idFile join path on files.idPath = path.idPath");
7742 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7744 strSQL = "select musicvideo_view.premiered, path.strPath, files.playCount from musicvideo_view ";
7745 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on files.idPath = path.idPath");
7747 else
7748 return false;
7750 else
7752 std::string group;
7753 if (idContent == VideoDbContentType::MOVIES)
7755 strSQL = "select movie_view.premiered, count(1), count(files.playCount) from movie_view ";
7756 extFilter.AppendJoin("join files on files.idFile = movie_view.idFile");
7757 extFilter.AppendGroup("movie_view.premiered");
7759 else if (idContent == VideoDbContentType::TVSHOWS)
7761 strSQL = PrepareSQL("select distinct tvshow_view.c%02d from tvshow_view", VIDEODB_ID_TV_PREMIERED);
7762 extFilter.AppendGroup(PrepareSQL("tvshow_view.c%02d", VIDEODB_ID_TV_PREMIERED));
7764 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7766 strSQL = "select musicvideo_view.premiered, count(1), count(files.playCount) from musicvideo_view ";
7767 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile");
7768 extFilter.AppendGroup("musicvideo_view.premiered");
7770 else
7771 return false;
7774 CVideoDbUrl videoUrl;
7775 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
7776 return false;
7778 int iRowsFound = RunQuery(strSQL);
7779 if (iRowsFound <= 0)
7780 return iRowsFound == 0;
7782 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7784 std::map<int, std::pair<std::string,int> > mapYears;
7785 while (!m_pDS->eof())
7787 int lYear = 0;
7788 std::string dateString = m_pDS->fv(0).get_asString();
7789 if (dateString.size() == 4)
7790 lYear = m_pDS->fv(0).get_asInt();
7791 else
7793 CDateTime time;
7794 time.SetFromDateString(dateString);
7795 if (time.IsValid())
7796 lYear = time.GetYear();
7798 auto it = mapYears.find(lYear);
7799 if (it == mapYears.end())
7801 // check path
7802 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7804 std::string year = std::to_string(lYear);
7805 if (idContent == VideoDbContentType::MOVIES ||
7806 idContent == VideoDbContentType::MUSICVIDEOS)
7807 mapYears.insert(std::pair<int, std::pair<std::string,int> >(lYear, std::pair<std::string,int>(year,m_pDS->fv(2).get_asInt())));
7808 else
7809 mapYears.insert(std::pair<int, std::pair<std::string,int> >(lYear, std::pair<std::string,int>(year,0)));
7812 m_pDS->next();
7814 m_pDS->close();
7816 for (const auto &i : mapYears)
7818 if (i.first == 0)
7819 continue;
7820 CFileItemPtr pItem(new CFileItem(i.second.first));
7822 CVideoDbUrl itemUrl = videoUrl;
7823 std::string path = StringUtils::Format("{}/", i.first);
7824 itemUrl.AppendPath(path);
7825 pItem->SetPath(itemUrl.ToString());
7827 pItem->m_bIsFolder=true;
7828 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7829 pItem->GetVideoInfoTag()->SetPlayCount(i.second.second);
7830 items.Add(pItem);
7833 else
7835 while (!m_pDS->eof())
7837 int lYear = 0;
7838 std::string strLabel = m_pDS->fv(0).get_asString();
7839 if (strLabel.size() == 4)
7840 lYear = m_pDS->fv(0).get_asInt();
7841 else
7843 CDateTime time;
7844 time.SetFromDateString(strLabel);
7845 if (time.IsValid())
7847 lYear = time.GetYear();
7848 strLabel = std::to_string(lYear);
7851 if (lYear == 0)
7853 m_pDS->next();
7854 continue;
7856 CFileItemPtr pItem(new CFileItem(strLabel));
7858 CVideoDbUrl itemUrl = videoUrl;
7859 std::string path = StringUtils::Format("{}/", lYear);
7860 itemUrl.AppendPath(path);
7861 pItem->SetPath(itemUrl.ToString());
7863 pItem->m_bIsFolder=true;
7864 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7866 // fv(2) is the number of videos watched, fv(1) is the total number. We set the playcount
7867 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7868 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(2).get_asInt() == m_pDS->fv(1).get_asInt()) ? 1 : 0);
7871 // take care of dupes ..
7872 if (!items.Contains(pItem->GetPath()))
7873 items.Add(pItem);
7875 m_pDS->next();
7877 m_pDS->close();
7880 return true;
7882 catch (...)
7884 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7886 return false;
7889 bool CVideoDatabase::GetSeasonsNav(const std::string& strBaseDir, CFileItemList& items, int idActor, int idDirector, int idGenre, int idYear, int idShow, bool getLinkedMovies /* = true */)
7891 // parse the base path to get additional filters
7892 CVideoDbUrl videoUrl;
7893 if (!videoUrl.FromString(strBaseDir))
7894 return false;
7896 if (idShow != -1)
7897 videoUrl.AddOption("tvshowid", idShow);
7898 if (idActor != -1)
7899 videoUrl.AddOption("actorid", idActor);
7900 else if (idDirector != -1)
7901 videoUrl.AddOption("directorid", idDirector);
7902 else if (idGenre != -1)
7903 videoUrl.AddOption("genreid", idGenre);
7904 else if (idYear != -1)
7905 videoUrl.AddOption("year", idYear);
7907 if (!GetSeasonsByWhere(videoUrl.ToString(), Filter(), items, false))
7908 return false;
7910 // now add any linked movies
7911 if (getLinkedMovies && idShow != -1)
7913 Filter movieFilter;
7914 movieFilter.join = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
7915 movieFilter.where = PrepareSQL("movielinktvshow.idShow = %i", idShow);
7916 CFileItemList movieItems;
7917 GetMoviesByWhere("videodb://movies/titles/", movieFilter, movieItems);
7919 if (movieItems.Size() > 0)
7920 items.Append(movieItems);
7923 return true;
7926 bool CVideoDatabase::GetSeasonsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool appendFullShowPath /* = true */, const SortDescription &sortDescription /* = SortDescription() */)
7930 if (nullptr == m_pDB)
7931 return false;
7932 if (nullptr == m_pDS)
7933 return false;
7935 int total = -1;
7937 std::string strSQL = "SELECT %s FROM season_view ";
7938 CVideoDbUrl videoUrl;
7939 std::string strSQLExtra;
7940 Filter extFilter = filter;
7941 SortDescription sorting = sortDescription;
7942 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
7943 return false;
7945 // Apply the limiting directly here if there's no special sorting but limiting
7946 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
7947 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
7948 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
7950 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7951 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7954 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7956 int iRowsFound = RunQuery(strSQL);
7958 // store the total value of items as a property
7959 if (total < iRowsFound)
7960 total = iRowsFound;
7961 items.SetProperty("total", total);
7963 if (iRowsFound <= 0)
7964 return iRowsFound == 0;
7966 std::set<std::pair<int, int>> mapSeasons;
7967 while (!m_pDS->eof())
7969 int id = m_pDS->fv(VIDEODB_ID_SEASON_ID).get_asInt();
7970 int showId = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_ID).get_asInt();
7971 int iSeason = m_pDS->fv(VIDEODB_ID_SEASON_NUMBER).get_asInt();
7972 std::string name = m_pDS->fv(VIDEODB_ID_SEASON_NAME).get_asString();
7973 std::string path = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PATH).get_asString();
7975 if (mapSeasons.find(std::make_pair(showId, iSeason)) == mapSeasons.end() &&
7976 (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || g_passwordManager.bMasterUser ||
7977 g_passwordManager.IsDatabasePathUnlocked(path, *CMediaSourceSettings::GetInstance().GetSources("video"))))
7979 mapSeasons.insert(std::make_pair(showId, iSeason));
7981 std::string strLabel = name;
7982 if (strLabel.empty())
7984 if (iSeason == 0)
7985 strLabel = g_localizeStrings.Get(20381);
7986 else
7987 strLabel = StringUtils::Format(g_localizeStrings.Get(20358), iSeason);
7989 CFileItemPtr pItem(new CFileItem(strLabel));
7991 CVideoDbUrl itemUrl = videoUrl;
7992 std::string strDir;
7993 if (appendFullShowPath)
7994 strDir += StringUtils::Format("{}/", showId);
7995 strDir += StringUtils::Format("{}/", iSeason);
7996 itemUrl.AppendPath(strDir);
7997 pItem->SetPath(itemUrl.ToString());
7999 pItem->m_bIsFolder = true;
8000 pItem->GetVideoInfoTag()->m_strTitle = strLabel;
8001 if (!name.empty())
8002 pItem->GetVideoInfoTag()->m_strSortTitle = name;
8003 pItem->GetVideoInfoTag()->m_iSeason = iSeason;
8004 pItem->GetVideoInfoTag()->m_iDbId = id;
8005 pItem->GetVideoInfoTag()->m_iIdSeason = id;
8006 pItem->GetVideoInfoTag()->m_type = MediaTypeSeason;
8007 pItem->GetVideoInfoTag()->m_strPath = path;
8008 pItem->GetVideoInfoTag()->m_strShowTitle = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_TITLE).get_asString();
8009 pItem->GetVideoInfoTag()->m_strPlot = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PLOT).get_asString();
8010 pItem->GetVideoInfoTag()->SetPremieredFromDBDate(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PREMIERED).get_asString());
8011 pItem->GetVideoInfoTag()->m_firstAired.SetFromDBDate(m_pDS->fv(VIDEODB_ID_SEASON_PREMIERED).get_asString());
8012 pItem->GetVideoInfoTag()->m_iUserRating = m_pDS->fv(VIDEODB_ID_SEASON_USER_RATING).get_asInt();
8013 // season premiered date based on first episode airdate associated to the season
8014 // tvshow premiered date is used as a fallback
8015 if (pItem->GetVideoInfoTag()->m_firstAired.IsValid())
8016 pItem->GetVideoInfoTag()->SetPremiered(pItem->GetVideoInfoTag()->m_firstAired);
8017 else if (pItem->GetVideoInfoTag()->HasPremiered())
8018 pItem->GetVideoInfoTag()->SetPremiered(pItem->GetVideoInfoTag()->GetPremiered());
8019 else if (pItem->GetVideoInfoTag()->HasYear())
8020 pItem->GetVideoInfoTag()->SetYear(pItem->GetVideoInfoTag()->GetYear());
8021 pItem->GetVideoInfoTag()->m_genre = StringUtils::Split(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_GENRE).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
8022 pItem->GetVideoInfoTag()->m_studio = StringUtils::Split(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_STUDIO).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
8023 pItem->GetVideoInfoTag()->m_strMPAARating = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_MPAA).get_asString();
8024 pItem->GetVideoInfoTag()->m_iIdShow = showId;
8026 const int totalEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_TOTAL).get_asInt();
8027 const int watchedEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_WATCHED).get_asInt();
8028 const int inProgressEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_INPROGRESS).get_asInt();
8030 pItem->GetVideoInfoTag()->m_iEpisode = totalEpisodes;
8031 pItem->SetProperty("totalepisodes", totalEpisodes);
8032 pItem->SetProperty("numepisodes", totalEpisodes); // will be changed later to reflect watchmode setting
8033 pItem->SetProperty("watchedepisodes", watchedEpisodes);
8034 pItem->SetProperty("unwatchedepisodes", totalEpisodes - watchedEpisodes);
8035 pItem->SetProperty("inprogressepisodes", inProgressEpisodes);
8036 pItem->SetProperty("watchedepisodepercent",
8037 totalEpisodes > 0 ? (watchedEpisodes * 100 / totalEpisodes) : 0);
8038 if (iSeason == 0)
8039 pItem->SetProperty("isspecial", true);
8040 pItem->GetVideoInfoTag()->SetPlayCount((totalEpisodes == watchedEpisodes) ? 1 : 0);
8041 pItem->SetOverlayImage((pItem->GetVideoInfoTag()->GetPlayCount() > 0) &&
8042 (pItem->GetVideoInfoTag()->m_iEpisode > 0)
8043 ? CGUIListItem::ICON_OVERLAY_WATCHED
8044 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8046 items.Add(pItem);
8049 m_pDS->next();
8051 m_pDS->close();
8053 return true;
8055 catch (...)
8057 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8059 return false;
8062 bool CVideoDatabase::GetSortedVideos(const MediaType &mediaType, const std::string& strBaseDir, const SortDescription &sortDescription, CFileItemList& items, const Filter &filter /* = Filter() */)
8064 if (nullptr == m_pDB || nullptr == m_pDS)
8065 return false;
8067 if (mediaType != MediaTypeMovie && mediaType != MediaTypeTvShow && mediaType != MediaTypeEpisode && mediaType != MediaTypeMusicVideo)
8068 return false;
8070 SortDescription sorting = sortDescription;
8071 if (sortDescription.sortBy == SortByFile || sortDescription.sortBy == SortByTitle ||
8072 sortDescription.sortBy == SortBySortTitle || sortDescription.sortBy == SortByOriginalTitle ||
8073 sortDescription.sortBy == SortByLabel || sortDescription.sortBy == SortByDateAdded ||
8074 sortDescription.sortBy == SortByRating || sortDescription.sortBy == SortByUserRating ||
8075 sortDescription.sortBy == SortByYear || sortDescription.sortBy == SortByLastPlayed ||
8076 sortDescription.sortBy == SortByPlaycount)
8077 sorting.sortAttributes = (SortAttribute)(sortDescription.sortAttributes | SortAttributeIgnoreFolders);
8079 bool success = false;
8080 if (mediaType == MediaTypeMovie)
8081 success = GetMoviesByWhere(strBaseDir, filter, items, sorting);
8082 else if (mediaType == MediaTypeTvShow)
8083 success = GetTvShowsByWhere(strBaseDir, filter, items, sorting);
8084 else if (mediaType == MediaTypeEpisode)
8085 success = GetEpisodesByWhere(strBaseDir, filter, items, true, sorting);
8086 else if (mediaType == MediaTypeMusicVideo)
8087 success = GetMusicVideosByWhere(strBaseDir, filter, items, true, sorting);
8088 else
8089 return false;
8091 items.SetContent(CMediaTypes::ToPlural(mediaType));
8092 return success;
8095 bool CVideoDatabase::GetItems(const std::string &strBaseDir, CFileItemList &items, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */)
8097 CVideoDbUrl videoUrl;
8098 if (!videoUrl.FromString(strBaseDir))
8099 return false;
8101 return GetItems(strBaseDir, videoUrl.GetType(), videoUrl.GetItemType(), items, filter, sortDescription);
8104 bool CVideoDatabase::GetItems(const std::string &strBaseDir, const std::string &mediaType, const std::string &itemType, CFileItemList &items, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */)
8106 VideoDbContentType contentType;
8107 if (StringUtils::EqualsNoCase(mediaType, "movies"))
8108 contentType = VideoDbContentType::MOVIES;
8109 else if (StringUtils::EqualsNoCase(mediaType, "tvshows"))
8111 if (StringUtils::EqualsNoCase(itemType, "episodes"))
8112 contentType = VideoDbContentType::EPISODES;
8113 else
8114 contentType = VideoDbContentType::TVSHOWS;
8116 else if (StringUtils::EqualsNoCase(mediaType, "musicvideos"))
8117 contentType = VideoDbContentType::MUSICVIDEOS;
8118 else
8119 return false;
8121 return GetItems(strBaseDir, contentType, itemType, items, filter, sortDescription);
8124 bool CVideoDatabase::GetItems(const std::string& strBaseDir,
8125 VideoDbContentType mediaType,
8126 const std::string& itemType,
8127 CFileItemList& items,
8128 const Filter& filter /* = Filter() */,
8129 const SortDescription& sortDescription /* = SortDescription() */)
8131 if (StringUtils::EqualsNoCase(itemType, "movies") &&
8132 (mediaType == VideoDbContentType::MOVIES || mediaType == VideoDbContentType::MOVIE_SETS))
8133 return GetMoviesByWhere(strBaseDir, filter, items, sortDescription);
8134 else if (StringUtils::EqualsNoCase(itemType, "tvshows") &&
8135 mediaType == VideoDbContentType::TVSHOWS)
8137 Filter extFilter = filter;
8138 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->
8139 GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS))
8140 extFilter.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
8141 return GetTvShowsByWhere(strBaseDir, extFilter, items, sortDescription);
8143 else if (StringUtils::EqualsNoCase(itemType, "musicvideos") &&
8144 mediaType == VideoDbContentType::MUSICVIDEOS)
8145 return GetMusicVideosByWhere(strBaseDir, filter, items, true, sortDescription);
8146 else if (StringUtils::EqualsNoCase(itemType, "episodes") &&
8147 mediaType == VideoDbContentType::EPISODES)
8148 return GetEpisodesByWhere(strBaseDir, filter, items, true, sortDescription);
8149 else if (StringUtils::EqualsNoCase(itemType, "seasons") &&
8150 mediaType == VideoDbContentType::TVSHOWS)
8151 return GetSeasonsNav(strBaseDir, items);
8152 else if (StringUtils::EqualsNoCase(itemType, "genres"))
8153 return GetGenresNav(strBaseDir, items, mediaType, filter);
8154 else if (StringUtils::EqualsNoCase(itemType, "years"))
8155 return GetYearsNav(strBaseDir, items, mediaType, filter);
8156 else if (StringUtils::EqualsNoCase(itemType, "actors"))
8157 return GetActorsNav(strBaseDir, items, mediaType, filter);
8158 else if (StringUtils::EqualsNoCase(itemType, "directors"))
8159 return GetDirectorsNav(strBaseDir, items, mediaType, filter);
8160 else if (StringUtils::EqualsNoCase(itemType, "writers"))
8161 return GetWritersNav(strBaseDir, items, mediaType, filter);
8162 else if (StringUtils::EqualsNoCase(itemType, "studios"))
8163 return GetStudiosNav(strBaseDir, items, mediaType, filter);
8164 else if (StringUtils::EqualsNoCase(itemType, "sets"))
8165 return GetSetsNav(strBaseDir, items, mediaType, filter, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPSINGLEITEMSETS));
8166 else if (StringUtils::EqualsNoCase(itemType, "countries"))
8167 return GetCountriesNav(strBaseDir, items, mediaType, filter);
8168 else if (StringUtils::EqualsNoCase(itemType, "tags"))
8169 return GetTagsNav(strBaseDir, items, mediaType, filter);
8170 else if (StringUtils::EqualsNoCase(itemType, "videoversions"))
8171 return GetVideoVersionsNav(strBaseDir, items, mediaType, filter);
8172 else if (StringUtils::EqualsNoCase(itemType, "artists") &&
8173 mediaType == VideoDbContentType::MUSICVIDEOS)
8174 return GetActorsNav(strBaseDir, items, mediaType, filter);
8175 else if (StringUtils::EqualsNoCase(itemType, "albums") &&
8176 mediaType == VideoDbContentType::MUSICVIDEOS)
8177 return GetMusicVideoAlbumsNav(strBaseDir, items, -1, filter);
8179 return false;
8182 std::string CVideoDatabase::GetItemById(const std::string &itemType, int id)
8184 if (StringUtils::EqualsNoCase(itemType, "genres"))
8185 return GetGenreById(id);
8186 else if (StringUtils::EqualsNoCase(itemType, "years"))
8187 return std::to_string(id);
8188 else if (StringUtils::EqualsNoCase(itemType, "actors") ||
8189 StringUtils::EqualsNoCase(itemType, "directors") ||
8190 StringUtils::EqualsNoCase(itemType, "artists"))
8191 return GetPersonById(id);
8192 else if (StringUtils::EqualsNoCase(itemType, "studios"))
8193 return GetStudioById(id);
8194 else if (StringUtils::EqualsNoCase(itemType, "sets"))
8195 return GetSetById(id);
8196 else if (StringUtils::EqualsNoCase(itemType, "countries"))
8197 return GetCountryById(id);
8198 else if (StringUtils::EqualsNoCase(itemType, "tags"))
8199 return GetTagById(id);
8200 else if (StringUtils::EqualsNoCase(itemType, "videoversions"))
8201 return GetVideoVersionById(id);
8202 else if (StringUtils::EqualsNoCase(itemType, "albums"))
8203 return GetMusicVideoAlbumById(id);
8205 return "";
8208 bool CVideoDatabase::GetMoviesNav(const std::string& strBaseDir, CFileItemList& items,
8209 int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */,
8210 int idStudio /* = -1 */, int idCountry /* = -1 */, int idSet /* = -1 */, int idTag /* = -1 */,
8211 const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8213 CVideoDbUrl videoUrl;
8214 if (!videoUrl.FromString(strBaseDir))
8215 return false;
8217 if (idGenre > 0)
8218 videoUrl.AddOption("genreid", idGenre);
8219 else if (idCountry > 0)
8220 videoUrl.AddOption("countryid", idCountry);
8221 else if (idStudio > 0)
8222 videoUrl.AddOption("studioid", idStudio);
8223 else if (idDirector > 0)
8224 videoUrl.AddOption("directorid", idDirector);
8225 else if (idYear > 0)
8226 videoUrl.AddOption("year", idYear);
8227 else if (idActor > 0)
8228 videoUrl.AddOption("actorid", idActor);
8229 else if (idSet > 0)
8230 videoUrl.AddOption("setid", idSet);
8231 else if (idTag > 0)
8232 videoUrl.AddOption("tagid", idTag);
8234 Filter filter;
8235 return GetMoviesByWhere(videoUrl.ToString(), filter, items, sortDescription, getDetails);
8238 namespace
8240 std::string RewriteVideoVersionURL(const std::string& baseDir, const CVideoInfoTag& movie)
8242 const CURL parentPath{URIUtils::GetParentPath(baseDir)};
8243 const std::string versionId{std::to_string(movie.GetAssetInfo().GetId())};
8244 const std::string mediaId{std::to_string(movie.m_iDbId)};
8245 CVideoDbUrl url;
8246 url.FromString(parentPath.GetWithoutOptions());
8247 url.AppendPath(versionId);
8248 url.AppendPath(mediaId);
8249 url.AddOption("videoversionid", versionId);
8250 url.AddOption("mediaid", mediaId);
8251 return url.ToString();
8253 } // unnamed namespace
8255 bool CVideoDatabase::GetMoviesByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8259 if (nullptr == m_pDB)
8260 return false;
8261 if (nullptr == m_pDS)
8262 return false;
8264 // parse the base path to get additional filters
8265 CVideoDbUrl videoUrl;
8266 Filter extFilter = filter;
8267 SortDescription sorting = sortDescription;
8268 if (!videoUrl.FromString(strBaseDir) || !GetFilter(videoUrl, extFilter, sorting))
8269 return false;
8271 int total = -1;
8273 std::string strSQL = "select %s from movie_view ";
8274 std::string strSQLExtra;
8275 if (!CDatabase::BuildSQL(strSQLExtra, extFilter, strSQLExtra))
8276 return false;
8278 // Apply the limiting directly here if there's no special sorting but limiting
8279 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
8280 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
8281 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
8283 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
8284 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
8287 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
8289 int iRowsFound = RunQuery(strSQL);
8291 // store the total value of items as a property
8292 if (total < iRowsFound)
8293 total = iRowsFound;
8294 items.SetProperty("total", total);
8296 if (iRowsFound <= 0)
8297 return iRowsFound == 0;
8299 DatabaseResults results;
8300 results.reserve(iRowsFound);
8302 if (!SortUtils::SortFromDataset(sortDescription, MediaTypeMovie, m_pDS, results))
8303 return false;
8305 // get data from returned rows
8306 items.Reserve(results.size());
8307 const query_data &data = m_pDS->get_result_set().records;
8308 for (const auto &i : results)
8310 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
8311 const dbiplus::sql_record* const record = data.at(targetRow);
8313 CVideoInfoTag movie = GetDetailsForMovie(record, getDetails);
8314 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
8315 g_passwordManager.bMasterUser ||
8316 g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
8318 CFileItemPtr pItem(new CFileItem(movie));
8320 std::string path;
8321 CVideoDbUrl itemUrl{videoUrl};
8322 CVariant value;
8323 if (itemUrl.GetOption("videoversionid", value))
8325 //! @todo get rid of "videos with versions as folder" hack!
8326 if (value.asInteger() == VIDEO_VERSION_ID_ALL)
8328 // all versions for the given media id requested; we need to insert the real video
8329 // version id for this movie into the videodb url
8330 path = RewriteVideoVersionURL(strBaseDir, movie);
8332 // this is a certain version, no need to resolve (e.g. no version chooser on select)
8333 pItem->SetProperty("has_resolved_video_asset", true);
8336 if (path.empty())
8338 itemUrl.AppendPath(std::to_string(movie.m_iDbId));
8339 path = itemUrl.ToString();
8342 pItem->SetPath(path);
8343 pItem->SetDynPath(movie.m_strFileNameAndPath);
8345 pItem->SetOverlayImage(movie.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
8346 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8347 items.Add(pItem);
8351 // cleanup
8352 m_pDS->close();
8353 return true;
8355 catch (...)
8357 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8359 return false;
8362 bool CVideoDatabase::GetTvShowsNav(const std::string& strBaseDir, CFileItemList& items,
8363 int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */, int idStudio /* = -1 */, int idTag /* = -1 */,
8364 const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8366 CVideoDbUrl videoUrl;
8367 if (!videoUrl.FromString(strBaseDir))
8368 return false;
8370 if (idGenre != -1)
8371 videoUrl.AddOption("genreid", idGenre);
8372 else if (idStudio != -1)
8373 videoUrl.AddOption("studioid", idStudio);
8374 else if (idDirector != -1)
8375 videoUrl.AddOption("directorid", idDirector);
8376 else if (idYear != -1)
8377 videoUrl.AddOption("year", idYear);
8378 else if (idActor != -1)
8379 videoUrl.AddOption("actorid", idActor);
8380 else if (idTag != -1)
8381 videoUrl.AddOption("tagid", idTag);
8383 Filter filter;
8384 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS))
8385 filter.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
8386 return GetTvShowsByWhere(videoUrl.ToString(), filter, items, sortDescription, getDetails);
8389 bool CVideoDatabase::GetTvShowsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8393 if (nullptr == m_pDB)
8394 return false;
8395 if (nullptr == m_pDS)
8396 return false;
8398 int total = -1;
8400 std::string strSQL = "SELECT %s FROM tvshow_view ";
8401 CVideoDbUrl videoUrl;
8402 std::string strSQLExtra;
8403 Filter extFilter = filter;
8404 SortDescription sorting = sortDescription;
8405 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
8406 return false;
8408 // Apply the limiting directly here if there's no special sorting but limiting
8409 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
8410 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
8411 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
8413 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
8414 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
8417 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
8419 int iRowsFound = RunQuery(strSQL);
8421 // store the total value of items as a property
8422 if (total < iRowsFound)
8423 total = iRowsFound;
8424 items.SetProperty("total", total);
8426 if (iRowsFound <= 0)
8427 return iRowsFound == 0;
8429 DatabaseResults results;
8430 results.reserve(iRowsFound);
8431 if (!SortUtils::SortFromDataset(sorting, MediaTypeTvShow, m_pDS, results))
8432 return false;
8434 // get data from returned rows
8435 items.Reserve(results.size());
8436 const query_data &data = m_pDS->get_result_set().records;
8437 for (const auto &i : results)
8439 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
8440 const dbiplus::sql_record* const record = data.at(targetRow);
8442 CFileItemPtr pItem(new CFileItem());
8443 CVideoInfoTag movie = GetDetailsForTvShow(record, getDetails, pItem.get());
8444 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
8445 g_passwordManager.bMasterUser ||
8446 g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
8448 pItem->SetFromVideoInfoTag(movie);
8450 CVideoDbUrl itemUrl = videoUrl;
8451 std::string path = StringUtils::Format("{}/", record->at(0).get_asInt());
8452 itemUrl.AppendPath(path);
8453 pItem->SetPath(itemUrl.ToString());
8455 pItem->SetOverlayImage((pItem->GetVideoInfoTag()->GetPlayCount() > 0) &&
8456 (pItem->GetVideoInfoTag()->m_iEpisode > 0)
8457 ? CGUIListItem::ICON_OVERLAY_WATCHED
8458 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8459 items.Add(pItem);
8463 // cleanup
8464 m_pDS->close();
8465 return true;
8467 catch (...)
8469 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8471 return false;
8474 bool CVideoDatabase::GetEpisodesNav(const std::string& strBaseDir, CFileItemList& items, int idGenre, int idYear, int idActor, int idDirector, int idShow, int idSeason, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8476 CVideoDbUrl videoUrl;
8477 if (!videoUrl.FromString(strBaseDir))
8478 return false;
8480 if (idShow != -1)
8482 videoUrl.AddOption("tvshowid", idShow);
8483 if (idSeason >= 0)
8484 videoUrl.AddOption("season", idSeason);
8486 if (idGenre != -1)
8487 videoUrl.AddOption("genreid", idGenre);
8488 else if (idYear !=-1)
8489 videoUrl.AddOption("year", idYear);
8490 else if (idActor != -1)
8491 videoUrl.AddOption("actorid", idActor);
8493 else if (idYear != -1)
8494 videoUrl.AddOption("year", idYear);
8496 if (idDirector != -1)
8497 videoUrl.AddOption("directorid", idDirector);
8499 Filter filter;
8500 bool ret = GetEpisodesByWhere(videoUrl.ToString(), filter, items, false, sortDescription, getDetails);
8502 if (idSeason == -1 && idShow != -1)
8503 { // add any linked movies
8504 Filter movieFilter;
8505 movieFilter.join = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
8506 movieFilter.where = PrepareSQL("movielinktvshow.idShow = %i", idShow);
8507 CFileItemList movieItems;
8508 GetMoviesByWhere("videodb://movies/titles/", movieFilter, movieItems);
8510 if (movieItems.Size() > 0)
8511 items.Append(movieItems);
8514 return ret;
8517 bool CVideoDatabase::GetEpisodesByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool appendFullShowPath /* = true */, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8521 if (nullptr == m_pDB)
8522 return false;
8523 if (nullptr == m_pDS)
8524 return false;
8526 int total = -1;
8528 std::string strSQL = "select %s from episode_view ";
8529 CVideoDbUrl videoUrl;
8530 std::string strSQLExtra;
8531 Filter extFilter = filter;
8532 SortDescription sorting = sortDescription;
8533 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
8534 return false;
8536 // Apply the limiting directly here if there's no special sorting but limiting
8537 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
8538 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
8539 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
8541 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
8542 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
8545 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
8547 int iRowsFound = RunQuery(strSQL);
8549 // store the total value of items as a property
8550 if (total < iRowsFound)
8551 total = iRowsFound;
8552 items.SetProperty("total", total);
8554 if (iRowsFound <= 0)
8555 return iRowsFound == 0;
8557 DatabaseResults results;
8558 results.reserve(iRowsFound);
8559 if (!SortUtils::SortFromDataset(sorting, MediaTypeEpisode, m_pDS, results))
8560 return false;
8562 // get data from returned rows
8563 items.Reserve(results.size());
8564 CLabelFormatter formatter("%H. %T", "");
8566 const query_data &data = m_pDS->get_result_set().records;
8567 for (const auto &i : results)
8569 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
8570 const dbiplus::sql_record* const record = data.at(targetRow);
8572 CVideoInfoTag episode = GetDetailsForEpisode(record, getDetails);
8573 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
8574 g_passwordManager.bMasterUser ||
8575 g_passwordManager.IsDatabasePathUnlocked(episode.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
8577 CFileItemPtr pItem(new CFileItem(episode));
8578 formatter.FormatLabel(pItem.get());
8580 int idEpisode = record->at(0).get_asInt();
8582 CVideoDbUrl itemUrl = videoUrl;
8583 std::string path;
8584 if (appendFullShowPath && videoUrl.GetItemType() != "episodes")
8585 path = StringUtils::Format("{}/{}/{}",
8586 record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt(),
8587 episode.m_iSeason, idEpisode);
8588 else
8589 path = std::to_string(idEpisode);
8590 itemUrl.AppendPath(path);
8591 pItem->SetPath(itemUrl.ToString());
8592 pItem->SetDynPath(episode.m_strFileNameAndPath);
8594 pItem->SetOverlayImage(episode.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
8595 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8596 pItem->m_dateTime = episode.m_firstAired;
8597 items.Add(pItem);
8601 // cleanup
8602 m_pDS->close();
8603 return true;
8605 catch (...)
8607 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8609 return false;
8612 bool CVideoDatabase::GetMusicVideosNav(const std::string& strBaseDir, CFileItemList& items, int idGenre, int idYear, int idArtist, int idDirector, int idStudio, int idAlbum, int idTag /* = -1 */, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8614 CVideoDbUrl videoUrl;
8615 if (!videoUrl.FromString(strBaseDir))
8616 return false;
8618 if (idGenre != -1)
8619 videoUrl.AddOption("genreid", idGenre);
8620 else if (idStudio != -1)
8621 videoUrl.AddOption("studioid", idStudio);
8622 else if (idDirector != -1)
8623 videoUrl.AddOption("directorid", idDirector);
8624 else if (idYear !=-1)
8625 videoUrl.AddOption("year", idYear);
8626 else if (idArtist != -1)
8627 videoUrl.AddOption("artistid", idArtist);
8628 else if (idTag != -1)
8629 videoUrl.AddOption("tagid", idTag);
8630 if (idAlbum != -1)
8631 videoUrl.AddOption("albumid", idAlbum);
8633 Filter filter;
8634 return GetMusicVideosByWhere(videoUrl.ToString(), filter, items, true, sortDescription, getDetails);
8637 bool CVideoDatabase::GetRecentlyAddedMoviesNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8639 Filter filter;
8640 filter.order = "dateAdded desc, idMovie desc";
8641 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8642 return GetMoviesByWhere(strBaseDir, filter, items, SortDescription(), getDetails);
8645 bool CVideoDatabase::GetRecentlyAddedEpisodesNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8647 Filter filter;
8648 filter.order = "dateAdded desc, idEpisode desc";
8649 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8650 return GetEpisodesByWhere(strBaseDir, filter, items, false, SortDescription(), getDetails);
8653 bool CVideoDatabase::GetRecentlyAddedMusicVideosNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8655 Filter filter;
8656 filter.order = "dateAdded desc, idMVideo desc";
8657 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8658 return GetMusicVideosByWhere(strBaseDir, filter, items, true, SortDescription(), getDetails);
8661 bool CVideoDatabase::GetInProgressTvShowsNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8663 Filter filter;
8664 filter.order = PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE);
8665 filter.where = "totalCount != watchedCount AND (inProgressCount > 0 OR watchedCount != 0)";
8666 return GetTvShowsByWhere(strBaseDir, filter, items, SortDescription(), getDetails);
8669 std::string CVideoDatabase::GetGenreById(int id)
8671 return GetSingleValue("genre", "name", PrepareSQL("genre_id=%i", id));
8674 std::string CVideoDatabase::GetCountryById(int id)
8676 return GetSingleValue("country", "name", PrepareSQL("country_id=%i", id));
8679 std::string CVideoDatabase::GetSetById(int id)
8681 return GetSingleValue("sets", "strSet", PrepareSQL("idSet=%i", id));
8684 std::string CVideoDatabase::GetSetByNameLike(const std::string& nameLike) const
8686 return GetSingleValue("sets", "strSet", PrepareSQL("strSet LIKE '%s'", nameLike.c_str()));
8689 std::string CVideoDatabase::GetTagById(int id)
8691 return GetSingleValue("tag", "name", PrepareSQL("tag_id = %i", id));
8694 std::string CVideoDatabase::GetPersonById(int id)
8696 return GetSingleValue("actor", "name", PrepareSQL("actor_id=%i", id));
8699 std::string CVideoDatabase::GetStudioById(int id)
8701 return GetSingleValue("studio", "name", PrepareSQL("studio_id=%i", id));
8704 std::string CVideoDatabase::GetTvShowTitleById(int id)
8706 return GetSingleValue("tvshow", PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE), PrepareSQL("idShow=%i", id));
8709 std::string CVideoDatabase::GetMusicVideoAlbumById(int id)
8711 return GetSingleValue("musicvideo", PrepareSQL("c%02d", VIDEODB_ID_MUSICVIDEO_ALBUM), PrepareSQL("idMVideo=%i", id));
8714 bool CVideoDatabase::HasSets() const
8718 if (nullptr == m_pDB)
8719 return false;
8720 if (nullptr == m_pDS)
8721 return false;
8723 m_pDS->query("SELECT movie_view.idSet,COUNT(1) AS c FROM movie_view "
8724 "JOIN sets ON sets.idSet = movie_view.idSet "
8725 "GROUP BY movie_view.idSet HAVING c>1");
8727 bool bResult = (m_pDS->num_rows() > 0);
8728 m_pDS->close();
8729 return bResult;
8731 catch (...)
8733 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8735 return false;
8738 int CVideoDatabase::GetTvShowForEpisode(int idEpisode)
8742 if (nullptr == m_pDB)
8743 return false;
8744 if (nullptr == m_pDS2)
8745 return false;
8747 // make sure we use m_pDS2, as this is called in loops using m_pDS
8748 std::string strSQL=PrepareSQL("select idShow from episode where idEpisode=%i", idEpisode);
8749 m_pDS2->query( strSQL );
8751 int result=-1;
8752 if (!m_pDS2->eof())
8753 result=m_pDS2->fv(0).get_asInt();
8754 m_pDS2->close();
8756 return result;
8758 catch (...)
8760 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idEpisode);
8762 return false;
8765 int CVideoDatabase::GetSeasonForEpisode(int idEpisode)
8767 char column[5];
8768 snprintf(column, sizeof(column), "c%0d", VIDEODB_ID_EPISODE_SEASON);
8769 std::string id = GetSingleValue("episode", column, PrepareSQL("idEpisode=%i", idEpisode));
8770 if (id.empty())
8771 return -1;
8772 return atoi(id.c_str());
8775 bool CVideoDatabase::HasContent()
8777 return (HasContent(VideoDbContentType::MOVIES) || HasContent(VideoDbContentType::TVSHOWS) ||
8778 HasContent(VideoDbContentType::MUSICVIDEOS));
8781 bool CVideoDatabase::HasContent(VideoDbContentType type)
8783 bool result = false;
8786 if (nullptr == m_pDB)
8787 return false;
8788 if (nullptr == m_pDS)
8789 return false;
8791 std::string sql;
8792 if (type == VideoDbContentType::MOVIES)
8793 sql = "select count(1) from movie";
8794 else if (type == VideoDbContentType::TVSHOWS)
8795 sql = "select count(1) from tvshow";
8796 else if (type == VideoDbContentType::MUSICVIDEOS)
8797 sql = "select count(1) from musicvideo";
8798 m_pDS->query( sql );
8800 if (!m_pDS->eof())
8801 result = (m_pDS->fv(0).get_asInt() > 0);
8803 m_pDS->close();
8805 catch (...)
8807 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8809 return result;
8812 ScraperPtr CVideoDatabase::GetScraperForPath( const std::string& strPath )
8814 SScanSettings settings;
8815 return GetScraperForPath(strPath, settings);
8818 ScraperPtr CVideoDatabase::GetScraperForPath(const std::string& strPath, SScanSettings& settings)
8820 bool dummy;
8821 return GetScraperForPath(strPath, settings, dummy);
8824 ScraperPtr CVideoDatabase::GetScraperForPath(const std::string& strPath, SScanSettings& settings, bool& foundDirectly)
8826 foundDirectly = false;
8829 if (strPath.empty() || !m_pDB || !m_pDS)
8830 return ScraperPtr();
8832 ScraperPtr scraper;
8833 std::string strPath2;
8835 if (URIUtils::IsMultiPath(strPath))
8836 strPath2 = CMultiPathDirectory::GetFirstPath(strPath);
8837 else
8838 strPath2 = strPath;
8840 std::string strPath1 = URIUtils::GetDirectory(strPath2);
8841 int idPath = GetPathId(strPath1);
8843 if (idPath > -1)
8845 std::string strSQL = PrepareSQL(
8846 "SELECT path.strContent, path.strScraper, path.scanRecursive, path.useFolderNames, "
8847 "path.strSettings, path.noUpdate, path.exclude, path.allAudio FROM path WHERE idPath=%i",
8848 idPath);
8849 m_pDS->query( strSQL );
8852 int iFound = 1;
8853 CONTENT_TYPE content = CONTENT_NONE;
8854 if (!m_pDS->eof())
8855 { // path is stored in db
8857 settings.m_allExtAudio = m_pDS->fv("path.allAudio").get_asBool();
8859 if (m_pDS->fv("path.exclude").get_asBool())
8861 settings.exclude = true;
8862 m_pDS->close();
8863 return ScraperPtr();
8865 settings.exclude = false;
8867 // try and ascertain scraper for this path
8868 std::string strcontent = m_pDS->fv("path.strContent").get_asString();
8869 StringUtils::ToLower(strcontent);
8870 content = TranslateContent(strcontent);
8872 //FIXME paths stored should not have empty strContent
8873 //assert(content != CONTENT_NONE);
8874 std::string scraperID = m_pDS->fv("path.strScraper").get_asString();
8876 AddonPtr addon;
8877 if (!scraperID.empty() &&
8878 CServiceBroker::GetAddonMgr().GetAddon(scraperID, addon, ADDON::OnlyEnabled::CHOICE_YES))
8880 scraper = std::dynamic_pointer_cast<CScraper>(addon);
8881 if (!scraper)
8882 return ScraperPtr();
8884 // store this path's content & settings
8885 scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
8886 settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
8887 settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
8888 settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
8892 if (content == CONTENT_NONE)
8893 { // this path is not saved in db
8894 // we must drill up until a scraper is configured
8895 std::string strParent;
8896 while (URIUtils::GetParentPath(strPath1, strParent))
8898 iFound++;
8900 std::string strSQL =
8901 PrepareSQL("SELECT path.strContent, path.strScraper, path.scanRecursive, "
8902 "path.useFolderNames, path.strSettings, path.noUpdate, path.exclude, "
8903 "path.allAudio FROM path WHERE strPath='%s'",
8904 strParent.c_str());
8905 m_pDS->query(strSQL);
8907 CONTENT_TYPE content = CONTENT_NONE;
8908 if (!m_pDS->eof())
8910 settings.m_allExtAudio = m_pDS->fv("path.allAudio").get_asBool();
8911 std::string strcontent = m_pDS->fv("path.strContent").get_asString();
8912 StringUtils::ToLower(strcontent);
8913 if (m_pDS->fv("path.exclude").get_asBool())
8915 settings.exclude = true;
8916 scraper.reset();
8917 m_pDS->close();
8918 break;
8921 content = TranslateContent(strcontent);
8923 AddonPtr addon;
8924 if (content != CONTENT_NONE &&
8925 CServiceBroker::GetAddonMgr().GetAddon(m_pDS->fv("path.strScraper").get_asString(),
8926 addon, ADDON::OnlyEnabled::CHOICE_YES))
8928 scraper = std::dynamic_pointer_cast<CScraper>(addon);
8929 scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
8930 settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
8931 settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
8932 settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
8933 settings.exclude = false;
8934 break;
8937 strPath1 = strParent;
8940 m_pDS->close();
8942 if (!scraper || scraper->Content() == CONTENT_NONE)
8943 return ScraperPtr();
8945 if (scraper->Content() == CONTENT_TVSHOWS)
8947 settings.recurse = 0;
8948 if(settings.parent_name) // single show
8950 settings.parent_name_root = settings.parent_name = (iFound == 1);
8952 else // show root
8954 settings.parent_name_root = settings.parent_name = (iFound == 2);
8957 else if (scraper->Content() == CONTENT_MOVIES)
8959 settings.recurse = settings.recurse - (iFound-1);
8960 settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
8962 else if (scraper->Content() == CONTENT_MUSICVIDEOS)
8964 settings.recurse = settings.recurse - (iFound-1);
8965 settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
8967 else
8969 iFound = 0;
8970 return ScraperPtr();
8972 foundDirectly = (iFound == 1);
8973 return scraper;
8975 catch (...)
8977 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8979 return ScraperPtr();
8982 bool CVideoDatabase::GetUseAllExternalAudioForVideo(const std::string& videoPath)
8984 // Find longest configured source path for given video path
8985 std::string strSQL = PrepareSQL("SELECT allAudio FROM path WHERE allAudio IS NOT NULL AND "
8986 "instr('%s', strPath) = 1 ORDER BY length(strPath) DESC LIMIT 1",
8987 videoPath.c_str());
8988 m_pDS->query(strSQL);
8990 if (!m_pDS->eof())
8991 return m_pDS->fv("allAudio").get_asBool();
8993 return false;
8996 std::string CVideoDatabase::GetContentForPath(const std::string& strPath)
8998 SScanSettings settings;
8999 bool foundDirectly = false;
9000 ScraperPtr scraper = GetScraperForPath(strPath, settings, foundDirectly);
9001 if (scraper)
9003 if (scraper->Content() == CONTENT_TVSHOWS)
9005 // check for episodes or seasons. Assumptions are:
9006 // 1. if episodes are in the path then we're in episodes.
9007 // 2. if no episodes are found, and content was set directly on this path, then we're in shows.
9008 // 3. if no episodes are found, and content was not set directly on this path, we're in seasons (assumes tvshows/seasons/episodes)
9009 std::string sql = "SELECT COUNT(*) FROM episode_view ";
9011 if (foundDirectly)
9012 sql += PrepareSQL("WHERE strPath = '%s'", strPath.c_str());
9013 else
9014 sql += PrepareSQL("WHERE strPath LIKE '%s%%'", strPath.c_str());
9016 m_pDS->query( sql );
9017 if (m_pDS->num_rows() && m_pDS->fv(0).get_asInt() > 0)
9018 return "episodes";
9019 return foundDirectly ? "tvshows" : "seasons";
9021 return TranslateContent(scraper->Content());
9023 return "";
9026 void CVideoDatabase::GetMovieGenresByName(const std::string& strSearch, CFileItemList& items)
9028 std::string strSQL;
9032 if (nullptr == m_pDB)
9033 return;
9034 if (nullptr == m_pDS)
9035 return;
9037 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9038 strSQL=PrepareSQL("SELECT genre.genre_id, genre.name, path.strPath FROM genre INNER JOIN genre_link ON genre_link.genre_id = genre.genre_id INNER JOIN movie ON (genre_link.media_type='movie' = genre_link.media_id=movie.idMovie) INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE genre.name LIKE '%%%s%%'",strSearch.c_str());
9039 else
9040 strSQL=PrepareSQL("SELECT DISTINCT genre.genre_id, genre.name FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id WHERE genre_link.media_type='movie' AND name LIKE '%%%s%%'", strSearch.c_str());
9041 m_pDS->query( strSQL );
9043 while (!m_pDS->eof())
9045 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9046 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),
9047 *CMediaSourceSettings::GetInstance().GetSources("video")))
9049 m_pDS->next();
9050 continue;
9053 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9054 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9055 pItem->SetPath("videodb://movies/genres/"+ strDir);
9056 pItem->m_bIsFolder=true;
9057 items.Add(pItem);
9058 m_pDS->next();
9060 m_pDS->close();
9062 catch (...)
9064 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9068 void CVideoDatabase::GetMovieCountriesByName(const std::string& strSearch, CFileItemList& items)
9070 std::string strSQL;
9074 if (nullptr == m_pDB)
9075 return;
9076 if (nullptr == m_pDS)
9077 return;
9079 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9080 strSQL=PrepareSQL("SELECT country.country_id, country.name, path.strPath FROM country INNER JOIN country_link ON country_link.country_id=country.country_id INNER JOIN movie ON country_link.media_id=movie.idMovie INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE country_link.media_type='movie' AND country.name LIKE '%%%s%%'", strSearch.c_str());
9081 else
9082 strSQL=PrepareSQL("SELECT DISTINCT country.country_id, country.name FROM country INNER JOIN country_link ON country_link.country_id=country.country_id WHERE country_link.media_type='movie' AND name like '%%%s%%'", strSearch.c_str());
9083 m_pDS->query( strSQL );
9085 while (!m_pDS->eof())
9087 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9088 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),
9089 *CMediaSourceSettings::GetInstance().GetSources("video")))
9091 m_pDS->next();
9092 continue;
9095 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9096 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9097 pItem->SetPath("videodb://movies/genres/"+ strDir);
9098 pItem->m_bIsFolder=true;
9099 items.Add(pItem);
9100 m_pDS->next();
9102 m_pDS->close();
9104 catch (...)
9106 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9110 void CVideoDatabase::GetTvShowGenresByName(const std::string& strSearch, CFileItemList& items)
9112 std::string strSQL;
9116 if (nullptr == m_pDB)
9117 return;
9118 if (nullptr == m_pDS)
9119 return;
9121 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9122 strSQL=PrepareSQL("SELECT genre.genre_id, genre.name, path.strPath FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id INNER JOIN tvshow ON genre_link.media_id=tvshow.idShow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idShow=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE genre_link.media_type='tvshow' AND genre.name LIKE '%%%s%%'", strSearch.c_str());
9123 else
9124 strSQL=PrepareSQL("SELECT DISTINCT genre.genre_id, genre.name FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id WHERE genre_link.media_type='tvshow' AND name LIKE '%%%s%%'", strSearch.c_str());
9125 m_pDS->query( strSQL );
9127 while (!m_pDS->eof())
9129 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9130 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9132 m_pDS->next();
9133 continue;
9136 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9137 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9138 pItem->SetPath("videodb://tvshows/genres/"+ strDir);
9139 pItem->m_bIsFolder=true;
9140 items.Add(pItem);
9141 m_pDS->next();
9143 m_pDS->close();
9145 catch (...)
9147 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9151 void CVideoDatabase::GetMovieActorsByName(const std::string& strSearch, CFileItemList& items)
9153 std::string strSQL;
9157 if (nullptr == m_pDB)
9158 return;
9159 if (nullptr == m_pDS)
9160 return;
9162 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9163 strSQL=PrepareSQL("SELECT actor.actor_id, actor.name, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN movie ON actor_link.media_id=movie.idMovie INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='movie' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9164 else
9165 strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, actor.name FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN movie ON actor_link.media_id=movie.idMovie WHERE actor_link.media_type='movie' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9166 m_pDS->query( strSQL );
9168 while (!m_pDS->eof())
9170 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9171 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9173 m_pDS->next();
9174 continue;
9177 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9178 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9179 pItem->SetPath("videodb://movies/actors/"+ strDir);
9180 pItem->m_bIsFolder=true;
9181 items.Add(pItem);
9182 m_pDS->next();
9184 m_pDS->close();
9186 catch (...)
9188 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9192 void CVideoDatabase::GetTvShowsActorsByName(const std::string& strSearch, CFileItemList& items)
9194 std::string strSQL;
9198 if (nullptr == m_pDB)
9199 return;
9200 if (nullptr == m_pDS)
9201 return;
9203 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9204 strSQL=PrepareSQL("SELECT actor.actor_id, actor.name, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN tvshow ON actor_link.media_id=tvshow.idShow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idPath=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE actor_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9205 else
9206 strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, actor.name FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN tvshow ON actor_link.media_id=tvshow.idShow WHERE actor_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'",strSearch.c_str());
9207 m_pDS->query( strSQL );
9209 while (!m_pDS->eof())
9211 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9212 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9214 m_pDS->next();
9215 continue;
9218 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9219 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9220 pItem->SetPath("videodb://tvshows/actors/"+ strDir);
9221 pItem->m_bIsFolder=true;
9222 items.Add(pItem);
9223 m_pDS->next();
9225 m_pDS->close();
9227 catch (...)
9229 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9233 void CVideoDatabase::GetMusicVideoArtistsByName(const std::string& strSearch, CFileItemList& items)
9235 std::string strSQL;
9239 if (nullptr == m_pDB)
9240 return;
9241 if (nullptr == m_pDS)
9242 return;
9244 std::string strLike;
9245 if (!strSearch.empty())
9246 strLike = "and actor.name like '%%%s%%'";
9247 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9248 strSQL=PrepareSQL("SELECT actor.actor_id, actor.name, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN musicvideo ON actor_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='musicvideo' "+strLike, strSearch.c_str());
9249 else
9250 strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, actor.name from actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id WHERE actor_link.media_type='musicvideo' "+strLike,strSearch.c_str());
9251 m_pDS->query( strSQL );
9253 while (!m_pDS->eof())
9255 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9256 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9258 m_pDS->next();
9259 continue;
9262 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9263 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9264 pItem->SetPath("videodb://musicvideos/artists/"+ strDir);
9265 pItem->m_bIsFolder=true;
9266 items.Add(pItem);
9267 m_pDS->next();
9269 m_pDS->close();
9271 catch (...)
9273 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9277 void CVideoDatabase::GetMusicVideoGenresByName(const std::string& strSearch, CFileItemList& items)
9279 std::string strSQL;
9283 if (nullptr == m_pDB)
9284 return;
9285 if (nullptr == m_pDS)
9286 return;
9288 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9289 strSQL=PrepareSQL("SELECT genre.genre_id, genre.name, path.strPath FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id INNER JOIN musicvideo ON genre_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE genre_link.media_type='musicvideo' AND genre.name LIKE '%%%s%%'", strSearch.c_str());
9290 else
9291 strSQL=PrepareSQL("SELECT DISTINCT genre.genre_id, genre.name FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id WHERE genre_link.media_type='musicvideo' AND genre.name LIKE '%%%s%%'", strSearch.c_str());
9292 m_pDS->query( strSQL );
9294 while (!m_pDS->eof())
9296 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9297 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9299 m_pDS->next();
9300 continue;
9303 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9304 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9305 pItem->SetPath("videodb://musicvideos/genres/"+ strDir);
9306 pItem->m_bIsFolder=true;
9307 items.Add(pItem);
9308 m_pDS->next();
9310 m_pDS->close();
9312 catch (...)
9314 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9318 void CVideoDatabase::GetMusicVideoAlbumsByName(const std::string& strSearch, CFileItemList& items)
9320 std::string strSQL;
9324 if (nullptr == m_pDB)
9325 return;
9326 if (nullptr == m_pDS)
9327 return;
9329 strSQL = StringUtils::Format("SELECT DISTINCT"
9330 " musicvideo.c{:02},"
9331 " musicvideo.idMVideo,"
9332 " path.strPath"
9333 " FROM"
9334 " musicvideo"
9335 " JOIN files ON"
9336 " files.idFile=musicvideo.idFile"
9337 " JOIN path ON"
9338 " path.idPath=files.idPath",
9339 VIDEODB_ID_MUSICVIDEO_ALBUM);
9340 if (!strSearch.empty())
9341 strSQL += PrepareSQL(" WHERE musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_ALBUM, strSearch.c_str());
9343 m_pDS->query( strSQL );
9345 while (!m_pDS->eof())
9347 if (m_pDS->fv(0).get_asString().empty())
9349 m_pDS->next();
9350 continue;
9353 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9354 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9356 m_pDS->next();
9357 continue;
9360 CFileItemPtr pItem(new CFileItem(m_pDS->fv(0).get_asString()));
9361 std::string strDir = std::to_string(m_pDS->fv(1).get_asInt());
9362 pItem->SetPath("videodb://musicvideos/titles/"+ strDir);
9363 pItem->m_bIsFolder=false;
9364 items.Add(pItem);
9365 m_pDS->next();
9367 m_pDS->close();
9369 catch (...)
9371 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9375 void CVideoDatabase::GetMusicVideosByAlbum(const std::string& strSearch, CFileItemList& items)
9377 std::string strSQL;
9381 if (nullptr == m_pDB)
9382 return;
9383 if (nullptr == m_pDS)
9384 return;
9386 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9387 strSQL = PrepareSQL("SELECT musicvideo.idMVideo, musicvideo.c%02d,musicvideo.c%02d, path.strPath FROM musicvideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE musicvideo.c%02d LIKE '%%%s%%'", VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_TITLE, VIDEODB_ID_MUSICVIDEO_ALBUM, strSearch.c_str());
9388 else
9389 strSQL = PrepareSQL("select musicvideo.idMVideo,musicvideo.c%02d,musicvideo.c%02d from musicvideo where musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_ALBUM,VIDEODB_ID_MUSICVIDEO_TITLE,VIDEODB_ID_MUSICVIDEO_ALBUM,strSearch.c_str());
9390 m_pDS->query( strSQL );
9392 while (!m_pDS->eof())
9394 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9395 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9397 m_pDS->next();
9398 continue;
9401 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" - "+m_pDS->fv(2).get_asString()));
9402 std::string strDir =
9403 StringUtils::Format("3/2/{}", m_pDS->fv("musicvideo.idMVideo").get_asInt());
9405 pItem->SetPath("videodb://"+ strDir);
9406 pItem->m_bIsFolder=false;
9407 items.Add(pItem);
9408 m_pDS->next();
9410 m_pDS->close();
9412 catch (...)
9414 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9418 bool CVideoDatabase::GetMusicVideosByWhere(const std::string &baseDir, const Filter &filter, CFileItemList &items, bool checkLocks /*= true*/, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
9423 if (nullptr == m_pDB)
9424 return false;
9425 if (nullptr == m_pDS)
9426 return false;
9428 int total = -1;
9430 std::string strSQL = "select %s from musicvideo_view ";
9431 CVideoDbUrl videoUrl;
9432 if (!videoUrl.FromString(baseDir))
9433 return false;
9435 std::string strSQLExtra;
9436 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
9437 std::string strArtist;
9438 int idArtist = -1;
9439 // If we have an artistid then get the artist name and use that to fix up the path displayed in
9440 // the GUI (musicvideos/artist-name instead of musicvideos/artistid)
9441 auto option = options.find("artistid");
9442 if (option != options.end())
9444 idArtist = option->second.asInteger();
9445 strArtist = GetSingleValue(
9446 PrepareSQL("SELECT name FROM actor where actor_id = '%i'", idArtist), m_pDS)
9447 .c_str();
9449 Filter extFilter = filter;
9450 SortDescription sorting = sortDescription;
9451 if (!BuildSQL(baseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
9452 return false;
9454 // Apply the limiting directly here if there's no special sorting but limiting
9455 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
9456 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
9457 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
9459 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
9460 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
9463 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
9465 int iRowsFound = RunQuery(strSQL);
9467 // store the total value of items as a property
9468 if (total < iRowsFound)
9469 total = iRowsFound;
9470 items.SetProperty("total", total);
9472 if (iRowsFound <= 0)
9473 return iRowsFound == 0;
9475 DatabaseResults results;
9476 results.reserve(iRowsFound);
9477 if (!SortUtils::SortFromDataset(sorting, MediaTypeMusicVideo, m_pDS, results))
9478 return false;
9480 // get data from returned rows
9481 items.Reserve(results.size());
9482 // get songs from returned subtable
9483 const query_data &data = m_pDS->get_result_set().records;
9484 for (const auto &i : results)
9486 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
9487 const dbiplus::sql_record* const record = data.at(targetRow);
9489 CVideoInfoTag musicvideo = GetDetailsForMusicVideo(record, getDetails);
9490 if (!checkLocks || m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || g_passwordManager.bMasterUser ||
9491 g_passwordManager.IsDatabasePathUnlocked(musicvideo.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
9493 CFileItemPtr item(new CFileItem(musicvideo));
9495 CVideoDbUrl itemUrl = videoUrl;
9496 std::string path = std::to_string(record->at(0).get_asInt());
9497 itemUrl.AppendPath(path);
9498 item->SetPath(itemUrl.ToString());
9499 item->SetDynPath(musicvideo.m_strFileNameAndPath);
9501 item->SetOverlayImage(musicvideo.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
9502 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
9503 items.Add(item);
9507 // cleanup
9508 m_pDS->close();
9509 if (!strArtist.empty())
9510 items.SetProperty("customtitle", strArtist);
9511 return true;
9513 catch (...)
9515 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
9517 return false;
9520 unsigned int CVideoDatabase::GetRandomMusicVideoIDs(const std::string& strWhere, std::vector<std::pair<int,int> > &songIDs)
9524 if (nullptr == m_pDB)
9525 return 0;
9526 if (nullptr == m_pDS)
9527 return 0;
9529 std::string strSQL = "select distinct idMVideo from musicvideo_view";
9530 if (!strWhere.empty())
9531 strSQL += " where " + strWhere;
9532 strSQL += PrepareSQL(" ORDER BY RANDOM()");
9534 if (!m_pDS->query(strSQL)) return 0;
9535 songIDs.clear();
9536 if (m_pDS->num_rows() == 0)
9538 m_pDS->close();
9539 return 0;
9541 songIDs.reserve(m_pDS->num_rows());
9542 while (!m_pDS->eof())
9544 songIDs.push_back(std::make_pair<int,int>(2,m_pDS->fv(0).get_asInt()));
9545 m_pDS->next();
9546 } // cleanup
9547 m_pDS->close();
9548 return songIDs.size();
9550 catch (...)
9552 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strWhere);
9554 return 0;
9557 int CVideoDatabase::GetMatchingMusicVideo(const std::string& strArtist, const std::string& strAlbum, const std::string& strTitle)
9561 if (nullptr == m_pDB)
9562 return -1;
9563 if (nullptr == m_pDS)
9564 return -1;
9566 std::string strSQL;
9567 if (strAlbum.empty() && strTitle.empty())
9568 { // we want to return matching artists only
9569 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9570 strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN musicvideo ON actor_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='musicvideo' AND actor.name like '%s'", strArtist.c_str());
9571 else
9572 strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id WHERE actor_link.media_type='musicvideo' AND actor.name LIKE '%s'", strArtist.c_str());
9574 else
9575 { // we want to return the matching musicvideo
9576 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9577 strSQL = PrepareSQL("SELECT musicvideo.idMVideo FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN musicvideo ON actor_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='musicvideo' AND musicvideo.c%02d LIKE '%s' AND musicvideo.c%02d LIKE '%s' AND actor.name LIKE '%s'", VIDEODB_ID_MUSICVIDEO_ALBUM, strAlbum.c_str(), VIDEODB_ID_MUSICVIDEO_TITLE, strTitle.c_str(), strArtist.c_str());
9578 else
9579 strSQL = PrepareSQL("select musicvideo.idMVideo from musicvideo join actor_link on actor_link.media_id=musicvideo.idMVideo AND actor_link.media_type='musicvideo' join actor on actor.actor_id=actor_link.actor_id where musicvideo.c%02d like '%s' and musicvideo.c%02d like '%s' and actor.name like '%s'",VIDEODB_ID_MUSICVIDEO_ALBUM,strAlbum.c_str(),VIDEODB_ID_MUSICVIDEO_TITLE,strTitle.c_str(),strArtist.c_str());
9581 m_pDS->query( strSQL );
9583 if (m_pDS->eof())
9584 return -1;
9586 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9587 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9589 m_pDS->close();
9590 return -1;
9593 int lResult = m_pDS->fv(0).get_asInt();
9594 m_pDS->close();
9595 return lResult;
9597 catch (...)
9599 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
9601 return -1;
9604 void CVideoDatabase::GetMoviesByName(const std::string& strSearch, CFileItemList& items)
9606 std::string strSQL;
9610 if (nullptr == m_pDB)
9611 return;
9612 if (nullptr == m_pDS)
9613 return;
9615 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9616 strSQL = PrepareSQL("SELECT movie.idMovie, movie.c%02d, path.strPath, movie.idSet FROM movie "
9617 "INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON "
9618 "path.idPath=files.idPath "
9619 "WHERE movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
9620 VIDEODB_ID_TITLE, VIDEODB_ID_TITLE, strSearch.c_str(),
9621 VIDEODB_ID_ORIGINALTITLE, strSearch.c_str());
9622 else
9623 strSQL = PrepareSQL("SELECT movie.idMovie,movie.c%02d, movie.idSet FROM movie WHERE "
9624 "movie.c%02d like '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
9625 VIDEODB_ID_TITLE, VIDEODB_ID_TITLE, strSearch.c_str(),
9626 VIDEODB_ID_ORIGINALTITLE, strSearch.c_str());
9627 m_pDS->query( strSQL );
9629 while (!m_pDS->eof())
9631 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9632 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9634 m_pDS->next();
9635 continue;
9638 int movieId = m_pDS->fv("movie.idMovie").get_asInt();
9639 int setId = m_pDS->fv("movie.idSet").get_asInt();
9640 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9641 std::string path;
9642 if (setId <= 0 || !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS))
9643 path = StringUtils::Format("videodb://movies/titles/{}", movieId);
9644 else
9645 path = StringUtils::Format("videodb://movies/sets/{}/{}", setId, movieId);
9646 pItem->SetPath(path);
9647 pItem->m_bIsFolder=false;
9648 items.Add(pItem);
9649 m_pDS->next();
9651 m_pDS->close();
9653 catch (...)
9655 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9659 void CVideoDatabase::GetTvShowsByName(const std::string& strSearch, CFileItemList& items)
9661 std::string strSQL;
9665 if (nullptr == m_pDB)
9666 return;
9667 if (nullptr == m_pDS)
9668 return;
9670 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9671 strSQL = PrepareSQL("SELECT tvshow.idShow, tvshow.c%02d, path.strPath FROM tvshow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idShow=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE tvshow.c%02d LIKE '%%%s%%'", VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_TITLE, strSearch.c_str());
9672 else
9673 strSQL = PrepareSQL("select tvshow.idShow,tvshow.c%02d from tvshow where tvshow.c%02d like '%%%s%%'",VIDEODB_ID_TV_TITLE,VIDEODB_ID_TV_TITLE,strSearch.c_str());
9674 m_pDS->query( strSQL );
9676 while (!m_pDS->eof())
9678 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9679 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9681 m_pDS->next();
9682 continue;
9685 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9686 std::string strDir =
9687 StringUtils::Format("tvshows/titles/{}/", m_pDS->fv("tvshow.idShow").get_asInt());
9689 pItem->SetPath("videodb://"+ strDir);
9690 pItem->m_bIsFolder=true;
9691 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv("tvshow.idShow").get_asInt();
9692 items.Add(pItem);
9693 m_pDS->next();
9695 m_pDS->close();
9697 catch (...)
9699 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9703 void CVideoDatabase::GetEpisodesByName(const std::string& strSearch, CFileItemList& items)
9705 std::string strSQL;
9709 if (nullptr == m_pDB)
9710 return;
9711 if (nullptr == m_pDS)
9712 return;
9714 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9715 strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d, path.strPath FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow INNER JOIN files ON files.idFile=episode.idFile INNER JOIN path ON path.idPath=files.idPath WHERE episode.c%02d LIKE '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_TITLE, strSearch.c_str());
9716 else
9717 strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow WHERE episode.c%02d like '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_TITLE, strSearch.c_str());
9718 m_pDS->query( strSQL );
9720 while (!m_pDS->eof())
9722 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9723 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9725 m_pDS->next();
9726 continue;
9729 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
9730 std::string path = StringUtils::Format("videodb://tvshows/titles/{}/{}/{}",
9731 m_pDS->fv("episode.idShow").get_asInt(),
9732 m_pDS->fv(2).get_asInt(), m_pDS->fv(0).get_asInt());
9733 pItem->SetPath(path);
9734 pItem->m_bIsFolder=false;
9735 items.Add(pItem);
9736 m_pDS->next();
9738 m_pDS->close();
9740 catch (...)
9742 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9746 void CVideoDatabase::GetMusicVideosByName(const std::string& strSearch, CFileItemList& items)
9748 // Alternative searching - not quite as fast though due to
9749 // retrieving all information
9750 // Filter filter(PrepareSQL("c%02d like '%s%%' or c%02d like '%% %s%%'", VIDEODB_ID_MUSICVIDEO_TITLE, strSearch.c_str(), VIDEODB_ID_MUSICVIDEO_TITLE, strSearch.c_str()));
9751 // GetMusicVideosByWhere("videodb://musicvideos/titles/", filter, items);
9752 std::string strSQL;
9756 if (nullptr == m_pDB)
9757 return;
9758 if (nullptr == m_pDS)
9759 return;
9761 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9762 strSQL = PrepareSQL("SELECT musicvideo.idMVideo, musicvideo.c%02d, path.strPath FROM musicvideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE musicvideo.c%02d LIKE '%%%s%%'", VIDEODB_ID_MUSICVIDEO_TITLE, VIDEODB_ID_MUSICVIDEO_TITLE, strSearch.c_str());
9763 else
9764 strSQL = PrepareSQL("select musicvideo.idMVideo,musicvideo.c%02d from musicvideo where musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_TITLE,VIDEODB_ID_MUSICVIDEO_TITLE,strSearch.c_str());
9765 m_pDS->query( strSQL );
9767 while (!m_pDS->eof())
9769 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9770 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9772 m_pDS->next();
9773 continue;
9776 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9777 std::string strDir =
9778 StringUtils::Format("3/2/{}", m_pDS->fv("musicvideo.idMVideo").get_asInt());
9780 pItem->SetPath("videodb://"+ strDir);
9781 pItem->m_bIsFolder=false;
9782 items.Add(pItem);
9783 m_pDS->next();
9785 m_pDS->close();
9787 catch (...)
9789 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9793 void CVideoDatabase::GetEpisodesByPlot(const std::string& strSearch, CFileItemList& items)
9795 // Alternative searching - not quite as fast though due to
9796 // retrieving all information
9797 // Filter filter;
9798 // filter.where = PrepareSQL("c%02d like '%s%%' or c%02d like '%% %s%%'", VIDEODB_ID_EPISODE_PLOT, strSearch.c_str(), VIDEODB_ID_EPISODE_PLOT, strSearch.c_str());
9799 // filter.where += PrepareSQL("or c%02d like '%s%%' or c%02d like '%% %s%%'", VIDEODB_ID_EPISODE_TITLE, strSearch.c_str(), VIDEODB_ID_EPISODE_TITLE, strSearch.c_str());
9800 // GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
9801 // return;
9802 std::string strSQL;
9806 if (nullptr == m_pDB)
9807 return;
9808 if (nullptr == m_pDS)
9809 return;
9811 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9812 strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d, path.strPath FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow INNER JOIN files ON files.idFile=episode.idFile INNER JOIN path ON path.idPath=files.idPath WHERE episode.c%02d LIKE '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_PLOT, strSearch.c_str());
9813 else
9814 strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow WHERE episode.c%02d LIKE '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_PLOT, strSearch.c_str());
9815 m_pDS->query( strSQL );
9817 while (!m_pDS->eof())
9819 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9820 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9822 m_pDS->next();
9823 continue;
9826 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
9827 std::string path = StringUtils::Format("videodb://tvshows/titles/{}/{}/{}",
9828 m_pDS->fv("episode.idShow").get_asInt(),
9829 m_pDS->fv(2).get_asInt(), m_pDS->fv(0).get_asInt());
9830 pItem->SetPath(path);
9831 pItem->m_bIsFolder=false;
9832 items.Add(pItem);
9833 m_pDS->next();
9835 m_pDS->close();
9837 catch (...)
9839 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9843 void CVideoDatabase::GetMoviesByPlot(const std::string& strSearch, CFileItemList& items)
9845 std::string strSQL;
9849 if (nullptr == m_pDB)
9850 return;
9851 if (nullptr == m_pDS)
9852 return;
9854 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9855 strSQL = PrepareSQL("select movie.idMovie, movie.c%02d, path.strPath FROM movie INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%'", VIDEODB_ID_TITLE,VIDEODB_ID_PLOT, strSearch.c_str(), VIDEODB_ID_PLOTOUTLINE, strSearch.c_str(), VIDEODB_ID_TAGLINE,strSearch.c_str());
9856 else
9857 strSQL = PrepareSQL("SELECT movie.idMovie, movie.c%02d FROM movie WHERE (movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%')", VIDEODB_ID_TITLE, VIDEODB_ID_PLOT, strSearch.c_str(), VIDEODB_ID_PLOTOUTLINE, strSearch.c_str(), VIDEODB_ID_TAGLINE, strSearch.c_str());
9859 m_pDS->query( strSQL );
9861 while (!m_pDS->eof())
9863 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9864 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9866 m_pDS->next();
9867 continue;
9870 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9871 std::string path =
9872 StringUtils::Format("videodb://movies/titles/{}", m_pDS->fv(0).get_asInt());
9873 pItem->SetPath(path);
9874 pItem->m_bIsFolder=false;
9876 items.Add(pItem);
9877 m_pDS->next();
9879 m_pDS->close();
9882 catch (...)
9884 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9888 void CVideoDatabase::GetMovieDirectorsByName(const std::string& strSearch, CFileItemList& items)
9890 std::string strSQL;
9894 if (nullptr == m_pDB)
9895 return;
9896 if (nullptr == m_pDS)
9897 return;
9899 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9900 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name, path.strPath FROM movie INNER JOIN director_link ON (director_link.media_id=movie.idMovie AND director_link.media_type='movie') INNER JOIN actor ON actor.actor_id=director_link.actor_id INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor.name LIKE '%%%s%%'", strSearch.c_str());
9901 else
9902 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN movie ON director_link.media_id=movie.idMovie WHERE director_link.media_type='movie' AND actor.name like '%%%s%%'", strSearch.c_str());
9904 m_pDS->query( strSQL );
9906 while (!m_pDS->eof())
9908 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9909 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9911 m_pDS->next();
9912 continue;
9915 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9916 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9918 pItem->SetPath("videodb://movies/directors/"+ strDir);
9919 pItem->m_bIsFolder=true;
9920 items.Add(pItem);
9921 m_pDS->next();
9923 m_pDS->close();
9925 catch (...)
9927 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9931 void CVideoDatabase::GetTvShowsDirectorsByName(const std::string& strSearch, CFileItemList& items)
9933 std::string strSQL;
9937 if (nullptr == m_pDB)
9938 return;
9939 if (nullptr == m_pDS)
9940 return;
9942 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9943 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name, path.strPath FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN tvshow ON director_link.media_id=tvshow.idShow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idShow=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE director_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9944 else
9945 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN tvshow ON director_link.media_id=tvshow.idShow WHERE director_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9947 m_pDS->query( strSQL );
9949 while (!m_pDS->eof())
9951 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9952 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9954 m_pDS->next();
9955 continue;
9958 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9959 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9961 pItem->SetPath("videodb://tvshows/directors/"+ strDir);
9962 pItem->m_bIsFolder=true;
9963 items.Add(pItem);
9964 m_pDS->next();
9966 m_pDS->close();
9968 catch (...)
9970 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9974 void CVideoDatabase::GetMusicVideoDirectorsByName(const std::string& strSearch, CFileItemList& items)
9976 std::string strSQL;
9980 if (nullptr == m_pDB)
9981 return;
9982 if (nullptr == m_pDS)
9983 return;
9985 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9986 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name, path.strPath FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN musicvideo ON director_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE director_link.media_type='musicvideo' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9987 else
9988 strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN musicvideo ON director_link.media_id=musicvideo.idMVideo WHERE director_link.media_type='musicvideo' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9990 m_pDS->query( strSQL );
9992 while (!m_pDS->eof())
9994 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9995 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9997 m_pDS->next();
9998 continue;
10001 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
10002 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
10004 pItem->SetPath("videodb://musicvideos/albums/"+ strDir);
10005 pItem->m_bIsFolder=true;
10006 items.Add(pItem);
10007 m_pDS->next();
10009 m_pDS->close();
10011 catch (...)
10013 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
10017 void CVideoDatabase::CleanDatabase(CGUIDialogProgressBarHandle* handle,
10018 const std::set<int>& paths,
10019 bool showProgress)
10021 CGUIDialogProgress* progress = NULL;
10024 if (nullptr == m_pDB)
10025 return;
10026 if (nullptr == m_pDS)
10027 return;
10028 if (nullptr == m_pDS2)
10029 return;
10031 auto start = std::chrono::steady_clock::now();
10032 CLog::Log(LOGINFO, "{}: Starting videodatabase cleanup ..", __FUNCTION__);
10033 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary,
10034 "OnCleanStarted");
10036 if (handle)
10038 handle->SetTitle(g_localizeStrings.Get(700));
10039 handle->SetText("");
10041 else if (showProgress)
10043 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(
10044 WINDOW_DIALOG_PROGRESS);
10045 if (progress)
10047 progress->SetHeading(CVariant{700});
10048 progress->SetLine(0, CVariant{""});
10049 progress->SetLine(1, CVariant{313});
10050 progress->SetLine(2, CVariant{330});
10051 progress->SetPercentage(0);
10052 progress->Open();
10053 progress->ShowProgressBar(true);
10057 BeginTransaction();
10059 // find all the files
10060 std::string sql = "SELECT files.idFile, files.strFileName, path.strPath FROM files "
10061 "INNER JOIN path ON path.idPath=files.idPath";
10062 if (!paths.empty())
10064 std::string strPaths;
10065 for (const auto& i : paths)
10066 strPaths += StringUtils::Format(",{}", i);
10067 sql += PrepareSQL(" AND path.idPath IN (%s)", strPaths.substr(1).c_str());
10070 // For directory caching to work properly, we need to sort the files by path
10071 sql += " ORDER BY path.strPath";
10073 m_pDS2->query(sql);
10074 if (m_pDS2->num_rows() > 0)
10076 std::string filesToTestForDelete;
10077 std::vector<CMediaSource> videoSources(
10078 *CMediaSourceSettings::GetInstance().GetSources("video"));
10079 CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources);
10081 int total = m_pDS2->num_rows();
10082 int current = 0;
10083 std::string lastDir;
10084 bool gotDir = true;
10086 while (!m_pDS2->eof())
10088 std::string path = m_pDS2->fv("path.strPath").get_asString();
10089 std::string fileName = m_pDS2->fv("files.strFileName").get_asString();
10090 std::string fullPath;
10091 ConstructPath(fullPath, path, fileName);
10093 // get the first stacked file
10094 if (URIUtils::IsStack(fullPath))
10095 fullPath = CStackDirectory::GetFirstStackedFile(fullPath);
10097 // get the actual archive path
10098 if (URIUtils::IsInArchive(fullPath))
10099 fullPath = CURL(fullPath).GetHostName();
10101 bool del = true;
10102 if (URIUtils::IsPlugin(fullPath))
10104 SScanSettings settings;
10105 bool foundDirectly = false;
10106 ScraperPtr scraper = GetScraperForPath(fullPath, settings, foundDirectly);
10107 if (scraper &&
10108 CPluginDirectory::CheckExists(TranslateContent(scraper->Content()), fullPath))
10109 del = false;
10111 else
10113 // Only consider keeping this file if not optical and belonging to a (matching) source
10114 bool bIsSource;
10115 if (!URIUtils::IsOnDVD(fullPath) &&
10116 CUtil::GetMatchingSource(fullPath, videoSources, bIsSource) >= 0)
10118 const std::string pathDir = URIUtils::GetDirectory(fullPath);
10120 // Cache file's directory in case it's different from the previous file
10121 if (lastDir != pathDir)
10123 lastDir = pathDir;
10124 CFileItemList items; // Dummy list
10125 gotDir = CDirectory::GetDirectory(pathDir, items, "",
10126 DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO);
10129 // Keep existing files
10130 if (gotDir && CFile::Exists(fullPath, true))
10131 del = false;
10134 if (del)
10135 filesToTestForDelete += m_pDS2->fv("files.idFile").get_asString() + ",";
10137 if (handle == NULL && progress != NULL)
10139 int percentage = current * 100 / total;
10140 if (percentage > progress->GetPercentage())
10142 progress->SetPercentage(percentage);
10143 progress->Progress();
10145 if (progress->IsCanceled())
10147 progress->Close();
10148 m_pDS2->close();
10149 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary,
10150 "OnCleanFinished");
10151 return;
10154 else if (handle != NULL)
10155 handle->SetPercentage(current * 100 / (float)total);
10157 m_pDS2->next();
10158 current++;
10160 m_pDS2->close();
10162 std::string filesToDelete;
10164 // Add any files that don't have a valid idPath entry to the filesToDelete list.
10165 m_pDS->query("SELECT files.idFile FROM files WHERE NOT EXISTS (SELECT 1 FROM path "
10166 "WHERE path.idPath = files.idPath)");
10167 while (!m_pDS->eof())
10169 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
10170 filesToTestForDelete += file;
10171 filesToDelete += file;
10173 m_pDS->next();
10175 m_pDS->close();
10177 std::map<int, bool> pathsDeleteDecisions;
10178 std::vector<int> movieIDs;
10179 std::vector<int> tvshowIDs;
10180 std::vector<int> episodeIDs;
10181 std::vector<int> musicVideoIDs;
10182 std::vector<int> videoVersionIDs;
10184 if (!filesToTestForDelete.empty())
10186 StringUtils::TrimRight(filesToTestForDelete, ",");
10188 movieIDs = CleanMediaType(MediaTypeMovie, filesToTestForDelete, pathsDeleteDecisions,
10189 filesToDelete, !showProgress);
10190 episodeIDs = CleanMediaType(MediaTypeEpisode, filesToTestForDelete, pathsDeleteDecisions,
10191 filesToDelete, !showProgress);
10192 musicVideoIDs = CleanMediaType(MediaTypeMusicVideo, filesToTestForDelete,
10193 pathsDeleteDecisions, filesToDelete, !showProgress);
10194 videoVersionIDs = CleanMediaType(MediaTypeVideoVersion, filesToTestForDelete,
10195 pathsDeleteDecisions, filesToDelete, !showProgress);
10198 if (progress != NULL)
10200 progress->SetPercentage(100);
10201 progress->Progress();
10204 if (!filesToDelete.empty())
10206 filesToDelete = "(" + StringUtils::TrimRight(filesToDelete, ",") + ")";
10208 // Clean hashes of all paths that files are deleted from
10209 // Otherwise there is a mismatch between the path contents and the hash in the
10210 // database, leading to potentially missed items on re-scan (if deleted files are
10211 // later re-added to a source)
10212 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning path hashes");
10213 m_pDS->query("SELECT DISTINCT strPath FROM path JOIN files ON files.idPath=path.idPath "
10214 "WHERE files.idFile IN " +
10215 filesToDelete);
10216 int pathHashCount = m_pDS->num_rows();
10217 while (!m_pDS->eof())
10219 InvalidatePathHash(m_pDS->fv("strPath").get_asString());
10220 m_pDS->next();
10222 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaned {} path hashes", pathHashCount);
10224 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning files table", __FUNCTION__);
10225 sql = "DELETE FROM files WHERE idFile IN " + filesToDelete;
10226 m_pDS->exec(sql);
10229 if (!movieIDs.empty())
10231 std::string moviesToDelete;
10232 for (const auto& i : movieIDs)
10233 moviesToDelete += StringUtils::Format("{},", i);
10234 moviesToDelete = "(" + StringUtils::TrimRight(moviesToDelete, ",") + ")";
10236 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning movie table", __FUNCTION__);
10237 sql = "DELETE FROM movie WHERE idMovie IN " + moviesToDelete;
10238 m_pDS->exec(sql);
10241 if (!episodeIDs.empty())
10243 std::string episodesToDelete;
10244 for (const auto& i : episodeIDs)
10245 episodesToDelete += StringUtils::Format("{},", i);
10246 episodesToDelete = "(" + StringUtils::TrimRight(episodesToDelete, ",") + ")";
10248 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning episode table", __FUNCTION__);
10249 sql = "DELETE FROM episode WHERE idEpisode IN " + episodesToDelete;
10250 m_pDS->exec(sql);
10253 CLog::Log(LOGDEBUG, LOGDATABASE,
10254 "{}: Cleaning paths that don't exist and have content set...", __FUNCTION__);
10255 sql = "SELECT path.idPath, path.strPath, path.idParentPath FROM path "
10256 "WHERE NOT ((strContent IS NULL OR strContent = '') "
10257 "AND (strSettings IS NULL OR strSettings = '') "
10258 "AND (strHash IS NULL OR strHash = '') "
10259 "AND (exclude IS NULL OR exclude != 1))";
10260 m_pDS2->query(sql);
10261 std::string strIds;
10262 while (!m_pDS2->eof())
10264 auto pathsDeleteDecision = pathsDeleteDecisions.find(m_pDS2->fv(0).get_asInt());
10265 // Check if we have a decision for the parent path
10266 auto pathsDeleteDecisionByParent = pathsDeleteDecisions.find(m_pDS2->fv(2).get_asInt());
10267 std::string path = m_pDS2->fv(1).get_asString();
10269 bool exists = false;
10270 if (URIUtils::IsPlugin(path))
10272 SScanSettings settings;
10273 bool foundDirectly = false;
10274 ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
10275 if (scraper && CPluginDirectory::CheckExists(TranslateContent(scraper->Content()), path))
10276 exists = true;
10278 else
10279 exists = CDirectory::Exists(path, false);
10281 if (((pathsDeleteDecision != pathsDeleteDecisions.end() && pathsDeleteDecision->second) ||
10282 (pathsDeleteDecision == pathsDeleteDecisions.end() && !exists)) &&
10283 ((pathsDeleteDecisionByParent != pathsDeleteDecisions.end() &&
10284 pathsDeleteDecisionByParent->second) ||
10285 (pathsDeleteDecisionByParent == pathsDeleteDecisions.end())))
10286 strIds += m_pDS2->fv(0).get_asString() + ",";
10288 m_pDS2->next();
10290 m_pDS2->close();
10292 if (!strIds.empty())
10294 sql = PrepareSQL("DELETE FROM path WHERE idPath IN (%s)",
10295 StringUtils::TrimRight(strIds, ",").c_str());
10296 m_pDS->exec(sql);
10297 sql = "DELETE FROM tvshowlinkpath "
10298 "WHERE NOT EXISTS (SELECT 1 FROM path WHERE path.idPath = tvshowlinkpath.idPath)";
10299 m_pDS->exec(sql);
10302 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning tvshow table", __FUNCTION__);
10304 std::string tvshowsToDelete;
10305 sql = "SELECT idShow FROM tvshow "
10306 "WHERE NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idShow = "
10307 "tvshow.idShow)";
10308 m_pDS->query(sql);
10309 while (!m_pDS->eof())
10311 tvshowIDs.push_back(m_pDS->fv(0).get_asInt());
10312 tvshowsToDelete += m_pDS->fv(0).get_asString() + ",";
10313 m_pDS->next();
10315 m_pDS->close();
10316 if (!tvshowsToDelete.empty())
10318 sql = "DELETE FROM tvshow WHERE idShow IN (" +
10319 StringUtils::TrimRight(tvshowsToDelete, ",") + ")";
10320 m_pDS->exec(sql);
10323 if (!musicVideoIDs.empty())
10325 std::string musicVideosToDelete;
10326 for (const auto& i : musicVideoIDs)
10327 musicVideosToDelete += StringUtils::Format("{},", i);
10328 musicVideosToDelete = "(" + StringUtils::TrimRight(musicVideosToDelete, ",") + ")";
10330 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning musicvideo table", __FUNCTION__);
10331 sql = "DELETE FROM musicvideo WHERE idMVideo IN " + musicVideosToDelete;
10332 m_pDS->exec(sql);
10335 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning path table", __FUNCTION__);
10336 sql = StringUtils::Format(
10337 "DELETE FROM path "
10338 "WHERE (strContent IS NULL OR strContent = '') "
10339 "AND (strSettings IS NULL OR strSettings = '') "
10340 "AND (strHash IS NULL OR strHash = '') "
10341 "AND (exclude IS NULL OR exclude != 1) "
10342 "AND (idParentPath IS NULL OR NOT EXISTS (SELECT 1 FROM (SELECT idPath FROM path) as "
10343 "parentPath WHERE parentPath.idPath = path.idParentPath)) " // MySQL only fix (#5007)
10344 "AND NOT EXISTS (SELECT 1 FROM files WHERE files.idPath = path.idPath) "
10345 "AND NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idPath = path.idPath) "
10346 "AND NOT EXISTS (SELECT 1 FROM movie WHERE movie.c{:02} = path.idPath) "
10347 "AND NOT EXISTS (SELECT 1 FROM episode WHERE episode.c{:02} = path.idPath) "
10348 "AND NOT EXISTS (SELECT 1 FROM musicvideo WHERE musicvideo.c{:02} = path.idPath)",
10349 VIDEODB_ID_PARENTPATHID, VIDEODB_ID_EPISODE_PARENTPATHID,
10350 VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
10351 m_pDS->exec(sql);
10353 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning genre table", __FUNCTION__);
10354 sql =
10355 "DELETE FROM genre "
10356 "WHERE NOT EXISTS (SELECT 1 FROM genre_link WHERE genre_link.genre_id = genre.genre_id)";
10357 m_pDS->exec(sql);
10359 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning country table", __FUNCTION__);
10360 sql = "DELETE FROM country WHERE NOT EXISTS (SELECT 1 FROM country_link WHERE "
10361 "country_link.country_id = country.country_id)";
10362 m_pDS->exec(sql);
10364 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning actor table of actors, directors and writers",
10365 __FUNCTION__);
10366 sql =
10367 "DELETE FROM actor "
10368 "WHERE NOT EXISTS (SELECT 1 FROM actor_link WHERE actor_link.actor_id = actor.actor_id) "
10369 "AND NOT EXISTS (SELECT 1 FROM director_link WHERE director_link.actor_id = "
10370 "actor.actor_id) "
10371 "AND NOT EXISTS (SELECT 1 FROM writer_link WHERE writer_link.actor_id = actor.actor_id)";
10372 m_pDS->exec(sql);
10374 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning studio table", __FUNCTION__);
10375 sql = "DELETE FROM studio "
10376 "WHERE NOT EXISTS (SELECT 1 FROM studio_link WHERE studio_link.studio_id = "
10377 "studio.studio_id)";
10378 m_pDS->exec(sql);
10380 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning set table", __FUNCTION__);
10381 sql = "DELETE FROM sets "
10382 "WHERE NOT EXISTS (SELECT 1 FROM movie WHERE movie.idSet = sets.idSet)";
10383 m_pDS->exec(sql);
10385 CommitTransaction();
10387 if (handle)
10388 handle->SetTitle(g_localizeStrings.Get(331));
10390 Compress(false);
10392 CUtil::DeleteVideoDatabaseDirectoryCache();
10394 auto end = std::chrono::steady_clock::now();
10395 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
10397 CLog::Log(LOGINFO, "{}: Cleaning videodatabase done. Operation took {} ms", __FUNCTION__,
10398 duration.count());
10400 for (const auto& i : movieIDs)
10401 AnnounceRemove(MediaTypeMovie, i, true);
10403 for (const auto& i : episodeIDs)
10404 AnnounceRemove(MediaTypeEpisode, i, true);
10406 for (const auto& i : tvshowIDs)
10407 AnnounceRemove(MediaTypeTvShow, i, true);
10409 for (const auto& i : musicVideoIDs)
10410 AnnounceRemove(MediaTypeMusicVideo, i, true);
10412 for (const auto& i : videoVersionIDs)
10413 AnnounceRemove(MediaTypeVideoVersion, i, true);
10416 catch (...)
10418 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
10419 RollbackTransaction();
10421 if (progress)
10422 progress->Close();
10424 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnCleanFinished");
10427 std::vector<int> CVideoDatabase::CleanMediaType(const std::string &mediaType, const std::string &cleanableFileIDs,
10428 std::map<int, bool> &pathsDeleteDecisions, std::string &deletedFileIDs, bool silent)
10430 std::vector<int> cleanedIDs;
10431 if (mediaType.empty() || cleanableFileIDs.empty())
10432 return cleanedIDs;
10434 const std::string& table = mediaType;
10435 std::string idField;
10436 std::string parentPathIdField;
10437 bool isEpisode = false;
10438 if (mediaType == MediaTypeMovie)
10440 idField = "idMovie";
10441 parentPathIdField = StringUtils::Format("{}.c{:02}", table, VIDEODB_ID_PARENTPATHID);
10443 else if (mediaType == MediaTypeEpisode)
10445 idField = "idEpisode";
10446 parentPathIdField = "showPath.idParentPath";
10447 isEpisode = true;
10449 else if (mediaType == MediaTypeMusicVideo)
10451 idField = "idMVideo";
10452 parentPathIdField = StringUtils::Format("{}.c{:02}", table, VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
10454 else if (mediaType == MediaTypeVideoVersion)
10456 idField = "idMedia";
10457 parentPathIdField = "path.idPath";
10459 else
10460 return cleanedIDs;
10462 // now grab them media items
10463 std::string sql = PrepareSQL("SELECT %s.%s, %s.idFile, path.idPath, parentPath.strPath FROM %s "
10464 "JOIN files ON files.idFile = %s.idFile "
10465 "JOIN path ON path.idPath = files.idPath ",
10466 table.c_str(), idField.c_str(), table.c_str(), table.c_str(),
10467 table.c_str());
10469 if (isEpisode)
10470 sql += "JOIN tvshowlinkpath ON tvshowlinkpath.idShow = episode.idShow JOIN path AS showPath ON showPath.idPath=tvshowlinkpath.idPath ";
10472 sql += PrepareSQL("LEFT JOIN path as parentPath ON parentPath.idPath = %s "
10473 "WHERE %s.idFile IN (%s)",
10474 parentPathIdField.c_str(),
10475 table.c_str(), cleanableFileIDs.c_str());
10477 std::vector<CMediaSource> videoSources(*CMediaSourceSettings::GetInstance().GetSources("video"));
10478 CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources);
10480 // map of parent path ID to boolean pair (if not exists and user choice)
10481 std::map<int, std::pair<bool, bool> > sourcePathsDeleteDecisions;
10482 m_pDS2->query(sql);
10483 while (!m_pDS2->eof())
10485 bool del = true;
10486 if (m_pDS2->fv(3).get_isNull() == false)
10488 std::string parentPath = m_pDS2->fv(3).get_asString();
10490 // try to find the source path the parent path belongs to
10491 SScanSettings scanSettings;
10492 std::string sourcePath;
10493 GetSourcePath(parentPath, sourcePath, scanSettings);
10495 bool bIsSourceName;
10496 bool sourceNotFound = (CUtil::GetMatchingSource(parentPath, videoSources, bIsSourceName) < 0);
10498 if (sourceNotFound && sourcePath.empty())
10499 sourcePath = parentPath;
10501 int sourcePathID = GetPathId(sourcePath);
10502 auto sourcePathsDeleteDecision = sourcePathsDeleteDecisions.find(sourcePathID);
10503 if (sourcePathsDeleteDecision == sourcePathsDeleteDecisions.end())
10505 bool sourcePathNotExists = (sourceNotFound || !CDirectory::Exists(sourcePath, false));
10506 // if the parent path exists, the file will be deleted without asking
10507 // if the parent path doesn't exist or does not belong to a valid media source,
10508 // ask the user whether to remove all items it contained
10509 if (sourcePathNotExists)
10511 // in silent mode assume that the files are just temporarily missing
10512 if (silent)
10513 del = false;
10514 else
10516 CGUIDialogYesNo* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO);
10517 if (pDialog != NULL)
10519 CURL sourceUrl(sourcePath);
10520 pDialog->SetHeading(CVariant{15012});
10521 pDialog->SetText(CVariant{StringUtils::Format(g_localizeStrings.Get(15013),
10522 sourceUrl.GetWithoutUserDetails())});
10523 pDialog->SetChoice(0, CVariant{15015});
10524 pDialog->SetChoice(1, CVariant{15014});
10525 pDialog->Open();
10527 del = !pDialog->IsConfirmed();
10532 sourcePathsDeleteDecisions.insert(std::make_pair(sourcePathID, std::make_pair(sourcePathNotExists, del)));
10533 pathsDeleteDecisions.insert(std::make_pair(sourcePathID, sourcePathNotExists && del));
10535 // the only reason not to delete the file is if the parent path doesn't
10536 // exist and the user decided to delete all the items it contained
10537 else if (sourcePathsDeleteDecision->second.first &&
10538 !sourcePathsDeleteDecision->second.second)
10539 del = false;
10541 if (scanSettings.parent_name)
10542 pathsDeleteDecisions.insert(std::make_pair(m_pDS2->fv(2).get_asInt(), del));
10545 if (del)
10547 deletedFileIDs += m_pDS2->fv(1).get_asString() + ",";
10548 cleanedIDs.push_back(m_pDS2->fv(0).get_asInt());
10551 m_pDS2->next();
10553 m_pDS2->close();
10555 return cleanedIDs;
10558 void CVideoDatabase::DumpToDummyFiles(const std::string &path)
10560 // get all tvshows
10561 CFileItemList items;
10562 GetTvShowsByWhere("videodb://tvshows/titles/", CDatabase::Filter(), items);
10563 std::string showPath = URIUtils::AddFileToFolder(path, "shows");
10564 CDirectory::Create(showPath);
10565 for (int i = 0; i < items.Size(); i++)
10567 // create a folder in this directory
10568 std::string showName = CUtil::MakeLegalFileName(items[i]->GetVideoInfoTag()->m_strShowTitle);
10569 std::string TVFolder = URIUtils::AddFileToFolder(showPath, showName);
10570 if (CDirectory::Create(TVFolder))
10571 { // right - grab the episodes and dump them as well
10572 CFileItemList episodes;
10573 Filter filter(PrepareSQL("idShow=%i", items[i]->GetVideoInfoTag()->m_iDbId));
10574 GetEpisodesByWhere("videodb://tvshows/titles/", filter, episodes);
10575 for (int i = 0; i < episodes.Size(); i++)
10577 CVideoInfoTag *tag = episodes[i]->GetVideoInfoTag();
10578 std::string episode =
10579 StringUtils::Format("{}.s{:02}e{:02}.avi", showName, tag->m_iSeason, tag->m_iEpisode);
10580 // and make a file
10581 std::string episodePath = URIUtils::AddFileToFolder(TVFolder, episode);
10582 CFile file;
10583 if (file.OpenForWrite(episodePath))
10584 file.Close();
10588 // get all movies
10589 items.Clear();
10590 GetMoviesByWhere("videodb://movies/titles/", CDatabase::Filter(), items);
10591 std::string moviePath = URIUtils::AddFileToFolder(path, "movies");
10592 CDirectory::Create(moviePath);
10593 for (int i = 0; i < items.Size(); i++)
10595 CVideoInfoTag *tag = items[i]->GetVideoInfoTag();
10596 std::string movie = StringUtils::Format("{}.avi", tag->m_strTitle);
10597 CFile file;
10598 if (file.OpenForWrite(URIUtils::AddFileToFolder(moviePath, movie)))
10599 file.Close();
10603 void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = true */, bool images /* = false */, bool actorThumbs /* false */, bool overwrite /*=false*/)
10605 int iFailCount = 0;
10606 CGUIDialogProgress *progress=NULL;
10609 if (nullptr == m_pDB)
10610 return;
10611 if (nullptr == m_pDS)
10612 return;
10613 if (nullptr == m_pDS2)
10614 return;
10616 // create a 3rd dataset as well as GetEpisodeDetails() etc. uses m_pDS2, and we need to do 3 nested queries on tv shows
10617 std::unique_ptr<Dataset> pDS;
10618 pDS.reset(m_pDB->CreateDataset());
10619 if (nullptr == pDS)
10620 return;
10622 std::unique_ptr<Dataset> pDS2;
10623 pDS2.reset(m_pDB->CreateDataset());
10624 if (nullptr == pDS2)
10625 return;
10627 // if we're exporting to a single folder, we export thumbs as well
10628 std::string exportRoot = URIUtils::AddFileToFolder(path, "kodi_videodb_" + CDateTime::GetCurrentDateTime().GetAsDBDate());
10629 std::string xmlFile = URIUtils::AddFileToFolder(exportRoot, "videodb.xml");
10630 std::string actorsDir = URIUtils::AddFileToFolder(exportRoot, "actors");
10631 std::string moviesDir = URIUtils::AddFileToFolder(exportRoot, "movies");
10632 std::string movieSetsDir = URIUtils::AddFileToFolder(exportRoot, "moviesets");
10633 std::string musicvideosDir = URIUtils::AddFileToFolder(exportRoot, "musicvideos");
10634 std::string tvshowsDir = URIUtils::AddFileToFolder(exportRoot, "tvshows");
10635 if (singleFile)
10637 images = true;
10638 overwrite = false;
10639 actorThumbs = true;
10640 CDirectory::Remove(exportRoot);
10641 CDirectory::Create(exportRoot);
10642 CDirectory::Create(actorsDir);
10643 CDirectory::Create(moviesDir);
10644 CDirectory::Create(movieSetsDir);
10645 CDirectory::Create(musicvideosDir);
10646 CDirectory::Create(tvshowsDir);
10649 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
10650 // find all movies
10651 std::string sql = "select * from movie_view";
10653 m_pDS->query(sql);
10655 if (progress)
10657 progress->SetHeading(CVariant{647});
10658 progress->SetLine(0, CVariant{650});
10659 progress->SetLine(1, CVariant{""});
10660 progress->SetLine(2, CVariant{""});
10661 progress->SetPercentage(0);
10662 progress->Open();
10663 progress->ShowProgressBar(true);
10666 int total = m_pDS->num_rows();
10667 int current = 0;
10669 // create our xml document
10670 CXBMCTinyXML xmlDoc;
10671 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10672 xmlDoc.InsertEndChild(decl);
10673 TiXmlNode *pMain = NULL;
10674 if (!singleFile)
10675 pMain = &xmlDoc;
10676 else
10678 TiXmlElement xmlMainElement("videodb");
10679 pMain = xmlDoc.InsertEndChild(xmlMainElement);
10680 XMLUtils::SetInt(pMain,"version", GetExportVersion());
10683 while (!m_pDS->eof())
10685 CVideoInfoTag movie = GetDetailsForMovie(m_pDS, VideoDbDetailsAll);
10686 // strip paths to make them relative
10687 if (StringUtils::StartsWith(movie.m_strTrailer, movie.m_strPath))
10688 movie.m_strTrailer = movie.m_strTrailer.substr(movie.m_strPath.size());
10689 std::map<std::string, std::string> artwork;
10690 if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && singleFile)
10692 TiXmlElement additionalNode("art");
10693 for (const auto &i : artwork)
10694 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10695 movie.Save(pMain, "movie", true, &additionalNode);
10697 else
10698 movie.Save(pMain, "movie", singleFile);
10700 // reset old skip state
10701 bool bSkip = false;
10703 if (progress)
10705 progress->SetLine(1, CVariant{movie.m_strTitle});
10706 progress->SetPercentage(current * 100 / total);
10707 progress->Progress();
10708 if (progress->IsCanceled())
10710 progress->Close();
10711 m_pDS->close();
10712 return;
10716 CFileItem item(movie.m_strFileNameAndPath,false);
10717 if (!singleFile && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
10719 if (!item.Exists(false))
10721 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10722 movie.m_strFileNameAndPath);
10723 bSkip = true;
10725 else
10727 std::string nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), ".nfo"));
10729 if (item.IsOpticalMediaFile())
10731 nfoFile = URIUtils::AddFileToFolder(
10732 URIUtils::GetParentPath(nfoFile),
10733 URIUtils::GetFileName(nfoFile));
10736 if (overwrite || !CFile::Exists(nfoFile, false))
10738 if(!xmlDoc.SaveFile(nfoFile))
10740 CLog::Log(LOGERROR, "{}: Movie nfo export failed! ('{}')", __FUNCTION__, nfoFile);
10741 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
10742 g_localizeStrings.Get(20302),
10743 CURL::GetRedacted(nfoFile));
10744 iFailCount++;
10749 if (!singleFile)
10751 xmlDoc.Clear();
10752 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10753 xmlDoc.InsertEndChild(decl);
10756 if (images && !bSkip)
10758 if (singleFile)
10760 std::string strFileName(movie.m_strTitle);
10761 if (movie.HasYear())
10762 strFileName += StringUtils::Format("_{}", movie.GetYear());
10763 item.SetPath(GetSafeFile(moviesDir, strFileName) + ".avi");
10765 for (const auto &i : artwork)
10767 std::string savedThumb = ART::GetLocalArt(item, i.first, false);
10768 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
10770 if (actorThumbs)
10771 ExportActorThumbs(actorsDir, movie, !singleFile, overwrite);
10773 m_pDS->next();
10774 current++;
10776 m_pDS->close();
10778 if (!singleFile)
10779 movieSetsDir = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
10780 CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER);
10781 if (images && !movieSetsDir.empty())
10783 // find all movie sets
10784 sql = "select idSet, strSet from sets";
10786 m_pDS->query(sql);
10788 total = m_pDS->num_rows();
10789 current = 0;
10791 while (!m_pDS->eof())
10793 std::string title = m_pDS->fv("strSet").get_asString();
10795 if (progress)
10797 progress->SetLine(1, CVariant{title});
10798 progress->SetPercentage(current * 100 / total);
10799 progress->Progress();
10800 if (progress->IsCanceled())
10802 progress->Close();
10803 m_pDS->close();
10804 return;
10808 std::string itemPath = URIUtils::AddFileToFolder(movieSetsDir,
10809 CUtil::MakeLegalFileName(title, LEGAL_WIN32_COMPAT));
10810 if (CDirectory::Exists(itemPath) || CDirectory::Create(itemPath))
10812 std::map<std::string, std::string> artwork;
10813 GetArtForItem(m_pDS->fv("idSet").get_asInt(), MediaTypeVideoCollection, artwork);
10814 for (const auto& art : artwork)
10816 std::string savedThumb = URIUtils::AddFileToFolder(itemPath, art.first);
10817 CServiceBroker::GetTextureCache()->Export(art.second, savedThumb, overwrite);
10820 else
10821 CLog::Log(
10822 LOGDEBUG,
10823 "CVideoDatabase::{} - Not exporting movie set '{}' as could not create folder '{}'",
10824 __FUNCTION__, title, itemPath);
10825 m_pDS->next();
10826 current++;
10828 m_pDS->close();
10831 // find all musicvideos
10832 sql = "select * from musicvideo_view";
10834 m_pDS->query(sql);
10836 total = m_pDS->num_rows();
10837 current = 0;
10839 while (!m_pDS->eof())
10841 CVideoInfoTag movie = GetDetailsForMusicVideo(m_pDS, VideoDbDetailsAll);
10842 std::map<std::string, std::string> artwork;
10843 if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && singleFile)
10845 TiXmlElement additionalNode("art");
10846 for (const auto &i : artwork)
10847 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10848 movie.Save(pMain, "musicvideo", true, &additionalNode);
10850 else
10851 movie.Save(pMain, "musicvideo", singleFile);
10853 // reset old skip state
10854 bool bSkip = false;
10856 if (progress)
10858 progress->SetLine(1, CVariant{movie.m_strTitle});
10859 progress->SetPercentage(current * 100 / total);
10860 progress->Progress();
10861 if (progress->IsCanceled())
10863 progress->Close();
10864 m_pDS->close();
10865 return;
10869 CFileItem item(movie.m_strFileNameAndPath,false);
10870 if (!singleFile && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
10872 if (!item.Exists(false))
10874 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10875 movie.m_strFileNameAndPath);
10876 bSkip = true;
10878 else
10880 std::string nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), ".nfo"));
10882 if (overwrite || !CFile::Exists(nfoFile, false))
10884 if(!xmlDoc.SaveFile(nfoFile))
10886 CLog::Log(LOGERROR, "{}: Musicvideo nfo export failed! ('{}')", __FUNCTION__,
10887 nfoFile);
10888 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
10889 g_localizeStrings.Get(20302),
10890 CURL::GetRedacted(nfoFile));
10891 iFailCount++;
10896 if (!singleFile)
10898 xmlDoc.Clear();
10899 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10900 xmlDoc.InsertEndChild(decl);
10902 if (images && !bSkip)
10904 if (singleFile)
10906 std::string strFileName(StringUtils::Join(movie.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + "." + movie.m_strTitle);
10907 if (movie.HasYear())
10908 strFileName += StringUtils::Format("_{}", movie.GetYear());
10909 item.SetPath(GetSafeFile(musicvideosDir, strFileName) + ".avi");
10911 for (const auto &i : artwork)
10913 std::string savedThumb = ART::GetLocalArt(item, i.first, false);
10914 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
10917 m_pDS->next();
10918 current++;
10920 m_pDS->close();
10922 // repeat for all tvshows
10923 sql = "SELECT * FROM tvshow_view";
10924 m_pDS->query(sql);
10926 total = m_pDS->num_rows();
10927 current = 0;
10929 while (!m_pDS->eof())
10931 CVideoInfoTag tvshow = GetDetailsForTvShow(m_pDS, VideoDbDetailsAll);
10932 GetTvShowNamedSeasons(tvshow.m_iDbId, tvshow.m_namedSeasons);
10934 std::map<int, std::map<std::string, std::string> > seasonArt;
10935 GetTvShowSeasonArt(tvshow.m_iDbId, seasonArt);
10937 std::map<std::string, std::string> artwork;
10938 if (GetArtForItem(tvshow.m_iDbId, tvshow.m_type, artwork) && singleFile)
10940 TiXmlElement additionalNode("art");
10941 for (const auto &i : artwork)
10942 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10943 for (const auto &i : seasonArt)
10945 TiXmlElement seasonNode("season");
10946 seasonNode.SetAttribute("num", i.first);
10947 for (const auto &j : i.second)
10948 XMLUtils::SetString(&seasonNode, j.first.c_str(), j.second);
10949 additionalNode.InsertEndChild(seasonNode);
10951 tvshow.Save(pMain, "tvshow", true, &additionalNode);
10953 else
10954 tvshow.Save(pMain, "tvshow", singleFile);
10956 // reset old skip state
10957 bool bSkip = false;
10959 if (progress)
10961 progress->SetLine(1, CVariant{tvshow.m_strTitle});
10962 progress->SetPercentage(current * 100 / total);
10963 progress->Progress();
10964 if (progress->IsCanceled())
10966 progress->Close();
10967 m_pDS->close();
10968 return;
10972 CFileItem item(tvshow.m_strPath, true);
10973 if (!singleFile && CUtil::SupportsWriteFileOperations(tvshow.m_strPath))
10975 if (!item.Exists(false))
10977 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10978 tvshow.m_strPath);
10979 bSkip = true;
10981 else
10983 std::string nfoFile = URIUtils::AddFileToFolder(tvshow.m_strPath, "tvshow.nfo");
10985 if (overwrite || !CFile::Exists(nfoFile, false))
10987 if(!xmlDoc.SaveFile(nfoFile))
10989 CLog::Log(LOGERROR, "{}: TVShow nfo export failed! ('{}')", __FUNCTION__, nfoFile);
10990 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
10991 g_localizeStrings.Get(20302),
10992 CURL::GetRedacted(nfoFile));
10993 iFailCount++;
10998 if (!singleFile)
11000 xmlDoc.Clear();
11001 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
11002 xmlDoc.InsertEndChild(decl);
11004 if (images && !bSkip)
11006 if (singleFile)
11007 item.SetPath(GetSafeFile(tvshowsDir, tvshow.m_strTitle));
11009 for (const auto &i : artwork)
11011 std::string savedThumb = ART::GetLocalArt(item, i.first, true);
11012 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
11015 if (actorThumbs)
11016 ExportActorThumbs(actorsDir, tvshow, !singleFile, overwrite);
11018 // export season thumbs
11019 for (const auto &i : seasonArt)
11021 std::string seasonThumb;
11022 if (i.first == -1)
11023 seasonThumb = "season-all";
11024 else if (i.first == 0)
11025 seasonThumb = "season-specials";
11026 else
11027 seasonThumb = StringUtils::Format("season{:02}", i.first);
11028 for (const auto &j : i.second)
11030 std::string savedThumb(ART::GetLocalArt(item, seasonThumb + "-" + j.first, true));
11031 if (!i.second.empty())
11032 CServiceBroker::GetTextureCache()->Export(j.second, savedThumb, overwrite);
11037 // now save the episodes from this show
11038 sql = PrepareSQL("select * from episode_view where idShow=%i order by strFileName, idEpisode",tvshow.m_iDbId);
11039 pDS->query(sql);
11040 std::string showDir(item.GetPath());
11042 while (!pDS->eof())
11044 CVideoInfoTag episode = GetDetailsForEpisode(pDS, VideoDbDetailsAll);
11045 std::map<std::string, std::string> artwork;
11046 if (GetArtForItem(episode.m_iDbId, MediaTypeEpisode, artwork) && singleFile)
11048 TiXmlElement additionalNode("art");
11049 for (const auto &i : artwork)
11050 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
11051 episode.Save(pMain->LastChild(), "episodedetails", true, &additionalNode);
11053 else if (singleFile)
11054 episode.Save(pMain->LastChild(), "episodedetails", singleFile);
11055 else
11056 episode.Save(pMain, "episodedetails", singleFile);
11057 pDS->next();
11058 // multi-episode files need dumping to the same XML
11059 while (!singleFile && !pDS->eof() &&
11060 episode.m_iFileId == pDS->fv("idFile").get_asInt())
11062 episode = GetDetailsForEpisode(pDS, VideoDbDetailsAll);
11063 episode.Save(pMain, "episodedetails", singleFile);
11064 pDS->next();
11067 // reset old skip state
11068 bool bSkip = false;
11070 CFileItem item(episode.m_strFileNameAndPath, false);
11071 if (!singleFile && CUtil::SupportsWriteFileOperations(episode.m_strFileNameAndPath))
11073 if (!item.Exists(false))
11075 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
11076 episode.m_strFileNameAndPath);
11077 bSkip = true;
11079 else
11081 std::string nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), ".nfo"));
11083 if (overwrite || !CFile::Exists(nfoFile, false))
11085 if(!xmlDoc.SaveFile(nfoFile))
11087 CLog::Log(LOGERROR, "{}: Episode nfo export failed! ('{}')", __FUNCTION__, nfoFile);
11088 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
11089 g_localizeStrings.Get(20302),
11090 CURL::GetRedacted(nfoFile));
11091 iFailCount++;
11096 if (!singleFile)
11098 xmlDoc.Clear();
11099 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
11100 xmlDoc.InsertEndChild(decl);
11103 if (images && !bSkip)
11105 if (singleFile)
11107 std::string epName =
11108 StringUtils::Format("s{:02}e{:02}.avi", episode.m_iSeason, episode.m_iEpisode);
11109 item.SetPath(URIUtils::AddFileToFolder(showDir, epName));
11111 for (const auto &i : artwork)
11113 std::string savedThumb = ART::GetLocalArt(item, i.first, false);
11114 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
11116 if (actorThumbs)
11117 ExportActorThumbs(actorsDir, episode, !singleFile, overwrite);
11120 pDS->close();
11121 m_pDS->next();
11122 current++;
11124 m_pDS->close();
11126 if (!singleFile && progress)
11128 progress->SetPercentage(100);
11129 progress->Progress();
11132 if (singleFile)
11134 // now dump path info
11135 std::set<std::string> paths;
11136 GetPaths(paths);
11137 TiXmlElement xmlPathElement("paths");
11138 TiXmlNode *pPaths = pMain->InsertEndChild(xmlPathElement);
11139 for (const auto &i : paths)
11141 bool foundDirectly = false;
11142 SScanSettings settings;
11143 ScraperPtr info = GetScraperForPath(i, settings, foundDirectly);
11144 if (info && foundDirectly)
11146 TiXmlElement xmlPathElement2("path");
11147 TiXmlNode *pPath = pPaths->InsertEndChild(xmlPathElement2);
11148 XMLUtils::SetString(pPath,"url", i);
11149 XMLUtils::SetInt(pPath,"scanrecursive", settings.recurse);
11150 XMLUtils::SetBoolean(pPath,"usefoldernames", settings.parent_name);
11151 XMLUtils::SetString(pPath,"content", TranslateContent(info->Content()));
11152 XMLUtils::SetString(pPath,"scraperpath", info->ID());
11155 xmlDoc.SaveFile(xmlFile);
11157 CVariant data;
11158 if (singleFile)
11160 data["root"] = exportRoot;
11161 data["file"] = xmlFile;
11162 if (iFailCount > 0)
11163 data["failcount"] = iFailCount;
11165 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnExport",
11166 data);
11168 catch (...)
11170 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
11171 iFailCount++;
11174 if (progress)
11175 progress->Close();
11177 if (iFailCount > 0)
11178 HELPERS::ShowOKDialogText(
11179 CVariant{647}, CVariant{StringUtils::Format(g_localizeStrings.Get(15011), iFailCount)});
11182 void CVideoDatabase::ExportActorThumbs(const std::string &strDir, const CVideoInfoTag &tag, bool singleFiles, bool overwrite /*=false*/)
11184 std::string strPath(strDir);
11185 if (singleFiles)
11187 strPath = URIUtils::AddFileToFolder(tag.m_strPath, ".actors");
11188 if (!CDirectory::Exists(strPath))
11190 CDirectory::Create(strPath);
11191 CFile::SetHidden(strPath, true);
11195 for (const auto &i : tag.m_cast)
11197 CFileItem item;
11198 item.SetLabel(i.strName);
11199 if (!i.thumb.empty())
11201 std::string thumbFile(GetSafeFile(strPath, i.strName));
11202 CServiceBroker::GetTextureCache()->Export(i.thumb, thumbFile, overwrite);
11207 void CVideoDatabase::ImportFromXML(const std::string &path)
11209 CGUIDialogProgress *progress=NULL;
11212 if (nullptr == m_pDB)
11213 return;
11214 if (nullptr == m_pDS)
11215 return;
11217 CXBMCTinyXML xmlDoc;
11218 if (!xmlDoc.LoadFile(URIUtils::AddFileToFolder(path, "videodb.xml")))
11219 return;
11221 TiXmlElement *root = xmlDoc.RootElement();
11222 if (!root) return;
11224 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
11225 if (progress)
11227 progress->SetHeading(CVariant{648});
11228 progress->SetLine(0, CVariant{649});
11229 progress->SetLine(1, CVariant{330});
11230 progress->SetLine(2, CVariant{""});
11231 progress->SetPercentage(0);
11232 progress->Open();
11233 progress->ShowProgressBar(true);
11236 int iVersion = 0;
11237 XMLUtils::GetInt(root, "version", iVersion);
11239 CLog::Log(LOGINFO, "{}: Starting import (export version = {})", __FUNCTION__, iVersion);
11241 TiXmlElement *movie = root->FirstChildElement();
11242 int current = 0;
11243 int total = 0;
11244 // first count the number of items...
11245 while (movie)
11247 if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMovie, 5) == 0 ||
11248 StringUtils::CompareNoCase(movie->Value(), MediaTypeTvShow, 6) == 0 ||
11249 StringUtils::CompareNoCase(movie->Value(), MediaTypeMusicVideo, 10) == 0)
11250 total++;
11251 movie = movie->NextSiblingElement();
11254 std::string actorsDir(URIUtils::AddFileToFolder(path, "actors"));
11255 std::string moviesDir(URIUtils::AddFileToFolder(path, "movies"));
11256 std::string movieSetsDir(URIUtils::AddFileToFolder(path, "moviesets"));
11257 std::string musicvideosDir(URIUtils::AddFileToFolder(path, "musicvideos"));
11258 std::string tvshowsDir(URIUtils::AddFileToFolder(path, "tvshows"));
11259 CVideoInfoScanner scanner;
11260 // add paths first (so we have scraper settings available)
11261 TiXmlElement *path = root->FirstChildElement("paths");
11262 path = path->FirstChildElement();
11263 while (path)
11265 std::string strPath;
11266 if (XMLUtils::GetString(path,"url",strPath) && !strPath.empty())
11267 AddPath(strPath);
11269 std::string content;
11270 if (XMLUtils::GetString(path,"content", content) && !content.empty())
11271 { // check the scraper exists, if so store the path
11272 AddonPtr addon;
11273 std::string id;
11274 XMLUtils::GetString(path,"scraperpath",id);
11275 if (CServiceBroker::GetAddonMgr().GetAddon(id, addon, ADDON::OnlyEnabled::CHOICE_YES))
11277 SScanSettings settings;
11278 ScraperPtr scraper = std::dynamic_pointer_cast<CScraper>(addon);
11279 // FIXME: scraper settings are not exported?
11280 scraper->SetPathSettings(TranslateContent(content), "");
11281 XMLUtils::GetInt(path,"scanrecursive",settings.recurse);
11282 XMLUtils::GetBoolean(path,"usefoldernames",settings.parent_name);
11283 SetScraperForPath(strPath,scraper,settings);
11286 path = path->NextSiblingElement();
11288 movie = root->FirstChildElement();
11289 while (movie)
11291 CVideoInfoTag info;
11292 if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMovie, 5) == 0)
11294 info.Load(movie);
11295 CFileItem item(info);
11296 bool useFolders = info.m_basePath.empty() ? LookupByFolders(item.GetPath()) : false;
11297 std::string filename = info.m_strTitle;
11298 if (info.HasYear())
11299 filename += StringUtils::Format("_{}", info.GetYear());
11300 CFileItem artItem(item);
11301 artItem.SetPath(GetSafeFile(moviesDir, filename) + ".avi");
11302 scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
11303 item.SetArt(artItem.GetArt());
11304 if (!item.GetVideoInfoTag()->m_set.title.empty())
11306 std::string setPath = URIUtils::AddFileToFolder(movieSetsDir,
11307 CUtil::MakeLegalFileName(item.GetVideoInfoTag()->m_set.title, LEGAL_WIN32_COMPAT));
11308 if (CDirectory::Exists(setPath))
11310 CGUIListItem::ArtMap setArt;
11311 CFileItem artItem(setPath, true);
11312 for (const auto& artType : CVideoThumbLoader::GetArtTypes(MediaTypeVideoCollection))
11314 std::string artPath = CVideoThumbLoader::GetLocalArt(artItem, artType, true);
11315 if (!artPath.empty())
11317 setArt[artType] = artPath;
11320 item.AppendArt(setArt, "set");
11323 scanner.AddVideo(&item, CONTENT_MOVIES, useFolders, true, NULL, true);
11324 current++;
11326 else if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMusicVideo, 10) == 0)
11328 info.Load(movie);
11329 CFileItem item(info);
11330 bool useFolders = info.m_basePath.empty() ? LookupByFolders(item.GetPath()) : false;
11331 std::string filename = StringUtils::Join(info.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + "." + info.m_strTitle;
11332 if (info.HasYear())
11333 filename += StringUtils::Format("_{}", info.GetYear());
11334 CFileItem artItem(item);
11335 artItem.SetPath(GetSafeFile(musicvideosDir, filename) + ".avi");
11336 scanner.GetArtwork(&artItem, CONTENT_MUSICVIDEOS, useFolders, true, actorsDir);
11337 item.SetArt(artItem.GetArt());
11338 scanner.AddVideo(&item, CONTENT_MUSICVIDEOS, useFolders, true, NULL, true);
11339 current++;
11341 else if (StringUtils::CompareNoCase(movie->Value(), MediaTypeTvShow, 6) == 0)
11343 // load the TV show in. NOTE: This deletes all episodes under the TV Show, which may not be
11344 // what we desire. It may make better sense to only delete (or even better, update) the show information
11345 info.Load(movie);
11346 URIUtils::AddSlashAtEnd(info.m_strPath);
11347 DeleteTvShow(info.m_strPath);
11348 CFileItem showItem(info);
11349 bool useFolders = info.m_basePath.empty() ? LookupByFolders(showItem.GetPath(), true) : false;
11350 CFileItem artItem(showItem);
11351 std::string artPath(GetSafeFile(tvshowsDir, info.m_strTitle));
11352 artItem.SetPath(artPath);
11353 scanner.GetArtwork(&artItem, CONTENT_TVSHOWS, useFolders, true, actorsDir);
11354 showItem.SetArt(artItem.GetArt());
11355 int showID = scanner.AddVideo(&showItem, CONTENT_TVSHOWS, useFolders, true, NULL, true);
11356 // season artwork
11357 std::map<int, std::map<std::string, std::string> > seasonArt;
11358 artItem.GetVideoInfoTag()->m_strPath = artPath;
11359 scanner.GetSeasonThumbs(*artItem.GetVideoInfoTag(), seasonArt, CVideoThumbLoader::GetArtTypes(MediaTypeSeason), true);
11360 for (const auto &i : seasonArt)
11362 int seasonID = AddSeason(showID, i.first);
11363 SetArtForItem(seasonID, MediaTypeSeason, i.second);
11365 current++;
11366 // now load the episodes
11367 TiXmlElement *episode = movie->FirstChildElement("episodedetails");
11368 while (episode)
11370 // no need to delete the episode info, due to the above deletion
11371 CVideoInfoTag info;
11372 info.Load(episode);
11373 CFileItem item(info);
11374 std::string filename =
11375 StringUtils::Format("s{:02}e{:02}.avi", info.m_iSeason, info.m_iEpisode);
11376 CFileItem artItem(item);
11377 artItem.SetPath(GetSafeFile(artPath, filename));
11378 scanner.GetArtwork(&artItem, CONTENT_TVSHOWS, useFolders, true, actorsDir);
11379 item.SetArt(artItem.GetArt());
11380 scanner.AddVideo(&item,CONTENT_TVSHOWS, false, false, showItem.GetVideoInfoTag(), true);
11381 episode = episode->NextSiblingElement("episodedetails");
11384 movie = movie->NextSiblingElement();
11385 if (progress && total)
11387 progress->SetPercentage(current * 100 / total);
11388 progress->SetLine(2, CVariant{info.m_strTitle});
11389 progress->Progress();
11390 if (progress->IsCanceled())
11392 progress->Close();
11393 return;
11398 catch (...)
11400 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
11402 if (progress)
11403 progress->Close();
11406 bool CVideoDatabase::ImportArtFromXML(const TiXmlNode *node, std::map<std::string, std::string> &artwork)
11408 if (!node) return false;
11409 const TiXmlNode *art = node->FirstChild();
11410 while (art && art->FirstChild())
11412 artwork.insert(make_pair(art->ValueStr(), art->FirstChild()->ValueStr()));
11413 art = art->NextSibling();
11415 return !artwork.empty();
11418 void CVideoDatabase::ConstructPath(std::string& strDest, const std::string& strPath, const std::string& strFileName)
11420 if (URIUtils::IsStack(strFileName) ||
11421 URIUtils::IsInArchive(strFileName) || URIUtils::IsPlugin(strPath))
11422 strDest = strFileName;
11423 else
11424 strDest = URIUtils::AddFileToFolder(strPath, strFileName);
11427 void CVideoDatabase::SplitPath(const std::string& strFileNameAndPath, std::string& strPath, std::string& strFileName)
11429 if (URIUtils::IsStack(strFileNameAndPath) || StringUtils::StartsWithNoCase(strFileNameAndPath, "rar://") || StringUtils::StartsWithNoCase(strFileNameAndPath, "zip://"))
11431 URIUtils::GetParentPath(strFileNameAndPath,strPath);
11432 strFileName = strFileNameAndPath;
11434 else if (URIUtils::IsPlugin(strFileNameAndPath))
11436 CURL url(strFileNameAndPath);
11437 strPath = url.GetOptions().empty() ? url.GetWithoutFilename() : url.GetWithoutOptions();
11438 strFileName = strFileNameAndPath;
11440 else
11442 URIUtils::Split(strFileNameAndPath, strPath, strFileName);
11443 // Keep protocol options as part of the path
11444 if (URIUtils::IsURL(strFileNameAndPath))
11446 CURL url(strFileNameAndPath);
11447 if (!url.GetProtocolOptions().empty())
11448 strPath += "|" + url.GetProtocolOptions();
11453 void CVideoDatabase::InvalidatePathHash(const std::string& strPath)
11455 SScanSettings settings;
11456 bool foundDirectly;
11457 ScraperPtr info = GetScraperForPath(strPath,settings,foundDirectly);
11458 SetPathHash(strPath,"");
11459 if (!info)
11460 return;
11461 if (info->Content() == CONTENT_TVSHOWS || (info->Content() == CONTENT_MOVIES && !foundDirectly)) // if we scan by folder name we need to invalidate parent as well
11463 if (info->Content() == CONTENT_TVSHOWS || settings.parent_name_root)
11465 std::string strParent;
11466 if (URIUtils::GetParentPath(strPath, strParent) && (!URIUtils::IsPlugin(strPath) || !CURL(strParent).GetHostName().empty()))
11467 SetPathHash(strParent, "");
11472 bool CVideoDatabase::CommitTransaction()
11474 if (CDatabase::CommitTransaction())
11475 { // number of items in the db has likely changed, so recalculate
11476 GUIINFO::CLibraryGUIInfo& guiInfo = CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider();
11477 guiInfo.SetLibraryBool(LIBRARY_HAS_MOVIES, HasContent(VideoDbContentType::MOVIES));
11478 guiInfo.SetLibraryBool(LIBRARY_HAS_TVSHOWS, HasContent(VideoDbContentType::TVSHOWS));
11479 guiInfo.SetLibraryBool(LIBRARY_HAS_MUSICVIDEOS, HasContent(VideoDbContentType::MUSICVIDEOS));
11480 return true;
11482 return false;
11485 bool CVideoDatabase::SetSingleValue(VideoDbContentType type,
11486 int dbId,
11487 int dbField,
11488 const std::string& strValue)
11490 std::string strSQL;
11493 if (nullptr == m_pDB || nullptr == m_pDS)
11494 return false;
11496 std::string strTable, strField;
11497 if (type == VideoDbContentType::MOVIES)
11499 strTable = "movie";
11500 strField = "idMovie";
11502 else if (type == VideoDbContentType::TVSHOWS)
11504 strTable = "tvshow";
11505 strField = "idShow";
11507 else if (type == VideoDbContentType::EPISODES)
11509 strTable = "episode";
11510 strField = "idEpisode";
11512 else if (type == VideoDbContentType::MUSICVIDEOS)
11514 strTable = "musicvideo";
11515 strField = "idMVideo";
11518 if (strTable.empty())
11519 return false;
11521 return SetSingleValue(strTable, StringUtils::Format("c{:02}", dbField), strValue, strField,
11522 dbId);
11524 catch (...)
11526 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
11528 return false;
11531 bool CVideoDatabase::SetSingleValue(VideoDbContentType type,
11532 int dbId,
11533 Field dbField,
11534 const std::string& strValue)
11536 MediaType mediaType = DatabaseUtils::MediaTypeFromVideoContentType(type);
11537 if (mediaType == MediaTypeNone)
11538 return false;
11540 int dbFieldIndex = DatabaseUtils::GetField(dbField, mediaType);
11541 if (dbFieldIndex < 0)
11542 return false;
11544 return SetSingleValue(type, dbId, dbFieldIndex, strValue);
11547 bool CVideoDatabase::SetSingleValue(const std::string &table, const std::string &fieldName, const std::string &strValue,
11548 const std::string &conditionName /* = "" */, int conditionValue /* = -1 */)
11550 if (table.empty() || fieldName.empty())
11551 return false;
11553 std::string sql;
11556 if (nullptr == m_pDB || nullptr == m_pDS)
11557 return false;
11559 sql = PrepareSQL("UPDATE %s SET %s='%s'", table.c_str(), fieldName.c_str(), strValue.c_str());
11560 if (!conditionName.empty())
11561 sql += PrepareSQL(" WHERE %s=%u", conditionName.c_str(), conditionValue);
11562 if (m_pDS->exec(sql) == 0)
11563 return true;
11565 catch (...)
11567 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, sql);
11569 return false;
11572 std::string CVideoDatabase::GetSafeFile(const std::string &dir, const std::string &name) const
11574 std::string safeThumb(name);
11575 StringUtils::Replace(safeThumb, ' ', '_');
11576 return URIUtils::AddFileToFolder(dir, CUtil::MakeLegalFileName(std::move(safeThumb)));
11579 void CVideoDatabase::AnnounceRemove(const std::string& content, int id, bool scanning /* = false */)
11581 CVariant data;
11582 data["type"] = content;
11583 data["id"] = id;
11584 if (scanning)
11585 data["transaction"] = true;
11586 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnRemove", data);
11589 void CVideoDatabase::AnnounceUpdate(const std::string& content, int id)
11591 CVariant data;
11592 data["type"] = content;
11593 data["id"] = id;
11594 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnUpdate", data);
11597 bool CVideoDatabase::GetItemsForPath(const std::string &content, const std::string &strPath, CFileItemList &items)
11599 const std::string& path(strPath);
11601 if(URIUtils::IsMultiPath(path))
11603 std::vector<std::string> paths;
11604 CMultiPathDirectory::GetPaths(path, paths);
11606 for(unsigned i=0;i<paths.size();i++)
11607 GetItemsForPath(content, paths[i], items);
11609 return items.Size() > 0;
11612 int pathID = GetPathId(path);
11613 if (pathID < 0)
11614 return false;
11616 if (content == "movies")
11618 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_PARENTPATHID, pathID));
11619 GetMoviesByWhere("videodb://movies/titles/", filter, items);
11621 else if (content == "episodes")
11623 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_EPISODE_PARENTPATHID, pathID));
11624 GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
11626 else if (content == "tvshows")
11628 Filter filter(PrepareSQL("idParentPath=%d", pathID));
11629 GetTvShowsByWhere("videodb://tvshows/titles/", filter, items);
11631 else if (content == "musicvideos")
11633 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_MUSICVIDEO_PARENTPATHID, pathID));
11634 GetMusicVideosByWhere("videodb://musicvideos/titles/", filter, items);
11636 for (int i = 0; i < items.Size(); i++)
11637 items[i]->SetPath(items[i]->GetVideoInfoTag()->m_basePath);
11638 return items.Size() > 0;
11641 void CVideoDatabase::AppendIdLinkFilter(const char* field, const char *table, const MediaType& mediaType, const char *view, const char *viewKey, const CUrlOptions::UrlOptions& options, Filter &filter)
11643 auto option = options.find((std::string)field + "id");
11644 if (option == options.end())
11645 return;
11647 filter.AppendJoin(PrepareSQL("JOIN %s_link ON %s_link.media_id=%s_view.%s AND %s_link.media_type='%s'", field, field, view, viewKey, field, mediaType.c_str()));
11648 filter.AppendWhere(PrepareSQL("%s_link.%s_id = %i", field, table, (int)option->second.asInteger()));
11651 void CVideoDatabase::AppendLinkFilter(const char* field, const char *table, const MediaType& mediaType, const char *view, const char *viewKey, const CUrlOptions::UrlOptions& options, Filter &filter)
11653 auto option = options.find(field);
11654 if (option == options.end())
11655 return;
11657 filter.AppendJoin(PrepareSQL("JOIN %s_link ON %s_link.media_id=%s_view.%s AND %s_link.media_type='%s'", field, field, view, viewKey, field, mediaType.c_str()));
11658 filter.AppendJoin(PrepareSQL("JOIN %s ON %s.%s_id=%s_link.%s_id", table, table, field, table, field));
11659 filter.AppendWhere(PrepareSQL("%s.name like '%s'", table, option->second.asString().c_str()));
11662 bool CVideoDatabase::GetFilter(CDbUrl &videoUrl, Filter &filter, SortDescription &sorting)
11664 if (!videoUrl.IsValid())
11665 return false;
11667 std::string type = videoUrl.GetType();
11668 std::string itemType = ((const CVideoDbUrl &)videoUrl).GetItemType();
11669 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
11671 if (type == "movies")
11673 AppendIdLinkFilter("genre", "genre", "movie", "movie", "idMovie", options, filter);
11674 AppendLinkFilter("genre", "genre", "movie", "movie", "idMovie", options, filter);
11676 AppendIdLinkFilter("country", "country", "movie", "movie", "idMovie", options, filter);
11677 AppendLinkFilter("country", "country", "movie", "movie", "idMovie", options, filter);
11679 AppendIdLinkFilter("studio", "studio", "movie", "movie", "idMovie", options, filter);
11680 AppendLinkFilter("studio", "studio", "movie", "movie", "idMovie", options, filter);
11682 AppendIdLinkFilter("director", "actor", "movie", "movie", "idMovie", options, filter);
11683 AppendLinkFilter("director", "actor", "movie", "movie", "idMovie", options, filter);
11685 auto option = options.find("year");
11686 if (option != options.end())
11687 filter.AppendWhere(PrepareSQL("movie_view.premiered like '%i%%'", (int)option->second.asInteger()));
11689 AppendIdLinkFilter("actor", "actor", "movie", "movie", "idMovie", options, filter);
11690 AppendLinkFilter("actor", "actor", "movie", "movie", "idMovie", options, filter);
11692 option = options.find("setid");
11693 if (option != options.end())
11694 filter.AppendWhere(PrepareSQL("movie_view.idSet = %i", (int)option->second.asInteger()));
11696 option = options.find("set");
11697 if (option != options.end())
11698 filter.AppendWhere(PrepareSQL("movie_view.strSet LIKE '%s'", option->second.asString().c_str()));
11700 option = options.find("videoversionid");
11701 if (option != options.end())
11703 const int idVideoVersion{static_cast<int>(option->second.asInteger())};
11704 if (idVideoVersion > 0)
11705 filter.AppendWhere(PrepareSQL("videoVersionTypeId = %i", idVideoVersion));
11706 else
11708 option = options.find("mediaid");
11709 if (option != options.end())
11711 const int mediaId{static_cast<int>(option->second.asInteger())};
11712 if (mediaId > 0)
11713 filter.AppendWhere(PrepareSQL("idMovie = %i", mediaId));
11717 else
11719 filter.AppendWhere("isDefaultVersion = 1");
11722 AppendIdLinkFilter("tag", "tag", "movie", "movie", "idMovie", options, filter);
11723 AppendLinkFilter("tag", "tag", "movie", "movie", "idMovie", options, filter);
11725 else if (type == "tvshows")
11727 if (itemType == "tvshows")
11729 AppendIdLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options, filter);
11730 AppendLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options, filter);
11732 AppendIdLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options, filter);
11733 AppendLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options, filter);
11735 AppendIdLinkFilter("director", "actor", "tvshow", "tvshow", "idShow", options, filter);
11737 auto option = options.find("year");
11738 if (option != options.end())
11739 filter.AppendWhere(PrepareSQL("tvshow_view.c%02d like '%%%i%%'", VIDEODB_ID_TV_PREMIERED, (int)option->second.asInteger()));
11741 AppendIdLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options, filter);
11742 AppendLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options, filter);
11744 AppendIdLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options, filter);
11745 AppendLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options, filter);
11747 else if (itemType == "seasons")
11749 auto option = options.find("tvshowid");
11750 if (option != options.end())
11751 filter.AppendWhere(PrepareSQL("season_view.idShow = %i", (int)option->second.asInteger()));
11753 AppendIdLinkFilter("genre", "genre", "tvshow", "season", "idShow", options, filter);
11755 AppendIdLinkFilter("director", "actor", "tvshow", "season", "idShow", options, filter);
11757 option = options.find("year");
11758 if (option != options.end())
11759 filter.AppendWhere(PrepareSQL("season_view.premiered like '%%%i%%'", (int)option->second.asInteger()));
11761 AppendIdLinkFilter("actor", "actor", "tvshow", "season", "idShow", options, filter);
11763 else if (itemType == "episodes")
11765 int idShow = -1;
11766 auto option = options.find("tvshowid");
11767 if (option != options.end())
11768 idShow = (int)option->second.asInteger();
11770 int season = -1;
11771 option = options.find("season");
11772 if (option != options.end())
11773 season = (int)option->second.asInteger();
11775 if (idShow > -1)
11777 bool condition = false;
11779 AppendIdLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options, filter);
11780 AppendLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options, filter);
11782 AppendIdLinkFilter("director", "actor", "tvshow", "episode", "idShow", options, filter);
11783 AppendLinkFilter("director", "actor", "tvshow", "episode", "idShow", options, filter);
11785 option = options.find("year");
11786 if (option != options.end())
11788 condition = true;
11789 filter.AppendWhere(PrepareSQL("episode_view.idShow = %i and episode_view.premiered like '%%%i%%'", idShow, (int)option->second.asInteger()));
11792 AppendIdLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options, filter);
11793 AppendLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options, filter);
11795 if (!condition)
11796 filter.AppendWhere(PrepareSQL("episode_view.idShow = %i", idShow));
11798 if (season > -1)
11800 if (season == 0) // season = 0 indicates a special - we grab all specials here (see below)
11801 filter.AppendWhere(PrepareSQL("episode_view.c%02d = %i", VIDEODB_ID_EPISODE_SEASON, season));
11802 else
11803 filter.AppendWhere(PrepareSQL("(episode_view.c%02d = %i or (episode_view.c%02d = 0 and (episode_view.c%02d = 0 or episode_view.c%02d = %i)))",
11804 VIDEODB_ID_EPISODE_SEASON, season, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTSEASON, season));
11807 else
11809 option = options.find("year");
11810 if (option != options.end())
11811 filter.AppendWhere(PrepareSQL("episode_view.premiered like '%%%i%%'", (int)option->second.asInteger()));
11813 AppendIdLinkFilter("director", "actor", "episode", "episode", "idEpisode", options, filter);
11814 AppendLinkFilter("director", "actor", "episode", "episode", "idEpisode", options, filter);
11818 else if (type == "musicvideos")
11820 AppendIdLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options, filter);
11821 AppendLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options, filter);
11823 AppendIdLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options, filter);
11824 AppendLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options, filter);
11826 AppendIdLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options, filter);
11827 AppendLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options, filter);
11829 auto option = options.find("year");
11830 if (option != options.end())
11831 filter.AppendWhere(PrepareSQL("musicvideo_view.premiered like '%i%%'", (int)option->second.asInteger()));
11833 option = options.find("artistid");
11834 if (option != options.end())
11836 if (itemType != "albums")
11837 filter.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
11838 filter.AppendWhere(PrepareSQL("actor_link.actor_id = %i", (int)option->second.asInteger()));
11841 option = options.find("artist");
11842 if (option != options.end())
11844 if (itemType != "albums")
11846 filter.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
11847 filter.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id=actor_link.actor_id"));
11849 filter.AppendWhere(PrepareSQL("actor.name LIKE '%s'", option->second.asString().c_str()));
11852 option = options.find("albumid");
11853 if (option != options.end())
11854 filter.AppendWhere(PrepareSQL("musicvideo_view.c%02d = (select c%02d from musicvideo where idMVideo = %i)", VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_ALBUM, (int)option->second.asInteger()));
11856 AppendIdLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options, filter);
11857 AppendLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options, filter);
11859 else
11860 return false;
11862 auto option = options.find("xsp");
11863 if (option != options.end())
11865 PLAYLIST::CSmartPlaylist xsp;
11866 if (!xsp.LoadFromJson(option->second.asString()))
11867 return false;
11869 // check if the filter playlist matches the item type
11870 if (xsp.GetType() == itemType ||
11871 (xsp.GetGroup() == itemType && !xsp.IsGroupMixed()) ||
11872 // handle episode listings with videodb://tvshows/titles/ which get the rest
11873 // of the path (season and episodeid) appended later
11874 (xsp.GetType() == "episodes" && itemType == "tvshows"))
11876 std::set<std::string> playlists;
11877 filter.AppendWhere(xsp.GetWhereClause(*this, playlists));
11879 if (xsp.GetLimit() > 0)
11880 sorting.limitEnd = xsp.GetLimit();
11881 if (xsp.GetOrder() != SortByNone)
11882 sorting.sortBy = xsp.GetOrder();
11883 if (xsp.GetOrderDirection() != SortOrderNone)
11884 sorting.sortOrder = xsp.GetOrderDirection();
11885 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING))
11886 sorting.sortAttributes = SortAttributeIgnoreArticle;
11890 option = options.find("filter");
11891 if (option != options.end())
11893 PLAYLIST::CSmartPlaylist xspFilter;
11894 if (!xspFilter.LoadFromJson(option->second.asString()))
11895 return false;
11897 // check if the filter playlist matches the item type
11898 if (xspFilter.GetType() == itemType)
11900 std::set<std::string> playlists;
11901 filter.AppendWhere(xspFilter.GetWhereClause(*this, playlists));
11903 // remove the filter if it doesn't match the item type
11904 else
11905 videoUrl.RemoveOption("filter");
11908 return true;
11911 bool CVideoDatabase::SetVideoUserRating(int dbId, int rating, const MediaType& mediaType)
11915 if (nullptr == m_pDB)
11916 return false;
11917 if (nullptr == m_pDS)
11918 return false;
11920 if (mediaType == MediaTypeNone)
11921 return false;
11923 std::string sql;
11924 if (mediaType == MediaTypeMovie)
11925 sql = PrepareSQL("UPDATE movie SET userrating=%i WHERE idMovie = %i", rating, dbId);
11926 else if (mediaType == MediaTypeEpisode)
11927 sql = PrepareSQL("UPDATE episode SET userrating=%i WHERE idEpisode = %i", rating, dbId);
11928 else if (mediaType == MediaTypeMusicVideo)
11929 sql = PrepareSQL("UPDATE musicvideo SET userrating=%i WHERE idMVideo = %i", rating, dbId);
11930 else if (mediaType == MediaTypeTvShow)
11931 sql = PrepareSQL("UPDATE tvshow SET userrating=%i WHERE idShow = %i", rating, dbId);
11932 else if (mediaType == MediaTypeSeason)
11933 sql = PrepareSQL("UPDATE seasons SET userrating=%i WHERE idSeason = %i", rating, dbId);
11935 m_pDS->exec(sql);
11936 return true;
11938 catch (...)
11940 CLog::Log(LOGERROR, "{} ({}, {}, {}) failed", __FUNCTION__, dbId, mediaType, rating);
11942 return false;
11945 CDateTime CVideoDatabase::GetDateAdded(const std::string& filename,
11946 CDateTime dateAdded /* = CDateTime() */)
11948 if (!dateAdded.IsValid())
11950 // suppress warnings if we have plugin source
11951 if (!URIUtils::IsPlugin(filename))
11953 const auto dateAddedSetting =
11954 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryDateAdded;
11956 // 1 prefer using the files mtime (if it's valid) and
11957 // only use the file's ctime if mtime isn't valid
11958 if (dateAddedSetting == 1)
11959 dateAdded = CFileUtils::GetModificationDate(filename, false);
11960 // 2 use the newer datetime of the file's mtime and ctime
11961 else if (dateAddedSetting == 2)
11962 dateAdded = CFileUtils::GetModificationDate(filename, true);
11965 // 0 use the current datetime if non of the above match or one returns an invalid datetime
11966 if (!dateAdded.IsValid())
11967 dateAdded = CDateTime::GetCurrentDateTime();
11970 return dateAdded;
11973 void CVideoDatabase::EraseAllForPath(const std::string& path)
11977 std::string itemsToDelete;
11978 std::string sql =
11979 PrepareSQL("SELECT files.idFile FROM files WHERE idFile IN (SELECT idFile FROM files INNER "
11980 "JOIN path ON path.idPath = files.idPath AND path.strPath LIKE \"%s%%\")",
11981 path.c_str());
11983 m_pDS->query(sql);
11984 while (!m_pDS->eof())
11986 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
11987 itemsToDelete += file;
11988 m_pDS->next();
11990 m_pDS->close();
11992 sql = PrepareSQL("DELETE FROM path WHERE strPath LIKE \"%s%%\"", path.c_str());
11993 m_pDS->exec(sql);
11995 if (!itemsToDelete.empty())
11997 itemsToDelete = "(" + StringUtils::TrimRight(itemsToDelete, ",") + ")";
11999 sql = "DELETE FROM files WHERE idFile IN " + itemsToDelete;
12000 m_pDS->exec(sql);
12002 sql = "DELETE FROM settings WHERE idFile IN " + itemsToDelete;
12003 m_pDS->exec(sql);
12005 sql = "DELETE FROM bookmark WHERE idFile IN " + itemsToDelete;
12006 m_pDS->exec(sql);
12008 sql = "DELETE FROM streamdetails WHERE idFile IN " + itemsToDelete;
12009 m_pDS->exec(sql);
12012 catch (...)
12014 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12018 void CVideoDatabase::EraseAllForFile(const std::string& fileNameAndPath)
12022 const int fileId{GetFileId(fileNameAndPath)};
12023 if (fileId != -1)
12025 std::string sql = PrepareSQL("DELETE FROM settings WHERE idFile = %i", fileId);
12026 m_pDS->exec(sql);
12028 sql = PrepareSQL("DELETE FROM bookmark WHERE idFile = %i", fileId);
12029 m_pDS->exec(sql);
12031 sql = PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", fileId);
12032 m_pDS->exec(sql);
12034 sql = PrepareSQL("DELETE FROM files WHERE idFile = %i", fileId);
12035 m_pDS->exec(sql);
12037 std::string path;
12038 std::string fileName;
12039 SplitPath(fileNameAndPath, path, fileName);
12040 const int pathId{GetPathId(path)};
12041 if (pathId != -1)
12043 sql = PrepareSQL("DELETE FROM path WHERE idPath = %i "
12044 "AND NOT EXISTS (SELECT 1 FROM files WHERE files.idPath = %i)",
12045 pathId, pathId);
12046 m_pDS->exec(sql);
12050 catch (...)
12052 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12056 std::string CVideoDatabase::GetVideoItemTitle(VideoDbContentType itemType, int dbId)
12058 switch (itemType)
12060 case VideoDbContentType::MOVIES:
12061 return GetMovieTitle(dbId);
12062 default:
12063 return "";
12067 void CVideoDatabase::InitializeVideoVersionTypeTable(int schemaVersion)
12069 assert(m_pDB->in_transaction());
12073 for (int id = VIDEO_VERSION_ID_BEGIN; id <= VIDEO_VERSION_ID_END; ++id)
12075 // Exclude removed pre-populated "quality" values
12076 if (id == 40405 || (id >= 40418 && id <= 40430))
12077 continue;
12079 const std::string& type{g_localizeStrings.Get(id)};
12080 if (schemaVersion < 127)
12082 m_pDS->exec(
12083 PrepareSQL("INSERT INTO videoversiontype (id, name, owner) VALUES(%i, '%s', %i)", id,
12084 type.c_str(), VideoAssetTypeOwner::SYSTEM));
12086 else
12088 m_pDS->exec(PrepareSQL(
12089 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(%i, '%s', %i, %i)", id,
12090 type.c_str(), VideoAssetTypeOwner::SYSTEM, VideoAssetType::VERSION));
12094 catch (...)
12096 CLog::LogF(LOGERROR, "failed");
12097 throw;
12101 void CVideoDatabase::UpdateVideoVersionTypeTable()
12105 BeginTransaction();
12107 for (int id = VIDEO_VERSION_ID_BEGIN; id <= VIDEO_VERSION_ID_END; ++id)
12109 const std::string& type = g_localizeStrings.Get(id);
12110 m_pDS->exec(PrepareSQL("UPDATE videoversiontype SET name = '%s', owner = %i WHERE id = '%i'",
12111 type.c_str(), VideoAssetTypeOwner::SYSTEM, id));
12114 CommitTransaction();
12116 catch (...)
12118 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12119 RollbackTransaction();
12123 int CVideoDatabase::AddVideoVersionType(const std::string& typeVideoVersion,
12124 VideoAssetTypeOwner owner,
12125 VideoAssetType assetType)
12127 if (typeVideoVersion.empty())
12128 return -1;
12130 int id = -1;
12134 if (!m_pDB || !m_pDS)
12135 return -1;
12137 m_pDS->query(PrepareSQL(
12138 "SELECT id, owner, itemType FROM videoversiontype WHERE name = '%s' AND itemtype = %i",
12139 typeVideoVersion.c_str(), assetType));
12140 if (m_pDS->num_rows() == 0)
12142 m_pDS->exec(PrepareSQL("INSERT INTO videoversiontype (id, name, owner, itemType) "
12143 "VALUES(NULL, '%s', %i, %i)",
12144 typeVideoVersion.c_str(), owner, assetType));
12145 id = static_cast<int>(m_pDS->lastinsertid());
12147 else
12149 id = m_pDS->fv("id").get_asInt();
12151 // if user is adding an existing version type, overwrite the existing non-system one
12152 VideoAssetTypeOwner oldOwner =
12153 static_cast<VideoAssetTypeOwner>(m_pDS->fv("owner").get_asInt());
12154 if (oldOwner != VideoAssetTypeOwner::SYSTEM && owner == VideoAssetTypeOwner::USER)
12156 m_pDS->exec(PrepareSQL("UPDATE videoversiontype SET owner = %i WHERE id = %i", owner, id));
12160 catch (...)
12162 CLog::Log(LOGERROR, "{} failed to add video version {}", __FUNCTION__, typeVideoVersion);
12165 return id;
12168 void CVideoDatabase::GetVideoVersions(VideoDbContentType itemType,
12169 int dbId,
12170 CFileItemList& items,
12171 VideoAssetType videoAssetType)
12173 if (!m_pDB || !m_pDS2)
12174 return;
12176 MediaType mediaType;
12178 if (itemType == VideoDbContentType::MOVIES)
12179 mediaType = MediaTypeMovie;
12180 else
12181 return;
12185 m_pDS2->query(PrepareSQL("SELECT videoversiontype.name AS name,"
12186 " videoversiontype.id AS id,"
12187 " videoversion.idFile AS idFile "
12188 "FROM videoversiontype"
12189 " JOIN videoversion ON"
12190 " videoversion.idType = videoversiontype.id "
12191 "WHERE videoversion.idMedia = %i AND videoversion.media_type = '%s' "
12192 "AND videoversion.itemType = %i",
12193 dbId, mediaType.c_str(), videoAssetType));
12195 std::vector<std::tuple<std::string, int, int>> versions;
12197 while (!m_pDS2->eof())
12199 versions.emplace_back(m_pDS2->fv("name").get_asString(), m_pDS2->fv("id").get_asInt(),
12200 m_pDS2->fv("idFile").get_asInt());
12201 m_pDS2->next();
12203 m_pDS2->close();
12205 CFileItem videoItem;
12206 GetDetailsByTypeAndId(videoItem, itemType, dbId);
12208 for (auto& version : versions)
12210 std::string name = std::get<0>(version);
12211 int id = std::get<1>(version);
12212 int idFile = std::get<2>(version);
12214 CVideoInfoTag infoTag;
12215 if (GetFileInfo("", infoTag, idFile))
12217 infoTag.m_type = MediaTypeVideoVersion;
12218 infoTag.m_iDbId = idFile;
12219 infoTag.GetAssetInfo().SetId(id);
12220 infoTag.GetAssetInfo().SetTitle(name);
12221 infoTag.GetAssetInfo().SetType(videoAssetType);
12222 infoTag.m_strTitle = name;
12224 infoTag.m_strPictureURL = videoItem.GetVideoInfoTag()->m_strPictureURL;
12225 infoTag.m_fanart = videoItem.GetVideoInfoTag()->m_fanart;
12227 auto item(std::make_shared<CFileItem>(infoTag));
12228 item->m_strTitle = name;
12229 item->SetLabel(name);
12231 CVideoDbUrl itemUrl;
12232 if (itemUrl.FromString(StringUtils::Format("videodb://{}/videoversions/{}",
12233 CMediaTypes::ToPlural(mediaType), id)))
12235 itemUrl.AddOption("mediaid", dbId);
12236 item->SetPath(itemUrl.ToString());
12239 item->SetDynPath(infoTag.m_strFileNameAndPath);
12241 item->SetOverlayImage(GetPlayCount(idFile) > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
12242 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
12244 items.Add(item);
12248 catch (...)
12250 CLog::Log(LOGERROR, "{} failed for {} {}", __FUNCTION__, mediaType, dbId);
12254 void CVideoDatabase::GetDefaultVideoVersion(VideoDbContentType itemType, int dbId, CFileItem& item)
12256 if (!m_pDB || !m_pDS)
12257 return;
12259 MediaType mediaType;
12260 std::string strSQL;
12262 if (itemType == VideoDbContentType::MOVIES)
12264 mediaType = MediaTypeMovie;
12265 strSQL = PrepareSQL("SELECT videoversiontype.name AS name,"
12266 " videoversiontype.id AS id,"
12267 " videoversion.idFile AS idFile,"
12268 " videoversion.itemType AS itemType "
12269 "FROM videoversiontype"
12270 " JOIN videoversion ON"
12271 " videoversion.idType = videoversiontype.id"
12272 " JOIN movie ON"
12273 " movie.idFile = videoversion.idFile "
12274 "WHERE movie.idMovie = %i",
12275 dbId);
12277 else
12278 return;
12282 m_pDS->query(strSQL);
12284 if (!m_pDS->eof())
12286 std::string name = m_pDS->fv("name").get_asString();
12287 int id = m_pDS->fv("id").get_asInt();
12288 int idFile = m_pDS->fv("idFile").get_asInt();
12289 const auto videoAssetType{static_cast<VideoAssetType>(m_pDS->fv("itemType").get_asInt())};
12290 CVideoInfoTag infoTag;
12291 if (GetFileInfo("", infoTag, idFile))
12293 infoTag.m_type = MediaTypeVideoVersion;
12294 infoTag.m_iDbId = idFile;
12295 infoTag.GetAssetInfo().SetId(id);
12296 infoTag.GetAssetInfo().SetTitle(name);
12297 infoTag.GetAssetInfo().SetType(videoAssetType);
12298 infoTag.m_strTitle = name;
12300 item.SetFromVideoInfoTag(infoTag);
12301 item.m_strTitle = name;
12302 item.SetLabel(name);
12305 m_pDS->close();
12307 catch (...)
12309 CLog::Log(LOGERROR, "{} failed for {} {}", __FUNCTION__, mediaType, dbId);
12313 bool CVideoDatabase::UpdateAssetsOwner(const std::string& mediaType, int dbIdSource, int dbIdTarget)
12315 if (dbIdSource != dbIdTarget)
12317 return ExecuteQuery(
12318 PrepareSQL("UPDATE videoversion SET idMedia = %i WHERE idMedia = %i AND media_type = '%s'",
12319 dbIdTarget, dbIdSource, mediaType.c_str()));
12321 return true;
12324 bool CVideoDatabase::FillMovieItem(std::unique_ptr<Dataset>& dataset, int movieId, CFileItem& item)
12326 CVideoInfoTag infoTag{GetDetailsForMovie(dataset)};
12327 if (infoTag.IsEmpty())
12329 CLog::LogF(LOGERROR, "Unable to fill movie item with id '{}'!", movieId);
12330 return false;
12333 item.SetFromVideoInfoTag(infoTag);
12335 CVideoDbUrl itemUrl;
12336 itemUrl.FromString(
12337 StringUtils::Format("videodb://movies/videoversions/{}", infoTag.GetAssetInfo().GetId()));
12338 itemUrl.AppendPath(std::to_string(movieId));
12339 itemUrl.AddOption("mediaid", movieId);
12340 item.SetPath(itemUrl.ToString());
12341 item.SetDynPath(infoTag.m_strFileNameAndPath);
12342 return true;
12345 bool CVideoDatabase::GetAssetsForVideo(VideoDbContentType itemType,
12346 int mediaId,
12347 VideoAssetType assetType,
12348 CFileItemList& items)
12350 if (assetType != VideoAssetType::VERSION)
12352 //! @todo add bool return type to GetVideoVersions
12353 GetVideoVersions(itemType, mediaId, items, assetType);
12354 return true;
12357 if (!m_pDB || !m_pDS)
12358 return false;
12360 MediaType mediaType;
12362 if (itemType == VideoDbContentType::MOVIES)
12363 mediaType = MediaTypeMovie;
12364 else
12366 CLog::LogF(LOGERROR, "Unsupported item type '{}'!", static_cast<int>(itemType));
12367 return false;
12372 m_pDS->query(
12373 PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND videoVersionTypeItemType = %i",
12374 mediaId, assetType));
12376 if (m_pDS->eof())
12378 CLog::LogF(LOGERROR, "Query returned no data!");
12379 return false;
12382 while (!m_pDS->eof())
12384 const auto item{std::make_shared<CFileItem>()};
12385 if (FillMovieItem(m_pDS, mediaId, *item))
12386 items.Add(item);
12388 m_pDS->next();
12390 m_pDS->close();
12392 catch (...)
12394 CLog::LogF(LOGERROR, "Execution failed for {} {}", mediaType, mediaId);
12395 return false;
12397 return true;
12400 bool CVideoDatabase::GetDefaultVersionForVideo(VideoDbContentType itemType,
12401 int mediaId,
12402 CFileItem& item)
12404 if (!m_pDB || !m_pDS)
12405 return false;
12407 MediaType mediaType;
12409 if (itemType == VideoDbContentType::MOVIES)
12410 mediaType = MediaTypeMovie;
12411 else
12413 CLog::LogF(LOGERROR, "Unsupported item type '{}'!", static_cast<int>(itemType));
12414 return false;
12419 m_pDS->query(PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND "
12420 "videoVersionTypeItemType = %i AND isDefaultVersion = 1",
12421 mediaId, VideoAssetType::VERSION));
12422 if (m_pDS->eof())
12424 CLog::LogF(LOGERROR, "Query returned no data!");
12425 return false;
12428 if (!FillMovieItem(m_pDS, mediaId, item))
12429 return false;
12431 catch (...)
12433 CLog::LogF(LOGERROR, "Execution failed for {} {}", mediaType, mediaId);
12434 return false;
12436 return true;
12439 bool CVideoDatabase::ConvertVideoToVersion(VideoDbContentType itemType,
12440 int dbIdSource,
12441 int dbIdTarget,
12442 int idVideoVersion,
12443 VideoAssetType assetType)
12445 int idFile = -1;
12446 MediaType mediaType;
12447 VideoContentTypeToString(itemType, mediaType);
12449 if (itemType == VideoDbContentType::MOVIES)
12451 idFile = GetFileIdByMovie(dbIdSource);
12453 else
12454 return false;
12456 if (idFile < 0)
12457 return false;
12459 BeginTransaction();
12461 if (dbIdSource != dbIdTarget)
12463 // Transfer all assets (versions, extras,...) to the new movie.
12464 UpdateAssetsOwner(mediaType, dbIdSource, dbIdTarget);
12466 // version-level art doesn't need any change.
12467 // 'movie' art is converted to 'videoversion' art.
12468 SetVideoVersionDefaultArt(idFile, dbIdSource, itemType);
12470 if (itemType == VideoDbContentType::MOVIES)
12471 DeleteMovie(dbIdSource, DeleteMovieCascadeAction::ALL_ASSETS,
12472 DeleteMovieHashAction::HASH_PRESERVE);
12475 // Rename the default version
12476 ExecuteQuery(PrepareSQL("UPDATE videoversion SET idType = %i, itemType = %i WHERE idFile = %i",
12477 idVideoVersion, assetType, idFile));
12479 CommitTransaction();
12481 return true;
12484 void CVideoDatabase::SetDefaultVideoVersion(VideoDbContentType itemType, int dbId, int idFile)
12486 if (!m_pDB || !m_pDS)
12487 return;
12489 std::string path = GetFileBasePathById(idFile);
12490 if (path.empty())
12491 return;
12495 if (itemType == VideoDbContentType::MOVIES)
12496 m_pDS->exec(PrepareSQL("UPDATE movie SET idFile = %i, c%02d = '%s' WHERE idMovie = %i",
12497 idFile, VIDEODB_ID_BASEPATH, path.c_str(), dbId));
12499 catch (...)
12501 CLog::Log(LOGERROR, "{} failed for video {}", __FUNCTION__, dbId);
12505 bool CVideoDatabase::IsDefaultVideoVersion(int idFile)
12507 if (!m_pDB || !m_pDS)
12508 return false;
12512 m_pDS->query(
12513 PrepareSQL("SELECT idMedia, media_type FROM videoversion WHERE idFile = %i", idFile));
12514 if (m_pDS->num_rows() > 0)
12516 int idMedia = m_pDS->fv("idMedia").get_asInt();
12517 std::string mediaType = m_pDS->fv("media_type").get_asString();
12519 if (mediaType == MediaTypeMovie)
12521 m_pDS->query(PrepareSQL("SELECT idFile FROM movie WHERE idMovie = %i", idMedia));
12522 if (m_pDS->num_rows() > 0)
12524 if (m_pDS->fv("idFile").get_asInt() == idFile)
12525 return true;
12530 catch (...)
12532 CLog::Log(LOGERROR, "{} failed for {}", __FUNCTION__, idFile);
12535 return false;
12538 bool CVideoDatabase::DeleteVideoAsset(int idFile)
12540 if (!m_pDB || !m_pDS)
12541 return false;
12543 if (IsDefaultVideoVersion(idFile))
12544 return false;
12546 const bool inTransaction{m_pDB->in_transaction()};
12550 if (!inTransaction)
12551 BeginTransaction();
12553 const std::string path = GetSingleValue(PrepareSQL(
12554 "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i",
12555 idFile));
12556 if (!path.empty())
12557 InvalidatePathHash(path);
12559 m_pDS->exec(PrepareSQL("DELETE FROM videoversion WHERE idFile=%i", idFile));
12561 if (!inTransaction)
12562 CommitTransaction();
12564 return true;
12566 catch (...)
12568 CLog::LogF(LOGERROR, "failed for {}", idFile);
12569 if (!inTransaction)
12570 RollbackTransaction();
12571 return false;
12575 void CVideoDatabase::SetVideoVersion(int idFile, int idVideoVersion)
12577 if (!m_pDB || !m_pDS)
12578 return;
12582 m_pDS->exec(PrepareSQL("UPDATE videoversion SET idType = %i WHERE idFile = %i", idVideoVersion,
12583 idFile));
12585 catch (...)
12587 CLog::Log(LOGERROR, "{} failed for video {}", __FUNCTION__, idFile);
12591 bool CVideoDatabase::AddVideoAsset(VideoDbContentType itemType,
12592 int dbId,
12593 int idVideoAsset,
12594 VideoAssetType videoAssetType,
12595 CFileItem& item)
12597 if (!m_pDB || !m_pDS)
12598 return false;
12600 assert(m_pDB->in_transaction() == false);
12602 if (itemType != VideoDbContentType::MOVIES)
12603 return false;
12605 MediaType mediaType;
12606 VideoContentTypeToString(itemType, mediaType);
12608 int idFile = AddFile(item.GetPath());
12609 if (idFile < 0)
12610 return false;
12614 BeginTransaction();
12616 m_pDS->query(PrepareSQL("SELECT idFile FROM videoversion WHERE idFile = %i", idFile));
12618 if (m_pDS->num_rows() == 0)
12619 m_pDS->exec(
12620 PrepareSQL("INSERT INTO videoversion (idFile, idMedia, media_type, itemType, idType) "
12621 "VALUES(%i, %i, '%s', %i, %i)",
12622 idFile, dbId, mediaType.c_str(), videoAssetType, idVideoAsset));
12623 else
12624 m_pDS->exec(PrepareSQL("UPDATE videoversion "
12625 "SET idMedia = %i, media_type = '%s', itemType = %i, idType = %i "
12626 "WHERE idFile = %i",
12627 dbId, mediaType.c_str(), videoAssetType, idVideoAsset, idFile));
12629 if (item.GetVideoInfoTag()->HasStreamDetails())
12630 SetStreamDetailsForFileId(item.GetVideoInfoTag()->m_streamDetails, idFile);
12632 if (videoAssetType == VideoAssetType::VERSION)
12633 SetVideoVersionDefaultArt(idFile, item.GetVideoInfoTag()->m_iDbId, itemType);
12635 SetArtForItem(idFile, MediaTypeVideoVersion, item.GetArt());
12637 CommitTransaction();
12639 return true;
12641 catch (...)
12643 CLog::LogF(LOGERROR, "failed for video {}", dbId);
12644 RollbackTransaction();
12645 return false;
12649 VideoAssetInfo CVideoDatabase::GetVideoVersionInfo(const std::string& filenameAndPath)
12651 VideoAssetInfo info;
12653 info.m_idFile = GetFileId(filenameAndPath);
12654 if (info.m_idFile < 0)
12655 return info;
12657 if (!m_pDB || !m_pDS)
12658 return info;
12662 m_pDS->query(PrepareSQL("SELECT videoversiontype.name,"
12663 " videoversiontype.id,"
12664 " videoversion.idMedia,"
12665 " videoversion.media_type,"
12666 " videoversion.itemType "
12667 "FROM videoversion"
12668 " JOIN videoversiontype ON "
12669 " videoversiontype.id = videoversion.idType "
12670 "WHERE videoversion.idFile = %i",
12671 info.m_idFile));
12673 if (m_pDS->num_rows() > 0)
12675 info.m_assetTypeId = m_pDS->fv("id").get_asInt();
12676 info.m_assetTypeName = m_pDS->fv("name").get_asString();
12677 info.m_idMedia = m_pDS->fv("idMedia").get_asInt();
12678 info.m_mediaType = m_pDS->fv("media_type").get_asString();
12679 info.m_assetType = static_cast<VideoAssetType>(m_pDS->fv("itemType").get_asInt());
12682 m_pDS->close();
12684 catch (...)
12686 CLog::LogF(LOGERROR, "failed for {}", filenameAndPath);
12689 return info;
12692 bool CVideoDatabase::GetVideoVersionsNav(const std::string& strBaseDir,
12693 CFileItemList& items,
12694 VideoDbContentType idContent /* = UNKNOWN */,
12695 const Filter& filter /* = Filter() */)
12697 if (!m_pDB || !m_pDS)
12698 return false;
12700 MediaType mediaType;
12702 if (idContent == VideoDbContentType::MOVIES)
12704 mediaType = MediaTypeMovie;
12706 else
12707 return false;
12709 CVideoDbUrl videoUrl;
12710 if (!videoUrl.FromString(strBaseDir))
12711 return false;
12715 m_pDS->query(PrepareSQL(
12716 "SELECT DISTINCT videoversiontype.name AS name,"
12717 " videoversiontype.id AS id "
12718 "FROM videoversiontype"
12719 " JOIN videoversion ON"
12720 " videoversion.idType = videoversiontype.id "
12721 "WHERE name != '' AND owner IN (%i, %i) AND videoversiontype.itemType = %i",
12722 VideoAssetTypeOwner::SYSTEM, VideoAssetTypeOwner::USER, VideoAssetType::VERSION));
12724 while (!m_pDS->eof())
12726 const int id{m_pDS->fv("id").get_asInt()};
12728 CVideoDbUrl itemUrl{videoUrl};
12729 itemUrl.AppendPath(StringUtils::Format("{}/", id));
12731 const auto item{std::make_shared<CFileItem>(itemUrl.ToString(), true)};
12732 item->SetLabel(m_pDS->fv("name").get_asString());
12733 auto tag{item->GetVideoInfoTag()};
12734 tag->m_type = MediaTypeVideoVersion;
12735 tag->m_iDbId = id;
12737 items.Add(item);
12738 m_pDS->next();
12740 m_pDS->close();
12741 return true;
12743 catch (...)
12745 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12747 return false;
12750 bool CVideoDatabase::GetVideoVersionTypes(VideoDbContentType idContent,
12751 VideoAssetType assetType,
12752 CFileItemList& items)
12754 if (!m_pDB || !m_pDS)
12755 return false;
12757 MediaType mediaType;
12759 if (idContent == VideoDbContentType::MOVIES)
12761 mediaType = MediaTypeMovie;
12763 else
12764 return false;
12768 m_pDS->query(
12769 PrepareSQL("SELECT name, id FROM videoversiontype WHERE name != '' AND itemType = %i "
12770 "AND owner IN (%i, %i)",
12771 assetType, VideoAssetTypeOwner::SYSTEM, VideoAssetTypeOwner::USER));
12773 while (!m_pDS->eof())
12775 std::string name = m_pDS->fv("name").get_asString();
12776 int id = m_pDS->fv("id").get_asInt();
12778 const auto item{std::make_shared<CFileItem>(name)};
12779 item->GetVideoInfoTag()->m_type = MediaTypeVideoVersion;
12780 item->GetVideoInfoTag()->m_iDbId = id;
12781 item->GetVideoInfoTag()->GetAssetInfo().SetId(id);
12782 item->GetVideoInfoTag()->GetAssetInfo().SetTitle(name);
12783 item->GetVideoInfoTag()->m_strTitle = name;
12785 item->m_strTitle = name;
12786 item->SetLabel(name);
12788 items.Add(item);
12789 m_pDS->next();
12791 m_pDS->close();
12792 return true;
12794 catch (...)
12796 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12798 return false;
12801 std::string CVideoDatabase::GetVideoVersionById(int id)
12803 return GetSingleValue(PrepareSQL("SELECT name FROM videoversiontype WHERE id=%i", id), m_pDS2);
12806 void CVideoDatabase::SetVideoVersionDefaultArt(int dbId, int idFrom, VideoDbContentType type)
12808 MediaType mediaType;
12809 VideoContentTypeToString(type, mediaType);
12811 std::map<std::string, std::string> art;
12812 if (GetArtForItem(idFrom, mediaType, art))
12814 for (const auto& it : art)
12815 SetArtForItem(dbId, MediaTypeVideoVersion, it.first, it.second);
12819 std::vector<std::string> CVideoDatabase::GetUsedImages(
12820 const std::vector<std::string>& imagesToCheck)
12824 if (!m_pDB || !m_pDS)
12825 return imagesToCheck;
12827 if (!imagesToCheck.size())
12828 return {};
12830 int artworkLevel = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
12831 CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL);
12832 if (artworkLevel == CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_NONE)
12834 return {};
12837 // first check the art table
12839 std::string sql = "SELECT DISTINCT url FROM art WHERE url IN (";
12840 for (const auto& image : imagesToCheck)
12842 sql += PrepareSQL("'%s',", image.c_str());
12844 sql.pop_back(); // remove last ','
12845 sql += ")";
12847 // add arttype filters if not set to "Maximum"
12848 if (artworkLevel != CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_ALL)
12850 static std::array<std::string, 7> mediatypes = {
12851 MediaTypeEpisode, MediaTypeTvShow, MediaTypeSeason, MediaTypeMovie,
12852 MediaTypeVideoCollection, MediaTypeMusicVideo, MediaTypeVideoVersion};
12854 std::string arttypeSQL;
12855 for (const auto& mediatype : mediatypes)
12857 const auto& arttypes = CVideoThumbLoader::GetArtTypes(mediatype);
12858 if (arttypes.empty())
12859 continue;
12861 if (!arttypeSQL.empty())
12862 arttypeSQL += ") OR ";
12863 arttypeSQL += PrepareSQL("media_type = '%s' AND (", mediatype.c_str());
12864 bool workingNext = false;
12865 for (const auto& arttype : arttypes)
12867 if (workingNext)
12868 arttypeSQL += " OR ";
12869 workingNext = true;
12870 if (artworkLevel == CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_BASIC)
12872 // for basic match exact artwork type
12873 arttypeSQL += PrepareSQL("type = '%s'", arttype.c_str());
12875 else
12877 // otherwise check for arttype 'families', like fanart, fanart1, fanart13;
12878 // still avoid most "happens to start with" like fanartstuff
12879 arttypeSQL +=
12880 PrepareSQL("type BETWEEN '%s' AND '%s999'", arttype.c_str(), arttype.c_str());
12885 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
12886 CSettings::SETTING_VIDEOLIBRARY_ACTORTHUMBS))
12888 if (!arttypeSQL.empty())
12889 arttypeSQL += ") OR ";
12890 arttypeSQL += "media_type = 'actor'";
12893 if (!arttypeSQL.empty())
12894 sql += " AND (" + arttypeSQL + ")";
12897 std::vector<std::string> result;
12898 if (m_pDS->query(sql))
12900 while (!m_pDS->eof())
12902 result.push_back(m_pDS->fv(0).get_asString());
12903 m_pDS->next();
12905 m_pDS->close();
12908 // then check any chapter thumbnails against path and file tables
12910 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
12911 CSettings::SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS))
12913 std::vector<std::string> foundVideoFiles;
12914 for (const auto& image : imagesToCheck)
12916 auto imageFile = IMAGE_FILES::CImageFileURL(image);
12917 if (imageFile.GetSpecialType() == "video" && !imageFile.GetOption("chapter").empty())
12919 const auto& target = imageFile.GetTargetFile();
12920 auto quickFind = std::find(foundVideoFiles.begin(), foundVideoFiles.end(), target);
12921 if (quickFind != foundVideoFiles.end())
12923 result.push_back(image);
12925 else
12927 int fileId = GetFileId(target);
12928 if (fileId != -1)
12930 result.push_back(image);
12931 foundVideoFiles.push_back(target);
12938 return result;
12940 catch (...)
12942 CLog::LogF(LOGERROR, "failed");
12944 return {};