[videodb] Remove nested transaction when saving state after stopping PVR playback
[xbmc.git] / xbmc / video / VideoDatabase.cpp
blob892322416343a6cc8349881597c2de9a69592633
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(CFileItem& item)
4228 // Note that this function (possibly) creates VideoInfoTags for items that don't have one yet!
4229 int fileId = -1;
4231 if (item.HasVideoInfoTag())
4232 fileId = item.GetVideoInfoTag()->m_iFileId;
4233 if (fileId < 0)
4234 fileId = GetFileId(item);
4236 if (fileId < 0)
4237 return false;
4239 // Have a file id, get stream details if available (creates tag either way)
4240 item.GetVideoInfoTag()->m_iFileId = fileId;
4241 return GetStreamDetails(*item.GetVideoInfoTag());
4244 bool CVideoDatabase::GetStreamDetails(CVideoInfoTag& tag)
4247 const std::string path = tag.m_strFileNameAndPath;
4248 int fileId{-1};
4249 if (URIUtils::GetExtension(path) == ".mpls")
4250 fileId = GetFileId(path);
4251 else
4252 fileId = tag.m_iFileId;
4254 if (fileId < 0)
4255 return false;
4257 bool retVal = false;
4259 CStreamDetails& details = tag.m_streamDetails;
4260 details.Reset();
4262 std::unique_ptr<Dataset> pDS(m_pDB->CreateDataset());
4265 std::string strSQL = PrepareSQL("SELECT * FROM streamdetails WHERE idFile = %i", fileId);
4266 pDS->query(strSQL);
4268 while (!pDS->eof())
4270 CStreamDetail::StreamType e = (CStreamDetail::StreamType)pDS->fv(1).get_asInt();
4271 switch (e)
4273 case CStreamDetail::VIDEO:
4275 CStreamDetailVideo *p = new CStreamDetailVideo();
4276 p->m_strCodec = pDS->fv(2).get_asString();
4277 p->m_fAspect = pDS->fv(3).get_asFloat();
4278 p->m_iWidth = pDS->fv(4).get_asInt();
4279 p->m_iHeight = pDS->fv(5).get_asInt();
4280 p->m_iDuration = pDS->fv(10).get_asInt();
4281 p->m_strStereoMode = pDS->fv(11).get_asString();
4282 p->m_strLanguage = pDS->fv(12).get_asString();
4283 p->m_strHdrType = pDS->fv(13).get_asString();
4284 details.AddStream(p);
4285 retVal = true;
4286 break;
4288 case CStreamDetail::AUDIO:
4290 CStreamDetailAudio *p = new CStreamDetailAudio();
4291 p->m_strCodec = pDS->fv(6).get_asString();
4292 if (pDS->fv(7).get_isNull())
4293 p->m_iChannels = -1;
4294 else
4295 p->m_iChannels = pDS->fv(7).get_asInt();
4296 p->m_strLanguage = pDS->fv(8).get_asString();
4297 details.AddStream(p);
4298 retVal = true;
4299 break;
4301 case CStreamDetail::SUBTITLE:
4303 CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
4304 p->m_strLanguage = pDS->fv(9).get_asString();
4305 details.AddStream(p);
4306 retVal = true;
4307 break;
4311 pDS->next();
4314 pDS->close();
4316 catch (...)
4318 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, tag.m_iFileId);
4320 details.DetermineBestStreams();
4322 if (details.GetVideoDuration() > 0)
4323 tag.SetDuration(details.GetVideoDuration());
4325 return retVal;
4328 bool CVideoDatabase::GetResumePoint(CVideoInfoTag& tag)
4330 if (tag.m_iFileId < 0)
4331 return false;
4333 bool match = false;
4337 if (URIUtils::IsDiscImageStack(tag.m_strFileNameAndPath))
4339 CStackDirectory dir;
4340 CFileItemList fileList;
4341 const CURL pathToUrl(tag.m_strFileNameAndPath);
4342 dir.GetDirectory(pathToUrl, fileList);
4343 tag.SetResumePoint(CBookmark());
4344 for (int i = fileList.Size() - 1; i >= 0; i--)
4346 CBookmark bookmark;
4347 if (GetResumeBookMark(fileList[i]->GetPath(), bookmark))
4349 bookmark.partNumber = (i+1); /* store part number in here */
4350 tag.SetResumePoint(bookmark);
4351 match = true;
4352 break;
4356 else
4358 std::string strSQL=PrepareSQL("select timeInSeconds, totalTimeInSeconds from bookmark where idFile=%i and type=%i order by timeInSeconds", tag.m_iFileId, CBookmark::RESUME);
4359 m_pDS2->query( strSQL );
4360 if (!m_pDS2->eof())
4362 tag.SetResumePoint(m_pDS2->fv(0).get_asDouble(), m_pDS2->fv(1).get_asDouble(), "");
4363 match = true;
4365 m_pDS2->close();
4368 catch (...)
4370 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, tag.m_strFileNameAndPath);
4373 return match;
4376 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4378 return GetDetailsForMovie(pDS->get_sql_record(), getDetails);
4381 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4383 CVideoInfoTag details;
4385 if (record == NULL)
4386 return details;
4388 int idMovie = record->at(0).get_asInt();
4390 GetDetailsFromDB(record, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets, details);
4392 details.m_iDbId = idMovie;
4393 details.m_type = MediaTypeMovie;
4394 details.SetHasVideoVersions(record->at(VIDEODB_DETAILS_MOVIE_HASVERSIONS).get_asBool());
4395 details.SetHasVideoExtras(record->at(VIDEODB_DETAILS_MOVIE_HASEXTRAS).get_asBool());
4396 details.SetIsDefaultVideoVersion(record->at(VIDEODB_DETAILS_MOVIE_ISDEFAULTVERSION).get_asBool());
4397 auto& versionInfo = details.GetAssetInfo();
4398 versionInfo.SetId(record->at(VIDEODB_DETAILS_MOVIE_VERSION_TYPEID).get_asInt());
4399 versionInfo.SetTitle(record->at(VIDEODB_DETAILS_MOVIE_VERSION_TYPENAME).get_asString());
4400 versionInfo.SetType(
4401 static_cast<VideoAssetType>(record->at(VIDEODB_DETAILS_MOVIE_VERSION_ITEMTYPE).get_asInt()));
4402 details.m_set.id = record->at(VIDEODB_DETAILS_MOVIE_SET_ID).get_asInt();
4403 details.m_set.title = record->at(VIDEODB_DETAILS_MOVIE_SET_NAME).get_asString();
4404 details.m_set.overview = record->at(VIDEODB_DETAILS_MOVIE_SET_OVERVIEW).get_asString();
4405 details.m_iFileId = record->at(VIDEODB_DETAILS_MOVIE_VERSION_FILEID).get_asInt();
4406 details.m_strPath = record->at(VIDEODB_DETAILS_MOVIE_PATH).get_asString();
4407 std::string strFileName = record->at(VIDEODB_DETAILS_MOVIE_FILE).get_asString();
4408 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4409 details.SetPlayCount(record->at(VIDEODB_DETAILS_MOVIE_PLAYCOUNT).get_asInt());
4410 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_LASTPLAYED).get_asString());
4411 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_DATEADDED).get_asString());
4412 details.SetResumePoint(record->at(VIDEODB_DETAILS_MOVIE_RESUME_TIME).get_asInt(),
4413 record->at(VIDEODB_DETAILS_MOVIE_TOTAL_TIME).get_asInt(),
4414 record->at(VIDEODB_DETAILS_MOVIE_PLAYER_STATE).get_asString());
4415 details.m_iUserRating = record->at(VIDEODB_DETAILS_MOVIE_USER_RATING).get_asInt();
4416 details.SetRating(record->at(VIDEODB_DETAILS_MOVIE_RATING).get_asFloat(),
4417 record->at(VIDEODB_DETAILS_MOVIE_VOTES).get_asInt(),
4418 record->at(VIDEODB_DETAILS_MOVIE_RATING_TYPE).get_asString(), true);
4419 details.SetUniqueID(record->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_TYPE).get_asString() ,true);
4420 std::string premieredString = record->at(VIDEODB_DETAILS_MOVIE_PREMIERED).get_asString();
4421 if (premieredString.size() == 4)
4422 details.SetYear(record->at(VIDEODB_DETAILS_MOVIE_PREMIERED).get_asInt());
4423 else
4424 details.SetPremieredFromDBDate(premieredString);
4426 if (getDetails)
4428 if (getDetails & VideoDbDetailsCast)
4429 GetCast(details.m_iDbId, MediaTypeMovie, details.m_cast);
4431 if (getDetails & VideoDbDetailsTag)
4432 GetTags(details.m_iDbId, MediaTypeMovie, details.m_tags);
4434 if (getDetails & VideoDbDetailsRating)
4435 GetRatings(details.m_iDbId, MediaTypeMovie, details.m_ratings);
4437 if (getDetails & VideoDbDetailsUniqueID)
4438 GetUniqueIDs(details.m_iDbId, MediaTypeMovie, details);
4440 if (getDetails & VideoDbDetailsShowLink)
4442 // create tvshowlink string
4443 std::vector<int> links;
4444 GetLinksToTvShow(idMovie, links);
4445 for (unsigned int i = 0; i < links.size(); ++i)
4447 std::string strSQL = PrepareSQL("select c%02d from tvshow where idShow=%i",
4448 VIDEODB_ID_TV_TITLE, links[i]);
4449 m_pDS2->query(strSQL);
4450 if (!m_pDS2->eof())
4451 details.m_showLink.emplace_back(m_pDS2->fv(0).get_asString());
4453 m_pDS2->close();
4456 if (getDetails & VideoDbDetailsStream)
4457 GetStreamDetails(details);
4459 details.m_parsedDetails = getDetails;
4461 return details;
4464 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */, CFileItem* item /* = NULL */)
4466 return GetDetailsForTvShow(pDS->get_sql_record(), getDetails, item);
4469 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */, CFileItem* item /* = NULL */)
4471 CVideoInfoTag details;
4473 if (record == NULL)
4474 return details;
4476 int idTvShow = record->at(0).get_asInt();
4478 GetDetailsFromDB(record, VIDEODB_ID_TV_MIN, VIDEODB_ID_TV_MAX, DbTvShowOffsets, details, 1);
4479 details.m_bHasPremiered = details.m_premiered.IsValid();
4480 details.m_iDbId = idTvShow;
4481 details.m_type = MediaTypeTvShow;
4482 details.m_strPath = record->at(VIDEODB_DETAILS_TVSHOW_PATH).get_asString();
4483 details.m_basePath = details.m_strPath;
4484 details.m_parentPathID = record->at(VIDEODB_DETAILS_TVSHOW_PARENTPATHID).get_asInt();
4485 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_DATEADDED).get_asString());
4486 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_LASTPLAYED).get_asString());
4487 details.m_iSeason = record->at(VIDEODB_DETAILS_TVSHOW_NUM_SEASONS).get_asInt();
4488 details.m_iEpisode = record->at(VIDEODB_DETAILS_TVSHOW_NUM_EPISODES).get_asInt();
4489 details.SetPlayCount(record->at(VIDEODB_DETAILS_TVSHOW_NUM_WATCHED).get_asInt());
4490 details.m_strShowTitle = details.m_strTitle;
4491 details.m_iUserRating = record->at(VIDEODB_DETAILS_TVSHOW_USER_RATING).get_asInt();
4492 details.SetRating(record->at(VIDEODB_DETAILS_TVSHOW_RATING).get_asFloat(),
4493 record->at(VIDEODB_DETAILS_TVSHOW_VOTES).get_asInt(),
4494 record->at(VIDEODB_DETAILS_TVSHOW_RATING_TYPE).get_asString(), true);
4495 details.SetUniqueID(record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_TYPE).get_asString(), true);
4496 details.SetDuration(record->at(VIDEODB_DETAILS_TVSHOW_DURATION).get_asInt());
4498 //! @todo videotag member + guiinfo int needed?
4499 //! -- Currently not needed; having it available as item prop seems sufficient for skinning
4500 const int inProgressEpisodes = record->at(VIDEODB_DETAILS_TVSHOW_NUM_INPROGRESS).get_asInt();
4502 if (getDetails)
4504 if (getDetails & VideoDbDetailsCast)
4506 GetCast(details.m_iDbId, "tvshow", details.m_cast);
4509 if (getDetails & VideoDbDetailsTag)
4510 GetTags(details.m_iDbId, MediaTypeTvShow, details.m_tags);
4512 if (getDetails & VideoDbDetailsRating)
4513 GetRatings(details.m_iDbId, MediaTypeTvShow, details.m_ratings);
4515 if (getDetails & VideoDbDetailsUniqueID)
4516 GetUniqueIDs(details.m_iDbId, MediaTypeTvShow, details);
4518 details.m_parsedDetails = getDetails;
4521 if (item != NULL)
4523 item->m_dateTime = details.GetPremiered();
4524 item->SetProperty("totalseasons", details.m_iSeason);
4525 item->SetProperty("totalepisodes", details.m_iEpisode);
4526 item->SetProperty("numepisodes", details.m_iEpisode); // will be changed later to reflect watchmode setting
4527 item->SetProperty("watchedepisodes", details.GetPlayCount());
4528 item->SetProperty("unwatchedepisodes", details.m_iEpisode - details.GetPlayCount());
4529 item->SetProperty("inprogressepisodes", inProgressEpisodes);
4530 item->SetProperty("watchedepisodepercent",
4531 details.m_iEpisode > 0 ? (details.GetPlayCount() * 100 / details.m_iEpisode)
4532 : 0);
4534 details.SetPlayCount((details.m_iEpisode <= details.GetPlayCount()) ? 1 : 0);
4536 return details;
4539 CVideoInfoTag CVideoDatabase::GetBasicDetailsForEpisode(std::unique_ptr<Dataset> &pDS)
4541 return GetBasicDetailsForEpisode(pDS->get_sql_record());
4544 CVideoInfoTag CVideoDatabase::GetBasicDetailsForEpisode(const dbiplus::sql_record* const record)
4546 CVideoInfoTag details;
4548 if (record == nullptr)
4549 return details;
4551 int idEpisode = record->at(0).get_asInt();
4553 GetDetailsFromDB(record, VIDEODB_ID_EPISODE_MIN, VIDEODB_ID_EPISODE_MAX, DbEpisodeOffsets, details);
4554 details.m_iDbId = idEpisode;
4555 details.m_type = MediaTypeEpisode;
4556 details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4557 details.m_iIdShow = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt();
4558 details.m_iIdSeason = record->at(VIDEODB_DETAILS_EPISODE_SEASON_ID).get_asInt();
4559 details.m_iUserRating = record->at(VIDEODB_DETAILS_EPISODE_USER_RATING).get_asInt();
4561 return details;
4564 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4566 return GetDetailsForEpisode(pDS->get_sql_record(), getDetails);
4569 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4571 CVideoInfoTag details;
4573 if (record == nullptr)
4574 return details;
4576 details = GetBasicDetailsForEpisode(record);
4578 details.m_strPath = record->at(VIDEODB_DETAILS_EPISODE_PATH).get_asString();
4579 std::string strFileName = record->at(VIDEODB_DETAILS_EPISODE_FILE).get_asString();
4580 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4581 details.SetPlayCount(record->at(VIDEODB_DETAILS_EPISODE_PLAYCOUNT).get_asInt());
4582 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_LASTPLAYED).get_asString());
4583 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_DATEADDED).get_asString());
4584 details.m_strMPAARating = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_MPAA).get_asString();
4585 details.m_strShowTitle = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_NAME).get_asString();
4586 details.m_genre = StringUtils::Split(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_GENRE).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4587 details.m_studio = StringUtils::Split(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_STUDIO).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4588 details.SetPremieredFromDBDate(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_AIRED).get_asString());
4590 details.SetResumePoint(record->at(VIDEODB_DETAILS_EPISODE_RESUME_TIME).get_asInt(),
4591 record->at(VIDEODB_DETAILS_EPISODE_TOTAL_TIME).get_asInt(),
4592 record->at(VIDEODB_DETAILS_EPISODE_PLAYER_STATE).get_asString());
4594 details.SetRating(record->at(VIDEODB_DETAILS_EPISODE_RATING).get_asFloat(),
4595 record->at(VIDEODB_DETAILS_EPISODE_VOTES).get_asInt(),
4596 record->at(VIDEODB_DETAILS_EPISODE_RATING_TYPE).get_asString(), true);
4597 details.SetUniqueID(record->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_TYPE).get_asString(), true);
4599 if (getDetails)
4601 if (getDetails & VideoDbDetailsCast)
4603 GetCast(details.m_iDbId, MediaTypeEpisode, details.m_cast);
4604 GetCast(details.m_iIdShow, MediaTypeTvShow, details.m_cast);
4607 if (getDetails & VideoDbDetailsRating)
4608 GetRatings(details.m_iDbId, MediaTypeEpisode, details.m_ratings);
4610 if (getDetails & VideoDbDetailsUniqueID)
4611 GetUniqueIDs(details.m_iDbId, MediaTypeEpisode, details);
4613 if (getDetails & VideoDbDetailsBookmark)
4614 GetBookMarkForEpisode(details, details.m_EpBookmark);
4616 if (getDetails & VideoDbDetailsStream)
4617 GetStreamDetails(details);
4619 details.m_parsedDetails = getDetails;
4621 return details;
4624 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4626 return GetDetailsForMusicVideo(pDS->get_sql_record(), getDetails);
4629 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4631 CVideoInfoTag details;
4632 CArtist artist;
4634 if (record == nullptr)
4635 return details;
4637 int idMVideo = record->at(0).get_asInt();
4639 GetDetailsFromDB(record, VIDEODB_ID_MUSICVIDEO_MIN, VIDEODB_ID_MUSICVIDEO_MAX, DbMusicVideoOffsets, details);
4640 details.m_iDbId = idMVideo;
4641 details.m_type = MediaTypeMusicVideo;
4643 details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4644 details.m_strPath = record->at(VIDEODB_DETAILS_MUSICVIDEO_PATH).get_asString();
4645 std::string strFileName = record->at(VIDEODB_DETAILS_MUSICVIDEO_FILE).get_asString();
4646 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4647 details.SetPlayCount(record->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYCOUNT).get_asInt());
4648 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_LASTPLAYED).get_asString());
4649 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_DATEADDED).get_asString());
4650 details.SetResumePoint(record->at(VIDEODB_DETAILS_MUSICVIDEO_RESUME_TIME).get_asInt(),
4651 record->at(VIDEODB_DETAILS_MUSICVIDEO_TOTAL_TIME).get_asInt(),
4652 record->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYER_STATE).get_asString());
4653 details.m_iUserRating = record->at(VIDEODB_DETAILS_MUSICVIDEO_USER_RATING).get_asInt();
4654 details.SetUniqueID(record->at(VIDEODB_DETAILS_MUSICVIDEO_UNIQUEID_VALUE).get_asString(),
4655 record->at(VIDEODB_DETAILS_MUSICVIDEO_UNIQUEID_TYPE).get_asString(), true);
4656 std::string premieredString = record->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED).get_asString();
4657 if (premieredString.size() == 4)
4658 details.SetYear(record->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED).get_asInt());
4659 else
4660 details.SetPremieredFromDBDate(premieredString);
4662 if (getDetails)
4664 if (getDetails & VideoDbDetailsTag)
4665 GetTags(details.m_iDbId, MediaTypeMusicVideo, details.m_tags);
4667 if (getDetails & VideoDbDetailsUniqueID)
4668 GetUniqueIDs(details.m_iDbId, MediaTypeMusicVideo, details);
4670 if (getDetails & VideoDbDetailsStream)
4671 GetStreamDetails(details);
4673 if (getDetails & VideoDbDetailsAll)
4675 GetCast(details.m_iDbId, "musicvideo", details.m_cast);
4678 details.m_parsedDetails = getDetails;
4680 return details;
4683 void CVideoDatabase::GetCast(int media_id, const std::string &media_type, std::vector<SActorInfo> &cast)
4687 if (!m_pDB)
4688 return;
4689 if (!m_pDS2)
4690 return;
4692 std::string sql = PrepareSQL("SELECT actor.name,"
4693 " actor_link.role,"
4694 " actor_link.cast_order,"
4695 " actor.art_urls,"
4696 " art.url "
4697 "FROM actor_link"
4698 " JOIN actor ON"
4699 " actor_link.actor_id=actor.actor_id"
4700 " LEFT JOIN art ON"
4701 " art.media_id=actor.actor_id AND art.media_type='actor' AND art.type='thumb' "
4702 "WHERE actor_link.media_id=%i AND actor_link.media_type='%s'"
4703 "ORDER BY actor_link.cast_order", media_id, media_type.c_str());
4704 m_pDS2->query(sql);
4705 while (!m_pDS2->eof())
4707 SActorInfo info;
4708 info.strName = m_pDS2->fv(0).get_asString();
4709 info.strRole = m_pDS2->fv(1).get_asString();
4711 // ignore identical actors (since cast might already be prefilled)
4712 if (std::none_of(cast.begin(), cast.end(), [info](const SActorInfo& actor) {
4713 return actor.strName == info.strName && actor.strRole == info.strRole;
4716 info.order = m_pDS2->fv(2).get_asInt();
4717 info.thumbUrl.ParseFromData(m_pDS2->fv(3).get_asString());
4718 info.thumb = m_pDS2->fv(4).get_asString();
4719 cast.emplace_back(std::move(info));
4722 m_pDS2->next();
4724 m_pDS2->close();
4726 catch (...)
4728 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4732 void CVideoDatabase::GetTags(int media_id, const std::string &media_type, std::vector<std::string> &tags)
4736 if (!m_pDB)
4737 return;
4738 if (!m_pDS2)
4739 return;
4741 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());
4742 m_pDS2->query(sql);
4743 while (!m_pDS2->eof())
4745 tags.emplace_back(m_pDS2->fv(0).get_asString());
4746 m_pDS2->next();
4748 m_pDS2->close();
4750 catch (...)
4752 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4756 void CVideoDatabase::GetRatings(int media_id, const std::string &media_type, RatingMap &ratings)
4760 if (!m_pDB)
4761 return;
4762 if (!m_pDS2)
4763 return;
4765 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());
4766 m_pDS2->query(sql);
4767 while (!m_pDS2->eof())
4769 ratings[m_pDS2->fv(0).get_asString()] = CRating(m_pDS2->fv(1).get_asFloat(), m_pDS2->fv(2).get_asInt());
4770 m_pDS2->next();
4772 m_pDS2->close();
4774 catch (...)
4776 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4780 void CVideoDatabase::GetUniqueIDs(int media_id, const std::string &media_type, CVideoInfoTag& details)
4784 if (!m_pDB)
4785 return;
4786 if (!m_pDS2)
4787 return;
4789 std::string sql = PrepareSQL("SELECT type, value FROM uniqueid WHERE media_id = %i AND media_type = '%s'", media_id, media_type.c_str());
4790 m_pDS2->query(sql);
4791 while (!m_pDS2->eof())
4793 details.SetUniqueID(m_pDS2->fv(1).get_asString(), m_pDS2->fv(0).get_asString());
4794 m_pDS2->next();
4796 m_pDS2->close();
4798 catch (...)
4800 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4804 bool CVideoDatabase::GetVideoSettings(const CFileItem &item, CVideoSettings &settings)
4806 return GetVideoSettings(GetFileId(item), settings);
4809 /// \brief GetVideoSettings() obtains any saved video settings for the current file.
4810 /// \retval Returns true if the settings exist, false otherwise.
4811 bool CVideoDatabase::GetVideoSettings(const std::string &filePath, CVideoSettings &settings)
4813 return GetVideoSettings(GetFileId(filePath), settings);
4816 bool CVideoDatabase::GetVideoSettings(int idFile, CVideoSettings &settings)
4820 if (idFile < 0) return false;
4821 if (nullptr == m_pDB)
4822 return false;
4823 if (nullptr == m_pDS)
4824 return false;
4826 std::string strSQL=PrepareSQL("select * from settings where settings.idFile = '%i'", idFile);
4827 m_pDS->query( strSQL );
4829 if (m_pDS->num_rows() > 0)
4830 { // get the video settings info
4831 settings.m_AudioDelay = m_pDS->fv("AudioDelay").get_asFloat();
4832 settings.m_AudioStream = m_pDS->fv("AudioStream").get_asInt();
4833 settings.m_Brightness = m_pDS->fv("Brightness").get_asFloat();
4834 settings.m_Contrast = m_pDS->fv("Contrast").get_asFloat();
4835 settings.m_CustomPixelRatio = m_pDS->fv("PixelRatio").get_asFloat();
4836 settings.m_CustomNonLinStretch = m_pDS->fv("NonLinStretch").get_asBool();
4837 settings.m_NoiseReduction = m_pDS->fv("NoiseReduction").get_asFloat();
4838 settings.m_PostProcess = m_pDS->fv("PostProcess").get_asBool();
4839 settings.m_Sharpness = m_pDS->fv("Sharpness").get_asFloat();
4840 settings.m_CustomZoomAmount = m_pDS->fv("ZoomAmount").get_asFloat();
4841 settings.m_CustomVerticalShift = m_pDS->fv("VerticalShift").get_asFloat();
4842 settings.m_Gamma = m_pDS->fv("Gamma").get_asFloat();
4843 settings.m_SubtitleDelay = m_pDS->fv("SubtitleDelay").get_asFloat();
4844 settings.m_SubtitleOn = m_pDS->fv("SubtitlesOn").get_asBool();
4845 settings.m_SubtitleStream = m_pDS->fv("SubtitleStream").get_asInt();
4846 settings.m_ViewMode = m_pDS->fv("ViewMode").get_asInt();
4847 settings.m_ResumeTime = m_pDS->fv("ResumeTime").get_asInt();
4848 settings.m_InterlaceMethod = (EINTERLACEMETHOD)m_pDS->fv("Deinterlace").get_asInt();
4849 settings.m_VolumeAmplification = m_pDS->fv("VolumeAmplification").get_asFloat();
4850 settings.m_ScalingMethod = (ESCALINGMETHOD)m_pDS->fv("ScalingMethod").get_asInt();
4851 settings.m_StereoMode = m_pDS->fv("StereoMode").get_asInt();
4852 settings.m_StereoInvert = m_pDS->fv("StereoInvert").get_asBool();
4853 settings.m_VideoStream = m_pDS->fv("VideoStream").get_asInt();
4854 settings.m_ToneMapMethod =
4855 static_cast<ETONEMAPMETHOD>(m_pDS->fv("TonemapMethod").get_asInt());
4856 settings.m_ToneMapParam = m_pDS->fv("TonemapParam").get_asFloat();
4857 settings.m_Orientation = m_pDS->fv("Orientation").get_asInt();
4858 settings.m_CenterMixLevel = m_pDS->fv("CenterMixLevel").get_asInt();
4859 m_pDS->close();
4860 return true;
4862 m_pDS->close();
4864 catch (...)
4866 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
4868 return false;
4871 void CVideoDatabase::SetVideoSettings(const CFileItem &item, const CVideoSettings &settings)
4873 int idFile = AddFile(item);
4874 SetVideoSettings(idFile, settings);
4877 /// \brief Sets the settings for a particular video file
4878 void CVideoDatabase::SetVideoSettings(int idFile, const CVideoSettings &setting)
4882 if (nullptr == m_pDB)
4883 return;
4884 if (nullptr == m_pDS)
4885 return;
4886 if (idFile < 0)
4887 return;
4888 std::string strSQL = PrepareSQL("select * from settings where idFile=%i", idFile);
4889 m_pDS->query( strSQL );
4890 if (m_pDS->num_rows() > 0)
4892 m_pDS->close();
4893 // update the item
4894 strSQL = PrepareSQL(
4895 "update settings set "
4896 "Deinterlace=%i,ViewMode=%i,ZoomAmount=%f,PixelRatio=%f,VerticalShift=%f,"
4897 "AudioStream=%i,SubtitleStream=%i,SubtitleDelay=%f,SubtitlesOn=%i,Brightness=%f,Contrast="
4898 "%f,Gamma=%f,"
4899 "VolumeAmplification=%f,AudioDelay=%f,Sharpness=%f,NoiseReduction=%f,NonLinStretch=%i,"
4900 "PostProcess=%i,ScalingMethod=%i,",
4901 setting.m_InterlaceMethod, setting.m_ViewMode,
4902 static_cast<double>(setting.m_CustomZoomAmount),
4903 static_cast<double>(setting.m_CustomPixelRatio),
4904 static_cast<double>(setting.m_CustomVerticalShift), setting.m_AudioStream,
4905 setting.m_SubtitleStream, static_cast<double>(setting.m_SubtitleDelay),
4906 setting.m_SubtitleOn, static_cast<double>(setting.m_Brightness),
4907 static_cast<double>(setting.m_Contrast), static_cast<double>(setting.m_Gamma),
4908 static_cast<double>(setting.m_VolumeAmplification),
4909 static_cast<double>(setting.m_AudioDelay), static_cast<double>(setting.m_Sharpness),
4910 static_cast<double>(setting.m_NoiseReduction), setting.m_CustomNonLinStretch,
4911 setting.m_PostProcess, setting.m_ScalingMethod);
4912 std::string strSQL2;
4914 strSQL2 = PrepareSQL("ResumeTime=%i,StereoMode=%i,StereoInvert=%i,VideoStream=%i,"
4915 "TonemapMethod=%i,TonemapParam=%f where idFile=%i\n",
4916 setting.m_ResumeTime, setting.m_StereoMode, setting.m_StereoInvert,
4917 setting.m_VideoStream, setting.m_ToneMapMethod,
4918 static_cast<double>(setting.m_ToneMapParam), idFile);
4919 strSQL += strSQL2;
4920 m_pDS->exec(strSQL);
4921 return ;
4923 else
4924 { // add the items
4925 m_pDS->close();
4926 strSQL= "INSERT INTO settings (idFile,Deinterlace,ViewMode,ZoomAmount,PixelRatio, VerticalShift, "
4927 "AudioStream,SubtitleStream,SubtitleDelay,SubtitlesOn,Brightness,"
4928 "Contrast,Gamma,VolumeAmplification,AudioDelay,"
4929 "ResumeTime,"
4930 "Sharpness,NoiseReduction,NonLinStretch,PostProcess,ScalingMethod,StereoMode,StereoInvert,VideoStream,TonemapMethod,TonemapParam,Orientation,CenterMixLevel) "
4931 "VALUES ";
4932 strSQL += PrepareSQL(
4933 "(%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)",
4934 idFile, setting.m_InterlaceMethod, setting.m_ViewMode,
4935 static_cast<double>(setting.m_CustomZoomAmount),
4936 static_cast<double>(setting.m_CustomPixelRatio),
4937 static_cast<double>(setting.m_CustomVerticalShift), setting.m_AudioStream,
4938 setting.m_SubtitleStream, static_cast<double>(setting.m_SubtitleDelay),
4939 setting.m_SubtitleOn, static_cast<double>(setting.m_Brightness),
4940 static_cast<double>(setting.m_Contrast), static_cast<double>(setting.m_Gamma),
4941 static_cast<double>(setting.m_VolumeAmplification),
4942 static_cast<double>(setting.m_AudioDelay), setting.m_ResumeTime,
4943 static_cast<double>(setting.m_Sharpness), static_cast<double>(setting.m_NoiseReduction),
4944 setting.m_CustomNonLinStretch, setting.m_PostProcess, setting.m_ScalingMethod,
4945 setting.m_StereoMode, setting.m_StereoInvert, setting.m_VideoStream,
4946 setting.m_ToneMapMethod, static_cast<double>(setting.m_ToneMapParam),
4947 setting.m_Orientation, setting.m_CenterMixLevel);
4948 m_pDS->exec(strSQL);
4951 catch (...)
4953 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idFile);
4957 void CVideoDatabase::UpdateArtForItem(int mediaId, const MediaType& mediaType)
4959 AnnounceUpdate(mediaType, mediaId);
4962 void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, const std::map<std::string, std::string> &art)
4964 for (const auto &i : art)
4965 SetArtForItem(mediaId, mediaType, i.first, i.second);
4968 void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType, const std::string &url)
4972 if (nullptr == m_pDB)
4973 return;
4974 if (nullptr == m_pDS)
4975 return;
4977 // don't set <foo>.<bar> art types - these are derivative types from parent items
4978 if (artType.find('.') != std::string::npos)
4979 return;
4981 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());
4982 m_pDS->query(sql);
4983 if (!m_pDS->eof())
4984 { // update
4985 int artId = m_pDS->fv(0).get_asInt();
4986 std::string oldUrl = m_pDS->fv(1).get_asString();
4987 m_pDS->close();
4988 if (oldUrl != url)
4990 sql = PrepareSQL("UPDATE art SET url='%s' where art_id=%d", url.c_str(), artId);
4991 m_pDS->exec(sql);
4994 else
4995 { // insert
4996 m_pDS->close();
4997 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());
4998 m_pDS->exec(sql);
5001 catch (...)
5003 CLog::Log(LOGERROR, "{}({}, '{}', '{}', '{}') failed", __FUNCTION__, mediaId, mediaType,
5004 artType, url);
5008 bool CVideoDatabase::GetArtForItem(int mediaId, const MediaType &mediaType, std::map<std::string, std::string> &art)
5012 if (nullptr == m_pDB)
5013 return false;
5014 if (nullptr == m_pDS2)
5015 return false; // using dataset 2 as we're likely called in loops on dataset 1
5017 std::string sql = PrepareSQL("SELECT type,url FROM art WHERE media_id=%i AND media_type='%s'", mediaId, mediaType.c_str());
5019 m_pDS2->query(sql);
5020 while (!m_pDS2->eof())
5022 art.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString()));
5023 m_pDS2->next();
5025 m_pDS2->close();
5026 return !art.empty();
5028 catch (...)
5030 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaId);
5032 return false;
5035 bool CVideoDatabase::GetArtForAsset(int assetId,
5036 ArtFallbackOptions fallback,
5037 std::map<std::string, std::string>& art)
5041 if (nullptr == m_pDB)
5042 return false;
5043 if (nullptr == m_pDS2)
5044 return false; // using dataset 2 as we're likely called in loops on dataset 1
5046 std::string sql{PrepareSQL("SELECT art.media_type, art.type, art.url "
5047 "FROM art "
5048 "WHERE media_id = %i AND media_type = '%s' ",
5049 assetId, MediaTypeVideoVersion)};
5051 if (fallback == ArtFallbackOptions::PARENT)
5052 sql.append(PrepareSQL("UNION "
5053 "SELECT art.media_type, art.type, art.url "
5054 "FROM art "
5055 " JOIN videoversion as vv "
5056 " ON art.media_id = vv.idMedia AND art.media_type = vv.media_type "
5057 "WHERE idFile = %i",
5058 assetId));
5060 m_pDS2->query(sql);
5061 while (!m_pDS2->eof())
5063 if (m_pDS2->fv(0).get_asString() == MediaTypeVideoVersion)
5065 // version data has priority over owner's data
5066 art[m_pDS2->fv(1).get_asString()] = m_pDS2->fv(2).get_asString();
5068 else if (fallback == ArtFallbackOptions::PARENT)
5070 // insert if not yet present
5071 art.insert(make_pair(m_pDS2->fv(1).get_asString(), m_pDS2->fv(2).get_asString()));
5073 m_pDS2->next();
5075 m_pDS2->close();
5076 return !art.empty();
5078 catch (...)
5080 CLog::LogF(LOGERROR, "retrieval failed ({})", assetId);
5082 return false;
5085 std::string CVideoDatabase::GetArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType)
5087 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());
5088 return GetSingleValue(query, m_pDS2);
5091 bool CVideoDatabase::RemoveArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType)
5093 return ExecuteQuery(PrepareSQL("DELETE FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str()));
5096 bool CVideoDatabase::RemoveArtForItem(int mediaId, const MediaType &mediaType, const std::set<std::string> &artTypes)
5098 bool result = true;
5099 for (const auto &i : artTypes)
5100 result &= RemoveArtForItem(mediaId, mediaType, i);
5102 return result;
5105 bool CVideoDatabase::HasArtForItem(int mediaId, const MediaType &mediaType)
5109 if (nullptr == m_pDB)
5110 return false;
5111 if (nullptr == m_pDS2)
5112 return false; // using dataset 2 as we're likely called in loops on dataset 1
5114 std::string sql = PrepareSQL("SELECT 1 FROM art WHERE media_id=%i AND media_type='%s' LIMIT 1", mediaId, mediaType.c_str());
5115 m_pDS2->query(sql);
5116 bool result = !m_pDS2->eof();
5117 m_pDS2->close();
5118 return result;
5120 catch (...)
5122 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaId);
5124 return false;
5127 bool CVideoDatabase::GetTvShowSeasons(int showId, std::map<int, int> &seasons)
5131 if (nullptr == m_pDB)
5132 return false;
5133 if (nullptr == m_pDS2)
5134 return false; // using dataset 2 as we're likely called in loops on dataset 1
5136 // get all seasons for this show
5137 std::string sql = PrepareSQL("select idSeason,season from seasons where idShow=%i", showId);
5138 m_pDS2->query(sql);
5140 seasons.clear();
5141 while (!m_pDS2->eof())
5143 seasons.insert(std::make_pair(m_pDS2->fv(1).get_asInt(), m_pDS2->fv(0).get_asInt()));
5144 m_pDS2->next();
5146 m_pDS2->close();
5147 return true;
5149 catch (...)
5151 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
5153 return false;
5156 bool CVideoDatabase::GetTvShowNamedSeasons(int showId, std::map<int, std::string> &seasons)
5160 if (nullptr == m_pDB)
5161 return false;
5162 if (nullptr == m_pDS2)
5163 return false; // using dataset 2 as we're likely called in loops on dataset 1
5165 // get all named seasons for this show
5166 std::string sql = PrepareSQL("select season, name from seasons where season > 0 and name is not null and name <> '' and idShow = %i", showId);
5167 m_pDS2->query(sql);
5169 seasons.clear();
5170 while (!m_pDS2->eof())
5172 seasons.insert(std::make_pair(m_pDS2->fv(0).get_asInt(), m_pDS2->fv(1).get_asString()));
5173 m_pDS2->next();
5175 m_pDS2->close();
5176 return true;
5178 catch (...)
5180 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
5182 return false;
5185 std::string CVideoDatabase::GetTvShowNamedSeasonById(int tvshowId, int seasonId)
5187 return GetSingleValue("seasons", "name",
5188 PrepareSQL("season=%i AND idShow=%i", seasonId, tvshowId));
5191 bool CVideoDatabase::GetTvShowSeasonArt(int showId, std::map<int, std::map<std::string, std::string> > &seasonArt)
5195 if (nullptr == m_pDB)
5196 return false;
5197 if (nullptr == m_pDS2)
5198 return false; // using dataset 2 as we're likely called in loops on dataset 1
5200 std::map<int, int> seasons;
5201 GetTvShowSeasons(showId, seasons);
5203 for (const auto &i : seasons)
5205 std::map<std::string, std::string> art;
5206 GetArtForItem(i.second, MediaTypeSeason, art);
5207 seasonArt.insert(std::make_pair(i.first,art));
5209 return true;
5211 catch (...)
5213 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
5215 return false;
5218 bool CVideoDatabase::GetArtTypes(const MediaType &mediaType, std::vector<std::string> &artTypes)
5222 if (nullptr == m_pDB)
5223 return false;
5224 if (nullptr == m_pDS)
5225 return false;
5227 std::string sql = PrepareSQL("SELECT DISTINCT type FROM art WHERE media_type='%s'", mediaType.c_str());
5228 int numRows = RunQuery(sql);
5229 if (numRows <= 0)
5230 return numRows == 0;
5232 while (!m_pDS->eof())
5234 artTypes.emplace_back(m_pDS->fv(0).get_asString());
5235 m_pDS->next();
5237 m_pDS->close();
5238 return true;
5240 catch (...)
5242 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaType);
5244 return false;
5247 namespace
5249 std::vector<std::string> GetBasicItemAvailableArtTypes(int mediaId,
5250 VideoDbContentType dbType,
5251 CVideoDatabase& db)
5253 std::vector<std::string> result;
5254 CVideoInfoTag tag = db.GetDetailsByTypeAndId(dbType, mediaId);
5256 //! @todo artwork: fanart stored separately, doesn't need to be
5257 tag.m_fanart.Unpack();
5258 if (tag.m_fanart.GetNumFanarts() && std::find(result.cbegin(), result.cend(), "fanart") == result.cend())
5259 result.emplace_back("fanart");
5261 // all other images
5262 tag.m_strPictureURL.Parse();
5263 for (const auto& urlEntry : tag.m_strPictureURL.GetUrls())
5265 std::string artType = urlEntry.m_aspect;
5266 if (artType.empty())
5267 artType = tag.m_type == MediaTypeEpisode ? "thumb" : "poster";
5268 if (urlEntry.m_type == CScraperUrl::UrlType::General && // exclude season artwork for TV shows
5269 !StringUtils::StartsWith(artType, "set.") && // exclude movie set artwork for movies
5270 std::find(result.cbegin(), result.cend(), artType) == result.cend())
5272 result.push_back(artType);
5275 return result;
5278 std::vector<std::string> GetSeasonAvailableArtTypes(int mediaId, CVideoDatabase& db)
5280 CVideoInfoTag tag;
5281 db.GetSeasonInfo(mediaId, tag);
5283 std::vector<std::string> result;
5285 CVideoInfoTag sourceShow;
5286 db.GetTvShowInfo("", sourceShow, tag.m_iIdShow);
5287 sourceShow.m_strPictureURL.Parse();
5288 for (const auto& urlEntry : sourceShow.m_strPictureURL.GetUrls())
5290 std::string artType = urlEntry.m_aspect;
5291 if (artType.empty())
5292 artType = "poster";
5293 if (urlEntry.m_type == CScraperUrl::UrlType::Season && urlEntry.m_season == tag.m_iSeason &&
5294 std::find(result.cbegin(), result.cend(), artType) == result.cend())
5296 result.push_back(artType);
5299 return result;
5302 std::vector<std::string> GetMovieSetAvailableArtTypes(int mediaId, CVideoDatabase& db)
5304 std::vector<std::string> result;
5305 CFileItemList items;
5306 std::string baseDir = StringUtils::Format("videodb://movies/sets/{}", mediaId);
5307 if (db.GetMoviesNav(baseDir, items))
5309 for (const auto& item : items)
5311 CVideoInfoTag* pTag = item->GetVideoInfoTag();
5312 pTag->m_strPictureURL.Parse();
5314 for (const auto& urlEntry : pTag->m_strPictureURL.GetUrls())
5316 if (!StringUtils::StartsWith(urlEntry.m_aspect, "set."))
5317 continue;
5319 std::string artType = urlEntry.m_aspect.substr(4);
5320 if (std::find(result.cbegin(), result.cend(), artType) == result.cend())
5321 result.push_back(artType);
5325 return result;
5328 std::vector<CScraperUrl::SUrlEntry> GetBasicItemAvailableArt(int mediaId,
5329 VideoDbContentType dbType,
5330 const std::string& artType,
5331 CVideoDatabase& db)
5333 std::vector<CScraperUrl::SUrlEntry> result;
5334 CVideoInfoTag tag = db.GetDetailsByTypeAndId(dbType, mediaId);
5336 if (artType.empty() || artType == "fanart")
5338 tag.m_fanart.Unpack();
5339 for (unsigned int i = 0; i < tag.m_fanart.GetNumFanarts(); i++)
5341 CScraperUrl::SUrlEntry url(tag.m_fanart.GetImageURL(i));
5342 url.m_preview = tag.m_fanart.GetPreviewURL(i);
5343 url.m_aspect = "fanart";
5344 result.push_back(url);
5347 tag.m_strPictureURL.Parse();
5348 for (auto urlEntry : tag.m_strPictureURL.GetUrls())
5350 if (urlEntry.m_aspect.empty())
5351 urlEntry.m_aspect = tag.m_type == MediaTypeEpisode ? "thumb" : "poster";
5352 if ((urlEntry.m_aspect == artType ||
5353 (artType.empty() && !StringUtils::StartsWith(urlEntry.m_aspect, "set."))) &&
5354 urlEntry.m_type == CScraperUrl::UrlType::General)
5356 result.push_back(urlEntry);
5360 return result;
5363 std::vector<CScraperUrl::SUrlEntry> GetSeasonAvailableArt(
5364 int mediaId, const std::string& artType, CVideoDatabase& db)
5366 CVideoInfoTag tag;
5367 db.GetSeasonInfo(mediaId, tag);
5369 std::vector<CScraperUrl::SUrlEntry> result;
5371 CVideoInfoTag sourceShow;
5372 db.GetTvShowInfo("", sourceShow, tag.m_iIdShow);
5373 sourceShow.m_strPictureURL.Parse();
5374 for (auto urlEntry : sourceShow.m_strPictureURL.GetUrls())
5376 if (urlEntry.m_aspect.empty())
5377 urlEntry.m_aspect = "poster";
5378 if ((artType.empty() || urlEntry.m_aspect == artType) &&
5379 urlEntry.m_type == CScraperUrl::UrlType::Season &&
5380 urlEntry.m_season == tag.m_iSeason)
5382 result.push_back(urlEntry);
5385 return result;
5388 std::vector<CScraperUrl::SUrlEntry> GetMovieSetAvailableArt(
5389 int mediaId, const std::string& artType, CVideoDatabase& db)
5391 std::vector<CScraperUrl::SUrlEntry> result;
5392 CFileItemList items;
5393 std::string baseDir = StringUtils::Format("videodb://movies/sets/{}", mediaId);
5394 std::unordered_set<std::string> addedURLs;
5395 if (db.GetMoviesNav(baseDir, items))
5397 for (const auto& item : items)
5399 CVideoInfoTag* pTag = item->GetVideoInfoTag();
5400 pTag->m_strPictureURL.Parse();
5402 for (auto urlEntry : pTag->m_strPictureURL.GetUrls())
5404 bool isSetArt = !artType.empty() ? urlEntry.m_aspect == "set." + artType :
5405 StringUtils::StartsWith(urlEntry.m_aspect, "set.");
5406 if (isSetArt && addedURLs.insert(urlEntry.m_url).second)
5408 urlEntry.m_aspect = urlEntry.m_aspect.substr(4);
5409 result.push_back(urlEntry);
5414 return result;
5417 VideoDbContentType CovertMediaTypeToContentType(const MediaType& mediaType)
5419 VideoDbContentType dbType{VideoDbContentType::UNKNOWN};
5420 if (mediaType == MediaTypeTvShow)
5421 dbType = VideoDbContentType::TVSHOWS;
5422 else if (mediaType == MediaTypeMovie)
5423 dbType = VideoDbContentType::MOVIES;
5424 else if (mediaType == MediaTypeEpisode)
5425 dbType = VideoDbContentType::EPISODES;
5426 else if (mediaType == MediaTypeMusicVideo)
5427 dbType = VideoDbContentType::MUSICVIDEOS;
5429 return dbType;
5431 } // namespace
5433 std::vector<CScraperUrl::SUrlEntry> CVideoDatabase::GetAvailableArtForItem(
5434 int mediaId, const MediaType& mediaType, const std::string& artType)
5436 VideoDbContentType dbType = CovertMediaTypeToContentType(mediaType);
5438 if (dbType != VideoDbContentType::UNKNOWN)
5439 return GetBasicItemAvailableArt(mediaId, dbType, artType, *this);
5440 if (mediaType == MediaTypeSeason)
5441 return GetSeasonAvailableArt(mediaId, artType, *this);
5442 if (mediaType == MediaTypeVideoCollection)
5443 return GetMovieSetAvailableArt(mediaId, artType, *this);
5444 return {};
5447 std::vector<std::string> CVideoDatabase::GetAvailableArtTypesForItem(int mediaId,
5448 const MediaType& mediaType)
5450 VideoDbContentType dbType = CovertMediaTypeToContentType(mediaType);
5452 if (dbType != VideoDbContentType::UNKNOWN)
5453 return GetBasicItemAvailableArtTypes(mediaId, dbType, *this);
5454 if (mediaType == MediaTypeSeason)
5455 return GetSeasonAvailableArtTypes(mediaId, *this);
5456 if (mediaType == MediaTypeVideoCollection)
5457 return GetMovieSetAvailableArtTypes(mediaId, *this);
5458 return {};
5461 /// \brief GetStackTimes() obtains any saved video times for the stacked file
5462 /// \retval Returns true if the stack times exist, false otherwise.
5463 bool CVideoDatabase::GetStackTimes(const std::string &filePath, std::vector<uint64_t> &times)
5467 // obtain the FileID (if it exists)
5468 int idFile = GetFileId(filePath);
5469 if (idFile < 0) return false;
5470 if (nullptr == m_pDB)
5471 return false;
5472 if (nullptr == m_pDS)
5473 return false;
5474 // ok, now obtain the settings for this file
5475 std::string strSQL=PrepareSQL("select times from stacktimes where idFile=%i\n", idFile);
5476 m_pDS->query( strSQL );
5477 if (m_pDS->num_rows() > 0)
5478 { // get the video settings info
5479 uint64_t timeTotal = 0;
5480 std::vector<std::string> timeString = StringUtils::Split(m_pDS->fv("times").get_asString(), ",");
5481 times.clear();
5482 for (const auto &i : timeString)
5484 uint64_t partTime = static_cast<uint64_t>(atof(i.c_str()) * 1000.0);
5485 times.push_back(partTime); // db stores in secs, convert to msecs
5486 timeTotal += partTime;
5488 m_pDS->close();
5489 return (timeTotal > 0);
5491 m_pDS->close();
5493 catch (...)
5495 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
5497 return false;
5500 /// \brief Sets the stack times for a particular video file
5501 void CVideoDatabase::SetStackTimes(const std::string& filePath, const std::vector<uint64_t> &times)
5505 if (nullptr == m_pDB)
5506 return;
5507 if (nullptr == m_pDS)
5508 return;
5509 int idFile = AddFile(filePath);
5510 if (idFile < 0)
5511 return;
5513 // delete any existing items
5514 m_pDS->exec( PrepareSQL("delete from stacktimes where idFile=%i", idFile) );
5516 // add the items
5517 std::string timeString = StringUtils::Format("{:.3f}", times[0] / 1000.0f);
5518 for (unsigned int i = 1; i < times.size(); i++)
5519 timeString += StringUtils::Format(",{:.3f}", times[i] / 1000.0f);
5521 m_pDS->exec( PrepareSQL("insert into stacktimes (idFile,times) values (%i,'%s')\n", idFile, timeString.c_str()) );
5523 catch (...)
5525 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
5529 void CVideoDatabase::RemoveContentForPath(const std::string& strPath, CGUIDialogProgress *progress /* = NULL */)
5531 if(URIUtils::IsMultiPath(strPath))
5533 std::vector<std::string> paths;
5534 CMultiPathDirectory::GetPaths(strPath, paths);
5536 for(unsigned i=0;i<paths.size();i++)
5537 RemoveContentForPath(paths[i], progress);
5542 if (nullptr == m_pDB)
5543 return;
5544 if (nullptr == m_pDS)
5545 return;
5547 if (progress)
5549 progress->SetHeading(CVariant{700});
5550 progress->SetLine(0, CVariant{""});
5551 progress->SetLine(1, CVariant{313});
5552 progress->SetLine(2, CVariant{330});
5553 progress->SetPercentage(0);
5554 progress->Open();
5555 progress->ShowProgressBar(true);
5557 std::vector<std::pair<int, std::string> > paths;
5558 GetSubPaths(strPath, paths);
5559 int iCurr = 0;
5560 for (const auto &i : paths)
5562 bool bMvidsChecked=false;
5563 if (progress)
5565 progress->SetPercentage((int)((float)(iCurr++)/paths.size()*100.f));
5566 progress->Progress();
5569 const auto tvshowId = GetTvShowId(i.second);
5570 if (tvshowId > 0)
5571 DeleteTvShow(tvshowId);
5572 else
5574 std::string strSQL = PrepareSQL("select files.strFilename from files join movie on movie.idFile=files.idFile where files.idPath=%i", i.first);
5575 m_pDS2->query(strSQL);
5576 if (m_pDS2->eof())
5578 strSQL = PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i.first);
5579 m_pDS2->query(strSQL);
5580 bMvidsChecked = true;
5582 while (!m_pDS2->eof())
5584 std::string strMoviePath;
5585 std::string strFileName = m_pDS2->fv("files.strFilename").get_asString();
5586 ConstructPath(strMoviePath, i.second, strFileName);
5587 const auto movieId = GetMovieId(strMoviePath);
5588 if (movieId > 0)
5589 DeleteMovie(movieId);
5590 else
5592 const auto musicvideoId = GetMusicVideoId(strMoviePath);
5593 if (musicvideoId > 0)
5594 DeleteMusicVideo(musicvideoId);
5596 m_pDS2->next();
5597 if (m_pDS2->eof() && !bMvidsChecked)
5599 strSQL =PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i.first);
5600 m_pDS2->query(strSQL);
5601 bMvidsChecked = true;
5604 m_pDS2->close();
5605 m_pDS2->exec(PrepareSQL("update path set strContent='', strScraper='', strHash='',strSettings='',useFolderNames=0,scanRecursive=0 where idPath=%i", i.first));
5609 catch (...)
5611 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
5613 if (progress)
5614 progress->Close();
5617 void CVideoDatabase::SetScraperForPath(const std::string& filePath, const ScraperPtr& scraper, const VIDEO::SScanSettings& settings)
5619 // if we have a multipath, set scraper for all contained paths
5620 if(URIUtils::IsMultiPath(filePath))
5622 std::vector<std::string> paths;
5623 CMultiPathDirectory::GetPaths(filePath, paths);
5625 for(unsigned i=0;i<paths.size();i++)
5626 SetScraperForPath(paths[i],scraper,settings);
5628 return;
5633 if (nullptr == m_pDB)
5634 return;
5635 if (nullptr == m_pDS)
5636 return;
5638 int idPath = AddPath(filePath);
5639 if (idPath < 0)
5640 return;
5642 // Update
5643 std::string strSQL;
5644 if (settings.exclude)
5645 { //NB See note in ::GetScraperForPath about strContent=='none'
5646 strSQL = PrepareSQL(
5647 "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5648 "strSettings='', noUpdate=0, exclude=1, allAudio=%i WHERE idPath=%i",
5649 settings.m_allExtAudio, idPath);
5651 else if(!scraper)
5652 { // catch clearing content, but not excluding
5653 strSQL = PrepareSQL(
5654 "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5655 "strSettings='', noUpdate=0, exclude=0, allAudio=%i WHERE idPath=%i",
5656 settings.m_allExtAudio, idPath);
5658 else
5660 std::string content = TranslateContent(scraper->Content());
5661 strSQL = PrepareSQL(
5662 "UPDATE path SET strContent='%s', strScraper='%s', scanRecursive=%i, useFolderNames=%i, "
5663 "strSettings='%s', noUpdate=%i, exclude=0, allAudio=%i WHERE idPath=%i",
5664 content.c_str(), scraper->ID().c_str(), settings.recurse, settings.parent_name,
5665 scraper->GetPathSettings().c_str(), settings.noupdate, settings.m_allExtAudio, idPath);
5667 m_pDS->exec(strSQL);
5669 catch (...)
5671 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
5675 bool CVideoDatabase::ScraperInUse(const std::string &scraperID) const
5679 if (nullptr == m_pDB)
5680 return false;
5681 if (nullptr == m_pDS)
5682 return false;
5684 std::string sql = PrepareSQL("select count(1) from path where strScraper='%s'", scraperID.c_str());
5685 if (!m_pDS->query(sql) || m_pDS->num_rows() == 0)
5686 return false;
5687 bool found = m_pDS->fv(0).get_asInt() > 0;
5688 m_pDS->close();
5689 return found;
5691 catch (...)
5693 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, scraperID);
5695 return false;
5698 class CArtItem
5700 public:
5701 CArtItem() { art_id = 0; media_id = 0; };
5702 int art_id;
5703 std::string art_type;
5704 std::string art_url;
5705 int media_id;
5706 std::string media_type;
5709 // used for database update to v83
5710 class CShowItem
5712 public:
5713 bool operator==(const CShowItem &r) const
5715 return (!ident.empty() && ident == r.ident) || (title == r.title && year == r.year);
5717 int id;
5718 int path;
5719 std::string title;
5720 std::string year;
5721 std::string ident;
5724 // used for database update to v84
5725 class CShowLink
5727 public:
5728 int show;
5729 int pathId;
5730 std::string path;
5733 void CVideoDatabase::UpdateTables(int iVersion)
5735 // Important: DO NOT use CREATE TABLE [...] AS SELECT [...] - it does not work on MySQL with GTID consistency enforced
5737 if (iVersion < 76)
5739 m_pDS->exec("ALTER TABLE settings ADD StereoMode integer");
5740 m_pDS->exec("ALTER TABLE settings ADD StereoInvert bool");
5742 if (iVersion < 77)
5743 m_pDS->exec("ALTER TABLE streamdetails ADD strStereoMode text");
5745 if (iVersion < 81)
5746 { // add idParentPath to path table
5747 m_pDS->exec("ALTER TABLE path ADD idParentPath integer");
5748 std::map<std::string, int> paths;
5749 m_pDS->query("select idPath,strPath from path");
5750 while (!m_pDS->eof())
5752 paths.insert(make_pair(m_pDS->fv(1).get_asString(), m_pDS->fv(0).get_asInt()));
5753 m_pDS->next();
5755 m_pDS->close();
5756 // run through these paths figuring out the parent path, and add to the table if found
5757 for (const auto &i : paths)
5759 std::string parent = URIUtils::GetParentPath(i.first);
5760 auto j = paths.find(parent);
5761 if (j != paths.end())
5762 m_pDS->exec(PrepareSQL("UPDATE path SET idParentPath=%i WHERE idPath=%i", j->second, i.second));
5765 if (iVersion < 82)
5767 // drop parent path id and basePath from tvshow table
5768 m_pDS->exec("UPDATE tvshow SET c16=NULL,c17=NULL");
5770 if (iVersion < 83)
5772 // drop duplicates in tvshow table, and update tvshowlinkpath accordingly
5773 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);
5774 m_pDS->query(sql);
5775 std::vector<CShowItem> shows;
5776 while (!m_pDS->eof())
5778 CShowItem show;
5779 show.id = m_pDS->fv(0).get_asInt();
5780 show.path = m_pDS->fv(1).get_asInt();
5781 show.title = m_pDS->fv(2).get_asString();
5782 show.year = m_pDS->fv(3).get_asString();
5783 show.ident = m_pDS->fv(4).get_asString();
5784 shows.emplace_back(std::move(show));
5785 m_pDS->next();
5787 m_pDS->close();
5788 if (!shows.empty())
5790 for (auto i = shows.begin() + 1; i != shows.end(); ++i)
5792 // has this show been found before?
5793 auto j = find(shows.begin(), i, *i);
5794 if (j != i)
5795 { // this is a duplicate
5796 // update the tvshowlinkpath table
5797 m_pDS->exec(PrepareSQL("UPDATE tvshowlinkpath SET idShow = %d WHERE idShow = %d AND idPath = %d", j->id, i->id, i->path));
5798 // update episodes, seasons, movie links
5799 m_pDS->exec(PrepareSQL("UPDATE episode SET idShow = %d WHERE idShow = %d", j->id, i->id));
5800 m_pDS->exec(PrepareSQL("UPDATE seasons SET idShow = %d WHERE idShow = %d", j->id, i->id));
5801 m_pDS->exec(PrepareSQL("UPDATE movielinktvshow SET idShow = %d WHERE idShow = %d", j->id, i->id));
5802 // delete tvshow
5803 m_pDS->exec(PrepareSQL("DELETE FROM genrelinktvshow WHERE idShow=%i", i->id));
5804 m_pDS->exec(PrepareSQL("DELETE FROM actorlinktvshow WHERE idShow=%i", i->id));
5805 m_pDS->exec(PrepareSQL("DELETE FROM directorlinktvshow WHERE idShow=%i", i->id));
5806 m_pDS->exec(PrepareSQL("DELETE FROM studiolinktvshow WHERE idShow=%i", i->id));
5807 m_pDS->exec(PrepareSQL("DELETE FROM tvshow WHERE idShow = %d", i->id));
5810 // cleanup duplicate seasons
5811 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)");
5814 if (iVersion < 84)
5815 { // replace any multipaths in tvshowlinkpath table
5816 m_pDS->query("SELECT idShow, tvshowlinkpath.idPath, strPath FROM tvshowlinkpath JOIN path ON tvshowlinkpath.idPath=path.idPath WHERE path.strPath LIKE 'multipath://%'");
5817 std::vector<CShowLink> shows;
5818 while (!m_pDS->eof())
5820 CShowLink link;
5821 link.show = m_pDS->fv(0).get_asInt();
5822 link.pathId = m_pDS->fv(1).get_asInt();
5823 link.path = m_pDS->fv(2).get_asString();
5824 shows.emplace_back(std::move(link));
5825 m_pDS->next();
5827 m_pDS->close();
5828 // update these
5829 for (auto i = shows.begin(); i != shows.end(); ++i)
5831 std::vector<std::string> paths;
5832 CMultiPathDirectory::GetPaths(i->path, paths);
5833 for (auto j = paths.begin(); j != paths.end(); ++j)
5835 int idPath = AddPath(*j, URIUtils::GetParentPath(*j));
5836 /* we can't rely on REPLACE INTO here as analytics (indices) aren't online yet */
5837 if (GetSingleValue(PrepareSQL("SELECT 1 FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i->show, idPath)).empty())
5838 m_pDS->exec(PrepareSQL("INSERT INTO tvshowlinkpath(idShow, idPath) VALUES(%i,%i)", i->show, idPath));
5840 m_pDS->exec(PrepareSQL("DELETE FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i->show, i->pathId));
5843 if (iVersion < 85)
5845 // drop multipaths from the path table - they're not needed for anything at all
5846 m_pDS->exec("DELETE FROM path WHERE strPath LIKE 'multipath://%'");
5848 if (iVersion < 87)
5849 { // due to the tvshow merging above, there could be orphaned season or show art
5850 m_pDS->exec("DELETE from art WHERE media_type='tvshow' AND NOT EXISTS (SELECT 1 FROM tvshow WHERE tvshow.idShow = art.media_id)");
5851 m_pDS->exec("DELETE from art WHERE media_type='season' AND NOT EXISTS (SELECT 1 FROM seasons WHERE seasons.idSeason = art.media_id)");
5853 if (iVersion < 91)
5855 // create actor link table
5856 m_pDS->exec("CREATE TABLE actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
5857 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");
5858 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");
5859 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");
5860 m_pDS->exec("DROP TABLE IF EXISTS actorlinkmovie");
5861 m_pDS->exec("DROP TABLE IF EXISTS actorlinktvshow");
5862 m_pDS->exec("DROP TABLE IF EXISTS actorlinkepisode");
5863 m_pDS->exec("CREATE TABLE actor(actor_id INTEGER PRIMARY KEY, name TEXT, art_urls TEXT)");
5864 m_pDS->exec("INSERT INTO actor(actor_id, name, art_urls) SELECT idActor,strActor,strThumb FROM actors");
5865 m_pDS->exec("DROP TABLE IF EXISTS actors");
5867 // directors
5868 m_pDS->exec("CREATE TABLE director_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5869 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMovie, 'movie' FROM directorlinkmovie");
5870 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idShow, 'tvshow' FROM directorlinktvshow");
5871 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idEpisode, 'episode' FROM directorlinkepisode");
5872 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMVideo, 'musicvideo' FROM directorlinkmusicvideo");
5873 m_pDS->exec("DROP TABLE IF EXISTS directorlinkmovie");
5874 m_pDS->exec("DROP TABLE IF EXISTS directorlinktvshow");
5875 m_pDS->exec("DROP TABLE IF EXISTS directorlinkepisode");
5876 m_pDS->exec("DROP TABLE IF EXISTS directorlinkmusicvideo");
5878 // writers
5879 m_pDS->exec("CREATE TABLE writer_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5880 m_pDS->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idMovie, 'movie' FROM writerlinkmovie");
5881 m_pDS->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idEpisode, 'episode' FROM writerlinkepisode");
5882 m_pDS->exec("DROP TABLE IF EXISTS writerlinkmovie");
5883 m_pDS->exec("DROP TABLE IF EXISTS writerlinkepisode");
5885 // music artist
5886 m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type) SELECT DISTINCT idArtist, idMVideo, 'musicvideo' FROM artistlinkmusicvideo");
5887 m_pDS->exec("DROP TABLE IF EXISTS artistlinkmusicvideo");
5889 // studios
5890 m_pDS->exec("CREATE TABLE studio_link(studio_id INTEGER, media_id INTEGER, media_type TEXT)");
5891 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMovie, 'movie' FROM studiolinkmovie");
5892 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idShow, 'tvshow' FROM studiolinktvshow");
5893 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMVideo, 'musicvideo' FROM studiolinkmusicvideo");
5894 m_pDS->exec("DROP TABLE IF EXISTS studiolinkmovie");
5895 m_pDS->exec("DROP TABLE IF EXISTS studiolinktvshow");
5896 m_pDS->exec("DROP TABLE IF EXISTS studiolinkmusicvideo");
5897 m_pDS->exec("CREATE TABLE studionew(studio_id INTEGER PRIMARY KEY, name TEXT)");
5898 m_pDS->exec("INSERT INTO studionew(studio_id, name) SELECT idStudio,strStudio FROM studio");
5899 m_pDS->exec("DROP TABLE IF EXISTS studio");
5900 m_pDS->exec("ALTER TABLE studionew RENAME TO studio");
5902 // genres
5903 m_pDS->exec("CREATE TABLE genre_link(genre_id INTEGER, media_id INTEGER, media_type TEXT)");
5904 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMovie, 'movie' FROM genrelinkmovie");
5905 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idShow, 'tvshow' FROM genrelinktvshow");
5906 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMVideo, 'musicvideo' FROM genrelinkmusicvideo");
5907 m_pDS->exec("DROP TABLE IF EXISTS genrelinkmovie");
5908 m_pDS->exec("DROP TABLE IF EXISTS genrelinktvshow");
5909 m_pDS->exec("DROP TABLE IF EXISTS genrelinkmusicvideo");
5910 m_pDS->exec("CREATE TABLE genrenew(genre_id INTEGER PRIMARY KEY, name TEXT)");
5911 m_pDS->exec("INSERT INTO genrenew(genre_id, name) SELECT idGenre,strGenre FROM genre");
5912 m_pDS->exec("DROP TABLE IF EXISTS genre");
5913 m_pDS->exec("ALTER TABLE genrenew RENAME TO genre");
5915 // country
5916 m_pDS->exec("CREATE TABLE country_link(country_id INTEGER, media_id INTEGER, media_type TEXT)");
5917 m_pDS->exec("INSERT INTO country_link(country_id, media_id, media_type) SELECT DISTINCT idCountry, idMovie, 'movie' FROM countrylinkmovie");
5918 m_pDS->exec("DROP TABLE IF EXISTS countrylinkmovie");
5919 m_pDS->exec("CREATE TABLE countrynew(country_id INTEGER PRIMARY KEY, name TEXT)");
5920 m_pDS->exec("INSERT INTO countrynew(country_id, name) SELECT idCountry,strCountry FROM country");
5921 m_pDS->exec("DROP TABLE IF EXISTS country");
5922 m_pDS->exec("ALTER TABLE countrynew RENAME TO country");
5924 // tags
5925 m_pDS->exec("CREATE TABLE tag_link(tag_id INTEGER, media_id INTEGER, media_type TEXT)");
5926 m_pDS->exec("INSERT INTO tag_link(tag_id, media_id, media_type) SELECT DISTINCT idTag, idMedia, media_type FROM taglinks");
5927 m_pDS->exec("DROP TABLE IF EXISTS taglinks");
5928 m_pDS->exec("CREATE TABLE tagnew(tag_id INTEGER PRIMARY KEY, name TEXT)");
5929 m_pDS->exec("INSERT INTO tagnew(tag_id, name) SELECT idTag,strTag FROM tag");
5930 m_pDS->exec("DROP TABLE IF EXISTS tag");
5931 m_pDS->exec("ALTER TABLE tagnew RENAME TO tag");
5934 if (iVersion < 93)
5936 // cleanup main tables
5937 std::string valuesSql;
5938 for(int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
5940 valuesSql += StringUtils::Format("c{:02} = TRIM(c{:02})", i, i);
5941 if (i < VIDEODB_MAX_COLUMNS - 1)
5942 valuesSql += ",";
5944 m_pDS->exec("UPDATE episode SET " + valuesSql);
5945 m_pDS->exec("UPDATE movie SET " + valuesSql);
5946 m_pDS->exec("UPDATE musicvideo SET " + valuesSql);
5947 m_pDS->exec("UPDATE tvshow SET " + valuesSql);
5949 // cleanup additional tables
5950 std::map<std::string, std::vector<std::string>> additionalTablesMap = {
5951 {"actor", {"actor_link", "director_link", "writer_link"}},
5952 {"studio", {"studio_link"}},
5953 {"genre", {"genre_link"}},
5954 {"country", {"country_link"}},
5955 {"tag", {"tag_link"}}
5957 for (const auto& additionalTableEntry : additionalTablesMap)
5959 std::string table = additionalTableEntry.first;
5960 std::string tablePk = additionalTableEntry.first + "_id";
5961 std::map<int, std::string> duplicatesMinMap;
5962 std::map<int, std::string> duplicatesMap;
5964 // cleanup name
5965 m_pDS->exec(PrepareSQL("UPDATE %s SET name = TRIM(name)",
5966 table.c_str()));
5968 // shrink name to length 255
5969 m_pDS->exec(PrepareSQL("UPDATE %s SET name = SUBSTR(name, 1, 255) WHERE LENGTH(name) > 255",
5970 table.c_str()));
5972 // fetch main entries
5973 m_pDS->query(PrepareSQL("SELECT MIN(%s), name FROM %s GROUP BY name HAVING COUNT(1) > 1",
5974 tablePk.c_str(), table.c_str()));
5976 while (!m_pDS->eof())
5978 duplicatesMinMap.insert(std::make_pair(m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asString()));
5979 m_pDS->next();
5981 m_pDS->close();
5983 // fetch duplicate entries
5984 for (const auto& entry : duplicatesMinMap)
5986 m_pDS->query(PrepareSQL("SELECT %s FROM %s WHERE name = '%s' AND %s <> %i",
5987 tablePk.c_str(), table.c_str(),
5988 entry.second.c_str(), tablePk.c_str(), entry.first));
5990 std::stringstream ids;
5991 while (!m_pDS->eof())
5993 int id = m_pDS->fv(0).get_asInt();
5994 m_pDS->next();
5996 ids << id;
5997 if (!m_pDS->eof())
5998 ids << ",";
6000 m_pDS->close();
6002 duplicatesMap.insert(std::make_pair(entry.first, ids.str()));
6005 // cleanup duplicates in link tables
6006 for (const auto& subTable : additionalTableEntry.second)
6008 // create indexes to speed up things
6009 m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s ON %s (%s)",
6010 subTable.c_str(), subTable.c_str(), tablePk.c_str()));
6012 // migrate every duplicate entry to the main entry
6013 for (const auto& entry : duplicatesMap)
6015 m_pDS->exec(PrepareSQL("UPDATE %s SET %s = %i WHERE %s IN (%s) ",
6016 subTable.c_str(), tablePk.c_str(), entry.first,
6017 tablePk.c_str(), entry.second.c_str()));
6020 // clear all duplicates in the link tables
6021 if (subTable == "actor_link")
6023 // as a distinct won't work because of role and cast_order and a group by kills a
6024 // low powered mysql, we de-dupe it with REPLACE INTO while using the real unique index
6025 m_pDS->exec("CREATE TABLE temp_actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
6026 m_pDS->exec("CREATE UNIQUE INDEX ix_temp_actor_link ON temp_actor_link (actor_id, media_type(20), media_id)");
6027 m_pDS->exec("REPLACE INTO temp_actor_link SELECT * FROM actor_link");
6028 m_pDS->exec("DROP INDEX ix_temp_actor_link ON temp_actor_link");
6030 else
6032 m_pDS->exec(PrepareSQL("CREATE TABLE temp_%s AS SELECT DISTINCT * FROM %s",
6033 subTable.c_str(), subTable.c_str()));
6036 m_pDS->exec(PrepareSQL("DROP TABLE IF EXISTS %s",
6037 subTable.c_str()));
6039 m_pDS->exec(PrepareSQL("ALTER TABLE temp_%s RENAME TO %s",
6040 subTable.c_str(), subTable.c_str()));
6043 // delete duplicates in main table
6044 for (const auto& entry : duplicatesMap)
6046 m_pDS->exec(PrepareSQL("DELETE FROM %s WHERE %s IN (%s)",
6047 table.c_str(), tablePk.c_str(), entry.second.c_str()));
6052 if (iVersion < 96)
6054 m_pDS->exec("ALTER TABLE movie ADD userrating integer");
6055 m_pDS->exec("ALTER TABLE episode ADD userrating integer");
6056 m_pDS->exec("ALTER TABLE tvshow ADD userrating integer");
6057 m_pDS->exec("ALTER TABLE musicvideo ADD userrating integer");
6060 if (iVersion < 97)
6061 m_pDS->exec("ALTER TABLE sets ADD strOverview TEXT");
6063 if (iVersion < 98)
6064 m_pDS->exec("ALTER TABLE seasons ADD name text");
6066 if (iVersion < 99)
6068 // Add idSeason to episode table, so we don't have to join via idShow and season in the future
6069 m_pDS->exec("ALTER TABLE episode ADD idSeason integer");
6071 m_pDS->query("SELECT idSeason, idShow, season FROM seasons");
6072 while (!m_pDS->eof())
6074 m_pDS2->exec(PrepareSQL("UPDATE episode "
6075 "SET idSeason = %d "
6076 "WHERE "
6077 "episode.idShow = %d AND "
6078 "episode.c%02d = %d",
6079 m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asInt(),
6080 VIDEODB_ID_EPISODE_SEASON, m_pDS->fv(2).get_asInt()));
6082 m_pDS->next();
6085 if (iVersion < 101)
6086 m_pDS->exec("ALTER TABLE seasons ADD userrating INTEGER");
6088 if (iVersion < 102)
6090 m_pDS->exec("CREATE TABLE rating (rating_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, rating_type TEXT, rating FLOAT, votes INTEGER)");
6092 std::string sql = PrepareSQL("SELECT DISTINCT idMovie, c%02d, c%02d FROM movie", VIDEODB_ID_RATING_ID, VIDEODB_ID_VOTES);
6093 m_pDS->query(sql);
6094 while (!m_pDS->eof())
6096 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6097 "votes) VALUES (%i, 'movie', 'default', %f, %i)",
6098 m_pDS->fv(0).get_asInt(),
6099 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
6100 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
6101 int idRating = (int)m_pDS2->lastinsertid();
6102 m_pDS2->exec(PrepareSQL("UPDATE movie SET c%02d=%i WHERE idMovie=%i", VIDEODB_ID_RATING_ID, idRating, m_pDS->fv(0).get_asInt()));
6103 m_pDS->next();
6105 m_pDS->close();
6107 sql = PrepareSQL("SELECT DISTINCT idShow, c%02d, c%02d FROM tvshow", VIDEODB_ID_TV_RATING_ID, VIDEODB_ID_TV_VOTES);
6108 m_pDS->query(sql);
6109 while (!m_pDS->eof())
6111 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6112 "votes) VALUES (%i, 'tvshow', 'default', %f, %i)",
6113 m_pDS->fv(0).get_asInt(),
6114 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
6115 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
6116 int idRating = (int)m_pDS2->lastinsertid();
6117 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()));
6118 m_pDS->next();
6120 m_pDS->close();
6122 sql = PrepareSQL("SELECT DISTINCT idEpisode, c%02d, c%02d FROM episode", VIDEODB_ID_EPISODE_RATING_ID, VIDEODB_ID_EPISODE_VOTES);
6123 m_pDS->query(sql);
6124 while (!m_pDS->eof())
6126 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6127 "votes) VALUES (%i, 'episode', 'default', %f, %i)",
6128 m_pDS->fv(0).get_asInt(),
6129 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
6130 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
6131 int idRating = (int)m_pDS2->lastinsertid();
6132 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()));
6133 m_pDS->next();
6135 m_pDS->close();
6138 if (iVersion < 103)
6140 m_pDS->exec("ALTER TABLE settings ADD VideoStream integer");
6141 m_pDS->exec("ALTER TABLE streamdetails ADD strVideoLanguage text");
6144 if (iVersion < 104)
6146 m_pDS->exec("ALTER TABLE tvshow ADD duration INTEGER");
6148 std::string sql = PrepareSQL( "SELECT episode.idShow, MAX(episode.c%02d) "
6149 "FROM episode "
6151 "LEFT JOIN streamdetails "
6152 "ON streamdetails.idFile = episode.idFile "
6153 "AND streamdetails.iStreamType = 0 " // only grab video streams
6155 "WHERE episode.c%02d <> streamdetails.iVideoDuration "
6156 "OR streamdetails.iVideoDuration IS NULL "
6157 "GROUP BY episode.idShow", VIDEODB_ID_EPISODE_RUNTIME, VIDEODB_ID_EPISODE_RUNTIME);
6159 m_pDS->query(sql);
6160 while (!m_pDS->eof())
6162 m_pDS2->exec(PrepareSQL("UPDATE tvshow SET duration=%i WHERE idShow=%i", m_pDS->fv(1).get_asInt(), m_pDS->fv(0).get_asInt()));
6163 m_pDS->next();
6165 m_pDS->close();
6168 if (iVersion < 105)
6170 m_pDS->exec("ALTER TABLE movie ADD premiered TEXT");
6171 m_pDS->exec(PrepareSQL("UPDATE movie SET premiered=c%02d", VIDEODB_ID_YEAR));
6172 m_pDS->exec("ALTER TABLE musicvideo ADD premiered TEXT");
6173 m_pDS->exec(PrepareSQL("UPDATE musicvideo SET premiered=c%02d", VIDEODB_ID_MUSICVIDEO_YEAR));
6176 if (iVersion < 107)
6178 // need this due to the nested GetScraperPath query
6179 std::unique_ptr<Dataset> pDS;
6180 pDS.reset(m_pDB->CreateDataset());
6181 if (nullptr == pDS)
6182 return;
6184 pDS->exec("CREATE TABLE uniqueid (uniqueid_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, value TEXT, type TEXT)");
6186 for (int i = 0; i < 3; ++i)
6188 std::string mediatype, columnID;
6189 int columnUniqueID;
6190 switch (i)
6192 case (0):
6193 mediatype = "movie";
6194 columnID = "idMovie";
6195 columnUniqueID = VIDEODB_ID_IDENT_ID;
6196 break;
6197 case (1):
6198 mediatype = "tvshow";
6199 columnID = "idShow";
6200 columnUniqueID = VIDEODB_ID_TV_IDENT_ID;
6201 break;
6202 case (2):
6203 mediatype = "episode";
6204 columnID = "idEpisode";
6205 columnUniqueID = VIDEODB_ID_EPISODE_IDENT_ID;
6206 break;
6207 default:
6208 continue;
6210 pDS->query(PrepareSQL("SELECT %s, c%02d FROM %s", columnID.c_str(), columnUniqueID, mediatype.c_str()));
6211 while (!pDS->eof())
6213 std::string uniqueid = pDS->fv(1).get_asString();
6214 if (!uniqueid.empty())
6216 int mediaid = pDS->fv(0).get_asInt();
6217 if (StringUtils::StartsWith(uniqueid, "tt"))
6218 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()));
6219 else
6220 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()));
6221 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));
6223 pDS->next();
6225 pDS->close();
6229 if (iVersion < 109)
6231 m_pDS->exec("ALTER TABLE settings RENAME TO settingsold");
6232 m_pDS->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
6233 "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
6234 "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
6235 "VolumeAmplification float, AudioDelay float, ResumeTime integer,"
6236 "Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
6237 "ScalingMethod integer, DeinterlaceMode integer, StereoMode integer, StereoInvert bool, VideoStream integer)");
6238 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");
6239 m_pDS->exec("DROP TABLE settingsold");
6242 if (iVersion < 110)
6244 m_pDS->exec("ALTER TABLE settings ADD TonemapMethod integer");
6245 m_pDS->exec("ALTER TABLE settings ADD TonemapParam float");
6248 if (iVersion < 111)
6249 m_pDS->exec("ALTER TABLE settings ADD Orientation integer");
6251 if (iVersion < 112)
6252 m_pDS->exec("ALTER TABLE settings ADD CenterMixLevel integer");
6254 if (iVersion < 113)
6256 // fb9c25f5 and e5f6d204 changed the behavior of path splitting for plugin URIs (previously it would only use the root)
6257 // Re-split paths for plugin files in order to maintain watched state etc.
6258 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://%'");
6259 while (!m_pDS->eof())
6261 std::string path, fn;
6262 SplitPath(m_pDS->fv(1).get_asString(), path, fn);
6263 if (path != m_pDS->fv(2).get_asString())
6265 int pathid = -1;
6266 m_pDS2->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", path.c_str()));
6267 if (!m_pDS2->eof())
6268 pathid = m_pDS2->fv(0).get_asInt();
6269 m_pDS2->close();
6270 if (pathid < 0)
6272 std::string parent = URIUtils::GetParentPath(path);
6273 int parentid = -1;
6274 m_pDS2->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", parent.c_str()));
6275 if (!m_pDS2->eof())
6276 parentid = m_pDS2->fv(0).get_asInt();
6277 m_pDS2->close();
6278 if (parentid < 0)
6280 m_pDS2->exec(PrepareSQL("INSERT INTO path (strPath) VALUES ('%s')", parent.c_str()));
6281 parentid = (int)m_pDS2->lastinsertid();
6283 m_pDS2->exec(PrepareSQL("INSERT INTO path (strPath, idParentPath) VALUES ('%s', %i)", path.c_str(), parentid));
6284 pathid = (int)m_pDS2->lastinsertid();
6286 m_pDS2->query(PrepareSQL("SELECT idFile FROM files WHERE strFileName='%s' AND idPath=%i", fn.c_str(), pathid));
6287 bool exists = !m_pDS2->eof();
6288 m_pDS2->close();
6289 if (exists)
6290 m_pDS2->exec(PrepareSQL("DELETE FROM files WHERE idFile=%i", m_pDS->fv(0).get_asInt()));
6291 else
6292 m_pDS2->exec(PrepareSQL("UPDATE files SET idPath=%i WHERE idFile=%i", pathid, m_pDS->fv(0).get_asInt()));
6294 m_pDS->next();
6296 m_pDS->close();
6299 if (iVersion < 119)
6300 m_pDS->exec("ALTER TABLE path ADD allAudio bool");
6302 if (iVersion < 120)
6303 m_pDS->exec("ALTER TABLE streamdetails ADD strHdrType text");
6305 if (iVersion < 121)
6307 // https://github.com/xbmc/xbmc/issues/21253 - Kodi picks up wrong "year" for PVR recording.
6309 m_pDS->query("SELECT idFile, strFilename FROM files WHERE strFilename LIKE '% (1969)%.pvr' OR "
6310 "strFilename LIKE '% (1601)%.pvr'");
6311 while (!m_pDS->eof())
6313 std::string fixedFileName = m_pDS->fv(1).get_asString();
6314 size_t pos = fixedFileName.find(" (1969)");
6315 if (pos == std::string::npos)
6316 pos = fixedFileName.find(" (1601)");
6318 if (pos != std::string::npos)
6320 fixedFileName.erase(pos, 7);
6322 m_pDS2->exec(PrepareSQL("UPDATE files SET strFilename='%s' WHERE idFile=%i",
6323 fixedFileName.c_str(), m_pDS->fv(0).get_asInt()));
6325 m_pDS->next();
6327 m_pDS->close();
6330 if (iVersion < 123)
6332 // create videoversiontype table
6333 m_pDS->exec("CREATE TABLE videoversiontype (id INTEGER PRIMARY KEY, name TEXT, owner INTEGER)");
6334 InitializeVideoVersionTypeTable(iVersion);
6336 // create videoversion table
6337 m_pDS->exec("CREATE TABLE videoversion (idFile INTEGER PRIMARY KEY, idMedia INTEGER, mediaType "
6338 "TEXT, itemType INTEGER, idType INTEGER)");
6339 m_pDS->exec(PrepareSQL(
6340 "INSERT INTO videoversion SELECT idFile, idMovie, 'movie', '%i', '%i' FROM movie",
6341 VideoAssetType::VERSION, VIDEO_VERSION_ID_DEFAULT));
6344 if (iVersion < 127)
6346 m_pDS->exec("ALTER TABLE videoversiontype ADD itemType INTEGER");
6348 // First, assume all types are video version types
6349 m_pDS->exec(PrepareSQL("UPDATE videoversiontype SET itemType = %i", VideoAssetType::VERSION));
6351 // Then, check current extras entries and their assigned item type and migrate it
6353 // get all assets with extras item type
6354 m_pDS->query("SELECT DISTINCT idType FROM videoversion WHERE itemType = 1");
6355 while (!m_pDS->eof())
6357 const int idType{m_pDS->fv(0).get_asInt()};
6358 if (idType > VIDEO_VERSION_ID_END)
6360 // user-added type for extras. change its item type to extras
6361 m_pDS2->exec(PrepareSQL("UPDATE videoversiontype SET itemType = %i WHERE id = %i",
6362 VideoAssetType::EXTRA, idType));
6364 else
6366 // system type used for an extra. copy as extras item type.
6367 m_pDS2->query(
6368 PrepareSQL("SELECT itemType, name FROM videoversiontype WHERE id = %i", idType));
6369 if (m_pDS2->fv(0).get_asInt() == 0)
6371 // currently a versions type, create a corresponding user-added type for extras
6372 m_pDS2->exec(PrepareSQL(
6373 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(NULL, '%s', %i, %i)",
6374 m_pDS2->fv(1).get_asString().c_str(), VideoAssetTypeOwner::USER,
6375 VideoAssetType::EXTRA));
6377 // update the respective extras to use the new extras type
6378 const int newId{static_cast<int>(m_pDS2->lastinsertid())};
6379 m_pDS2->exec(
6380 PrepareSQL("UPDATE videoversion SET idType = %i WHERE itemType = 1 AND idType = %i",
6381 newId, idType));
6384 m_pDS->next();
6386 m_pDS->close();
6389 if (iVersion < 128)
6391 m_pDS->exec("CREATE TABLE videoversion_new "
6392 "(idFile INTEGER PRIMARY KEY, idMedia INTEGER, media_type TEXT, "
6393 " itemType INTEGER, idType INTEGER)");
6394 m_pDS->exec("INSERT INTO videoversion_new "
6395 " (idFile, idMedia, media_type, itemType, idType) "
6396 "SELECT idFile, idMedia, mediaType, itemType, idType FROM videoversion");
6397 m_pDS->exec("DROP TABLE videoversion");
6398 m_pDS->exec("ALTER TABLE videoversion_new RENAME TO videoversion");
6400 // Fix gap in the migration to videodb v127 for unused user-defined video version types.
6401 // Unfortunately due to original design we cannot tell which ones were movie versions or
6402 // extras and now they're all displayed in the version type selection for movies.
6403 // Remove them all as the better fix of providing a GUI to manage version types will not be
6404 // available in Omega v21. That implies the loss of the unused user-defined version names
6405 // created since v21 beta 2.
6406 m_pDS2->exec(PrepareSQL("DELETE FROM videoversiontype "
6407 "WHERE id NOT IN (SELECT idType FROM videoversion) "
6408 "AND owner = %i "
6409 "AND itemType = %i",
6410 VideoAssetTypeOwner::USER, VideoAssetType::VERSION));
6413 if (iVersion < 131)
6415 // Remove quality-like predefined version types
6417 // Retrieve current utilization per type
6418 m_pDS->query("SELECT vvt.id, vvt.name, count(vv.idType) "
6419 "FROM videoversiontype vvt "
6420 " LEFT JOIN videoversion vv ON vvt.id = vv.idType "
6421 "WHERE vvt.id = 40405 OR vvt.id BETWEEN 40418 AND 40430 "
6422 "GROUP BY vvt.id");
6424 while (!m_pDS->eof())
6426 const int typeId{m_pDS->fv(0).get_asInt()};
6427 const std::string typeName{m_pDS->fv(1).get_asString()};
6428 const int versionsCount{m_pDS->fv(2).get_asInt()};
6430 if (versionsCount > 0)
6432 // type used by some versions, recreate as user type and link the versions to the new id
6433 m_pDS2->exec(PrepareSQL(
6434 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(NULL, '%s', %i, %i)",
6435 typeName.c_str(), VideoAssetTypeOwner::USER, VideoAssetType::VERSION));
6437 const int newId{static_cast<int>(m_pDS2->lastinsertid())};
6439 m_pDS2->exec(
6440 PrepareSQL("UPDATE videoversion SET idType = %i WHERE idType = %i", newId, typeId));
6442 m_pDS2->exec(PrepareSQL("DELETE FROM videoversiontype WHERE id = %i", typeId));
6443 m_pDS->next();
6445 m_pDS->close();
6448 if (iVersion < 133)
6450 // Remove episodes with invalid idSeason values.
6451 // Since 2015 they were masked from episode_view and are not going to be missed.
6452 // Those records would be misses in database converted in 2015 (see videodb version 99).
6454 m_pDS->exec("DELETE FROM episode WHERE idSeason NOT IN (SELECT idSeason from seasons)");
6458 int CVideoDatabase::GetSchemaVersion() const
6460 return 133;
6463 bool CVideoDatabase::LookupByFolders(const std::string &path, bool shows)
6465 SScanSettings settings;
6466 bool foundDirectly = false;
6467 ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
6468 if (scraper && scraper->Content() == CONTENT_TVSHOWS && !shows)
6469 return false; // episodes
6470 return settings.parent_name_root; // shows, movies, musicvids
6473 bool CVideoDatabase::GetPlayCounts(const std::string &strPath, CFileItemList &items)
6475 if(URIUtils::IsMultiPath(strPath))
6477 std::vector<std::string> paths;
6478 CMultiPathDirectory::GetPaths(strPath, paths);
6480 bool ret = false;
6481 for(unsigned i=0;i<paths.size();i++)
6482 ret |= GetPlayCounts(paths[i], items);
6484 return ret;
6486 int pathID = -1;
6487 if (!URIUtils::IsPlugin(strPath))
6489 pathID = GetPathId(strPath);
6490 if (pathID < 0)
6491 return false; // path (and thus files) aren't in the database
6496 // error!
6497 if (nullptr == m_pDB)
6498 return false;
6499 if (nullptr == m_pDS)
6500 return false;
6502 std::string sql =
6503 "SELECT"
6504 " files.strFilename, files.playCount,"
6505 " bookmark.timeInSeconds, bookmark.totalTimeInSeconds "
6506 "FROM files"
6507 " LEFT JOIN bookmark ON"
6508 " files.idFile = bookmark.idFile AND bookmark.type = %i ";
6510 if (URIUtils::IsPlugin(strPath))
6512 for (auto& item : items)
6514 if (!item || item->m_bIsFolder || !item->GetProperty("IsPlayable").asBoolean())
6515 continue;
6517 std::string path, filename;
6518 SplitPath(item->GetPath(), path, filename);
6519 m_pDS->query(PrepareSQL(sql +
6520 "INNER JOIN path ON files.idPath = path.idPath "
6521 "WHERE files.strFilename='%s' AND path.strPath='%s'",
6522 (int)CBookmark::RESUME, filename.c_str(), path.c_str()));
6524 if (!m_pDS->eof())
6526 if (!item->GetVideoInfoTag()->IsPlayCountSet())
6527 item->GetVideoInfoTag()->SetPlayCount(m_pDS->fv(1).get_asInt());
6528 if (!item->GetVideoInfoTag()->GetResumePoint().IsSet())
6529 item->GetVideoInfoTag()->SetResumePoint(m_pDS->fv(2).get_asInt(), m_pDS->fv(3).get_asInt(), "");
6531 m_pDS->close();
6534 else
6536 //! @todo also test a single query for the above and below
6537 sql = PrepareSQL(sql + "WHERE files.idPath=%i", (int)CBookmark::RESUME, pathID);
6539 if (RunQuery(sql) <= 0)
6540 return false;
6542 items.SetFastLookup(true); // note: it's possibly quicker the other way around (map on db returned items)?
6543 while (!m_pDS->eof())
6545 std::string path;
6546 ConstructPath(path, strPath, m_pDS->fv(0).get_asString());
6547 CFileItemPtr item = items.Get(path);
6548 if (item)
6550 if (!items.IsPlugin() || !item->GetVideoInfoTag()->IsPlayCountSet())
6551 item->GetVideoInfoTag()->SetPlayCount(m_pDS->fv(1).get_asInt());
6553 if (!item->GetVideoInfoTag()->GetResumePoint().IsSet())
6555 item->GetVideoInfoTag()->SetResumePoint(m_pDS->fv(2).get_asInt(), m_pDS->fv(3).get_asInt(), "");
6558 m_pDS->next();
6562 return true;
6564 catch (...)
6566 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6568 return false;
6571 int CVideoDatabase::GetPlayCount(int iFileId)
6573 if (iFileId < 0)
6574 return 0; // not in db, so not watched
6578 // error!
6579 if (nullptr == m_pDB)
6580 return -1;
6581 if (nullptr == m_pDS)
6582 return -1;
6584 std::string strSQL = PrepareSQL("select playCount from files WHERE idFile=%i", iFileId);
6585 int count = 0;
6586 if (m_pDS->query(strSQL))
6588 // there should only ever be one row returned
6589 if (m_pDS->num_rows() == 1)
6590 count = m_pDS->fv(0).get_asInt();
6591 m_pDS->close();
6593 return count;
6595 catch (...)
6597 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6599 return -1;
6602 int CVideoDatabase::GetPlayCount(const std::string& strFilenameAndPath)
6604 return GetPlayCount(GetFileId(strFilenameAndPath));
6607 int CVideoDatabase::GetPlayCount(const CFileItem &item)
6609 if (IsBlurayPlaylist(item))
6610 return GetPlayCount(GetFileId(item.GetDynPath()));
6611 else
6612 return GetPlayCount(GetFileId(item));
6615 CDateTime CVideoDatabase::GetLastPlayed(int iFileId)
6617 if (iFileId < 0)
6618 return {}; // not in db, so not watched
6622 // error!
6623 if (nullptr == m_pDB)
6624 return {};
6625 if (nullptr == m_pDS)
6626 return {};
6628 std::string strSQL = PrepareSQL("select lastPlayed from files WHERE idFile=%i", iFileId);
6629 CDateTime lastPlayed;
6630 if (m_pDS->query(strSQL))
6632 // there should only ever be one row returned
6633 if (m_pDS->num_rows() == 1)
6634 lastPlayed.SetFromDBDateTime(m_pDS->fv(0).get_asString());
6635 m_pDS->close();
6637 return lastPlayed;
6639 catch (...)
6641 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6643 return {};
6646 CDateTime CVideoDatabase::GetLastPlayed(const std::string& strFilenameAndPath)
6648 return GetLastPlayed(GetFileId(strFilenameAndPath));
6651 void CVideoDatabase::UpdateFanart(const CFileItem& item, VideoDbContentType type)
6653 if (nullptr == m_pDB)
6654 return;
6655 if (nullptr == m_pDS)
6656 return;
6657 if (!item.HasVideoInfoTag() || item.GetVideoInfoTag()->m_iDbId < 0) return;
6659 std::string exec;
6660 if (type == VideoDbContentType::TVSHOWS)
6661 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);
6662 else if (type == VideoDbContentType::MOVIES)
6663 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);
6667 m_pDS->exec(exec);
6669 if (type == VideoDbContentType::TVSHOWS)
6670 AnnounceUpdate(MediaTypeTvShow, item.GetVideoInfoTag()->m_iDbId);
6671 else if (type == VideoDbContentType::MOVIES)
6672 AnnounceUpdate(MediaTypeMovie, item.GetVideoInfoTag()->m_iDbId);
6674 catch (...)
6676 CLog::Log(LOGERROR, "{} - error updating fanart for {}", __FUNCTION__, item.GetPath());
6680 CDateTime CVideoDatabase::SetPlayCount(const CFileItem& item, int count, const CDateTime& date)
6682 int id{-1};
6683 if (IsBlurayPlaylist(item))
6684 id = AddFile(item.GetDynPath());
6685 else if (item.HasProperty("original_listitem_url") &&
6686 URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString()))
6688 CFileItem item2(item);
6689 item2.SetPath(item.GetProperty("original_listitem_url").asString());
6690 id = AddFile(item2);
6692 else
6693 id = AddFile(item);
6694 if (id < 0)
6695 return {};
6697 // and mark as watched
6700 const CDateTime lastPlayed(date.IsValid() ? date : CDateTime::GetCurrentDateTime());
6702 if (nullptr == m_pDB)
6703 return {};
6704 if (nullptr == m_pDS)
6705 return {};
6707 std::string strSQL;
6708 if (count)
6710 strSQL = PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", count,
6711 lastPlayed.GetAsDBDateTime().c_str(), id);
6713 else
6715 if (!date.IsValid())
6716 strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed=NULL where idFile=%i", id);
6717 else
6718 strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed='%s' where idFile=%i",
6719 lastPlayed.GetAsDBDateTime().c_str(), id);
6722 m_pDS->exec(strSQL);
6724 // We only need to announce changes to video items in the library
6725 if (item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId > 0)
6727 CVariant data;
6728 if (CVideoLibraryQueue::GetInstance().IsScanningLibrary())
6729 data["transaction"] = true;
6730 // Only provide the "playcount" value if it has actually changed
6731 if (item.GetVideoInfoTag()->GetPlayCount() != count)
6732 data["playcount"] = count;
6733 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnUpdate",
6734 std::make_shared<CFileItem>(item), data);
6737 return lastPlayed;
6739 catch (...)
6741 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6744 return {};
6747 CDateTime CVideoDatabase::IncrementPlayCount(const CFileItem& item)
6749 return SetPlayCount(item, GetPlayCount(item) + 1);
6752 CDateTime CVideoDatabase::UpdateLastPlayed(const CFileItem& item)
6754 return SetPlayCount(item, GetPlayCount(item), CDateTime::GetCurrentDateTime());
6757 void CVideoDatabase::UpdateMovieTitle(int idMovie,
6758 const std::string& strNewMovieTitle,
6759 VideoDbContentType iType)
6763 if (nullptr == m_pDB)
6764 return;
6765 if (nullptr == m_pDS)
6766 return;
6767 std::string content;
6768 if (iType == VideoDbContentType::MOVIES)
6770 CLog::Log(LOGINFO, "Changing Movie:id:{} New Title:{}", idMovie, strNewMovieTitle);
6771 content = MediaTypeMovie;
6773 else if (iType == VideoDbContentType::EPISODES)
6775 CLog::Log(LOGINFO, "Changing Episode:id:{} New Title:{}", idMovie, strNewMovieTitle);
6776 content = MediaTypeEpisode;
6778 else if (iType == VideoDbContentType::TVSHOWS)
6780 CLog::Log(LOGINFO, "Changing TvShow:id:{} New Title:{}", idMovie, strNewMovieTitle);
6781 content = MediaTypeTvShow;
6783 else if (iType == VideoDbContentType::MUSICVIDEOS)
6785 CLog::Log(LOGINFO, "Changing MusicVideo:id:{} New Title:{}", idMovie, strNewMovieTitle);
6786 content = MediaTypeMusicVideo;
6788 else if (iType == VideoDbContentType::MOVIE_SETS)
6790 CLog::Log(LOGINFO, "Changing Movie set:id:{} New Title:{}", idMovie, strNewMovieTitle);
6791 std::string strSQL = PrepareSQL("UPDATE sets SET strSet='%s' WHERE idSet=%i", strNewMovieTitle.c_str(), idMovie );
6792 m_pDS->exec(strSQL);
6795 if (!content.empty())
6797 SetSingleValue(iType, idMovie, FieldTitle, strNewMovieTitle);
6798 AnnounceUpdate(content, idMovie);
6801 catch (...)
6803 CLog::Log(
6804 LOGERROR,
6805 "{} (int idMovie, const std::string& strNewMovieTitle) failed on MovieID:{} and Title:{}",
6806 __FUNCTION__, idMovie, strNewMovieTitle);
6810 bool CVideoDatabase::UpdateVideoSortTitle(int idDb,
6811 const std::string& strNewSortTitle,
6812 VideoDbContentType iType /* = MOVIES */)
6816 if (nullptr == m_pDB || nullptr == m_pDS)
6817 return false;
6818 if (iType != VideoDbContentType::MOVIES && iType != VideoDbContentType::TVSHOWS)
6819 return false;
6821 std::string content = MediaTypeMovie;
6822 if (iType == VideoDbContentType::TVSHOWS)
6823 content = MediaTypeTvShow;
6825 if (SetSingleValue(iType, idDb, FieldSortTitle, strNewSortTitle))
6827 AnnounceUpdate(content, idDb);
6828 return true;
6831 catch (...)
6833 CLog::Log(LOGERROR,
6834 "{} (int idDb, const std::string& strNewSortTitle, VIDEODB_CONTENT_TYPE iType) "
6835 "failed on ID: {} and Sort Title: {}",
6836 __FUNCTION__, idDb, strNewSortTitle);
6839 return false;
6842 /// \brief EraseVideoSettings() Erases the videoSettings table and reconstructs it
6843 void CVideoDatabase::EraseVideoSettings(const CFileItem &item)
6845 int idFile = GetFileId(item);
6846 if (idFile < 0)
6847 return;
6851 std::string sql = PrepareSQL("DELETE FROM settings WHERE idFile=%i", idFile);
6853 CLog::Log(LOGINFO, "Deleting settings information for files {}",
6854 CURL::GetRedacted(item.GetPath()));
6855 m_pDS->exec(sql);
6857 catch (...)
6859 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6863 void CVideoDatabase::EraseAllVideoSettings()
6867 std::string sql = "DELETE FROM settings";
6869 CLog::Log(LOGINFO, "Deleting all video settings");
6870 m_pDS->exec(sql);
6872 catch (...)
6874 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6878 void CVideoDatabase::EraseAllVideoSettings(const std::string& path)
6880 std::string itemsToDelete;
6884 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());
6885 m_pDS->query(sql);
6886 while (!m_pDS->eof())
6888 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
6889 itemsToDelete += file;
6890 m_pDS->next();
6892 m_pDS->close();
6894 if (!itemsToDelete.empty())
6896 itemsToDelete = "(" + StringUtils::TrimRight(itemsToDelete, ",") + ")";
6898 sql = "DELETE FROM settings WHERE idFile IN " + itemsToDelete;
6899 m_pDS->exec(sql);
6902 catch (...)
6904 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6908 bool CVideoDatabase::GetGenresNav(const std::string& strBaseDir,
6909 CFileItemList& items,
6910 VideoDbContentType idContent /* = UNKNOWN */,
6911 const Filter& filter /* = Filter() */,
6912 bool countOnly /* = false */)
6914 return GetNavCommon(strBaseDir, items, "genre", idContent, filter, countOnly);
6917 bool CVideoDatabase::GetCountriesNav(const std::string& strBaseDir,
6918 CFileItemList& items,
6919 VideoDbContentType idContent /* = UNKNOWN */,
6920 const Filter& filter /* = Filter() */,
6921 bool countOnly /* = false */)
6923 return GetNavCommon(strBaseDir, items, "country", idContent, filter, countOnly);
6926 bool CVideoDatabase::GetStudiosNav(const std::string& strBaseDir,
6927 CFileItemList& items,
6928 VideoDbContentType idContent /* = UNKNOWN */,
6929 const Filter& filter /* = Filter() */,
6930 bool countOnly /* = false */)
6932 return GetNavCommon(strBaseDir, items, "studio", idContent, filter, countOnly);
6935 bool CVideoDatabase::GetNavCommon(const std::string& strBaseDir,
6936 CFileItemList& items,
6937 const char* type,
6938 VideoDbContentType idContent /* = UNKNOWN */,
6939 const Filter& filter /* = Filter() */,
6940 bool countOnly /* = false */)
6944 if (nullptr == m_pDB)
6945 return false;
6946 if (nullptr == m_pDS)
6947 return false;
6949 std::string strSQL;
6950 Filter extFilter = filter;
6951 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6953 std::string view, view_id, media_type, extraField, extraJoin;
6954 if (idContent == VideoDbContentType::MOVIES)
6956 view = MediaTypeMovie;
6957 view_id = "idMovie";
6958 media_type = MediaTypeMovie;
6959 extraField = "files.playCount";
6961 else if (idContent == VideoDbContentType::TVSHOWS) //this will not get tvshows with 0 episodes
6963 view = MediaTypeEpisode;
6964 view_id = "idShow";
6965 media_type = MediaTypeTvShow;
6966 // in order to make use of FieldPlaycount in smart playlists we need an extra join
6967 if (StringUtils::EqualsNoCase(type, "tag"))
6968 extraJoin = PrepareSQL("JOIN tvshow_view ON tvshow_view.idShow = tag_link.media_id AND tag_link.media_type='tvshow'");
6970 else if (idContent == VideoDbContentType::MUSICVIDEOS)
6972 view = MediaTypeMusicVideo;
6973 view_id = "idMVideo";
6974 media_type = MediaTypeMusicVideo;
6975 extraField = "files.playCount";
6977 else
6978 return false;
6980 strSQL = "SELECT {} " + PrepareSQL("FROM %s ", type);
6981 extFilter.fields = PrepareSQL("%s.%s_id, %s.name, path.strPath", type, type, type);
6982 extFilter.AppendField(extraField);
6983 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type, type, type, type, type));
6984 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()));
6985 extFilter.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str()));
6986 extFilter.AppendJoin("JOIN path ON path.idPath = files.idPath");
6987 extFilter.AppendJoin(extraJoin);
6989 else
6991 std::string view, view_id, media_type, extraField, extraJoin;
6992 if (idContent == VideoDbContentType::MOVIES)
6994 view = MediaTypeMovie;
6995 view_id = "idMovie";
6996 media_type = MediaTypeMovie;
6997 extraField = "count(1), count(files.playCount)";
6998 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
7000 else if (idContent == VideoDbContentType::TVSHOWS)
7002 view = MediaTypeTvShow;
7003 view_id = "idShow";
7004 media_type = MediaTypeTvShow;
7006 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7008 view = MediaTypeMusicVideo;
7009 view_id = "idMVideo";
7010 media_type = MediaTypeMusicVideo;
7011 extraField = "count(1), count(files.playCount)";
7012 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
7014 else
7015 return false;
7017 strSQL = "SELECT {} " + PrepareSQL("FROM %s ", type);
7018 extFilter.fields = PrepareSQL("%s.%s_id, %s.name", type, type, type);
7019 extFilter.AppendField(extraField);
7020 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type, type, type, type, type));
7021 extFilter.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'",
7022 view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
7023 extFilter.AppendJoin(extraJoin);
7024 extFilter.AppendGroup(PrepareSQL("%s.%s_id", type, type));
7027 if (countOnly)
7029 extFilter.fields = PrepareSQL("COUNT(DISTINCT %s.%s_id)", type, type);
7030 extFilter.group.clear();
7031 extFilter.order.clear();
7033 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
7035 CVideoDbUrl videoUrl;
7036 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
7037 return false;
7039 int iRowsFound = RunQuery(strSQL);
7040 if (iRowsFound <= 0)
7041 return iRowsFound == 0;
7043 if (countOnly)
7045 CFileItemPtr pItem(new CFileItem());
7046 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
7047 items.Add(pItem);
7049 m_pDS->close();
7050 return true;
7053 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7055 std::map<int, std::pair<std::string,int> > mapItems;
7056 while (!m_pDS->eof())
7058 int id = m_pDS->fv(0).get_asInt();
7059 std::string str = m_pDS->fv(1).get_asString();
7061 // was this already found?
7062 auto it = mapItems.find(id);
7063 if (it == mapItems.end())
7065 // check path
7066 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7068 if (idContent == VideoDbContentType::MOVIES ||
7069 idContent == VideoDbContentType::MUSICVIDEOS)
7070 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
7071 else if (idContent == VideoDbContentType::TVSHOWS)
7072 mapItems.insert(std::pair<int, std::pair<std::string,int> >(id, std::pair<std::string,int>(str,0)));
7075 m_pDS->next();
7077 m_pDS->close();
7079 for (const auto &i : mapItems)
7081 CFileItemPtr pItem(new CFileItem(i.second.first));
7082 pItem->GetVideoInfoTag()->m_iDbId = i.first;
7083 pItem->GetVideoInfoTag()->m_type = type;
7085 CVideoDbUrl itemUrl = videoUrl;
7086 std::string path = StringUtils::Format("{}/", i.first);
7087 itemUrl.AppendPath(path);
7088 pItem->SetPath(itemUrl.ToString());
7090 pItem->m_bIsFolder = true;
7091 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7092 pItem->GetVideoInfoTag()->SetPlayCount(i.second.second);
7093 if (!items.Contains(pItem->GetPath()))
7095 pItem->SetLabelPreformatted(true);
7096 items.Add(pItem);
7100 else
7102 while (!m_pDS->eof())
7104 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
7105 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
7106 pItem->GetVideoInfoTag()->m_type = type;
7108 CVideoDbUrl itemUrl = videoUrl;
7109 std::string path = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
7110 itemUrl.AppendPath(path);
7111 pItem->SetPath(itemUrl.ToString());
7113 pItem->m_bIsFolder = true;
7114 pItem->SetLabelPreformatted(true);
7115 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7116 { // fv(3) is the number of videos watched, fv(2) is the total number. We set the playcount
7117 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7118 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(3).get_asInt() == m_pDS->fv(2).get_asInt()) ? 1 : 0);
7120 items.Add(pItem);
7121 m_pDS->next();
7123 m_pDS->close();
7125 return true;
7127 catch (...)
7129 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7131 return false;
7134 bool CVideoDatabase::GetTagsNav(const std::string& strBaseDir,
7135 CFileItemList& items,
7136 VideoDbContentType idContent /* = UNKNOWN */,
7137 const Filter& filter /* = Filter() */,
7138 bool countOnly /* = false */)
7140 return GetNavCommon(strBaseDir, items, "tag", idContent, filter, countOnly);
7143 bool CVideoDatabase::GetSetsNav(const std::string& strBaseDir,
7144 CFileItemList& items,
7145 VideoDbContentType idContent /* = UNKNOWN */,
7146 const Filter& filter /* = Filter() */,
7147 bool ignoreSingleMovieSets /* = false */)
7149 if (idContent != VideoDbContentType::MOVIES)
7150 return false;
7152 return GetSetsByWhere(strBaseDir, filter, items, ignoreSingleMovieSets);
7155 bool CVideoDatabase::GetSetsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool ignoreSingleMovieSets /* = false */)
7159 if (nullptr == m_pDB)
7160 return false;
7161 if (nullptr == m_pDS)
7162 return false;
7164 CVideoDbUrl videoUrl;
7165 if (!videoUrl.FromString(strBaseDir))
7166 return false;
7168 Filter setFilter = filter;
7169 setFilter.join += " JOIN sets ON movie_view.idSet = sets.idSet";
7170 if (!setFilter.order.empty())
7171 setFilter.order += ",";
7172 setFilter.order += "sets.idSet";
7174 if (!GetMoviesByWhere(strBaseDir, setFilter, items))
7175 return false;
7177 CFileItemList sets;
7178 GroupAttribute groupingAttributes;
7179 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
7180 auto option = options.find("ignoreSingleMovieSets");
7182 if (option != options.end())
7184 groupingAttributes =
7185 option->second.asBoolean() ? GroupAttributeIgnoreSingleItems : GroupAttributeNone;
7187 else
7189 groupingAttributes =
7190 ignoreSingleMovieSets ? GroupAttributeIgnoreSingleItems : GroupAttributeNone;
7193 if (!GroupUtils::Group(GroupBySet, strBaseDir, items, sets, groupingAttributes))
7194 return false;
7196 items.ClearItems();
7197 items.Append(sets);
7199 return true;
7201 catch (...)
7203 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7205 return false;
7208 bool CVideoDatabase::GetMusicVideoAlbumsNav(const std::string& strBaseDir, CFileItemList& items, int idArtist /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
7212 if (nullptr == m_pDB)
7213 return false;
7214 if (nullptr == m_pDS)
7215 return false;
7217 CVideoDbUrl videoUrl;
7218 if (!videoUrl.FromString(strBaseDir))
7219 return false;
7221 std::string strSQL = "select {} from musicvideo_view ";
7222 Filter extFilter = filter;
7223 extFilter.fields = PrepareSQL("musicvideo_view.c%02d, musicvideo_view.idMVideo, actor.name, "
7224 "musicvideo_view.c%02d, musicvideo_view.c%02d, musicvideo_view.c%02d ",
7225 VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_TITLE,
7226 VIDEODB_ID_MUSICVIDEO_PLOT, VIDEODB_ID_MUSICVIDEO_ARTIST);
7227 extFilter.AppendJoin(
7228 PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo "));
7229 extFilter.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id = actor_link.actor_id"));
7230 extFilter.fields += ", path.strPath";
7231 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on path.idPath = files.idPath");
7233 if (StringUtils::EndsWith(strBaseDir,"albums/"))
7234 extFilter.AppendWhere(PrepareSQL("musicvideo_view.c%02d != ''", VIDEODB_ID_MUSICVIDEO_ALBUM));
7236 if (idArtist > -1)
7237 videoUrl.AddOption("artistid", idArtist);
7239 extFilter.AppendGroup(PrepareSQL(" CASE WHEN musicvideo_view.c09 !='' THEN musicvideo_view.c09 "
7240 "ELSE musicvideo_view.c00 END"));
7242 if (countOnly)
7244 extFilter.fields = "COUNT(1)";
7245 extFilter.group.clear();
7246 extFilter.order.clear();
7248 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
7250 if (!BuildSQL(videoUrl.ToString(), strSQL, extFilter, strSQL, videoUrl))
7251 return false;
7253 int iRowsFound = RunQuery(strSQL);
7254 /* fields returned by query are :-
7255 (0) - Album title (if any)
7256 (1) - idMVideo
7257 (2) - Artist name
7258 (3) - Music video title
7259 (4) - Music video plot
7260 (5) - Music Video artist
7261 (6) - Path to video
7263 if (iRowsFound <= 0)
7264 return iRowsFound == 0;
7266 std::string strArtist;
7267 if (idArtist> -1)
7268 strArtist = m_pDS->fv("actor.name").get_asString();
7270 if (countOnly)
7272 CFileItemPtr pItem(new CFileItem());
7273 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
7274 items.Add(pItem);
7276 m_pDS->close();
7277 return true;
7280 std::list <int> idMVideoList;
7281 std::list <std::pair<std::string, std::string>> idData;
7283 while (!m_pDS->eof())
7285 bool isAlbum = true;
7286 std::string strAlbum = m_pDS->fv(0).get_asString(); //Album title
7287 int idMVideo = m_pDS->fv(1).get_asInt();
7288 if (strAlbum.empty())
7290 strAlbum = m_pDS->fv(3).get_asString(); // video title if not an album
7291 isAlbum = false;
7294 CFileItemPtr pItem(new CFileItem(strAlbum));
7296 CVideoDbUrl itemUrl = videoUrl;
7297 std::string path = StringUtils::Format("{}/", idMVideo);
7298 if (!isAlbum)
7300 itemUrl.AddOption("albumid", idMVideo);
7301 path += std::to_string(idMVideo);
7303 strSQL = PrepareSQL(
7304 "SELECT type, url FROM art WHERE media_id = %i AND media_type = 'musicvideo'",
7305 idMVideo);
7306 m_pDS2->query(strSQL);
7307 while (!m_pDS2->eof())
7309 pItem->SetArt(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString());
7310 m_pDS2->next();
7312 m_pDS2->close();
7314 itemUrl.AppendPath(path);
7315 pItem->SetPath(itemUrl.ToString());
7316 pItem->m_bIsFolder = isAlbum;
7317 pItem->SetLabelPreformatted(true);
7319 if (!items.Contains(pItem->GetPath()))
7320 if (g_passwordManager.IsDatabasePathUnlocked(
7321 m_pDS->fv("path.strPath").get_asString(),
7322 *CMediaSourceSettings::GetInstance().GetSources("video")))
7324 pItem->GetVideoInfoTag()->m_artist.emplace_back(strArtist);
7325 pItem->GetVideoInfoTag()->m_iDbId = idMVideo;
7326 items.Add(pItem);
7327 idMVideoList.push_back(idMVideo);
7328 idData.emplace_back(m_pDS->fv(0).get_asString(), m_pDS->fv(5).get_asString());
7330 m_pDS->next();
7332 m_pDS->close();
7334 for (int i = 0; i < items.Size(); i++)
7336 CVideoInfoTag details;
7338 if (items[i]->m_bIsFolder)
7340 details.SetPath(items[i]->GetPath());
7341 details.m_strAlbum = idData.front().first;
7342 details.m_type = MediaTypeAlbum;
7343 details.m_artist.emplace_back(idData.front().second);
7344 details.m_iDbId = idMVideoList.front();
7345 items[i]->SetProperty("musicvideomediatype", MediaTypeAlbum);
7346 items[i]->SetLabel(idData.front().first);
7347 items[i]->SetFromVideoInfoTag(details);
7349 idMVideoList.pop_front();
7350 idData.pop_front();
7351 continue;
7353 else
7355 GetMusicVideoInfo("", details, idMVideoList.front());
7356 items[i]->SetFromVideoInfoTag(details);
7357 idMVideoList.pop_front();
7358 idData.pop_front();
7362 if (!strArtist.empty())
7363 items.SetProperty("customtitle",strArtist); // change displayed path from eg /23 to /Artist
7365 return true;
7367 catch (...)
7369 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7371 return false;
7374 bool CVideoDatabase::GetWritersNav(const std::string& strBaseDir,
7375 CFileItemList& items,
7376 VideoDbContentType idContent /* = UNKNOWN */,
7377 const Filter& filter /* = Filter() */,
7378 bool countOnly /* = false */)
7380 return GetPeopleNav(strBaseDir, items, "writer", idContent, filter, countOnly);
7383 bool CVideoDatabase::GetDirectorsNav(const std::string& strBaseDir,
7384 CFileItemList& items,
7385 VideoDbContentType idContent /* = UNKNOWN */,
7386 const Filter& filter /* = Filter() */,
7387 bool countOnly /* = false */)
7389 return GetPeopleNav(strBaseDir, items, "director", idContent, filter, countOnly);
7392 bool CVideoDatabase::GetActorsNav(const std::string& strBaseDir,
7393 CFileItemList& items,
7394 VideoDbContentType idContent /* = UNKNOWN */,
7395 const Filter& filter /* = Filter() */,
7396 bool countOnly /* = false */)
7398 if (GetPeopleNav(strBaseDir, items, "actor", idContent, filter, countOnly))
7399 { // set thumbs - ideally this should be in the normal thumb setting routines
7400 for (int i = 0; i < items.Size() && !countOnly; i++)
7402 CFileItemPtr pItem = items[i];
7403 if (idContent == VideoDbContentType::MUSICVIDEOS)
7404 pItem->SetArt("icon", "DefaultArtist.png");
7405 else
7406 pItem->SetArt("icon", "DefaultActor.png");
7408 return true;
7410 return false;
7413 bool CVideoDatabase::GetPeopleNav(const std::string& strBaseDir,
7414 CFileItemList& items,
7415 const char* type,
7416 VideoDbContentType idContent /* = UNKNOWN */,
7417 const Filter& filter /* = Filter() */,
7418 bool countOnly /* = false */)
7420 if (nullptr == m_pDB)
7421 return false;
7422 if (nullptr == m_pDS)
7423 return false;
7427 //! @todo This routine (and probably others at this same level) use playcount as a reference to filter on at a later
7428 //! point. This means that we *MUST* filter these levels as you'll get double ups. Ideally we'd allow playcount
7429 //! to filter through as we normally do for tvshows to save this happening.
7430 //! Also, we apply this same filtering logic to the locked or unlocked paths to prevent these from showing.
7431 //! Whether or not this should happen is a tricky one - it complicates all the high level categories (everything
7432 //! above titles).
7434 // General routine that the other actor/director/writer routines call
7436 // get primary genres for movies
7437 std::string strSQL;
7438 bool bMainArtistOnly = false;
7439 Filter extFilter = filter;
7440 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7442 std::string view, view_id, media_type, extraField, extraJoin, group;
7443 if (idContent == VideoDbContentType::MOVIES)
7445 view = MediaTypeMovie;
7446 view_id = "idMovie";
7447 media_type = MediaTypeMovie;
7448 extraField = "files.playCount";
7450 else if (idContent == VideoDbContentType::TVSHOWS)
7452 view = MediaTypeEpisode;
7453 view_id = "idShow";
7454 media_type = MediaTypeTvShow;
7455 extraField = "count(DISTINCT idShow)";
7456 group = "actor.actor_id";
7458 else if (idContent == VideoDbContentType::EPISODES)
7460 view = MediaTypeEpisode;
7461 view_id = "idEpisode";
7462 media_type = MediaTypeEpisode;
7463 extraField = "files.playCount";
7465 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7467 bMainArtistOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
7468 CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS);
7469 if (StringUtils::EndsWith(strBaseDir, "directors/"))
7470 // only set this to true if getting artists and show all performers is false
7471 bMainArtistOnly = false;
7472 view = MediaTypeMusicVideo;
7473 view_id = "idMVideo";
7474 media_type = MediaTypeMusicVideo;
7475 extraField = "count(1), count(files.playCount)";
7476 if (bMainArtistOnly)
7477 extraJoin =
7478 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
7479 group = "actor.actor_id";
7481 else
7482 return false;
7484 strSQL = "SELECT {} FROM actor ";
7485 extFilter.fields = "actor.actor_id, actor.name, actor.art_urls, path.strPath";
7486 extFilter.AppendField(extraField);
7487 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON actor.actor_id = %s_link.actor_id", type, type));
7488 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()));
7489 extFilter.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str()));
7490 extFilter.AppendJoin("JOIN path ON path.idPath = files.idPath");
7491 extFilter.AppendJoin(extraJoin);
7492 extFilter.AppendGroup(group);
7494 else
7496 std::string view, view_id, media_type, extraField, extraJoin;
7497 if (idContent == VideoDbContentType::MOVIES)
7499 view = MediaTypeMovie;
7500 view_id = "idMovie";
7501 media_type = MediaTypeMovie;
7502 extraField = "count(1), count(files.playCount)";
7503 extraJoin = PrepareSQL(" JOIN files ON files.idFile=%s_view.idFile", view.c_str());
7505 else if (idContent == VideoDbContentType::TVSHOWS)
7507 view = MediaTypeTvShow;
7508 view_id = "idShow";
7509 media_type = MediaTypeTvShow;
7510 extraField = "count(idShow)";
7512 else if (idContent == VideoDbContentType::EPISODES)
7514 view = MediaTypeEpisode;
7515 view_id = "idEpisode";
7516 media_type = MediaTypeEpisode;
7517 extraField = "count(1), count(files.playCount)";
7518 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
7520 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7522 bMainArtistOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
7523 CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS);
7524 if (StringUtils::EndsWith(strBaseDir, "directors/"))
7525 // only set this to true if getting artists and show all performers is false
7526 bMainArtistOnly = false;
7527 view = MediaTypeMusicVideo;
7528 view_id = "idMVideo";
7529 media_type = MediaTypeMusicVideo;
7530 extraField = "count(1), count(files.playCount)";
7531 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
7532 if (bMainArtistOnly)
7533 extraJoin =
7534 extraJoin +
7535 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
7537 else
7538 return false;
7540 strSQL = "SELECT {} FROM actor ";
7541 extFilter.fields = "actor.actor_id, actor.name, actor.art_urls";
7542 extFilter.AppendField(extraField);
7543 extFilter.AppendJoin(PrepareSQL("JOIN %s_link on actor.actor_id = %s_link.actor_id", type, type));
7544 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()));
7545 extFilter.AppendJoin(extraJoin);
7546 extFilter.AppendGroup("actor.actor_id");
7549 if (countOnly)
7551 extFilter.fields = "COUNT(1)";
7552 extFilter.group.clear();
7553 extFilter.order.clear();
7555 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
7557 CVideoDbUrl videoUrl;
7558 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
7559 return false;
7561 // run query
7562 auto start = std::chrono::steady_clock::now();
7564 if (!m_pDS->query(strSQL)) return false;
7566 auto end = std::chrono::steady_clock::now();
7567 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
7569 CLog::Log(LOGDEBUG, LOGDATABASE, "{} - query took {} ms", __FUNCTION__, duration.count());
7571 start = std::chrono::steady_clock::now();
7573 int iRowsFound = m_pDS->num_rows();
7574 if (iRowsFound == 0)
7576 m_pDS->close();
7577 return true;
7580 if (countOnly)
7582 CFileItemPtr pItem(new CFileItem());
7583 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
7584 items.Add(pItem);
7586 m_pDS->close();
7587 return true;
7590 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7592 std::map<int, CActor> mapActors;
7594 while (!m_pDS->eof())
7596 int idActor = m_pDS->fv(0).get_asInt();
7597 CActor actor;
7598 actor.name = m_pDS->fv(1).get_asString();
7599 actor.thumb = m_pDS->fv(2).get_asString();
7600 if (idContent != VideoDbContentType::TVSHOWS &&
7601 idContent != VideoDbContentType::MUSICVIDEOS)
7603 actor.playcount = m_pDS->fv(3).get_asInt();
7604 actor.appearances = 1;
7606 else actor.appearances = m_pDS->fv(4).get_asInt();
7607 auto it = mapActors.find(idActor);
7608 // is this actor already known?
7609 if (it == mapActors.end())
7611 // check path
7612 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7613 mapActors.insert(std::pair<int, CActor>(idActor, actor));
7615 else if (idContent != VideoDbContentType::TVSHOWS &&
7616 idContent != VideoDbContentType::MUSICVIDEOS)
7617 it->second.appearances++;
7618 m_pDS->next();
7620 m_pDS->close();
7622 for (const auto &i : mapActors)
7624 CFileItemPtr pItem(new CFileItem(i.second.name));
7626 CVideoDbUrl itemUrl = videoUrl;
7627 std::string path = StringUtils::Format("{}/", i.first);
7628 itemUrl.AppendPath(path);
7629 pItem->SetPath(itemUrl.ToString());
7631 pItem->m_bIsFolder=true;
7632 pItem->GetVideoInfoTag()->SetPlayCount(i.second.playcount);
7633 pItem->GetVideoInfoTag()->m_strPictureURL.ParseFromData(i.second.thumb);
7634 pItem->GetVideoInfoTag()->m_iDbId = i.first;
7635 pItem->GetVideoInfoTag()->m_type = type;
7636 pItem->GetVideoInfoTag()->m_relevance = i.second.appearances;
7637 if (idContent == VideoDbContentType::MUSICVIDEOS)
7639 // Get artist bio from music db later if available
7640 pItem->GetVideoInfoTag()->m_artist.emplace_back(i.second.name);
7641 pItem->SetProperty("musicvideomediatype", MediaTypeArtist);
7643 items.Add(pItem);
7646 else
7648 while (!m_pDS->eof())
7652 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
7654 CVideoDbUrl itemUrl = videoUrl;
7655 std::string path = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
7656 itemUrl.AppendPath(path);
7657 pItem->SetPath(itemUrl.ToString());
7659 pItem->m_bIsFolder=true;
7660 pItem->GetVideoInfoTag()->m_strPictureURL.ParseFromData(m_pDS->fv(2).get_asString());
7661 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
7662 pItem->GetVideoInfoTag()->m_type = type;
7663 if (idContent != VideoDbContentType::TVSHOWS)
7665 // fv(4) is the number of videos watched, fv(3) is the total number. We set the playcount
7666 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7667 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(4).get_asInt() == m_pDS->fv(3).get_asInt()) ? 1 : 0);
7669 pItem->GetVideoInfoTag()->m_relevance = m_pDS->fv(3).get_asInt();
7670 if (idContent == VideoDbContentType::MUSICVIDEOS)
7672 pItem->GetVideoInfoTag()->m_artist.emplace_back(pItem->GetLabel());
7673 pItem->SetProperty("musicvideomediatype", MediaTypeArtist);
7675 items.Add(pItem);
7676 m_pDS->next();
7678 catch (...)
7680 m_pDS->close();
7681 CLog::Log(LOGERROR, "{}: out of memory - retrieved {} items", __FUNCTION__, items.Size());
7682 return items.Size() > 0;
7685 m_pDS->close();
7688 end = std::chrono::steady_clock::now();
7689 duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
7691 CLog::Log(LOGDEBUG, LOGDATABASE, "{} item retrieval took {} ms", __FUNCTION__,
7692 duration.count());
7694 return true;
7696 catch (...)
7698 m_pDS->close();
7699 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7701 return false;
7704 bool CVideoDatabase::GetYearsNav(const std::string& strBaseDir,
7705 CFileItemList& items,
7706 VideoDbContentType idContent /* = UNKNOWN */,
7707 const Filter& filter /* = Filter() */)
7711 if (nullptr == m_pDB)
7712 return false;
7713 if (nullptr == m_pDS)
7714 return false;
7716 std::string strSQL;
7717 Filter extFilter = filter;
7718 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7720 if (idContent == VideoDbContentType::MOVIES)
7722 strSQL = "select movie_view.premiered, path.strPath, files.playCount from movie_view ";
7723 extFilter.AppendJoin("join files on files.idFile = movie_view.idFile join path on files.idPath = path.idPath");
7725 else if (idContent == VideoDbContentType::TVSHOWS)
7727 strSQL = PrepareSQL("select tvshow_view.c%02d, path.strPath from tvshow_view ", VIDEODB_ID_TV_PREMIERED);
7728 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");
7730 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7732 strSQL = "select musicvideo_view.premiered, path.strPath, files.playCount from musicvideo_view ";
7733 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on files.idPath = path.idPath");
7735 else
7736 return false;
7738 else
7740 std::string group;
7741 if (idContent == VideoDbContentType::MOVIES)
7743 strSQL = "select movie_view.premiered, count(1), count(files.playCount) from movie_view ";
7744 extFilter.AppendJoin("join files on files.idFile = movie_view.idFile");
7745 extFilter.AppendGroup("movie_view.premiered");
7747 else if (idContent == VideoDbContentType::TVSHOWS)
7749 strSQL = PrepareSQL("select distinct tvshow_view.c%02d from tvshow_view", VIDEODB_ID_TV_PREMIERED);
7750 extFilter.AppendGroup(PrepareSQL("tvshow_view.c%02d", VIDEODB_ID_TV_PREMIERED));
7752 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7754 strSQL = "select musicvideo_view.premiered, count(1), count(files.playCount) from musicvideo_view ";
7755 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile");
7756 extFilter.AppendGroup("musicvideo_view.premiered");
7758 else
7759 return false;
7762 CVideoDbUrl videoUrl;
7763 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
7764 return false;
7766 int iRowsFound = RunQuery(strSQL);
7767 if (iRowsFound <= 0)
7768 return iRowsFound == 0;
7770 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7772 std::map<int, std::pair<std::string,int> > mapYears;
7773 while (!m_pDS->eof())
7775 int lYear = 0;
7776 std::string dateString = m_pDS->fv(0).get_asString();
7777 if (dateString.size() == 4)
7778 lYear = m_pDS->fv(0).get_asInt();
7779 else
7781 CDateTime time;
7782 time.SetFromDateString(dateString);
7783 if (time.IsValid())
7784 lYear = time.GetYear();
7786 auto it = mapYears.find(lYear);
7787 if (it == mapYears.end())
7789 // check path
7790 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7792 std::string year = std::to_string(lYear);
7793 if (idContent == VideoDbContentType::MOVIES ||
7794 idContent == VideoDbContentType::MUSICVIDEOS)
7795 mapYears.insert(std::pair<int, std::pair<std::string,int> >(lYear, std::pair<std::string,int>(year,m_pDS->fv(2).get_asInt())));
7796 else
7797 mapYears.insert(std::pair<int, std::pair<std::string,int> >(lYear, std::pair<std::string,int>(year,0)));
7800 m_pDS->next();
7802 m_pDS->close();
7804 for (const auto &i : mapYears)
7806 if (i.first == 0)
7807 continue;
7808 CFileItemPtr pItem(new CFileItem(i.second.first));
7810 CVideoDbUrl itemUrl = videoUrl;
7811 std::string path = StringUtils::Format("{}/", i.first);
7812 itemUrl.AppendPath(path);
7813 pItem->SetPath(itemUrl.ToString());
7815 pItem->m_bIsFolder=true;
7816 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7817 pItem->GetVideoInfoTag()->SetPlayCount(i.second.second);
7818 items.Add(pItem);
7821 else
7823 while (!m_pDS->eof())
7825 int lYear = 0;
7826 std::string strLabel = m_pDS->fv(0).get_asString();
7827 if (strLabel.size() == 4)
7828 lYear = m_pDS->fv(0).get_asInt();
7829 else
7831 CDateTime time;
7832 time.SetFromDateString(strLabel);
7833 if (time.IsValid())
7835 lYear = time.GetYear();
7836 strLabel = std::to_string(lYear);
7839 if (lYear == 0)
7841 m_pDS->next();
7842 continue;
7844 CFileItemPtr pItem(new CFileItem(strLabel));
7846 CVideoDbUrl itemUrl = videoUrl;
7847 std::string path = StringUtils::Format("{}/", lYear);
7848 itemUrl.AppendPath(path);
7849 pItem->SetPath(itemUrl.ToString());
7851 pItem->m_bIsFolder=true;
7852 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7854 // fv(2) is the number of videos watched, fv(1) is the total number. We set the playcount
7855 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7856 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(2).get_asInt() == m_pDS->fv(1).get_asInt()) ? 1 : 0);
7859 // take care of dupes ..
7860 if (!items.Contains(pItem->GetPath()))
7861 items.Add(pItem);
7863 m_pDS->next();
7865 m_pDS->close();
7868 return true;
7870 catch (...)
7872 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7874 return false;
7877 bool CVideoDatabase::GetSeasonsNav(const std::string& strBaseDir, CFileItemList& items, int idActor, int idDirector, int idGenre, int idYear, int idShow, bool getLinkedMovies /* = true */)
7879 // parse the base path to get additional filters
7880 CVideoDbUrl videoUrl;
7881 if (!videoUrl.FromString(strBaseDir))
7882 return false;
7884 if (idShow != -1)
7885 videoUrl.AddOption("tvshowid", idShow);
7886 if (idActor != -1)
7887 videoUrl.AddOption("actorid", idActor);
7888 else if (idDirector != -1)
7889 videoUrl.AddOption("directorid", idDirector);
7890 else if (idGenre != -1)
7891 videoUrl.AddOption("genreid", idGenre);
7892 else if (idYear != -1)
7893 videoUrl.AddOption("year", idYear);
7895 if (!GetSeasonsByWhere(videoUrl.ToString(), Filter(), items, false))
7896 return false;
7898 // now add any linked movies
7899 if (getLinkedMovies && idShow != -1)
7901 Filter movieFilter;
7902 movieFilter.join = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
7903 movieFilter.where = PrepareSQL("movielinktvshow.idShow = %i", idShow);
7904 CFileItemList movieItems;
7905 GetMoviesByWhere("videodb://movies/titles/", movieFilter, movieItems);
7907 if (movieItems.Size() > 0)
7908 items.Append(movieItems);
7911 return true;
7914 bool CVideoDatabase::GetSeasonsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool appendFullShowPath /* = true */, const SortDescription &sortDescription /* = SortDescription() */)
7918 if (nullptr == m_pDB)
7919 return false;
7920 if (nullptr == m_pDS)
7921 return false;
7923 int total = -1;
7925 std::string strSQL = "SELECT %s FROM season_view ";
7926 CVideoDbUrl videoUrl;
7927 std::string strSQLExtra;
7928 Filter extFilter = filter;
7929 SortDescription sorting = sortDescription;
7930 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
7931 return false;
7933 // Apply the limiting directly here if there's no special sorting but limiting
7934 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
7935 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
7936 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
7938 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7939 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7942 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7944 int iRowsFound = RunQuery(strSQL);
7946 // store the total value of items as a property
7947 if (total < iRowsFound)
7948 total = iRowsFound;
7949 items.SetProperty("total", total);
7951 if (iRowsFound <= 0)
7952 return iRowsFound == 0;
7954 std::set<std::pair<int, int>> mapSeasons;
7955 while (!m_pDS->eof())
7957 int id = m_pDS->fv(VIDEODB_ID_SEASON_ID).get_asInt();
7958 int showId = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_ID).get_asInt();
7959 int iSeason = m_pDS->fv(VIDEODB_ID_SEASON_NUMBER).get_asInt();
7960 std::string name = m_pDS->fv(VIDEODB_ID_SEASON_NAME).get_asString();
7961 std::string path = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PATH).get_asString();
7963 if (mapSeasons.find(std::make_pair(showId, iSeason)) == mapSeasons.end() &&
7964 (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || g_passwordManager.bMasterUser ||
7965 g_passwordManager.IsDatabasePathUnlocked(path, *CMediaSourceSettings::GetInstance().GetSources("video"))))
7967 mapSeasons.insert(std::make_pair(showId, iSeason));
7969 std::string strLabel = name;
7970 if (strLabel.empty())
7972 if (iSeason == 0)
7973 strLabel = g_localizeStrings.Get(20381);
7974 else
7975 strLabel = StringUtils::Format(g_localizeStrings.Get(20358), iSeason);
7977 CFileItemPtr pItem(new CFileItem(strLabel));
7979 CVideoDbUrl itemUrl = videoUrl;
7980 std::string strDir;
7981 if (appendFullShowPath)
7982 strDir += StringUtils::Format("{}/", showId);
7983 strDir += StringUtils::Format("{}/", iSeason);
7984 itemUrl.AppendPath(strDir);
7985 pItem->SetPath(itemUrl.ToString());
7987 pItem->m_bIsFolder = true;
7988 pItem->GetVideoInfoTag()->m_strTitle = strLabel;
7989 if (!name.empty())
7990 pItem->GetVideoInfoTag()->m_strSortTitle = name;
7991 pItem->GetVideoInfoTag()->m_iSeason = iSeason;
7992 pItem->GetVideoInfoTag()->m_iDbId = id;
7993 pItem->GetVideoInfoTag()->m_iIdSeason = id;
7994 pItem->GetVideoInfoTag()->m_type = MediaTypeSeason;
7995 pItem->GetVideoInfoTag()->m_strPath = path;
7996 pItem->GetVideoInfoTag()->m_strShowTitle = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_TITLE).get_asString();
7997 pItem->GetVideoInfoTag()->m_strPlot = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PLOT).get_asString();
7998 pItem->GetVideoInfoTag()->SetPremieredFromDBDate(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PREMIERED).get_asString());
7999 pItem->GetVideoInfoTag()->m_firstAired.SetFromDBDate(m_pDS->fv(VIDEODB_ID_SEASON_PREMIERED).get_asString());
8000 pItem->GetVideoInfoTag()->m_iUserRating = m_pDS->fv(VIDEODB_ID_SEASON_USER_RATING).get_asInt();
8001 // season premiered date based on first episode airdate associated to the season
8002 // tvshow premiered date is used as a fallback
8003 if (pItem->GetVideoInfoTag()->m_firstAired.IsValid())
8004 pItem->GetVideoInfoTag()->SetPremiered(pItem->GetVideoInfoTag()->m_firstAired);
8005 else if (pItem->GetVideoInfoTag()->HasPremiered())
8006 pItem->GetVideoInfoTag()->SetPremiered(pItem->GetVideoInfoTag()->GetPremiered());
8007 else if (pItem->GetVideoInfoTag()->HasYear())
8008 pItem->GetVideoInfoTag()->SetYear(pItem->GetVideoInfoTag()->GetYear());
8009 pItem->GetVideoInfoTag()->m_genre = StringUtils::Split(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_GENRE).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
8010 pItem->GetVideoInfoTag()->m_studio = StringUtils::Split(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_STUDIO).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
8011 pItem->GetVideoInfoTag()->m_strMPAARating = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_MPAA).get_asString();
8012 pItem->GetVideoInfoTag()->m_iIdShow = showId;
8014 const int totalEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_TOTAL).get_asInt();
8015 const int watchedEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_WATCHED).get_asInt();
8016 const int inProgressEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_INPROGRESS).get_asInt();
8018 pItem->GetVideoInfoTag()->m_iEpisode = totalEpisodes;
8019 pItem->SetProperty("totalepisodes", totalEpisodes);
8020 pItem->SetProperty("numepisodes", totalEpisodes); // will be changed later to reflect watchmode setting
8021 pItem->SetProperty("watchedepisodes", watchedEpisodes);
8022 pItem->SetProperty("unwatchedepisodes", totalEpisodes - watchedEpisodes);
8023 pItem->SetProperty("inprogressepisodes", inProgressEpisodes);
8024 pItem->SetProperty("watchedepisodepercent",
8025 totalEpisodes > 0 ? (watchedEpisodes * 100 / totalEpisodes) : 0);
8026 if (iSeason == 0)
8027 pItem->SetProperty("isspecial", true);
8028 pItem->GetVideoInfoTag()->SetPlayCount((totalEpisodes == watchedEpisodes) ? 1 : 0);
8029 pItem->SetOverlayImage((pItem->GetVideoInfoTag()->GetPlayCount() > 0) &&
8030 (pItem->GetVideoInfoTag()->m_iEpisode > 0)
8031 ? CGUIListItem::ICON_OVERLAY_WATCHED
8032 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8034 items.Add(pItem);
8037 m_pDS->next();
8039 m_pDS->close();
8041 return true;
8043 catch (...)
8045 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8047 return false;
8050 bool CVideoDatabase::GetSortedVideos(const MediaType &mediaType, const std::string& strBaseDir, const SortDescription &sortDescription, CFileItemList& items, const Filter &filter /* = Filter() */)
8052 if (nullptr == m_pDB || nullptr == m_pDS)
8053 return false;
8055 if (mediaType != MediaTypeMovie && mediaType != MediaTypeTvShow && mediaType != MediaTypeEpisode && mediaType != MediaTypeMusicVideo)
8056 return false;
8058 SortDescription sorting = sortDescription;
8059 if (sortDescription.sortBy == SortByFile || sortDescription.sortBy == SortByTitle ||
8060 sortDescription.sortBy == SortBySortTitle || sortDescription.sortBy == SortByOriginalTitle ||
8061 sortDescription.sortBy == SortByLabel || sortDescription.sortBy == SortByDateAdded ||
8062 sortDescription.sortBy == SortByRating || sortDescription.sortBy == SortByUserRating ||
8063 sortDescription.sortBy == SortByYear || sortDescription.sortBy == SortByLastPlayed ||
8064 sortDescription.sortBy == SortByPlaycount)
8065 sorting.sortAttributes = (SortAttribute)(sortDescription.sortAttributes | SortAttributeIgnoreFolders);
8067 bool success = false;
8068 if (mediaType == MediaTypeMovie)
8069 success = GetMoviesByWhere(strBaseDir, filter, items, sorting);
8070 else if (mediaType == MediaTypeTvShow)
8071 success = GetTvShowsByWhere(strBaseDir, filter, items, sorting);
8072 else if (mediaType == MediaTypeEpisode)
8073 success = GetEpisodesByWhere(strBaseDir, filter, items, true, sorting);
8074 else if (mediaType == MediaTypeMusicVideo)
8075 success = GetMusicVideosByWhere(strBaseDir, filter, items, true, sorting);
8076 else
8077 return false;
8079 items.SetContent(CMediaTypes::ToPlural(mediaType));
8080 return success;
8083 bool CVideoDatabase::GetItems(const std::string &strBaseDir, CFileItemList &items, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */)
8085 CVideoDbUrl videoUrl;
8086 if (!videoUrl.FromString(strBaseDir))
8087 return false;
8089 return GetItems(strBaseDir, videoUrl.GetType(), videoUrl.GetItemType(), items, filter, sortDescription);
8092 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() */)
8094 VideoDbContentType contentType;
8095 if (StringUtils::EqualsNoCase(mediaType, "movies"))
8096 contentType = VideoDbContentType::MOVIES;
8097 else if (StringUtils::EqualsNoCase(mediaType, "tvshows"))
8099 if (StringUtils::EqualsNoCase(itemType, "episodes"))
8100 contentType = VideoDbContentType::EPISODES;
8101 else
8102 contentType = VideoDbContentType::TVSHOWS;
8104 else if (StringUtils::EqualsNoCase(mediaType, "musicvideos"))
8105 contentType = VideoDbContentType::MUSICVIDEOS;
8106 else
8107 return false;
8109 return GetItems(strBaseDir, contentType, itemType, items, filter, sortDescription);
8112 bool CVideoDatabase::GetItems(const std::string& strBaseDir,
8113 VideoDbContentType mediaType,
8114 const std::string& itemType,
8115 CFileItemList& items,
8116 const Filter& filter /* = Filter() */,
8117 const SortDescription& sortDescription /* = SortDescription() */)
8119 if (StringUtils::EqualsNoCase(itemType, "movies") &&
8120 (mediaType == VideoDbContentType::MOVIES || mediaType == VideoDbContentType::MOVIE_SETS))
8121 return GetMoviesByWhere(strBaseDir, filter, items, sortDescription);
8122 else if (StringUtils::EqualsNoCase(itemType, "tvshows") &&
8123 mediaType == VideoDbContentType::TVSHOWS)
8125 Filter extFilter = filter;
8126 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->
8127 GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS))
8128 extFilter.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
8129 return GetTvShowsByWhere(strBaseDir, extFilter, items, sortDescription);
8131 else if (StringUtils::EqualsNoCase(itemType, "musicvideos") &&
8132 mediaType == VideoDbContentType::MUSICVIDEOS)
8133 return GetMusicVideosByWhere(strBaseDir, filter, items, true, sortDescription);
8134 else if (StringUtils::EqualsNoCase(itemType, "episodes") &&
8135 mediaType == VideoDbContentType::EPISODES)
8136 return GetEpisodesByWhere(strBaseDir, filter, items, true, sortDescription);
8137 else if (StringUtils::EqualsNoCase(itemType, "seasons") &&
8138 mediaType == VideoDbContentType::TVSHOWS)
8139 return GetSeasonsNav(strBaseDir, items);
8140 else if (StringUtils::EqualsNoCase(itemType, "genres"))
8141 return GetGenresNav(strBaseDir, items, mediaType, filter);
8142 else if (StringUtils::EqualsNoCase(itemType, "years"))
8143 return GetYearsNav(strBaseDir, items, mediaType, filter);
8144 else if (StringUtils::EqualsNoCase(itemType, "actors"))
8145 return GetActorsNav(strBaseDir, items, mediaType, filter);
8146 else if (StringUtils::EqualsNoCase(itemType, "directors"))
8147 return GetDirectorsNav(strBaseDir, items, mediaType, filter);
8148 else if (StringUtils::EqualsNoCase(itemType, "writers"))
8149 return GetWritersNav(strBaseDir, items, mediaType, filter);
8150 else if (StringUtils::EqualsNoCase(itemType, "studios"))
8151 return GetStudiosNav(strBaseDir, items, mediaType, filter);
8152 else if (StringUtils::EqualsNoCase(itemType, "sets"))
8153 return GetSetsNav(strBaseDir, items, mediaType, filter, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPSINGLEITEMSETS));
8154 else if (StringUtils::EqualsNoCase(itemType, "countries"))
8155 return GetCountriesNav(strBaseDir, items, mediaType, filter);
8156 else if (StringUtils::EqualsNoCase(itemType, "tags"))
8157 return GetTagsNav(strBaseDir, items, mediaType, filter);
8158 else if (StringUtils::EqualsNoCase(itemType, "videoversions"))
8159 return GetVideoVersionsNav(strBaseDir, items, mediaType, filter);
8160 else if (StringUtils::EqualsNoCase(itemType, "artists") &&
8161 mediaType == VideoDbContentType::MUSICVIDEOS)
8162 return GetActorsNav(strBaseDir, items, mediaType, filter);
8163 else if (StringUtils::EqualsNoCase(itemType, "albums") &&
8164 mediaType == VideoDbContentType::MUSICVIDEOS)
8165 return GetMusicVideoAlbumsNav(strBaseDir, items, -1, filter);
8167 return false;
8170 std::string CVideoDatabase::GetItemById(const std::string &itemType, int id)
8172 if (StringUtils::EqualsNoCase(itemType, "genres"))
8173 return GetGenreById(id);
8174 else if (StringUtils::EqualsNoCase(itemType, "years"))
8175 return std::to_string(id);
8176 else if (StringUtils::EqualsNoCase(itemType, "actors") ||
8177 StringUtils::EqualsNoCase(itemType, "directors") ||
8178 StringUtils::EqualsNoCase(itemType, "artists"))
8179 return GetPersonById(id);
8180 else if (StringUtils::EqualsNoCase(itemType, "studios"))
8181 return GetStudioById(id);
8182 else if (StringUtils::EqualsNoCase(itemType, "sets"))
8183 return GetSetById(id);
8184 else if (StringUtils::EqualsNoCase(itemType, "countries"))
8185 return GetCountryById(id);
8186 else if (StringUtils::EqualsNoCase(itemType, "tags"))
8187 return GetTagById(id);
8188 else if (StringUtils::EqualsNoCase(itemType, "videoversions"))
8189 return GetVideoVersionById(id);
8190 else if (StringUtils::EqualsNoCase(itemType, "albums"))
8191 return GetMusicVideoAlbumById(id);
8193 return "";
8196 bool CVideoDatabase::GetMoviesNav(const std::string& strBaseDir, CFileItemList& items,
8197 int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */,
8198 int idStudio /* = -1 */, int idCountry /* = -1 */, int idSet /* = -1 */, int idTag /* = -1 */,
8199 const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8201 CVideoDbUrl videoUrl;
8202 if (!videoUrl.FromString(strBaseDir))
8203 return false;
8205 if (idGenre > 0)
8206 videoUrl.AddOption("genreid", idGenre);
8207 else if (idCountry > 0)
8208 videoUrl.AddOption("countryid", idCountry);
8209 else if (idStudio > 0)
8210 videoUrl.AddOption("studioid", idStudio);
8211 else if (idDirector > 0)
8212 videoUrl.AddOption("directorid", idDirector);
8213 else if (idYear > 0)
8214 videoUrl.AddOption("year", idYear);
8215 else if (idActor > 0)
8216 videoUrl.AddOption("actorid", idActor);
8217 else if (idSet > 0)
8218 videoUrl.AddOption("setid", idSet);
8219 else if (idTag > 0)
8220 videoUrl.AddOption("tagid", idTag);
8222 Filter filter;
8223 return GetMoviesByWhere(videoUrl.ToString(), filter, items, sortDescription, getDetails);
8226 namespace
8228 std::string RewriteVideoVersionURL(const std::string& baseDir, const CVideoInfoTag& movie)
8230 const CURL parentPath{URIUtils::GetParentPath(baseDir)};
8231 const std::string versionId{std::to_string(movie.GetAssetInfo().GetId())};
8232 const std::string mediaId{std::to_string(movie.m_iDbId)};
8233 CVideoDbUrl url;
8234 url.FromString(parentPath.GetWithoutOptions());
8235 url.AppendPath(versionId);
8236 url.AppendPath(mediaId);
8237 url.AddOption("videoversionid", versionId);
8238 url.AddOption("mediaid", mediaId);
8239 return url.ToString();
8241 } // unnamed namespace
8243 bool CVideoDatabase::GetMoviesByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8247 if (nullptr == m_pDB)
8248 return false;
8249 if (nullptr == m_pDS)
8250 return false;
8252 // parse the base path to get additional filters
8253 CVideoDbUrl videoUrl;
8254 Filter extFilter = filter;
8255 SortDescription sorting = sortDescription;
8256 if (!videoUrl.FromString(strBaseDir) || !GetFilter(videoUrl, extFilter, sorting))
8257 return false;
8259 int total = -1;
8261 std::string strSQL = "select %s from movie_view ";
8262 std::string strSQLExtra;
8263 if (!CDatabase::BuildSQL(strSQLExtra, extFilter, strSQLExtra))
8264 return false;
8266 // Apply the limiting directly here if there's no special sorting but limiting
8267 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
8268 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
8269 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
8271 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
8272 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
8275 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
8277 int iRowsFound = RunQuery(strSQL);
8279 // store the total value of items as a property
8280 if (total < iRowsFound)
8281 total = iRowsFound;
8282 items.SetProperty("total", total);
8284 if (iRowsFound <= 0)
8285 return iRowsFound == 0;
8287 DatabaseResults results;
8288 results.reserve(iRowsFound);
8290 if (!SortUtils::SortFromDataset(sortDescription, MediaTypeMovie, m_pDS, results))
8291 return false;
8293 // get data from returned rows
8294 items.Reserve(results.size());
8295 const query_data &data = m_pDS->get_result_set().records;
8296 for (const auto &i : results)
8298 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
8299 const dbiplus::sql_record* const record = data.at(targetRow);
8301 CVideoInfoTag movie = GetDetailsForMovie(record, getDetails);
8302 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
8303 g_passwordManager.bMasterUser ||
8304 g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
8306 CFileItemPtr pItem(new CFileItem(movie));
8308 std::string path;
8309 CVideoDbUrl itemUrl{videoUrl};
8310 CVariant value;
8311 if (itemUrl.GetOption("videoversionid", value))
8313 //! @todo get rid of "videos with versions as folder" hack!
8314 if (value.asInteger() == VIDEO_VERSION_ID_ALL)
8316 // all versions for the given media id requested; we need to insert the real video
8317 // version id for this movie into the videodb url
8318 path = RewriteVideoVersionURL(strBaseDir, movie);
8320 // this is a certain version, no need to resolve (e.g. no version chooser on select)
8321 pItem->SetProperty("has_resolved_video_asset", true);
8324 if (path.empty())
8326 itemUrl.AppendPath(std::to_string(movie.m_iDbId));
8327 path = itemUrl.ToString();
8330 pItem->SetPath(path);
8331 pItem->SetDynPath(movie.m_strFileNameAndPath);
8333 pItem->SetOverlayImage(movie.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
8334 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8335 items.Add(pItem);
8339 // cleanup
8340 m_pDS->close();
8341 return true;
8343 catch (...)
8345 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8347 return false;
8350 bool CVideoDatabase::GetTvShowsNav(const std::string& strBaseDir, CFileItemList& items,
8351 int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */, int idStudio /* = -1 */, int idTag /* = -1 */,
8352 const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8354 CVideoDbUrl videoUrl;
8355 if (!videoUrl.FromString(strBaseDir))
8356 return false;
8358 if (idGenre != -1)
8359 videoUrl.AddOption("genreid", idGenre);
8360 else if (idStudio != -1)
8361 videoUrl.AddOption("studioid", idStudio);
8362 else if (idDirector != -1)
8363 videoUrl.AddOption("directorid", idDirector);
8364 else if (idYear != -1)
8365 videoUrl.AddOption("year", idYear);
8366 else if (idActor != -1)
8367 videoUrl.AddOption("actorid", idActor);
8368 else if (idTag != -1)
8369 videoUrl.AddOption("tagid", idTag);
8371 Filter filter;
8372 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS))
8373 filter.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
8374 return GetTvShowsByWhere(videoUrl.ToString(), filter, items, sortDescription, getDetails);
8377 bool CVideoDatabase::GetTvShowsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8381 if (nullptr == m_pDB)
8382 return false;
8383 if (nullptr == m_pDS)
8384 return false;
8386 int total = -1;
8388 std::string strSQL = "SELECT %s FROM tvshow_view ";
8389 CVideoDbUrl videoUrl;
8390 std::string strSQLExtra;
8391 Filter extFilter = filter;
8392 SortDescription sorting = sortDescription;
8393 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
8394 return false;
8396 // Apply the limiting directly here if there's no special sorting but limiting
8397 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
8398 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
8399 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
8401 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
8402 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
8405 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
8407 int iRowsFound = RunQuery(strSQL);
8409 // store the total value of items as a property
8410 if (total < iRowsFound)
8411 total = iRowsFound;
8412 items.SetProperty("total", total);
8414 if (iRowsFound <= 0)
8415 return iRowsFound == 0;
8417 DatabaseResults results;
8418 results.reserve(iRowsFound);
8419 if (!SortUtils::SortFromDataset(sorting, MediaTypeTvShow, m_pDS, results))
8420 return false;
8422 // get data from returned rows
8423 items.Reserve(results.size());
8424 const query_data &data = m_pDS->get_result_set().records;
8425 for (const auto &i : results)
8427 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
8428 const dbiplus::sql_record* const record = data.at(targetRow);
8430 CFileItemPtr pItem(new CFileItem());
8431 CVideoInfoTag movie = GetDetailsForTvShow(record, getDetails, pItem.get());
8432 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
8433 g_passwordManager.bMasterUser ||
8434 g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
8436 pItem->SetFromVideoInfoTag(movie);
8438 CVideoDbUrl itemUrl = videoUrl;
8439 std::string path = StringUtils::Format("{}/", record->at(0).get_asInt());
8440 itemUrl.AppendPath(path);
8441 pItem->SetPath(itemUrl.ToString());
8443 pItem->SetOverlayImage((pItem->GetVideoInfoTag()->GetPlayCount() > 0) &&
8444 (pItem->GetVideoInfoTag()->m_iEpisode > 0)
8445 ? CGUIListItem::ICON_OVERLAY_WATCHED
8446 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8447 items.Add(pItem);
8451 // cleanup
8452 m_pDS->close();
8453 return true;
8455 catch (...)
8457 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8459 return false;
8462 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 */)
8464 CVideoDbUrl videoUrl;
8465 if (!videoUrl.FromString(strBaseDir))
8466 return false;
8468 if (idShow != -1)
8470 videoUrl.AddOption("tvshowid", idShow);
8471 if (idSeason >= 0)
8472 videoUrl.AddOption("season", idSeason);
8474 if (idGenre != -1)
8475 videoUrl.AddOption("genreid", idGenre);
8476 else if (idYear !=-1)
8477 videoUrl.AddOption("year", idYear);
8478 else if (idActor != -1)
8479 videoUrl.AddOption("actorid", idActor);
8481 else if (idYear != -1)
8482 videoUrl.AddOption("year", idYear);
8484 if (idDirector != -1)
8485 videoUrl.AddOption("directorid", idDirector);
8487 Filter filter;
8488 bool ret = GetEpisodesByWhere(videoUrl.ToString(), filter, items, false, sortDescription, getDetails);
8490 if (idSeason == -1 && idShow != -1)
8491 { // add any linked movies
8492 Filter movieFilter;
8493 movieFilter.join = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
8494 movieFilter.where = PrepareSQL("movielinktvshow.idShow = %i", idShow);
8495 CFileItemList movieItems;
8496 GetMoviesByWhere("videodb://movies/titles/", movieFilter, movieItems);
8498 if (movieItems.Size() > 0)
8499 items.Append(movieItems);
8502 return ret;
8505 bool CVideoDatabase::GetEpisodesByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool appendFullShowPath /* = true */, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8509 if (nullptr == m_pDB)
8510 return false;
8511 if (nullptr == m_pDS)
8512 return false;
8514 int total = -1;
8516 std::string strSQL = "select %s from episode_view ";
8517 CVideoDbUrl videoUrl;
8518 std::string strSQLExtra;
8519 Filter extFilter = filter;
8520 SortDescription sorting = sortDescription;
8521 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
8522 return false;
8524 // Apply the limiting directly here if there's no special sorting but limiting
8525 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
8526 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
8527 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
8529 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
8530 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
8533 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
8535 int iRowsFound = RunQuery(strSQL);
8537 // store the total value of items as a property
8538 if (total < iRowsFound)
8539 total = iRowsFound;
8540 items.SetProperty("total", total);
8542 if (iRowsFound <= 0)
8543 return iRowsFound == 0;
8545 DatabaseResults results;
8546 results.reserve(iRowsFound);
8547 if (!SortUtils::SortFromDataset(sorting, MediaTypeEpisode, m_pDS, results))
8548 return false;
8550 // get data from returned rows
8551 items.Reserve(results.size());
8552 CLabelFormatter formatter("%H. %T", "");
8554 const query_data &data = m_pDS->get_result_set().records;
8555 for (const auto &i : results)
8557 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
8558 const dbiplus::sql_record* const record = data.at(targetRow);
8560 CVideoInfoTag episode = GetDetailsForEpisode(record, getDetails);
8561 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
8562 g_passwordManager.bMasterUser ||
8563 g_passwordManager.IsDatabasePathUnlocked(episode.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
8565 CFileItemPtr pItem(new CFileItem(episode));
8566 formatter.FormatLabel(pItem.get());
8568 int idEpisode = record->at(0).get_asInt();
8570 CVideoDbUrl itemUrl = videoUrl;
8571 std::string path;
8572 if (appendFullShowPath && videoUrl.GetItemType() != "episodes")
8573 path = StringUtils::Format("{}/{}/{}",
8574 record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt(),
8575 episode.m_iSeason, idEpisode);
8576 else
8577 path = std::to_string(idEpisode);
8578 itemUrl.AppendPath(path);
8579 pItem->SetPath(itemUrl.ToString());
8580 pItem->SetDynPath(episode.m_strFileNameAndPath);
8582 pItem->SetOverlayImage(episode.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
8583 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8584 pItem->m_dateTime = episode.m_firstAired;
8585 items.Add(pItem);
8589 // cleanup
8590 m_pDS->close();
8591 return true;
8593 catch (...)
8595 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8597 return false;
8600 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 */)
8602 CVideoDbUrl videoUrl;
8603 if (!videoUrl.FromString(strBaseDir))
8604 return false;
8606 if (idGenre != -1)
8607 videoUrl.AddOption("genreid", idGenre);
8608 else if (idStudio != -1)
8609 videoUrl.AddOption("studioid", idStudio);
8610 else if (idDirector != -1)
8611 videoUrl.AddOption("directorid", idDirector);
8612 else if (idYear !=-1)
8613 videoUrl.AddOption("year", idYear);
8614 else if (idArtist != -1)
8615 videoUrl.AddOption("artistid", idArtist);
8616 else if (idTag != -1)
8617 videoUrl.AddOption("tagid", idTag);
8618 if (idAlbum != -1)
8619 videoUrl.AddOption("albumid", idAlbum);
8621 Filter filter;
8622 return GetMusicVideosByWhere(videoUrl.ToString(), filter, items, true, sortDescription, getDetails);
8625 bool CVideoDatabase::GetRecentlyAddedMoviesNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8627 Filter filter;
8628 filter.order = "dateAdded desc, idMovie desc";
8629 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8630 return GetMoviesByWhere(strBaseDir, filter, items, SortDescription(), getDetails);
8633 bool CVideoDatabase::GetRecentlyAddedEpisodesNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8635 Filter filter;
8636 filter.order = "dateAdded desc, idEpisode desc";
8637 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8638 return GetEpisodesByWhere(strBaseDir, filter, items, false, SortDescription(), getDetails);
8641 bool CVideoDatabase::GetRecentlyAddedMusicVideosNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8643 Filter filter;
8644 filter.order = "dateAdded desc, idMVideo desc";
8645 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8646 return GetMusicVideosByWhere(strBaseDir, filter, items, true, SortDescription(), getDetails);
8649 bool CVideoDatabase::GetInProgressTvShowsNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8651 Filter filter;
8652 filter.order = PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE);
8653 filter.where = "totalCount != watchedCount AND (inProgressCount > 0 OR watchedCount != 0)";
8654 return GetTvShowsByWhere(strBaseDir, filter, items, SortDescription(), getDetails);
8657 std::string CVideoDatabase::GetGenreById(int id)
8659 return GetSingleValue("genre", "name", PrepareSQL("genre_id=%i", id));
8662 std::string CVideoDatabase::GetCountryById(int id)
8664 return GetSingleValue("country", "name", PrepareSQL("country_id=%i", id));
8667 std::string CVideoDatabase::GetSetById(int id)
8669 return GetSingleValue("sets", "strSet", PrepareSQL("idSet=%i", id));
8672 std::string CVideoDatabase::GetSetByNameLike(const std::string& nameLike) const
8674 return GetSingleValue("sets", "strSet", PrepareSQL("strSet LIKE '%s'", nameLike.c_str()));
8677 std::string CVideoDatabase::GetTagById(int id)
8679 return GetSingleValue("tag", "name", PrepareSQL("tag_id = %i", id));
8682 std::string CVideoDatabase::GetPersonById(int id)
8684 return GetSingleValue("actor", "name", PrepareSQL("actor_id=%i", id));
8687 std::string CVideoDatabase::GetStudioById(int id)
8689 return GetSingleValue("studio", "name", PrepareSQL("studio_id=%i", id));
8692 std::string CVideoDatabase::GetTvShowTitleById(int id)
8694 return GetSingleValue("tvshow", PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE), PrepareSQL("idShow=%i", id));
8697 std::string CVideoDatabase::GetMusicVideoAlbumById(int id)
8699 return GetSingleValue("musicvideo", PrepareSQL("c%02d", VIDEODB_ID_MUSICVIDEO_ALBUM), PrepareSQL("idMVideo=%i", id));
8702 bool CVideoDatabase::HasSets() const
8706 if (nullptr == m_pDB)
8707 return false;
8708 if (nullptr == m_pDS)
8709 return false;
8711 m_pDS->query("SELECT movie_view.idSet,COUNT(1) AS c FROM movie_view "
8712 "JOIN sets ON sets.idSet = movie_view.idSet "
8713 "GROUP BY movie_view.idSet HAVING c>1");
8715 bool bResult = (m_pDS->num_rows() > 0);
8716 m_pDS->close();
8717 return bResult;
8719 catch (...)
8721 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8723 return false;
8726 int CVideoDatabase::GetTvShowForEpisode(int idEpisode)
8730 if (nullptr == m_pDB)
8731 return false;
8732 if (nullptr == m_pDS2)
8733 return false;
8735 // make sure we use m_pDS2, as this is called in loops using m_pDS
8736 std::string strSQL=PrepareSQL("select idShow from episode where idEpisode=%i", idEpisode);
8737 m_pDS2->query( strSQL );
8739 int result=-1;
8740 if (!m_pDS2->eof())
8741 result=m_pDS2->fv(0).get_asInt();
8742 m_pDS2->close();
8744 return result;
8746 catch (...)
8748 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idEpisode);
8750 return false;
8753 int CVideoDatabase::GetSeasonForEpisode(int idEpisode)
8755 char column[5];
8756 snprintf(column, sizeof(column), "c%0d", VIDEODB_ID_EPISODE_SEASON);
8757 std::string id = GetSingleValue("episode", column, PrepareSQL("idEpisode=%i", idEpisode));
8758 if (id.empty())
8759 return -1;
8760 return atoi(id.c_str());
8763 bool CVideoDatabase::HasContent()
8765 return (HasContent(VideoDbContentType::MOVIES) || HasContent(VideoDbContentType::TVSHOWS) ||
8766 HasContent(VideoDbContentType::MUSICVIDEOS));
8769 bool CVideoDatabase::HasContent(VideoDbContentType type)
8771 bool result = false;
8774 if (nullptr == m_pDB)
8775 return false;
8776 if (nullptr == m_pDS)
8777 return false;
8779 std::string sql;
8780 if (type == VideoDbContentType::MOVIES)
8781 sql = "select count(1) from movie";
8782 else if (type == VideoDbContentType::TVSHOWS)
8783 sql = "select count(1) from tvshow";
8784 else if (type == VideoDbContentType::MUSICVIDEOS)
8785 sql = "select count(1) from musicvideo";
8786 m_pDS->query( sql );
8788 if (!m_pDS->eof())
8789 result = (m_pDS->fv(0).get_asInt() > 0);
8791 m_pDS->close();
8793 catch (...)
8795 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8797 return result;
8800 ScraperPtr CVideoDatabase::GetScraperForPath( const std::string& strPath )
8802 SScanSettings settings;
8803 return GetScraperForPath(strPath, settings);
8806 ScraperPtr CVideoDatabase::GetScraperForPath(const std::string& strPath, SScanSettings& settings)
8808 bool dummy;
8809 return GetScraperForPath(strPath, settings, dummy);
8812 ScraperPtr CVideoDatabase::GetScraperForPath(const std::string& strPath, SScanSettings& settings, bool& foundDirectly)
8814 foundDirectly = false;
8817 if (strPath.empty() || !m_pDB || !m_pDS)
8818 return ScraperPtr();
8820 ScraperPtr scraper;
8821 std::string strPath2;
8823 if (URIUtils::IsMultiPath(strPath))
8824 strPath2 = CMultiPathDirectory::GetFirstPath(strPath);
8825 else
8826 strPath2 = strPath;
8828 std::string strPath1 = URIUtils::GetDirectory(strPath2);
8829 int idPath = GetPathId(strPath1);
8831 if (idPath > -1)
8833 std::string strSQL = PrepareSQL(
8834 "SELECT path.strContent, path.strScraper, path.scanRecursive, path.useFolderNames, "
8835 "path.strSettings, path.noUpdate, path.exclude, path.allAudio FROM path WHERE idPath=%i",
8836 idPath);
8837 m_pDS->query( strSQL );
8840 int iFound = 1;
8841 CONTENT_TYPE content = CONTENT_NONE;
8842 if (!m_pDS->eof())
8843 { // path is stored in db
8845 settings.m_allExtAudio = m_pDS->fv("path.allAudio").get_asBool();
8847 if (m_pDS->fv("path.exclude").get_asBool())
8849 settings.exclude = true;
8850 m_pDS->close();
8851 return ScraperPtr();
8853 settings.exclude = false;
8855 // try and ascertain scraper for this path
8856 std::string strcontent = m_pDS->fv("path.strContent").get_asString();
8857 StringUtils::ToLower(strcontent);
8858 content = TranslateContent(strcontent);
8860 //FIXME paths stored should not have empty strContent
8861 //assert(content != CONTENT_NONE);
8862 std::string scraperID = m_pDS->fv("path.strScraper").get_asString();
8864 AddonPtr addon;
8865 if (!scraperID.empty() &&
8866 CServiceBroker::GetAddonMgr().GetAddon(scraperID, addon, ADDON::OnlyEnabled::CHOICE_YES))
8868 scraper = std::dynamic_pointer_cast<CScraper>(addon);
8869 if (!scraper)
8870 return ScraperPtr();
8872 // store this path's content & settings
8873 scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
8874 settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
8875 settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
8876 settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
8880 if (content == CONTENT_NONE)
8881 { // this path is not saved in db
8882 // we must drill up until a scraper is configured
8883 std::string strParent;
8884 while (URIUtils::GetParentPath(strPath1, strParent))
8886 iFound++;
8888 std::string strSQL =
8889 PrepareSQL("SELECT path.strContent, path.strScraper, path.scanRecursive, "
8890 "path.useFolderNames, path.strSettings, path.noUpdate, path.exclude, "
8891 "path.allAudio FROM path WHERE strPath='%s'",
8892 strParent.c_str());
8893 m_pDS->query(strSQL);
8895 CONTENT_TYPE content = CONTENT_NONE;
8896 if (!m_pDS->eof())
8898 settings.m_allExtAudio = m_pDS->fv("path.allAudio").get_asBool();
8899 std::string strcontent = m_pDS->fv("path.strContent").get_asString();
8900 StringUtils::ToLower(strcontent);
8901 if (m_pDS->fv("path.exclude").get_asBool())
8903 settings.exclude = true;
8904 scraper.reset();
8905 m_pDS->close();
8906 break;
8909 content = TranslateContent(strcontent);
8911 AddonPtr addon;
8912 if (content != CONTENT_NONE &&
8913 CServiceBroker::GetAddonMgr().GetAddon(m_pDS->fv("path.strScraper").get_asString(),
8914 addon, ADDON::OnlyEnabled::CHOICE_YES))
8916 scraper = std::dynamic_pointer_cast<CScraper>(addon);
8917 scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
8918 settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
8919 settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
8920 settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
8921 settings.exclude = false;
8922 break;
8925 strPath1 = strParent;
8928 m_pDS->close();
8930 if (!scraper || scraper->Content() == CONTENT_NONE)
8931 return ScraperPtr();
8933 if (scraper->Content() == CONTENT_TVSHOWS)
8935 settings.recurse = 0;
8936 if(settings.parent_name) // single show
8938 settings.parent_name_root = settings.parent_name = (iFound == 1);
8940 else // show root
8942 settings.parent_name_root = settings.parent_name = (iFound == 2);
8945 else if (scraper->Content() == CONTENT_MOVIES)
8947 settings.recurse = settings.recurse - (iFound-1);
8948 settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
8950 else if (scraper->Content() == CONTENT_MUSICVIDEOS)
8952 settings.recurse = settings.recurse - (iFound-1);
8953 settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
8955 else
8957 iFound = 0;
8958 return ScraperPtr();
8960 foundDirectly = (iFound == 1);
8961 return scraper;
8963 catch (...)
8965 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8967 return ScraperPtr();
8970 bool CVideoDatabase::GetUseAllExternalAudioForVideo(const std::string& videoPath)
8972 // Find longest configured source path for given video path
8973 std::string strSQL = PrepareSQL("SELECT allAudio FROM path WHERE allAudio IS NOT NULL AND "
8974 "instr('%s', strPath) = 1 ORDER BY length(strPath) DESC LIMIT 1",
8975 videoPath.c_str());
8976 m_pDS->query(strSQL);
8978 if (!m_pDS->eof())
8979 return m_pDS->fv("allAudio").get_asBool();
8981 return false;
8984 std::string CVideoDatabase::GetContentForPath(const std::string& strPath)
8986 SScanSettings settings;
8987 bool foundDirectly = false;
8988 ScraperPtr scraper = GetScraperForPath(strPath, settings, foundDirectly);
8989 if (scraper)
8991 if (scraper->Content() == CONTENT_TVSHOWS)
8993 // check for episodes or seasons. Assumptions are:
8994 // 1. if episodes are in the path then we're in episodes.
8995 // 2. if no episodes are found, and content was set directly on this path, then we're in shows.
8996 // 3. if no episodes are found, and content was not set directly on this path, we're in seasons (assumes tvshows/seasons/episodes)
8997 std::string sql = "SELECT COUNT(*) FROM episode_view ";
8999 if (foundDirectly)
9000 sql += PrepareSQL("WHERE strPath = '%s'", strPath.c_str());
9001 else
9002 sql += PrepareSQL("WHERE strPath LIKE '%s%%'", strPath.c_str());
9004 m_pDS->query( sql );
9005 if (m_pDS->num_rows() && m_pDS->fv(0).get_asInt() > 0)
9006 return "episodes";
9007 return foundDirectly ? "tvshows" : "seasons";
9009 return TranslateContent(scraper->Content());
9011 return "";
9014 void CVideoDatabase::GetMovieGenresByName(const std::string& strSearch, CFileItemList& items)
9016 std::string strSQL;
9020 if (nullptr == m_pDB)
9021 return;
9022 if (nullptr == m_pDS)
9023 return;
9025 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9026 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());
9027 else
9028 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());
9029 m_pDS->query( strSQL );
9031 while (!m_pDS->eof())
9033 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9034 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),
9035 *CMediaSourceSettings::GetInstance().GetSources("video")))
9037 m_pDS->next();
9038 continue;
9041 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9042 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9043 pItem->SetPath("videodb://movies/genres/"+ strDir);
9044 pItem->m_bIsFolder=true;
9045 items.Add(pItem);
9046 m_pDS->next();
9048 m_pDS->close();
9050 catch (...)
9052 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9056 void CVideoDatabase::GetMovieCountriesByName(const std::string& strSearch, CFileItemList& items)
9058 std::string strSQL;
9062 if (nullptr == m_pDB)
9063 return;
9064 if (nullptr == m_pDS)
9065 return;
9067 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9068 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());
9069 else
9070 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());
9071 m_pDS->query( strSQL );
9073 while (!m_pDS->eof())
9075 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9076 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),
9077 *CMediaSourceSettings::GetInstance().GetSources("video")))
9079 m_pDS->next();
9080 continue;
9083 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9084 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9085 pItem->SetPath("videodb://movies/genres/"+ strDir);
9086 pItem->m_bIsFolder=true;
9087 items.Add(pItem);
9088 m_pDS->next();
9090 m_pDS->close();
9092 catch (...)
9094 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9098 void CVideoDatabase::GetTvShowGenresByName(const std::string& strSearch, CFileItemList& items)
9100 std::string strSQL;
9104 if (nullptr == m_pDB)
9105 return;
9106 if (nullptr == m_pDS)
9107 return;
9109 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9110 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());
9111 else
9112 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());
9113 m_pDS->query( strSQL );
9115 while (!m_pDS->eof())
9117 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9118 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9120 m_pDS->next();
9121 continue;
9124 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9125 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9126 pItem->SetPath("videodb://tvshows/genres/"+ strDir);
9127 pItem->m_bIsFolder=true;
9128 items.Add(pItem);
9129 m_pDS->next();
9131 m_pDS->close();
9133 catch (...)
9135 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9139 void CVideoDatabase::GetMovieActorsByName(const std::string& strSearch, CFileItemList& items)
9141 std::string strSQL;
9145 if (nullptr == m_pDB)
9146 return;
9147 if (nullptr == m_pDS)
9148 return;
9150 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9151 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());
9152 else
9153 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());
9154 m_pDS->query( strSQL );
9156 while (!m_pDS->eof())
9158 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9159 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9161 m_pDS->next();
9162 continue;
9165 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9166 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9167 pItem->SetPath("videodb://movies/actors/"+ strDir);
9168 pItem->m_bIsFolder=true;
9169 items.Add(pItem);
9170 m_pDS->next();
9172 m_pDS->close();
9174 catch (...)
9176 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9180 void CVideoDatabase::GetTvShowsActorsByName(const std::string& strSearch, CFileItemList& items)
9182 std::string strSQL;
9186 if (nullptr == m_pDB)
9187 return;
9188 if (nullptr == m_pDS)
9189 return;
9191 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9192 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());
9193 else
9194 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());
9195 m_pDS->query( strSQL );
9197 while (!m_pDS->eof())
9199 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9200 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9202 m_pDS->next();
9203 continue;
9206 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9207 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9208 pItem->SetPath("videodb://tvshows/actors/"+ strDir);
9209 pItem->m_bIsFolder=true;
9210 items.Add(pItem);
9211 m_pDS->next();
9213 m_pDS->close();
9215 catch (...)
9217 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9221 void CVideoDatabase::GetMusicVideoArtistsByName(const std::string& strSearch, CFileItemList& items)
9223 std::string strSQL;
9227 if (nullptr == m_pDB)
9228 return;
9229 if (nullptr == m_pDS)
9230 return;
9232 std::string strLike;
9233 if (!strSearch.empty())
9234 strLike = "and actor.name like '%%%s%%'";
9235 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9236 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());
9237 else
9238 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());
9239 m_pDS->query( strSQL );
9241 while (!m_pDS->eof())
9243 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9244 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9246 m_pDS->next();
9247 continue;
9250 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9251 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9252 pItem->SetPath("videodb://musicvideos/artists/"+ strDir);
9253 pItem->m_bIsFolder=true;
9254 items.Add(pItem);
9255 m_pDS->next();
9257 m_pDS->close();
9259 catch (...)
9261 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9265 void CVideoDatabase::GetMusicVideoGenresByName(const std::string& strSearch, CFileItemList& items)
9267 std::string strSQL;
9271 if (nullptr == m_pDB)
9272 return;
9273 if (nullptr == m_pDS)
9274 return;
9276 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9277 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());
9278 else
9279 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());
9280 m_pDS->query( strSQL );
9282 while (!m_pDS->eof())
9284 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9285 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9287 m_pDS->next();
9288 continue;
9291 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9292 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9293 pItem->SetPath("videodb://musicvideos/genres/"+ strDir);
9294 pItem->m_bIsFolder=true;
9295 items.Add(pItem);
9296 m_pDS->next();
9298 m_pDS->close();
9300 catch (...)
9302 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9306 void CVideoDatabase::GetMusicVideoAlbumsByName(const std::string& strSearch, CFileItemList& items)
9308 std::string strSQL;
9312 if (nullptr == m_pDB)
9313 return;
9314 if (nullptr == m_pDS)
9315 return;
9317 strSQL = StringUtils::Format("SELECT DISTINCT"
9318 " musicvideo.c{:02},"
9319 " musicvideo.idMVideo,"
9320 " path.strPath"
9321 " FROM"
9322 " musicvideo"
9323 " JOIN files ON"
9324 " files.idFile=musicvideo.idFile"
9325 " JOIN path ON"
9326 " path.idPath=files.idPath",
9327 VIDEODB_ID_MUSICVIDEO_ALBUM);
9328 if (!strSearch.empty())
9329 strSQL += PrepareSQL(" WHERE musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_ALBUM, strSearch.c_str());
9331 m_pDS->query( strSQL );
9333 while (!m_pDS->eof())
9335 if (m_pDS->fv(0).get_asString().empty())
9337 m_pDS->next();
9338 continue;
9341 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9342 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9344 m_pDS->next();
9345 continue;
9348 CFileItemPtr pItem(new CFileItem(m_pDS->fv(0).get_asString()));
9349 std::string strDir = std::to_string(m_pDS->fv(1).get_asInt());
9350 pItem->SetPath("videodb://musicvideos/titles/"+ strDir);
9351 pItem->m_bIsFolder=false;
9352 items.Add(pItem);
9353 m_pDS->next();
9355 m_pDS->close();
9357 catch (...)
9359 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9363 void CVideoDatabase::GetMusicVideosByAlbum(const std::string& strSearch, CFileItemList& items)
9365 std::string strSQL;
9369 if (nullptr == m_pDB)
9370 return;
9371 if (nullptr == m_pDS)
9372 return;
9374 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9375 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());
9376 else
9377 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());
9378 m_pDS->query( strSQL );
9380 while (!m_pDS->eof())
9382 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9383 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9385 m_pDS->next();
9386 continue;
9389 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" - "+m_pDS->fv(2).get_asString()));
9390 std::string strDir =
9391 StringUtils::Format("3/2/{}", m_pDS->fv("musicvideo.idMVideo").get_asInt());
9393 pItem->SetPath("videodb://"+ strDir);
9394 pItem->m_bIsFolder=false;
9395 items.Add(pItem);
9396 m_pDS->next();
9398 m_pDS->close();
9400 catch (...)
9402 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9406 bool CVideoDatabase::GetMusicVideosByWhere(const std::string &baseDir, const Filter &filter, CFileItemList &items, bool checkLocks /*= true*/, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
9411 if (nullptr == m_pDB)
9412 return false;
9413 if (nullptr == m_pDS)
9414 return false;
9416 int total = -1;
9418 std::string strSQL = "select %s from musicvideo_view ";
9419 CVideoDbUrl videoUrl;
9420 if (!videoUrl.FromString(baseDir))
9421 return false;
9423 std::string strSQLExtra;
9424 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
9425 std::string strArtist;
9426 int idArtist = -1;
9427 // If we have an artistid then get the artist name and use that to fix up the path displayed in
9428 // the GUI (musicvideos/artist-name instead of musicvideos/artistid)
9429 auto option = options.find("artistid");
9430 if (option != options.end())
9432 idArtist = option->second.asInteger();
9433 strArtist = GetSingleValue(
9434 PrepareSQL("SELECT name FROM actor where actor_id = '%i'", idArtist), m_pDS)
9435 .c_str();
9437 Filter extFilter = filter;
9438 SortDescription sorting = sortDescription;
9439 if (!BuildSQL(baseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
9440 return false;
9442 // Apply the limiting directly here if there's no special sorting but limiting
9443 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
9444 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
9445 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
9447 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
9448 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
9451 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
9453 int iRowsFound = RunQuery(strSQL);
9455 // store the total value of items as a property
9456 if (total < iRowsFound)
9457 total = iRowsFound;
9458 items.SetProperty("total", total);
9460 if (iRowsFound <= 0)
9461 return iRowsFound == 0;
9463 DatabaseResults results;
9464 results.reserve(iRowsFound);
9465 if (!SortUtils::SortFromDataset(sorting, MediaTypeMusicVideo, m_pDS, results))
9466 return false;
9468 // get data from returned rows
9469 items.Reserve(results.size());
9470 // get songs from returned subtable
9471 const query_data &data = m_pDS->get_result_set().records;
9472 for (const auto &i : results)
9474 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
9475 const dbiplus::sql_record* const record = data.at(targetRow);
9477 CVideoInfoTag musicvideo = GetDetailsForMusicVideo(record, getDetails);
9478 if (!checkLocks || m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || g_passwordManager.bMasterUser ||
9479 g_passwordManager.IsDatabasePathUnlocked(musicvideo.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
9481 CFileItemPtr item(new CFileItem(musicvideo));
9483 CVideoDbUrl itemUrl = videoUrl;
9484 std::string path = std::to_string(record->at(0).get_asInt());
9485 itemUrl.AppendPath(path);
9486 item->SetPath(itemUrl.ToString());
9487 item->SetDynPath(musicvideo.m_strFileNameAndPath);
9489 item->SetOverlayImage(musicvideo.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
9490 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
9491 items.Add(item);
9495 // cleanup
9496 m_pDS->close();
9497 if (!strArtist.empty())
9498 items.SetProperty("customtitle", strArtist);
9499 return true;
9501 catch (...)
9503 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
9505 return false;
9508 unsigned int CVideoDatabase::GetRandomMusicVideoIDs(const std::string& strWhere, std::vector<std::pair<int,int> > &songIDs)
9512 if (nullptr == m_pDB)
9513 return 0;
9514 if (nullptr == m_pDS)
9515 return 0;
9517 std::string strSQL = "select distinct idMVideo from musicvideo_view";
9518 if (!strWhere.empty())
9519 strSQL += " where " + strWhere;
9520 strSQL += PrepareSQL(" ORDER BY RANDOM()");
9522 if (!m_pDS->query(strSQL)) return 0;
9523 songIDs.clear();
9524 if (m_pDS->num_rows() == 0)
9526 m_pDS->close();
9527 return 0;
9529 songIDs.reserve(m_pDS->num_rows());
9530 while (!m_pDS->eof())
9532 songIDs.push_back(std::make_pair<int,int>(2,m_pDS->fv(0).get_asInt()));
9533 m_pDS->next();
9534 } // cleanup
9535 m_pDS->close();
9536 return songIDs.size();
9538 catch (...)
9540 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strWhere);
9542 return 0;
9545 int CVideoDatabase::GetMatchingMusicVideo(const std::string& strArtist, const std::string& strAlbum, const std::string& strTitle)
9549 if (nullptr == m_pDB)
9550 return -1;
9551 if (nullptr == m_pDS)
9552 return -1;
9554 std::string strSQL;
9555 if (strAlbum.empty() && strTitle.empty())
9556 { // we want to return matching artists only
9557 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9558 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());
9559 else
9560 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());
9562 else
9563 { // we want to return the matching musicvideo
9564 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9565 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());
9566 else
9567 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());
9569 m_pDS->query( strSQL );
9571 if (m_pDS->eof())
9572 return -1;
9574 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9575 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9577 m_pDS->close();
9578 return -1;
9581 int lResult = m_pDS->fv(0).get_asInt();
9582 m_pDS->close();
9583 return lResult;
9585 catch (...)
9587 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
9589 return -1;
9592 void CVideoDatabase::GetMoviesByName(const std::string& strSearch, CFileItemList& items)
9594 std::string strSQL;
9598 if (nullptr == m_pDB)
9599 return;
9600 if (nullptr == m_pDS)
9601 return;
9603 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9604 strSQL = PrepareSQL("SELECT movie.idMovie, movie.c%02d, path.strPath, movie.idSet FROM movie "
9605 "INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON "
9606 "path.idPath=files.idPath "
9607 "WHERE movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
9608 VIDEODB_ID_TITLE, VIDEODB_ID_TITLE, strSearch.c_str(),
9609 VIDEODB_ID_ORIGINALTITLE, strSearch.c_str());
9610 else
9611 strSQL = PrepareSQL("SELECT movie.idMovie,movie.c%02d, movie.idSet FROM movie WHERE "
9612 "movie.c%02d like '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
9613 VIDEODB_ID_TITLE, VIDEODB_ID_TITLE, strSearch.c_str(),
9614 VIDEODB_ID_ORIGINALTITLE, strSearch.c_str());
9615 m_pDS->query( strSQL );
9617 while (!m_pDS->eof())
9619 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9620 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9622 m_pDS->next();
9623 continue;
9626 int movieId = m_pDS->fv("movie.idMovie").get_asInt();
9627 int setId = m_pDS->fv("movie.idSet").get_asInt();
9628 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9629 std::string path;
9630 if (setId <= 0 || !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS))
9631 path = StringUtils::Format("videodb://movies/titles/{}", movieId);
9632 else
9633 path = StringUtils::Format("videodb://movies/sets/{}/{}", setId, movieId);
9634 pItem->SetPath(path);
9635 pItem->m_bIsFolder=false;
9636 items.Add(pItem);
9637 m_pDS->next();
9639 m_pDS->close();
9641 catch (...)
9643 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9647 void CVideoDatabase::GetTvShowsByName(const std::string& strSearch, CFileItemList& items)
9649 std::string strSQL;
9653 if (nullptr == m_pDB)
9654 return;
9655 if (nullptr == m_pDS)
9656 return;
9658 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9659 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());
9660 else
9661 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());
9662 m_pDS->query( strSQL );
9664 while (!m_pDS->eof())
9666 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9667 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9669 m_pDS->next();
9670 continue;
9673 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9674 std::string strDir =
9675 StringUtils::Format("tvshows/titles/{}/", m_pDS->fv("tvshow.idShow").get_asInt());
9677 pItem->SetPath("videodb://"+ strDir);
9678 pItem->m_bIsFolder=true;
9679 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv("tvshow.idShow").get_asInt();
9680 items.Add(pItem);
9681 m_pDS->next();
9683 m_pDS->close();
9685 catch (...)
9687 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9691 void CVideoDatabase::GetEpisodesByName(const std::string& strSearch, CFileItemList& items)
9693 std::string strSQL;
9697 if (nullptr == m_pDB)
9698 return;
9699 if (nullptr == m_pDS)
9700 return;
9702 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9703 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());
9704 else
9705 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());
9706 m_pDS->query( strSQL );
9708 while (!m_pDS->eof())
9710 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9711 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9713 m_pDS->next();
9714 continue;
9717 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
9718 std::string path = StringUtils::Format("videodb://tvshows/titles/{}/{}/{}",
9719 m_pDS->fv("episode.idShow").get_asInt(),
9720 m_pDS->fv(2).get_asInt(), m_pDS->fv(0).get_asInt());
9721 pItem->SetPath(path);
9722 pItem->m_bIsFolder=false;
9723 items.Add(pItem);
9724 m_pDS->next();
9726 m_pDS->close();
9728 catch (...)
9730 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9734 void CVideoDatabase::GetMusicVideosByName(const std::string& strSearch, CFileItemList& items)
9736 // Alternative searching - not quite as fast though due to
9737 // retrieving all information
9738 // 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()));
9739 // GetMusicVideosByWhere("videodb://musicvideos/titles/", filter, items);
9740 std::string strSQL;
9744 if (nullptr == m_pDB)
9745 return;
9746 if (nullptr == m_pDS)
9747 return;
9749 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9750 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());
9751 else
9752 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());
9753 m_pDS->query( strSQL );
9755 while (!m_pDS->eof())
9757 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9758 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9760 m_pDS->next();
9761 continue;
9764 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9765 std::string strDir =
9766 StringUtils::Format("3/2/{}", m_pDS->fv("musicvideo.idMVideo").get_asInt());
9768 pItem->SetPath("videodb://"+ strDir);
9769 pItem->m_bIsFolder=false;
9770 items.Add(pItem);
9771 m_pDS->next();
9773 m_pDS->close();
9775 catch (...)
9777 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9781 void CVideoDatabase::GetEpisodesByPlot(const std::string& strSearch, CFileItemList& items)
9783 // Alternative searching - not quite as fast though due to
9784 // retrieving all information
9785 // Filter filter;
9786 // 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());
9787 // 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());
9788 // GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
9789 // return;
9790 std::string strSQL;
9794 if (nullptr == m_pDB)
9795 return;
9796 if (nullptr == m_pDS)
9797 return;
9799 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9800 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());
9801 else
9802 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());
9803 m_pDS->query( strSQL );
9805 while (!m_pDS->eof())
9807 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9808 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9810 m_pDS->next();
9811 continue;
9814 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
9815 std::string path = StringUtils::Format("videodb://tvshows/titles/{}/{}/{}",
9816 m_pDS->fv("episode.idShow").get_asInt(),
9817 m_pDS->fv(2).get_asInt(), m_pDS->fv(0).get_asInt());
9818 pItem->SetPath(path);
9819 pItem->m_bIsFolder=false;
9820 items.Add(pItem);
9821 m_pDS->next();
9823 m_pDS->close();
9825 catch (...)
9827 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9831 void CVideoDatabase::GetMoviesByPlot(const std::string& strSearch, CFileItemList& items)
9833 std::string strSQL;
9837 if (nullptr == m_pDB)
9838 return;
9839 if (nullptr == m_pDS)
9840 return;
9842 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9843 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());
9844 else
9845 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());
9847 m_pDS->query( strSQL );
9849 while (!m_pDS->eof())
9851 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9852 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9854 m_pDS->next();
9855 continue;
9858 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9859 std::string path =
9860 StringUtils::Format("videodb://movies/titles/{}", m_pDS->fv(0).get_asInt());
9861 pItem->SetPath(path);
9862 pItem->m_bIsFolder=false;
9864 items.Add(pItem);
9865 m_pDS->next();
9867 m_pDS->close();
9870 catch (...)
9872 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9876 void CVideoDatabase::GetMovieDirectorsByName(const std::string& strSearch, CFileItemList& items)
9878 std::string strSQL;
9882 if (nullptr == m_pDB)
9883 return;
9884 if (nullptr == m_pDS)
9885 return;
9887 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9888 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());
9889 else
9890 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());
9892 m_pDS->query( strSQL );
9894 while (!m_pDS->eof())
9896 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9897 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9899 m_pDS->next();
9900 continue;
9903 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9904 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9906 pItem->SetPath("videodb://movies/directors/"+ strDir);
9907 pItem->m_bIsFolder=true;
9908 items.Add(pItem);
9909 m_pDS->next();
9911 m_pDS->close();
9913 catch (...)
9915 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9919 void CVideoDatabase::GetTvShowsDirectorsByName(const std::string& strSearch, CFileItemList& items)
9921 std::string strSQL;
9925 if (nullptr == m_pDB)
9926 return;
9927 if (nullptr == m_pDS)
9928 return;
9930 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9931 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());
9932 else
9933 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());
9935 m_pDS->query( strSQL );
9937 while (!m_pDS->eof())
9939 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9940 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9942 m_pDS->next();
9943 continue;
9946 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9947 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9949 pItem->SetPath("videodb://tvshows/directors/"+ strDir);
9950 pItem->m_bIsFolder=true;
9951 items.Add(pItem);
9952 m_pDS->next();
9954 m_pDS->close();
9956 catch (...)
9958 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9962 void CVideoDatabase::GetMusicVideoDirectorsByName(const std::string& strSearch, CFileItemList& items)
9964 std::string strSQL;
9968 if (nullptr == m_pDB)
9969 return;
9970 if (nullptr == m_pDS)
9971 return;
9973 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9974 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());
9975 else
9976 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());
9978 m_pDS->query( strSQL );
9980 while (!m_pDS->eof())
9982 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9983 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9985 m_pDS->next();
9986 continue;
9989 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9990 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9992 pItem->SetPath("videodb://musicvideos/albums/"+ strDir);
9993 pItem->m_bIsFolder=true;
9994 items.Add(pItem);
9995 m_pDS->next();
9997 m_pDS->close();
9999 catch (...)
10001 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
10005 void CVideoDatabase::CleanDatabase(CGUIDialogProgressBarHandle* handle,
10006 const std::set<int>& paths,
10007 bool showProgress)
10009 CGUIDialogProgress* progress = NULL;
10012 if (nullptr == m_pDB)
10013 return;
10014 if (nullptr == m_pDS)
10015 return;
10016 if (nullptr == m_pDS2)
10017 return;
10019 auto start = std::chrono::steady_clock::now();
10020 CLog::Log(LOGINFO, "{}: Starting videodatabase cleanup ..", __FUNCTION__);
10021 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary,
10022 "OnCleanStarted");
10024 if (handle)
10026 handle->SetTitle(g_localizeStrings.Get(700));
10027 handle->SetText("");
10029 else if (showProgress)
10031 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(
10032 WINDOW_DIALOG_PROGRESS);
10033 if (progress)
10035 progress->SetHeading(CVariant{700});
10036 progress->SetLine(0, CVariant{""});
10037 progress->SetLine(1, CVariant{313});
10038 progress->SetLine(2, CVariant{330});
10039 progress->SetPercentage(0);
10040 progress->Open();
10041 progress->ShowProgressBar(true);
10045 BeginTransaction();
10047 // find all the files
10048 std::string sql = "SELECT files.idFile, files.strFileName, path.strPath FROM files "
10049 "INNER JOIN path ON path.idPath=files.idPath";
10050 if (!paths.empty())
10052 std::string strPaths;
10053 for (const auto& i : paths)
10054 strPaths += StringUtils::Format(",{}", i);
10055 sql += PrepareSQL(" AND path.idPath IN (%s)", strPaths.substr(1).c_str());
10058 // For directory caching to work properly, we need to sort the files by path
10059 sql += " ORDER BY path.strPath";
10061 m_pDS2->query(sql);
10062 if (m_pDS2->num_rows() > 0)
10064 std::string filesToTestForDelete;
10065 std::vector<CMediaSource> videoSources(
10066 *CMediaSourceSettings::GetInstance().GetSources("video"));
10067 CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources);
10069 int total = m_pDS2->num_rows();
10070 int current = 0;
10071 std::string lastDir;
10072 bool gotDir = true;
10074 while (!m_pDS2->eof())
10076 std::string path = m_pDS2->fv("path.strPath").get_asString();
10077 std::string fileName = m_pDS2->fv("files.strFileName").get_asString();
10078 std::string fullPath;
10079 ConstructPath(fullPath, path, fileName);
10081 // get the first stacked file
10082 if (URIUtils::IsStack(fullPath))
10083 fullPath = CStackDirectory::GetFirstStackedFile(fullPath);
10085 // get the actual archive path
10086 if (URIUtils::IsInArchive(fullPath))
10087 fullPath = CURL(fullPath).GetHostName();
10089 bool del = true;
10090 if (URIUtils::IsPlugin(fullPath))
10092 SScanSettings settings;
10093 bool foundDirectly = false;
10094 ScraperPtr scraper = GetScraperForPath(fullPath, settings, foundDirectly);
10095 if (scraper &&
10096 CPluginDirectory::CheckExists(TranslateContent(scraper->Content()), fullPath))
10097 del = false;
10099 else
10101 // Only consider keeping this file if not optical and belonging to a (matching) source
10102 bool bIsSource;
10103 if (!URIUtils::IsOnDVD(fullPath) &&
10104 CUtil::GetMatchingSource(fullPath, videoSources, bIsSource) >= 0)
10106 const std::string pathDir = URIUtils::GetDirectory(fullPath);
10108 // Cache file's directory in case it's different from the previous file
10109 if (lastDir != pathDir)
10111 lastDir = pathDir;
10112 CFileItemList items; // Dummy list
10113 gotDir = CDirectory::GetDirectory(pathDir, items, "",
10114 DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO);
10117 // Keep existing files
10118 if (gotDir && CFile::Exists(fullPath, true))
10119 del = false;
10122 if (del)
10123 filesToTestForDelete += m_pDS2->fv("files.idFile").get_asString() + ",";
10125 if (handle == NULL && progress != NULL)
10127 int percentage = current * 100 / total;
10128 if (percentage > progress->GetPercentage())
10130 progress->SetPercentage(percentage);
10131 progress->Progress();
10133 if (progress->IsCanceled())
10135 progress->Close();
10136 m_pDS2->close();
10137 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary,
10138 "OnCleanFinished");
10139 return;
10142 else if (handle != NULL)
10143 handle->SetPercentage(current * 100 / (float)total);
10145 m_pDS2->next();
10146 current++;
10148 m_pDS2->close();
10150 std::string filesToDelete;
10152 // Add any files that don't have a valid idPath entry to the filesToDelete list.
10153 m_pDS->query("SELECT files.idFile FROM files WHERE NOT EXISTS (SELECT 1 FROM path "
10154 "WHERE path.idPath = files.idPath)");
10155 while (!m_pDS->eof())
10157 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
10158 filesToTestForDelete += file;
10159 filesToDelete += file;
10161 m_pDS->next();
10163 m_pDS->close();
10165 std::map<int, bool> pathsDeleteDecisions;
10166 std::vector<int> movieIDs;
10167 std::vector<int> tvshowIDs;
10168 std::vector<int> episodeIDs;
10169 std::vector<int> musicVideoIDs;
10170 std::vector<int> videoVersionIDs;
10172 if (!filesToTestForDelete.empty())
10174 StringUtils::TrimRight(filesToTestForDelete, ",");
10176 movieIDs = CleanMediaType(MediaTypeMovie, filesToTestForDelete, pathsDeleteDecisions,
10177 filesToDelete, !showProgress);
10178 episodeIDs = CleanMediaType(MediaTypeEpisode, filesToTestForDelete, pathsDeleteDecisions,
10179 filesToDelete, !showProgress);
10180 musicVideoIDs = CleanMediaType(MediaTypeMusicVideo, filesToTestForDelete,
10181 pathsDeleteDecisions, filesToDelete, !showProgress);
10182 videoVersionIDs = CleanMediaType(MediaTypeVideoVersion, filesToTestForDelete,
10183 pathsDeleteDecisions, filesToDelete, !showProgress);
10186 if (progress != NULL)
10188 progress->SetPercentage(100);
10189 progress->Progress();
10192 if (!filesToDelete.empty())
10194 filesToDelete = "(" + StringUtils::TrimRight(filesToDelete, ",") + ")";
10196 // Clean hashes of all paths that files are deleted from
10197 // Otherwise there is a mismatch between the path contents and the hash in the
10198 // database, leading to potentially missed items on re-scan (if deleted files are
10199 // later re-added to a source)
10200 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning path hashes");
10201 m_pDS->query("SELECT DISTINCT strPath FROM path JOIN files ON files.idPath=path.idPath "
10202 "WHERE files.idFile IN " +
10203 filesToDelete);
10204 int pathHashCount = m_pDS->num_rows();
10205 while (!m_pDS->eof())
10207 InvalidatePathHash(m_pDS->fv("strPath").get_asString());
10208 m_pDS->next();
10210 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaned {} path hashes", pathHashCount);
10212 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning files table", __FUNCTION__);
10213 sql = "DELETE FROM files WHERE idFile IN " + filesToDelete;
10214 m_pDS->exec(sql);
10217 if (!movieIDs.empty())
10219 std::string moviesToDelete;
10220 for (const auto& i : movieIDs)
10221 moviesToDelete += StringUtils::Format("{},", i);
10222 moviesToDelete = "(" + StringUtils::TrimRight(moviesToDelete, ",") + ")";
10224 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning movie table", __FUNCTION__);
10225 sql = "DELETE FROM movie WHERE idMovie IN " + moviesToDelete;
10226 m_pDS->exec(sql);
10229 if (!episodeIDs.empty())
10231 std::string episodesToDelete;
10232 for (const auto& i : episodeIDs)
10233 episodesToDelete += StringUtils::Format("{},", i);
10234 episodesToDelete = "(" + StringUtils::TrimRight(episodesToDelete, ",") + ")";
10236 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning episode table", __FUNCTION__);
10237 sql = "DELETE FROM episode WHERE idEpisode IN " + episodesToDelete;
10238 m_pDS->exec(sql);
10241 CLog::Log(LOGDEBUG, LOGDATABASE,
10242 "{}: Cleaning paths that don't exist and have content set...", __FUNCTION__);
10243 sql = "SELECT path.idPath, path.strPath, path.idParentPath FROM path "
10244 "WHERE NOT ((strContent IS NULL OR strContent = '') "
10245 "AND (strSettings IS NULL OR strSettings = '') "
10246 "AND (strHash IS NULL OR strHash = '') "
10247 "AND (exclude IS NULL OR exclude != 1))";
10248 m_pDS2->query(sql);
10249 std::string strIds;
10250 while (!m_pDS2->eof())
10252 auto pathsDeleteDecision = pathsDeleteDecisions.find(m_pDS2->fv(0).get_asInt());
10253 // Check if we have a decision for the parent path
10254 auto pathsDeleteDecisionByParent = pathsDeleteDecisions.find(m_pDS2->fv(2).get_asInt());
10255 std::string path = m_pDS2->fv(1).get_asString();
10257 bool exists = false;
10258 if (URIUtils::IsPlugin(path))
10260 SScanSettings settings;
10261 bool foundDirectly = false;
10262 ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
10263 if (scraper && CPluginDirectory::CheckExists(TranslateContent(scraper->Content()), path))
10264 exists = true;
10266 else
10267 exists = CDirectory::Exists(path, false);
10269 if (((pathsDeleteDecision != pathsDeleteDecisions.end() && pathsDeleteDecision->second) ||
10270 (pathsDeleteDecision == pathsDeleteDecisions.end() && !exists)) &&
10271 ((pathsDeleteDecisionByParent != pathsDeleteDecisions.end() &&
10272 pathsDeleteDecisionByParent->second) ||
10273 (pathsDeleteDecisionByParent == pathsDeleteDecisions.end())))
10274 strIds += m_pDS2->fv(0).get_asString() + ",";
10276 m_pDS2->next();
10278 m_pDS2->close();
10280 if (!strIds.empty())
10282 sql = PrepareSQL("DELETE FROM path WHERE idPath IN (%s)",
10283 StringUtils::TrimRight(strIds, ",").c_str());
10284 m_pDS->exec(sql);
10285 sql = "DELETE FROM tvshowlinkpath "
10286 "WHERE NOT EXISTS (SELECT 1 FROM path WHERE path.idPath = tvshowlinkpath.idPath)";
10287 m_pDS->exec(sql);
10290 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning tvshow table", __FUNCTION__);
10292 std::string tvshowsToDelete;
10293 sql = "SELECT idShow FROM tvshow "
10294 "WHERE NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idShow = "
10295 "tvshow.idShow)";
10296 m_pDS->query(sql);
10297 while (!m_pDS->eof())
10299 tvshowIDs.push_back(m_pDS->fv(0).get_asInt());
10300 tvshowsToDelete += m_pDS->fv(0).get_asString() + ",";
10301 m_pDS->next();
10303 m_pDS->close();
10304 if (!tvshowsToDelete.empty())
10306 sql = "DELETE FROM tvshow WHERE idShow IN (" +
10307 StringUtils::TrimRight(tvshowsToDelete, ",") + ")";
10308 m_pDS->exec(sql);
10311 if (!musicVideoIDs.empty())
10313 std::string musicVideosToDelete;
10314 for (const auto& i : musicVideoIDs)
10315 musicVideosToDelete += StringUtils::Format("{},", i);
10316 musicVideosToDelete = "(" + StringUtils::TrimRight(musicVideosToDelete, ",") + ")";
10318 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning musicvideo table", __FUNCTION__);
10319 sql = "DELETE FROM musicvideo WHERE idMVideo IN " + musicVideosToDelete;
10320 m_pDS->exec(sql);
10323 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning path table", __FUNCTION__);
10324 sql = StringUtils::Format(
10325 "DELETE FROM path "
10326 "WHERE (strContent IS NULL OR strContent = '') "
10327 "AND (strSettings IS NULL OR strSettings = '') "
10328 "AND (strHash IS NULL OR strHash = '') "
10329 "AND (exclude IS NULL OR exclude != 1) "
10330 "AND (idParentPath IS NULL OR NOT EXISTS (SELECT 1 FROM (SELECT idPath FROM path) as "
10331 "parentPath WHERE parentPath.idPath = path.idParentPath)) " // MySQL only fix (#5007)
10332 "AND NOT EXISTS (SELECT 1 FROM files WHERE files.idPath = path.idPath) "
10333 "AND NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idPath = path.idPath) "
10334 "AND NOT EXISTS (SELECT 1 FROM movie WHERE movie.c{:02} = path.idPath) "
10335 "AND NOT EXISTS (SELECT 1 FROM episode WHERE episode.c{:02} = path.idPath) "
10336 "AND NOT EXISTS (SELECT 1 FROM musicvideo WHERE musicvideo.c{:02} = path.idPath)",
10337 VIDEODB_ID_PARENTPATHID, VIDEODB_ID_EPISODE_PARENTPATHID,
10338 VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
10339 m_pDS->exec(sql);
10341 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning genre table", __FUNCTION__);
10342 sql =
10343 "DELETE FROM genre "
10344 "WHERE NOT EXISTS (SELECT 1 FROM genre_link WHERE genre_link.genre_id = genre.genre_id)";
10345 m_pDS->exec(sql);
10347 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning country table", __FUNCTION__);
10348 sql = "DELETE FROM country WHERE NOT EXISTS (SELECT 1 FROM country_link WHERE "
10349 "country_link.country_id = country.country_id)";
10350 m_pDS->exec(sql);
10352 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning actor table of actors, directors and writers",
10353 __FUNCTION__);
10354 sql =
10355 "DELETE FROM actor "
10356 "WHERE NOT EXISTS (SELECT 1 FROM actor_link WHERE actor_link.actor_id = actor.actor_id) "
10357 "AND NOT EXISTS (SELECT 1 FROM director_link WHERE director_link.actor_id = "
10358 "actor.actor_id) "
10359 "AND NOT EXISTS (SELECT 1 FROM writer_link WHERE writer_link.actor_id = actor.actor_id)";
10360 m_pDS->exec(sql);
10362 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning studio table", __FUNCTION__);
10363 sql = "DELETE FROM studio "
10364 "WHERE NOT EXISTS (SELECT 1 FROM studio_link WHERE studio_link.studio_id = "
10365 "studio.studio_id)";
10366 m_pDS->exec(sql);
10368 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning set table", __FUNCTION__);
10369 sql = "DELETE FROM sets "
10370 "WHERE NOT EXISTS (SELECT 1 FROM movie WHERE movie.idSet = sets.idSet)";
10371 m_pDS->exec(sql);
10373 CommitTransaction();
10375 if (handle)
10376 handle->SetTitle(g_localizeStrings.Get(331));
10378 Compress(false);
10380 CUtil::DeleteVideoDatabaseDirectoryCache();
10382 auto end = std::chrono::steady_clock::now();
10383 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
10385 CLog::Log(LOGINFO, "{}: Cleaning videodatabase done. Operation took {} ms", __FUNCTION__,
10386 duration.count());
10388 for (const auto& i : movieIDs)
10389 AnnounceRemove(MediaTypeMovie, i, true);
10391 for (const auto& i : episodeIDs)
10392 AnnounceRemove(MediaTypeEpisode, i, true);
10394 for (const auto& i : tvshowIDs)
10395 AnnounceRemove(MediaTypeTvShow, i, true);
10397 for (const auto& i : musicVideoIDs)
10398 AnnounceRemove(MediaTypeMusicVideo, i, true);
10400 for (const auto& i : videoVersionIDs)
10401 AnnounceRemove(MediaTypeVideoVersion, i, true);
10404 catch (...)
10406 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
10407 RollbackTransaction();
10409 if (progress)
10410 progress->Close();
10412 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnCleanFinished");
10415 std::vector<int> CVideoDatabase::CleanMediaType(const std::string &mediaType, const std::string &cleanableFileIDs,
10416 std::map<int, bool> &pathsDeleteDecisions, std::string &deletedFileIDs, bool silent)
10418 std::vector<int> cleanedIDs;
10419 if (mediaType.empty() || cleanableFileIDs.empty())
10420 return cleanedIDs;
10422 const std::string& table = mediaType;
10423 std::string idField;
10424 std::string parentPathIdField;
10425 bool isEpisode = false;
10426 if (mediaType == MediaTypeMovie)
10428 idField = "idMovie";
10429 parentPathIdField = StringUtils::Format("{}.c{:02}", table, VIDEODB_ID_PARENTPATHID);
10431 else if (mediaType == MediaTypeEpisode)
10433 idField = "idEpisode";
10434 parentPathIdField = "showPath.idParentPath";
10435 isEpisode = true;
10437 else if (mediaType == MediaTypeMusicVideo)
10439 idField = "idMVideo";
10440 parentPathIdField = StringUtils::Format("{}.c{:02}", table, VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
10442 else if (mediaType == MediaTypeVideoVersion)
10444 idField = "idMedia";
10445 parentPathIdField = "path.idPath";
10447 else
10448 return cleanedIDs;
10450 // now grab them media items
10451 std::string sql = PrepareSQL("SELECT %s.%s, %s.idFile, path.idPath, parentPath.strPath FROM %s "
10452 "JOIN files ON files.idFile = %s.idFile "
10453 "JOIN path ON path.idPath = files.idPath ",
10454 table.c_str(), idField.c_str(), table.c_str(), table.c_str(),
10455 table.c_str());
10457 if (isEpisode)
10458 sql += "JOIN tvshowlinkpath ON tvshowlinkpath.idShow = episode.idShow JOIN path AS showPath ON showPath.idPath=tvshowlinkpath.idPath ";
10460 sql += PrepareSQL("LEFT JOIN path as parentPath ON parentPath.idPath = %s "
10461 "WHERE %s.idFile IN (%s)",
10462 parentPathIdField.c_str(),
10463 table.c_str(), cleanableFileIDs.c_str());
10465 std::vector<CMediaSource> videoSources(*CMediaSourceSettings::GetInstance().GetSources("video"));
10466 CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources);
10468 // map of parent path ID to boolean pair (if not exists and user choice)
10469 std::map<int, std::pair<bool, bool> > sourcePathsDeleteDecisions;
10470 m_pDS2->query(sql);
10471 while (!m_pDS2->eof())
10473 bool del = true;
10474 if (m_pDS2->fv(3).get_isNull() == false)
10476 std::string parentPath = m_pDS2->fv(3).get_asString();
10478 // try to find the source path the parent path belongs to
10479 SScanSettings scanSettings;
10480 std::string sourcePath;
10481 GetSourcePath(parentPath, sourcePath, scanSettings);
10483 bool bIsSourceName;
10484 bool sourceNotFound = (CUtil::GetMatchingSource(parentPath, videoSources, bIsSourceName) < 0);
10486 if (sourceNotFound && sourcePath.empty())
10487 sourcePath = parentPath;
10489 int sourcePathID = GetPathId(sourcePath);
10490 auto sourcePathsDeleteDecision = sourcePathsDeleteDecisions.find(sourcePathID);
10491 if (sourcePathsDeleteDecision == sourcePathsDeleteDecisions.end())
10493 bool sourcePathNotExists = (sourceNotFound || !CDirectory::Exists(sourcePath, false));
10494 // if the parent path exists, the file will be deleted without asking
10495 // if the parent path doesn't exist or does not belong to a valid media source,
10496 // ask the user whether to remove all items it contained
10497 if (sourcePathNotExists)
10499 // in silent mode assume that the files are just temporarily missing
10500 if (silent)
10501 del = false;
10502 else
10504 CGUIDialogYesNo* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO);
10505 if (pDialog != NULL)
10507 CURL sourceUrl(sourcePath);
10508 pDialog->SetHeading(CVariant{15012});
10509 pDialog->SetText(CVariant{StringUtils::Format(g_localizeStrings.Get(15013),
10510 sourceUrl.GetWithoutUserDetails())});
10511 pDialog->SetChoice(0, CVariant{15015});
10512 pDialog->SetChoice(1, CVariant{15014});
10513 pDialog->Open();
10515 del = !pDialog->IsConfirmed();
10520 sourcePathsDeleteDecisions.insert(std::make_pair(sourcePathID, std::make_pair(sourcePathNotExists, del)));
10521 pathsDeleteDecisions.insert(std::make_pair(sourcePathID, sourcePathNotExists && del));
10523 // the only reason not to delete the file is if the parent path doesn't
10524 // exist and the user decided to delete all the items it contained
10525 else if (sourcePathsDeleteDecision->second.first &&
10526 !sourcePathsDeleteDecision->second.second)
10527 del = false;
10529 if (scanSettings.parent_name)
10530 pathsDeleteDecisions.insert(std::make_pair(m_pDS2->fv(2).get_asInt(), del));
10533 if (del)
10535 deletedFileIDs += m_pDS2->fv(1).get_asString() + ",";
10536 cleanedIDs.push_back(m_pDS2->fv(0).get_asInt());
10539 m_pDS2->next();
10541 m_pDS2->close();
10543 return cleanedIDs;
10546 void CVideoDatabase::DumpToDummyFiles(const std::string &path)
10548 // get all tvshows
10549 CFileItemList items;
10550 GetTvShowsByWhere("videodb://tvshows/titles/", CDatabase::Filter(), items);
10551 std::string showPath = URIUtils::AddFileToFolder(path, "shows");
10552 CDirectory::Create(showPath);
10553 for (int i = 0; i < items.Size(); i++)
10555 // create a folder in this directory
10556 std::string showName = CUtil::MakeLegalFileName(items[i]->GetVideoInfoTag()->m_strShowTitle);
10557 std::string TVFolder = URIUtils::AddFileToFolder(showPath, showName);
10558 if (CDirectory::Create(TVFolder))
10559 { // right - grab the episodes and dump them as well
10560 CFileItemList episodes;
10561 Filter filter(PrepareSQL("idShow=%i", items[i]->GetVideoInfoTag()->m_iDbId));
10562 GetEpisodesByWhere("videodb://tvshows/titles/", filter, episodes);
10563 for (int i = 0; i < episodes.Size(); i++)
10565 CVideoInfoTag *tag = episodes[i]->GetVideoInfoTag();
10566 std::string episode =
10567 StringUtils::Format("{}.s{:02}e{:02}.avi", showName, tag->m_iSeason, tag->m_iEpisode);
10568 // and make a file
10569 std::string episodePath = URIUtils::AddFileToFolder(TVFolder, episode);
10570 CFile file;
10571 if (file.OpenForWrite(episodePath))
10572 file.Close();
10576 // get all movies
10577 items.Clear();
10578 GetMoviesByWhere("videodb://movies/titles/", CDatabase::Filter(), items);
10579 std::string moviePath = URIUtils::AddFileToFolder(path, "movies");
10580 CDirectory::Create(moviePath);
10581 for (int i = 0; i < items.Size(); i++)
10583 CVideoInfoTag *tag = items[i]->GetVideoInfoTag();
10584 std::string movie = StringUtils::Format("{}.avi", tag->m_strTitle);
10585 CFile file;
10586 if (file.OpenForWrite(URIUtils::AddFileToFolder(moviePath, movie)))
10587 file.Close();
10591 void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = true */, bool images /* = false */, bool actorThumbs /* false */, bool overwrite /*=false*/)
10593 int iFailCount = 0;
10594 CGUIDialogProgress *progress=NULL;
10597 if (nullptr == m_pDB)
10598 return;
10599 if (nullptr == m_pDS)
10600 return;
10601 if (nullptr == m_pDS2)
10602 return;
10604 // create a 3rd dataset as well as GetEpisodeDetails() etc. uses m_pDS2, and we need to do 3 nested queries on tv shows
10605 std::unique_ptr<Dataset> pDS;
10606 pDS.reset(m_pDB->CreateDataset());
10607 if (nullptr == pDS)
10608 return;
10610 std::unique_ptr<Dataset> pDS2;
10611 pDS2.reset(m_pDB->CreateDataset());
10612 if (nullptr == pDS2)
10613 return;
10615 // if we're exporting to a single folder, we export thumbs as well
10616 std::string exportRoot = URIUtils::AddFileToFolder(path, "kodi_videodb_" + CDateTime::GetCurrentDateTime().GetAsDBDate());
10617 std::string xmlFile = URIUtils::AddFileToFolder(exportRoot, "videodb.xml");
10618 std::string actorsDir = URIUtils::AddFileToFolder(exportRoot, "actors");
10619 std::string moviesDir = URIUtils::AddFileToFolder(exportRoot, "movies");
10620 std::string movieSetsDir = URIUtils::AddFileToFolder(exportRoot, "moviesets");
10621 std::string musicvideosDir = URIUtils::AddFileToFolder(exportRoot, "musicvideos");
10622 std::string tvshowsDir = URIUtils::AddFileToFolder(exportRoot, "tvshows");
10623 if (singleFile)
10625 images = true;
10626 overwrite = false;
10627 actorThumbs = true;
10628 CDirectory::Remove(exportRoot);
10629 CDirectory::Create(exportRoot);
10630 CDirectory::Create(actorsDir);
10631 CDirectory::Create(moviesDir);
10632 CDirectory::Create(movieSetsDir);
10633 CDirectory::Create(musicvideosDir);
10634 CDirectory::Create(tvshowsDir);
10637 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
10638 // find all movies
10639 std::string sql = "select * from movie_view";
10641 m_pDS->query(sql);
10643 if (progress)
10645 progress->SetHeading(CVariant{647});
10646 progress->SetLine(0, CVariant{650});
10647 progress->SetLine(1, CVariant{""});
10648 progress->SetLine(2, CVariant{""});
10649 progress->SetPercentage(0);
10650 progress->Open();
10651 progress->ShowProgressBar(true);
10654 int total = m_pDS->num_rows();
10655 int current = 0;
10657 // create our xml document
10658 CXBMCTinyXML xmlDoc;
10659 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10660 xmlDoc.InsertEndChild(decl);
10661 TiXmlNode *pMain = NULL;
10662 if (!singleFile)
10663 pMain = &xmlDoc;
10664 else
10666 TiXmlElement xmlMainElement("videodb");
10667 pMain = xmlDoc.InsertEndChild(xmlMainElement);
10668 XMLUtils::SetInt(pMain,"version", GetExportVersion());
10671 while (!m_pDS->eof())
10673 CVideoInfoTag movie = GetDetailsForMovie(m_pDS, VideoDbDetailsAll);
10674 // strip paths to make them relative
10675 if (StringUtils::StartsWith(movie.m_strTrailer, movie.m_strPath))
10676 movie.m_strTrailer = movie.m_strTrailer.substr(movie.m_strPath.size());
10677 std::map<std::string, std::string> artwork;
10678 if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && singleFile)
10680 TiXmlElement additionalNode("art");
10681 for (const auto &i : artwork)
10682 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10683 movie.Save(pMain, "movie", true, &additionalNode);
10685 else
10686 movie.Save(pMain, "movie", singleFile);
10688 // reset old skip state
10689 bool bSkip = false;
10691 if (progress)
10693 progress->SetLine(1, CVariant{movie.m_strTitle});
10694 progress->SetPercentage(current * 100 / total);
10695 progress->Progress();
10696 if (progress->IsCanceled())
10698 progress->Close();
10699 m_pDS->close();
10700 return;
10704 CFileItem item(movie.m_strFileNameAndPath,false);
10705 if (!singleFile && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
10707 if (!item.Exists(false))
10709 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10710 movie.m_strFileNameAndPath);
10711 bSkip = true;
10713 else
10715 std::string nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), ".nfo"));
10717 if (item.IsOpticalMediaFile())
10719 nfoFile = URIUtils::AddFileToFolder(
10720 URIUtils::GetParentPath(nfoFile),
10721 URIUtils::GetFileName(nfoFile));
10724 if (overwrite || !CFile::Exists(nfoFile, false))
10726 if(!xmlDoc.SaveFile(nfoFile))
10728 CLog::Log(LOGERROR, "{}: Movie nfo export failed! ('{}')", __FUNCTION__, nfoFile);
10729 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
10730 g_localizeStrings.Get(20302),
10731 CURL::GetRedacted(nfoFile));
10732 iFailCount++;
10737 if (!singleFile)
10739 xmlDoc.Clear();
10740 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10741 xmlDoc.InsertEndChild(decl);
10744 if (images && !bSkip)
10746 if (singleFile)
10748 std::string strFileName(movie.m_strTitle);
10749 if (movie.HasYear())
10750 strFileName += StringUtils::Format("_{}", movie.GetYear());
10751 item.SetPath(GetSafeFile(moviesDir, strFileName) + ".avi");
10753 for (const auto &i : artwork)
10755 std::string savedThumb = ART::GetLocalArt(item, i.first, false);
10756 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
10758 if (actorThumbs)
10759 ExportActorThumbs(actorsDir, movie, !singleFile, overwrite);
10761 m_pDS->next();
10762 current++;
10764 m_pDS->close();
10766 if (!singleFile)
10767 movieSetsDir = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
10768 CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER);
10769 if (images && !movieSetsDir.empty())
10771 // find all movie sets
10772 sql = "select idSet, strSet from sets";
10774 m_pDS->query(sql);
10776 total = m_pDS->num_rows();
10777 current = 0;
10779 while (!m_pDS->eof())
10781 std::string title = m_pDS->fv("strSet").get_asString();
10783 if (progress)
10785 progress->SetLine(1, CVariant{title});
10786 progress->SetPercentage(current * 100 / total);
10787 progress->Progress();
10788 if (progress->IsCanceled())
10790 progress->Close();
10791 m_pDS->close();
10792 return;
10796 std::string itemPath = URIUtils::AddFileToFolder(movieSetsDir,
10797 CUtil::MakeLegalFileName(title, LEGAL_WIN32_COMPAT));
10798 if (CDirectory::Exists(itemPath) || CDirectory::Create(itemPath))
10800 std::map<std::string, std::string> artwork;
10801 GetArtForItem(m_pDS->fv("idSet").get_asInt(), MediaTypeVideoCollection, artwork);
10802 for (const auto& art : artwork)
10804 std::string savedThumb = URIUtils::AddFileToFolder(itemPath, art.first);
10805 CServiceBroker::GetTextureCache()->Export(art.second, savedThumb, overwrite);
10808 else
10809 CLog::Log(
10810 LOGDEBUG,
10811 "CVideoDatabase::{} - Not exporting movie set '{}' as could not create folder '{}'",
10812 __FUNCTION__, title, itemPath);
10813 m_pDS->next();
10814 current++;
10816 m_pDS->close();
10819 // find all musicvideos
10820 sql = "select * from musicvideo_view";
10822 m_pDS->query(sql);
10824 total = m_pDS->num_rows();
10825 current = 0;
10827 while (!m_pDS->eof())
10829 CVideoInfoTag movie = GetDetailsForMusicVideo(m_pDS, VideoDbDetailsAll);
10830 std::map<std::string, std::string> artwork;
10831 if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && singleFile)
10833 TiXmlElement additionalNode("art");
10834 for (const auto &i : artwork)
10835 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10836 movie.Save(pMain, "musicvideo", true, &additionalNode);
10838 else
10839 movie.Save(pMain, "musicvideo", singleFile);
10841 // reset old skip state
10842 bool bSkip = false;
10844 if (progress)
10846 progress->SetLine(1, CVariant{movie.m_strTitle});
10847 progress->SetPercentage(current * 100 / total);
10848 progress->Progress();
10849 if (progress->IsCanceled())
10851 progress->Close();
10852 m_pDS->close();
10853 return;
10857 CFileItem item(movie.m_strFileNameAndPath,false);
10858 if (!singleFile && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
10860 if (!item.Exists(false))
10862 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10863 movie.m_strFileNameAndPath);
10864 bSkip = true;
10866 else
10868 std::string nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), ".nfo"));
10870 if (overwrite || !CFile::Exists(nfoFile, false))
10872 if(!xmlDoc.SaveFile(nfoFile))
10874 CLog::Log(LOGERROR, "{}: Musicvideo nfo export failed! ('{}')", __FUNCTION__,
10875 nfoFile);
10876 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
10877 g_localizeStrings.Get(20302),
10878 CURL::GetRedacted(nfoFile));
10879 iFailCount++;
10884 if (!singleFile)
10886 xmlDoc.Clear();
10887 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10888 xmlDoc.InsertEndChild(decl);
10890 if (images && !bSkip)
10892 if (singleFile)
10894 std::string strFileName(StringUtils::Join(movie.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + "." + movie.m_strTitle);
10895 if (movie.HasYear())
10896 strFileName += StringUtils::Format("_{}", movie.GetYear());
10897 item.SetPath(GetSafeFile(musicvideosDir, strFileName) + ".avi");
10899 for (const auto &i : artwork)
10901 std::string savedThumb = ART::GetLocalArt(item, i.first, false);
10902 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
10905 m_pDS->next();
10906 current++;
10908 m_pDS->close();
10910 // repeat for all tvshows
10911 sql = "SELECT * FROM tvshow_view";
10912 m_pDS->query(sql);
10914 total = m_pDS->num_rows();
10915 current = 0;
10917 while (!m_pDS->eof())
10919 CVideoInfoTag tvshow = GetDetailsForTvShow(m_pDS, VideoDbDetailsAll);
10920 GetTvShowNamedSeasons(tvshow.m_iDbId, tvshow.m_namedSeasons);
10922 std::map<int, std::map<std::string, std::string> > seasonArt;
10923 GetTvShowSeasonArt(tvshow.m_iDbId, seasonArt);
10925 std::map<std::string, std::string> artwork;
10926 if (GetArtForItem(tvshow.m_iDbId, tvshow.m_type, artwork) && singleFile)
10928 TiXmlElement additionalNode("art");
10929 for (const auto &i : artwork)
10930 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10931 for (const auto &i : seasonArt)
10933 TiXmlElement seasonNode("season");
10934 seasonNode.SetAttribute("num", i.first);
10935 for (const auto &j : i.second)
10936 XMLUtils::SetString(&seasonNode, j.first.c_str(), j.second);
10937 additionalNode.InsertEndChild(seasonNode);
10939 tvshow.Save(pMain, "tvshow", true, &additionalNode);
10941 else
10942 tvshow.Save(pMain, "tvshow", singleFile);
10944 // reset old skip state
10945 bool bSkip = false;
10947 if (progress)
10949 progress->SetLine(1, CVariant{tvshow.m_strTitle});
10950 progress->SetPercentage(current * 100 / total);
10951 progress->Progress();
10952 if (progress->IsCanceled())
10954 progress->Close();
10955 m_pDS->close();
10956 return;
10960 CFileItem item(tvshow.m_strPath, true);
10961 if (!singleFile && CUtil::SupportsWriteFileOperations(tvshow.m_strPath))
10963 if (!item.Exists(false))
10965 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10966 tvshow.m_strPath);
10967 bSkip = true;
10969 else
10971 std::string nfoFile = URIUtils::AddFileToFolder(tvshow.m_strPath, "tvshow.nfo");
10973 if (overwrite || !CFile::Exists(nfoFile, false))
10975 if(!xmlDoc.SaveFile(nfoFile))
10977 CLog::Log(LOGERROR, "{}: TVShow nfo export failed! ('{}')", __FUNCTION__, nfoFile);
10978 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
10979 g_localizeStrings.Get(20302),
10980 CURL::GetRedacted(nfoFile));
10981 iFailCount++;
10986 if (!singleFile)
10988 xmlDoc.Clear();
10989 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10990 xmlDoc.InsertEndChild(decl);
10992 if (images && !bSkip)
10994 if (singleFile)
10995 item.SetPath(GetSafeFile(tvshowsDir, tvshow.m_strTitle));
10997 for (const auto &i : artwork)
10999 std::string savedThumb = ART::GetLocalArt(item, i.first, true);
11000 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
11003 if (actorThumbs)
11004 ExportActorThumbs(actorsDir, tvshow, !singleFile, overwrite);
11006 // export season thumbs
11007 for (const auto &i : seasonArt)
11009 std::string seasonThumb;
11010 if (i.first == -1)
11011 seasonThumb = "season-all";
11012 else if (i.first == 0)
11013 seasonThumb = "season-specials";
11014 else
11015 seasonThumb = StringUtils::Format("season{:02}", i.first);
11016 for (const auto &j : i.second)
11018 std::string savedThumb(ART::GetLocalArt(item, seasonThumb + "-" + j.first, true));
11019 if (!i.second.empty())
11020 CServiceBroker::GetTextureCache()->Export(j.second, savedThumb, overwrite);
11025 // now save the episodes from this show
11026 sql = PrepareSQL("select * from episode_view where idShow=%i order by strFileName, idEpisode",tvshow.m_iDbId);
11027 pDS->query(sql);
11028 std::string showDir(item.GetPath());
11030 while (!pDS->eof())
11032 CVideoInfoTag episode = GetDetailsForEpisode(pDS, VideoDbDetailsAll);
11033 std::map<std::string, std::string> artwork;
11034 if (GetArtForItem(episode.m_iDbId, MediaTypeEpisode, artwork) && singleFile)
11036 TiXmlElement additionalNode("art");
11037 for (const auto &i : artwork)
11038 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
11039 episode.Save(pMain->LastChild(), "episodedetails", true, &additionalNode);
11041 else if (singleFile)
11042 episode.Save(pMain->LastChild(), "episodedetails", singleFile);
11043 else
11044 episode.Save(pMain, "episodedetails", singleFile);
11045 pDS->next();
11046 // multi-episode files need dumping to the same XML
11047 while (!singleFile && !pDS->eof() &&
11048 episode.m_iFileId == pDS->fv("idFile").get_asInt())
11050 episode = GetDetailsForEpisode(pDS, VideoDbDetailsAll);
11051 episode.Save(pMain, "episodedetails", singleFile);
11052 pDS->next();
11055 // reset old skip state
11056 bool bSkip = false;
11058 CFileItem item(episode.m_strFileNameAndPath, false);
11059 if (!singleFile && CUtil::SupportsWriteFileOperations(episode.m_strFileNameAndPath))
11061 if (!item.Exists(false))
11063 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
11064 episode.m_strFileNameAndPath);
11065 bSkip = true;
11067 else
11069 std::string nfoFile(URIUtils::ReplaceExtension(ART::GetTBNFile(item), ".nfo"));
11071 if (overwrite || !CFile::Exists(nfoFile, false))
11073 if(!xmlDoc.SaveFile(nfoFile))
11075 CLog::Log(LOGERROR, "{}: Episode nfo export failed! ('{}')", __FUNCTION__, nfoFile);
11076 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
11077 g_localizeStrings.Get(20302),
11078 CURL::GetRedacted(nfoFile));
11079 iFailCount++;
11084 if (!singleFile)
11086 xmlDoc.Clear();
11087 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
11088 xmlDoc.InsertEndChild(decl);
11091 if (images && !bSkip)
11093 if (singleFile)
11095 std::string epName =
11096 StringUtils::Format("s{:02}e{:02}.avi", episode.m_iSeason, episode.m_iEpisode);
11097 item.SetPath(URIUtils::AddFileToFolder(showDir, epName));
11099 for (const auto &i : artwork)
11101 std::string savedThumb = ART::GetLocalArt(item, i.first, false);
11102 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
11104 if (actorThumbs)
11105 ExportActorThumbs(actorsDir, episode, !singleFile, overwrite);
11108 pDS->close();
11109 m_pDS->next();
11110 current++;
11112 m_pDS->close();
11114 if (!singleFile && progress)
11116 progress->SetPercentage(100);
11117 progress->Progress();
11120 if (singleFile)
11122 // now dump path info
11123 std::set<std::string> paths;
11124 GetPaths(paths);
11125 TiXmlElement xmlPathElement("paths");
11126 TiXmlNode *pPaths = pMain->InsertEndChild(xmlPathElement);
11127 for (const auto &i : paths)
11129 bool foundDirectly = false;
11130 SScanSettings settings;
11131 ScraperPtr info = GetScraperForPath(i, settings, foundDirectly);
11132 if (info && foundDirectly)
11134 TiXmlElement xmlPathElement2("path");
11135 TiXmlNode *pPath = pPaths->InsertEndChild(xmlPathElement2);
11136 XMLUtils::SetString(pPath,"url", i);
11137 XMLUtils::SetInt(pPath,"scanrecursive", settings.recurse);
11138 XMLUtils::SetBoolean(pPath,"usefoldernames", settings.parent_name);
11139 XMLUtils::SetString(pPath,"content", TranslateContent(info->Content()));
11140 XMLUtils::SetString(pPath,"scraperpath", info->ID());
11143 xmlDoc.SaveFile(xmlFile);
11145 CVariant data;
11146 if (singleFile)
11148 data["root"] = exportRoot;
11149 data["file"] = xmlFile;
11150 if (iFailCount > 0)
11151 data["failcount"] = iFailCount;
11153 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnExport",
11154 data);
11156 catch (...)
11158 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
11159 iFailCount++;
11162 if (progress)
11163 progress->Close();
11165 if (iFailCount > 0)
11166 HELPERS::ShowOKDialogText(
11167 CVariant{647}, CVariant{StringUtils::Format(g_localizeStrings.Get(15011), iFailCount)});
11170 void CVideoDatabase::ExportActorThumbs(const std::string &strDir, const CVideoInfoTag &tag, bool singleFiles, bool overwrite /*=false*/)
11172 std::string strPath(strDir);
11173 if (singleFiles)
11175 strPath = URIUtils::AddFileToFolder(tag.m_strPath, ".actors");
11176 if (!CDirectory::Exists(strPath))
11178 CDirectory::Create(strPath);
11179 CFile::SetHidden(strPath, true);
11183 for (const auto &i : tag.m_cast)
11185 CFileItem item;
11186 item.SetLabel(i.strName);
11187 if (!i.thumb.empty())
11189 std::string thumbFile(GetSafeFile(strPath, i.strName));
11190 CServiceBroker::GetTextureCache()->Export(i.thumb, thumbFile, overwrite);
11195 void CVideoDatabase::ImportFromXML(const std::string &path)
11197 CGUIDialogProgress *progress=NULL;
11200 if (nullptr == m_pDB)
11201 return;
11202 if (nullptr == m_pDS)
11203 return;
11205 CXBMCTinyXML xmlDoc;
11206 if (!xmlDoc.LoadFile(URIUtils::AddFileToFolder(path, "videodb.xml")))
11207 return;
11209 TiXmlElement *root = xmlDoc.RootElement();
11210 if (!root) return;
11212 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
11213 if (progress)
11215 progress->SetHeading(CVariant{648});
11216 progress->SetLine(0, CVariant{649});
11217 progress->SetLine(1, CVariant{330});
11218 progress->SetLine(2, CVariant{""});
11219 progress->SetPercentage(0);
11220 progress->Open();
11221 progress->ShowProgressBar(true);
11224 int iVersion = 0;
11225 XMLUtils::GetInt(root, "version", iVersion);
11227 CLog::Log(LOGINFO, "{}: Starting import (export version = {})", __FUNCTION__, iVersion);
11229 TiXmlElement *movie = root->FirstChildElement();
11230 int current = 0;
11231 int total = 0;
11232 // first count the number of items...
11233 while (movie)
11235 if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMovie, 5) == 0 ||
11236 StringUtils::CompareNoCase(movie->Value(), MediaTypeTvShow, 6) == 0 ||
11237 StringUtils::CompareNoCase(movie->Value(), MediaTypeMusicVideo, 10) == 0)
11238 total++;
11239 movie = movie->NextSiblingElement();
11242 std::string actorsDir(URIUtils::AddFileToFolder(path, "actors"));
11243 std::string moviesDir(URIUtils::AddFileToFolder(path, "movies"));
11244 std::string movieSetsDir(URIUtils::AddFileToFolder(path, "moviesets"));
11245 std::string musicvideosDir(URIUtils::AddFileToFolder(path, "musicvideos"));
11246 std::string tvshowsDir(URIUtils::AddFileToFolder(path, "tvshows"));
11247 CVideoInfoScanner scanner;
11248 // add paths first (so we have scraper settings available)
11249 TiXmlElement *path = root->FirstChildElement("paths");
11250 path = path->FirstChildElement();
11251 while (path)
11253 std::string strPath;
11254 if (XMLUtils::GetString(path,"url",strPath) && !strPath.empty())
11255 AddPath(strPath);
11257 std::string content;
11258 if (XMLUtils::GetString(path,"content", content) && !content.empty())
11259 { // check the scraper exists, if so store the path
11260 AddonPtr addon;
11261 std::string id;
11262 XMLUtils::GetString(path,"scraperpath",id);
11263 if (CServiceBroker::GetAddonMgr().GetAddon(id, addon, ADDON::OnlyEnabled::CHOICE_YES))
11265 SScanSettings settings;
11266 ScraperPtr scraper = std::dynamic_pointer_cast<CScraper>(addon);
11267 // FIXME: scraper settings are not exported?
11268 scraper->SetPathSettings(TranslateContent(content), "");
11269 XMLUtils::GetInt(path,"scanrecursive",settings.recurse);
11270 XMLUtils::GetBoolean(path,"usefoldernames",settings.parent_name);
11271 SetScraperForPath(strPath,scraper,settings);
11274 path = path->NextSiblingElement();
11276 movie = root->FirstChildElement();
11277 while (movie)
11279 CVideoInfoTag info;
11280 if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMovie, 5) == 0)
11282 info.Load(movie);
11283 CFileItem item(info);
11284 bool useFolders = info.m_basePath.empty() ? LookupByFolders(item.GetPath()) : false;
11285 std::string filename = info.m_strTitle;
11286 if (info.HasYear())
11287 filename += StringUtils::Format("_{}", info.GetYear());
11288 CFileItem artItem(item);
11289 artItem.SetPath(GetSafeFile(moviesDir, filename) + ".avi");
11290 scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
11291 item.SetArt(artItem.GetArt());
11292 if (!item.GetVideoInfoTag()->m_set.title.empty())
11294 std::string setPath = URIUtils::AddFileToFolder(movieSetsDir,
11295 CUtil::MakeLegalFileName(item.GetVideoInfoTag()->m_set.title, LEGAL_WIN32_COMPAT));
11296 if (CDirectory::Exists(setPath))
11298 CGUIListItem::ArtMap setArt;
11299 CFileItem artItem(setPath, true);
11300 for (const auto& artType : CVideoThumbLoader::GetArtTypes(MediaTypeVideoCollection))
11302 std::string artPath = CVideoThumbLoader::GetLocalArt(artItem, artType, true);
11303 if (!artPath.empty())
11305 setArt[artType] = artPath;
11308 item.AppendArt(setArt, "set");
11311 scanner.AddVideo(&item, CONTENT_MOVIES, useFolders, true, NULL, true);
11312 current++;
11314 else if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMusicVideo, 10) == 0)
11316 info.Load(movie);
11317 CFileItem item(info);
11318 bool useFolders = info.m_basePath.empty() ? LookupByFolders(item.GetPath()) : false;
11319 std::string filename = StringUtils::Join(info.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + "." + info.m_strTitle;
11320 if (info.HasYear())
11321 filename += StringUtils::Format("_{}", info.GetYear());
11322 CFileItem artItem(item);
11323 artItem.SetPath(GetSafeFile(musicvideosDir, filename) + ".avi");
11324 scanner.GetArtwork(&artItem, CONTENT_MUSICVIDEOS, useFolders, true, actorsDir);
11325 item.SetArt(artItem.GetArt());
11326 scanner.AddVideo(&item, CONTENT_MUSICVIDEOS, useFolders, true, NULL, true);
11327 current++;
11329 else if (StringUtils::CompareNoCase(movie->Value(), MediaTypeTvShow, 6) == 0)
11331 // load the TV show in. NOTE: This deletes all episodes under the TV Show, which may not be
11332 // what we desire. It may make better sense to only delete (or even better, update) the show information
11333 info.Load(movie);
11334 URIUtils::AddSlashAtEnd(info.m_strPath);
11335 DeleteTvShow(info.m_strPath);
11336 CFileItem showItem(info);
11337 bool useFolders = info.m_basePath.empty() ? LookupByFolders(showItem.GetPath(), true) : false;
11338 CFileItem artItem(showItem);
11339 std::string artPath(GetSafeFile(tvshowsDir, info.m_strTitle));
11340 artItem.SetPath(artPath);
11341 scanner.GetArtwork(&artItem, CONTENT_TVSHOWS, useFolders, true, actorsDir);
11342 showItem.SetArt(artItem.GetArt());
11343 int showID = scanner.AddVideo(&showItem, CONTENT_TVSHOWS, useFolders, true, NULL, true);
11344 // season artwork
11345 std::map<int, std::map<std::string, std::string> > seasonArt;
11346 artItem.GetVideoInfoTag()->m_strPath = artPath;
11347 scanner.GetSeasonThumbs(*artItem.GetVideoInfoTag(), seasonArt, CVideoThumbLoader::GetArtTypes(MediaTypeSeason), true);
11348 for (const auto &i : seasonArt)
11350 int seasonID = AddSeason(showID, i.first);
11351 SetArtForItem(seasonID, MediaTypeSeason, i.second);
11353 current++;
11354 // now load the episodes
11355 TiXmlElement *episode = movie->FirstChildElement("episodedetails");
11356 while (episode)
11358 // no need to delete the episode info, due to the above deletion
11359 CVideoInfoTag info;
11360 info.Load(episode);
11361 CFileItem item(info);
11362 std::string filename =
11363 StringUtils::Format("s{:02}e{:02}.avi", info.m_iSeason, info.m_iEpisode);
11364 CFileItem artItem(item);
11365 artItem.SetPath(GetSafeFile(artPath, filename));
11366 scanner.GetArtwork(&artItem, CONTENT_TVSHOWS, useFolders, true, actorsDir);
11367 item.SetArt(artItem.GetArt());
11368 scanner.AddVideo(&item,CONTENT_TVSHOWS, false, false, showItem.GetVideoInfoTag(), true);
11369 episode = episode->NextSiblingElement("episodedetails");
11372 movie = movie->NextSiblingElement();
11373 if (progress && total)
11375 progress->SetPercentage(current * 100 / total);
11376 progress->SetLine(2, CVariant{info.m_strTitle});
11377 progress->Progress();
11378 if (progress->IsCanceled())
11380 progress->Close();
11381 return;
11386 catch (...)
11388 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
11390 if (progress)
11391 progress->Close();
11394 bool CVideoDatabase::ImportArtFromXML(const TiXmlNode *node, std::map<std::string, std::string> &artwork)
11396 if (!node) return false;
11397 const TiXmlNode *art = node->FirstChild();
11398 while (art && art->FirstChild())
11400 artwork.insert(make_pair(art->ValueStr(), art->FirstChild()->ValueStr()));
11401 art = art->NextSibling();
11403 return !artwork.empty();
11406 void CVideoDatabase::ConstructPath(std::string& strDest, const std::string& strPath, const std::string& strFileName)
11408 if (URIUtils::IsStack(strFileName) ||
11409 URIUtils::IsInArchive(strFileName) || URIUtils::IsPlugin(strPath))
11410 strDest = strFileName;
11411 else
11412 strDest = URIUtils::AddFileToFolder(strPath, strFileName);
11415 void CVideoDatabase::SplitPath(const std::string& strFileNameAndPath, std::string& strPath, std::string& strFileName)
11417 if (URIUtils::IsStack(strFileNameAndPath) || StringUtils::StartsWithNoCase(strFileNameAndPath, "rar://") || StringUtils::StartsWithNoCase(strFileNameAndPath, "zip://"))
11419 URIUtils::GetParentPath(strFileNameAndPath,strPath);
11420 strFileName = strFileNameAndPath;
11422 else if (URIUtils::IsPlugin(strFileNameAndPath))
11424 CURL url(strFileNameAndPath);
11425 strPath = url.GetOptions().empty() ? url.GetWithoutFilename() : url.GetWithoutOptions();
11426 strFileName = strFileNameAndPath;
11428 else
11430 URIUtils::Split(strFileNameAndPath, strPath, strFileName);
11431 // Keep protocol options as part of the path
11432 if (URIUtils::IsURL(strFileNameAndPath))
11434 CURL url(strFileNameAndPath);
11435 if (!url.GetProtocolOptions().empty())
11436 strPath += "|" + url.GetProtocolOptions();
11441 void CVideoDatabase::InvalidatePathHash(const std::string& strPath)
11443 SScanSettings settings;
11444 bool foundDirectly;
11445 ScraperPtr info = GetScraperForPath(strPath,settings,foundDirectly);
11446 SetPathHash(strPath,"");
11447 if (!info)
11448 return;
11449 if (info->Content() == CONTENT_TVSHOWS || (info->Content() == CONTENT_MOVIES && !foundDirectly)) // if we scan by folder name we need to invalidate parent as well
11451 if (info->Content() == CONTENT_TVSHOWS || settings.parent_name_root)
11453 std::string strParent;
11454 if (URIUtils::GetParentPath(strPath, strParent) && (!URIUtils::IsPlugin(strPath) || !CURL(strParent).GetHostName().empty()))
11455 SetPathHash(strParent, "");
11460 bool CVideoDatabase::CommitTransaction()
11462 if (CDatabase::CommitTransaction())
11463 { // number of items in the db has likely changed, so recalculate
11464 GUIINFO::CLibraryGUIInfo& guiInfo = CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider();
11465 guiInfo.SetLibraryBool(LIBRARY_HAS_MOVIES, HasContent(VideoDbContentType::MOVIES));
11466 guiInfo.SetLibraryBool(LIBRARY_HAS_TVSHOWS, HasContent(VideoDbContentType::TVSHOWS));
11467 guiInfo.SetLibraryBool(LIBRARY_HAS_MUSICVIDEOS, HasContent(VideoDbContentType::MUSICVIDEOS));
11468 return true;
11470 return false;
11473 bool CVideoDatabase::SetSingleValue(VideoDbContentType type,
11474 int dbId,
11475 int dbField,
11476 const std::string& strValue)
11478 std::string strSQL;
11481 if (nullptr == m_pDB || nullptr == m_pDS)
11482 return false;
11484 std::string strTable, strField;
11485 if (type == VideoDbContentType::MOVIES)
11487 strTable = "movie";
11488 strField = "idMovie";
11490 else if (type == VideoDbContentType::TVSHOWS)
11492 strTable = "tvshow";
11493 strField = "idShow";
11495 else if (type == VideoDbContentType::EPISODES)
11497 strTable = "episode";
11498 strField = "idEpisode";
11500 else if (type == VideoDbContentType::MUSICVIDEOS)
11502 strTable = "musicvideo";
11503 strField = "idMVideo";
11506 if (strTable.empty())
11507 return false;
11509 return SetSingleValue(strTable, StringUtils::Format("c{:02}", dbField), strValue, strField,
11510 dbId);
11512 catch (...)
11514 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
11516 return false;
11519 bool CVideoDatabase::SetSingleValue(VideoDbContentType type,
11520 int dbId,
11521 Field dbField,
11522 const std::string& strValue)
11524 MediaType mediaType = DatabaseUtils::MediaTypeFromVideoContentType(type);
11525 if (mediaType == MediaTypeNone)
11526 return false;
11528 int dbFieldIndex = DatabaseUtils::GetField(dbField, mediaType);
11529 if (dbFieldIndex < 0)
11530 return false;
11532 return SetSingleValue(type, dbId, dbFieldIndex, strValue);
11535 bool CVideoDatabase::SetSingleValue(const std::string &table, const std::string &fieldName, const std::string &strValue,
11536 const std::string &conditionName /* = "" */, int conditionValue /* = -1 */)
11538 if (table.empty() || fieldName.empty())
11539 return false;
11541 std::string sql;
11544 if (nullptr == m_pDB || nullptr == m_pDS)
11545 return false;
11547 sql = PrepareSQL("UPDATE %s SET %s='%s'", table.c_str(), fieldName.c_str(), strValue.c_str());
11548 if (!conditionName.empty())
11549 sql += PrepareSQL(" WHERE %s=%u", conditionName.c_str(), conditionValue);
11550 if (m_pDS->exec(sql) == 0)
11551 return true;
11553 catch (...)
11555 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, sql);
11557 return false;
11560 std::string CVideoDatabase::GetSafeFile(const std::string &dir, const std::string &name) const
11562 std::string safeThumb(name);
11563 StringUtils::Replace(safeThumb, ' ', '_');
11564 return URIUtils::AddFileToFolder(dir, CUtil::MakeLegalFileName(std::move(safeThumb)));
11567 void CVideoDatabase::AnnounceRemove(const std::string& content, int id, bool scanning /* = false */)
11569 CVariant data;
11570 data["type"] = content;
11571 data["id"] = id;
11572 if (scanning)
11573 data["transaction"] = true;
11574 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnRemove", data);
11577 void CVideoDatabase::AnnounceUpdate(const std::string& content, int id)
11579 CVariant data;
11580 data["type"] = content;
11581 data["id"] = id;
11582 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnUpdate", data);
11585 bool CVideoDatabase::GetItemsForPath(const std::string &content, const std::string &strPath, CFileItemList &items)
11587 const std::string& path(strPath);
11589 if(URIUtils::IsMultiPath(path))
11591 std::vector<std::string> paths;
11592 CMultiPathDirectory::GetPaths(path, paths);
11594 for(unsigned i=0;i<paths.size();i++)
11595 GetItemsForPath(content, paths[i], items);
11597 return items.Size() > 0;
11600 int pathID = GetPathId(path);
11601 if (pathID < 0)
11602 return false;
11604 if (content == "movies")
11606 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_PARENTPATHID, pathID));
11607 GetMoviesByWhere("videodb://movies/titles/", filter, items);
11609 else if (content == "episodes")
11611 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_EPISODE_PARENTPATHID, pathID));
11612 GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
11614 else if (content == "tvshows")
11616 Filter filter(PrepareSQL("idParentPath=%d", pathID));
11617 GetTvShowsByWhere("videodb://tvshows/titles/", filter, items);
11619 else if (content == "musicvideos")
11621 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_MUSICVIDEO_PARENTPATHID, pathID));
11622 GetMusicVideosByWhere("videodb://musicvideos/titles/", filter, items);
11624 for (int i = 0; i < items.Size(); i++)
11625 items[i]->SetPath(items[i]->GetVideoInfoTag()->m_basePath);
11626 return items.Size() > 0;
11629 void CVideoDatabase::AppendIdLinkFilter(const char* field, const char *table, const MediaType& mediaType, const char *view, const char *viewKey, const CUrlOptions::UrlOptions& options, Filter &filter)
11631 auto option = options.find((std::string)field + "id");
11632 if (option == options.end())
11633 return;
11635 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()));
11636 filter.AppendWhere(PrepareSQL("%s_link.%s_id = %i", field, table, (int)option->second.asInteger()));
11639 void CVideoDatabase::AppendLinkFilter(const char* field, const char *table, const MediaType& mediaType, const char *view, const char *viewKey, const CUrlOptions::UrlOptions& options, Filter &filter)
11641 auto option = options.find(field);
11642 if (option == options.end())
11643 return;
11645 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()));
11646 filter.AppendJoin(PrepareSQL("JOIN %s ON %s.%s_id=%s_link.%s_id", table, table, field, table, field));
11647 filter.AppendWhere(PrepareSQL("%s.name like '%s'", table, option->second.asString().c_str()));
11650 bool CVideoDatabase::GetFilter(CDbUrl &videoUrl, Filter &filter, SortDescription &sorting)
11652 if (!videoUrl.IsValid())
11653 return false;
11655 std::string type = videoUrl.GetType();
11656 std::string itemType = ((const CVideoDbUrl &)videoUrl).GetItemType();
11657 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
11659 if (type == "movies")
11661 AppendIdLinkFilter("genre", "genre", "movie", "movie", "idMovie", options, filter);
11662 AppendLinkFilter("genre", "genre", "movie", "movie", "idMovie", options, filter);
11664 AppendIdLinkFilter("country", "country", "movie", "movie", "idMovie", options, filter);
11665 AppendLinkFilter("country", "country", "movie", "movie", "idMovie", options, filter);
11667 AppendIdLinkFilter("studio", "studio", "movie", "movie", "idMovie", options, filter);
11668 AppendLinkFilter("studio", "studio", "movie", "movie", "idMovie", options, filter);
11670 AppendIdLinkFilter("director", "actor", "movie", "movie", "idMovie", options, filter);
11671 AppendLinkFilter("director", "actor", "movie", "movie", "idMovie", options, filter);
11673 auto option = options.find("year");
11674 if (option != options.end())
11675 filter.AppendWhere(PrepareSQL("movie_view.premiered like '%i%%'", (int)option->second.asInteger()));
11677 AppendIdLinkFilter("actor", "actor", "movie", "movie", "idMovie", options, filter);
11678 AppendLinkFilter("actor", "actor", "movie", "movie", "idMovie", options, filter);
11680 option = options.find("setid");
11681 if (option != options.end())
11682 filter.AppendWhere(PrepareSQL("movie_view.idSet = %i", (int)option->second.asInteger()));
11684 option = options.find("set");
11685 if (option != options.end())
11686 filter.AppendWhere(PrepareSQL("movie_view.strSet LIKE '%s'", option->second.asString().c_str()));
11688 option = options.find("videoversionid");
11689 if (option != options.end())
11691 const int idVideoVersion{static_cast<int>(option->second.asInteger())};
11692 if (idVideoVersion > 0)
11693 filter.AppendWhere(PrepareSQL("videoVersionTypeId = %i", idVideoVersion));
11694 else
11696 option = options.find("mediaid");
11697 if (option != options.end())
11699 const int mediaId{static_cast<int>(option->second.asInteger())};
11700 if (mediaId > 0)
11701 filter.AppendWhere(PrepareSQL("idMovie = %i", mediaId));
11705 else
11707 filter.AppendWhere("isDefaultVersion = 1");
11710 AppendIdLinkFilter("tag", "tag", "movie", "movie", "idMovie", options, filter);
11711 AppendLinkFilter("tag", "tag", "movie", "movie", "idMovie", options, filter);
11713 else if (type == "tvshows")
11715 if (itemType == "tvshows")
11717 AppendIdLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options, filter);
11718 AppendLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options, filter);
11720 AppendIdLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options, filter);
11721 AppendLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options, filter);
11723 AppendIdLinkFilter("director", "actor", "tvshow", "tvshow", "idShow", options, filter);
11725 auto option = options.find("year");
11726 if (option != options.end())
11727 filter.AppendWhere(PrepareSQL("tvshow_view.c%02d like '%%%i%%'", VIDEODB_ID_TV_PREMIERED, (int)option->second.asInteger()));
11729 AppendIdLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options, filter);
11730 AppendLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options, filter);
11732 AppendIdLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options, filter);
11733 AppendLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options, filter);
11735 else if (itemType == "seasons")
11737 auto option = options.find("tvshowid");
11738 if (option != options.end())
11739 filter.AppendWhere(PrepareSQL("season_view.idShow = %i", (int)option->second.asInteger()));
11741 AppendIdLinkFilter("genre", "genre", "tvshow", "season", "idShow", options, filter);
11743 AppendIdLinkFilter("director", "actor", "tvshow", "season", "idShow", options, filter);
11745 option = options.find("year");
11746 if (option != options.end())
11747 filter.AppendWhere(PrepareSQL("season_view.premiered like '%%%i%%'", (int)option->second.asInteger()));
11749 AppendIdLinkFilter("actor", "actor", "tvshow", "season", "idShow", options, filter);
11751 else if (itemType == "episodes")
11753 int idShow = -1;
11754 auto option = options.find("tvshowid");
11755 if (option != options.end())
11756 idShow = (int)option->second.asInteger();
11758 int season = -1;
11759 option = options.find("season");
11760 if (option != options.end())
11761 season = (int)option->second.asInteger();
11763 if (idShow > -1)
11765 bool condition = false;
11767 AppendIdLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options, filter);
11768 AppendLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options, filter);
11770 AppendIdLinkFilter("director", "actor", "tvshow", "episode", "idShow", options, filter);
11771 AppendLinkFilter("director", "actor", "tvshow", "episode", "idShow", options, filter);
11773 option = options.find("year");
11774 if (option != options.end())
11776 condition = true;
11777 filter.AppendWhere(PrepareSQL("episode_view.idShow = %i and episode_view.premiered like '%%%i%%'", idShow, (int)option->second.asInteger()));
11780 AppendIdLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options, filter);
11781 AppendLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options, filter);
11783 if (!condition)
11784 filter.AppendWhere(PrepareSQL("episode_view.idShow = %i", idShow));
11786 if (season > -1)
11788 if (season == 0) // season = 0 indicates a special - we grab all specials here (see below)
11789 filter.AppendWhere(PrepareSQL("episode_view.c%02d = %i", VIDEODB_ID_EPISODE_SEASON, season));
11790 else
11791 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)))",
11792 VIDEODB_ID_EPISODE_SEASON, season, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTSEASON, season));
11795 else
11797 option = options.find("year");
11798 if (option != options.end())
11799 filter.AppendWhere(PrepareSQL("episode_view.premiered like '%%%i%%'", (int)option->second.asInteger()));
11801 AppendIdLinkFilter("director", "actor", "episode", "episode", "idEpisode", options, filter);
11802 AppendLinkFilter("director", "actor", "episode", "episode", "idEpisode", options, filter);
11806 else if (type == "musicvideos")
11808 AppendIdLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options, filter);
11809 AppendLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options, filter);
11811 AppendIdLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options, filter);
11812 AppendLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options, filter);
11814 AppendIdLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options, filter);
11815 AppendLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options, filter);
11817 auto option = options.find("year");
11818 if (option != options.end())
11819 filter.AppendWhere(PrepareSQL("musicvideo_view.premiered like '%i%%'", (int)option->second.asInteger()));
11821 option = options.find("artistid");
11822 if (option != options.end())
11824 if (itemType != "albums")
11825 filter.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
11826 filter.AppendWhere(PrepareSQL("actor_link.actor_id = %i", (int)option->second.asInteger()));
11829 option = options.find("artist");
11830 if (option != options.end())
11832 if (itemType != "albums")
11834 filter.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
11835 filter.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id=actor_link.actor_id"));
11837 filter.AppendWhere(PrepareSQL("actor.name LIKE '%s'", option->second.asString().c_str()));
11840 option = options.find("albumid");
11841 if (option != options.end())
11842 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()));
11844 AppendIdLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options, filter);
11845 AppendLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options, filter);
11847 else
11848 return false;
11850 auto option = options.find("xsp");
11851 if (option != options.end())
11853 PLAYLIST::CSmartPlaylist xsp;
11854 if (!xsp.LoadFromJson(option->second.asString()))
11855 return false;
11857 // check if the filter playlist matches the item type
11858 if (xsp.GetType() == itemType ||
11859 (xsp.GetGroup() == itemType && !xsp.IsGroupMixed()) ||
11860 // handle episode listings with videodb://tvshows/titles/ which get the rest
11861 // of the path (season and episodeid) appended later
11862 (xsp.GetType() == "episodes" && itemType == "tvshows"))
11864 std::set<std::string> playlists;
11865 filter.AppendWhere(xsp.GetWhereClause(*this, playlists));
11867 if (xsp.GetLimit() > 0)
11868 sorting.limitEnd = xsp.GetLimit();
11869 if (xsp.GetOrder() != SortByNone)
11870 sorting.sortBy = xsp.GetOrder();
11871 if (xsp.GetOrderDirection() != SortOrderNone)
11872 sorting.sortOrder = xsp.GetOrderDirection();
11873 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING))
11874 sorting.sortAttributes = SortAttributeIgnoreArticle;
11878 option = options.find("filter");
11879 if (option != options.end())
11881 PLAYLIST::CSmartPlaylist xspFilter;
11882 if (!xspFilter.LoadFromJson(option->second.asString()))
11883 return false;
11885 // check if the filter playlist matches the item type
11886 if (xspFilter.GetType() == itemType)
11888 std::set<std::string> playlists;
11889 filter.AppendWhere(xspFilter.GetWhereClause(*this, playlists));
11891 // remove the filter if it doesn't match the item type
11892 else
11893 videoUrl.RemoveOption("filter");
11896 return true;
11899 bool CVideoDatabase::SetVideoUserRating(int dbId, int rating, const MediaType& mediaType)
11903 if (nullptr == m_pDB)
11904 return false;
11905 if (nullptr == m_pDS)
11906 return false;
11908 if (mediaType == MediaTypeNone)
11909 return false;
11911 std::string sql;
11912 if (mediaType == MediaTypeMovie)
11913 sql = PrepareSQL("UPDATE movie SET userrating=%i WHERE idMovie = %i", rating, dbId);
11914 else if (mediaType == MediaTypeEpisode)
11915 sql = PrepareSQL("UPDATE episode SET userrating=%i WHERE idEpisode = %i", rating, dbId);
11916 else if (mediaType == MediaTypeMusicVideo)
11917 sql = PrepareSQL("UPDATE musicvideo SET userrating=%i WHERE idMVideo = %i", rating, dbId);
11918 else if (mediaType == MediaTypeTvShow)
11919 sql = PrepareSQL("UPDATE tvshow SET userrating=%i WHERE idShow = %i", rating, dbId);
11920 else if (mediaType == MediaTypeSeason)
11921 sql = PrepareSQL("UPDATE seasons SET userrating=%i WHERE idSeason = %i", rating, dbId);
11923 m_pDS->exec(sql);
11924 return true;
11926 catch (...)
11928 CLog::Log(LOGERROR, "{} ({}, {}, {}) failed", __FUNCTION__, dbId, mediaType, rating);
11930 return false;
11933 CDateTime CVideoDatabase::GetDateAdded(const std::string& filename,
11934 CDateTime dateAdded /* = CDateTime() */)
11936 if (!dateAdded.IsValid())
11938 // suppress warnings if we have plugin source
11939 if (!URIUtils::IsPlugin(filename))
11941 const auto dateAddedSetting =
11942 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryDateAdded;
11944 // 1 prefer using the files mtime (if it's valid) and
11945 // only use the file's ctime if mtime isn't valid
11946 if (dateAddedSetting == 1)
11947 dateAdded = CFileUtils::GetModificationDate(filename, false);
11948 // 2 use the newer datetime of the file's mtime and ctime
11949 else if (dateAddedSetting == 2)
11950 dateAdded = CFileUtils::GetModificationDate(filename, true);
11953 // 0 use the current datetime if non of the above match or one returns an invalid datetime
11954 if (!dateAdded.IsValid())
11955 dateAdded = CDateTime::GetCurrentDateTime();
11958 return dateAdded;
11961 void CVideoDatabase::EraseAllForPath(const std::string& path)
11965 std::string itemsToDelete;
11966 std::string sql =
11967 PrepareSQL("SELECT files.idFile FROM files WHERE idFile IN (SELECT idFile FROM files INNER "
11968 "JOIN path ON path.idPath = files.idPath AND path.strPath LIKE \"%s%%\")",
11969 path.c_str());
11971 m_pDS->query(sql);
11972 while (!m_pDS->eof())
11974 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
11975 itemsToDelete += file;
11976 m_pDS->next();
11978 m_pDS->close();
11980 sql = PrepareSQL("DELETE FROM path WHERE strPath LIKE \"%s%%\"", path.c_str());
11981 m_pDS->exec(sql);
11983 if (!itemsToDelete.empty())
11985 itemsToDelete = "(" + StringUtils::TrimRight(itemsToDelete, ",") + ")";
11987 sql = "DELETE FROM files WHERE idFile IN " + itemsToDelete;
11988 m_pDS->exec(sql);
11990 sql = "DELETE FROM settings WHERE idFile IN " + itemsToDelete;
11991 m_pDS->exec(sql);
11993 sql = "DELETE FROM bookmark WHERE idFile IN " + itemsToDelete;
11994 m_pDS->exec(sql);
11996 sql = "DELETE FROM streamdetails WHERE idFile IN " + itemsToDelete;
11997 m_pDS->exec(sql);
12000 catch (...)
12002 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12006 std::string CVideoDatabase::GetVideoItemTitle(VideoDbContentType itemType, int dbId)
12008 switch (itemType)
12010 case VideoDbContentType::MOVIES:
12011 return GetMovieTitle(dbId);
12012 default:
12013 return "";
12017 void CVideoDatabase::InitializeVideoVersionTypeTable(int schemaVersion)
12019 assert(m_pDB->in_transaction());
12023 for (int id = VIDEO_VERSION_ID_BEGIN; id <= VIDEO_VERSION_ID_END; ++id)
12025 // Exclude removed pre-populated "quality" values
12026 if (id == 40405 || (id >= 40418 && id <= 40430))
12027 continue;
12029 const std::string& type{g_localizeStrings.Get(id)};
12030 if (schemaVersion < 127)
12032 m_pDS->exec(
12033 PrepareSQL("INSERT INTO videoversiontype (id, name, owner) VALUES(%i, '%s', %i)", id,
12034 type.c_str(), VideoAssetTypeOwner::SYSTEM));
12036 else
12038 m_pDS->exec(PrepareSQL(
12039 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(%i, '%s', %i, %i)", id,
12040 type.c_str(), VideoAssetTypeOwner::SYSTEM, VideoAssetType::VERSION));
12044 catch (...)
12046 CLog::LogF(LOGERROR, "failed");
12047 throw;
12051 void CVideoDatabase::UpdateVideoVersionTypeTable()
12055 BeginTransaction();
12057 for (int id = VIDEO_VERSION_ID_BEGIN; id <= VIDEO_VERSION_ID_END; ++id)
12059 const std::string& type = g_localizeStrings.Get(id);
12060 m_pDS->exec(PrepareSQL("UPDATE videoversiontype SET name = '%s', owner = %i WHERE id = '%i'",
12061 type.c_str(), VideoAssetTypeOwner::SYSTEM, id));
12064 CommitTransaction();
12066 catch (...)
12068 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12069 RollbackTransaction();
12073 int CVideoDatabase::AddVideoVersionType(const std::string& typeVideoVersion,
12074 VideoAssetTypeOwner owner,
12075 VideoAssetType assetType)
12077 if (typeVideoVersion.empty())
12078 return -1;
12080 int id = -1;
12084 if (!m_pDB || !m_pDS)
12085 return -1;
12087 m_pDS->query(PrepareSQL(
12088 "SELECT id, owner, itemType FROM videoversiontype WHERE name = '%s' AND itemtype = %i",
12089 typeVideoVersion.c_str(), assetType));
12090 if (m_pDS->num_rows() == 0)
12092 m_pDS->exec(PrepareSQL("INSERT INTO videoversiontype (id, name, owner, itemType) "
12093 "VALUES(NULL, '%s', %i, %i)",
12094 typeVideoVersion.c_str(), owner, assetType));
12095 id = static_cast<int>(m_pDS->lastinsertid());
12097 else
12099 id = m_pDS->fv("id").get_asInt();
12101 // if user is adding an existing version type, overwrite the existing non-system one
12102 VideoAssetTypeOwner oldOwner =
12103 static_cast<VideoAssetTypeOwner>(m_pDS->fv("owner").get_asInt());
12104 if (oldOwner != VideoAssetTypeOwner::SYSTEM && owner == VideoAssetTypeOwner::USER)
12106 m_pDS->exec(PrepareSQL("UPDATE videoversiontype SET owner = %i WHERE id = %i", owner, id));
12110 catch (...)
12112 CLog::Log(LOGERROR, "{} failed to add video version {}", __FUNCTION__, typeVideoVersion);
12115 return id;
12118 void CVideoDatabase::GetVideoVersions(VideoDbContentType itemType,
12119 int dbId,
12120 CFileItemList& items,
12121 VideoAssetType videoAssetType)
12123 if (!m_pDB || !m_pDS2)
12124 return;
12126 MediaType mediaType;
12128 if (itemType == VideoDbContentType::MOVIES)
12129 mediaType = MediaTypeMovie;
12130 else
12131 return;
12135 m_pDS2->query(PrepareSQL("SELECT videoversiontype.name AS name,"
12136 " videoversiontype.id AS id,"
12137 " videoversion.idFile AS idFile "
12138 "FROM videoversiontype"
12139 " JOIN videoversion ON"
12140 " videoversion.idType = videoversiontype.id "
12141 "WHERE videoversion.idMedia = %i AND videoversion.media_type = '%s' "
12142 "AND videoversion.itemType = %i",
12143 dbId, mediaType.c_str(), videoAssetType));
12145 std::vector<std::tuple<std::string, int, int>> versions;
12147 while (!m_pDS2->eof())
12149 versions.emplace_back(m_pDS2->fv("name").get_asString(), m_pDS2->fv("id").get_asInt(),
12150 m_pDS2->fv("idFile").get_asInt());
12151 m_pDS2->next();
12153 m_pDS2->close();
12155 CFileItem videoItem;
12156 GetDetailsByTypeAndId(videoItem, itemType, dbId);
12158 for (auto& version : versions)
12160 std::string name = std::get<0>(version);
12161 int id = std::get<1>(version);
12162 int idFile = std::get<2>(version);
12164 CVideoInfoTag infoTag;
12165 if (GetFileInfo("", infoTag, idFile))
12167 infoTag.m_type = MediaTypeVideoVersion;
12168 infoTag.m_iDbId = idFile;
12169 infoTag.GetAssetInfo().SetId(id);
12170 infoTag.GetAssetInfo().SetTitle(name);
12171 infoTag.GetAssetInfo().SetType(videoAssetType);
12172 infoTag.m_strTitle = name;
12174 infoTag.m_strPictureURL = videoItem.GetVideoInfoTag()->m_strPictureURL;
12175 infoTag.m_fanart = videoItem.GetVideoInfoTag()->m_fanart;
12177 auto item(std::make_shared<CFileItem>(infoTag));
12178 item->m_strTitle = name;
12179 item->SetLabel(name);
12181 CVideoDbUrl itemUrl;
12182 if (itemUrl.FromString(StringUtils::Format("videodb://{}/videoversions/{}",
12183 CMediaTypes::ToPlural(mediaType), id)))
12185 itemUrl.AddOption("mediaid", dbId);
12186 item->SetPath(itemUrl.ToString());
12189 item->SetDynPath(infoTag.m_strFileNameAndPath);
12191 item->SetOverlayImage(GetPlayCount(idFile) > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
12192 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
12194 items.Add(item);
12198 catch (...)
12200 CLog::Log(LOGERROR, "{} failed for {} {}", __FUNCTION__, mediaType, dbId);
12204 void CVideoDatabase::GetDefaultVideoVersion(VideoDbContentType itemType, int dbId, CFileItem& item)
12206 if (!m_pDB || !m_pDS)
12207 return;
12209 MediaType mediaType;
12210 std::string strSQL;
12212 if (itemType == VideoDbContentType::MOVIES)
12214 mediaType = MediaTypeMovie;
12215 strSQL = PrepareSQL("SELECT videoversiontype.name AS name,"
12216 " videoversiontype.id AS id,"
12217 " videoversion.idFile AS idFile,"
12218 " videoversion.itemType AS itemType "
12219 "FROM videoversiontype"
12220 " JOIN videoversion ON"
12221 " videoversion.idType = videoversiontype.id"
12222 " JOIN movie ON"
12223 " movie.idFile = videoversion.idFile "
12224 "WHERE movie.idMovie = %i",
12225 dbId);
12227 else
12228 return;
12232 m_pDS->query(strSQL);
12234 if (!m_pDS->eof())
12236 std::string name = m_pDS->fv("name").get_asString();
12237 int id = m_pDS->fv("id").get_asInt();
12238 int idFile = m_pDS->fv("idFile").get_asInt();
12239 const auto videoAssetType{static_cast<VideoAssetType>(m_pDS->fv("itemType").get_asInt())};
12240 CVideoInfoTag infoTag;
12241 if (GetFileInfo("", infoTag, idFile))
12243 infoTag.m_type = MediaTypeVideoVersion;
12244 infoTag.m_iDbId = idFile;
12245 infoTag.GetAssetInfo().SetId(id);
12246 infoTag.GetAssetInfo().SetTitle(name);
12247 infoTag.GetAssetInfo().SetType(videoAssetType);
12248 infoTag.m_strTitle = name;
12250 item.SetFromVideoInfoTag(infoTag);
12251 item.m_strTitle = name;
12252 item.SetLabel(name);
12255 m_pDS->close();
12257 catch (...)
12259 CLog::Log(LOGERROR, "{} failed for {} {}", __FUNCTION__, mediaType, dbId);
12263 bool CVideoDatabase::UpdateAssetsOwner(const std::string& mediaType, int dbIdSource, int dbIdTarget)
12265 if (dbIdSource != dbIdTarget)
12267 return ExecuteQuery(
12268 PrepareSQL("UPDATE videoversion SET idMedia = %i WHERE idMedia = %i AND media_type = '%s'",
12269 dbIdTarget, dbIdSource, mediaType.c_str()));
12271 return true;
12274 bool CVideoDatabase::FillMovieItem(std::unique_ptr<Dataset>& dataset, int movieId, CFileItem& item)
12276 CVideoInfoTag infoTag{GetDetailsForMovie(dataset)};
12277 if (infoTag.IsEmpty())
12279 CLog::LogF(LOGERROR, "Unable to fill movie item with id '{}'!", movieId);
12280 return false;
12283 item.SetFromVideoInfoTag(infoTag);
12285 CVideoDbUrl itemUrl;
12286 itemUrl.FromString(
12287 StringUtils::Format("videodb://movies/videoversions/{}", infoTag.GetAssetInfo().GetId()));
12288 itemUrl.AppendPath(std::to_string(movieId));
12289 itemUrl.AddOption("mediaid", movieId);
12290 item.SetPath(itemUrl.ToString());
12291 item.SetDynPath(infoTag.m_strFileNameAndPath);
12292 return true;
12295 bool CVideoDatabase::GetAssetsForVideo(VideoDbContentType itemType,
12296 int mediaId,
12297 VideoAssetType assetType,
12298 CFileItemList& items)
12300 if (assetType != VideoAssetType::VERSION)
12302 //! @todo add bool return type to GetVideoVersions
12303 GetVideoVersions(itemType, mediaId, items, assetType);
12304 return true;
12307 if (!m_pDB || !m_pDS)
12308 return false;
12310 MediaType mediaType;
12312 if (itemType == VideoDbContentType::MOVIES)
12313 mediaType = MediaTypeMovie;
12314 else
12316 CLog::LogF(LOGERROR, "Unsupported item type '{}'!", static_cast<int>(itemType));
12317 return false;
12322 m_pDS->query(
12323 PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND videoVersionTypeItemType = %i",
12324 mediaId, assetType));
12326 if (m_pDS->eof())
12328 CLog::LogF(LOGERROR, "Query returned no data!");
12329 return false;
12332 while (!m_pDS->eof())
12334 const auto item{std::make_shared<CFileItem>()};
12335 if (FillMovieItem(m_pDS, mediaId, *item))
12336 items.Add(item);
12338 m_pDS->next();
12340 m_pDS->close();
12342 catch (...)
12344 CLog::LogF(LOGERROR, "Execution failed for {} {}", mediaType, mediaId);
12345 return false;
12347 return true;
12350 bool CVideoDatabase::GetDefaultVersionForVideo(VideoDbContentType itemType,
12351 int mediaId,
12352 CFileItem& item)
12354 if (!m_pDB || !m_pDS)
12355 return false;
12357 MediaType mediaType;
12359 if (itemType == VideoDbContentType::MOVIES)
12360 mediaType = MediaTypeMovie;
12361 else
12363 CLog::LogF(LOGERROR, "Unsupported item type '{}'!", static_cast<int>(itemType));
12364 return false;
12369 m_pDS->query(PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND "
12370 "videoVersionTypeItemType = %i AND isDefaultVersion = 1",
12371 mediaId, VideoAssetType::VERSION));
12372 if (m_pDS->eof())
12374 CLog::LogF(LOGERROR, "Query returned no data!");
12375 return false;
12378 if (!FillMovieItem(m_pDS, mediaId, item))
12379 return false;
12381 catch (...)
12383 CLog::LogF(LOGERROR, "Execution failed for {} {}", mediaType, mediaId);
12384 return false;
12386 return true;
12389 bool CVideoDatabase::ConvertVideoToVersion(VideoDbContentType itemType,
12390 int dbIdSource,
12391 int dbIdTarget,
12392 int idVideoVersion,
12393 VideoAssetType assetType)
12395 int idFile = -1;
12396 MediaType mediaType;
12397 VideoContentTypeToString(itemType, mediaType);
12399 if (itemType == VideoDbContentType::MOVIES)
12401 idFile = GetFileIdByMovie(dbIdSource);
12403 else
12404 return false;
12406 if (idFile < 0)
12407 return false;
12409 BeginTransaction();
12411 if (dbIdSource != dbIdTarget)
12413 // Transfer all assets (versions, extras,...) to the new movie.
12414 UpdateAssetsOwner(mediaType, dbIdSource, dbIdTarget);
12416 // version-level art doesn't need any change.
12417 // 'movie' art is converted to 'videoversion' art.
12418 SetVideoVersionDefaultArt(idFile, dbIdSource, itemType);
12420 if (itemType == VideoDbContentType::MOVIES)
12421 DeleteMovie(dbIdSource, DeleteMovieCascadeAction::ALL_ASSETS,
12422 DeleteMovieHashAction::HASH_PRESERVE);
12425 // Rename the default version
12426 ExecuteQuery(PrepareSQL("UPDATE videoversion SET idType = %i, itemType = %i WHERE idFile = %i",
12427 idVideoVersion, assetType, idFile));
12429 CommitTransaction();
12431 return true;
12434 void CVideoDatabase::SetDefaultVideoVersion(VideoDbContentType itemType, int dbId, int idFile)
12436 if (!m_pDB || !m_pDS)
12437 return;
12439 std::string path = GetFileBasePathById(idFile);
12440 if (path.empty())
12441 return;
12445 if (itemType == VideoDbContentType::MOVIES)
12446 m_pDS->exec(PrepareSQL("UPDATE movie SET idFile = %i, c%02d = '%s' WHERE idMovie = %i",
12447 idFile, VIDEODB_ID_BASEPATH, path.c_str(), dbId));
12449 catch (...)
12451 CLog::Log(LOGERROR, "{} failed for video {}", __FUNCTION__, dbId);
12455 bool CVideoDatabase::IsDefaultVideoVersion(int idFile)
12457 if (!m_pDB || !m_pDS)
12458 return false;
12462 m_pDS->query(
12463 PrepareSQL("SELECT idMedia, media_type FROM videoversion WHERE idFile = %i", idFile));
12464 if (m_pDS->num_rows() > 0)
12466 int idMedia = m_pDS->fv("idMedia").get_asInt();
12467 std::string mediaType = m_pDS->fv("media_type").get_asString();
12469 if (mediaType == MediaTypeMovie)
12471 m_pDS->query(PrepareSQL("SELECT idFile FROM movie WHERE idMovie = %i", idMedia));
12472 if (m_pDS->num_rows() > 0)
12474 if (m_pDS->fv("idFile").get_asInt() == idFile)
12475 return true;
12480 catch (...)
12482 CLog::Log(LOGERROR, "{} failed for {}", __FUNCTION__, idFile);
12485 return false;
12488 bool CVideoDatabase::DeleteVideoAsset(int idFile)
12490 if (!m_pDB || !m_pDS)
12491 return false;
12493 if (IsDefaultVideoVersion(idFile))
12494 return false;
12496 const bool inTransaction{m_pDB->in_transaction()};
12500 if (!inTransaction)
12501 BeginTransaction();
12503 const std::string path = GetSingleValue(PrepareSQL(
12504 "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i",
12505 idFile));
12506 if (!path.empty())
12507 InvalidatePathHash(path);
12509 m_pDS->exec(PrepareSQL("DELETE FROM videoversion WHERE idFile=%i", idFile));
12511 if (!inTransaction)
12512 CommitTransaction();
12514 return true;
12516 catch (...)
12518 CLog::LogF(LOGERROR, "failed for {}", idFile);
12519 if (!inTransaction)
12520 RollbackTransaction();
12521 return false;
12525 void CVideoDatabase::SetVideoVersion(int idFile, int idVideoVersion)
12527 if (!m_pDB || !m_pDS)
12528 return;
12532 m_pDS->exec(PrepareSQL("UPDATE videoversion SET idType = %i WHERE idFile = %i", idVideoVersion,
12533 idFile));
12535 catch (...)
12537 CLog::Log(LOGERROR, "{} failed for video {}", __FUNCTION__, idFile);
12541 void CVideoDatabase::AddVideoAsset(VideoDbContentType itemType,
12542 int dbId,
12543 int idVideoVersion,
12544 VideoAssetType videoAssetType,
12545 CFileItem& item)
12547 if (!m_pDB || !m_pDS)
12548 return;
12550 assert(m_pDB->in_transaction() == false);
12552 MediaType mediaType;
12553 if (itemType == VideoDbContentType::MOVIES)
12555 mediaType = MediaTypeMovie;
12557 else
12558 return;
12560 int idFile = AddFile(item.GetPath());
12561 if (idFile < 0)
12562 return;
12566 BeginTransaction();
12568 m_pDS->query(PrepareSQL("SELECT idFile FROM videoversion WHERE idFile = %i", idFile));
12570 if (m_pDS->num_rows() == 0)
12571 m_pDS->exec(PrepareSQL("INSERT INTO videoversion VALUES(%i, %i, '%s', %i, %i)", idFile, dbId,
12572 mediaType.c_str(), videoAssetType, idVideoVersion));
12573 else
12574 m_pDS->exec(PrepareSQL("UPDATE videoversion SET idMedia = %i, media_type = '%s', itemType = "
12575 "%i, idType = %i WHERE idFile = %i",
12576 dbId, mediaType.c_str(), videoAssetType, idVideoVersion, idFile));
12578 if (item.GetVideoInfoTag()->HasStreamDetails())
12579 SetStreamDetailsForFileId(item.GetVideoInfoTag()->m_streamDetails, idFile);
12581 if (videoAssetType == VideoAssetType::VERSION)
12582 SetVideoVersionDefaultArt(idFile, item.GetVideoInfoTag()->m_iDbId, itemType);
12584 CommitTransaction();
12586 catch (...)
12588 CLog::LogF(LOGERROR, "failed for video {}", dbId);
12589 RollbackTransaction();
12593 VideoAssetInfo CVideoDatabase::GetVideoVersionInfo(const std::string& filenameAndPath)
12595 VideoAssetInfo info;
12597 info.m_idFile = GetFileId(filenameAndPath);
12598 if (info.m_idFile < 0)
12599 return info;
12601 if (!m_pDB || !m_pDS)
12602 return info;
12606 m_pDS->query(PrepareSQL("SELECT videoversiontype.name,"
12607 " videoversiontype.id,"
12608 " videoversion.idMedia,"
12609 " videoversion.media_type,"
12610 " videoversion.itemType "
12611 "FROM videoversion"
12612 " JOIN videoversiontype ON "
12613 " videoversiontype.id = videoversion.idType "
12614 "WHERE videoversion.idFile = %i",
12615 info.m_idFile));
12617 if (m_pDS->num_rows() > 0)
12619 info.m_assetTypeId = m_pDS->fv("id").get_asInt();
12620 info.m_assetTypeName = m_pDS->fv("name").get_asString();
12621 info.m_idMedia = m_pDS->fv("idMedia").get_asInt();
12622 info.m_mediaType = m_pDS->fv("media_type").get_asString();
12623 info.m_assetType = static_cast<VideoAssetType>(m_pDS->fv("itemType").get_asInt());
12626 m_pDS->close();
12628 catch (...)
12630 CLog::LogF(LOGERROR, "failed for {}", filenameAndPath);
12633 return info;
12636 bool CVideoDatabase::GetVideoVersionsNav(const std::string& strBaseDir,
12637 CFileItemList& items,
12638 VideoDbContentType idContent /* = UNKNOWN */,
12639 const Filter& filter /* = Filter() */)
12641 if (!m_pDB || !m_pDS)
12642 return false;
12644 MediaType mediaType;
12646 if (idContent == VideoDbContentType::MOVIES)
12648 mediaType = MediaTypeMovie;
12650 else
12651 return false;
12653 CVideoDbUrl videoUrl;
12654 if (!videoUrl.FromString(strBaseDir))
12655 return false;
12659 m_pDS->query(PrepareSQL(
12660 "SELECT DISTINCT videoversiontype.name AS name,"
12661 " videoversiontype.id AS id "
12662 "FROM videoversiontype"
12663 " JOIN videoversion ON"
12664 " videoversion.idType = videoversiontype.id "
12665 "WHERE name != '' AND owner IN (%i, %i) AND videoversiontype.itemType = %i",
12666 VideoAssetTypeOwner::SYSTEM, VideoAssetTypeOwner::USER, VideoAssetType::VERSION));
12668 while (!m_pDS->eof())
12670 const int id{m_pDS->fv("id").get_asInt()};
12672 CVideoDbUrl itemUrl{videoUrl};
12673 itemUrl.AppendPath(StringUtils::Format("{}/", id));
12675 const auto item{std::make_shared<CFileItem>(itemUrl.ToString(), true)};
12676 item->SetLabel(m_pDS->fv("name").get_asString());
12677 auto tag{item->GetVideoInfoTag()};
12678 tag->m_type = MediaTypeVideoVersion;
12679 tag->m_iDbId = id;
12681 items.Add(item);
12682 m_pDS->next();
12684 m_pDS->close();
12685 return true;
12687 catch (...)
12689 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12691 return false;
12694 bool CVideoDatabase::GetVideoVersionTypes(VideoDbContentType idContent,
12695 VideoAssetType assetType,
12696 CFileItemList& items)
12698 if (!m_pDB || !m_pDS)
12699 return false;
12701 MediaType mediaType;
12703 if (idContent == VideoDbContentType::MOVIES)
12705 mediaType = MediaTypeMovie;
12707 else
12708 return false;
12712 m_pDS->query(
12713 PrepareSQL("SELECT name, id FROM videoversiontype WHERE name != '' AND itemType = %i "
12714 "AND owner IN (%i, %i)",
12715 assetType, VideoAssetTypeOwner::SYSTEM, VideoAssetTypeOwner::USER));
12717 while (!m_pDS->eof())
12719 std::string name = m_pDS->fv("name").get_asString();
12720 int id = m_pDS->fv("id").get_asInt();
12722 const auto item{std::make_shared<CFileItem>(name)};
12723 item->GetVideoInfoTag()->m_type = MediaTypeVideoVersion;
12724 item->GetVideoInfoTag()->m_iDbId = id;
12725 item->GetVideoInfoTag()->GetAssetInfo().SetId(id);
12726 item->GetVideoInfoTag()->GetAssetInfo().SetTitle(name);
12727 item->GetVideoInfoTag()->m_strTitle = name;
12729 item->m_strTitle = name;
12730 item->SetLabel(name);
12732 items.Add(item);
12733 m_pDS->next();
12735 m_pDS->close();
12736 return true;
12738 catch (...)
12740 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12742 return false;
12745 std::string CVideoDatabase::GetVideoVersionById(int id)
12747 return GetSingleValue(PrepareSQL("SELECT name FROM videoversiontype WHERE id=%i", id), m_pDS2);
12750 void CVideoDatabase::SetVideoVersionDefaultArt(int dbId, int idFrom, VideoDbContentType type)
12752 MediaType mediaType;
12753 VideoContentTypeToString(type, mediaType);
12755 std::map<std::string, std::string> art;
12756 if (GetArtForItem(idFrom, mediaType, art))
12758 for (const auto& it : art)
12759 SetArtForItem(dbId, MediaTypeVideoVersion, it.first, it.second);
12763 std::vector<std::string> CVideoDatabase::GetUsedImages(
12764 const std::vector<std::string>& imagesToCheck)
12768 if (!m_pDB || !m_pDS)
12769 return imagesToCheck;
12771 if (!imagesToCheck.size())
12772 return {};
12774 int artworkLevel = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
12775 CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL);
12776 if (artworkLevel == CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_NONE)
12778 return {};
12781 // first check the art table
12783 std::string sql = "SELECT DISTINCT url FROM art WHERE url IN (";
12784 for (const auto& image : imagesToCheck)
12786 sql += PrepareSQL("'%s',", image.c_str());
12788 sql.pop_back(); // remove last ','
12789 sql += ")";
12791 // add arttype filters if not set to "Maximum"
12792 if (artworkLevel != CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_ALL)
12794 static std::array<std::string, 7> mediatypes = {
12795 MediaTypeEpisode, MediaTypeTvShow, MediaTypeSeason, MediaTypeMovie,
12796 MediaTypeVideoCollection, MediaTypeMusicVideo, MediaTypeVideoVersion};
12798 std::string arttypeSQL;
12799 for (const auto& mediatype : mediatypes)
12801 const auto& arttypes = CVideoThumbLoader::GetArtTypes(mediatype);
12802 if (arttypes.empty())
12803 continue;
12805 if (!arttypeSQL.empty())
12806 arttypeSQL += ") OR ";
12807 arttypeSQL += PrepareSQL("media_type = '%s' AND (", mediatype.c_str());
12808 bool workingNext = false;
12809 for (const auto& arttype : arttypes)
12811 if (workingNext)
12812 arttypeSQL += " OR ";
12813 workingNext = true;
12814 if (artworkLevel == CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_BASIC)
12816 // for basic match exact artwork type
12817 arttypeSQL += PrepareSQL("type = '%s'", arttype.c_str());
12819 else
12821 // otherwise check for arttype 'families', like fanart, fanart1, fanart13;
12822 // still avoid most "happens to start with" like fanartstuff
12823 arttypeSQL +=
12824 PrepareSQL("type BETWEEN '%s' AND '%s999'", arttype.c_str(), arttype.c_str());
12829 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
12830 CSettings::SETTING_VIDEOLIBRARY_ACTORTHUMBS))
12832 if (!arttypeSQL.empty())
12833 arttypeSQL += ") OR ";
12834 arttypeSQL += "media_type = 'actor'";
12837 if (!arttypeSQL.empty())
12838 sql += " AND (" + arttypeSQL + ")";
12841 std::vector<std::string> result;
12842 if (m_pDS->query(sql))
12844 while (!m_pDS->eof())
12846 result.push_back(m_pDS->fv(0).get_asString());
12847 m_pDS->next();
12849 m_pDS->close();
12852 // then check any chapter thumbnails against path and file tables
12854 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
12855 CSettings::SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS))
12857 std::vector<std::string> foundVideoFiles;
12858 for (const auto& image : imagesToCheck)
12860 auto imageFile = IMAGE_FILES::CImageFileURL(image);
12861 if (imageFile.GetSpecialType() == "video" && !imageFile.GetOption("chapter").empty())
12863 const auto& target = imageFile.GetTargetFile();
12864 auto quickFind = std::find(foundVideoFiles.begin(), foundVideoFiles.end(), target);
12865 if (quickFind != foundVideoFiles.end())
12867 result.push_back(image);
12869 else
12871 int fileId = GetFileId(target);
12872 if (fileId != -1)
12874 result.push_back(image);
12875 foundVideoFiles.push_back(target);
12882 return result;
12884 catch (...)
12886 CLog::LogF(LOGERROR, "failed");
12888 return {};