Merge pull request #25083 from CrystalP/fix-versiondelete
[xbmc.git] / xbmc / video / VideoDatabase.cpp
blobd23851bbc043dea53c42d09d19cb2460b315821c
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 "interfaces/AnnouncementManager.h"
37 #include "messaging/helpers/DialogOKHelper.h"
38 #include "music/Artist.h"
39 #include "playlists/SmartPlayList.h"
40 #include "profiles/ProfileManager.h"
41 #include "settings/AdvancedSettings.h"
42 #include "settings/MediaSettings.h"
43 #include "settings/MediaSourceSettings.h"
44 #include "settings/Settings.h"
45 #include "settings/SettingsComponent.h"
46 #include "storage/MediaManager.h"
47 #include "utils/FileUtils.h"
48 #include "utils/GroupUtils.h"
49 #include "utils/LabelFormatter.h"
50 #include "utils/StringUtils.h"
51 #include "utils/URIUtils.h"
52 #include "utils/Variant.h"
53 #include "utils/XMLUtils.h"
54 #include "utils/log.h"
55 #include "video/VideoDbUrl.h"
56 #include "video/VideoFileItemClassify.h"
57 #include "video/VideoInfoTag.h"
58 #include "video/VideoLibraryQueue.h"
59 #include "video/VideoManagerTypes.h"
60 #include "video/VideoThumbLoader.h"
62 #include <algorithm>
63 #include <map>
64 #include <memory>
65 #include <string>
66 #include <unordered_set>
67 #include <vector>
69 using namespace dbiplus;
70 using namespace XFILE;
71 using namespace ADDON;
72 using namespace KODI;
73 using namespace KODI::MESSAGING;
74 using namespace KODI::GUILIB;
75 using namespace KODI::VIDEO;
77 //********************************************************************************************************************************
78 CVideoDatabase::CVideoDatabase(void) = default;
80 //********************************************************************************************************************************
81 CVideoDatabase::~CVideoDatabase(void) = default;
83 //********************************************************************************************************************************
84 bool CVideoDatabase::Open()
86 return CDatabase::Open(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_databaseVideo);
89 void CVideoDatabase::CreateTables()
91 CLog::Log(LOGINFO, "create bookmark table");
92 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");
94 CLog::Log(LOGINFO, "create settings table");
95 m_pDS->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
96 "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
97 "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
98 "VolumeAmplification float, AudioDelay float, ResumeTime integer,"
99 "Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
100 "ScalingMethod integer, DeinterlaceMode integer, StereoMode integer, StereoInvert bool, VideoStream integer,"
101 "TonemapMethod integer, TonemapParam float, Orientation integer, CenterMixLevel integer)\n");
103 CLog::Log(LOGINFO, "create stacktimes table");
104 m_pDS->exec("CREATE TABLE stacktimes (idFile integer, times text)\n");
106 CLog::Log(LOGINFO, "create genre table");
107 m_pDS->exec("CREATE TABLE genre ( genre_id integer primary key, name TEXT)\n");
108 m_pDS->exec("CREATE TABLE genre_link (genre_id integer, media_id integer, media_type TEXT)");
110 CLog::Log(LOGINFO, "create country table");
111 m_pDS->exec("CREATE TABLE country ( country_id integer primary key, name TEXT)");
112 m_pDS->exec("CREATE TABLE country_link (country_id integer, media_id integer, media_type TEXT)");
114 CLog::Log(LOGINFO, "create movie table");
115 std::string columns = "CREATE TABLE movie ( idMovie integer primary key, idFile integer";
117 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
118 columns += StringUtils::Format(",c{:02} text", i);
120 columns += ", idSet integer, userrating integer, premiered text)";
121 m_pDS->exec(columns);
123 CLog::Log(LOGINFO, "create actor table");
124 m_pDS->exec("CREATE TABLE actor ( actor_id INTEGER PRIMARY KEY, name TEXT, art_urls TEXT )");
125 m_pDS->exec("CREATE TABLE actor_link(actor_id INTEGER, media_id INTEGER, media_type TEXT, role TEXT, cast_order INTEGER)");
126 m_pDS->exec("CREATE TABLE director_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
127 m_pDS->exec("CREATE TABLE writer_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
129 CLog::Log(LOGINFO, "create path table");
130 m_pDS->exec(
131 "CREATE TABLE path ( idPath integer primary key, strPath text, strContent text, strScraper "
132 "text, strHash text, scanRecursive integer, useFolderNames bool, strSettings text, noUpdate "
133 "bool, exclude bool, allAudio bool, dateAdded text, idParentPath integer)");
135 CLog::Log(LOGINFO, "create files table");
136 m_pDS->exec("CREATE TABLE files ( idFile integer primary key, idPath integer, strFilename text, playCount integer, lastPlayed text, dateAdded text)");
138 CLog::Log(LOGINFO, "create tvshow table");
139 columns = "CREATE TABLE tvshow ( idShow integer primary key";
141 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
142 columns += StringUtils::Format(",c{:02} text", i);
144 columns += ", userrating integer, duration INTEGER)";
145 m_pDS->exec(columns);
147 CLog::Log(LOGINFO, "create episode table");
148 columns = "CREATE TABLE episode ( idEpisode integer primary key, idFile integer";
149 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
151 std::string column;
152 if ( i == VIDEODB_ID_EPISODE_SEASON || i == VIDEODB_ID_EPISODE_EPISODE || i == VIDEODB_ID_EPISODE_BOOKMARK)
153 column = StringUtils::Format(",c{:02} varchar(24)", i);
154 else
155 column = StringUtils::Format(",c{:02} text", i);
157 columns += column;
159 columns += ", idShow integer, userrating integer, idSeason integer)";
160 m_pDS->exec(columns);
162 CLog::Log(LOGINFO, "create tvshowlinkpath table");
163 m_pDS->exec("CREATE TABLE tvshowlinkpath (idShow integer, idPath integer)\n");
165 CLog::Log(LOGINFO, "create movielinktvshow table");
166 m_pDS->exec("CREATE TABLE movielinktvshow ( idMovie integer, IdShow integer)\n");
168 CLog::Log(LOGINFO, "create studio table");
169 m_pDS->exec("CREATE TABLE studio ( studio_id integer primary key, name TEXT)\n");
170 m_pDS->exec("CREATE TABLE studio_link (studio_id integer, media_id integer, media_type TEXT)");
172 CLog::Log(LOGINFO, "create musicvideo table");
173 columns = "CREATE TABLE musicvideo ( idMVideo integer primary key, idFile integer";
174 for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
175 columns += StringUtils::Format(",c{:02} text", i);
177 columns += ", userrating integer, premiered text)";
178 m_pDS->exec(columns);
180 CLog::Log(LOGINFO, "create streaminfo table");
181 m_pDS->exec("CREATE TABLE streamdetails (idFile integer, iStreamType integer, "
182 "strVideoCodec text, fVideoAspect float, iVideoWidth integer, iVideoHeight integer, "
183 "strAudioCodec text, iAudioChannels integer, strAudioLanguage text, "
184 "strSubtitleLanguage text, iVideoDuration integer, strStereoMode text, strVideoLanguage text, "
185 "strHdrType text)");
187 CLog::Log(LOGINFO, "create sets table");
188 m_pDS->exec("CREATE TABLE sets ( idSet integer primary key, strSet text, strOverview text)");
190 CLog::Log(LOGINFO, "create seasons table");
191 m_pDS->exec("CREATE TABLE seasons ( idSeason integer primary key, idShow integer, season integer, name text, userrating integer)");
193 CLog::Log(LOGINFO, "create art table");
194 m_pDS->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
196 CLog::Log(LOGINFO, "create tag table");
197 m_pDS->exec("CREATE TABLE tag (tag_id integer primary key, name TEXT)");
198 m_pDS->exec("CREATE TABLE tag_link (tag_id integer, media_id integer, media_type TEXT)");
200 CLog::Log(LOGINFO, "create rating table");
201 m_pDS->exec("CREATE TABLE rating (rating_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, rating_type TEXT, rating FLOAT, votes INTEGER)");
203 CLog::Log(LOGINFO, "create uniqueid table");
204 m_pDS->exec("CREATE TABLE uniqueid (uniqueid_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, value TEXT, type TEXT)");
206 CLog::Log(LOGINFO, "create videoversiontype table");
207 m_pDS->exec("CREATE TABLE videoversiontype (id INTEGER PRIMARY KEY, name TEXT, owner INTEGER, "
208 "itemType INTEGER)");
209 InitializeVideoVersionTypeTable(GetSchemaVersion());
211 CLog::Log(LOGINFO, "create videoversion table");
212 m_pDS->exec("CREATE TABLE videoversion (idFile INTEGER PRIMARY KEY, idMedia INTEGER, media_type "
213 "TEXT, itemType INTEGER, idType INTEGER)");
216 void CVideoDatabase::CreateLinkIndex(const char *table)
218 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_1 ON %s (name(255))", table, table));
219 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_1 ON %s_link (%s_id, media_type(20), media_id)", table, table, table));
220 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_2 ON %s_link (media_id, media_type(20), %s_id)", table, table, table));
221 m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s_link_3 ON %s_link (media_type(20))", table, table));
224 void CVideoDatabase::CreateForeignLinkIndex(const char *table, const char *foreignkey)
226 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_1 ON %s_link (%s_id, media_type(20), media_id)", table, table, foreignkey));
227 m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_2 ON %s_link (media_id, media_type(20), %s_id)", table, table, foreignkey));
228 m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s_link_3 ON %s_link (media_type(20))", table, table));
231 void CVideoDatabase::CreateAnalytics()
233 /* indexes should be added on any columns that are used in */
234 /* a where or a join. primary key on a column is the same as a */
235 /* unique index on that column, so there is no need to add any */
236 /* index if no other columns are referred */
238 /* order of indexes are important, for an index to be considered all */
239 /* columns up to the column in question have to have been specified */
240 /* select * from foolink where foo_id = 1, can not take */
241 /* advantage of a index that has been created on ( bar_id, foo_id ) */
242 /* however an index on ( foo_id, bar_id ) will be considered for use */
244 CLog::Log(LOGINFO, "{} - creating indices", __FUNCTION__);
245 m_pDS->exec("CREATE INDEX ix_bookmark ON bookmark (idFile, type)");
246 m_pDS->exec("CREATE UNIQUE INDEX ix_settings ON settings ( idFile )\n");
247 m_pDS->exec("CREATE UNIQUE INDEX ix_stacktimes ON stacktimes ( idFile )\n");
248 m_pDS->exec("CREATE INDEX ix_path ON path ( strPath(255) )");
249 m_pDS->exec("CREATE INDEX ix_path2 ON path ( idParentPath )");
250 m_pDS->exec("CREATE INDEX ix_files ON files ( idPath, strFilename(255) )");
252 m_pDS->exec("CREATE UNIQUE INDEX ix_movie_file_1 ON movie (idFile, idMovie)");
253 m_pDS->exec("CREATE UNIQUE INDEX ix_movie_file_2 ON movie (idMovie, idFile)");
255 m_pDS->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_1 ON tvshowlinkpath ( idShow, idPath )\n");
256 m_pDS->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_2 ON tvshowlinkpath ( idPath, idShow )\n");
257 m_pDS->exec("CREATE UNIQUE INDEX ix_movielinktvshow_1 ON movielinktvshow ( idShow, idMovie)\n");
258 m_pDS->exec("CREATE UNIQUE INDEX ix_movielinktvshow_2 ON movielinktvshow ( idMovie, idShow)\n");
260 m_pDS->exec("CREATE UNIQUE INDEX ix_episode_file_1 on episode (idEpisode, idFile)");
261 m_pDS->exec("CREATE UNIQUE INDEX id_episode_file_2 on episode (idFile, idEpisode)");
262 std::string createColIndex =
263 StringUtils::Format("CREATE INDEX ix_episode_season_episode on episode (c{:02}, c{:02})",
264 VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_EPISODE);
265 m_pDS->exec(createColIndex);
266 createColIndex = StringUtils::Format("CREATE INDEX ix_episode_bookmark on episode (c{:02})",
267 VIDEODB_ID_EPISODE_BOOKMARK);
268 m_pDS->exec(createColIndex);
269 m_pDS->exec("CREATE INDEX ix_episode_show1 on episode(idEpisode,idShow)");
270 m_pDS->exec("CREATE INDEX ix_episode_show2 on episode(idShow,idEpisode)");
272 m_pDS->exec("CREATE UNIQUE INDEX ix_musicvideo_file_1 on musicvideo (idMVideo, idFile)");
273 m_pDS->exec("CREATE UNIQUE INDEX ix_musicvideo_file_2 on musicvideo (idFile, idMVideo)");
275 m_pDS->exec("CREATE INDEX ixMovieBasePath ON movie ( c23(12) )");
276 m_pDS->exec("CREATE INDEX ixMusicVideoBasePath ON musicvideo ( c14(12) )");
277 m_pDS->exec("CREATE INDEX ixEpisodeBasePath ON episode ( c19(12) )");
279 m_pDS->exec("CREATE INDEX ix_streamdetails ON streamdetails (idFile)");
280 m_pDS->exec("CREATE INDEX ix_seasons ON seasons (idShow, season)");
281 m_pDS->exec("CREATE INDEX ix_art ON art(media_id, media_type(20), type(20))");
283 m_pDS->exec("CREATE INDEX ix_rating ON rating(media_id, media_type(20))");
285 m_pDS->exec("CREATE INDEX ix_uniqueid1 ON uniqueid(media_id, media_type(20), type(20))");
286 m_pDS->exec("CREATE INDEX ix_uniqueid2 ON uniqueid(media_type(20), value(20))");
288 m_pDS->exec("CREATE UNIQUE INDEX ix_actor_1 ON actor (name(255))");
289 m_pDS->exec("CREATE UNIQUE INDEX ix_actor_link_1 ON "
290 "actor_link (actor_id, media_type(20), media_id, role(255))");
291 m_pDS->exec("CREATE INDEX ix_actor_link_2 ON "
292 "actor_link (media_id, media_type(20), actor_id)");
293 m_pDS->exec("CREATE INDEX ix_actor_link_3 ON actor_link (media_type(20))");
295 m_pDS->exec("CREATE INDEX ix_videoversion ON videoversion (idMedia, media_type(20))");
297 m_pDS->exec(PrepareSQL("CREATE INDEX ix_movie_title ON movie (c%02d(255))", VIDEODB_ID_TITLE));
299 CreateLinkIndex("tag");
300 CreateForeignLinkIndex("director", "actor");
301 CreateForeignLinkIndex("writer", "actor");
302 CreateLinkIndex("studio");
303 CreateLinkIndex("genre");
304 CreateLinkIndex("country");
306 CLog::Log(LOGINFO, "{} - creating triggers", __FUNCTION__);
307 m_pDS->exec("CREATE TRIGGER delete_movie AFTER DELETE ON movie FOR EACH ROW BEGIN "
308 "DELETE FROM genre_link WHERE media_id=old.idMovie AND media_type='movie'; "
309 "DELETE FROM actor_link WHERE media_id=old.idMovie AND media_type='movie'; "
310 "DELETE FROM director_link WHERE media_id=old.idMovie AND media_type='movie'; "
311 "DELETE FROM studio_link WHERE media_id=old.idMovie AND media_type='movie'; "
312 "DELETE FROM country_link WHERE media_id=old.idMovie AND media_type='movie'; "
313 "DELETE FROM writer_link WHERE media_id=old.idMovie AND media_type='movie'; "
314 "DELETE FROM movielinktvshow WHERE idMovie=old.idMovie; "
315 "DELETE FROM art WHERE media_id=old.idMovie AND media_type='movie'; "
316 "DELETE FROM tag_link WHERE media_id=old.idMovie AND media_type='movie'; "
317 "DELETE FROM rating WHERE media_id=old.idMovie AND media_type='movie'; "
318 "DELETE FROM uniqueid WHERE media_id=old.idMovie AND media_type='movie'; "
319 "DELETE FROM videoversion "
320 "WHERE idFile=old.idFile AND idMedia=old.idMovie AND media_type='movie'; "
321 "END");
322 m_pDS->exec("CREATE TRIGGER delete_tvshow AFTER DELETE ON tvshow FOR EACH ROW BEGIN "
323 "DELETE FROM actor_link WHERE media_id=old.idShow AND media_type='tvshow'; "
324 "DELETE FROM director_link WHERE media_id=old.idShow AND media_type='tvshow'; "
325 "DELETE FROM studio_link WHERE media_id=old.idShow AND media_type='tvshow'; "
326 "DELETE FROM tvshowlinkpath WHERE idShow=old.idShow; "
327 "DELETE FROM genre_link WHERE media_id=old.idShow AND media_type='tvshow'; "
328 "DELETE FROM movielinktvshow WHERE idShow=old.idShow; "
329 "DELETE FROM seasons WHERE idShow=old.idShow; "
330 "DELETE FROM art WHERE media_id=old.idShow AND media_type='tvshow'; "
331 "DELETE FROM tag_link WHERE media_id=old.idShow AND media_type='tvshow'; "
332 "DELETE FROM rating WHERE media_id=old.idShow AND media_type='tvshow'; "
333 "DELETE FROM uniqueid WHERE media_id=old.idShow AND media_type='tvshow'; "
334 "END");
335 m_pDS->exec("CREATE TRIGGER delete_musicvideo AFTER DELETE ON musicvideo FOR EACH ROW BEGIN "
336 "DELETE FROM actor_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
337 "DELETE FROM director_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
338 "DELETE FROM genre_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
339 "DELETE FROM studio_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
340 "DELETE FROM art WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
341 "DELETE FROM tag_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
342 "DELETE FROM uniqueid WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
343 "END");
344 m_pDS->exec("CREATE TRIGGER delete_episode AFTER DELETE ON episode FOR EACH ROW BEGIN "
345 "DELETE FROM actor_link WHERE media_id=old.idEpisode AND media_type='episode'; "
346 "DELETE FROM director_link WHERE media_id=old.idEpisode AND media_type='episode'; "
347 "DELETE FROM writer_link WHERE media_id=old.idEpisode AND media_type='episode'; "
348 "DELETE FROM art WHERE media_id=old.idEpisode AND media_type='episode'; "
349 "DELETE FROM rating WHERE media_id=old.idEpisode AND media_type='episode'; "
350 "DELETE FROM uniqueid WHERE media_id=old.idEpisode AND media_type='episode'; "
351 "END");
352 m_pDS->exec("CREATE TRIGGER delete_season AFTER DELETE ON seasons FOR EACH ROW BEGIN "
353 "DELETE FROM art WHERE media_id=old.idSeason AND media_type='season'; "
354 "END");
355 m_pDS->exec("CREATE TRIGGER delete_set AFTER DELETE ON sets FOR EACH ROW BEGIN "
356 "DELETE FROM art WHERE media_id=old.idSet AND media_type='set'; "
357 "END");
358 m_pDS->exec("CREATE TRIGGER delete_person AFTER DELETE ON actor FOR EACH ROW BEGIN "
359 "DELETE FROM art WHERE media_id=old.actor_id AND media_type IN ('actor','artist','writer','director'); "
360 "END");
361 m_pDS->exec("CREATE TRIGGER delete_tag AFTER DELETE ON tag_link FOR EACH ROW BEGIN "
362 "DELETE FROM tag WHERE tag_id=old.tag_id AND tag_id NOT IN (SELECT DISTINCT tag_id FROM tag_link); "
363 "END");
364 m_pDS->exec("CREATE TRIGGER delete_file AFTER DELETE ON files FOR EACH ROW BEGIN "
365 "DELETE FROM bookmark WHERE idFile=old.idFile; "
366 "DELETE FROM settings WHERE idFile=old.idFile; "
367 "DELETE FROM stacktimes WHERE idFile=old.idFile; "
368 "DELETE FROM streamdetails WHERE idFile=old.idFile; "
369 "DELETE FROM videoversion WHERE idFile=old.idFile; "
370 "DELETE FROM art WHERE media_id=old.idFile AND media_type='videoversion'; "
371 "END");
372 m_pDS->exec("CREATE TRIGGER delete_videoversion AFTER DELETE ON videoversion FOR EACH ROW BEGIN "
373 "DELETE FROM art WHERE media_id=old.idFile AND media_type='videoversion'; "
374 "DELETE FROM streamdetails WHERE idFile=old.idFile; "
375 "END");
377 CreateViews();
380 void CVideoDatabase::CreateViews()
382 CLog::Log(LOGINFO, "create episode_view");
383 std::string episodeview = PrepareSQL("CREATE VIEW episode_view AS SELECT "
384 " episode.*,"
385 " files.strFileName AS strFileName,"
386 " path.strPath AS strPath,"
387 " files.playCount AS playCount,"
388 " files.lastPlayed AS lastPlayed,"
389 " files.dateAdded AS dateAdded,"
390 " tvshow.c%02d AS strTitle,"
391 " tvshow.c%02d AS genre,"
392 " tvshow.c%02d AS studio,"
393 " tvshow.c%02d AS premiered,"
394 " tvshow.c%02d AS mpaa,"
395 " bookmark.timeInSeconds AS resumeTimeInSeconds, "
396 " bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
397 " bookmark.playerState AS playerState, "
398 " rating.rating AS rating, "
399 " rating.votes AS votes, "
400 " rating.rating_type AS rating_type, "
401 " uniqueid.value AS uniqueid_value, "
402 " uniqueid.type AS uniqueid_type "
403 "FROM episode"
404 " JOIN files ON"
405 " files.idFile=episode.idFile"
406 " JOIN tvshow ON"
407 " tvshow.idShow=episode.idShow"
408 " JOIN seasons ON"
409 " seasons.idSeason=episode.idSeason"
410 " JOIN path ON"
411 " files.idPath=path.idPath"
412 " LEFT JOIN bookmark ON"
413 " bookmark.idFile=episode.idFile AND bookmark.type=1"
414 " LEFT JOIN rating ON"
415 " rating.rating_id=episode.c%02d"
416 " LEFT JOIN uniqueid ON"
417 " uniqueid.uniqueid_id=episode.c%02d",
418 VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_GENRE,
419 VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_PREMIERED,
420 VIDEODB_ID_TV_MPAA, VIDEODB_ID_EPISODE_RATING_ID,
421 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 const auto filePath = details.GetPath();
1472 if (nullptr == m_pDB)
1473 return -1;
1474 if (nullptr == m_pDS)
1475 return -1;
1477 if (details.m_iFileId <= 0)
1479 details.m_iFileId = AddFile(details);
1480 if (details.m_iFileId <= 0)
1481 return -1;
1484 BeginTransaction();
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 CommitTransaction();
1497 return details.m_iDbId;
1499 catch (...)
1501 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
1502 RollbackTransaction();
1504 return -1;
1507 bool CVideoDatabase::AddPathToTvShow(int idShow, const std::string &path, const std::string &parentPath, const CDateTime& dateAdded /* = CDateTime() */)
1509 // Check if this path is already added
1510 int idPath = GetPathId(path);
1511 if (idPath < 0)
1512 idPath = AddPath(path, parentPath, GetDateAdded(path, dateAdded));
1514 return ExecuteQuery(PrepareSQL("REPLACE INTO tvshowlinkpath(idShow, idPath) VALUES (%i,%i)", idShow, idPath));
1517 int CVideoDatabase::AddTvShow()
1519 if (ExecuteQuery("INSERT INTO tvshow(idShow) VALUES(NULL)"))
1520 return (int)m_pDS->lastinsertid();
1521 return -1;
1524 //********************************************************************************************************************************
1525 int CVideoDatabase::AddNewEpisode(int idShow, CVideoInfoTag& details)
1527 const auto filePath = details.GetPath();
1531 if (nullptr == m_pDB || nullptr == m_pDS)
1532 return -1;
1534 if (details.m_iFileId <= 0)
1536 details.m_iFileId = AddFile(details);
1537 if (details.m_iFileId <= 0)
1538 return -1;
1541 std::string strSQL =
1542 PrepareSQL("INSERT INTO episode (idEpisode, idFile, idShow) VALUES (NULL, %i, %i)",
1543 details.m_iFileId, idShow);
1544 m_pDS->exec(strSQL);
1545 details.m_iDbId = static_cast<int>(m_pDS->lastinsertid());
1547 return details.m_iDbId;
1549 catch (...)
1551 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
1553 return -1;
1556 int CVideoDatabase::AddNewMusicVideo(CVideoInfoTag& details)
1558 const auto filePath = details.GetPath();
1562 if (nullptr == m_pDB)
1563 return -1;
1564 if (nullptr == m_pDS)
1565 return -1;
1567 if (details.m_iFileId <= 0)
1569 details.m_iFileId = AddFile(details);
1570 if (details.m_iFileId <= 0)
1571 return -1;
1574 std::string strSQL = PrepareSQL("INSERT INTO musicvideo (idMVideo, idFile) VALUES (NULL, %i)",
1575 details.m_iFileId);
1576 m_pDS->exec(strSQL);
1577 details.m_iDbId = static_cast<int>(m_pDS->lastinsertid());
1579 return details.m_iDbId;
1581 catch (...)
1583 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
1585 return -1;
1588 //********************************************************************************************************************************
1589 int CVideoDatabase::AddToTable(const std::string& table, const std::string& firstField, const std::string& secondField, const std::string& value)
1593 if (nullptr == m_pDB)
1594 return -1;
1595 if (nullptr == m_pDS)
1596 return -1;
1598 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());
1599 m_pDS->query(strSQL);
1600 if (m_pDS->num_rows() == 0)
1602 m_pDS->close();
1603 // doesn't exists, add it
1604 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());
1605 m_pDS->exec(strSQL);
1606 int id = (int)m_pDS->lastinsertid();
1607 return id;
1609 else
1611 int id = m_pDS->fv(firstField.c_str()).get_asInt();
1612 m_pDS->close();
1613 return id;
1616 catch (...)
1618 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, value);
1621 return -1;
1624 int CVideoDatabase::UpdateRatings(int mediaId, const char *mediaType, const RatingMap& values, const std::string& defaultRating)
1628 if (nullptr == m_pDB)
1629 return -1;
1630 if (nullptr == m_pDS)
1631 return -1;
1633 std::string sql = PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='%s'", mediaId, mediaType);
1634 m_pDS->exec(sql);
1636 return AddRatings(mediaId, mediaType, values, defaultRating);
1638 catch (...)
1640 CLog::Log(LOGERROR, "{} unable to update ratings of ({})", __FUNCTION__, mediaType);
1642 return -1;
1645 int CVideoDatabase::AddRatings(int mediaId, const char *mediaType, const RatingMap& values, const std::string& defaultRating)
1647 int ratingid = -1;
1650 if (nullptr == m_pDB)
1651 return -1;
1652 if (nullptr == m_pDS)
1653 return -1;
1655 for (const auto& i : values)
1657 int id;
1658 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());
1659 m_pDS->query(strSQL);
1660 if (m_pDS->num_rows() == 0)
1662 m_pDS->close();
1663 // doesn't exists, add it
1664 strSQL = PrepareSQL("INSERT INTO rating (media_id, media_type, rating_type, rating, votes) "
1665 "VALUES (%i, '%s', '%s', %f, %i)",
1666 mediaId, mediaType, i.first.c_str(),
1667 static_cast<double>(i.second.rating), i.second.votes);
1668 m_pDS->exec(strSQL);
1669 id = (int)m_pDS->lastinsertid();
1671 else
1673 id = m_pDS->fv(0).get_asInt();
1674 m_pDS->close();
1675 strSQL = PrepareSQL("UPDATE rating SET rating = %f, votes = %i WHERE rating_id = %i",
1676 static_cast<double>(i.second.rating), i.second.votes, id);
1677 m_pDS->exec(strSQL);
1679 if (i.first == defaultRating)
1680 ratingid = id;
1682 return ratingid;
1685 catch (...)
1687 CLog::Log(LOGERROR, "{} ({} - {}) failed", __FUNCTION__, mediaId, mediaType);
1690 return ratingid;
1693 int CVideoDatabase::UpdateUniqueIDs(int mediaId, const char *mediaType, const CVideoInfoTag& details)
1697 if (nullptr == m_pDB)
1698 return -1;
1699 if (nullptr == m_pDS)
1700 return -1;
1702 std::string sql = PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='%s'", mediaId, mediaType);
1703 m_pDS->exec(sql);
1705 return AddUniqueIDs(mediaId, mediaType, details);
1707 catch (...)
1709 CLog::Log(LOGERROR, "{} unable to update unique ids of ({})", __FUNCTION__, mediaType);
1711 return -1;
1714 int CVideoDatabase::AddUniqueIDs(int mediaId, const char *mediaType, const CVideoInfoTag& details)
1716 int uniqueid = -1;
1719 if (nullptr == m_pDB)
1720 return -1;
1721 if (nullptr == m_pDS)
1722 return -1;
1724 for (const auto& i : details.GetUniqueIDs())
1726 int id;
1727 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());
1728 m_pDS->query(strSQL);
1729 if (m_pDS->num_rows() == 0)
1731 m_pDS->close();
1732 // doesn't exists, add it
1733 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());
1734 m_pDS->exec(strSQL);
1735 id = (int)m_pDS->lastinsertid();
1737 else
1739 id = m_pDS->fv(0).get_asInt();
1740 m_pDS->close();
1741 strSQL = PrepareSQL("UPDATE uniqueid SET value = '%s', type = '%s' WHERE uniqueid_id = %i", i.second.c_str(), i.first.c_str(), id);
1742 m_pDS->exec(strSQL);
1744 if (i.first == details.GetDefaultUniqueID())
1745 uniqueid = id;
1747 return uniqueid;
1750 catch (...)
1752 CLog::Log(LOGERROR, "{} ({} - {}) failed", __FUNCTION__, mediaId, mediaType);
1755 return uniqueid;
1758 int CVideoDatabase::AddSet(const std::string& strSet,
1759 const std::string& strOverview /* = "" */,
1760 const bool updateOverview /* = true */)
1762 if (strSet.empty())
1763 return -1;
1767 if (m_pDB == nullptr || m_pDS == nullptr)
1768 return -1;
1770 std::string strSQL = PrepareSQL("SELECT idSet FROM sets WHERE strSet LIKE '%s'", strSet.c_str());
1771 m_pDS->query(strSQL);
1772 if (m_pDS->num_rows() == 0)
1774 m_pDS->close();
1775 strSQL = PrepareSQL("INSERT INTO sets (idSet, strSet, strOverview) VALUES(NULL, '%s', '%s')", strSet.c_str(), strOverview.c_str());
1776 m_pDS->exec(strSQL);
1777 int id = static_cast<int>(m_pDS->lastinsertid());
1778 return id;
1780 else
1782 int id = m_pDS->fv("idSet").get_asInt();
1783 m_pDS->close();
1785 // update set data
1786 if (updateOverview)
1788 strSQL = PrepareSQL("UPDATE sets SET strOverview = '%s' WHERE idSet = %i",
1789 strOverview.c_str(), id);
1790 m_pDS->exec(strSQL);
1793 return id;
1796 catch (...)
1798 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSet);
1801 return -1;
1804 int CVideoDatabase::AddTag(const std::string& name)
1806 if (name.empty())
1807 return -1;
1809 return AddToTable("tag", "tag_id", "name", name);
1812 int CVideoDatabase::AddActor(const std::string& name, const std::string& thumbURLs, const std::string &thumb)
1816 if (nullptr == m_pDB)
1817 return -1;
1818 if (nullptr == m_pDS)
1819 return -1;
1820 int idActor = -1;
1822 // ATTENTION: the trimming of actor names should really not be done here but after the scraping / NFO-parsing
1823 std::string trimmedName = name;
1824 StringUtils::Trim(trimmedName);
1826 std::string strSQL=PrepareSQL("select actor_id from actor where name like '%s'", trimmedName.substr(0, 255).c_str());
1827 m_pDS->query(strSQL);
1828 if (m_pDS->num_rows() == 0)
1830 m_pDS->close();
1831 // doesn't exists, add it
1832 strSQL=PrepareSQL("insert into actor (actor_id, name, art_urls) values(NULL, '%s', '%s')", trimmedName.substr(0,255).c_str(), thumbURLs.c_str());
1833 m_pDS->exec(strSQL);
1834 idActor = (int)m_pDS->lastinsertid();
1836 else
1838 idActor = m_pDS->fv(0).get_asInt();
1839 m_pDS->close();
1840 // update the thumb url's
1841 if (!thumbURLs.empty())
1843 strSQL=PrepareSQL("update actor set art_urls = '%s' where actor_id = %i", thumbURLs.c_str(), idActor);
1844 m_pDS->exec(strSQL);
1847 // add artwork
1848 if (!thumb.empty())
1849 SetArtForItem(idActor, "actor", "thumb", thumb);
1850 return idActor;
1852 catch (...)
1854 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, name);
1856 return -1;
1861 void CVideoDatabase::AddLinkToActor(int mediaId, const char *mediaType, int actorId, const std::string &role, int order)
1863 std::string sql = PrepareSQL("SELECT 1 FROM actor_link WHERE actor_id=%i AND "
1864 "media_id=%i AND media_type='%s' AND role='%s'",
1865 actorId, mediaId, mediaType, role.c_str());
1867 if (GetSingleValue(sql).empty())
1868 { // doesn't exists, add it
1869 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);
1870 ExecuteQuery(sql);
1874 void CVideoDatabase::AddToLinkTable(int mediaId, const std::string& mediaType, const std::string& table, int valueId, const char *foreignKey)
1876 const char *key = foreignKey ? foreignKey : table.c_str();
1877 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());
1879 if (GetSingleValue(sql).empty())
1880 { // doesn't exists, add it
1881 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());
1882 ExecuteQuery(sql);
1886 void CVideoDatabase::RemoveFromLinkTable(int mediaId, const std::string& mediaType, const std::string& table, int valueId, const char *foreignKey)
1888 const char *key = foreignKey ? foreignKey : table.c_str();
1889 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());
1891 ExecuteQuery(sql);
1894 void CVideoDatabase::AddLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1896 for (const auto &i : values)
1898 if (!i.empty())
1900 int idValue = AddToTable(field, field + "_id", "name", i);
1901 if (idValue > -1)
1902 AddToLinkTable(mediaId, mediaType, field, idValue);
1907 void CVideoDatabase::UpdateLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1909 std::string sql = PrepareSQL("DELETE FROM %s_link WHERE media_id=%i AND media_type='%s'", field.c_str(), mediaId, mediaType.c_str());
1910 m_pDS->exec(sql);
1912 AddLinksToItem(mediaId, mediaType, field, values);
1915 void CVideoDatabase::AddActorLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1917 for (const auto &i : values)
1919 if (!i.empty())
1921 int idValue = AddActor(i, "");
1922 if (idValue > -1)
1923 AddToLinkTable(mediaId, mediaType, field, idValue, "actor");
1928 void CVideoDatabase::UpdateActorLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1930 std::string sql = PrepareSQL("DELETE FROM %s_link WHERE media_id=%i AND media_type='%s'", field.c_str(), mediaId, mediaType.c_str());
1931 m_pDS->exec(sql);
1933 AddActorLinksToItem(mediaId, mediaType, field, values);
1936 //****Tags****
1937 void CVideoDatabase::AddTagToItem(int media_id, int tag_id, const std::string &type)
1939 if (type.empty())
1940 return;
1942 AddToLinkTable(media_id, type, "tag", tag_id);
1945 void CVideoDatabase::RemoveTagFromItem(int media_id, int tag_id, const std::string &type)
1947 if (type.empty())
1948 return;
1950 RemoveFromLinkTable(media_id, type, "tag", tag_id);
1953 void CVideoDatabase::RemoveTagsFromItem(int media_id, const std::string &type)
1955 if (type.empty())
1956 return;
1958 m_pDS2->exec(PrepareSQL("DELETE FROM tag_link WHERE media_id=%d AND media_type='%s'", media_id, type.c_str()));
1961 //****Actors****
1962 void CVideoDatabase::AddCast(int mediaId, const char *mediaType, const std::vector< SActorInfo > &cast)
1964 if (cast.empty())
1965 return;
1967 int order = std::max_element(cast.begin(), cast.end())->order;
1968 for (const auto &i : cast)
1970 int idActor = AddActor(i.strName, i.thumbUrl.GetData(), i.thumb);
1971 AddLinkToActor(mediaId, mediaType, idActor, i.strRole, i.order >= 0 ? i.order : ++order);
1975 //********************************************************************************************************************************
1976 bool CVideoDatabase::LoadVideoInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int getDetails /* = VideoDbDetailsAll */)
1978 if (GetMovieInfo(strFilenameAndPath, details))
1979 return true;
1980 if (GetEpisodeInfo(strFilenameAndPath, details))
1981 return true;
1982 if (GetMusicVideoInfo(strFilenameAndPath, details))
1983 return true;
1984 if (GetFileInfo(strFilenameAndPath, details))
1985 return true;
1987 return false;
1990 bool CVideoDatabase::HasMovieInfo(const std::string& strFilenameAndPath)
1994 if (nullptr == m_pDB)
1995 return false;
1996 if (nullptr == m_pDS)
1997 return false;
1998 int idMovie = GetMovieId(strFilenameAndPath);
1999 return (idMovie > 0); // index of zero is also invalid
2001 catch (...)
2003 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2005 return false;
2008 bool CVideoDatabase::HasTvShowInfo(const std::string& strPath)
2012 if (nullptr == m_pDB)
2013 return false;
2014 if (nullptr == m_pDS)
2015 return false;
2016 int idTvShow = GetTvShowId(strPath);
2017 return (idTvShow > 0); // index of zero is also invalid
2019 catch (...)
2021 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
2023 return false;
2026 bool CVideoDatabase::HasEpisodeInfo(const std::string& strFilenameAndPath)
2030 if (nullptr == m_pDB)
2031 return false;
2032 if (nullptr == m_pDS)
2033 return false;
2034 int idEpisode = GetEpisodeId(strFilenameAndPath);
2035 return (idEpisode > 0); // index of zero is also invalid
2037 catch (...)
2039 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2041 return false;
2044 bool CVideoDatabase::HasMusicVideoInfo(const std::string& strFilenameAndPath)
2048 if (nullptr == m_pDB)
2049 return false;
2050 if (nullptr == m_pDS)
2051 return false;
2052 int idMVideo = GetMusicVideoId(strFilenameAndPath);
2053 return (idMVideo > 0); // index of zero is also invalid
2055 catch (...)
2057 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2059 return false;
2062 void CVideoDatabase::DeleteDetailsForTvShow(int idTvShow)
2066 if (nullptr == m_pDB)
2067 return;
2068 if (nullptr == m_pDS)
2069 return;
2071 std::string strSQL;
2072 strSQL=PrepareSQL("DELETE from genre_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2073 m_pDS->exec(strSQL);
2075 strSQL=PrepareSQL("DELETE FROM actor_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2076 m_pDS->exec(strSQL);
2078 strSQL=PrepareSQL("DELETE FROM director_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2079 m_pDS->exec(strSQL);
2081 strSQL=PrepareSQL("DELETE FROM studio_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2082 m_pDS->exec(strSQL);
2084 strSQL = PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2085 m_pDS->exec(strSQL);
2087 strSQL = PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='tvshow'", idTvShow);
2088 m_pDS->exec(strSQL);
2090 // remove all info other than the id
2091 // we do this due to the way we have the link between the file + movie tables.
2093 std::vector<std::string> ids;
2094 for (int iType = VIDEODB_ID_TV_MIN + 1; iType < VIDEODB_ID_TV_MAX; iType++)
2095 ids.emplace_back(StringUtils::Format("c{:02}=NULL", iType));
2097 strSQL = "update tvshow set ";
2098 strSQL += StringUtils::Join(ids, ", ");
2099 strSQL += PrepareSQL(" where idShow=%i", idTvShow);
2100 m_pDS->exec(strSQL);
2102 catch (...)
2104 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idTvShow);
2108 //********************************************************************************************************************************
2109 void CVideoDatabase::GetMoviesByActor(const std::string& name, CFileItemList& items)
2111 Filter filter;
2112 filter.join = "LEFT JOIN actor_link ON actor_link.media_id=movie_view.idMovie AND actor_link.media_type='movie' "
2113 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2114 "LEFT JOIN director_link ON director_link.media_id=movie_view.idMovie AND director_link.media_type='movie' "
2115 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2116 filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
2117 filter.group = "movie_view.idMovie";
2118 GetMoviesByWhere("videodb://movies/titles/", filter, items);
2121 void CVideoDatabase::GetTvShowsByActor(const std::string& name, CFileItemList& items)
2123 Filter filter;
2124 filter.join = "LEFT JOIN actor_link ON actor_link.media_id=tvshow_view.idShow AND actor_link.media_type='tvshow' "
2125 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2126 "LEFT JOIN director_link ON director_link.media_id=tvshow_view.idShow AND director_link.media_type='tvshow' "
2127 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2128 filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
2129 GetTvShowsByWhere("videodb://tvshows/titles/", filter, items);
2132 void CVideoDatabase::GetEpisodesByActor(const std::string& name, CFileItemList& items)
2134 Filter filter;
2135 filter.join = "LEFT JOIN actor_link ON actor_link.media_id=episode_view.idEpisode AND actor_link.media_type='episode' "
2136 "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2137 "LEFT JOIN director_link ON director_link.media_id=episode_view.idEpisode AND director_link.media_type='episode' "
2138 "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2139 filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
2140 filter.group = "episode_view.idEpisode";
2141 GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
2144 void CVideoDatabase::GetMusicVideosByArtist(const std::string& strArtist, CFileItemList& items)
2148 items.Clear();
2149 if (nullptr == m_pDB)
2150 return;
2151 if (nullptr == m_pDS)
2152 return;
2154 std::string strSQL;
2155 if (strArtist.empty()) //! @todo SMARTPLAYLISTS what is this here for???
2156 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");
2157 else // same artist OR same director
2158 strSQL = PrepareSQL(
2159 "select * from musicvideo_view join actor_link on "
2160 "actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo' "
2161 "join actor on actor.actor_id=actor_link.actor_id where actor.name='%s' OR "
2162 "musicvideo_view.c05='%s' GROUP BY idMVideo",
2163 strArtist.c_str(), strArtist.c_str());
2164 m_pDS->query( strSQL );
2166 while (!m_pDS->eof())
2168 CVideoInfoTag tag = GetDetailsForMusicVideo(m_pDS);
2169 CFileItemPtr pItem(new CFileItem(tag));
2170 pItem->SetLabel(StringUtils::Join(tag.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator));
2171 items.Add(pItem);
2172 m_pDS->next();
2174 m_pDS->close();
2176 catch (...)
2178 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strArtist);
2182 //********************************************************************************************************************************
2183 bool CVideoDatabase::GetMovieInfo(const std::string& strFilenameAndPath,
2184 CVideoInfoTag& details,
2185 int idMovie /* = -1 */,
2186 int idVersion /* = -1 */,
2187 int getDetails /* = VideoDbDetailsAll */)
2191 if (m_pDB == nullptr || m_pDS == nullptr)
2192 return false;
2194 if (idMovie < 0)
2195 idMovie = GetMovieId(strFilenameAndPath);
2197 if (idMovie < 0)
2198 return false;
2200 std::string sql;
2201 if (idVersion >= 0)
2203 //! @todo get rid of "videos with versions as folder" hack!
2204 if (idVersion != VIDEO_VERSION_ID_ALL)
2205 sql = PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND videoVersionTypeId = %i",
2206 idMovie, idVersion);
2208 else if (!strFilenameAndPath.empty())
2210 const int idFile{GetFileId(strFilenameAndPath)};
2211 if (idFile != -1)
2212 sql = PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND videoVersionIdFile = %i",
2213 idMovie, idFile);
2216 if (sql.empty())
2217 sql = PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND isDefaultVersion = 1",
2218 idMovie);
2220 if (!m_pDS->query(sql))
2221 return false;
2223 details = GetDetailsForMovie(m_pDS, getDetails);
2224 return !details.IsEmpty();
2226 catch (...)
2228 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2230 return false;
2233 std::string CVideoDatabase::GetMovieTitle(int idMovie)
2235 if (!m_pDB || !m_pDS)
2236 return "";
2238 m_pDS->query(PrepareSQL("SELECT c%02d from movie where idMovie = %i", VIDEODB_ID_TITLE, idMovie));
2240 if (!m_pDS->eof())
2241 return m_pDS->fv(0).get_asString();
2242 else
2243 return "";
2246 //********************************************************************************************************************************
2247 bool CVideoDatabase::GetTvShowInfo(const std::string& strPath, CVideoInfoTag& details, int idTvShow /* = -1 */, CFileItem *item /* = NULL */, int getDetails /* = VideoDbDetailsAll */)
2251 if (m_pDB == nullptr || m_pDS == nullptr)
2252 return false;
2254 if (idTvShow < 0)
2255 idTvShow = GetTvShowId(strPath);
2256 if (idTvShow < 0) return false;
2258 std::string sql = PrepareSQL("SELECT * FROM tvshow_view WHERE idShow=%i GROUP BY idShow", idTvShow);
2259 if (!m_pDS->query(sql))
2260 return false;
2261 details = GetDetailsForTvShow(m_pDS, getDetails, item);
2262 return !details.IsEmpty();
2264 catch (...)
2266 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
2268 return false;
2271 bool CVideoDatabase::GetSeasonInfo(const std::string& path,
2272 int season,
2273 CVideoInfoTag& details,
2274 CFileItem* item)
2278 const std::string sql = PrepareSQL("strPath='%s' AND season=%i", path.c_str(), season);
2279 const std::string id = GetSingleValue("season_view", "idSeason", sql);
2280 if (id.empty())
2282 CLog::LogF(LOGERROR, "Failed to obtain seasonId for path={}, season={}", path, season);
2284 else
2286 const int idSeason = static_cast<int>(std::strtol(id.c_str(), nullptr, 10));
2287 return GetSeasonInfo(idSeason, details, item);
2290 catch (...)
2292 CLog::LogF(LOGERROR, "Exception while trying to to obtain seasonId for path={}, season={}",
2293 path, season);
2295 return false;
2298 bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, bool allDetails /* = true */)
2300 return GetSeasonInfo(idSeason, details, allDetails, nullptr);
2303 bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, CFileItem* item)
2305 return GetSeasonInfo(idSeason, details, true, item);
2308 bool CVideoDatabase::GetSeasonInfo(int idSeason,
2309 CVideoInfoTag& details,
2310 bool allDetails,
2311 CFileItem* item)
2313 if (idSeason < 0)
2314 return false;
2318 if (!m_pDB || !m_pDS)
2319 return false;
2321 std::string sql = PrepareSQL("SELECT idSeason, idShow, season, name, userrating FROM seasons WHERE idSeason=%i", idSeason);
2322 if (!m_pDS->query(sql))
2323 return false;
2325 if (m_pDS->num_rows() != 1)
2326 return false;
2328 if (allDetails)
2330 int idShow = m_pDS->fv(1).get_asInt();
2332 // close the current result because we are going to query the season view for all details
2333 m_pDS->close();
2335 if (idShow < 0)
2336 return false;
2338 CFileItemList seasons;
2339 if (!GetSeasonsNav(StringUtils::Format("videodb://tvshows/titles/{}/", idShow), seasons, -1,
2340 -1, -1, -1, idShow, false) ||
2341 seasons.Size() <= 0)
2342 return false;
2344 for (int index = 0; index < seasons.Size(); index++)
2346 const CFileItemPtr season = seasons.Get(index);
2347 if (season->HasVideoInfoTag() && season->GetVideoInfoTag()->m_iDbId == idSeason && season->GetVideoInfoTag()->m_iIdShow == idShow)
2349 details = *season->GetVideoInfoTag();
2350 if (item)
2351 *item = *season;
2352 return true;
2356 return false;
2359 const int season = m_pDS->fv(2).get_asInt();
2360 std::string name = m_pDS->fv(3).get_asString();
2362 if (name.empty())
2364 if (season == 0)
2365 name = g_localizeStrings.Get(20381);
2366 else
2367 name = StringUtils::Format(g_localizeStrings.Get(20358), season);
2370 details.m_strTitle = name;
2371 if (!name.empty())
2372 details.m_strSortTitle = name;
2373 details.m_iSeason = season;
2374 details.m_iDbId = m_pDS->fv(0).get_asInt();
2375 details.m_iIdSeason = details.m_iDbId;
2376 details.m_type = MediaTypeSeason;
2377 details.m_iUserRating = m_pDS->fv(4).get_asInt();
2378 details.m_iIdShow = m_pDS->fv(1).get_asInt();
2380 return true;
2382 catch (...)
2384 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSeason);
2386 return false;
2389 bool CVideoDatabase::GetEpisodeBasicInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idEpisode /* = -1 */)
2393 if (idEpisode < 0)
2394 idEpisode = GetEpisodeId(strFilenameAndPath);
2396 if (idEpisode < 0)
2397 return false;
2399 std::string sql = PrepareSQL("select * from episode where idEpisode=%i",idEpisode);
2400 if (!m_pDS->query(sql))
2401 return false;
2402 details = GetBasicDetailsForEpisode(m_pDS);
2403 return !details.IsEmpty();
2405 catch (...)
2407 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2409 return false;
2412 bool CVideoDatabase::GetEpisodeInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idEpisode /* = -1 */, int getDetails /* = VideoDbDetailsAll */)
2416 if (m_pDB == nullptr || m_pDS == nullptr)
2417 return false;
2419 if (idEpisode < 0)
2420 idEpisode = GetEpisodeId(strFilenameAndPath, details.m_iEpisode, details.m_iSeason);
2421 if (idEpisode < 0) return false;
2423 std::string sql = PrepareSQL("select * from episode_view where idEpisode=%i",idEpisode);
2424 if (!m_pDS->query(sql))
2425 return false;
2426 details = GetDetailsForEpisode(m_pDS, getDetails);
2427 return !details.IsEmpty();
2429 catch (...)
2431 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2433 return false;
2436 bool CVideoDatabase::GetMusicVideoInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idMVideo /* = -1 */, int getDetails /* = VideoDbDetailsAll */)
2440 if (m_pDB == nullptr || m_pDS == nullptr)
2441 return false;
2443 if (idMVideo < 0)
2444 idMVideo = GetMusicVideoId(strFilenameAndPath);
2445 if (idMVideo < 0) return false;
2447 std::string sql = PrepareSQL("select * from musicvideo_view where idMVideo=%i", idMVideo);
2448 if (!m_pDS->query(sql))
2449 return false;
2450 details = GetDetailsForMusicVideo(m_pDS, getDetails);
2451 return !details.IsEmpty();
2453 catch (...)
2455 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2457 return false;
2460 bool CVideoDatabase::GetSetInfo(int idSet, CVideoInfoTag& details, CFileItem* item /* = nullptr */)
2464 if (idSet < 0)
2465 return false;
2467 Filter filter;
2468 filter.where = PrepareSQL("sets.idSet=%d", idSet);
2469 CFileItemList items;
2470 if (!GetSetsByWhere("videodb://movies/sets/", filter, items) ||
2471 items.Size() != 1 ||
2472 !items[0]->HasVideoInfoTag())
2473 return false;
2475 details = *(items[0]->GetVideoInfoTag());
2476 if (item)
2477 *item = *items[0];
2478 return !details.IsEmpty();
2480 catch (...)
2482 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSet);
2484 return false;
2487 bool CVideoDatabase::GetFileInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idFile /* = -1 */)
2491 if (idFile < 0)
2492 idFile = GetFileId(strFilenameAndPath);
2493 if (idFile < 0)
2494 return false;
2496 std::string sql = PrepareSQL("SELECT * FROM files "
2497 "JOIN path ON path.idPath = files.idPath "
2498 "LEFT JOIN bookmark ON bookmark.idFile = files.idFile AND bookmark.type = %i "
2499 "WHERE files.idFile = %i", CBookmark::RESUME, idFile);
2500 if (!m_pDS->query(sql))
2501 return false;
2503 details.m_iFileId = m_pDS->fv("files.idFile").get_asInt();
2504 details.m_strPath = m_pDS->fv("path.strPath").get_asString();
2505 std::string strFileName = m_pDS->fv("files.strFilename").get_asString();
2506 ConstructPath(details.m_strFileNameAndPath, details.m_strPath, strFileName);
2507 details.SetPlayCount(std::max(details.GetPlayCount(), m_pDS->fv("files.playCount").get_asInt()));
2508 if (!details.m_lastPlayed.IsValid())
2509 details.m_lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
2510 if (!details.m_dateAdded.IsValid())
2511 details.m_dateAdded.SetFromDBDateTime(m_pDS->fv("files.dateAdded").get_asString());
2512 if (!details.GetResumePoint().IsSet() ||
2513 (!details.GetResumePoint().HasSavedPlayerState() &&
2514 !m_pDS->fv("bookmark.playerState").get_asString().empty()))
2516 details.SetResumePoint(m_pDS->fv("bookmark.timeInSeconds").get_asDouble(),
2517 m_pDS->fv("bookmark.totalTimeInSeconds").get_asDouble(),
2518 m_pDS->fv("bookmark.playerState").get_asString());
2521 // get streamdetails
2522 GetStreamDetails(details);
2524 return !details.IsEmpty();
2526 catch (...)
2528 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
2530 return false;
2533 std::string CVideoDatabase::GetValueString(const CVideoInfoTag &details, int min, int max, const SDbTableOffsets *offsets) const
2535 std::vector<std::string> conditions;
2536 for (int i = min + 1; i < max; ++i)
2538 switch (offsets[i].type)
2540 case VIDEODB_TYPE_STRING:
2541 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const std::string*)(((const char*)&details)+offsets[i].offset))->c_str()));
2542 break;
2543 case VIDEODB_TYPE_INT:
2544 conditions.emplace_back(PrepareSQL("c%02d='%i'", i, *(const int*)(((const char*)&details)+offsets[i].offset)));
2545 break;
2546 case VIDEODB_TYPE_COUNT:
2548 int value = *(const int*)(((const char*)&details)+offsets[i].offset);
2549 if (value)
2550 conditions.emplace_back(PrepareSQL("c%02d=%i", i, value));
2551 else
2552 conditions.emplace_back(PrepareSQL("c%02d=NULL", i));
2554 break;
2555 case VIDEODB_TYPE_BOOL:
2556 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, *(const bool*)(((const char*)&details)+offsets[i].offset)?"true":"false"));
2557 break;
2558 case VIDEODB_TYPE_FLOAT:
2559 conditions.emplace_back(PrepareSQL(
2560 "c%02d='%f'", i, *(const double*)(((const char*)&details) + offsets[i].offset)));
2561 break;
2562 case VIDEODB_TYPE_STRINGARRAY:
2563 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, StringUtils::Join(*((const std::vector<std::string>*)(((const char*)&details)+offsets[i].offset)),
2564 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator).c_str()));
2565 break;
2566 case VIDEODB_TYPE_DATE:
2567 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const CDateTime*)(((const char*)&details)+offsets[i].offset))->GetAsDBDate().c_str()));
2568 break;
2569 case VIDEODB_TYPE_DATETIME:
2570 conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const CDateTime*)(((const char*)&details)+offsets[i].offset))->GetAsDBDateTime().c_str()));
2571 break;
2572 case VIDEODB_TYPE_UNUSED: // Skip the unused field to avoid populating unused data
2573 continue;
2576 return StringUtils::Join(conditions, ",");
2579 //********************************************************************************************************************************
2580 int CVideoDatabase::SetDetailsForItem(CVideoInfoTag& details, const std::map<std::string, std::string> &artwork)
2582 return SetDetailsForItem(details.m_iDbId, details.m_type, details, artwork);
2585 int CVideoDatabase::SetDetailsForItem(int id, const MediaType& mediaType, CVideoInfoTag& details, const std::map<std::string, std::string> &artwork)
2587 if (mediaType == MediaTypeNone)
2588 return -1;
2590 if (mediaType == MediaTypeMovie)
2591 return SetDetailsForMovie(details, artwork, id);
2592 else if (mediaType == MediaTypeVideoCollection)
2593 return SetDetailsForMovieSet(details, artwork, id);
2594 else if (mediaType == MediaTypeTvShow)
2596 std::map<int, std::map<std::string, std::string> > seasonArtwork;
2597 if (!UpdateDetailsForTvShow(id, details, artwork, seasonArtwork))
2598 return -1;
2600 return id;
2602 else if (mediaType == MediaTypeSeason)
2603 return SetDetailsForSeason(details, artwork, details.m_iIdShow, id);
2604 else if (mediaType == MediaTypeEpisode)
2605 return SetDetailsForEpisode(details, artwork, details.m_iIdShow, id);
2606 else if (mediaType == MediaTypeMusicVideo)
2607 return SetDetailsForMusicVideo(details, artwork, id);
2609 return -1;
2612 int CVideoDatabase::SetDetailsForMovie(CVideoInfoTag& details,
2613 const std::map<std::string, std::string>& artwork,
2614 int idMovie /* = -1 */)
2616 const auto filePath = details.GetPath();
2620 BeginTransaction();
2622 if (idMovie < 0)
2623 idMovie = GetMovieId(filePath);
2625 if (idMovie > -1)
2626 DeleteMovie(idMovie, true); // true to keep the table entry
2627 else
2629 // only add a new movie if we don't already have a valid idMovie
2630 // (DeleteMovie is called with bKeepId == true so the movie won't
2631 // be removed from the movie table)
2632 idMovie = AddNewMovie(details);
2633 if (idMovie < 0)
2635 RollbackTransaction();
2636 return idMovie;
2640 // update dateadded if it's set
2641 if (details.m_dateAdded.IsValid())
2642 UpdateFileDateAdded(details);
2644 AddCast(idMovie, "movie", details.m_cast);
2645 AddLinksToItem(idMovie, MediaTypeMovie, "genre", details.m_genre);
2646 AddLinksToItem(idMovie, MediaTypeMovie, "studio", details.m_studio);
2647 AddLinksToItem(idMovie, MediaTypeMovie, "country", details.m_country);
2648 AddLinksToItem(idMovie, MediaTypeMovie, "tag", details.m_tags);
2649 AddActorLinksToItem(idMovie, MediaTypeMovie, "director", details.m_director);
2650 AddActorLinksToItem(idMovie, MediaTypeMovie, "writer", details.m_writingCredits);
2652 // add ratingsu
2653 details.m_iIdRating = AddRatings(idMovie, MediaTypeMovie, details.m_ratings, details.GetDefaultRating());
2655 // add unique ids
2656 details.m_iIdUniqueID = AddUniqueIDs(idMovie, MediaTypeMovie, details);
2658 // add set...
2659 int idSet = -1;
2660 if (!details.m_set.title.empty())
2662 idSet = AddSet(details.m_set.title, details.m_set.overview, details.GetUpdateSetOverview());
2663 // add art if not available
2664 if (!HasArtForItem(idSet, MediaTypeVideoCollection))
2666 for (const auto &it : artwork)
2668 if (StringUtils::StartsWith(it.first, "set."))
2669 SetArtForItem(idSet, MediaTypeVideoCollection, it.first.substr(4), it.second);
2674 if (details.HasStreamDetails())
2675 SetStreamDetailsForFileId(details.m_streamDetails, GetAndFillFileId(details));
2677 SetArtForItem(idMovie, MediaTypeMovie, artwork);
2679 if (!details.HasUniqueID() && details.HasYear())
2680 { // query DB for any movies matching online id and year
2681 std::string strSQL = PrepareSQL("SELECT files.playCount, files.lastPlayed "
2682 "FROM movie "
2683 " INNER JOIN files "
2684 " ON files.idFile=movie.idFile "
2685 " JOIN uniqueid "
2686 " ON movie.idMovie=uniqueid.media_id AND uniqueid.media_type='movie' AND uniqueid.value='%s'"
2687 "WHERE movie.premiered LIKE '%i%%' AND movie.idMovie!=%i AND files.playCount > 0",
2688 details.GetUniqueID().c_str(), details.GetYear(), idMovie);
2689 m_pDS->query(strSQL);
2691 if (!m_pDS->eof())
2693 int playCount = m_pDS->fv("files.playCount").get_asInt();
2695 CDateTime lastPlayed;
2696 lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
2698 // update with playCount and lastPlayed
2699 strSQL =
2700 PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", playCount,
2701 lastPlayed.GetAsDBDateTime().c_str(), GetAndFillFileId(details));
2702 m_pDS->exec(strSQL);
2705 m_pDS->close();
2707 // update our movie table (we know it was added already above)
2708 // and insert the new row
2709 std::string sql = "UPDATE movie SET " + GetValueString(details, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets);
2710 if (idSet > 0)
2711 sql += PrepareSQL(", idSet = %i", idSet);
2712 else
2713 sql += ", idSet = NULL";
2714 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2715 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2716 else
2717 sql += ", userrating = NULL";
2718 if (details.HasPremiered())
2719 sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
2720 else
2721 sql += PrepareSQL(", premiered = '%i'", details.GetYear());
2722 sql += PrepareSQL(" where idMovie=%i", idMovie);
2723 m_pDS->exec(sql);
2724 CommitTransaction();
2726 return idMovie;
2728 catch (...)
2730 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
2732 RollbackTransaction();
2733 return -1;
2736 int CVideoDatabase::UpdateDetailsForMovie(int idMovie, CVideoInfoTag& details, const std::map<std::string, std::string> &artwork, const std::set<std::string> &updatedDetails)
2738 if (idMovie < 0)
2739 return idMovie;
2743 CLog::Log(LOGINFO, "{}: Starting updates for movie {}", __FUNCTION__, idMovie);
2745 BeginTransaction();
2747 // process the link table updates
2748 if (updatedDetails.find("genre") != updatedDetails.end())
2749 UpdateLinksToItem(idMovie, MediaTypeMovie, "genre", details.m_genre);
2750 if (updatedDetails.find("studio") != updatedDetails.end())
2751 UpdateLinksToItem(idMovie, MediaTypeMovie, "studio", details.m_studio);
2752 if (updatedDetails.find("country") != updatedDetails.end())
2753 UpdateLinksToItem(idMovie, MediaTypeMovie, "country", details.m_country);
2754 if (updatedDetails.find("tag") != updatedDetails.end())
2755 UpdateLinksToItem(idMovie, MediaTypeMovie, "tag", details.m_tags);
2756 if (updatedDetails.find("director") != updatedDetails.end())
2757 UpdateActorLinksToItem(idMovie, MediaTypeMovie, "director", details.m_director);
2758 if (updatedDetails.find("writer") != updatedDetails.end())
2759 UpdateActorLinksToItem(idMovie, MediaTypeMovie, "writer", details.m_writingCredits);
2760 if (updatedDetails.find("art.altered") != updatedDetails.end())
2761 SetArtForItem(idMovie, MediaTypeMovie, artwork);
2762 if (updatedDetails.find("ratings") != updatedDetails.end())
2763 details.m_iIdRating = UpdateRatings(idMovie, MediaTypeMovie, details.m_ratings, details.GetDefaultRating());
2764 if (updatedDetails.find("uniqueid") != updatedDetails.end())
2765 details.m_iIdUniqueID = UpdateUniqueIDs(idMovie, MediaTypeMovie, details);
2766 if (updatedDetails.find("dateadded") != updatedDetails.end() && details.m_dateAdded.IsValid())
2767 UpdateFileDateAdded(details);
2769 // track if the set was updated
2770 int idSet = 0;
2771 if (updatedDetails.find("set") != updatedDetails.end())
2772 { // set
2773 idSet = -1;
2774 if (!details.m_set.title.empty())
2776 idSet = AddSet(details.m_set.title, details.m_set.overview);
2780 if (updatedDetails.find("showlink") != updatedDetails.end())
2782 // remove existing links
2783 std::vector<int> tvShowIds;
2784 GetLinksToTvShow(idMovie, tvShowIds);
2785 for (const auto& idTVShow : tvShowIds)
2786 LinkMovieToTvshow(idMovie, idTVShow, true);
2788 // setup links to shows if the linked shows are in the db
2789 for (const auto& showLink : details.m_showLink)
2791 CFileItemList items;
2792 GetTvShowsByName(showLink, items);
2793 if (!items.IsEmpty())
2794 LinkMovieToTvshow(idMovie, items[0]->GetVideoInfoTag()->m_iDbId, false);
2795 else
2796 CLog::Log(LOGWARNING, "{}: Failed to link movie {} to show {}", __FUNCTION__,
2797 details.m_strTitle, showLink);
2801 // and update the movie table
2802 std::string sql = "UPDATE movie SET " + GetValueString(details, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets);
2803 if (idSet > 0)
2804 sql += PrepareSQL(", idSet = %i", idSet);
2805 else if (idSet < 0)
2806 sql += ", idSet = NULL";
2807 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2808 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2809 else
2810 sql += ", userrating = NULL";
2811 if (details.HasPremiered())
2812 sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
2813 else
2814 sql += PrepareSQL(", premiered = '%i'", details.GetYear());
2815 sql += PrepareSQL(" where idMovie=%i", idMovie);
2816 m_pDS->exec(sql);
2818 CommitTransaction();
2820 CLog::Log(LOGINFO, "{}: Finished updates for movie {}", __FUNCTION__, idMovie);
2822 return idMovie;
2824 catch (...)
2826 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idMovie);
2828 RollbackTransaction();
2829 return -1;
2832 int CVideoDatabase::SetDetailsForMovieSet(const CVideoInfoTag& details, const std::map<std::string, std::string> &artwork, int idSet /* = -1 */)
2834 if (details.m_strTitle.empty())
2835 return -1;
2839 BeginTransaction();
2840 if (idSet < 0)
2842 idSet = AddSet(details.m_strTitle, details.m_strPlot);
2843 if (idSet < 0)
2845 RollbackTransaction();
2846 return -1;
2850 SetArtForItem(idSet, MediaTypeVideoCollection, artwork);
2852 // and insert the new row
2853 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);
2854 m_pDS->exec(sql);
2855 CommitTransaction();
2857 return idSet;
2859 catch (...)
2861 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSet);
2863 RollbackTransaction();
2864 return -1;
2867 int CVideoDatabase::GetMatchingTvShow(const CVideoInfoTag &details)
2869 // first try matching on uniqueid, then on title + year
2870 int id = -1;
2871 if (!details.HasUniqueID())
2872 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()));
2873 if (id < 0)
2874 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()));
2875 return id;
2878 int CVideoDatabase::SetDetailsForTvShow(const std::vector<std::pair<std::string, std::string> > &paths,
2879 CVideoInfoTag& details, const std::map<std::string, std::string> &artwork,
2880 const std::map<int, std::map<std::string, std::string> > &seasonArt, int idTvShow /*= -1 */)
2884 The steps are as follows.
2885 1. Check if the tvshow is found on any of the given paths. If found, we have the show id.
2886 2. Search for a matching show. If found, we have the show id.
2887 3. If we don't have the id, add a new show.
2888 4. Add the paths to the show.
2889 5. Add details for the show.
2892 if (idTvShow < 0)
2894 for (const auto &i : paths)
2896 idTvShow = GetTvShowId(i.first);
2897 if (idTvShow > -1)
2898 break;
2901 if (idTvShow < 0)
2902 idTvShow = GetMatchingTvShow(details);
2903 if (idTvShow < 0)
2905 idTvShow = AddTvShow();
2906 if (idTvShow < 0)
2907 return -1;
2910 // add any paths to the tvshow
2911 for (const auto &i : paths)
2912 AddPathToTvShow(idTvShow, i.first, i.second, details.m_dateAdded);
2914 UpdateDetailsForTvShow(idTvShow, details, artwork, seasonArt);
2916 return idTvShow;
2919 bool CVideoDatabase::UpdateDetailsForTvShow(int idTvShow, CVideoInfoTag &details,
2920 const std::map<std::string, std::string> &artwork, const std::map<int, std::map<std::string, std::string>> &seasonArt)
2922 BeginTransaction();
2924 DeleteDetailsForTvShow(idTvShow);
2926 AddCast(idTvShow, "tvshow", details.m_cast);
2927 AddLinksToItem(idTvShow, MediaTypeTvShow, "genre", details.m_genre);
2928 AddLinksToItem(idTvShow, MediaTypeTvShow, "studio", details.m_studio);
2929 AddLinksToItem(idTvShow, MediaTypeTvShow, "tag", details.m_tags);
2930 AddActorLinksToItem(idTvShow, MediaTypeTvShow, "director", details.m_director);
2932 // add ratings
2933 details.m_iIdRating = AddRatings(idTvShow, MediaTypeTvShow, details.m_ratings, details.GetDefaultRating());
2935 // add unique ids
2936 details.m_iIdUniqueID = AddUniqueIDs(idTvShow, MediaTypeTvShow, details);
2938 // add "all seasons" - the rest are added in SetDetailsForEpisode
2939 AddSeason(idTvShow, -1);
2941 // add any named seasons
2942 for (const auto& namedSeason : details.m_namedSeasons)
2944 // make sure the named season exists
2945 int seasonId = AddSeason(idTvShow, namedSeason.first, namedSeason.second);
2947 // get any existing details for the named season
2948 CVideoInfoTag season;
2949 if (!GetSeasonInfo(seasonId, season, false) || season.m_strSortTitle == namedSeason.second)
2950 continue;
2952 season.SetSortTitle(namedSeason.second);
2953 SetDetailsForSeason(season, std::map<std::string, std::string>(), idTvShow, seasonId);
2956 SetArtForItem(idTvShow, MediaTypeTvShow, artwork);
2957 for (const auto &i : seasonArt)
2959 int idSeason = AddSeason(idTvShow, i.first);
2960 if (idSeason > -1)
2961 SetArtForItem(idSeason, MediaTypeSeason, i.second);
2964 // and insert the new row
2965 std::string sql = "UPDATE tvshow SET " + GetValueString(details, VIDEODB_ID_TV_MIN, VIDEODB_ID_TV_MAX, DbTvShowOffsets);
2966 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2967 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2968 else
2969 sql += ", userrating = NULL";
2970 if (details.GetDuration() > 0)
2971 sql += PrepareSQL(", duration = %i", details.GetDuration());
2972 else
2973 sql += ", duration = NULL";
2974 sql += PrepareSQL(" WHERE idShow=%i", idTvShow);
2975 if (ExecuteQuery(sql))
2977 CommitTransaction();
2978 return true;
2980 RollbackTransaction();
2981 return false;
2984 int CVideoDatabase::SetDetailsForSeason(const CVideoInfoTag& details, const std::map<std::string,
2985 std::string> &artwork, int idShow, int idSeason /* = -1 */)
2987 if (idShow < 0 || details.m_iSeason < -1)
2988 return -1;
2992 BeginTransaction();
2993 if (idSeason < 0)
2995 idSeason = AddSeason(idShow, details.m_iSeason);
2996 if (idSeason < 0)
2998 RollbackTransaction();
2999 return -1;
3003 SetArtForItem(idSeason, MediaTypeSeason, artwork);
3005 // and insert the new row
3006 std::string sql = PrepareSQL("UPDATE seasons SET season=%i", details.m_iSeason);
3007 if (!details.m_strSortTitle.empty())
3008 sql += PrepareSQL(", name='%s'", details.m_strSortTitle.c_str());
3009 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
3010 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
3011 else
3012 sql += ", userrating = NULL";
3013 sql += PrepareSQL(" WHERE idSeason=%i", idSeason);
3014 m_pDS->exec(sql.c_str());
3015 CommitTransaction();
3017 return idSeason;
3019 catch (...)
3021 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSeason);
3023 RollbackTransaction();
3024 return -1;
3027 bool CVideoDatabase::SetFileForEpisode(const std::string& fileAndPath, int idEpisode, int idFile)
3031 BeginTransaction();
3032 std::string sql = PrepareSQL("UPDATE episode SET c18='%s', idFile=%i WHERE idEpisode=%i",
3033 fileAndPath.c_str(), idFile, idEpisode);
3034 m_pDS->exec(sql);
3035 CommitTransaction();
3037 return true;
3039 catch (...)
3041 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idEpisode);
3043 RollbackTransaction();
3044 return false;
3047 bool CVideoDatabase::SetFileForMovie(const std::string& fileAndPath, int idMovie, int idFile)
3051 BeginTransaction();
3052 std::string sql = PrepareSQL("UPDATE movie SET c22='%s', idFile=%i WHERE idMovie=%i",
3053 fileAndPath.c_str(), idFile, idMovie);
3054 m_pDS->exec(sql);
3055 sql = PrepareSQL("UPDATE videoversion SET idFile=%i WHERE idMedia=%i AND media_type='movie'",
3056 idFile, idMovie);
3057 m_pDS->exec(sql);
3058 CommitTransaction();
3060 return true;
3062 catch (...)
3064 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idMovie);
3066 RollbackTransaction();
3067 return false;
3070 int CVideoDatabase::SetDetailsForEpisode(CVideoInfoTag& details,
3071 const std::map<std::string, std::string>& artwork,
3072 int idShow,
3073 int idEpisode /* = -1 */)
3075 const auto filePath = details.GetPath();
3079 BeginTransaction();
3080 if (idEpisode < 0)
3081 idEpisode = GetEpisodeId(filePath);
3083 if (idEpisode > 0)
3084 DeleteEpisode(idEpisode, true); // true to keep the table entry
3085 else
3087 // only add a new episode if we don't already have a valid idEpisode
3088 // (DeleteEpisode is called with bKeepId == true so the episode won't
3089 // be removed from the episode table)
3090 idEpisode = AddNewEpisode(idShow, details);
3091 if (idEpisode < 0)
3093 RollbackTransaction();
3094 return -1;
3098 // update dateadded if it's set
3099 if (details.m_dateAdded.IsValid())
3100 UpdateFileDateAdded(details);
3102 AddCast(idEpisode, "episode", details.m_cast);
3103 AddActorLinksToItem(idEpisode, MediaTypeEpisode, "director", details.m_director);
3104 AddActorLinksToItem(idEpisode, MediaTypeEpisode, "writer", details.m_writingCredits);
3106 // add ratings
3107 details.m_iIdRating = AddRatings(idEpisode, MediaTypeEpisode, details.m_ratings, details.GetDefaultRating());
3109 // add unique ids
3110 details.m_iIdUniqueID = AddUniqueIDs(idEpisode, MediaTypeEpisode, details);
3112 if (details.HasStreamDetails())
3113 SetStreamDetailsForFileId(details.m_streamDetails, GetAndFillFileId(details));
3115 // ensure we have this season already added
3116 int idSeason = AddSeason(idShow, details.m_iSeason);
3118 SetArtForItem(idEpisode, MediaTypeEpisode, artwork);
3120 if (details.m_iEpisode != -1 && details.m_iSeason != -1)
3121 { // query DB for any episodes matching idShow, Season and Episode
3122 std::string strSQL = PrepareSQL("SELECT files.playCount, files.lastPlayed "
3123 "FROM episode INNER JOIN files ON files.idFile=episode.idFile "
3124 "WHERE episode.c%02d=%i AND episode.c%02d=%i AND episode.idShow=%i "
3125 "AND episode.idEpisode!=%i AND files.playCount > 0",
3126 VIDEODB_ID_EPISODE_SEASON, details.m_iSeason, VIDEODB_ID_EPISODE_EPISODE,
3127 details.m_iEpisode, idShow, idEpisode);
3128 m_pDS->query(strSQL);
3130 if (!m_pDS->eof())
3132 int playCount = m_pDS->fv("files.playCount").get_asInt();
3134 CDateTime lastPlayed;
3135 lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
3137 // update with playCount and lastPlayed
3138 strSQL =
3139 PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", playCount,
3140 lastPlayed.GetAsDBDateTime().c_str(), GetAndFillFileId(details));
3141 m_pDS->exec(strSQL);
3144 m_pDS->close();
3146 // and insert the new row
3147 std::string sql = "UPDATE episode SET " + GetValueString(details, VIDEODB_ID_EPISODE_MIN, VIDEODB_ID_EPISODE_MAX, DbEpisodeOffsets);
3148 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
3149 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
3150 else
3151 sql += ", userrating = NULL";
3152 sql += PrepareSQL(", idSeason = %i", idSeason);
3153 sql += PrepareSQL(" where idEpisode=%i", idEpisode);
3154 m_pDS->exec(sql);
3155 CommitTransaction();
3157 return idEpisode;
3159 catch (...)
3161 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
3163 RollbackTransaction();
3164 return -1;
3167 int CVideoDatabase::GetSeasonId(int showID, int season)
3169 std::string sql = PrepareSQL("idShow=%i AND season=%i", showID, season);
3170 std::string id = GetSingleValue("seasons", "idSeason", sql);
3171 if (id.empty())
3172 return -1;
3173 return strtol(id.c_str(), NULL, 10);
3176 int CVideoDatabase::AddSeason(int showID, int season, const std::string& name /* = "" */)
3178 int seasonId = GetSeasonId(showID, season);
3179 if (seasonId < 0)
3181 if (ExecuteQuery(PrepareSQL("INSERT INTO seasons (idShow, season, name) VALUES(%i, %i, '%s')", showID, season, name.c_str())))
3182 seasonId = (int)m_pDS->lastinsertid();
3184 return seasonId;
3187 int CVideoDatabase::SetDetailsForMusicVideo(CVideoInfoTag& details,
3188 const std::map<std::string, std::string>& artwork,
3189 int idMVideo /* = -1 */)
3191 const auto filePath = details.GetPath();
3195 BeginTransaction();
3197 if (idMVideo < 0)
3198 idMVideo = GetMusicVideoId(filePath);
3200 if (idMVideo > -1)
3201 DeleteMusicVideo(idMVideo, true); // Keep id
3202 else
3204 // only add a new musicvideo if we don't already have a valid idMVideo
3205 // (DeleteMusicVideo is called with bKeepId == true so the musicvideo won't
3206 // be removed from the musicvideo table)
3207 idMVideo = AddNewMusicVideo(details);
3208 if (idMVideo < 0)
3210 RollbackTransaction();
3211 return -1;
3215 // update dateadded if it's set
3216 if (details.m_dateAdded.IsValid())
3217 UpdateFileDateAdded(details);
3219 AddCast(idMVideo, MediaTypeMusicVideo, details.m_cast);
3220 AddActorLinksToItem(idMVideo, MediaTypeMusicVideo, "actor", details.m_artist);
3221 AddActorLinksToItem(idMVideo, MediaTypeMusicVideo, "director", details.m_director);
3222 AddLinksToItem(idMVideo, MediaTypeMusicVideo, "genre", details.m_genre);
3223 AddLinksToItem(idMVideo, MediaTypeMusicVideo, "studio", details.m_studio);
3224 AddLinksToItem(idMVideo, MediaTypeMusicVideo, "tag", details.m_tags);
3226 // add unique ids
3227 details.m_iIdUniqueID = UpdateUniqueIDs(idMVideo, MediaTypeMusicVideo, details);
3229 if (details.HasStreamDetails())
3230 SetStreamDetailsForFileId(details.m_streamDetails, GetAndFillFileId(details));
3232 SetArtForItem(idMVideo, MediaTypeMusicVideo, artwork);
3234 // update our movie table (we know it was added already above)
3235 // and insert the new row
3236 std::string sql = "UPDATE musicvideo SET " + GetValueString(details, VIDEODB_ID_MUSICVIDEO_MIN, VIDEODB_ID_MUSICVIDEO_MAX, DbMusicVideoOffsets);
3237 if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
3238 sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
3239 else
3240 sql += ", userrating = NULL";
3241 if (details.HasPremiered())
3242 sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
3243 else
3244 sql += PrepareSQL(", premiered = '%i'", details.GetYear());
3245 sql += PrepareSQL(" where idMVideo=%i", idMVideo);
3246 m_pDS->exec(sql);
3247 CommitTransaction();
3249 return idMVideo;
3251 catch (...)
3253 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
3255 RollbackTransaction();
3256 return -1;
3259 int CVideoDatabase::SetStreamDetailsForFile(const CStreamDetails& details,
3260 const std::string& strFileNameAndPath)
3262 // AddFile checks to make sure the file isn't already in the DB first
3263 int idFile = AddFile(strFileNameAndPath);
3264 if (idFile < 0)
3265 return -1;
3266 SetStreamDetailsForFileId(details, idFile);
3267 return idFile;
3270 void CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails& details, int idFile)
3272 if (idFile < 0)
3273 return;
3277 m_pDS->exec(PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", idFile));
3279 for (int i=1; i<=details.GetVideoStreamCount(); i++)
3281 m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
3282 "(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth, "
3283 "iVideoHeight, iVideoDuration, strStereoMode, strVideoLanguage, "
3284 "strHdrType)"
3285 "VALUES (%i,%i,'%s',%f,%i,%i,%i,'%s','%s','%s')",
3286 idFile, (int)CStreamDetail::VIDEO, details.GetVideoCodec(i).c_str(),
3287 static_cast<double>(details.GetVideoAspect(i)),
3288 details.GetVideoWidth(i), details.GetVideoHeight(i),
3289 details.GetVideoDuration(i), details.GetStereoMode(i).c_str(),
3290 details.GetVideoLanguage(i).c_str(),
3291 details.GetVideoHdrType(i).c_str()));
3293 for (int i=1; i<=details.GetAudioStreamCount(); i++)
3295 m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
3296 "(idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) "
3297 "VALUES (%i,%i,'%s',%i,'%s')",
3298 idFile, (int)CStreamDetail::AUDIO,
3299 details.GetAudioCodec(i).c_str(), details.GetAudioChannels(i),
3300 details.GetAudioLanguage(i).c_str()));
3302 for (int i=1; i<=details.GetSubtitleStreamCount(); i++)
3304 m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
3305 "(idFile, iStreamType, strSubtitleLanguage) "
3306 "VALUES (%i,%i,'%s')",
3307 idFile, (int)CStreamDetail::SUBTITLE,
3308 details.GetSubtitleLanguage(i).c_str()));
3311 // update the runtime information, if empty
3312 if (details.GetVideoDuration())
3314 std::vector<std::pair<std::string, int> > tables;
3315 tables.emplace_back("movie", VIDEODB_ID_RUNTIME);
3316 tables.emplace_back("episode", VIDEODB_ID_EPISODE_RUNTIME);
3317 tables.emplace_back("musicvideo", VIDEODB_ID_MUSICVIDEO_RUNTIME);
3318 for (const auto &i : tables)
3320 std::string sql = PrepareSQL("update %s set c%02d=%d where idFile=%d and c%02d=''",
3321 i.first.c_str(), i.second, details.GetVideoDuration(), idFile, i.second);
3322 m_pDS->exec(sql);
3326 catch (...)
3328 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idFile);
3332 //********************************************************************************************************************************
3333 void CVideoDatabase::GetFilePathById(int idMovie, std::string& filePath, VideoDbContentType iType)
3337 if (nullptr == m_pDB)
3338 return;
3339 if (nullptr == m_pDS)
3340 return;
3342 if (idMovie < 0) return ;
3344 std::string strSQL;
3345 if (iType == VideoDbContentType::MOVIES)
3346 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 );
3347 if (iType == VideoDbContentType::EPISODES)
3348 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 );
3349 if (iType == VideoDbContentType::TVSHOWS)
3350 strSQL=PrepareSQL("SELECT path.strPath FROM path INNER JOIN tvshowlinkpath ON path.idPath=tvshowlinkpath.idPath WHERE tvshowlinkpath.idShow=%i", idMovie );
3351 if (iType == VideoDbContentType::MUSICVIDEOS)
3352 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 );
3354 m_pDS->query( strSQL );
3355 if (!m_pDS->eof())
3357 if (iType != VideoDbContentType::TVSHOWS)
3359 std::string fileName = m_pDS->fv("files.strFilename").get_asString();
3360 ConstructPath(filePath,m_pDS->fv("path.strPath").get_asString(),fileName);
3362 else
3363 filePath = m_pDS->fv("path.strPath").get_asString();
3365 m_pDS->close();
3367 catch (...)
3369 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
3373 //********************************************************************************************************************************
3374 void CVideoDatabase::GetBookMarksForFile(const std::string& strFilenameAndPath, VECBOOKMARKS& bookmarks, CBookmark::EType type /*= CBookmark::STANDARD*/, bool bAppend, long partNumber)
3378 if (URIUtils::IsDiscImageStack(strFilenameAndPath))
3380 CStackDirectory dir;
3381 CFileItemList fileList;
3382 const CURL pathToUrl(strFilenameAndPath);
3383 dir.GetDirectory(pathToUrl, fileList);
3384 if (!bAppend)
3385 bookmarks.clear();
3386 for (int i = fileList.Size() - 1; i >= 0; i--) // put the bookmarks of the highest part first in the list
3387 GetBookMarksForFile(fileList[i]->GetPath(), bookmarks, type, true, (i+1));
3389 else
3391 int idFile = GetFileId(strFilenameAndPath);
3392 if (idFile < 0) return ;
3393 if (!bAppend)
3394 bookmarks.erase(bookmarks.begin(), bookmarks.end());
3395 if (nullptr == m_pDB)
3396 return;
3397 if (nullptr == m_pDS)
3398 return;
3400 std::string strSQL=PrepareSQL("select * from bookmark where idFile=%i and type=%i order by timeInSeconds", idFile, (int)type);
3401 m_pDS->query( strSQL );
3402 while (!m_pDS->eof())
3404 CBookmark bookmark;
3405 bookmark.timeInSeconds = m_pDS->fv("timeInSeconds").get_asDouble();
3406 bookmark.partNumber = partNumber;
3407 bookmark.totalTimeInSeconds = m_pDS->fv("totalTimeInSeconds").get_asDouble();
3408 bookmark.thumbNailImage = m_pDS->fv("thumbNailImage").get_asString();
3409 bookmark.playerState = m_pDS->fv("playerState").get_asString();
3410 bookmark.player = m_pDS->fv("player").get_asString();
3411 bookmark.type = type;
3412 if (type == CBookmark::EPISODE)
3414 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);
3415 m_pDS2->query(strSQL2);
3416 bookmark.episodeNumber = m_pDS2->fv(0).get_asInt();
3417 bookmark.seasonNumber = m_pDS2->fv(1).get_asInt();
3418 m_pDS2->close();
3420 bookmarks.push_back(bookmark);
3421 m_pDS->next();
3423 //sort(bookmarks.begin(), bookmarks.end(), SortBookmarks);
3424 m_pDS->close();
3427 catch (...)
3429 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3433 bool CVideoDatabase::GetResumeBookMark(const std::string& strFilenameAndPath, CBookmark &bookmark)
3435 VECBOOKMARKS bookmarks;
3436 GetBookMarksForFile(strFilenameAndPath, bookmarks, CBookmark::RESUME);
3437 if (!bookmarks.empty())
3439 bookmark = bookmarks[0];
3440 return true;
3442 return false;
3445 void CVideoDatabase::DeleteResumeBookMark(const CFileItem& item)
3447 if (!m_pDB || !m_pDS)
3448 return;
3450 int fileID = item.GetVideoInfoTag()->m_iFileId;
3451 if (fileID < 0)
3453 fileID = GetFileId(item.GetPath());
3454 if (fileID < 0)
3455 return;
3460 std::string sql = PrepareSQL("delete from bookmark where idFile=%i and type=%i", fileID, CBookmark::RESUME);
3461 m_pDS->exec(sql);
3463 VideoDbContentType iType = static_cast<VideoDbContentType>(item.GetVideoContentType());
3464 std::string content;
3465 switch (iType)
3467 case VideoDbContentType::MOVIES:
3468 content = MediaTypeMovie;
3469 break;
3470 case VideoDbContentType::EPISODES:
3471 content = MediaTypeEpisode;
3472 break;
3473 case VideoDbContentType::TVSHOWS:
3474 content = MediaTypeTvShow;
3475 break;
3476 case VideoDbContentType::MUSICVIDEOS:
3477 content = MediaTypeMusicVideo;
3478 break;
3479 default:
3480 break;
3483 if (!content.empty())
3485 AnnounceUpdate(content, item.GetVideoInfoTag()->m_iDbId);
3489 catch(...)
3491 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__,
3492 item.GetVideoInfoTag()->m_strFileNameAndPath);
3496 void CVideoDatabase::GetEpisodesByFile(const std::string& strFilenameAndPath, std::vector<CVideoInfoTag>& episodes)
3500 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);
3501 m_pDS->query(strSQL);
3502 while (!m_pDS->eof())
3504 episodes.emplace_back(GetDetailsForEpisode(m_pDS));
3505 m_pDS->next();
3507 m_pDS->close();
3509 catch (...)
3511 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3515 //********************************************************************************************************************************
3516 void CVideoDatabase::AddBookMarkToFile(const std::string& strFilenameAndPath, const CBookmark &bookmark, CBookmark::EType type /*= CBookmark::STANDARD*/)
3520 int idFile = AddFile(strFilenameAndPath);
3521 if (idFile < 0)
3522 return;
3523 if (nullptr == m_pDB)
3524 return;
3525 if (nullptr == m_pDS)
3526 return;
3528 std::string strSQL;
3529 int idBookmark=-1;
3530 if (type == CBookmark::RESUME) // get the same resume mark bookmark each time type
3532 strSQL=PrepareSQL("select idBookmark from bookmark where idFile=%i and type=1", idFile);
3534 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
3536 /* get a bookmark within the same time as previous */
3537 double mintime = bookmark.timeInSeconds - 0.5;
3538 double maxtime = bookmark.timeInSeconds + 0.5;
3539 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());
3542 if (type != CBookmark::EPISODE)
3544 // get current id
3545 m_pDS->query( strSQL );
3546 if (m_pDS->num_rows() != 0)
3547 idBookmark = m_pDS->get_field_value("idBookmark").get_asInt();
3548 m_pDS->close();
3550 // update or insert depending if it existed before
3551 if (idBookmark >= 0 )
3552 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);
3553 else
3554 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);
3556 m_pDS->exec(strSQL);
3558 catch (...)
3560 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3564 void CVideoDatabase::ClearBookMarkOfFile(const std::string& strFilenameAndPath, CBookmark& bookmark, CBookmark::EType type /*= CBookmark::STANDARD*/)
3568 int idFile = GetFileId(strFilenameAndPath);
3569 if (idFile < 0) return ;
3570 if (nullptr == m_pDB)
3571 return;
3572 if (nullptr == m_pDS)
3573 return;
3575 /* a little bit ugly, we clear first bookmark that is within one second of given */
3576 /* should be no problem since we never add bookmarks that are closer than that */
3577 double mintime = bookmark.timeInSeconds - 0.5;
3578 double maxtime = bookmark.timeInSeconds + 0.5;
3579 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);
3581 m_pDS->query( strSQL );
3582 if (m_pDS->num_rows() != 0)
3584 int idBookmark = m_pDS->get_field_value("idBookmark").get_asInt();
3585 strSQL=PrepareSQL("delete from bookmark where idBookmark=%i",idBookmark);
3586 m_pDS->exec(strSQL);
3587 if (type == CBookmark::EPISODE)
3589 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);
3590 m_pDS->exec(strSQL);
3594 m_pDS->close();
3596 catch (...)
3598 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strFilenameAndPath);
3602 //********************************************************************************************************************************
3603 void CVideoDatabase::ClearBookMarksOfFile(const std::string& strFilenameAndPath, CBookmark::EType type /*= CBookmark::STANDARD*/)
3605 int idFile = GetFileId(strFilenameAndPath);
3606 if (idFile >= 0)
3607 return ClearBookMarksOfFile(idFile, type);
3610 void CVideoDatabase::ClearBookMarksOfFile(int idFile, CBookmark::EType type /*= CBookmark::STANDARD*/)
3612 if (idFile < 0)
3613 return;
3617 if (nullptr == m_pDB)
3618 return;
3619 if (nullptr == m_pDS)
3620 return;
3622 std::string strSQL=PrepareSQL("delete from bookmark where idFile=%i and type=%i", idFile, (int)type);
3623 m_pDS->exec(strSQL);
3624 if (type == CBookmark::EPISODE)
3626 strSQL=PrepareSQL("update episode set c%02d=-1 where idFile=%i", VIDEODB_ID_EPISODE_BOOKMARK, idFile);
3627 m_pDS->exec(strSQL);
3630 catch (...)
3632 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idFile);
3637 bool CVideoDatabase::GetBookMarkForEpisode(const CVideoInfoTag& tag, CBookmark& bookmark)
3641 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);
3642 m_pDS2->query( strSQL );
3643 if (!m_pDS2->eof())
3645 bookmark.timeInSeconds = m_pDS2->fv("timeInSeconds").get_asDouble();
3646 bookmark.totalTimeInSeconds = m_pDS2->fv("totalTimeInSeconds").get_asDouble();
3647 bookmark.thumbNailImage = m_pDS2->fv("thumbNailImage").get_asString();
3648 bookmark.playerState = m_pDS2->fv("playerState").get_asString();
3649 bookmark.player = m_pDS2->fv("player").get_asString();
3650 bookmark.type = (CBookmark::EType)m_pDS2->fv("type").get_asInt();
3652 else
3654 m_pDS2->close();
3655 return false;
3657 m_pDS2->close();
3659 catch (...)
3661 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
3662 return false;
3664 return true;
3667 void CVideoDatabase::AddBookMarkForEpisode(const CVideoInfoTag& tag, const CBookmark& bookmark)
3671 int idFile = GetFileId(tag.m_strFileNameAndPath);
3672 // delete the current episode for the selected episode number
3673 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);
3674 m_pDS->exec(strSQL);
3676 AddBookMarkToFile(tag.m_strFileNameAndPath, bookmark, CBookmark::EPISODE);
3677 int idBookmark = (int)m_pDS->lastinsertid();
3678 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);
3679 m_pDS->exec(strSQL);
3681 catch (...)
3683 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, tag.m_iDbId);
3687 void CVideoDatabase::DeleteBookMarkForEpisode(const CVideoInfoTag& tag)
3691 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);
3692 m_pDS->exec(strSQL);
3693 strSQL = PrepareSQL("update episode set c%02d=-1 where idEpisode=%i", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
3694 m_pDS->exec(strSQL);
3696 catch (...)
3698 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, tag.m_iDbId);
3702 //********************************************************************************************************************************
3703 void CVideoDatabase::DeleteMovie(int idMovie,
3704 bool bKeepId /* = false */,
3705 DeleteMovieCascadeAction ca /* = ALL_ASSETS */)
3707 if (idMovie < 0)
3708 return;
3712 if (nullptr == m_pDB)
3713 return;
3714 if (nullptr == m_pDS)
3715 return;
3717 BeginTransaction();
3719 int idFile = GetDbId(PrepareSQL("SELECT idFile FROM movie WHERE idMovie=%i", idMovie));
3720 DeleteStreamDetails(idFile);
3722 // keep the movie table entry, linking to tv shows, and bookmarks
3723 // so we can update the data in place
3724 // the ancillary tables are still purged
3725 if (!bKeepId)
3727 const std::string path = GetSingleValue(PrepareSQL(
3728 "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i",
3729 idFile));
3730 if (!path.empty())
3731 InvalidatePathHash(path);
3733 const std::string strSQL = PrepareSQL("delete from movie where idMovie=%i", idMovie);
3734 m_pDS->exec(strSQL);
3736 if (ca == DeleteMovieCascadeAction::ALL_ASSETS)
3738 // The default version of the movie was removed by a delete trigger.
3739 // Clean up the other assets attached to the movie, if any.
3741 // need local dataset due to nested DeleteVideoAsset query
3742 std::unique_ptr<Dataset> pDS{m_pDB->CreateDataset()};
3744 pDS->query(
3745 PrepareSQL("SELECT idFile FROM videoversion WHERE idMedia=%i AND media_type='%s'",
3746 idMovie, MediaTypeMovie));
3748 while (!pDS->eof())
3750 if (!DeleteVideoAsset(pDS->fv(0).get_asInt()))
3752 RollbackTransaction();
3753 pDS->close();
3754 return;
3756 pDS->next();
3758 pDS->close();
3762 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3763 if (!bKeepId)
3764 AnnounceRemove(MediaTypeMovie, idMovie);
3766 CommitTransaction();
3769 catch (...)
3771 CLog::LogF(LOGERROR, "failed");
3772 RollbackTransaction();
3776 void CVideoDatabase::DeleteTvShow(const std::string& strPath)
3778 int idTvShow = GetTvShowId(strPath);
3779 if (idTvShow >= 0)
3780 DeleteTvShow(idTvShow);
3783 void CVideoDatabase::DeleteTvShow(int idTvShow, bool bKeepId /* = false */)
3785 if (idTvShow < 0)
3786 return;
3790 if (nullptr == m_pDB)
3791 return;
3792 if (nullptr == m_pDS)
3793 return;
3795 BeginTransaction();
3797 std::set<int> paths;
3798 GetPathsForTvShow(idTvShow, paths);
3800 std::string strSQL=PrepareSQL("SELECT episode.idEpisode FROM episode WHERE episode.idShow=%i",idTvShow);
3801 m_pDS2->query(strSQL);
3802 while (!m_pDS2->eof())
3804 DeleteEpisode(m_pDS2->fv(0).get_asInt(), bKeepId);
3805 m_pDS2->next();
3808 DeleteDetailsForTvShow(idTvShow);
3810 strSQL=PrepareSQL("delete from seasons where idShow=%i", idTvShow);
3811 m_pDS->exec(strSQL);
3813 // keep tvshow table and movielink table so we can update data in place
3814 if (!bKeepId)
3816 strSQL=PrepareSQL("delete from tvshow where idShow=%i", idTvShow);
3817 m_pDS->exec(strSQL);
3819 for (const auto &i : paths)
3821 std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path WHERE idPath=%i", i));
3822 if (!path.empty())
3823 InvalidatePathHash(path);
3827 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3828 if (!bKeepId)
3829 AnnounceRemove(MediaTypeTvShow, idTvShow);
3831 CommitTransaction();
3834 catch (...)
3836 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idTvShow);
3837 RollbackTransaction();
3841 void CVideoDatabase::DeleteSeason(int idSeason, bool bKeepId /* = false */)
3843 if (idSeason < 0)
3844 return;
3848 if (m_pDB == nullptr || m_pDS == nullptr || m_pDS2 == nullptr)
3849 return;
3851 BeginTransaction();
3853 std::string strSQL = PrepareSQL("SELECT episode.idEpisode FROM episode "
3854 "JOIN seasons ON seasons.idSeason = %d AND episode.idShow = seasons.idShow AND episode.c%02d = seasons.season ",
3855 idSeason, VIDEODB_ID_EPISODE_SEASON);
3856 m_pDS2->query(strSQL);
3857 while (!m_pDS2->eof())
3859 DeleteEpisode(m_pDS2->fv(0).get_asInt(), bKeepId);
3860 m_pDS2->next();
3863 ExecuteQuery(PrepareSQL("DELETE FROM seasons WHERE idSeason = %i", idSeason));
3865 CommitTransaction();
3867 catch (...)
3869 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSeason);
3870 RollbackTransaction();
3874 void CVideoDatabase::DeleteEpisode(int idEpisode, bool bKeepId /* = false */)
3876 if (idEpisode < 0)
3877 return;
3881 if (nullptr == m_pDB)
3882 return;
3883 if (nullptr == m_pDS)
3884 return;
3886 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3887 if (!bKeepId)
3888 AnnounceRemove(MediaTypeEpisode, idEpisode);
3890 int idFile = GetDbId(PrepareSQL("SELECT idFile FROM episode WHERE idEpisode=%i", idEpisode));
3891 DeleteStreamDetails(idFile);
3893 // keep episode table entry and bookmarks so we can update the data in place
3894 // the ancillary tables are still purged
3895 if (!bKeepId)
3897 std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile));
3898 if (!path.empty())
3899 InvalidatePathHash(path);
3901 std::string strSQL = PrepareSQL("delete from episode where idEpisode=%i", idEpisode);
3902 m_pDS->exec(strSQL);
3906 catch (...)
3908 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idEpisode);
3912 void CVideoDatabase::DeleteMusicVideo(int idMVideo, bool bKeepId /* = false */)
3914 if (idMVideo < 0)
3915 return;
3919 if (nullptr == m_pDB)
3920 return;
3921 if (nullptr == m_pDS)
3922 return;
3924 BeginTransaction();
3926 int idFile = GetDbId(PrepareSQL("SELECT idFile FROM musicvideo WHERE idMVideo=%i", idMVideo));
3927 DeleteStreamDetails(idFile);
3929 // keep the music video table entry and bookmarks so we can update data in place
3930 // the ancillary tables are still purged
3931 if (!bKeepId)
3933 std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile));
3934 if (!path.empty())
3935 InvalidatePathHash(path);
3937 std::string strSQL = PrepareSQL("delete from musicvideo where idMVideo=%i", idMVideo);
3938 m_pDS->exec(strSQL);
3941 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3942 if (!bKeepId)
3943 AnnounceRemove(MediaTypeMusicVideo, idMVideo);
3945 CommitTransaction();
3948 catch (...)
3950 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
3951 RollbackTransaction();
3955 int CVideoDatabase::GetDbId(const std::string &query)
3957 std::string result = GetSingleValue(query);
3958 if (!result.empty())
3960 int idDb = strtol(result.c_str(), NULL, 10);
3961 if (idDb > 0)
3962 return idDb;
3964 return -1;
3967 void CVideoDatabase::DeleteStreamDetails(int idFile)
3969 m_pDS->exec(PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", idFile));
3972 void CVideoDatabase::DeleteSet(int idSet)
3976 if (nullptr == m_pDB)
3977 return;
3978 if (nullptr == m_pDS)
3979 return;
3981 std::string strSQL;
3982 strSQL=PrepareSQL("delete from sets where idSet = %i", idSet);
3983 m_pDS->exec(strSQL);
3984 strSQL=PrepareSQL("update movie set idSet = null where idSet = %i", idSet);
3985 m_pDS->exec(strSQL);
3987 catch (...)
3989 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idSet);
3993 void CVideoDatabase::ClearMovieSet(int idMovie)
3995 SetMovieSet(idMovie, -1);
3998 void CVideoDatabase::SetMovieSet(int idMovie, int idSet)
4000 if (idSet >= 0)
4001 ExecuteQuery(PrepareSQL("update movie set idSet = %i where idMovie = %i", idSet, idMovie));
4002 else
4003 ExecuteQuery(PrepareSQL("update movie set idSet = null where idMovie = %i", idMovie));
4006 std::string CVideoDatabase::GetFileBasePathById(int idFile)
4008 if (!m_pDB || !m_pDS)
4009 return "";
4013 m_pDS->query(PrepareSQL(
4014 "SELECT strPath FROM path JOIN files ON path.idPath = files.idPath WHERE idFile = %i",
4015 idFile));
4017 if (!m_pDS->eof())
4019 return m_pDS->fv("strPath").get_asString();
4021 m_pDS->close();
4023 catch (...)
4025 CLog::Log(LOGERROR, "{} failed for file {}", __FUNCTION__, idFile);
4028 return "";
4031 int CVideoDatabase::GetFileIdByMovie(int idMovie)
4033 if (!m_pDB || !m_pDS)
4034 return -1;
4036 int idFile = -1;
4040 m_pDS->query(PrepareSQL("SELECT idFile FROM movie WHERE idMovie = %i", idMovie));
4042 if (!m_pDS->eof())
4044 idFile = m_pDS->fv("idFile").get_asInt();
4047 m_pDS->close();
4049 catch (...)
4051 CLog::Log(LOGERROR, "{} failed for movie {}", __FUNCTION__, idMovie);
4054 return idFile;
4057 void CVideoDatabase::GetSameVideoItems(const CFileItem& item, CFileItemList& items)
4059 if (!m_pDB || !m_pDS)
4060 return;
4062 std::vector<int> itemIds;
4064 int dbId = item.GetVideoInfoTag()->m_iDbId;
4065 MediaType mediaType = item.GetVideoInfoTag()->m_type;
4069 // get items with same unique ids (imdb, tmdb, etc.) as the specified item, these are
4070 // the different versions of the item
4071 // note: old records may have the type 'unknown'
4072 // note 2: for type 'tmdb' the same value may be used for a movie and a tv episode, only
4073 // distinguished by media_type.
4074 // @todo make the (value,type) pairs truly unique
4075 m_pDS->query(PrepareSQL("SELECT DISTINCT media_id "
4076 "FROM uniqueid "
4077 "WHERE (media_type, value, type) IN "
4078 " (SELECT media_type, value, type "
4079 " FROM uniqueid WHERE media_id = %i AND media_type = '%s') ",
4080 dbId, mediaType.c_str()));
4082 while (!m_pDS->eof())
4084 itemIds.push_back(m_pDS->fv("media_id").get_asInt());
4085 m_pDS->next();
4088 m_pDS->close();
4090 VideoDbContentType itemType = item.GetVideoContentType();
4092 // get items with same title (and year if exists) as the specified item, these are
4093 // potentially different versions of the item
4094 if (itemType == VideoDbContentType::MOVIES)
4096 if (item.GetVideoInfoTag()->HasYear())
4097 m_pDS->query(
4098 PrepareSQL("SELECT idMovie FROM movie WHERE c%02d = '%s' AND premiered LIKE '%i%%'",
4099 VIDEODB_ID_TITLE, item.GetVideoInfoTag()->GetTitle().c_str(),
4100 item.GetVideoInfoTag()->GetYear()));
4101 else
4102 m_pDS->query(
4103 PrepareSQL("SELECT idMovie FROM movie WHERE c%02d = '%s' AND LENGTH(premiered) < 4",
4104 VIDEODB_ID_TITLE, item.GetVideoInfoTag()->GetTitle().c_str()));
4106 while (!m_pDS->eof())
4108 int movieId = m_pDS->fv("idMovie").get_asInt();
4110 // add movieId if not already in itemIds
4111 if (std::find(itemIds.begin(), itemIds.end(), movieId) == itemIds.end())
4112 itemIds.push_back(movieId);
4114 m_pDS->next();
4117 m_pDS->close();
4120 // get video item details
4121 for (const auto id : itemIds)
4123 auto current = std::make_shared<CFileItem>();
4124 if (GetDetailsByTypeAndId(*current.get(), itemType, id))
4125 items.Add(current);
4128 catch (...)
4130 CLog::Log(LOGERROR, "{} failed for {} {}", __FUNCTION__, mediaType, dbId);
4134 void CVideoDatabase::DeleteTag(int idTag, VideoDbContentType mediaType)
4138 if (m_pDB == nullptr || m_pDS == nullptr)
4139 return;
4141 std::string type;
4142 if (mediaType == VideoDbContentType::MOVIES)
4143 type = MediaTypeMovie;
4144 else if (mediaType == VideoDbContentType::TVSHOWS)
4145 type = MediaTypeTvShow;
4146 else if (mediaType == VideoDbContentType::MUSICVIDEOS)
4147 type = MediaTypeMusicVideo;
4148 else
4149 return;
4151 std::string strSQL = PrepareSQL("DELETE FROM tag_link WHERE tag_id = %i AND media_type = '%s'", idTag, type.c_str());
4152 m_pDS->exec(strSQL);
4154 catch (...)
4156 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idTag);
4160 void CVideoDatabase::GetDetailsFromDB(std::unique_ptr<Dataset> &pDS, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
4162 GetDetailsFromDB(pDS->get_sql_record(), min, max, offsets, details, idxOffset);
4165 void CVideoDatabase::GetDetailsFromDB(const dbiplus::sql_record* const record, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
4167 for (int i = min + 1; i < max; i++)
4169 switch (offsets[i].type)
4171 case VIDEODB_TYPE_STRING:
4172 *(std::string*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asString();
4173 break;
4174 case VIDEODB_TYPE_INT:
4175 case VIDEODB_TYPE_COUNT:
4176 *(int*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asInt();
4177 break;
4178 case VIDEODB_TYPE_BOOL:
4179 *(bool*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asBool();
4180 break;
4181 case VIDEODB_TYPE_FLOAT:
4182 *(float*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asFloat();
4183 break;
4184 case VIDEODB_TYPE_STRINGARRAY:
4186 std::string value = record->at(i+idxOffset).get_asString();
4187 if (!value.empty())
4188 *(std::vector<std::string>*)(((char*)&details)+offsets[i].offset) = StringUtils::Split(value, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4189 break;
4191 case VIDEODB_TYPE_DATE:
4192 ((CDateTime*)(((char*)&details)+offsets[i].offset))->SetFromDBDate(record->at(i+idxOffset).get_asString());
4193 break;
4194 case VIDEODB_TYPE_DATETIME:
4195 ((CDateTime*)(((char*)&details)+offsets[i].offset))->SetFromDBDateTime(record->at(i+idxOffset).get_asString());
4196 break;
4197 case VIDEODB_TYPE_UNUSED: // Skip the unused field to avoid populating unused data
4198 continue;
4203 bool CVideoDatabase::GetDetailsByTypeAndId(CFileItem& item, VideoDbContentType type, int id)
4205 CVideoInfoTag details;
4206 details.Reset();
4208 switch (type)
4210 case VideoDbContentType::MOVIES:
4211 GetMovieInfo("", details, id);
4212 break;
4213 case VideoDbContentType::TVSHOWS:
4214 GetTvShowInfo("", details, id, &item);
4215 break;
4216 case VideoDbContentType::EPISODES:
4217 GetEpisodeInfo("", details, id);
4218 break;
4219 case VideoDbContentType::MUSICVIDEOS:
4220 GetMusicVideoInfo("", details, id);
4221 break;
4222 default:
4223 return false;
4226 item.SetFromVideoInfoTag(details);
4227 return true;
4230 CVideoInfoTag CVideoDatabase::GetDetailsByTypeAndId(VideoDbContentType type, int id)
4232 CFileItem item;
4233 if (GetDetailsByTypeAndId(item, type, id))
4234 return CVideoInfoTag(*item.GetVideoInfoTag());
4236 return {};
4239 bool CVideoDatabase::GetStreamDetails(CFileItem& item)
4241 // Note that this function (possibly) creates VideoInfoTags for items that don't have one yet!
4242 int fileId = -1;
4244 if (item.HasVideoInfoTag())
4245 fileId = item.GetVideoInfoTag()->m_iFileId;
4246 if (fileId < 0)
4247 fileId = GetFileId(item);
4249 if (fileId < 0)
4250 return false;
4252 // Have a file id, get stream details if available (creates tag either way)
4253 item.GetVideoInfoTag()->m_iFileId = fileId;
4254 return GetStreamDetails(*item.GetVideoInfoTag());
4257 bool CVideoDatabase::GetStreamDetails(CVideoInfoTag& tag)
4260 const std::string path = tag.m_strFileNameAndPath;
4261 int fileId{-1};
4262 if (URIUtils::GetExtension(path) == ".mpls")
4263 fileId = GetFileId(path);
4264 else
4265 fileId = tag.m_iFileId;
4267 if (fileId < 0)
4268 return false;
4270 bool retVal = false;
4272 CStreamDetails& details = tag.m_streamDetails;
4273 details.Reset();
4275 std::unique_ptr<Dataset> pDS(m_pDB->CreateDataset());
4278 std::string strSQL = PrepareSQL("SELECT * FROM streamdetails WHERE idFile = %i", fileId);
4279 pDS->query(strSQL);
4281 while (!pDS->eof())
4283 CStreamDetail::StreamType e = (CStreamDetail::StreamType)pDS->fv(1).get_asInt();
4284 switch (e)
4286 case CStreamDetail::VIDEO:
4288 CStreamDetailVideo *p = new CStreamDetailVideo();
4289 p->m_strCodec = pDS->fv(2).get_asString();
4290 p->m_fAspect = pDS->fv(3).get_asFloat();
4291 p->m_iWidth = pDS->fv(4).get_asInt();
4292 p->m_iHeight = pDS->fv(5).get_asInt();
4293 p->m_iDuration = pDS->fv(10).get_asInt();
4294 p->m_strStereoMode = pDS->fv(11).get_asString();
4295 p->m_strLanguage = pDS->fv(12).get_asString();
4296 p->m_strHdrType = pDS->fv(13).get_asString();
4297 details.AddStream(p);
4298 retVal = true;
4299 break;
4301 case CStreamDetail::AUDIO:
4303 CStreamDetailAudio *p = new CStreamDetailAudio();
4304 p->m_strCodec = pDS->fv(6).get_asString();
4305 if (pDS->fv(7).get_isNull())
4306 p->m_iChannels = -1;
4307 else
4308 p->m_iChannels = pDS->fv(7).get_asInt();
4309 p->m_strLanguage = pDS->fv(8).get_asString();
4310 details.AddStream(p);
4311 retVal = true;
4312 break;
4314 case CStreamDetail::SUBTITLE:
4316 CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
4317 p->m_strLanguage = pDS->fv(9).get_asString();
4318 details.AddStream(p);
4319 retVal = true;
4320 break;
4324 pDS->next();
4327 pDS->close();
4329 catch (...)
4331 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, tag.m_iFileId);
4333 details.DetermineBestStreams();
4335 if (details.GetVideoDuration() > 0)
4336 tag.SetDuration(details.GetVideoDuration());
4338 return retVal;
4341 bool CVideoDatabase::GetResumePoint(CVideoInfoTag& tag)
4343 if (tag.m_iFileId < 0)
4344 return false;
4346 bool match = false;
4350 if (URIUtils::IsDiscImageStack(tag.m_strFileNameAndPath))
4352 CStackDirectory dir;
4353 CFileItemList fileList;
4354 const CURL pathToUrl(tag.m_strFileNameAndPath);
4355 dir.GetDirectory(pathToUrl, fileList);
4356 tag.SetResumePoint(CBookmark());
4357 for (int i = fileList.Size() - 1; i >= 0; i--)
4359 CBookmark bookmark;
4360 if (GetResumeBookMark(fileList[i]->GetPath(), bookmark))
4362 bookmark.partNumber = (i+1); /* store part number in here */
4363 tag.SetResumePoint(bookmark);
4364 match = true;
4365 break;
4369 else
4371 std::string strSQL=PrepareSQL("select timeInSeconds, totalTimeInSeconds from bookmark where idFile=%i and type=%i order by timeInSeconds", tag.m_iFileId, CBookmark::RESUME);
4372 m_pDS2->query( strSQL );
4373 if (!m_pDS2->eof())
4375 tag.SetResumePoint(m_pDS2->fv(0).get_asDouble(), m_pDS2->fv(1).get_asDouble(), "");
4376 match = true;
4378 m_pDS2->close();
4381 catch (...)
4383 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, tag.m_strFileNameAndPath);
4386 return match;
4389 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4391 return GetDetailsForMovie(pDS->get_sql_record(), getDetails);
4394 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4396 CVideoInfoTag details;
4398 if (record == NULL)
4399 return details;
4401 int idMovie = record->at(0).get_asInt();
4403 GetDetailsFromDB(record, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets, details);
4405 details.m_iDbId = idMovie;
4406 details.m_type = MediaTypeMovie;
4407 details.SetHasVideoVersions(record->at(VIDEODB_DETAILS_MOVIE_HASVERSIONS).get_asBool());
4408 details.SetHasVideoExtras(record->at(VIDEODB_DETAILS_MOVIE_HASEXTRAS).get_asBool());
4409 details.SetIsDefaultVideoVersion(record->at(VIDEODB_DETAILS_MOVIE_ISDEFAULTVERSION).get_asBool());
4410 auto& versionInfo = details.GetAssetInfo();
4411 versionInfo.SetId(record->at(VIDEODB_DETAILS_MOVIE_VERSION_TYPEID).get_asInt());
4412 versionInfo.SetTitle(record->at(VIDEODB_DETAILS_MOVIE_VERSION_TYPENAME).get_asString());
4413 versionInfo.SetType(
4414 static_cast<VideoAssetType>(record->at(VIDEODB_DETAILS_MOVIE_VERSION_ITEMTYPE).get_asInt()));
4415 details.m_set.id = record->at(VIDEODB_DETAILS_MOVIE_SET_ID).get_asInt();
4416 details.m_set.title = record->at(VIDEODB_DETAILS_MOVIE_SET_NAME).get_asString();
4417 details.m_set.overview = record->at(VIDEODB_DETAILS_MOVIE_SET_OVERVIEW).get_asString();
4418 details.m_iFileId = record->at(VIDEODB_DETAILS_MOVIE_VERSION_FILEID).get_asInt();
4419 details.m_strPath = record->at(VIDEODB_DETAILS_MOVIE_PATH).get_asString();
4420 std::string strFileName = record->at(VIDEODB_DETAILS_MOVIE_FILE).get_asString();
4421 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4422 details.SetPlayCount(record->at(VIDEODB_DETAILS_MOVIE_PLAYCOUNT).get_asInt());
4423 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_LASTPLAYED).get_asString());
4424 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_DATEADDED).get_asString());
4425 details.SetResumePoint(record->at(VIDEODB_DETAILS_MOVIE_RESUME_TIME).get_asInt(),
4426 record->at(VIDEODB_DETAILS_MOVIE_TOTAL_TIME).get_asInt(),
4427 record->at(VIDEODB_DETAILS_MOVIE_PLAYER_STATE).get_asString());
4428 details.m_iUserRating = record->at(VIDEODB_DETAILS_MOVIE_USER_RATING).get_asInt();
4429 details.SetRating(record->at(VIDEODB_DETAILS_MOVIE_RATING).get_asFloat(),
4430 record->at(VIDEODB_DETAILS_MOVIE_VOTES).get_asInt(),
4431 record->at(VIDEODB_DETAILS_MOVIE_RATING_TYPE).get_asString(), true);
4432 details.SetUniqueID(record->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_TYPE).get_asString() ,true);
4433 std::string premieredString = record->at(VIDEODB_DETAILS_MOVIE_PREMIERED).get_asString();
4434 if (premieredString.size() == 4)
4435 details.SetYear(record->at(VIDEODB_DETAILS_MOVIE_PREMIERED).get_asInt());
4436 else
4437 details.SetPremieredFromDBDate(premieredString);
4439 if (getDetails)
4441 if (getDetails & VideoDbDetailsCast)
4442 GetCast(details.m_iDbId, MediaTypeMovie, details.m_cast);
4444 if (getDetails & VideoDbDetailsTag)
4445 GetTags(details.m_iDbId, MediaTypeMovie, details.m_tags);
4447 if (getDetails & VideoDbDetailsRating)
4448 GetRatings(details.m_iDbId, MediaTypeMovie, details.m_ratings);
4450 if (getDetails & VideoDbDetailsUniqueID)
4451 GetUniqueIDs(details.m_iDbId, MediaTypeMovie, details);
4453 if (getDetails & VideoDbDetailsShowLink)
4455 // create tvshowlink string
4456 std::vector<int> links;
4457 GetLinksToTvShow(idMovie, links);
4458 for (unsigned int i = 0; i < links.size(); ++i)
4460 std::string strSQL = PrepareSQL("select c%02d from tvshow where idShow=%i",
4461 VIDEODB_ID_TV_TITLE, links[i]);
4462 m_pDS2->query(strSQL);
4463 if (!m_pDS2->eof())
4464 details.m_showLink.emplace_back(m_pDS2->fv(0).get_asString());
4466 m_pDS2->close();
4469 if (getDetails & VideoDbDetailsStream)
4470 GetStreamDetails(details);
4472 details.m_parsedDetails = getDetails;
4474 return details;
4477 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */, CFileItem* item /* = NULL */)
4479 return GetDetailsForTvShow(pDS->get_sql_record(), getDetails, item);
4482 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */, CFileItem* item /* = NULL */)
4484 CVideoInfoTag details;
4486 if (record == NULL)
4487 return details;
4489 int idTvShow = record->at(0).get_asInt();
4491 GetDetailsFromDB(record, VIDEODB_ID_TV_MIN, VIDEODB_ID_TV_MAX, DbTvShowOffsets, details, 1);
4492 details.m_bHasPremiered = details.m_premiered.IsValid();
4493 details.m_iDbId = idTvShow;
4494 details.m_type = MediaTypeTvShow;
4495 details.m_strPath = record->at(VIDEODB_DETAILS_TVSHOW_PATH).get_asString();
4496 details.m_basePath = details.m_strPath;
4497 details.m_parentPathID = record->at(VIDEODB_DETAILS_TVSHOW_PARENTPATHID).get_asInt();
4498 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_DATEADDED).get_asString());
4499 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_LASTPLAYED).get_asString());
4500 details.m_iSeason = record->at(VIDEODB_DETAILS_TVSHOW_NUM_SEASONS).get_asInt();
4501 details.m_iEpisode = record->at(VIDEODB_DETAILS_TVSHOW_NUM_EPISODES).get_asInt();
4502 details.SetPlayCount(record->at(VIDEODB_DETAILS_TVSHOW_NUM_WATCHED).get_asInt());
4503 details.m_strShowTitle = details.m_strTitle;
4504 details.m_iUserRating = record->at(VIDEODB_DETAILS_TVSHOW_USER_RATING).get_asInt();
4505 details.SetRating(record->at(VIDEODB_DETAILS_TVSHOW_RATING).get_asFloat(),
4506 record->at(VIDEODB_DETAILS_TVSHOW_VOTES).get_asInt(),
4507 record->at(VIDEODB_DETAILS_TVSHOW_RATING_TYPE).get_asString(), true);
4508 details.SetUniqueID(record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_TYPE).get_asString(), true);
4509 details.SetDuration(record->at(VIDEODB_DETAILS_TVSHOW_DURATION).get_asInt());
4511 //! @todo videotag member + guiinfo int needed?
4512 //! -- Currently not needed; having it available as item prop seems sufficient for skinning
4513 const int inProgressEpisodes = record->at(VIDEODB_DETAILS_TVSHOW_NUM_INPROGRESS).get_asInt();
4515 if (getDetails)
4517 if (getDetails & VideoDbDetailsCast)
4519 GetCast(details.m_iDbId, "tvshow", details.m_cast);
4522 if (getDetails & VideoDbDetailsTag)
4523 GetTags(details.m_iDbId, MediaTypeTvShow, details.m_tags);
4525 if (getDetails & VideoDbDetailsRating)
4526 GetRatings(details.m_iDbId, MediaTypeTvShow, details.m_ratings);
4528 if (getDetails & VideoDbDetailsUniqueID)
4529 GetUniqueIDs(details.m_iDbId, MediaTypeTvShow, details);
4531 details.m_parsedDetails = getDetails;
4534 if (item != NULL)
4536 item->m_dateTime = details.GetPremiered();
4537 item->SetProperty("totalseasons", details.m_iSeason);
4538 item->SetProperty("totalepisodes", details.m_iEpisode);
4539 item->SetProperty("numepisodes", details.m_iEpisode); // will be changed later to reflect watchmode setting
4540 item->SetProperty("watchedepisodes", details.GetPlayCount());
4541 item->SetProperty("unwatchedepisodes", details.m_iEpisode - details.GetPlayCount());
4542 item->SetProperty("inprogressepisodes", inProgressEpisodes);
4543 item->SetProperty("watchedepisodepercent",
4544 details.m_iEpisode > 0 ? (details.GetPlayCount() * 100 / details.m_iEpisode)
4545 : 0);
4547 details.SetPlayCount((details.m_iEpisode <= details.GetPlayCount()) ? 1 : 0);
4549 return details;
4552 CVideoInfoTag CVideoDatabase::GetBasicDetailsForEpisode(std::unique_ptr<Dataset> &pDS)
4554 return GetBasicDetailsForEpisode(pDS->get_sql_record());
4557 CVideoInfoTag CVideoDatabase::GetBasicDetailsForEpisode(const dbiplus::sql_record* const record)
4559 CVideoInfoTag details;
4561 if (record == nullptr)
4562 return details;
4564 int idEpisode = record->at(0).get_asInt();
4566 GetDetailsFromDB(record, VIDEODB_ID_EPISODE_MIN, VIDEODB_ID_EPISODE_MAX, DbEpisodeOffsets, details);
4567 details.m_iDbId = idEpisode;
4568 details.m_type = MediaTypeEpisode;
4569 details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4570 details.m_iIdShow = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt();
4571 details.m_iIdSeason = record->at(VIDEODB_DETAILS_EPISODE_SEASON_ID).get_asInt();
4572 details.m_iUserRating = record->at(VIDEODB_DETAILS_EPISODE_USER_RATING).get_asInt();
4574 return details;
4577 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4579 return GetDetailsForEpisode(pDS->get_sql_record(), getDetails);
4582 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4584 CVideoInfoTag details;
4586 if (record == nullptr)
4587 return details;
4589 details = GetBasicDetailsForEpisode(record);
4591 details.m_strPath = record->at(VIDEODB_DETAILS_EPISODE_PATH).get_asString();
4592 std::string strFileName = record->at(VIDEODB_DETAILS_EPISODE_FILE).get_asString();
4593 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4594 details.SetPlayCount(record->at(VIDEODB_DETAILS_EPISODE_PLAYCOUNT).get_asInt());
4595 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_LASTPLAYED).get_asString());
4596 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_DATEADDED).get_asString());
4597 details.m_strMPAARating = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_MPAA).get_asString();
4598 details.m_strShowTitle = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_NAME).get_asString();
4599 details.m_genre = StringUtils::Split(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_GENRE).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4600 details.m_studio = StringUtils::Split(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_STUDIO).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4601 details.SetPremieredFromDBDate(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_AIRED).get_asString());
4603 details.SetResumePoint(record->at(VIDEODB_DETAILS_EPISODE_RESUME_TIME).get_asInt(),
4604 record->at(VIDEODB_DETAILS_EPISODE_TOTAL_TIME).get_asInt(),
4605 record->at(VIDEODB_DETAILS_EPISODE_PLAYER_STATE).get_asString());
4607 details.SetRating(record->at(VIDEODB_DETAILS_EPISODE_RATING).get_asFloat(),
4608 record->at(VIDEODB_DETAILS_EPISODE_VOTES).get_asInt(),
4609 record->at(VIDEODB_DETAILS_EPISODE_RATING_TYPE).get_asString(), true);
4610 details.SetUniqueID(record->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_TYPE).get_asString(), true);
4612 if (getDetails)
4614 if (getDetails & VideoDbDetailsCast)
4616 GetCast(details.m_iDbId, MediaTypeEpisode, details.m_cast);
4617 GetCast(details.m_iIdShow, MediaTypeTvShow, details.m_cast);
4620 if (getDetails & VideoDbDetailsRating)
4621 GetRatings(details.m_iDbId, MediaTypeEpisode, details.m_ratings);
4623 if (getDetails & VideoDbDetailsUniqueID)
4624 GetUniqueIDs(details.m_iDbId, MediaTypeEpisode, details);
4626 if (getDetails & VideoDbDetailsBookmark)
4627 GetBookMarkForEpisode(details, details.m_EpBookmark);
4629 if (getDetails & VideoDbDetailsStream)
4630 GetStreamDetails(details);
4632 details.m_parsedDetails = getDetails;
4634 return details;
4637 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4639 return GetDetailsForMusicVideo(pDS->get_sql_record(), getDetails);
4642 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4644 CVideoInfoTag details;
4645 CArtist artist;
4647 if (record == nullptr)
4648 return details;
4650 int idMVideo = record->at(0).get_asInt();
4652 GetDetailsFromDB(record, VIDEODB_ID_MUSICVIDEO_MIN, VIDEODB_ID_MUSICVIDEO_MAX, DbMusicVideoOffsets, details);
4653 details.m_iDbId = idMVideo;
4654 details.m_type = MediaTypeMusicVideo;
4656 details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4657 details.m_strPath = record->at(VIDEODB_DETAILS_MUSICVIDEO_PATH).get_asString();
4658 std::string strFileName = record->at(VIDEODB_DETAILS_MUSICVIDEO_FILE).get_asString();
4659 ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4660 details.SetPlayCount(record->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYCOUNT).get_asInt());
4661 details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_LASTPLAYED).get_asString());
4662 details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_DATEADDED).get_asString());
4663 details.SetResumePoint(record->at(VIDEODB_DETAILS_MUSICVIDEO_RESUME_TIME).get_asInt(),
4664 record->at(VIDEODB_DETAILS_MUSICVIDEO_TOTAL_TIME).get_asInt(),
4665 record->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYER_STATE).get_asString());
4666 details.m_iUserRating = record->at(VIDEODB_DETAILS_MUSICVIDEO_USER_RATING).get_asInt();
4667 details.SetUniqueID(record->at(VIDEODB_DETAILS_MUSICVIDEO_UNIQUEID_VALUE).get_asString(),
4668 record->at(VIDEODB_DETAILS_MUSICVIDEO_UNIQUEID_TYPE).get_asString(), true);
4669 std::string premieredString = record->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED).get_asString();
4670 if (premieredString.size() == 4)
4671 details.SetYear(record->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED).get_asInt());
4672 else
4673 details.SetPremieredFromDBDate(premieredString);
4675 if (getDetails)
4677 if (getDetails & VideoDbDetailsTag)
4678 GetTags(details.m_iDbId, MediaTypeMusicVideo, details.m_tags);
4680 if (getDetails & VideoDbDetailsUniqueID)
4681 GetUniqueIDs(details.m_iDbId, MediaTypeMusicVideo, details);
4683 if (getDetails & VideoDbDetailsStream)
4684 GetStreamDetails(details);
4686 if (getDetails & VideoDbDetailsAll)
4688 GetCast(details.m_iDbId, "musicvideo", details.m_cast);
4691 details.m_parsedDetails = getDetails;
4693 return details;
4696 void CVideoDatabase::GetCast(int media_id, const std::string &media_type, std::vector<SActorInfo> &cast)
4700 if (!m_pDB)
4701 return;
4702 if (!m_pDS2)
4703 return;
4705 std::string sql = PrepareSQL("SELECT actor.name,"
4706 " actor_link.role,"
4707 " actor_link.cast_order,"
4708 " actor.art_urls,"
4709 " art.url "
4710 "FROM actor_link"
4711 " JOIN actor ON"
4712 " actor_link.actor_id=actor.actor_id"
4713 " LEFT JOIN art ON"
4714 " art.media_id=actor.actor_id AND art.media_type='actor' AND art.type='thumb' "
4715 "WHERE actor_link.media_id=%i AND actor_link.media_type='%s'"
4716 "ORDER BY actor_link.cast_order", media_id, media_type.c_str());
4717 m_pDS2->query(sql);
4718 while (!m_pDS2->eof())
4720 SActorInfo info;
4721 info.strName = m_pDS2->fv(0).get_asString();
4722 info.strRole = m_pDS2->fv(1).get_asString();
4724 // ignore identical actors (since cast might already be prefilled)
4725 if (std::none_of(cast.begin(), cast.end(), [info](const SActorInfo& actor) {
4726 return actor.strName == info.strName && actor.strRole == info.strRole;
4729 info.order = m_pDS2->fv(2).get_asInt();
4730 info.thumbUrl.ParseFromData(m_pDS2->fv(3).get_asString());
4731 info.thumb = m_pDS2->fv(4).get_asString();
4732 cast.emplace_back(std::move(info));
4735 m_pDS2->next();
4737 m_pDS2->close();
4739 catch (...)
4741 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4745 void CVideoDatabase::GetTags(int media_id, const std::string &media_type, std::vector<std::string> &tags)
4749 if (!m_pDB)
4750 return;
4751 if (!m_pDS2)
4752 return;
4754 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());
4755 m_pDS2->query(sql);
4756 while (!m_pDS2->eof())
4758 tags.emplace_back(m_pDS2->fv(0).get_asString());
4759 m_pDS2->next();
4761 m_pDS2->close();
4763 catch (...)
4765 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4769 void CVideoDatabase::GetRatings(int media_id, const std::string &media_type, RatingMap &ratings)
4773 if (!m_pDB)
4774 return;
4775 if (!m_pDS2)
4776 return;
4778 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());
4779 m_pDS2->query(sql);
4780 while (!m_pDS2->eof())
4782 ratings[m_pDS2->fv(0).get_asString()] = CRating(m_pDS2->fv(1).get_asFloat(), m_pDS2->fv(2).get_asInt());
4783 m_pDS2->next();
4785 m_pDS2->close();
4787 catch (...)
4789 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4793 void CVideoDatabase::GetUniqueIDs(int media_id, const std::string &media_type, CVideoInfoTag& details)
4797 if (!m_pDB)
4798 return;
4799 if (!m_pDS2)
4800 return;
4802 std::string sql = PrepareSQL("SELECT type, value FROM uniqueid WHERE media_id = %i AND media_type = '%s'", media_id, media_type.c_str());
4803 m_pDS2->query(sql);
4804 while (!m_pDS2->eof())
4806 details.SetUniqueID(m_pDS2->fv(1).get_asString(), m_pDS2->fv(0).get_asString());
4807 m_pDS2->next();
4809 m_pDS2->close();
4811 catch (...)
4813 CLog::Log(LOGERROR, "{}({},{}) failed", __FUNCTION__, media_id, media_type);
4817 bool CVideoDatabase::GetVideoSettings(const CFileItem &item, CVideoSettings &settings)
4819 return GetVideoSettings(GetFileId(item), settings);
4822 /// \brief GetVideoSettings() obtains any saved video settings for the current file.
4823 /// \retval Returns true if the settings exist, false otherwise.
4824 bool CVideoDatabase::GetVideoSettings(const std::string &filePath, CVideoSettings &settings)
4826 return GetVideoSettings(GetFileId(filePath), settings);
4829 bool CVideoDatabase::GetVideoSettings(int idFile, CVideoSettings &settings)
4833 if (idFile < 0) return false;
4834 if (nullptr == m_pDB)
4835 return false;
4836 if (nullptr == m_pDS)
4837 return false;
4839 std::string strSQL=PrepareSQL("select * from settings where settings.idFile = '%i'", idFile);
4840 m_pDS->query( strSQL );
4842 if (m_pDS->num_rows() > 0)
4843 { // get the video settings info
4844 settings.m_AudioDelay = m_pDS->fv("AudioDelay").get_asFloat();
4845 settings.m_AudioStream = m_pDS->fv("AudioStream").get_asInt();
4846 settings.m_Brightness = m_pDS->fv("Brightness").get_asFloat();
4847 settings.m_Contrast = m_pDS->fv("Contrast").get_asFloat();
4848 settings.m_CustomPixelRatio = m_pDS->fv("PixelRatio").get_asFloat();
4849 settings.m_CustomNonLinStretch = m_pDS->fv("NonLinStretch").get_asBool();
4850 settings.m_NoiseReduction = m_pDS->fv("NoiseReduction").get_asFloat();
4851 settings.m_PostProcess = m_pDS->fv("PostProcess").get_asBool();
4852 settings.m_Sharpness = m_pDS->fv("Sharpness").get_asFloat();
4853 settings.m_CustomZoomAmount = m_pDS->fv("ZoomAmount").get_asFloat();
4854 settings.m_CustomVerticalShift = m_pDS->fv("VerticalShift").get_asFloat();
4855 settings.m_Gamma = m_pDS->fv("Gamma").get_asFloat();
4856 settings.m_SubtitleDelay = m_pDS->fv("SubtitleDelay").get_asFloat();
4857 settings.m_SubtitleOn = m_pDS->fv("SubtitlesOn").get_asBool();
4858 settings.m_SubtitleStream = m_pDS->fv("SubtitleStream").get_asInt();
4859 settings.m_ViewMode = m_pDS->fv("ViewMode").get_asInt();
4860 settings.m_ResumeTime = m_pDS->fv("ResumeTime").get_asInt();
4861 settings.m_InterlaceMethod = (EINTERLACEMETHOD)m_pDS->fv("Deinterlace").get_asInt();
4862 settings.m_VolumeAmplification = m_pDS->fv("VolumeAmplification").get_asFloat();
4863 settings.m_ScalingMethod = (ESCALINGMETHOD)m_pDS->fv("ScalingMethod").get_asInt();
4864 settings.m_StereoMode = m_pDS->fv("StereoMode").get_asInt();
4865 settings.m_StereoInvert = m_pDS->fv("StereoInvert").get_asBool();
4866 settings.m_VideoStream = m_pDS->fv("VideoStream").get_asInt();
4867 settings.m_ToneMapMethod =
4868 static_cast<ETONEMAPMETHOD>(m_pDS->fv("TonemapMethod").get_asInt());
4869 settings.m_ToneMapParam = m_pDS->fv("TonemapParam").get_asFloat();
4870 settings.m_Orientation = m_pDS->fv("Orientation").get_asInt();
4871 settings.m_CenterMixLevel = m_pDS->fv("CenterMixLevel").get_asInt();
4872 m_pDS->close();
4873 return true;
4875 m_pDS->close();
4877 catch (...)
4879 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
4881 return false;
4884 void CVideoDatabase::SetVideoSettings(const CFileItem &item, const CVideoSettings &settings)
4886 int idFile = AddFile(item);
4887 SetVideoSettings(idFile, settings);
4890 /// \brief Sets the settings for a particular video file
4891 void CVideoDatabase::SetVideoSettings(int idFile, const CVideoSettings &setting)
4895 if (nullptr == m_pDB)
4896 return;
4897 if (nullptr == m_pDS)
4898 return;
4899 if (idFile < 0)
4900 return;
4901 std::string strSQL = PrepareSQL("select * from settings where idFile=%i", idFile);
4902 m_pDS->query( strSQL );
4903 if (m_pDS->num_rows() > 0)
4905 m_pDS->close();
4906 // update the item
4907 strSQL = PrepareSQL(
4908 "update settings set "
4909 "Deinterlace=%i,ViewMode=%i,ZoomAmount=%f,PixelRatio=%f,VerticalShift=%f,"
4910 "AudioStream=%i,SubtitleStream=%i,SubtitleDelay=%f,SubtitlesOn=%i,Brightness=%f,Contrast="
4911 "%f,Gamma=%f,"
4912 "VolumeAmplification=%f,AudioDelay=%f,Sharpness=%f,NoiseReduction=%f,NonLinStretch=%i,"
4913 "PostProcess=%i,ScalingMethod=%i,",
4914 setting.m_InterlaceMethod, setting.m_ViewMode,
4915 static_cast<double>(setting.m_CustomZoomAmount),
4916 static_cast<double>(setting.m_CustomPixelRatio),
4917 static_cast<double>(setting.m_CustomVerticalShift), setting.m_AudioStream,
4918 setting.m_SubtitleStream, static_cast<double>(setting.m_SubtitleDelay),
4919 setting.m_SubtitleOn, static_cast<double>(setting.m_Brightness),
4920 static_cast<double>(setting.m_Contrast), static_cast<double>(setting.m_Gamma),
4921 static_cast<double>(setting.m_VolumeAmplification),
4922 static_cast<double>(setting.m_AudioDelay), static_cast<double>(setting.m_Sharpness),
4923 static_cast<double>(setting.m_NoiseReduction), setting.m_CustomNonLinStretch,
4924 setting.m_PostProcess, setting.m_ScalingMethod);
4925 std::string strSQL2;
4927 strSQL2 = PrepareSQL("ResumeTime=%i,StereoMode=%i,StereoInvert=%i,VideoStream=%i,"
4928 "TonemapMethod=%i,TonemapParam=%f where idFile=%i\n",
4929 setting.m_ResumeTime, setting.m_StereoMode, setting.m_StereoInvert,
4930 setting.m_VideoStream, setting.m_ToneMapMethod,
4931 static_cast<double>(setting.m_ToneMapParam), idFile);
4932 strSQL += strSQL2;
4933 m_pDS->exec(strSQL);
4934 return ;
4936 else
4937 { // add the items
4938 m_pDS->close();
4939 strSQL= "INSERT INTO settings (idFile,Deinterlace,ViewMode,ZoomAmount,PixelRatio, VerticalShift, "
4940 "AudioStream,SubtitleStream,SubtitleDelay,SubtitlesOn,Brightness,"
4941 "Contrast,Gamma,VolumeAmplification,AudioDelay,"
4942 "ResumeTime,"
4943 "Sharpness,NoiseReduction,NonLinStretch,PostProcess,ScalingMethod,StereoMode,StereoInvert,VideoStream,TonemapMethod,TonemapParam,Orientation,CenterMixLevel) "
4944 "VALUES ";
4945 strSQL += PrepareSQL(
4946 "(%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)",
4947 idFile, setting.m_InterlaceMethod, setting.m_ViewMode,
4948 static_cast<double>(setting.m_CustomZoomAmount),
4949 static_cast<double>(setting.m_CustomPixelRatio),
4950 static_cast<double>(setting.m_CustomVerticalShift), setting.m_AudioStream,
4951 setting.m_SubtitleStream, static_cast<double>(setting.m_SubtitleDelay),
4952 setting.m_SubtitleOn, static_cast<double>(setting.m_Brightness),
4953 static_cast<double>(setting.m_Contrast), static_cast<double>(setting.m_Gamma),
4954 static_cast<double>(setting.m_VolumeAmplification),
4955 static_cast<double>(setting.m_AudioDelay), setting.m_ResumeTime,
4956 static_cast<double>(setting.m_Sharpness), static_cast<double>(setting.m_NoiseReduction),
4957 setting.m_CustomNonLinStretch, setting.m_PostProcess, setting.m_ScalingMethod,
4958 setting.m_StereoMode, setting.m_StereoInvert, setting.m_VideoStream,
4959 setting.m_ToneMapMethod, static_cast<double>(setting.m_ToneMapParam),
4960 setting.m_Orientation, setting.m_CenterMixLevel);
4961 m_pDS->exec(strSQL);
4964 catch (...)
4966 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idFile);
4970 void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, const std::map<std::string, std::string> &art)
4972 for (const auto &i : art)
4973 SetArtForItem(mediaId, mediaType, i.first, i.second);
4976 void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType, const std::string &url)
4980 if (nullptr == m_pDB)
4981 return;
4982 if (nullptr == m_pDS)
4983 return;
4985 // don't set <foo>.<bar> art types - these are derivative types from parent items
4986 if (artType.find('.') != std::string::npos)
4987 return;
4989 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());
4990 m_pDS->query(sql);
4991 if (!m_pDS->eof())
4992 { // update
4993 int artId = m_pDS->fv(0).get_asInt();
4994 std::string oldUrl = m_pDS->fv(1).get_asString();
4995 m_pDS->close();
4996 if (oldUrl != url)
4998 sql = PrepareSQL("UPDATE art SET url='%s' where art_id=%d", url.c_str(), artId);
4999 m_pDS->exec(sql);
5002 else
5003 { // insert
5004 m_pDS->close();
5005 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());
5006 m_pDS->exec(sql);
5009 catch (...)
5011 CLog::Log(LOGERROR, "{}({}, '{}', '{}', '{}') failed", __FUNCTION__, mediaId, mediaType,
5012 artType, url);
5016 bool CVideoDatabase::GetArtForItem(int mediaId, const MediaType &mediaType, std::map<std::string, std::string> &art)
5020 if (nullptr == m_pDB)
5021 return false;
5022 if (nullptr == m_pDS2)
5023 return false; // using dataset 2 as we're likely called in loops on dataset 1
5025 std::string sql = PrepareSQL("SELECT type,url FROM art WHERE media_id=%i AND media_type='%s'", mediaId, mediaType.c_str());
5027 m_pDS2->query(sql);
5028 while (!m_pDS2->eof())
5030 art.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString()));
5031 m_pDS2->next();
5033 m_pDS2->close();
5034 return !art.empty();
5036 catch (...)
5038 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaId);
5040 return false;
5043 bool CVideoDatabase::GetArtForAsset(int assetId,
5044 ArtFallbackOptions fallback,
5045 std::map<std::string, std::string>& art)
5049 if (nullptr == m_pDB)
5050 return false;
5051 if (nullptr == m_pDS2)
5052 return false; // using dataset 2 as we're likely called in loops on dataset 1
5054 std::string sql{PrepareSQL("SELECT art.media_type, art.type, art.url "
5055 "FROM art "
5056 "WHERE media_id = %i AND media_type = '%s' ",
5057 assetId, MediaTypeVideoVersion)};
5059 if (fallback == ArtFallbackOptions::PARENT)
5060 sql.append(PrepareSQL("UNION "
5061 "SELECT art.media_type, art.type, art.url "
5062 "FROM art "
5063 " JOIN videoversion as vv "
5064 " ON art.media_id = vv.idMedia AND art.media_type = vv.media_type "
5065 "WHERE idFile = %i",
5066 assetId));
5068 m_pDS2->query(sql);
5069 while (!m_pDS2->eof())
5071 if (m_pDS2->fv(0).get_asString() == MediaTypeVideoVersion)
5073 // version data has priority over owner's data
5074 art[m_pDS2->fv(1).get_asString()] = m_pDS2->fv(2).get_asString();
5076 else if (fallback == ArtFallbackOptions::PARENT)
5078 // insert if not yet present
5079 art.insert(make_pair(m_pDS2->fv(1).get_asString(), m_pDS2->fv(2).get_asString()));
5081 m_pDS2->next();
5083 m_pDS2->close();
5084 return !art.empty();
5086 catch (...)
5088 CLog::LogF(LOGERROR, "retrieval failed ({})", assetId);
5090 return false;
5093 std::string CVideoDatabase::GetArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType)
5095 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());
5096 return GetSingleValue(query, m_pDS2);
5099 bool CVideoDatabase::RemoveArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType)
5101 return ExecuteQuery(PrepareSQL("DELETE FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str()));
5104 bool CVideoDatabase::RemoveArtForItem(int mediaId, const MediaType &mediaType, const std::set<std::string> &artTypes)
5106 bool result = true;
5107 for (const auto &i : artTypes)
5108 result &= RemoveArtForItem(mediaId, mediaType, i);
5110 return result;
5113 bool CVideoDatabase::HasArtForItem(int mediaId, const MediaType &mediaType)
5117 if (nullptr == m_pDB)
5118 return false;
5119 if (nullptr == m_pDS2)
5120 return false; // using dataset 2 as we're likely called in loops on dataset 1
5122 std::string sql = PrepareSQL("SELECT 1 FROM art WHERE media_id=%i AND media_type='%s' LIMIT 1", mediaId, mediaType.c_str());
5123 m_pDS2->query(sql);
5124 bool result = !m_pDS2->eof();
5125 m_pDS2->close();
5126 return result;
5128 catch (...)
5130 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaId);
5132 return false;
5135 bool CVideoDatabase::GetTvShowSeasons(int showId, std::map<int, int> &seasons)
5139 if (nullptr == m_pDB)
5140 return false;
5141 if (nullptr == m_pDS2)
5142 return false; // using dataset 2 as we're likely called in loops on dataset 1
5144 // get all seasons for this show
5145 std::string sql = PrepareSQL("select idSeason,season from seasons where idShow=%i", showId);
5146 m_pDS2->query(sql);
5148 seasons.clear();
5149 while (!m_pDS2->eof())
5151 seasons.insert(std::make_pair(m_pDS2->fv(1).get_asInt(), m_pDS2->fv(0).get_asInt()));
5152 m_pDS2->next();
5154 m_pDS2->close();
5155 return true;
5157 catch (...)
5159 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
5161 return false;
5164 bool CVideoDatabase::GetTvShowNamedSeasons(int showId, std::map<int, std::string> &seasons)
5168 if (nullptr == m_pDB)
5169 return false;
5170 if (nullptr == m_pDS2)
5171 return false; // using dataset 2 as we're likely called in loops on dataset 1
5173 // get all named seasons for this show
5174 std::string sql = PrepareSQL("select season, name from seasons where season > 0 and name is not null and name <> '' and idShow = %i", showId);
5175 m_pDS2->query(sql);
5177 seasons.clear();
5178 while (!m_pDS2->eof())
5180 seasons.insert(std::make_pair(m_pDS2->fv(0).get_asInt(), m_pDS2->fv(1).get_asString()));
5181 m_pDS2->next();
5183 m_pDS2->close();
5184 return true;
5186 catch (...)
5188 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
5190 return false;
5193 std::string CVideoDatabase::GetTvShowNamedSeasonById(int tvshowId, int seasonId)
5195 return GetSingleValue("seasons", "name",
5196 PrepareSQL("season=%i AND idShow=%i", seasonId, tvshowId));
5199 bool CVideoDatabase::GetTvShowSeasonArt(int showId, std::map<int, std::map<std::string, std::string> > &seasonArt)
5203 if (nullptr == m_pDB)
5204 return false;
5205 if (nullptr == m_pDS2)
5206 return false; // using dataset 2 as we're likely called in loops on dataset 1
5208 std::map<int, int> seasons;
5209 GetTvShowSeasons(showId, seasons);
5211 for (const auto &i : seasons)
5213 std::map<std::string, std::string> art;
5214 GetArtForItem(i.second, MediaTypeSeason, art);
5215 seasonArt.insert(std::make_pair(i.first,art));
5217 return true;
5219 catch (...)
5221 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, showId);
5223 return false;
5226 bool CVideoDatabase::GetArtTypes(const MediaType &mediaType, std::vector<std::string> &artTypes)
5230 if (nullptr == m_pDB)
5231 return false;
5232 if (nullptr == m_pDS)
5233 return false;
5235 std::string sql = PrepareSQL("SELECT DISTINCT type FROM art WHERE media_type='%s'", mediaType.c_str());
5236 int numRows = RunQuery(sql);
5237 if (numRows <= 0)
5238 return numRows == 0;
5240 while (!m_pDS->eof())
5242 artTypes.emplace_back(m_pDS->fv(0).get_asString());
5243 m_pDS->next();
5245 m_pDS->close();
5246 return true;
5248 catch (...)
5250 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, mediaType);
5252 return false;
5255 namespace
5257 std::vector<std::string> GetBasicItemAvailableArtTypes(int mediaId,
5258 VideoDbContentType dbType,
5259 CVideoDatabase& db)
5261 std::vector<std::string> result;
5262 CVideoInfoTag tag = db.GetDetailsByTypeAndId(dbType, mediaId);
5264 //! @todo artwork: fanart stored separately, doesn't need to be
5265 tag.m_fanart.Unpack();
5266 if (tag.m_fanart.GetNumFanarts() && std::find(result.cbegin(), result.cend(), "fanart") == result.cend())
5267 result.emplace_back("fanart");
5269 // all other images
5270 tag.m_strPictureURL.Parse();
5271 for (const auto& urlEntry : tag.m_strPictureURL.GetUrls())
5273 std::string artType = urlEntry.m_aspect;
5274 if (artType.empty())
5275 artType = tag.m_type == MediaTypeEpisode ? "thumb" : "poster";
5276 if (urlEntry.m_type == CScraperUrl::UrlType::General && // exclude season artwork for TV shows
5277 !StringUtils::StartsWith(artType, "set.") && // exclude movie set artwork for movies
5278 std::find(result.cbegin(), result.cend(), artType) == result.cend())
5280 result.push_back(artType);
5283 return result;
5286 std::vector<std::string> GetSeasonAvailableArtTypes(int mediaId, CVideoDatabase& db)
5288 CVideoInfoTag tag;
5289 db.GetSeasonInfo(mediaId, tag);
5291 std::vector<std::string> result;
5293 CVideoInfoTag sourceShow;
5294 db.GetTvShowInfo("", sourceShow, tag.m_iIdShow);
5295 sourceShow.m_strPictureURL.Parse();
5296 for (const auto& urlEntry : sourceShow.m_strPictureURL.GetUrls())
5298 std::string artType = urlEntry.m_aspect;
5299 if (artType.empty())
5300 artType = "poster";
5301 if (urlEntry.m_type == CScraperUrl::UrlType::Season && urlEntry.m_season == tag.m_iSeason &&
5302 std::find(result.cbegin(), result.cend(), artType) == result.cend())
5304 result.push_back(artType);
5307 return result;
5310 std::vector<std::string> GetMovieSetAvailableArtTypes(int mediaId, CVideoDatabase& db)
5312 std::vector<std::string> result;
5313 CFileItemList items;
5314 std::string baseDir = StringUtils::Format("videodb://movies/sets/{}", mediaId);
5315 if (db.GetMoviesNav(baseDir, items))
5317 for (const auto& item : items)
5319 CVideoInfoTag* pTag = item->GetVideoInfoTag();
5320 pTag->m_strPictureURL.Parse();
5322 for (const auto& urlEntry : pTag->m_strPictureURL.GetUrls())
5324 if (!StringUtils::StartsWith(urlEntry.m_aspect, "set."))
5325 continue;
5327 std::string artType = urlEntry.m_aspect.substr(4);
5328 if (std::find(result.cbegin(), result.cend(), artType) == result.cend())
5329 result.push_back(artType);
5333 return result;
5336 std::vector<CScraperUrl::SUrlEntry> GetBasicItemAvailableArt(int mediaId,
5337 VideoDbContentType dbType,
5338 const std::string& artType,
5339 CVideoDatabase& db)
5341 std::vector<CScraperUrl::SUrlEntry> result;
5342 CVideoInfoTag tag = db.GetDetailsByTypeAndId(dbType, mediaId);
5344 if (artType.empty() || artType == "fanart")
5346 tag.m_fanart.Unpack();
5347 for (unsigned int i = 0; i < tag.m_fanart.GetNumFanarts(); i++)
5349 CScraperUrl::SUrlEntry url(tag.m_fanart.GetImageURL(i));
5350 url.m_preview = tag.m_fanart.GetPreviewURL(i);
5351 url.m_aspect = "fanart";
5352 result.push_back(url);
5355 tag.m_strPictureURL.Parse();
5356 for (auto urlEntry : tag.m_strPictureURL.GetUrls())
5358 if (urlEntry.m_aspect.empty())
5359 urlEntry.m_aspect = tag.m_type == MediaTypeEpisode ? "thumb" : "poster";
5360 if ((urlEntry.m_aspect == artType ||
5361 (artType.empty() && !StringUtils::StartsWith(urlEntry.m_aspect, "set."))) &&
5362 urlEntry.m_type == CScraperUrl::UrlType::General)
5364 result.push_back(urlEntry);
5368 return result;
5371 std::vector<CScraperUrl::SUrlEntry> GetSeasonAvailableArt(
5372 int mediaId, const std::string& artType, CVideoDatabase& db)
5374 CVideoInfoTag tag;
5375 db.GetSeasonInfo(mediaId, tag);
5377 std::vector<CScraperUrl::SUrlEntry> result;
5379 CVideoInfoTag sourceShow;
5380 db.GetTvShowInfo("", sourceShow, tag.m_iIdShow);
5381 sourceShow.m_strPictureURL.Parse();
5382 for (auto urlEntry : sourceShow.m_strPictureURL.GetUrls())
5384 if (urlEntry.m_aspect.empty())
5385 urlEntry.m_aspect = "poster";
5386 if ((artType.empty() || urlEntry.m_aspect == artType) &&
5387 urlEntry.m_type == CScraperUrl::UrlType::Season &&
5388 urlEntry.m_season == tag.m_iSeason)
5390 result.push_back(urlEntry);
5393 return result;
5396 std::vector<CScraperUrl::SUrlEntry> GetMovieSetAvailableArt(
5397 int mediaId, const std::string& artType, CVideoDatabase& db)
5399 std::vector<CScraperUrl::SUrlEntry> result;
5400 CFileItemList items;
5401 std::string baseDir = StringUtils::Format("videodb://movies/sets/{}", mediaId);
5402 std::unordered_set<std::string> addedURLs;
5403 if (db.GetMoviesNav(baseDir, items))
5405 for (const auto& item : items)
5407 CVideoInfoTag* pTag = item->GetVideoInfoTag();
5408 pTag->m_strPictureURL.Parse();
5410 for (auto urlEntry : pTag->m_strPictureURL.GetUrls())
5412 bool isSetArt = !artType.empty() ? urlEntry.m_aspect == "set." + artType :
5413 StringUtils::StartsWith(urlEntry.m_aspect, "set.");
5414 if (isSetArt && addedURLs.insert(urlEntry.m_url).second)
5416 urlEntry.m_aspect = urlEntry.m_aspect.substr(4);
5417 result.push_back(urlEntry);
5422 return result;
5425 VideoDbContentType CovertMediaTypeToContentType(const MediaType& mediaType)
5427 VideoDbContentType dbType{VideoDbContentType::UNKNOWN};
5428 if (mediaType == MediaTypeTvShow)
5429 dbType = VideoDbContentType::TVSHOWS;
5430 else if (mediaType == MediaTypeMovie)
5431 dbType = VideoDbContentType::MOVIES;
5432 else if (mediaType == MediaTypeEpisode)
5433 dbType = VideoDbContentType::EPISODES;
5434 else if (mediaType == MediaTypeMusicVideo)
5435 dbType = VideoDbContentType::MUSICVIDEOS;
5437 return dbType;
5439 } // namespace
5441 std::vector<CScraperUrl::SUrlEntry> CVideoDatabase::GetAvailableArtForItem(
5442 int mediaId, const MediaType& mediaType, const std::string& artType)
5444 VideoDbContentType dbType = CovertMediaTypeToContentType(mediaType);
5446 if (dbType != VideoDbContentType::UNKNOWN)
5447 return GetBasicItemAvailableArt(mediaId, dbType, artType, *this);
5448 if (mediaType == MediaTypeSeason)
5449 return GetSeasonAvailableArt(mediaId, artType, *this);
5450 if (mediaType == MediaTypeVideoCollection)
5451 return GetMovieSetAvailableArt(mediaId, artType, *this);
5452 return {};
5455 std::vector<std::string> CVideoDatabase::GetAvailableArtTypesForItem(int mediaId,
5456 const MediaType& mediaType)
5458 VideoDbContentType dbType = CovertMediaTypeToContentType(mediaType);
5460 if (dbType != VideoDbContentType::UNKNOWN)
5461 return GetBasicItemAvailableArtTypes(mediaId, dbType, *this);
5462 if (mediaType == MediaTypeSeason)
5463 return GetSeasonAvailableArtTypes(mediaId, *this);
5464 if (mediaType == MediaTypeVideoCollection)
5465 return GetMovieSetAvailableArtTypes(mediaId, *this);
5466 return {};
5469 /// \brief GetStackTimes() obtains any saved video times for the stacked file
5470 /// \retval Returns true if the stack times exist, false otherwise.
5471 bool CVideoDatabase::GetStackTimes(const std::string &filePath, std::vector<uint64_t> &times)
5475 // obtain the FileID (if it exists)
5476 int idFile = GetFileId(filePath);
5477 if (idFile < 0) return false;
5478 if (nullptr == m_pDB)
5479 return false;
5480 if (nullptr == m_pDS)
5481 return false;
5482 // ok, now obtain the settings for this file
5483 std::string strSQL=PrepareSQL("select times from stacktimes where idFile=%i\n", idFile);
5484 m_pDS->query( strSQL );
5485 if (m_pDS->num_rows() > 0)
5486 { // get the video settings info
5487 uint64_t timeTotal = 0;
5488 std::vector<std::string> timeString = StringUtils::Split(m_pDS->fv("times").get_asString(), ",");
5489 times.clear();
5490 for (const auto &i : timeString)
5492 uint64_t partTime = static_cast<uint64_t>(atof(i.c_str()) * 1000.0);
5493 times.push_back(partTime); // db stores in secs, convert to msecs
5494 timeTotal += partTime;
5496 m_pDS->close();
5497 return (timeTotal > 0);
5499 m_pDS->close();
5501 catch (...)
5503 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
5505 return false;
5508 /// \brief Sets the stack times for a particular video file
5509 void CVideoDatabase::SetStackTimes(const std::string& filePath, const std::vector<uint64_t> &times)
5513 if (nullptr == m_pDB)
5514 return;
5515 if (nullptr == m_pDS)
5516 return;
5517 int idFile = AddFile(filePath);
5518 if (idFile < 0)
5519 return;
5521 // delete any existing items
5522 m_pDS->exec( PrepareSQL("delete from stacktimes where idFile=%i", idFile) );
5524 // add the items
5525 std::string timeString = StringUtils::Format("{:.3f}", times[0] / 1000.0f);
5526 for (unsigned int i = 1; i < times.size(); i++)
5527 timeString += StringUtils::Format(",{:.3f}", times[i] / 1000.0f);
5529 m_pDS->exec( PrepareSQL("insert into stacktimes (idFile,times) values (%i,'%s')\n", idFile, timeString.c_str()) );
5531 catch (...)
5533 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
5537 void CVideoDatabase::RemoveContentForPath(const std::string& strPath, CGUIDialogProgress *progress /* = NULL */)
5539 if(URIUtils::IsMultiPath(strPath))
5541 std::vector<std::string> paths;
5542 CMultiPathDirectory::GetPaths(strPath, paths);
5544 for(unsigned i=0;i<paths.size();i++)
5545 RemoveContentForPath(paths[i], progress);
5550 if (nullptr == m_pDB)
5551 return;
5552 if (nullptr == m_pDS)
5553 return;
5555 if (progress)
5557 progress->SetHeading(CVariant{700});
5558 progress->SetLine(0, CVariant{""});
5559 progress->SetLine(1, CVariant{313});
5560 progress->SetLine(2, CVariant{330});
5561 progress->SetPercentage(0);
5562 progress->Open();
5563 progress->ShowProgressBar(true);
5565 std::vector<std::pair<int, std::string> > paths;
5566 GetSubPaths(strPath, paths);
5567 int iCurr = 0;
5568 for (const auto &i : paths)
5570 bool bMvidsChecked=false;
5571 if (progress)
5573 progress->SetPercentage((int)((float)(iCurr++)/paths.size()*100.f));
5574 progress->Progress();
5577 const auto tvshowId = GetTvShowId(i.second);
5578 if (tvshowId > 0)
5579 DeleteTvShow(tvshowId);
5580 else
5582 std::string strSQL = PrepareSQL("select files.strFilename from files join movie on movie.idFile=files.idFile where files.idPath=%i", i.first);
5583 m_pDS2->query(strSQL);
5584 if (m_pDS2->eof())
5586 strSQL = PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i.first);
5587 m_pDS2->query(strSQL);
5588 bMvidsChecked = true;
5590 while (!m_pDS2->eof())
5592 std::string strMoviePath;
5593 std::string strFileName = m_pDS2->fv("files.strFilename").get_asString();
5594 ConstructPath(strMoviePath, i.second, strFileName);
5595 const auto movieId = GetMovieId(strMoviePath);
5596 if (movieId > 0)
5597 DeleteMovie(movieId);
5598 else
5600 const auto musicvideoId = GetMusicVideoId(strMoviePath);
5601 if (musicvideoId > 0)
5602 DeleteMusicVideo(musicvideoId);
5604 m_pDS2->next();
5605 if (m_pDS2->eof() && !bMvidsChecked)
5607 strSQL =PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i.first);
5608 m_pDS2->query(strSQL);
5609 bMvidsChecked = true;
5612 m_pDS2->close();
5613 m_pDS2->exec(PrepareSQL("update path set strContent='', strScraper='', strHash='',strSettings='',useFolderNames=0,scanRecursive=0 where idPath=%i", i.first));
5617 catch (...)
5619 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strPath);
5621 if (progress)
5622 progress->Close();
5625 void CVideoDatabase::SetScraperForPath(const std::string& filePath, const ScraperPtr& scraper, const VIDEO::SScanSettings& settings)
5627 // if we have a multipath, set scraper for all contained paths
5628 if(URIUtils::IsMultiPath(filePath))
5630 std::vector<std::string> paths;
5631 CMultiPathDirectory::GetPaths(filePath, paths);
5633 for(unsigned i=0;i<paths.size();i++)
5634 SetScraperForPath(paths[i],scraper,settings);
5636 return;
5641 if (nullptr == m_pDB)
5642 return;
5643 if (nullptr == m_pDS)
5644 return;
5646 int idPath = AddPath(filePath);
5647 if (idPath < 0)
5648 return;
5650 // Update
5651 std::string strSQL;
5652 if (settings.exclude)
5653 { //NB See note in ::GetScraperForPath about strContent=='none'
5654 strSQL = PrepareSQL(
5655 "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5656 "strSettings='', noUpdate=0, exclude=1, allAudio=%i WHERE idPath=%i",
5657 settings.m_allExtAudio, idPath);
5659 else if(!scraper)
5660 { // catch clearing content, but not excluding
5661 strSQL = PrepareSQL(
5662 "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5663 "strSettings='', noUpdate=0, exclude=0, allAudio=%i WHERE idPath=%i",
5664 settings.m_allExtAudio, idPath);
5666 else
5668 std::string content = TranslateContent(scraper->Content());
5669 strSQL = PrepareSQL(
5670 "UPDATE path SET strContent='%s', strScraper='%s', scanRecursive=%i, useFolderNames=%i, "
5671 "strSettings='%s', noUpdate=%i, exclude=0, allAudio=%i WHERE idPath=%i",
5672 content.c_str(), scraper->ID().c_str(), settings.recurse, settings.parent_name,
5673 scraper->GetPathSettings().c_str(), settings.noupdate, settings.m_allExtAudio, idPath);
5675 m_pDS->exec(strSQL);
5677 catch (...)
5679 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, filePath);
5683 bool CVideoDatabase::ScraperInUse(const std::string &scraperID) const
5687 if (nullptr == m_pDB)
5688 return false;
5689 if (nullptr == m_pDS)
5690 return false;
5692 std::string sql = PrepareSQL("select count(1) from path where strScraper='%s'", scraperID.c_str());
5693 if (!m_pDS->query(sql) || m_pDS->num_rows() == 0)
5694 return false;
5695 bool found = m_pDS->fv(0).get_asInt() > 0;
5696 m_pDS->close();
5697 return found;
5699 catch (...)
5701 CLog::Log(LOGERROR, "{}({}) failed", __FUNCTION__, scraperID);
5703 return false;
5706 class CArtItem
5708 public:
5709 CArtItem() { art_id = 0; media_id = 0; };
5710 int art_id;
5711 std::string art_type;
5712 std::string art_url;
5713 int media_id;
5714 std::string media_type;
5717 // used for database update to v83
5718 class CShowItem
5720 public:
5721 bool operator==(const CShowItem &r) const
5723 return (!ident.empty() && ident == r.ident) || (title == r.title && year == r.year);
5725 int id;
5726 int path;
5727 std::string title;
5728 std::string year;
5729 std::string ident;
5732 // used for database update to v84
5733 class CShowLink
5735 public:
5736 int show;
5737 int pathId;
5738 std::string path;
5741 void CVideoDatabase::UpdateTables(int iVersion)
5743 // Important: DO NOT use CREATE TABLE [...] AS SELECT [...] - it does not work on MySQL with GTID consistency enforced
5745 if (iVersion < 76)
5747 m_pDS->exec("ALTER TABLE settings ADD StereoMode integer");
5748 m_pDS->exec("ALTER TABLE settings ADD StereoInvert bool");
5750 if (iVersion < 77)
5751 m_pDS->exec("ALTER TABLE streamdetails ADD strStereoMode text");
5753 if (iVersion < 81)
5754 { // add idParentPath to path table
5755 m_pDS->exec("ALTER TABLE path ADD idParentPath integer");
5756 std::map<std::string, int> paths;
5757 m_pDS->query("select idPath,strPath from path");
5758 while (!m_pDS->eof())
5760 paths.insert(make_pair(m_pDS->fv(1).get_asString(), m_pDS->fv(0).get_asInt()));
5761 m_pDS->next();
5763 m_pDS->close();
5764 // run through these paths figuring out the parent path, and add to the table if found
5765 for (const auto &i : paths)
5767 std::string parent = URIUtils::GetParentPath(i.first);
5768 auto j = paths.find(parent);
5769 if (j != paths.end())
5770 m_pDS->exec(PrepareSQL("UPDATE path SET idParentPath=%i WHERE idPath=%i", j->second, i.second));
5773 if (iVersion < 82)
5775 // drop parent path id and basePath from tvshow table
5776 m_pDS->exec("UPDATE tvshow SET c16=NULL,c17=NULL");
5778 if (iVersion < 83)
5780 // drop duplicates in tvshow table, and update tvshowlinkpath accordingly
5781 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);
5782 m_pDS->query(sql);
5783 std::vector<CShowItem> shows;
5784 while (!m_pDS->eof())
5786 CShowItem show;
5787 show.id = m_pDS->fv(0).get_asInt();
5788 show.path = m_pDS->fv(1).get_asInt();
5789 show.title = m_pDS->fv(2).get_asString();
5790 show.year = m_pDS->fv(3).get_asString();
5791 show.ident = m_pDS->fv(4).get_asString();
5792 shows.emplace_back(std::move(show));
5793 m_pDS->next();
5795 m_pDS->close();
5796 if (!shows.empty())
5798 for (auto i = shows.begin() + 1; i != shows.end(); ++i)
5800 // has this show been found before?
5801 auto j = find(shows.begin(), i, *i);
5802 if (j != i)
5803 { // this is a duplicate
5804 // update the tvshowlinkpath table
5805 m_pDS->exec(PrepareSQL("UPDATE tvshowlinkpath SET idShow = %d WHERE idShow = %d AND idPath = %d", j->id, i->id, i->path));
5806 // update episodes, seasons, movie links
5807 m_pDS->exec(PrepareSQL("UPDATE episode SET idShow = %d WHERE idShow = %d", j->id, i->id));
5808 m_pDS->exec(PrepareSQL("UPDATE seasons SET idShow = %d WHERE idShow = %d", j->id, i->id));
5809 m_pDS->exec(PrepareSQL("UPDATE movielinktvshow SET idShow = %d WHERE idShow = %d", j->id, i->id));
5810 // delete tvshow
5811 m_pDS->exec(PrepareSQL("DELETE FROM genrelinktvshow WHERE idShow=%i", i->id));
5812 m_pDS->exec(PrepareSQL("DELETE FROM actorlinktvshow WHERE idShow=%i", i->id));
5813 m_pDS->exec(PrepareSQL("DELETE FROM directorlinktvshow WHERE idShow=%i", i->id));
5814 m_pDS->exec(PrepareSQL("DELETE FROM studiolinktvshow WHERE idShow=%i", i->id));
5815 m_pDS->exec(PrepareSQL("DELETE FROM tvshow WHERE idShow = %d", i->id));
5818 // cleanup duplicate seasons
5819 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)");
5822 if (iVersion < 84)
5823 { // replace any multipaths in tvshowlinkpath table
5824 m_pDS->query("SELECT idShow, tvshowlinkpath.idPath, strPath FROM tvshowlinkpath JOIN path ON tvshowlinkpath.idPath=path.idPath WHERE path.strPath LIKE 'multipath://%'");
5825 std::vector<CShowLink> shows;
5826 while (!m_pDS->eof())
5828 CShowLink link;
5829 link.show = m_pDS->fv(0).get_asInt();
5830 link.pathId = m_pDS->fv(1).get_asInt();
5831 link.path = m_pDS->fv(2).get_asString();
5832 shows.emplace_back(std::move(link));
5833 m_pDS->next();
5835 m_pDS->close();
5836 // update these
5837 for (auto i = shows.begin(); i != shows.end(); ++i)
5839 std::vector<std::string> paths;
5840 CMultiPathDirectory::GetPaths(i->path, paths);
5841 for (auto j = paths.begin(); j != paths.end(); ++j)
5843 int idPath = AddPath(*j, URIUtils::GetParentPath(*j));
5844 /* we can't rely on REPLACE INTO here as analytics (indices) aren't online yet */
5845 if (GetSingleValue(PrepareSQL("SELECT 1 FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i->show, idPath)).empty())
5846 m_pDS->exec(PrepareSQL("INSERT INTO tvshowlinkpath(idShow, idPath) VALUES(%i,%i)", i->show, idPath));
5848 m_pDS->exec(PrepareSQL("DELETE FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i->show, i->pathId));
5851 if (iVersion < 85)
5853 // drop multipaths from the path table - they're not needed for anything at all
5854 m_pDS->exec("DELETE FROM path WHERE strPath LIKE 'multipath://%'");
5856 if (iVersion < 87)
5857 { // due to the tvshow merging above, there could be orphaned season or show art
5858 m_pDS->exec("DELETE from art WHERE media_type='tvshow' AND NOT EXISTS (SELECT 1 FROM tvshow WHERE tvshow.idShow = art.media_id)");
5859 m_pDS->exec("DELETE from art WHERE media_type='season' AND NOT EXISTS (SELECT 1 FROM seasons WHERE seasons.idSeason = art.media_id)");
5861 if (iVersion < 91)
5863 // create actor link table
5864 m_pDS->exec("CREATE TABLE actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
5865 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");
5866 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");
5867 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");
5868 m_pDS->exec("DROP TABLE IF EXISTS actorlinkmovie");
5869 m_pDS->exec("DROP TABLE IF EXISTS actorlinktvshow");
5870 m_pDS->exec("DROP TABLE IF EXISTS actorlinkepisode");
5871 m_pDS->exec("CREATE TABLE actor(actor_id INTEGER PRIMARY KEY, name TEXT, art_urls TEXT)");
5872 m_pDS->exec("INSERT INTO actor(actor_id, name, art_urls) SELECT idActor,strActor,strThumb FROM actors");
5873 m_pDS->exec("DROP TABLE IF EXISTS actors");
5875 // directors
5876 m_pDS->exec("CREATE TABLE director_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5877 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMovie, 'movie' FROM directorlinkmovie");
5878 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idShow, 'tvshow' FROM directorlinktvshow");
5879 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idEpisode, 'episode' FROM directorlinkepisode");
5880 m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMVideo, 'musicvideo' FROM directorlinkmusicvideo");
5881 m_pDS->exec("DROP TABLE IF EXISTS directorlinkmovie");
5882 m_pDS->exec("DROP TABLE IF EXISTS directorlinktvshow");
5883 m_pDS->exec("DROP TABLE IF EXISTS directorlinkepisode");
5884 m_pDS->exec("DROP TABLE IF EXISTS directorlinkmusicvideo");
5886 // writers
5887 m_pDS->exec("CREATE TABLE writer_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5888 m_pDS->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idMovie, 'movie' FROM writerlinkmovie");
5889 m_pDS->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idEpisode, 'episode' FROM writerlinkepisode");
5890 m_pDS->exec("DROP TABLE IF EXISTS writerlinkmovie");
5891 m_pDS->exec("DROP TABLE IF EXISTS writerlinkepisode");
5893 // music artist
5894 m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type) SELECT DISTINCT idArtist, idMVideo, 'musicvideo' FROM artistlinkmusicvideo");
5895 m_pDS->exec("DROP TABLE IF EXISTS artistlinkmusicvideo");
5897 // studios
5898 m_pDS->exec("CREATE TABLE studio_link(studio_id INTEGER, media_id INTEGER, media_type TEXT)");
5899 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMovie, 'movie' FROM studiolinkmovie");
5900 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idShow, 'tvshow' FROM studiolinktvshow");
5901 m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMVideo, 'musicvideo' FROM studiolinkmusicvideo");
5902 m_pDS->exec("DROP TABLE IF EXISTS studiolinkmovie");
5903 m_pDS->exec("DROP TABLE IF EXISTS studiolinktvshow");
5904 m_pDS->exec("DROP TABLE IF EXISTS studiolinkmusicvideo");
5905 m_pDS->exec("CREATE TABLE studionew(studio_id INTEGER PRIMARY KEY, name TEXT)");
5906 m_pDS->exec("INSERT INTO studionew(studio_id, name) SELECT idStudio,strStudio FROM studio");
5907 m_pDS->exec("DROP TABLE IF EXISTS studio");
5908 m_pDS->exec("ALTER TABLE studionew RENAME TO studio");
5910 // genres
5911 m_pDS->exec("CREATE TABLE genre_link(genre_id INTEGER, media_id INTEGER, media_type TEXT)");
5912 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMovie, 'movie' FROM genrelinkmovie");
5913 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idShow, 'tvshow' FROM genrelinktvshow");
5914 m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMVideo, 'musicvideo' FROM genrelinkmusicvideo");
5915 m_pDS->exec("DROP TABLE IF EXISTS genrelinkmovie");
5916 m_pDS->exec("DROP TABLE IF EXISTS genrelinktvshow");
5917 m_pDS->exec("DROP TABLE IF EXISTS genrelinkmusicvideo");
5918 m_pDS->exec("CREATE TABLE genrenew(genre_id INTEGER PRIMARY KEY, name TEXT)");
5919 m_pDS->exec("INSERT INTO genrenew(genre_id, name) SELECT idGenre,strGenre FROM genre");
5920 m_pDS->exec("DROP TABLE IF EXISTS genre");
5921 m_pDS->exec("ALTER TABLE genrenew RENAME TO genre");
5923 // country
5924 m_pDS->exec("CREATE TABLE country_link(country_id INTEGER, media_id INTEGER, media_type TEXT)");
5925 m_pDS->exec("INSERT INTO country_link(country_id, media_id, media_type) SELECT DISTINCT idCountry, idMovie, 'movie' FROM countrylinkmovie");
5926 m_pDS->exec("DROP TABLE IF EXISTS countrylinkmovie");
5927 m_pDS->exec("CREATE TABLE countrynew(country_id INTEGER PRIMARY KEY, name TEXT)");
5928 m_pDS->exec("INSERT INTO countrynew(country_id, name) SELECT idCountry,strCountry FROM country");
5929 m_pDS->exec("DROP TABLE IF EXISTS country");
5930 m_pDS->exec("ALTER TABLE countrynew RENAME TO country");
5932 // tags
5933 m_pDS->exec("CREATE TABLE tag_link(tag_id INTEGER, media_id INTEGER, media_type TEXT)");
5934 m_pDS->exec("INSERT INTO tag_link(tag_id, media_id, media_type) SELECT DISTINCT idTag, idMedia, media_type FROM taglinks");
5935 m_pDS->exec("DROP TABLE IF EXISTS taglinks");
5936 m_pDS->exec("CREATE TABLE tagnew(tag_id INTEGER PRIMARY KEY, name TEXT)");
5937 m_pDS->exec("INSERT INTO tagnew(tag_id, name) SELECT idTag,strTag FROM tag");
5938 m_pDS->exec("DROP TABLE IF EXISTS tag");
5939 m_pDS->exec("ALTER TABLE tagnew RENAME TO tag");
5942 if (iVersion < 93)
5944 // cleanup main tables
5945 std::string valuesSql;
5946 for(int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
5948 valuesSql += StringUtils::Format("c{:02} = TRIM(c{:02})", i, i);
5949 if (i < VIDEODB_MAX_COLUMNS - 1)
5950 valuesSql += ",";
5952 m_pDS->exec("UPDATE episode SET " + valuesSql);
5953 m_pDS->exec("UPDATE movie SET " + valuesSql);
5954 m_pDS->exec("UPDATE musicvideo SET " + valuesSql);
5955 m_pDS->exec("UPDATE tvshow SET " + valuesSql);
5957 // cleanup additional tables
5958 std::map<std::string, std::vector<std::string>> additionalTablesMap = {
5959 {"actor", {"actor_link", "director_link", "writer_link"}},
5960 {"studio", {"studio_link"}},
5961 {"genre", {"genre_link"}},
5962 {"country", {"country_link"}},
5963 {"tag", {"tag_link"}}
5965 for (const auto& additionalTableEntry : additionalTablesMap)
5967 std::string table = additionalTableEntry.first;
5968 std::string tablePk = additionalTableEntry.first + "_id";
5969 std::map<int, std::string> duplicatesMinMap;
5970 std::map<int, std::string> duplicatesMap;
5972 // cleanup name
5973 m_pDS->exec(PrepareSQL("UPDATE %s SET name = TRIM(name)",
5974 table.c_str()));
5976 // shrink name to length 255
5977 m_pDS->exec(PrepareSQL("UPDATE %s SET name = SUBSTR(name, 1, 255) WHERE LENGTH(name) > 255",
5978 table.c_str()));
5980 // fetch main entries
5981 m_pDS->query(PrepareSQL("SELECT MIN(%s), name FROM %s GROUP BY name HAVING COUNT(1) > 1",
5982 tablePk.c_str(), table.c_str()));
5984 while (!m_pDS->eof())
5986 duplicatesMinMap.insert(std::make_pair(m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asString()));
5987 m_pDS->next();
5989 m_pDS->close();
5991 // fetch duplicate entries
5992 for (const auto& entry : duplicatesMinMap)
5994 m_pDS->query(PrepareSQL("SELECT %s FROM %s WHERE name = '%s' AND %s <> %i",
5995 tablePk.c_str(), table.c_str(),
5996 entry.second.c_str(), tablePk.c_str(), entry.first));
5998 std::stringstream ids;
5999 while (!m_pDS->eof())
6001 int id = m_pDS->fv(0).get_asInt();
6002 m_pDS->next();
6004 ids << id;
6005 if (!m_pDS->eof())
6006 ids << ",";
6008 m_pDS->close();
6010 duplicatesMap.insert(std::make_pair(entry.first, ids.str()));
6013 // cleanup duplicates in link tables
6014 for (const auto& subTable : additionalTableEntry.second)
6016 // create indexes to speed up things
6017 m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s ON %s (%s)",
6018 subTable.c_str(), subTable.c_str(), tablePk.c_str()));
6020 // migrate every duplicate entry to the main entry
6021 for (const auto& entry : duplicatesMap)
6023 m_pDS->exec(PrepareSQL("UPDATE %s SET %s = %i WHERE %s IN (%s) ",
6024 subTable.c_str(), tablePk.c_str(), entry.first,
6025 tablePk.c_str(), entry.second.c_str()));
6028 // clear all duplicates in the link tables
6029 if (subTable == "actor_link")
6031 // as a distinct won't work because of role and cast_order and a group by kills a
6032 // low powered mysql, we de-dupe it with REPLACE INTO while using the real unique index
6033 m_pDS->exec("CREATE TABLE temp_actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
6034 m_pDS->exec("CREATE UNIQUE INDEX ix_temp_actor_link ON temp_actor_link (actor_id, media_type(20), media_id)");
6035 m_pDS->exec("REPLACE INTO temp_actor_link SELECT * FROM actor_link");
6036 m_pDS->exec("DROP INDEX ix_temp_actor_link ON temp_actor_link");
6038 else
6040 m_pDS->exec(PrepareSQL("CREATE TABLE temp_%s AS SELECT DISTINCT * FROM %s",
6041 subTable.c_str(), subTable.c_str()));
6044 m_pDS->exec(PrepareSQL("DROP TABLE IF EXISTS %s",
6045 subTable.c_str()));
6047 m_pDS->exec(PrepareSQL("ALTER TABLE temp_%s RENAME TO %s",
6048 subTable.c_str(), subTable.c_str()));
6051 // delete duplicates in main table
6052 for (const auto& entry : duplicatesMap)
6054 m_pDS->exec(PrepareSQL("DELETE FROM %s WHERE %s IN (%s)",
6055 table.c_str(), tablePk.c_str(), entry.second.c_str()));
6060 if (iVersion < 96)
6062 m_pDS->exec("ALTER TABLE movie ADD userrating integer");
6063 m_pDS->exec("ALTER TABLE episode ADD userrating integer");
6064 m_pDS->exec("ALTER TABLE tvshow ADD userrating integer");
6065 m_pDS->exec("ALTER TABLE musicvideo ADD userrating integer");
6068 if (iVersion < 97)
6069 m_pDS->exec("ALTER TABLE sets ADD strOverview TEXT");
6071 if (iVersion < 98)
6072 m_pDS->exec("ALTER TABLE seasons ADD name text");
6074 if (iVersion < 99)
6076 // Add idSeason to episode table, so we don't have to join via idShow and season in the future
6077 m_pDS->exec("ALTER TABLE episode ADD idSeason integer");
6079 m_pDS->query("SELECT idSeason, idShow, season FROM seasons");
6080 while (!m_pDS->eof())
6082 m_pDS2->exec(PrepareSQL("UPDATE episode "
6083 "SET idSeason = %d "
6084 "WHERE "
6085 "episode.idShow = %d AND "
6086 "episode.c%02d = %d",
6087 m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asInt(),
6088 VIDEODB_ID_EPISODE_SEASON, m_pDS->fv(2).get_asInt()));
6090 m_pDS->next();
6093 if (iVersion < 101)
6094 m_pDS->exec("ALTER TABLE seasons ADD userrating INTEGER");
6096 if (iVersion < 102)
6098 m_pDS->exec("CREATE TABLE rating (rating_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, rating_type TEXT, rating FLOAT, votes INTEGER)");
6100 std::string sql = PrepareSQL("SELECT DISTINCT idMovie, c%02d, c%02d FROM movie", VIDEODB_ID_RATING_ID, VIDEODB_ID_VOTES);
6101 m_pDS->query(sql);
6102 while (!m_pDS->eof())
6104 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6105 "votes) VALUES (%i, 'movie', 'default', %f, %i)",
6106 m_pDS->fv(0).get_asInt(),
6107 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
6108 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
6109 int idRating = (int)m_pDS2->lastinsertid();
6110 m_pDS2->exec(PrepareSQL("UPDATE movie SET c%02d=%i WHERE idMovie=%i", VIDEODB_ID_RATING_ID, idRating, m_pDS->fv(0).get_asInt()));
6111 m_pDS->next();
6113 m_pDS->close();
6115 sql = PrepareSQL("SELECT DISTINCT idShow, c%02d, c%02d FROM tvshow", VIDEODB_ID_TV_RATING_ID, VIDEODB_ID_TV_VOTES);
6116 m_pDS->query(sql);
6117 while (!m_pDS->eof())
6119 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6120 "votes) VALUES (%i, 'tvshow', 'default', %f, %i)",
6121 m_pDS->fv(0).get_asInt(),
6122 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
6123 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
6124 int idRating = (int)m_pDS2->lastinsertid();
6125 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()));
6126 m_pDS->next();
6128 m_pDS->close();
6130 sql = PrepareSQL("SELECT DISTINCT idEpisode, c%02d, c%02d FROM episode", VIDEODB_ID_EPISODE_RATING_ID, VIDEODB_ID_EPISODE_VOTES);
6131 m_pDS->query(sql);
6132 while (!m_pDS->eof())
6134 m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, "
6135 "votes) VALUES (%i, 'episode', 'default', %f, %i)",
6136 m_pDS->fv(0).get_asInt(),
6137 strtod(m_pDS->fv(1).get_asString().c_str(), NULL),
6138 StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
6139 int idRating = (int)m_pDS2->lastinsertid();
6140 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()));
6141 m_pDS->next();
6143 m_pDS->close();
6146 if (iVersion < 103)
6148 m_pDS->exec("ALTER TABLE settings ADD VideoStream integer");
6149 m_pDS->exec("ALTER TABLE streamdetails ADD strVideoLanguage text");
6152 if (iVersion < 104)
6154 m_pDS->exec("ALTER TABLE tvshow ADD duration INTEGER");
6156 std::string sql = PrepareSQL( "SELECT episode.idShow, MAX(episode.c%02d) "
6157 "FROM episode "
6159 "LEFT JOIN streamdetails "
6160 "ON streamdetails.idFile = episode.idFile "
6161 "AND streamdetails.iStreamType = 0 " // only grab video streams
6163 "WHERE episode.c%02d <> streamdetails.iVideoDuration "
6164 "OR streamdetails.iVideoDuration IS NULL "
6165 "GROUP BY episode.idShow", VIDEODB_ID_EPISODE_RUNTIME, VIDEODB_ID_EPISODE_RUNTIME);
6167 m_pDS->query(sql);
6168 while (!m_pDS->eof())
6170 m_pDS2->exec(PrepareSQL("UPDATE tvshow SET duration=%i WHERE idShow=%i", m_pDS->fv(1).get_asInt(), m_pDS->fv(0).get_asInt()));
6171 m_pDS->next();
6173 m_pDS->close();
6176 if (iVersion < 105)
6178 m_pDS->exec("ALTER TABLE movie ADD premiered TEXT");
6179 m_pDS->exec(PrepareSQL("UPDATE movie SET premiered=c%02d", VIDEODB_ID_YEAR));
6180 m_pDS->exec("ALTER TABLE musicvideo ADD premiered TEXT");
6181 m_pDS->exec(PrepareSQL("UPDATE musicvideo SET premiered=c%02d", VIDEODB_ID_MUSICVIDEO_YEAR));
6184 if (iVersion < 107)
6186 // need this due to the nested GetScraperPath query
6187 std::unique_ptr<Dataset> pDS;
6188 pDS.reset(m_pDB->CreateDataset());
6189 if (nullptr == pDS)
6190 return;
6192 pDS->exec("CREATE TABLE uniqueid (uniqueid_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, value TEXT, type TEXT)");
6194 for (int i = 0; i < 3; ++i)
6196 std::string mediatype, columnID;
6197 int columnUniqueID;
6198 switch (i)
6200 case (0):
6201 mediatype = "movie";
6202 columnID = "idMovie";
6203 columnUniqueID = VIDEODB_ID_IDENT_ID;
6204 break;
6205 case (1):
6206 mediatype = "tvshow";
6207 columnID = "idShow";
6208 columnUniqueID = VIDEODB_ID_TV_IDENT_ID;
6209 break;
6210 case (2):
6211 mediatype = "episode";
6212 columnID = "idEpisode";
6213 columnUniqueID = VIDEODB_ID_EPISODE_IDENT_ID;
6214 break;
6215 default:
6216 continue;
6218 pDS->query(PrepareSQL("SELECT %s, c%02d FROM %s", columnID.c_str(), columnUniqueID, mediatype.c_str()));
6219 while (!pDS->eof())
6221 std::string uniqueid = pDS->fv(1).get_asString();
6222 if (!uniqueid.empty())
6224 int mediaid = pDS->fv(0).get_asInt();
6225 if (StringUtils::StartsWith(uniqueid, "tt"))
6226 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()));
6227 else
6228 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()));
6229 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));
6231 pDS->next();
6233 pDS->close();
6237 if (iVersion < 109)
6239 m_pDS->exec("ALTER TABLE settings RENAME TO settingsold");
6240 m_pDS->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
6241 "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
6242 "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
6243 "VolumeAmplification float, AudioDelay float, ResumeTime integer,"
6244 "Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
6245 "ScalingMethod integer, DeinterlaceMode integer, StereoMode integer, StereoInvert bool, VideoStream integer)");
6246 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");
6247 m_pDS->exec("DROP TABLE settingsold");
6250 if (iVersion < 110)
6252 m_pDS->exec("ALTER TABLE settings ADD TonemapMethod integer");
6253 m_pDS->exec("ALTER TABLE settings ADD TonemapParam float");
6256 if (iVersion < 111)
6257 m_pDS->exec("ALTER TABLE settings ADD Orientation integer");
6259 if (iVersion < 112)
6260 m_pDS->exec("ALTER TABLE settings ADD CenterMixLevel integer");
6262 if (iVersion < 113)
6264 // fb9c25f5 and e5f6d204 changed the behavior of path splitting for plugin URIs (previously it would only use the root)
6265 // Re-split paths for plugin files in order to maintain watched state etc.
6266 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://%'");
6267 while (!m_pDS->eof())
6269 std::string path, fn;
6270 SplitPath(m_pDS->fv(1).get_asString(), path, fn);
6271 if (path != m_pDS->fv(2).get_asString())
6273 int pathid = -1;
6274 m_pDS2->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", path.c_str()));
6275 if (!m_pDS2->eof())
6276 pathid = m_pDS2->fv(0).get_asInt();
6277 m_pDS2->close();
6278 if (pathid < 0)
6280 std::string parent = URIUtils::GetParentPath(path);
6281 int parentid = -1;
6282 m_pDS2->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", parent.c_str()));
6283 if (!m_pDS2->eof())
6284 parentid = m_pDS2->fv(0).get_asInt();
6285 m_pDS2->close();
6286 if (parentid < 0)
6288 m_pDS2->exec(PrepareSQL("INSERT INTO path (strPath) VALUES ('%s')", parent.c_str()));
6289 parentid = (int)m_pDS2->lastinsertid();
6291 m_pDS2->exec(PrepareSQL("INSERT INTO path (strPath, idParentPath) VALUES ('%s', %i)", path.c_str(), parentid));
6292 pathid = (int)m_pDS2->lastinsertid();
6294 m_pDS2->query(PrepareSQL("SELECT idFile FROM files WHERE strFileName='%s' AND idPath=%i", fn.c_str(), pathid));
6295 bool exists = !m_pDS2->eof();
6296 m_pDS2->close();
6297 if (exists)
6298 m_pDS2->exec(PrepareSQL("DELETE FROM files WHERE idFile=%i", m_pDS->fv(0).get_asInt()));
6299 else
6300 m_pDS2->exec(PrepareSQL("UPDATE files SET idPath=%i WHERE idFile=%i", pathid, m_pDS->fv(0).get_asInt()));
6302 m_pDS->next();
6304 m_pDS->close();
6307 if (iVersion < 119)
6308 m_pDS->exec("ALTER TABLE path ADD allAudio bool");
6310 if (iVersion < 120)
6311 m_pDS->exec("ALTER TABLE streamdetails ADD strHdrType text");
6313 if (iVersion < 121)
6315 // https://github.com/xbmc/xbmc/issues/21253 - Kodi picks up wrong "year" for PVR recording.
6317 m_pDS->query("SELECT idFile, strFilename FROM files WHERE strFilename LIKE '% (1969)%.pvr' OR "
6318 "strFilename LIKE '% (1601)%.pvr'");
6319 while (!m_pDS->eof())
6321 std::string fixedFileName = m_pDS->fv(1).get_asString();
6322 size_t pos = fixedFileName.find(" (1969)");
6323 if (pos == std::string::npos)
6324 pos = fixedFileName.find(" (1601)");
6326 if (pos != std::string::npos)
6328 fixedFileName.erase(pos, 7);
6330 m_pDS2->exec(PrepareSQL("UPDATE files SET strFilename='%s' WHERE idFile=%i",
6331 fixedFileName.c_str(), m_pDS->fv(0).get_asInt()));
6333 m_pDS->next();
6335 m_pDS->close();
6338 if (iVersion < 123)
6340 // create videoversiontype table
6341 m_pDS->exec("CREATE TABLE videoversiontype (id INTEGER PRIMARY KEY, name TEXT, owner INTEGER)");
6342 InitializeVideoVersionTypeTable(iVersion);
6344 // create videoversion table
6345 m_pDS->exec("CREATE TABLE videoversion (idFile INTEGER PRIMARY KEY, idMedia INTEGER, mediaType "
6346 "TEXT, itemType INTEGER, idType INTEGER)");
6347 m_pDS->exec(PrepareSQL(
6348 "INSERT INTO videoversion SELECT idFile, idMovie, 'movie', '%i', '%i' FROM movie",
6349 VideoAssetType::VERSION, VIDEO_VERSION_ID_DEFAULT));
6352 if (iVersion < 127)
6354 m_pDS->exec("ALTER TABLE videoversiontype ADD itemType INTEGER");
6356 // First, assume all types are video version types
6357 m_pDS->exec(PrepareSQL("UPDATE videoversiontype SET itemType = %i", VideoAssetType::VERSION));
6359 // Then, check current extras entries and their assigned item type and migrate it
6361 // get all assets with extras item type
6362 m_pDS->query("SELECT DISTINCT idType FROM videoversion WHERE itemType = 1");
6363 while (!m_pDS->eof())
6365 const int idType{m_pDS->fv(0).get_asInt()};
6366 if (idType > VIDEO_VERSION_ID_END)
6368 // user-added type for extras. change its item type to extras
6369 m_pDS2->exec(PrepareSQL("UPDATE videoversiontype SET itemType = %i WHERE id = %i",
6370 VideoAssetType::EXTRA, idType));
6372 else
6374 // system type used for an extra. copy as extras item type.
6375 m_pDS2->query(
6376 PrepareSQL("SELECT itemType, name FROM videoversiontype WHERE id = %i", idType));
6377 if (m_pDS2->fv(0).get_asInt() == 0)
6379 // currently a versions type, create a corresponding user-added type for extras
6380 m_pDS2->exec(PrepareSQL(
6381 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(NULL, '%s', %i, %i)",
6382 m_pDS2->fv(1).get_asString().c_str(), VideoAssetTypeOwner::USER,
6383 VideoAssetType::EXTRA));
6385 // update the respective extras to use the new extras type
6386 const int newId{static_cast<int>(m_pDS2->lastinsertid())};
6387 m_pDS2->exec(
6388 PrepareSQL("UPDATE videoversion SET idType = %i WHERE itemType = 1 AND idType = %i",
6389 newId, idType));
6392 m_pDS->next();
6394 m_pDS->close();
6397 if (iVersion < 128)
6399 m_pDS->exec("CREATE TABLE videoversion_new "
6400 "(idFile INTEGER PRIMARY KEY, idMedia INTEGER, media_type TEXT, "
6401 " itemType INTEGER, idType INTEGER)");
6402 m_pDS->exec("INSERT INTO videoversion_new "
6403 " (idFile, idMedia, media_type, itemType, idType) "
6404 "SELECT idFile, idMedia, mediaType, itemType, idType FROM videoversion");
6405 m_pDS->exec("DROP TABLE videoversion");
6406 m_pDS->exec("ALTER TABLE videoversion_new RENAME TO videoversion");
6408 // Fix gap in the migration to videodb v127 for unused user-defined video version types.
6409 // Unfortunately due to original design we cannot tell which ones were movie versions or
6410 // extras and now they're all displayed in the version type selection for movies.
6411 // Remove them all as the better fix of providing a GUI to manage version types will not be
6412 // available in Omega v21. That implies the loss of the unused user-defined version names
6413 // created since v21 beta 2.
6414 m_pDS2->exec(PrepareSQL("DELETE FROM videoversiontype "
6415 "WHERE id NOT IN (SELECT idType FROM videoversion) "
6416 "AND owner = %i "
6417 "AND itemType = %i",
6418 VideoAssetTypeOwner::USER, VideoAssetType::VERSION));
6421 if (iVersion < 131)
6423 // Remove quality-like predefined version types
6425 // Retrieve current utilization per type
6426 m_pDS->query("SELECT vvt.id, vvt.name, count(vv.idType) "
6427 "FROM videoversiontype vvt "
6428 " LEFT JOIN videoversion vv ON vvt.id = vv.idType "
6429 "WHERE vvt.id = 40405 OR vvt.id BETWEEN 40418 AND 40430 "
6430 "GROUP BY vvt.id");
6432 while (!m_pDS->eof())
6434 const int typeId{m_pDS->fv(0).get_asInt()};
6435 const std::string typeName{m_pDS->fv(1).get_asString()};
6436 const int versionsCount{m_pDS->fv(2).get_asInt()};
6438 if (versionsCount > 0)
6440 // type used by some versions, recreate as user type and link the versions to the new id
6441 m_pDS2->exec(PrepareSQL(
6442 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(NULL, '%s', %i, %i)",
6443 typeName.c_str(), VideoAssetTypeOwner::USER, VideoAssetType::VERSION));
6445 const int newId{static_cast<int>(m_pDS2->lastinsertid())};
6447 m_pDS2->exec(
6448 PrepareSQL("UPDATE videoversion SET idType = %i WHERE idType = %i", newId, typeId));
6450 m_pDS2->exec(PrepareSQL("DELETE FROM videoversiontype WHERE id = %i", typeId));
6451 m_pDS->next();
6453 m_pDS->close();
6457 int CVideoDatabase::GetSchemaVersion() const
6459 return 132;
6462 bool CVideoDatabase::LookupByFolders(const std::string &path, bool shows)
6464 SScanSettings settings;
6465 bool foundDirectly = false;
6466 ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
6467 if (scraper && scraper->Content() == CONTENT_TVSHOWS && !shows)
6468 return false; // episodes
6469 return settings.parent_name_root; // shows, movies, musicvids
6472 bool CVideoDatabase::GetPlayCounts(const std::string &strPath, CFileItemList &items)
6474 if(URIUtils::IsMultiPath(strPath))
6476 std::vector<std::string> paths;
6477 CMultiPathDirectory::GetPaths(strPath, paths);
6479 bool ret = false;
6480 for(unsigned i=0;i<paths.size();i++)
6481 ret |= GetPlayCounts(paths[i], items);
6483 return ret;
6485 int pathID = -1;
6486 if (!URIUtils::IsPlugin(strPath))
6488 pathID = GetPathId(strPath);
6489 if (pathID < 0)
6490 return false; // path (and thus files) aren't in the database
6495 // error!
6496 if (nullptr == m_pDB)
6497 return false;
6498 if (nullptr == m_pDS)
6499 return false;
6501 std::string sql =
6502 "SELECT"
6503 " files.strFilename, files.playCount,"
6504 " bookmark.timeInSeconds, bookmark.totalTimeInSeconds "
6505 "FROM files"
6506 " LEFT JOIN bookmark ON"
6507 " files.idFile = bookmark.idFile AND bookmark.type = %i ";
6509 if (URIUtils::IsPlugin(strPath))
6511 for (auto& item : items)
6513 if (!item || item->m_bIsFolder || !item->GetProperty("IsPlayable").asBoolean())
6514 continue;
6516 std::string path, filename;
6517 SplitPath(item->GetPath(), path, filename);
6518 m_pDS->query(PrepareSQL(sql +
6519 "INNER JOIN path ON files.idPath = path.idPath "
6520 "WHERE files.strFilename='%s' AND path.strPath='%s'",
6521 (int)CBookmark::RESUME, filename.c_str(), path.c_str()));
6523 if (!m_pDS->eof())
6525 if (!item->GetVideoInfoTag()->IsPlayCountSet())
6526 item->GetVideoInfoTag()->SetPlayCount(m_pDS->fv(1).get_asInt());
6527 if (!item->GetVideoInfoTag()->GetResumePoint().IsSet())
6528 item->GetVideoInfoTag()->SetResumePoint(m_pDS->fv(2).get_asInt(), m_pDS->fv(3).get_asInt(), "");
6530 m_pDS->close();
6533 else
6535 //! @todo also test a single query for the above and below
6536 sql = PrepareSQL(sql + "WHERE files.idPath=%i", (int)CBookmark::RESUME, pathID);
6538 if (RunQuery(sql) <= 0)
6539 return false;
6541 items.SetFastLookup(true); // note: it's possibly quicker the other way around (map on db returned items)?
6542 while (!m_pDS->eof())
6544 std::string path;
6545 ConstructPath(path, strPath, m_pDS->fv(0).get_asString());
6546 CFileItemPtr item = items.Get(path);
6547 if (item)
6549 if (!items.IsPlugin() || !item->GetVideoInfoTag()->IsPlayCountSet())
6550 item->GetVideoInfoTag()->SetPlayCount(m_pDS->fv(1).get_asInt());
6552 if (!item->GetVideoInfoTag()->GetResumePoint().IsSet())
6554 item->GetVideoInfoTag()->SetResumePoint(m_pDS->fv(2).get_asInt(), m_pDS->fv(3).get_asInt(), "");
6557 m_pDS->next();
6561 return true;
6563 catch (...)
6565 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6567 return false;
6570 int CVideoDatabase::GetPlayCount(int iFileId)
6572 if (iFileId < 0)
6573 return 0; // not in db, so not watched
6577 // error!
6578 if (nullptr == m_pDB)
6579 return -1;
6580 if (nullptr == m_pDS)
6581 return -1;
6583 std::string strSQL = PrepareSQL("select playCount from files WHERE idFile=%i", iFileId);
6584 int count = 0;
6585 if (m_pDS->query(strSQL))
6587 // there should only ever be one row returned
6588 if (m_pDS->num_rows() == 1)
6589 count = m_pDS->fv(0).get_asInt();
6590 m_pDS->close();
6592 return count;
6594 catch (...)
6596 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6598 return -1;
6601 int CVideoDatabase::GetPlayCount(const std::string& strFilenameAndPath)
6603 return GetPlayCount(GetFileId(strFilenameAndPath));
6606 int CVideoDatabase::GetPlayCount(const CFileItem &item)
6608 if (IsBlurayPlaylist(item))
6609 return GetPlayCount(GetFileId(item.GetDynPath()));
6610 else
6611 return GetPlayCount(GetFileId(item));
6614 CDateTime CVideoDatabase::GetLastPlayed(int iFileId)
6616 if (iFileId < 0)
6617 return {}; // not in db, so not watched
6621 // error!
6622 if (nullptr == m_pDB)
6623 return {};
6624 if (nullptr == m_pDS)
6625 return {};
6627 std::string strSQL = PrepareSQL("select lastPlayed from files WHERE idFile=%i", iFileId);
6628 CDateTime lastPlayed;
6629 if (m_pDS->query(strSQL))
6631 // there should only ever be one row returned
6632 if (m_pDS->num_rows() == 1)
6633 lastPlayed.SetFromDBDateTime(m_pDS->fv(0).get_asString());
6634 m_pDS->close();
6636 return lastPlayed;
6638 catch (...)
6640 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6642 return {};
6645 CDateTime CVideoDatabase::GetLastPlayed(const std::string& strFilenameAndPath)
6647 return GetLastPlayed(GetFileId(strFilenameAndPath));
6650 void CVideoDatabase::UpdateFanart(const CFileItem& item, VideoDbContentType type)
6652 if (nullptr == m_pDB)
6653 return;
6654 if (nullptr == m_pDS)
6655 return;
6656 if (!item.HasVideoInfoTag() || item.GetVideoInfoTag()->m_iDbId < 0) return;
6658 std::string exec;
6659 if (type == VideoDbContentType::TVSHOWS)
6660 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);
6661 else if (type == VideoDbContentType::MOVIES)
6662 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);
6666 m_pDS->exec(exec);
6668 if (type == VideoDbContentType::TVSHOWS)
6669 AnnounceUpdate(MediaTypeTvShow, item.GetVideoInfoTag()->m_iDbId);
6670 else if (type == VideoDbContentType::MOVIES)
6671 AnnounceUpdate(MediaTypeMovie, item.GetVideoInfoTag()->m_iDbId);
6673 catch (...)
6675 CLog::Log(LOGERROR, "{} - error updating fanart for {}", __FUNCTION__, item.GetPath());
6679 CDateTime CVideoDatabase::SetPlayCount(const CFileItem& item, int count, const CDateTime& date)
6681 int id{-1};
6682 if (IsBlurayPlaylist(item))
6683 id = AddFile(item.GetDynPath());
6684 else if (item.HasProperty("original_listitem_url") &&
6685 URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString()))
6687 CFileItem item2(item);
6688 item2.SetPath(item.GetProperty("original_listitem_url").asString());
6689 id = AddFile(item2);
6691 else
6692 id = AddFile(item);
6693 if (id < 0)
6694 return {};
6696 // and mark as watched
6699 const CDateTime lastPlayed(date.IsValid() ? date : CDateTime::GetCurrentDateTime());
6701 if (nullptr == m_pDB)
6702 return {};
6703 if (nullptr == m_pDS)
6704 return {};
6706 std::string strSQL;
6707 if (count)
6709 strSQL = PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", count,
6710 lastPlayed.GetAsDBDateTime().c_str(), id);
6712 else
6714 if (!date.IsValid())
6715 strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed=NULL where idFile=%i", id);
6716 else
6717 strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed='%s' where idFile=%i",
6718 lastPlayed.GetAsDBDateTime().c_str(), id);
6721 m_pDS->exec(strSQL);
6723 // We only need to announce changes to video items in the library
6724 if (item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId > 0)
6726 CVariant data;
6727 if (CVideoLibraryQueue::GetInstance().IsScanningLibrary())
6728 data["transaction"] = true;
6729 // Only provide the "playcount" value if it has actually changed
6730 if (item.GetVideoInfoTag()->GetPlayCount() != count)
6731 data["playcount"] = count;
6732 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnUpdate",
6733 std::make_shared<CFileItem>(item), data);
6736 return lastPlayed;
6738 catch (...)
6740 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6743 return {};
6746 CDateTime CVideoDatabase::IncrementPlayCount(const CFileItem& item)
6748 return SetPlayCount(item, GetPlayCount(item) + 1);
6751 CDateTime CVideoDatabase::UpdateLastPlayed(const CFileItem& item)
6753 return SetPlayCount(item, GetPlayCount(item), CDateTime::GetCurrentDateTime());
6756 void CVideoDatabase::UpdateMovieTitle(int idMovie,
6757 const std::string& strNewMovieTitle,
6758 VideoDbContentType iType)
6762 if (nullptr == m_pDB)
6763 return;
6764 if (nullptr == m_pDS)
6765 return;
6766 std::string content;
6767 if (iType == VideoDbContentType::MOVIES)
6769 CLog::Log(LOGINFO, "Changing Movie:id:{} New Title:{}", idMovie, strNewMovieTitle);
6770 content = MediaTypeMovie;
6772 else if (iType == VideoDbContentType::EPISODES)
6774 CLog::Log(LOGINFO, "Changing Episode:id:{} New Title:{}", idMovie, strNewMovieTitle);
6775 content = MediaTypeEpisode;
6777 else if (iType == VideoDbContentType::TVSHOWS)
6779 CLog::Log(LOGINFO, "Changing TvShow:id:{} New Title:{}", idMovie, strNewMovieTitle);
6780 content = MediaTypeTvShow;
6782 else if (iType == VideoDbContentType::MUSICVIDEOS)
6784 CLog::Log(LOGINFO, "Changing MusicVideo:id:{} New Title:{}", idMovie, strNewMovieTitle);
6785 content = MediaTypeMusicVideo;
6787 else if (iType == VideoDbContentType::MOVIE_SETS)
6789 CLog::Log(LOGINFO, "Changing Movie set:id:{} New Title:{}", idMovie, strNewMovieTitle);
6790 std::string strSQL = PrepareSQL("UPDATE sets SET strSet='%s' WHERE idSet=%i", strNewMovieTitle.c_str(), idMovie );
6791 m_pDS->exec(strSQL);
6794 if (!content.empty())
6796 SetSingleValue(iType, idMovie, FieldTitle, strNewMovieTitle);
6797 AnnounceUpdate(content, idMovie);
6800 catch (...)
6802 CLog::Log(
6803 LOGERROR,
6804 "{} (int idMovie, const std::string& strNewMovieTitle) failed on MovieID:{} and Title:{}",
6805 __FUNCTION__, idMovie, strNewMovieTitle);
6809 bool CVideoDatabase::UpdateVideoSortTitle(int idDb,
6810 const std::string& strNewSortTitle,
6811 VideoDbContentType iType /* = MOVIES */)
6815 if (nullptr == m_pDB || nullptr == m_pDS)
6816 return false;
6817 if (iType != VideoDbContentType::MOVIES && iType != VideoDbContentType::TVSHOWS)
6818 return false;
6820 std::string content = MediaTypeMovie;
6821 if (iType == VideoDbContentType::TVSHOWS)
6822 content = MediaTypeTvShow;
6824 if (SetSingleValue(iType, idDb, FieldSortTitle, strNewSortTitle))
6826 AnnounceUpdate(content, idDb);
6827 return true;
6830 catch (...)
6832 CLog::Log(LOGERROR,
6833 "{} (int idDb, const std::string& strNewSortTitle, VIDEODB_CONTENT_TYPE iType) "
6834 "failed on ID: {} and Sort Title: {}",
6835 __FUNCTION__, idDb, strNewSortTitle);
6838 return false;
6841 /// \brief EraseVideoSettings() Erases the videoSettings table and reconstructs it
6842 void CVideoDatabase::EraseVideoSettings(const CFileItem &item)
6844 int idFile = GetFileId(item);
6845 if (idFile < 0)
6846 return;
6850 std::string sql = PrepareSQL("DELETE FROM settings WHERE idFile=%i", idFile);
6852 CLog::Log(LOGINFO, "Deleting settings information for files {}",
6853 CURL::GetRedacted(item.GetPath()));
6854 m_pDS->exec(sql);
6856 catch (...)
6858 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6862 void CVideoDatabase::EraseAllVideoSettings()
6866 std::string sql = "DELETE FROM settings";
6868 CLog::Log(LOGINFO, "Deleting all video settings");
6869 m_pDS->exec(sql);
6871 catch (...)
6873 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6877 void CVideoDatabase::EraseAllVideoSettings(const std::string& path)
6879 std::string itemsToDelete;
6883 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());
6884 m_pDS->query(sql);
6885 while (!m_pDS->eof())
6887 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
6888 itemsToDelete += file;
6889 m_pDS->next();
6891 m_pDS->close();
6893 if (!itemsToDelete.empty())
6895 itemsToDelete = "(" + StringUtils::TrimRight(itemsToDelete, ",") + ")";
6897 sql = "DELETE FROM settings WHERE idFile IN " + itemsToDelete;
6898 m_pDS->exec(sql);
6901 catch (...)
6903 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
6907 bool CVideoDatabase::GetGenresNav(const std::string& strBaseDir,
6908 CFileItemList& items,
6909 VideoDbContentType idContent /* = UNKNOWN */,
6910 const Filter& filter /* = Filter() */,
6911 bool countOnly /* = false */)
6913 return GetNavCommon(strBaseDir, items, "genre", idContent, filter, countOnly);
6916 bool CVideoDatabase::GetCountriesNav(const std::string& strBaseDir,
6917 CFileItemList& items,
6918 VideoDbContentType idContent /* = UNKNOWN */,
6919 const Filter& filter /* = Filter() */,
6920 bool countOnly /* = false */)
6922 return GetNavCommon(strBaseDir, items, "country", idContent, filter, countOnly);
6925 bool CVideoDatabase::GetStudiosNav(const std::string& strBaseDir,
6926 CFileItemList& items,
6927 VideoDbContentType idContent /* = UNKNOWN */,
6928 const Filter& filter /* = Filter() */,
6929 bool countOnly /* = false */)
6931 return GetNavCommon(strBaseDir, items, "studio", idContent, filter, countOnly);
6934 bool CVideoDatabase::GetNavCommon(const std::string& strBaseDir,
6935 CFileItemList& items,
6936 const char* type,
6937 VideoDbContentType idContent /* = UNKNOWN */,
6938 const Filter& filter /* = Filter() */,
6939 bool countOnly /* = false */)
6943 if (nullptr == m_pDB)
6944 return false;
6945 if (nullptr == m_pDS)
6946 return false;
6948 std::string strSQL;
6949 Filter extFilter = filter;
6950 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6952 std::string view, view_id, media_type, extraField, extraJoin;
6953 if (idContent == VideoDbContentType::MOVIES)
6955 view = MediaTypeMovie;
6956 view_id = "idMovie";
6957 media_type = MediaTypeMovie;
6958 extraField = "files.playCount";
6960 else if (idContent == VideoDbContentType::TVSHOWS) //this will not get tvshows with 0 episodes
6962 view = MediaTypeEpisode;
6963 view_id = "idShow";
6964 media_type = MediaTypeTvShow;
6965 // in order to make use of FieldPlaycount in smart playlists we need an extra join
6966 if (StringUtils::EqualsNoCase(type, "tag"))
6967 extraJoin = PrepareSQL("JOIN tvshow_view ON tvshow_view.idShow = tag_link.media_id AND tag_link.media_type='tvshow'");
6969 else if (idContent == VideoDbContentType::MUSICVIDEOS)
6971 view = MediaTypeMusicVideo;
6972 view_id = "idMVideo";
6973 media_type = MediaTypeMusicVideo;
6974 extraField = "files.playCount";
6976 else
6977 return false;
6979 strSQL = "SELECT {} " + PrepareSQL("FROM %s ", type);
6980 extFilter.fields = PrepareSQL("%s.%s_id, %s.name, path.strPath", type, type, type);
6981 extFilter.AppendField(extraField);
6982 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type, type, type, type, type));
6983 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()));
6984 extFilter.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str()));
6985 extFilter.AppendJoin("JOIN path ON path.idPath = files.idPath");
6986 extFilter.AppendJoin(extraJoin);
6988 else
6990 std::string view, view_id, media_type, extraField, extraJoin;
6991 if (idContent == VideoDbContentType::MOVIES)
6993 view = MediaTypeMovie;
6994 view_id = "idMovie";
6995 media_type = MediaTypeMovie;
6996 extraField = "count(1), count(files.playCount)";
6997 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
6999 else if (idContent == VideoDbContentType::TVSHOWS)
7001 view = MediaTypeTvShow;
7002 view_id = "idShow";
7003 media_type = MediaTypeTvShow;
7005 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7007 view = MediaTypeMusicVideo;
7008 view_id = "idMVideo";
7009 media_type = MediaTypeMusicVideo;
7010 extraField = "count(1), count(files.playCount)";
7011 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
7013 else
7014 return false;
7016 strSQL = "SELECT {} " + PrepareSQL("FROM %s ", type);
7017 extFilter.fields = PrepareSQL("%s.%s_id, %s.name", type, type, type);
7018 extFilter.AppendField(extraField);
7019 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type, type, type, type, type));
7020 extFilter.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'",
7021 view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
7022 extFilter.AppendJoin(extraJoin);
7023 extFilter.AppendGroup(PrepareSQL("%s.%s_id", type, type));
7026 if (countOnly)
7028 extFilter.fields = PrepareSQL("COUNT(DISTINCT %s.%s_id)", type, type);
7029 extFilter.group.clear();
7030 extFilter.order.clear();
7032 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
7034 CVideoDbUrl videoUrl;
7035 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
7036 return false;
7038 int iRowsFound = RunQuery(strSQL);
7039 if (iRowsFound <= 0)
7040 return iRowsFound == 0;
7042 if (countOnly)
7044 CFileItemPtr pItem(new CFileItem());
7045 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
7046 items.Add(pItem);
7048 m_pDS->close();
7049 return true;
7052 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7054 std::map<int, std::pair<std::string,int> > mapItems;
7055 while (!m_pDS->eof())
7057 int id = m_pDS->fv(0).get_asInt();
7058 std::string str = m_pDS->fv(1).get_asString();
7060 // was this already found?
7061 auto it = mapItems.find(id);
7062 if (it == mapItems.end())
7064 // check path
7065 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7067 if (idContent == VideoDbContentType::MOVIES ||
7068 idContent == VideoDbContentType::MUSICVIDEOS)
7069 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
7070 else if (idContent == VideoDbContentType::TVSHOWS)
7071 mapItems.insert(std::pair<int, std::pair<std::string,int> >(id, std::pair<std::string,int>(str,0)));
7074 m_pDS->next();
7076 m_pDS->close();
7078 for (const auto &i : mapItems)
7080 CFileItemPtr pItem(new CFileItem(i.second.first));
7081 pItem->GetVideoInfoTag()->m_iDbId = i.first;
7082 pItem->GetVideoInfoTag()->m_type = type;
7084 CVideoDbUrl itemUrl = videoUrl;
7085 std::string path = StringUtils::Format("{}/", i.first);
7086 itemUrl.AppendPath(path);
7087 pItem->SetPath(itemUrl.ToString());
7089 pItem->m_bIsFolder = true;
7090 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7091 pItem->GetVideoInfoTag()->SetPlayCount(i.second.second);
7092 if (!items.Contains(pItem->GetPath()))
7094 pItem->SetLabelPreformatted(true);
7095 items.Add(pItem);
7099 else
7101 while (!m_pDS->eof())
7103 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
7104 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
7105 pItem->GetVideoInfoTag()->m_type = type;
7107 CVideoDbUrl itemUrl = videoUrl;
7108 std::string path = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
7109 itemUrl.AppendPath(path);
7110 pItem->SetPath(itemUrl.ToString());
7112 pItem->m_bIsFolder = true;
7113 pItem->SetLabelPreformatted(true);
7114 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7115 { // fv(3) is the number of videos watched, fv(2) is the total number. We set the playcount
7116 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7117 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(3).get_asInt() == m_pDS->fv(2).get_asInt()) ? 1 : 0);
7119 items.Add(pItem);
7120 m_pDS->next();
7122 m_pDS->close();
7124 return true;
7126 catch (...)
7128 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7130 return false;
7133 bool CVideoDatabase::GetTagsNav(const std::string& strBaseDir,
7134 CFileItemList& items,
7135 VideoDbContentType idContent /* = UNKNOWN */,
7136 const Filter& filter /* = Filter() */,
7137 bool countOnly /* = false */)
7139 return GetNavCommon(strBaseDir, items, "tag", idContent, filter, countOnly);
7142 bool CVideoDatabase::GetSetsNav(const std::string& strBaseDir,
7143 CFileItemList& items,
7144 VideoDbContentType idContent /* = UNKNOWN */,
7145 const Filter& filter /* = Filter() */,
7146 bool ignoreSingleMovieSets /* = false */)
7148 if (idContent != VideoDbContentType::MOVIES)
7149 return false;
7151 return GetSetsByWhere(strBaseDir, filter, items, ignoreSingleMovieSets);
7154 bool CVideoDatabase::GetSetsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool ignoreSingleMovieSets /* = false */)
7158 if (nullptr == m_pDB)
7159 return false;
7160 if (nullptr == m_pDS)
7161 return false;
7163 CVideoDbUrl videoUrl;
7164 if (!videoUrl.FromString(strBaseDir))
7165 return false;
7167 Filter setFilter = filter;
7168 setFilter.join += " JOIN sets ON movie_view.idSet = sets.idSet";
7169 if (!setFilter.order.empty())
7170 setFilter.order += ",";
7171 setFilter.order += "sets.idSet";
7173 if (!GetMoviesByWhere(strBaseDir, setFilter, items))
7174 return false;
7176 CFileItemList sets;
7177 GroupAttribute groupingAttributes;
7178 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
7179 auto option = options.find("ignoreSingleMovieSets");
7181 if (option != options.end())
7183 groupingAttributes =
7184 option->second.asBoolean() ? GroupAttributeIgnoreSingleItems : GroupAttributeNone;
7186 else
7188 groupingAttributes =
7189 ignoreSingleMovieSets ? GroupAttributeIgnoreSingleItems : GroupAttributeNone;
7192 if (!GroupUtils::Group(GroupBySet, strBaseDir, items, sets, groupingAttributes))
7193 return false;
7195 items.ClearItems();
7196 items.Append(sets);
7198 return true;
7200 catch (...)
7202 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7204 return false;
7207 bool CVideoDatabase::GetMusicVideoAlbumsNav(const std::string& strBaseDir, CFileItemList& items, int idArtist /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
7211 if (nullptr == m_pDB)
7212 return false;
7213 if (nullptr == m_pDS)
7214 return false;
7216 CVideoDbUrl videoUrl;
7217 if (!videoUrl.FromString(strBaseDir))
7218 return false;
7220 std::string strSQL = "select {} from musicvideo_view ";
7221 Filter extFilter = filter;
7222 extFilter.fields = PrepareSQL("musicvideo_view.c%02d, musicvideo_view.idMVideo, actor.name, "
7223 "musicvideo_view.c%02d, musicvideo_view.c%02d, musicvideo_view.c%02d ",
7224 VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_TITLE,
7225 VIDEODB_ID_MUSICVIDEO_PLOT, VIDEODB_ID_MUSICVIDEO_ARTIST);
7226 extFilter.AppendJoin(
7227 PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo "));
7228 extFilter.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id = actor_link.actor_id"));
7229 extFilter.fields += ", path.strPath";
7230 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on path.idPath = files.idPath");
7232 if (StringUtils::EndsWith(strBaseDir,"albums/"))
7233 extFilter.AppendWhere(PrepareSQL("musicvideo_view.c%02d != ''", VIDEODB_ID_MUSICVIDEO_ALBUM));
7235 if (idArtist > -1)
7236 videoUrl.AddOption("artistid", idArtist);
7238 extFilter.AppendGroup(PrepareSQL(" CASE WHEN musicvideo_view.c09 !='' THEN musicvideo_view.c09 "
7239 "ELSE musicvideo_view.c00 END"));
7241 if (countOnly)
7243 extFilter.fields = "COUNT(1)";
7244 extFilter.group.clear();
7245 extFilter.order.clear();
7247 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
7249 if (!BuildSQL(videoUrl.ToString(), strSQL, extFilter, strSQL, videoUrl))
7250 return false;
7252 int iRowsFound = RunQuery(strSQL);
7253 /* fields returned by query are :-
7254 (0) - Album title (if any)
7255 (1) - idMVideo
7256 (2) - Artist name
7257 (3) - Music video title
7258 (4) - Music video plot
7259 (5) - Music Video artist
7260 (6) - Path to video
7262 if (iRowsFound <= 0)
7263 return iRowsFound == 0;
7265 std::string strArtist;
7266 if (idArtist> -1)
7267 strArtist = m_pDS->fv("actor.name").get_asString();
7269 if (countOnly)
7271 CFileItemPtr pItem(new CFileItem());
7272 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
7273 items.Add(pItem);
7275 m_pDS->close();
7276 return true;
7279 std::list <int> idMVideoList;
7280 std::list <std::pair<std::string, std::string>> idData;
7282 while (!m_pDS->eof())
7284 bool isAlbum = true;
7285 std::string strAlbum = m_pDS->fv(0).get_asString(); //Album title
7286 int idMVideo = m_pDS->fv(1).get_asInt();
7287 if (strAlbum.empty())
7289 strAlbum = m_pDS->fv(3).get_asString(); // video title if not an album
7290 isAlbum = false;
7293 CFileItemPtr pItem(new CFileItem(strAlbum));
7295 CVideoDbUrl itemUrl = videoUrl;
7296 std::string path = StringUtils::Format("{}/", idMVideo);
7297 if (!isAlbum)
7299 itemUrl.AddOption("albumid", idMVideo);
7300 path += std::to_string(idMVideo);
7302 strSQL = PrepareSQL(
7303 "SELECT type, url FROM art WHERE media_id = %i AND media_type = 'musicvideo'",
7304 idMVideo);
7305 m_pDS2->query(strSQL);
7306 while (!m_pDS2->eof())
7308 pItem->SetArt(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString());
7309 m_pDS2->next();
7311 m_pDS2->close();
7313 itemUrl.AppendPath(path);
7314 pItem->SetPath(itemUrl.ToString());
7315 pItem->m_bIsFolder = isAlbum;
7316 pItem->SetLabelPreformatted(true);
7318 if (!items.Contains(pItem->GetPath()))
7319 if (g_passwordManager.IsDatabasePathUnlocked(
7320 m_pDS->fv("path.strPath").get_asString(),
7321 *CMediaSourceSettings::GetInstance().GetSources("video")))
7323 pItem->GetVideoInfoTag()->m_artist.emplace_back(strArtist);
7324 pItem->GetVideoInfoTag()->m_iDbId = idMVideo;
7325 items.Add(pItem);
7326 idMVideoList.push_back(idMVideo);
7327 idData.emplace_back(m_pDS->fv(0).get_asString(), m_pDS->fv(5).get_asString());
7329 m_pDS->next();
7331 m_pDS->close();
7333 for (int i = 0; i < items.Size(); i++)
7335 CVideoInfoTag details;
7337 if (items[i]->m_bIsFolder)
7339 details.SetPath(items[i]->GetPath());
7340 details.m_strAlbum = idData.front().first;
7341 details.m_type = MediaTypeAlbum;
7342 details.m_artist.emplace_back(idData.front().second);
7343 details.m_iDbId = idMVideoList.front();
7344 items[i]->SetProperty("musicvideomediatype", MediaTypeAlbum);
7345 items[i]->SetLabel(idData.front().first);
7346 items[i]->SetFromVideoInfoTag(details);
7348 idMVideoList.pop_front();
7349 idData.pop_front();
7350 continue;
7352 else
7354 GetMusicVideoInfo("", details, idMVideoList.front());
7355 items[i]->SetFromVideoInfoTag(details);
7356 idMVideoList.pop_front();
7357 idData.pop_front();
7361 if (!strArtist.empty())
7362 items.SetProperty("customtitle",strArtist); // change displayed path from eg /23 to /Artist
7364 return true;
7366 catch (...)
7368 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7370 return false;
7373 bool CVideoDatabase::GetWritersNav(const std::string& strBaseDir,
7374 CFileItemList& items,
7375 VideoDbContentType idContent /* = UNKNOWN */,
7376 const Filter& filter /* = Filter() */,
7377 bool countOnly /* = false */)
7379 return GetPeopleNav(strBaseDir, items, "writer", idContent, filter, countOnly);
7382 bool CVideoDatabase::GetDirectorsNav(const std::string& strBaseDir,
7383 CFileItemList& items,
7384 VideoDbContentType idContent /* = UNKNOWN */,
7385 const Filter& filter /* = Filter() */,
7386 bool countOnly /* = false */)
7388 return GetPeopleNav(strBaseDir, items, "director", idContent, filter, countOnly);
7391 bool CVideoDatabase::GetActorsNav(const std::string& strBaseDir,
7392 CFileItemList& items,
7393 VideoDbContentType idContent /* = UNKNOWN */,
7394 const Filter& filter /* = Filter() */,
7395 bool countOnly /* = false */)
7397 if (GetPeopleNav(strBaseDir, items, "actor", idContent, filter, countOnly))
7398 { // set thumbs - ideally this should be in the normal thumb setting routines
7399 for (int i = 0; i < items.Size() && !countOnly; i++)
7401 CFileItemPtr pItem = items[i];
7402 if (idContent == VideoDbContentType::MUSICVIDEOS)
7403 pItem->SetArt("icon", "DefaultArtist.png");
7404 else
7405 pItem->SetArt("icon", "DefaultActor.png");
7407 return true;
7409 return false;
7412 bool CVideoDatabase::GetPeopleNav(const std::string& strBaseDir,
7413 CFileItemList& items,
7414 const char* type,
7415 VideoDbContentType idContent /* = UNKNOWN */,
7416 const Filter& filter /* = Filter() */,
7417 bool countOnly /* = false */)
7419 if (nullptr == m_pDB)
7420 return false;
7421 if (nullptr == m_pDS)
7422 return false;
7426 //! @todo This routine (and probably others at this same level) use playcount as a reference to filter on at a later
7427 //! point. This means that we *MUST* filter these levels as you'll get double ups. Ideally we'd allow playcount
7428 //! to filter through as we normally do for tvshows to save this happening.
7429 //! Also, we apply this same filtering logic to the locked or unlocked paths to prevent these from showing.
7430 //! Whether or not this should happen is a tricky one - it complicates all the high level categories (everything
7431 //! above titles).
7433 // General routine that the other actor/director/writer routines call
7435 // get primary genres for movies
7436 std::string strSQL;
7437 bool bMainArtistOnly = false;
7438 Filter extFilter = filter;
7439 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7441 std::string view, view_id, media_type, extraField, extraJoin, group;
7442 if (idContent == VideoDbContentType::MOVIES)
7444 view = MediaTypeMovie;
7445 view_id = "idMovie";
7446 media_type = MediaTypeMovie;
7447 extraField = "files.playCount";
7449 else if (idContent == VideoDbContentType::TVSHOWS)
7451 view = MediaTypeEpisode;
7452 view_id = "idShow";
7453 media_type = MediaTypeTvShow;
7454 extraField = "count(DISTINCT idShow)";
7455 group = "actor.actor_id";
7457 else if (idContent == VideoDbContentType::EPISODES)
7459 view = MediaTypeEpisode;
7460 view_id = "idEpisode";
7461 media_type = MediaTypeEpisode;
7462 extraField = "files.playCount";
7464 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7466 bMainArtistOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
7467 CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS);
7468 if (StringUtils::EndsWith(strBaseDir, "directors/"))
7469 // only set this to true if getting artists and show all performers is false
7470 bMainArtistOnly = false;
7471 view = MediaTypeMusicVideo;
7472 view_id = "idMVideo";
7473 media_type = MediaTypeMusicVideo;
7474 extraField = "count(1), count(files.playCount)";
7475 if (bMainArtistOnly)
7476 extraJoin =
7477 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
7478 group = "actor.actor_id";
7480 else
7481 return false;
7483 strSQL = "SELECT {} FROM actor ";
7484 extFilter.fields = "actor.actor_id, actor.name, actor.art_urls, path.strPath";
7485 extFilter.AppendField(extraField);
7486 extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON actor.actor_id = %s_link.actor_id", type, type));
7487 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()));
7488 extFilter.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str()));
7489 extFilter.AppendJoin("JOIN path ON path.idPath = files.idPath");
7490 extFilter.AppendJoin(extraJoin);
7491 extFilter.AppendGroup(group);
7493 else
7495 std::string view, view_id, media_type, extraField, extraJoin;
7496 if (idContent == VideoDbContentType::MOVIES)
7498 view = MediaTypeMovie;
7499 view_id = "idMovie";
7500 media_type = MediaTypeMovie;
7501 extraField = "count(1), count(files.playCount)";
7502 extraJoin = PrepareSQL(" JOIN files ON files.idFile=%s_view.idFile", view.c_str());
7504 else if (idContent == VideoDbContentType::TVSHOWS)
7506 view = MediaTypeTvShow;
7507 view_id = "idShow";
7508 media_type = MediaTypeTvShow;
7509 extraField = "count(idShow)";
7511 else if (idContent == VideoDbContentType::EPISODES)
7513 view = MediaTypeEpisode;
7514 view_id = "idEpisode";
7515 media_type = MediaTypeEpisode;
7516 extraField = "count(1), count(files.playCount)";
7517 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
7519 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7521 bMainArtistOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
7522 CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS);
7523 if (StringUtils::EndsWith(strBaseDir, "directors/"))
7524 // only set this to true if getting artists and show all performers is false
7525 bMainArtistOnly = false;
7526 view = MediaTypeMusicVideo;
7527 view_id = "idMVideo";
7528 media_type = MediaTypeMusicVideo;
7529 extraField = "count(1), count(files.playCount)";
7530 extraJoin = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
7531 if (bMainArtistOnly)
7532 extraJoin =
7533 extraJoin +
7534 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
7536 else
7537 return false;
7539 strSQL = "SELECT {} FROM actor ";
7540 extFilter.fields = "actor.actor_id, actor.name, actor.art_urls";
7541 extFilter.AppendField(extraField);
7542 extFilter.AppendJoin(PrepareSQL("JOIN %s_link on actor.actor_id = %s_link.actor_id", type, type));
7543 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()));
7544 extFilter.AppendJoin(extraJoin);
7545 extFilter.AppendGroup("actor.actor_id");
7548 if (countOnly)
7550 extFilter.fields = "COUNT(1)";
7551 extFilter.group.clear();
7552 extFilter.order.clear();
7554 strSQL = StringUtils::Format(strSQL, !extFilter.fields.empty() ? extFilter.fields : "*");
7556 CVideoDbUrl videoUrl;
7557 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
7558 return false;
7560 // run query
7561 auto start = std::chrono::steady_clock::now();
7563 if (!m_pDS->query(strSQL)) return false;
7565 auto end = std::chrono::steady_clock::now();
7566 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
7568 CLog::Log(LOGDEBUG, LOGDATABASE, "{} - query took {} ms", __FUNCTION__, duration.count());
7570 start = std::chrono::steady_clock::now();
7572 int iRowsFound = m_pDS->num_rows();
7573 if (iRowsFound == 0)
7575 m_pDS->close();
7576 return true;
7579 if (countOnly)
7581 CFileItemPtr pItem(new CFileItem());
7582 pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
7583 items.Add(pItem);
7585 m_pDS->close();
7586 return true;
7589 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7591 std::map<int, CActor> mapActors;
7593 while (!m_pDS->eof())
7595 int idActor = m_pDS->fv(0).get_asInt();
7596 CActor actor;
7597 actor.name = m_pDS->fv(1).get_asString();
7598 actor.thumb = m_pDS->fv(2).get_asString();
7599 if (idContent != VideoDbContentType::TVSHOWS &&
7600 idContent != VideoDbContentType::MUSICVIDEOS)
7602 actor.playcount = m_pDS->fv(3).get_asInt();
7603 actor.appearances = 1;
7605 else actor.appearances = m_pDS->fv(4).get_asInt();
7606 auto it = mapActors.find(idActor);
7607 // is this actor already known?
7608 if (it == mapActors.end())
7610 // check path
7611 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7612 mapActors.insert(std::pair<int, CActor>(idActor, actor));
7614 else if (idContent != VideoDbContentType::TVSHOWS &&
7615 idContent != VideoDbContentType::MUSICVIDEOS)
7616 it->second.appearances++;
7617 m_pDS->next();
7619 m_pDS->close();
7621 for (const auto &i : mapActors)
7623 CFileItemPtr pItem(new CFileItem(i.second.name));
7625 CVideoDbUrl itemUrl = videoUrl;
7626 std::string path = StringUtils::Format("{}/", i.first);
7627 itemUrl.AppendPath(path);
7628 pItem->SetPath(itemUrl.ToString());
7630 pItem->m_bIsFolder=true;
7631 pItem->GetVideoInfoTag()->SetPlayCount(i.second.playcount);
7632 pItem->GetVideoInfoTag()->m_strPictureURL.ParseFromData(i.second.thumb);
7633 pItem->GetVideoInfoTag()->m_iDbId = i.first;
7634 pItem->GetVideoInfoTag()->m_type = type;
7635 pItem->GetVideoInfoTag()->m_relevance = i.second.appearances;
7636 if (idContent == VideoDbContentType::MUSICVIDEOS)
7638 // Get artist bio from music db later if available
7639 pItem->GetVideoInfoTag()->m_artist.emplace_back(i.second.name);
7640 pItem->SetProperty("musicvideomediatype", MediaTypeArtist);
7642 items.Add(pItem);
7645 else
7647 while (!m_pDS->eof())
7651 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
7653 CVideoDbUrl itemUrl = videoUrl;
7654 std::string path = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
7655 itemUrl.AppendPath(path);
7656 pItem->SetPath(itemUrl.ToString());
7658 pItem->m_bIsFolder=true;
7659 pItem->GetVideoInfoTag()->m_strPictureURL.ParseFromData(m_pDS->fv(2).get_asString());
7660 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
7661 pItem->GetVideoInfoTag()->m_type = type;
7662 if (idContent != VideoDbContentType::TVSHOWS)
7664 // fv(4) is the number of videos watched, fv(3) is the total number. We set the playcount
7665 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7666 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(4).get_asInt() == m_pDS->fv(3).get_asInt()) ? 1 : 0);
7668 pItem->GetVideoInfoTag()->m_relevance = m_pDS->fv(3).get_asInt();
7669 if (idContent == VideoDbContentType::MUSICVIDEOS)
7671 pItem->GetVideoInfoTag()->m_artist.emplace_back(pItem->GetLabel());
7672 pItem->SetProperty("musicvideomediatype", MediaTypeArtist);
7674 items.Add(pItem);
7675 m_pDS->next();
7677 catch (...)
7679 m_pDS->close();
7680 CLog::Log(LOGERROR, "{}: out of memory - retrieved {} items", __FUNCTION__, items.Size());
7681 return items.Size() > 0;
7684 m_pDS->close();
7687 end = std::chrono::steady_clock::now();
7688 duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
7690 CLog::Log(LOGDEBUG, LOGDATABASE, "{} item retrieval took {} ms", __FUNCTION__,
7691 duration.count());
7693 return true;
7695 catch (...)
7697 m_pDS->close();
7698 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7700 return false;
7703 bool CVideoDatabase::GetYearsNav(const std::string& strBaseDir,
7704 CFileItemList& items,
7705 VideoDbContentType idContent /* = UNKNOWN */,
7706 const Filter& filter /* = Filter() */)
7710 if (nullptr == m_pDB)
7711 return false;
7712 if (nullptr == m_pDS)
7713 return false;
7715 std::string strSQL;
7716 Filter extFilter = filter;
7717 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7719 if (idContent == VideoDbContentType::MOVIES)
7721 strSQL = "select movie_view.premiered, path.strPath, files.playCount from movie_view ";
7722 extFilter.AppendJoin("join files on files.idFile = movie_view.idFile join path on files.idPath = path.idPath");
7724 else if (idContent == VideoDbContentType::TVSHOWS)
7726 strSQL = PrepareSQL("select tvshow_view.c%02d, path.strPath from tvshow_view ", VIDEODB_ID_TV_PREMIERED);
7727 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");
7729 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7731 strSQL = "select musicvideo_view.premiered, path.strPath, files.playCount from musicvideo_view ";
7732 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on files.idPath = path.idPath");
7734 else
7735 return false;
7737 else
7739 std::string group;
7740 if (idContent == VideoDbContentType::MOVIES)
7742 strSQL = "select movie_view.premiered, count(1), count(files.playCount) from movie_view ";
7743 extFilter.AppendJoin("join files on files.idFile = movie_view.idFile");
7744 extFilter.AppendGroup("movie_view.premiered");
7746 else if (idContent == VideoDbContentType::TVSHOWS)
7748 strSQL = PrepareSQL("select distinct tvshow_view.c%02d from tvshow_view", VIDEODB_ID_TV_PREMIERED);
7749 extFilter.AppendGroup(PrepareSQL("tvshow_view.c%02d", VIDEODB_ID_TV_PREMIERED));
7751 else if (idContent == VideoDbContentType::MUSICVIDEOS)
7753 strSQL = "select musicvideo_view.premiered, count(1), count(files.playCount) from musicvideo_view ";
7754 extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile");
7755 extFilter.AppendGroup("musicvideo_view.premiered");
7757 else
7758 return false;
7761 CVideoDbUrl videoUrl;
7762 if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
7763 return false;
7765 int iRowsFound = RunQuery(strSQL);
7766 if (iRowsFound <= 0)
7767 return iRowsFound == 0;
7769 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7771 std::map<int, std::pair<std::string,int> > mapYears;
7772 while (!m_pDS->eof())
7774 int lYear = 0;
7775 std::string dateString = m_pDS->fv(0).get_asString();
7776 if (dateString.size() == 4)
7777 lYear = m_pDS->fv(0).get_asInt();
7778 else
7780 CDateTime time;
7781 time.SetFromDateString(dateString);
7782 if (time.IsValid())
7783 lYear = time.GetYear();
7785 auto it = mapYears.find(lYear);
7786 if (it == mapYears.end())
7788 // check path
7789 if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7791 std::string year = std::to_string(lYear);
7792 if (idContent == VideoDbContentType::MOVIES ||
7793 idContent == VideoDbContentType::MUSICVIDEOS)
7794 mapYears.insert(std::pair<int, std::pair<std::string,int> >(lYear, std::pair<std::string,int>(year,m_pDS->fv(2).get_asInt())));
7795 else
7796 mapYears.insert(std::pair<int, std::pair<std::string,int> >(lYear, std::pair<std::string,int>(year,0)));
7799 m_pDS->next();
7801 m_pDS->close();
7803 for (const auto &i : mapYears)
7805 if (i.first == 0)
7806 continue;
7807 CFileItemPtr pItem(new CFileItem(i.second.first));
7809 CVideoDbUrl itemUrl = videoUrl;
7810 std::string path = StringUtils::Format("{}/", i.first);
7811 itemUrl.AppendPath(path);
7812 pItem->SetPath(itemUrl.ToString());
7814 pItem->m_bIsFolder=true;
7815 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7816 pItem->GetVideoInfoTag()->SetPlayCount(i.second.second);
7817 items.Add(pItem);
7820 else
7822 while (!m_pDS->eof())
7824 int lYear = 0;
7825 std::string strLabel = m_pDS->fv(0).get_asString();
7826 if (strLabel.size() == 4)
7827 lYear = m_pDS->fv(0).get_asInt();
7828 else
7830 CDateTime time;
7831 time.SetFromDateString(strLabel);
7832 if (time.IsValid())
7834 lYear = time.GetYear();
7835 strLabel = std::to_string(lYear);
7838 if (lYear == 0)
7840 m_pDS->next();
7841 continue;
7843 CFileItemPtr pItem(new CFileItem(strLabel));
7845 CVideoDbUrl itemUrl = videoUrl;
7846 std::string path = StringUtils::Format("{}/", lYear);
7847 itemUrl.AppendPath(path);
7848 pItem->SetPath(itemUrl.ToString());
7850 pItem->m_bIsFolder=true;
7851 if (idContent == VideoDbContentType::MOVIES || idContent == VideoDbContentType::MUSICVIDEOS)
7853 // fv(2) is the number of videos watched, fv(1) is the total number. We set the playcount
7854 // only if the number of videos watched is equal to the total number (i.e. every video watched)
7855 pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(2).get_asInt() == m_pDS->fv(1).get_asInt()) ? 1 : 0);
7858 // take care of dupes ..
7859 if (!items.Contains(pItem->GetPath()))
7860 items.Add(pItem);
7862 m_pDS->next();
7864 m_pDS->close();
7867 return true;
7869 catch (...)
7871 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
7873 return false;
7876 bool CVideoDatabase::GetSeasonsNav(const std::string& strBaseDir, CFileItemList& items, int idActor, int idDirector, int idGenre, int idYear, int idShow, bool getLinkedMovies /* = true */)
7878 // parse the base path to get additional filters
7879 CVideoDbUrl videoUrl;
7880 if (!videoUrl.FromString(strBaseDir))
7881 return false;
7883 if (idShow != -1)
7884 videoUrl.AddOption("tvshowid", idShow);
7885 if (idActor != -1)
7886 videoUrl.AddOption("actorid", idActor);
7887 else if (idDirector != -1)
7888 videoUrl.AddOption("directorid", idDirector);
7889 else if (idGenre != -1)
7890 videoUrl.AddOption("genreid", idGenre);
7891 else if (idYear != -1)
7892 videoUrl.AddOption("year", idYear);
7894 if (!GetSeasonsByWhere(videoUrl.ToString(), Filter(), items, false))
7895 return false;
7897 // now add any linked movies
7898 if (getLinkedMovies && idShow != -1)
7900 Filter movieFilter;
7901 movieFilter.join = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
7902 movieFilter.where = PrepareSQL("movielinktvshow.idShow = %i", idShow);
7903 CFileItemList movieItems;
7904 GetMoviesByWhere("videodb://movies/titles/", movieFilter, movieItems);
7906 if (movieItems.Size() > 0)
7907 items.Append(movieItems);
7910 return true;
7913 bool CVideoDatabase::GetSeasonsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool appendFullShowPath /* = true */, const SortDescription &sortDescription /* = SortDescription() */)
7917 if (nullptr == m_pDB)
7918 return false;
7919 if (nullptr == m_pDS)
7920 return false;
7922 int total = -1;
7924 std::string strSQL = "SELECT %s FROM season_view ";
7925 CVideoDbUrl videoUrl;
7926 std::string strSQLExtra;
7927 Filter extFilter = filter;
7928 SortDescription sorting = sortDescription;
7929 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
7930 return false;
7932 // Apply the limiting directly here if there's no special sorting but limiting
7933 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
7934 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
7935 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
7937 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7938 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7941 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7943 int iRowsFound = RunQuery(strSQL);
7945 // store the total value of items as a property
7946 if (total < iRowsFound)
7947 total = iRowsFound;
7948 items.SetProperty("total", total);
7950 if (iRowsFound <= 0)
7951 return iRowsFound == 0;
7953 std::set<std::pair<int, int>> mapSeasons;
7954 while (!m_pDS->eof())
7956 int id = m_pDS->fv(VIDEODB_ID_SEASON_ID).get_asInt();
7957 int showId = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_ID).get_asInt();
7958 int iSeason = m_pDS->fv(VIDEODB_ID_SEASON_NUMBER).get_asInt();
7959 std::string name = m_pDS->fv(VIDEODB_ID_SEASON_NAME).get_asString();
7960 std::string path = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PATH).get_asString();
7962 if (mapSeasons.find(std::make_pair(showId, iSeason)) == mapSeasons.end() &&
7963 (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || g_passwordManager.bMasterUser ||
7964 g_passwordManager.IsDatabasePathUnlocked(path, *CMediaSourceSettings::GetInstance().GetSources("video"))))
7966 mapSeasons.insert(std::make_pair(showId, iSeason));
7968 std::string strLabel = name;
7969 if (strLabel.empty())
7971 if (iSeason == 0)
7972 strLabel = g_localizeStrings.Get(20381);
7973 else
7974 strLabel = StringUtils::Format(g_localizeStrings.Get(20358), iSeason);
7976 CFileItemPtr pItem(new CFileItem(strLabel));
7978 CVideoDbUrl itemUrl = videoUrl;
7979 std::string strDir;
7980 if (appendFullShowPath)
7981 strDir += StringUtils::Format("{}/", showId);
7982 strDir += StringUtils::Format("{}/", iSeason);
7983 itemUrl.AppendPath(strDir);
7984 pItem->SetPath(itemUrl.ToString());
7986 pItem->m_bIsFolder = true;
7987 pItem->GetVideoInfoTag()->m_strTitle = strLabel;
7988 if (!name.empty())
7989 pItem->GetVideoInfoTag()->m_strSortTitle = name;
7990 pItem->GetVideoInfoTag()->m_iSeason = iSeason;
7991 pItem->GetVideoInfoTag()->m_iDbId = id;
7992 pItem->GetVideoInfoTag()->m_iIdSeason = id;
7993 pItem->GetVideoInfoTag()->m_type = MediaTypeSeason;
7994 pItem->GetVideoInfoTag()->m_strPath = path;
7995 pItem->GetVideoInfoTag()->m_strShowTitle = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_TITLE).get_asString();
7996 pItem->GetVideoInfoTag()->m_strPlot = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PLOT).get_asString();
7997 pItem->GetVideoInfoTag()->SetPremieredFromDBDate(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PREMIERED).get_asString());
7998 pItem->GetVideoInfoTag()->m_firstAired.SetFromDBDate(m_pDS->fv(VIDEODB_ID_SEASON_PREMIERED).get_asString());
7999 pItem->GetVideoInfoTag()->m_iUserRating = m_pDS->fv(VIDEODB_ID_SEASON_USER_RATING).get_asInt();
8000 // season premiered date based on first episode airdate associated to the season
8001 // tvshow premiered date is used as a fallback
8002 if (pItem->GetVideoInfoTag()->m_firstAired.IsValid())
8003 pItem->GetVideoInfoTag()->SetPremiered(pItem->GetVideoInfoTag()->m_firstAired);
8004 else if (pItem->GetVideoInfoTag()->HasPremiered())
8005 pItem->GetVideoInfoTag()->SetPremiered(pItem->GetVideoInfoTag()->GetPremiered());
8006 else if (pItem->GetVideoInfoTag()->HasYear())
8007 pItem->GetVideoInfoTag()->SetYear(pItem->GetVideoInfoTag()->GetYear());
8008 pItem->GetVideoInfoTag()->m_genre = StringUtils::Split(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_GENRE).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
8009 pItem->GetVideoInfoTag()->m_studio = StringUtils::Split(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_STUDIO).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
8010 pItem->GetVideoInfoTag()->m_strMPAARating = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_MPAA).get_asString();
8011 pItem->GetVideoInfoTag()->m_iIdShow = showId;
8013 const int totalEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_TOTAL).get_asInt();
8014 const int watchedEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_WATCHED).get_asInt();
8015 const int inProgressEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_INPROGRESS).get_asInt();
8017 pItem->GetVideoInfoTag()->m_iEpisode = totalEpisodes;
8018 pItem->SetProperty("totalepisodes", totalEpisodes);
8019 pItem->SetProperty("numepisodes", totalEpisodes); // will be changed later to reflect watchmode setting
8020 pItem->SetProperty("watchedepisodes", watchedEpisodes);
8021 pItem->SetProperty("unwatchedepisodes", totalEpisodes - watchedEpisodes);
8022 pItem->SetProperty("inprogressepisodes", inProgressEpisodes);
8023 pItem->SetProperty("watchedepisodepercent",
8024 totalEpisodes > 0 ? (watchedEpisodes * 100 / totalEpisodes) : 0);
8025 if (iSeason == 0)
8026 pItem->SetProperty("isspecial", true);
8027 pItem->GetVideoInfoTag()->SetPlayCount((totalEpisodes == watchedEpisodes) ? 1 : 0);
8028 pItem->SetOverlayImage((pItem->GetVideoInfoTag()->GetPlayCount() > 0) &&
8029 (pItem->GetVideoInfoTag()->m_iEpisode > 0)
8030 ? CGUIListItem::ICON_OVERLAY_WATCHED
8031 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8033 items.Add(pItem);
8036 m_pDS->next();
8038 m_pDS->close();
8040 return true;
8042 catch (...)
8044 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8046 return false;
8049 bool CVideoDatabase::GetSortedVideos(const MediaType &mediaType, const std::string& strBaseDir, const SortDescription &sortDescription, CFileItemList& items, const Filter &filter /* = Filter() */)
8051 if (nullptr == m_pDB || nullptr == m_pDS)
8052 return false;
8054 if (mediaType != MediaTypeMovie && mediaType != MediaTypeTvShow && mediaType != MediaTypeEpisode && mediaType != MediaTypeMusicVideo)
8055 return false;
8057 SortDescription sorting = sortDescription;
8058 if (sortDescription.sortBy == SortByFile || sortDescription.sortBy == SortByTitle ||
8059 sortDescription.sortBy == SortBySortTitle || sortDescription.sortBy == SortByOriginalTitle ||
8060 sortDescription.sortBy == SortByLabel || sortDescription.sortBy == SortByDateAdded ||
8061 sortDescription.sortBy == SortByRating || sortDescription.sortBy == SortByUserRating ||
8062 sortDescription.sortBy == SortByYear || sortDescription.sortBy == SortByLastPlayed ||
8063 sortDescription.sortBy == SortByPlaycount)
8064 sorting.sortAttributes = (SortAttribute)(sortDescription.sortAttributes | SortAttributeIgnoreFolders);
8066 bool success = false;
8067 if (mediaType == MediaTypeMovie)
8068 success = GetMoviesByWhere(strBaseDir, filter, items, sorting);
8069 else if (mediaType == MediaTypeTvShow)
8070 success = GetTvShowsByWhere(strBaseDir, filter, items, sorting);
8071 else if (mediaType == MediaTypeEpisode)
8072 success = GetEpisodesByWhere(strBaseDir, filter, items, true, sorting);
8073 else if (mediaType == MediaTypeMusicVideo)
8074 success = GetMusicVideosByWhere(strBaseDir, filter, items, true, sorting);
8075 else
8076 return false;
8078 items.SetContent(CMediaTypes::ToPlural(mediaType));
8079 return success;
8082 bool CVideoDatabase::GetItems(const std::string &strBaseDir, CFileItemList &items, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */)
8084 CVideoDbUrl videoUrl;
8085 if (!videoUrl.FromString(strBaseDir))
8086 return false;
8088 return GetItems(strBaseDir, videoUrl.GetType(), videoUrl.GetItemType(), items, filter, sortDescription);
8091 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() */)
8093 VideoDbContentType contentType;
8094 if (StringUtils::EqualsNoCase(mediaType, "movies"))
8095 contentType = VideoDbContentType::MOVIES;
8096 else if (StringUtils::EqualsNoCase(mediaType, "tvshows"))
8098 if (StringUtils::EqualsNoCase(itemType, "episodes"))
8099 contentType = VideoDbContentType::EPISODES;
8100 else
8101 contentType = VideoDbContentType::TVSHOWS;
8103 else if (StringUtils::EqualsNoCase(mediaType, "musicvideos"))
8104 contentType = VideoDbContentType::MUSICVIDEOS;
8105 else
8106 return false;
8108 return GetItems(strBaseDir, contentType, itemType, items, filter, sortDescription);
8111 bool CVideoDatabase::GetItems(const std::string& strBaseDir,
8112 VideoDbContentType mediaType,
8113 const std::string& itemType,
8114 CFileItemList& items,
8115 const Filter& filter /* = Filter() */,
8116 const SortDescription& sortDescription /* = SortDescription() */)
8118 if (StringUtils::EqualsNoCase(itemType, "movies") &&
8119 (mediaType == VideoDbContentType::MOVIES || mediaType == VideoDbContentType::MOVIE_SETS))
8120 return GetMoviesByWhere(strBaseDir, filter, items, sortDescription);
8121 else if (StringUtils::EqualsNoCase(itemType, "tvshows") &&
8122 mediaType == VideoDbContentType::TVSHOWS)
8124 Filter extFilter = filter;
8125 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->
8126 GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS))
8127 extFilter.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
8128 return GetTvShowsByWhere(strBaseDir, extFilter, items, sortDescription);
8130 else if (StringUtils::EqualsNoCase(itemType, "musicvideos") &&
8131 mediaType == VideoDbContentType::MUSICVIDEOS)
8132 return GetMusicVideosByWhere(strBaseDir, filter, items, true, sortDescription);
8133 else if (StringUtils::EqualsNoCase(itemType, "episodes") &&
8134 mediaType == VideoDbContentType::EPISODES)
8135 return GetEpisodesByWhere(strBaseDir, filter, items, true, sortDescription);
8136 else if (StringUtils::EqualsNoCase(itemType, "seasons") &&
8137 mediaType == VideoDbContentType::TVSHOWS)
8138 return GetSeasonsNav(strBaseDir, items);
8139 else if (StringUtils::EqualsNoCase(itemType, "genres"))
8140 return GetGenresNav(strBaseDir, items, mediaType, filter);
8141 else if (StringUtils::EqualsNoCase(itemType, "years"))
8142 return GetYearsNav(strBaseDir, items, mediaType, filter);
8143 else if (StringUtils::EqualsNoCase(itemType, "actors"))
8144 return GetActorsNav(strBaseDir, items, mediaType, filter);
8145 else if (StringUtils::EqualsNoCase(itemType, "directors"))
8146 return GetDirectorsNav(strBaseDir, items, mediaType, filter);
8147 else if (StringUtils::EqualsNoCase(itemType, "writers"))
8148 return GetWritersNav(strBaseDir, items, mediaType, filter);
8149 else if (StringUtils::EqualsNoCase(itemType, "studios"))
8150 return GetStudiosNav(strBaseDir, items, mediaType, filter);
8151 else if (StringUtils::EqualsNoCase(itemType, "sets"))
8152 return GetSetsNav(strBaseDir, items, mediaType, filter, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPSINGLEITEMSETS));
8153 else if (StringUtils::EqualsNoCase(itemType, "countries"))
8154 return GetCountriesNav(strBaseDir, items, mediaType, filter);
8155 else if (StringUtils::EqualsNoCase(itemType, "tags"))
8156 return GetTagsNav(strBaseDir, items, mediaType, filter);
8157 else if (StringUtils::EqualsNoCase(itemType, "videoversions"))
8158 return GetVideoVersionsNav(strBaseDir, items, mediaType, filter);
8159 else if (StringUtils::EqualsNoCase(itemType, "artists") &&
8160 mediaType == VideoDbContentType::MUSICVIDEOS)
8161 return GetActorsNav(strBaseDir, items, mediaType, filter);
8162 else if (StringUtils::EqualsNoCase(itemType, "albums") &&
8163 mediaType == VideoDbContentType::MUSICVIDEOS)
8164 return GetMusicVideoAlbumsNav(strBaseDir, items, -1, filter);
8166 return false;
8169 std::string CVideoDatabase::GetItemById(const std::string &itemType, int id)
8171 if (StringUtils::EqualsNoCase(itemType, "genres"))
8172 return GetGenreById(id);
8173 else if (StringUtils::EqualsNoCase(itemType, "years"))
8174 return std::to_string(id);
8175 else if (StringUtils::EqualsNoCase(itemType, "actors") ||
8176 StringUtils::EqualsNoCase(itemType, "directors") ||
8177 StringUtils::EqualsNoCase(itemType, "artists"))
8178 return GetPersonById(id);
8179 else if (StringUtils::EqualsNoCase(itemType, "studios"))
8180 return GetStudioById(id);
8181 else if (StringUtils::EqualsNoCase(itemType, "sets"))
8182 return GetSetById(id);
8183 else if (StringUtils::EqualsNoCase(itemType, "countries"))
8184 return GetCountryById(id);
8185 else if (StringUtils::EqualsNoCase(itemType, "tags"))
8186 return GetTagById(id);
8187 else if (StringUtils::EqualsNoCase(itemType, "videoversions"))
8188 return GetVideoVersionById(id);
8189 else if (StringUtils::EqualsNoCase(itemType, "albums"))
8190 return GetMusicVideoAlbumById(id);
8192 return "";
8195 bool CVideoDatabase::GetMoviesNav(const std::string& strBaseDir, CFileItemList& items,
8196 int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */,
8197 int idStudio /* = -1 */, int idCountry /* = -1 */, int idSet /* = -1 */, int idTag /* = -1 */,
8198 const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8200 CVideoDbUrl videoUrl;
8201 if (!videoUrl.FromString(strBaseDir))
8202 return false;
8204 if (idGenre > 0)
8205 videoUrl.AddOption("genreid", idGenre);
8206 else if (idCountry > 0)
8207 videoUrl.AddOption("countryid", idCountry);
8208 else if (idStudio > 0)
8209 videoUrl.AddOption("studioid", idStudio);
8210 else if (idDirector > 0)
8211 videoUrl.AddOption("directorid", idDirector);
8212 else if (idYear > 0)
8213 videoUrl.AddOption("year", idYear);
8214 else if (idActor > 0)
8215 videoUrl.AddOption("actorid", idActor);
8216 else if (idSet > 0)
8217 videoUrl.AddOption("setid", idSet);
8218 else if (idTag > 0)
8219 videoUrl.AddOption("tagid", idTag);
8221 Filter filter;
8222 return GetMoviesByWhere(videoUrl.ToString(), filter, items, sortDescription, getDetails);
8225 namespace
8227 std::string RewriteVideoVersionURL(const std::string& baseDir, const CVideoInfoTag& movie)
8229 const CURL parentPath{URIUtils::GetParentPath(baseDir)};
8230 const std::string versionId{std::to_string(movie.GetAssetInfo().GetId())};
8231 const std::string mediaId{std::to_string(movie.m_iDbId)};
8232 CVideoDbUrl url;
8233 url.FromString(parentPath.GetWithoutOptions());
8234 url.AppendPath(versionId);
8235 url.AppendPath(mediaId);
8236 url.AddOption("videoversionid", versionId);
8237 url.AddOption("mediaid", mediaId);
8238 return url.ToString();
8240 } // unnamed namespace
8242 bool CVideoDatabase::GetMoviesByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8246 if (nullptr == m_pDB)
8247 return false;
8248 if (nullptr == m_pDS)
8249 return false;
8251 // parse the base path to get additional filters
8252 CVideoDbUrl videoUrl;
8253 Filter extFilter = filter;
8254 SortDescription sorting = sortDescription;
8255 if (!videoUrl.FromString(strBaseDir) || !GetFilter(videoUrl, extFilter, sorting))
8256 return false;
8258 int total = -1;
8260 std::string strSQL = "select %s from movie_view ";
8261 std::string strSQLExtra;
8262 if (!CDatabase::BuildSQL(strSQLExtra, extFilter, strSQLExtra))
8263 return false;
8265 // Apply the limiting directly here if there's no special sorting but limiting
8266 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
8267 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
8268 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
8270 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
8271 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
8274 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
8276 int iRowsFound = RunQuery(strSQL);
8278 // store the total value of items as a property
8279 if (total < iRowsFound)
8280 total = iRowsFound;
8281 items.SetProperty("total", total);
8283 if (iRowsFound <= 0)
8284 return iRowsFound == 0;
8286 DatabaseResults results;
8287 results.reserve(iRowsFound);
8289 if (!SortUtils::SortFromDataset(sortDescription, MediaTypeMovie, m_pDS, results))
8290 return false;
8292 // get data from returned rows
8293 items.Reserve(results.size());
8294 const query_data &data = m_pDS->get_result_set().records;
8295 for (const auto &i : results)
8297 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
8298 const dbiplus::sql_record* const record = data.at(targetRow);
8300 CVideoInfoTag movie = GetDetailsForMovie(record, getDetails);
8301 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
8302 g_passwordManager.bMasterUser ||
8303 g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
8305 CFileItemPtr pItem(new CFileItem(movie));
8307 std::string path;
8308 CVideoDbUrl itemUrl{videoUrl};
8309 CVariant value;
8310 if (itemUrl.GetOption("videoversionid", value))
8312 //! @todo get rid of "videos with versions as folder" hack!
8313 if (value.asInteger() == VIDEO_VERSION_ID_ALL)
8315 // all versions for the given media id requested; we need to insert the real video
8316 // version id for this movie into the videodb url
8317 path = RewriteVideoVersionURL(strBaseDir, movie);
8319 // this is a certain version, no need to resolve (e.g. no version chooser on select)
8320 pItem->SetProperty("has_resolved_video_asset", true);
8323 if (path.empty())
8325 itemUrl.AppendPath(std::to_string(movie.m_iDbId));
8326 path = itemUrl.ToString();
8329 pItem->SetPath(path);
8330 pItem->SetDynPath(movie.m_strFileNameAndPath);
8332 pItem->SetOverlayImage(movie.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
8333 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8334 items.Add(pItem);
8338 // cleanup
8339 m_pDS->close();
8340 return true;
8342 catch (...)
8344 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8346 return false;
8349 bool CVideoDatabase::GetTvShowsNav(const std::string& strBaseDir, CFileItemList& items,
8350 int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */, int idStudio /* = -1 */, int idTag /* = -1 */,
8351 const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8353 CVideoDbUrl videoUrl;
8354 if (!videoUrl.FromString(strBaseDir))
8355 return false;
8357 if (idGenre != -1)
8358 videoUrl.AddOption("genreid", idGenre);
8359 else if (idStudio != -1)
8360 videoUrl.AddOption("studioid", idStudio);
8361 else if (idDirector != -1)
8362 videoUrl.AddOption("directorid", idDirector);
8363 else if (idYear != -1)
8364 videoUrl.AddOption("year", idYear);
8365 else if (idActor != -1)
8366 videoUrl.AddOption("actorid", idActor);
8367 else if (idTag != -1)
8368 videoUrl.AddOption("tagid", idTag);
8370 Filter filter;
8371 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS))
8372 filter.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
8373 return GetTvShowsByWhere(videoUrl.ToString(), filter, items, sortDescription, getDetails);
8376 bool CVideoDatabase::GetTvShowsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8380 if (nullptr == m_pDB)
8381 return false;
8382 if (nullptr == m_pDS)
8383 return false;
8385 int total = -1;
8387 std::string strSQL = "SELECT %s FROM tvshow_view ";
8388 CVideoDbUrl videoUrl;
8389 std::string strSQLExtra;
8390 Filter extFilter = filter;
8391 SortDescription sorting = sortDescription;
8392 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
8393 return false;
8395 // Apply the limiting directly here if there's no special sorting but limiting
8396 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
8397 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
8398 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
8400 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
8401 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
8404 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
8406 int iRowsFound = RunQuery(strSQL);
8408 // store the total value of items as a property
8409 if (total < iRowsFound)
8410 total = iRowsFound;
8411 items.SetProperty("total", total);
8413 if (iRowsFound <= 0)
8414 return iRowsFound == 0;
8416 DatabaseResults results;
8417 results.reserve(iRowsFound);
8418 if (!SortUtils::SortFromDataset(sorting, MediaTypeTvShow, m_pDS, results))
8419 return false;
8421 // get data from returned rows
8422 items.Reserve(results.size());
8423 const query_data &data = m_pDS->get_result_set().records;
8424 for (const auto &i : results)
8426 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
8427 const dbiplus::sql_record* const record = data.at(targetRow);
8429 CFileItemPtr pItem(new CFileItem());
8430 CVideoInfoTag movie = GetDetailsForTvShow(record, getDetails, pItem.get());
8431 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
8432 g_passwordManager.bMasterUser ||
8433 g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
8435 pItem->SetFromVideoInfoTag(movie);
8437 CVideoDbUrl itemUrl = videoUrl;
8438 std::string path = StringUtils::Format("{}/", record->at(0).get_asInt());
8439 itemUrl.AppendPath(path);
8440 pItem->SetPath(itemUrl.ToString());
8442 pItem->SetOverlayImage((pItem->GetVideoInfoTag()->GetPlayCount() > 0) &&
8443 (pItem->GetVideoInfoTag()->m_iEpisode > 0)
8444 ? CGUIListItem::ICON_OVERLAY_WATCHED
8445 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8446 items.Add(pItem);
8450 // cleanup
8451 m_pDS->close();
8452 return true;
8454 catch (...)
8456 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8458 return false;
8461 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 */)
8463 CVideoDbUrl videoUrl;
8464 if (!videoUrl.FromString(strBaseDir))
8465 return false;
8467 if (idShow != -1)
8469 videoUrl.AddOption("tvshowid", idShow);
8470 if (idSeason >= 0)
8471 videoUrl.AddOption("season", idSeason);
8473 if (idGenre != -1)
8474 videoUrl.AddOption("genreid", idGenre);
8475 else if (idYear !=-1)
8476 videoUrl.AddOption("year", idYear);
8477 else if (idActor != -1)
8478 videoUrl.AddOption("actorid", idActor);
8480 else if (idYear != -1)
8481 videoUrl.AddOption("year", idYear);
8483 if (idDirector != -1)
8484 videoUrl.AddOption("directorid", idDirector);
8486 Filter filter;
8487 bool ret = GetEpisodesByWhere(videoUrl.ToString(), filter, items, false, sortDescription, getDetails);
8489 if (idSeason == -1 && idShow != -1)
8490 { // add any linked movies
8491 Filter movieFilter;
8492 movieFilter.join = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
8493 movieFilter.where = PrepareSQL("movielinktvshow.idShow = %i", idShow);
8494 CFileItemList movieItems;
8495 GetMoviesByWhere("videodb://movies/titles/", movieFilter, movieItems);
8497 if (movieItems.Size() > 0)
8498 items.Append(movieItems);
8501 return ret;
8504 bool CVideoDatabase::GetEpisodesByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool appendFullShowPath /* = true */, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8508 if (nullptr == m_pDB)
8509 return false;
8510 if (nullptr == m_pDS)
8511 return false;
8513 int total = -1;
8515 std::string strSQL = "select %s from episode_view ";
8516 CVideoDbUrl videoUrl;
8517 std::string strSQLExtra;
8518 Filter extFilter = filter;
8519 SortDescription sorting = sortDescription;
8520 if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
8521 return false;
8523 // Apply the limiting directly here if there's no special sorting but limiting
8524 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
8525 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
8526 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
8528 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
8529 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
8532 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
8534 int iRowsFound = RunQuery(strSQL);
8536 // store the total value of items as a property
8537 if (total < iRowsFound)
8538 total = iRowsFound;
8539 items.SetProperty("total", total);
8541 if (iRowsFound <= 0)
8542 return iRowsFound == 0;
8544 DatabaseResults results;
8545 results.reserve(iRowsFound);
8546 if (!SortUtils::SortFromDataset(sorting, MediaTypeEpisode, m_pDS, results))
8547 return false;
8549 // get data from returned rows
8550 items.Reserve(results.size());
8551 CLabelFormatter formatter("%H. %T", "");
8553 const query_data &data = m_pDS->get_result_set().records;
8554 for (const auto &i : results)
8556 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
8557 const dbiplus::sql_record* const record = data.at(targetRow);
8559 CVideoInfoTag episode = GetDetailsForEpisode(record, getDetails);
8560 if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
8561 g_passwordManager.bMasterUser ||
8562 g_passwordManager.IsDatabasePathUnlocked(episode.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
8564 CFileItemPtr pItem(new CFileItem(episode));
8565 formatter.FormatLabel(pItem.get());
8567 int idEpisode = record->at(0).get_asInt();
8569 CVideoDbUrl itemUrl = videoUrl;
8570 std::string path;
8571 if (appendFullShowPath && videoUrl.GetItemType() != "episodes")
8572 path = StringUtils::Format("{}/{}/{}",
8573 record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt(),
8574 episode.m_iSeason, idEpisode);
8575 else
8576 path = std::to_string(idEpisode);
8577 itemUrl.AppendPath(path);
8578 pItem->SetPath(itemUrl.ToString());
8579 pItem->SetDynPath(episode.m_strFileNameAndPath);
8581 pItem->SetOverlayImage(episode.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
8582 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
8583 pItem->m_dateTime = episode.m_firstAired;
8584 items.Add(pItem);
8588 // cleanup
8589 m_pDS->close();
8590 return true;
8592 catch (...)
8594 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8596 return false;
8599 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 */)
8601 CVideoDbUrl videoUrl;
8602 if (!videoUrl.FromString(strBaseDir))
8603 return false;
8605 if (idGenre != -1)
8606 videoUrl.AddOption("genreid", idGenre);
8607 else if (idStudio != -1)
8608 videoUrl.AddOption("studioid", idStudio);
8609 else if (idDirector != -1)
8610 videoUrl.AddOption("directorid", idDirector);
8611 else if (idYear !=-1)
8612 videoUrl.AddOption("year", idYear);
8613 else if (idArtist != -1)
8614 videoUrl.AddOption("artistid", idArtist);
8615 else if (idTag != -1)
8616 videoUrl.AddOption("tagid", idTag);
8617 if (idAlbum != -1)
8618 videoUrl.AddOption("albumid", idAlbum);
8620 Filter filter;
8621 return GetMusicVideosByWhere(videoUrl.ToString(), filter, items, true, sortDescription, getDetails);
8624 bool CVideoDatabase::GetRecentlyAddedMoviesNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8626 Filter filter;
8627 filter.order = "dateAdded desc, idMovie desc";
8628 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8629 return GetMoviesByWhere(strBaseDir, filter, items, SortDescription(), getDetails);
8632 bool CVideoDatabase::GetRecentlyAddedEpisodesNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8634 Filter filter;
8635 filter.order = "dateAdded desc, idEpisode desc";
8636 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8637 return GetEpisodesByWhere(strBaseDir, filter, items, false, SortDescription(), getDetails);
8640 bool CVideoDatabase::GetRecentlyAddedMusicVideosNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8642 Filter filter;
8643 filter.order = "dateAdded desc, idMVideo desc";
8644 filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
8645 return GetMusicVideosByWhere(strBaseDir, filter, items, true, SortDescription(), getDetails);
8648 bool CVideoDatabase::GetInProgressTvShowsNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
8650 Filter filter;
8651 filter.order = PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE);
8652 filter.where = "totalCount != watchedCount AND (inProgressCount > 0 OR watchedCount != 0)";
8653 return GetTvShowsByWhere(strBaseDir, filter, items, SortDescription(), getDetails);
8656 std::string CVideoDatabase::GetGenreById(int id)
8658 return GetSingleValue("genre", "name", PrepareSQL("genre_id=%i", id));
8661 std::string CVideoDatabase::GetCountryById(int id)
8663 return GetSingleValue("country", "name", PrepareSQL("country_id=%i", id));
8666 std::string CVideoDatabase::GetSetById(int id)
8668 return GetSingleValue("sets", "strSet", PrepareSQL("idSet=%i", id));
8671 std::string CVideoDatabase::GetSetByNameLike(const std::string& nameLike) const
8673 return GetSingleValue("sets", "strSet", PrepareSQL("strSet LIKE '%s'", nameLike.c_str()));
8676 std::string CVideoDatabase::GetTagById(int id)
8678 return GetSingleValue("tag", "name", PrepareSQL("tag_id = %i", id));
8681 std::string CVideoDatabase::GetPersonById(int id)
8683 return GetSingleValue("actor", "name", PrepareSQL("actor_id=%i", id));
8686 std::string CVideoDatabase::GetStudioById(int id)
8688 return GetSingleValue("studio", "name", PrepareSQL("studio_id=%i", id));
8691 std::string CVideoDatabase::GetTvShowTitleById(int id)
8693 return GetSingleValue("tvshow", PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE), PrepareSQL("idShow=%i", id));
8696 std::string CVideoDatabase::GetMusicVideoAlbumById(int id)
8698 return GetSingleValue("musicvideo", PrepareSQL("c%02d", VIDEODB_ID_MUSICVIDEO_ALBUM), PrepareSQL("idMVideo=%i", id));
8701 bool CVideoDatabase::HasSets() const
8705 if (nullptr == m_pDB)
8706 return false;
8707 if (nullptr == m_pDS)
8708 return false;
8710 m_pDS->query("SELECT movie_view.idSet,COUNT(1) AS c FROM movie_view "
8711 "JOIN sets ON sets.idSet = movie_view.idSet "
8712 "GROUP BY movie_view.idSet HAVING c>1");
8714 bool bResult = (m_pDS->num_rows() > 0);
8715 m_pDS->close();
8716 return bResult;
8718 catch (...)
8720 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8722 return false;
8725 int CVideoDatabase::GetTvShowForEpisode(int idEpisode)
8729 if (nullptr == m_pDB)
8730 return false;
8731 if (nullptr == m_pDS2)
8732 return false;
8734 // make sure we use m_pDS2, as this is called in loops using m_pDS
8735 std::string strSQL=PrepareSQL("select idShow from episode where idEpisode=%i", idEpisode);
8736 m_pDS2->query( strSQL );
8738 int result=-1;
8739 if (!m_pDS2->eof())
8740 result=m_pDS2->fv(0).get_asInt();
8741 m_pDS2->close();
8743 return result;
8745 catch (...)
8747 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, idEpisode);
8749 return false;
8752 int CVideoDatabase::GetSeasonForEpisode(int idEpisode)
8754 char column[5];
8755 snprintf(column, sizeof(column), "c%0d", VIDEODB_ID_EPISODE_SEASON);
8756 std::string id = GetSingleValue("episode", column, PrepareSQL("idEpisode=%i", idEpisode));
8757 if (id.empty())
8758 return -1;
8759 return atoi(id.c_str());
8762 bool CVideoDatabase::HasContent()
8764 return (HasContent(VideoDbContentType::MOVIES) || HasContent(VideoDbContentType::TVSHOWS) ||
8765 HasContent(VideoDbContentType::MUSICVIDEOS));
8768 bool CVideoDatabase::HasContent(VideoDbContentType type)
8770 bool result = false;
8773 if (nullptr == m_pDB)
8774 return false;
8775 if (nullptr == m_pDS)
8776 return false;
8778 std::string sql;
8779 if (type == VideoDbContentType::MOVIES)
8780 sql = "select count(1) from movie";
8781 else if (type == VideoDbContentType::TVSHOWS)
8782 sql = "select count(1) from tvshow";
8783 else if (type == VideoDbContentType::MUSICVIDEOS)
8784 sql = "select count(1) from musicvideo";
8785 m_pDS->query( sql );
8787 if (!m_pDS->eof())
8788 result = (m_pDS->fv(0).get_asInt() > 0);
8790 m_pDS->close();
8792 catch (...)
8794 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8796 return result;
8799 ScraperPtr CVideoDatabase::GetScraperForPath( const std::string& strPath )
8801 SScanSettings settings;
8802 return GetScraperForPath(strPath, settings);
8805 ScraperPtr CVideoDatabase::GetScraperForPath(const std::string& strPath, SScanSettings& settings)
8807 bool dummy;
8808 return GetScraperForPath(strPath, settings, dummy);
8811 ScraperPtr CVideoDatabase::GetScraperForPath(const std::string& strPath, SScanSettings& settings, bool& foundDirectly)
8813 foundDirectly = false;
8816 if (strPath.empty() || !m_pDB || !m_pDS)
8817 return ScraperPtr();
8819 ScraperPtr scraper;
8820 std::string strPath2;
8822 if (URIUtils::IsMultiPath(strPath))
8823 strPath2 = CMultiPathDirectory::GetFirstPath(strPath);
8824 else
8825 strPath2 = strPath;
8827 std::string strPath1 = URIUtils::GetDirectory(strPath2);
8828 int idPath = GetPathId(strPath1);
8830 if (idPath > -1)
8832 std::string strSQL = PrepareSQL(
8833 "SELECT path.strContent, path.strScraper, path.scanRecursive, path.useFolderNames, "
8834 "path.strSettings, path.noUpdate, path.exclude, path.allAudio FROM path WHERE idPath=%i",
8835 idPath);
8836 m_pDS->query( strSQL );
8839 int iFound = 1;
8840 CONTENT_TYPE content = CONTENT_NONE;
8841 if (!m_pDS->eof())
8842 { // path is stored in db
8844 settings.m_allExtAudio = m_pDS->fv("path.allAudio").get_asBool();
8846 if (m_pDS->fv("path.exclude").get_asBool())
8848 settings.exclude = true;
8849 m_pDS->close();
8850 return ScraperPtr();
8852 settings.exclude = false;
8854 // try and ascertain scraper for this path
8855 std::string strcontent = m_pDS->fv("path.strContent").get_asString();
8856 StringUtils::ToLower(strcontent);
8857 content = TranslateContent(strcontent);
8859 //FIXME paths stored should not have empty strContent
8860 //assert(content != CONTENT_NONE);
8861 std::string scraperID = m_pDS->fv("path.strScraper").get_asString();
8863 AddonPtr addon;
8864 if (!scraperID.empty() &&
8865 CServiceBroker::GetAddonMgr().GetAddon(scraperID, addon, ADDON::OnlyEnabled::CHOICE_YES))
8867 scraper = std::dynamic_pointer_cast<CScraper>(addon);
8868 if (!scraper)
8869 return ScraperPtr();
8871 // store this path's content & settings
8872 scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
8873 settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
8874 settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
8875 settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
8879 if (content == CONTENT_NONE)
8880 { // this path is not saved in db
8881 // we must drill up until a scraper is configured
8882 std::string strParent;
8883 while (URIUtils::GetParentPath(strPath1, strParent))
8885 iFound++;
8887 std::string strSQL =
8888 PrepareSQL("SELECT path.strContent, path.strScraper, path.scanRecursive, "
8889 "path.useFolderNames, path.strSettings, path.noUpdate, path.exclude, "
8890 "path.allAudio FROM path WHERE strPath='%s'",
8891 strParent.c_str());
8892 m_pDS->query(strSQL);
8894 CONTENT_TYPE content = CONTENT_NONE;
8895 if (!m_pDS->eof())
8897 settings.m_allExtAudio = m_pDS->fv("path.allAudio").get_asBool();
8898 std::string strcontent = m_pDS->fv("path.strContent").get_asString();
8899 StringUtils::ToLower(strcontent);
8900 if (m_pDS->fv("path.exclude").get_asBool())
8902 settings.exclude = true;
8903 scraper.reset();
8904 m_pDS->close();
8905 break;
8908 content = TranslateContent(strcontent);
8910 AddonPtr addon;
8911 if (content != CONTENT_NONE &&
8912 CServiceBroker::GetAddonMgr().GetAddon(m_pDS->fv("path.strScraper").get_asString(),
8913 addon, ADDON::OnlyEnabled::CHOICE_YES))
8915 scraper = std::dynamic_pointer_cast<CScraper>(addon);
8916 scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
8917 settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
8918 settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
8919 settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
8920 settings.exclude = false;
8921 break;
8924 strPath1 = strParent;
8927 m_pDS->close();
8929 if (!scraper || scraper->Content() == CONTENT_NONE)
8930 return ScraperPtr();
8932 if (scraper->Content() == CONTENT_TVSHOWS)
8934 settings.recurse = 0;
8935 if(settings.parent_name) // single show
8937 settings.parent_name_root = settings.parent_name = (iFound == 1);
8939 else // show root
8941 settings.parent_name_root = settings.parent_name = (iFound == 2);
8944 else if (scraper->Content() == CONTENT_MOVIES)
8946 settings.recurse = settings.recurse - (iFound-1);
8947 settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
8949 else if (scraper->Content() == CONTENT_MUSICVIDEOS)
8951 settings.recurse = settings.recurse - (iFound-1);
8952 settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
8954 else
8956 iFound = 0;
8957 return ScraperPtr();
8959 foundDirectly = (iFound == 1);
8960 return scraper;
8962 catch (...)
8964 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
8966 return ScraperPtr();
8969 bool CVideoDatabase::GetUseAllExternalAudioForVideo(const std::string& videoPath)
8971 // Find longest configured source path for given video path
8972 std::string strSQL = PrepareSQL("SELECT allAudio FROM path WHERE allAudio IS NOT NULL AND "
8973 "instr('%s', strPath) = 1 ORDER BY length(strPath) DESC LIMIT 1",
8974 videoPath.c_str());
8975 m_pDS->query(strSQL);
8977 if (!m_pDS->eof())
8978 return m_pDS->fv("allAudio").get_asBool();
8980 return false;
8983 std::string CVideoDatabase::GetContentForPath(const std::string& strPath)
8985 SScanSettings settings;
8986 bool foundDirectly = false;
8987 ScraperPtr scraper = GetScraperForPath(strPath, settings, foundDirectly);
8988 if (scraper)
8990 if (scraper->Content() == CONTENT_TVSHOWS)
8992 // check for episodes or seasons. Assumptions are:
8993 // 1. if episodes are in the path then we're in episodes.
8994 // 2. if no episodes are found, and content was set directly on this path, then we're in shows.
8995 // 3. if no episodes are found, and content was not set directly on this path, we're in seasons (assumes tvshows/seasons/episodes)
8996 std::string sql = "SELECT COUNT(*) FROM episode_view ";
8998 if (foundDirectly)
8999 sql += PrepareSQL("WHERE strPath = '%s'", strPath.c_str());
9000 else
9001 sql += PrepareSQL("WHERE strPath LIKE '%s%%'", strPath.c_str());
9003 m_pDS->query( sql );
9004 if (m_pDS->num_rows() && m_pDS->fv(0).get_asInt() > 0)
9005 return "episodes";
9006 return foundDirectly ? "tvshows" : "seasons";
9008 return TranslateContent(scraper->Content());
9010 return "";
9013 void CVideoDatabase::GetMovieGenresByName(const std::string& strSearch, CFileItemList& items)
9015 std::string strSQL;
9019 if (nullptr == m_pDB)
9020 return;
9021 if (nullptr == m_pDS)
9022 return;
9024 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9025 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());
9026 else
9027 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());
9028 m_pDS->query( strSQL );
9030 while (!m_pDS->eof())
9032 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9033 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),
9034 *CMediaSourceSettings::GetInstance().GetSources("video")))
9036 m_pDS->next();
9037 continue;
9040 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9041 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9042 pItem->SetPath("videodb://movies/genres/"+ strDir);
9043 pItem->m_bIsFolder=true;
9044 items.Add(pItem);
9045 m_pDS->next();
9047 m_pDS->close();
9049 catch (...)
9051 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9055 void CVideoDatabase::GetMovieCountriesByName(const std::string& strSearch, CFileItemList& items)
9057 std::string strSQL;
9061 if (nullptr == m_pDB)
9062 return;
9063 if (nullptr == m_pDS)
9064 return;
9066 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9067 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());
9068 else
9069 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());
9070 m_pDS->query( strSQL );
9072 while (!m_pDS->eof())
9074 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9075 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),
9076 *CMediaSourceSettings::GetInstance().GetSources("video")))
9078 m_pDS->next();
9079 continue;
9082 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9083 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9084 pItem->SetPath("videodb://movies/genres/"+ strDir);
9085 pItem->m_bIsFolder=true;
9086 items.Add(pItem);
9087 m_pDS->next();
9089 m_pDS->close();
9091 catch (...)
9093 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9097 void CVideoDatabase::GetTvShowGenresByName(const std::string& strSearch, CFileItemList& items)
9099 std::string strSQL;
9103 if (nullptr == m_pDB)
9104 return;
9105 if (nullptr == m_pDS)
9106 return;
9108 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9109 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());
9110 else
9111 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());
9112 m_pDS->query( strSQL );
9114 while (!m_pDS->eof())
9116 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9117 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9119 m_pDS->next();
9120 continue;
9123 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9124 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9125 pItem->SetPath("videodb://tvshows/genres/"+ strDir);
9126 pItem->m_bIsFolder=true;
9127 items.Add(pItem);
9128 m_pDS->next();
9130 m_pDS->close();
9132 catch (...)
9134 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9138 void CVideoDatabase::GetMovieActorsByName(const std::string& strSearch, CFileItemList& items)
9140 std::string strSQL;
9144 if (nullptr == m_pDB)
9145 return;
9146 if (nullptr == m_pDS)
9147 return;
9149 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9150 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());
9151 else
9152 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());
9153 m_pDS->query( strSQL );
9155 while (!m_pDS->eof())
9157 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9158 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9160 m_pDS->next();
9161 continue;
9164 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9165 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9166 pItem->SetPath("videodb://movies/actors/"+ strDir);
9167 pItem->m_bIsFolder=true;
9168 items.Add(pItem);
9169 m_pDS->next();
9171 m_pDS->close();
9173 catch (...)
9175 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9179 void CVideoDatabase::GetTvShowsActorsByName(const std::string& strSearch, CFileItemList& items)
9181 std::string strSQL;
9185 if (nullptr == m_pDB)
9186 return;
9187 if (nullptr == m_pDS)
9188 return;
9190 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9191 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());
9192 else
9193 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());
9194 m_pDS->query( strSQL );
9196 while (!m_pDS->eof())
9198 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9199 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9201 m_pDS->next();
9202 continue;
9205 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9206 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9207 pItem->SetPath("videodb://tvshows/actors/"+ strDir);
9208 pItem->m_bIsFolder=true;
9209 items.Add(pItem);
9210 m_pDS->next();
9212 m_pDS->close();
9214 catch (...)
9216 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9220 void CVideoDatabase::GetMusicVideoArtistsByName(const std::string& strSearch, CFileItemList& items)
9222 std::string strSQL;
9226 if (nullptr == m_pDB)
9227 return;
9228 if (nullptr == m_pDS)
9229 return;
9231 std::string strLike;
9232 if (!strSearch.empty())
9233 strLike = "and actor.name like '%%%s%%'";
9234 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9235 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());
9236 else
9237 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());
9238 m_pDS->query( strSQL );
9240 while (!m_pDS->eof())
9242 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9243 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9245 m_pDS->next();
9246 continue;
9249 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9250 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9251 pItem->SetPath("videodb://musicvideos/artists/"+ strDir);
9252 pItem->m_bIsFolder=true;
9253 items.Add(pItem);
9254 m_pDS->next();
9256 m_pDS->close();
9258 catch (...)
9260 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9264 void CVideoDatabase::GetMusicVideoGenresByName(const std::string& strSearch, CFileItemList& items)
9266 std::string strSQL;
9270 if (nullptr == m_pDB)
9271 return;
9272 if (nullptr == m_pDS)
9273 return;
9275 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9276 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());
9277 else
9278 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());
9279 m_pDS->query( strSQL );
9281 while (!m_pDS->eof())
9283 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9284 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9286 m_pDS->next();
9287 continue;
9290 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9291 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9292 pItem->SetPath("videodb://musicvideos/genres/"+ strDir);
9293 pItem->m_bIsFolder=true;
9294 items.Add(pItem);
9295 m_pDS->next();
9297 m_pDS->close();
9299 catch (...)
9301 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9305 void CVideoDatabase::GetMusicVideoAlbumsByName(const std::string& strSearch, CFileItemList& items)
9307 std::string strSQL;
9311 if (nullptr == m_pDB)
9312 return;
9313 if (nullptr == m_pDS)
9314 return;
9316 strSQL = StringUtils::Format("SELECT DISTINCT"
9317 " musicvideo.c{:02},"
9318 " musicvideo.idMVideo,"
9319 " path.strPath"
9320 " FROM"
9321 " musicvideo"
9322 " JOIN files ON"
9323 " files.idFile=musicvideo.idFile"
9324 " JOIN path ON"
9325 " path.idPath=files.idPath",
9326 VIDEODB_ID_MUSICVIDEO_ALBUM);
9327 if (!strSearch.empty())
9328 strSQL += PrepareSQL(" WHERE musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_ALBUM, strSearch.c_str());
9330 m_pDS->query( strSQL );
9332 while (!m_pDS->eof())
9334 if (m_pDS->fv(0).get_asString().empty())
9336 m_pDS->next();
9337 continue;
9340 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9341 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9343 m_pDS->next();
9344 continue;
9347 CFileItemPtr pItem(new CFileItem(m_pDS->fv(0).get_asString()));
9348 std::string strDir = std::to_string(m_pDS->fv(1).get_asInt());
9349 pItem->SetPath("videodb://musicvideos/titles/"+ strDir);
9350 pItem->m_bIsFolder=false;
9351 items.Add(pItem);
9352 m_pDS->next();
9354 m_pDS->close();
9356 catch (...)
9358 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9362 void CVideoDatabase::GetMusicVideosByAlbum(const std::string& strSearch, CFileItemList& items)
9364 std::string strSQL;
9368 if (nullptr == m_pDB)
9369 return;
9370 if (nullptr == m_pDS)
9371 return;
9373 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9374 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());
9375 else
9376 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());
9377 m_pDS->query( strSQL );
9379 while (!m_pDS->eof())
9381 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9382 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9384 m_pDS->next();
9385 continue;
9388 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" - "+m_pDS->fv(2).get_asString()));
9389 std::string strDir =
9390 StringUtils::Format("3/2/{}", m_pDS->fv("musicvideo.idMVideo").get_asInt());
9392 pItem->SetPath("videodb://"+ strDir);
9393 pItem->m_bIsFolder=false;
9394 items.Add(pItem);
9395 m_pDS->next();
9397 m_pDS->close();
9399 catch (...)
9401 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9405 bool CVideoDatabase::GetMusicVideosByWhere(const std::string &baseDir, const Filter &filter, CFileItemList &items, bool checkLocks /*= true*/, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
9410 if (nullptr == m_pDB)
9411 return false;
9412 if (nullptr == m_pDS)
9413 return false;
9415 int total = -1;
9417 std::string strSQL = "select %s from musicvideo_view ";
9418 CVideoDbUrl videoUrl;
9419 if (!videoUrl.FromString(baseDir))
9420 return false;
9422 std::string strSQLExtra;
9423 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
9424 std::string strArtist;
9425 int idArtist = -1;
9426 // If we have an artistid then get the artist name and use that to fix up the path displayed in
9427 // the GUI (musicvideos/artist-name instead of musicvideos/artistid)
9428 auto option = options.find("artistid");
9429 if (option != options.end())
9431 idArtist = option->second.asInteger();
9432 strArtist = GetSingleValue(
9433 PrepareSQL("SELECT name FROM actor where actor_id = '%i'", idArtist), m_pDS)
9434 .c_str();
9436 Filter extFilter = filter;
9437 SortDescription sorting = sortDescription;
9438 if (!BuildSQL(baseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
9439 return false;
9441 // Apply the limiting directly here if there's no special sorting but limiting
9442 if (extFilter.limit.empty() && sorting.sortBy == SortByNone &&
9443 (sorting.limitStart > 0 || sorting.limitEnd > 0 ||
9444 (sorting.limitStart == 0 && sorting.limitEnd == 0)))
9446 total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
9447 strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
9450 strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
9452 int iRowsFound = RunQuery(strSQL);
9454 // store the total value of items as a property
9455 if (total < iRowsFound)
9456 total = iRowsFound;
9457 items.SetProperty("total", total);
9459 if (iRowsFound <= 0)
9460 return iRowsFound == 0;
9462 DatabaseResults results;
9463 results.reserve(iRowsFound);
9464 if (!SortUtils::SortFromDataset(sorting, MediaTypeMusicVideo, m_pDS, results))
9465 return false;
9467 // get data from returned rows
9468 items.Reserve(results.size());
9469 // get songs from returned subtable
9470 const query_data &data = m_pDS->get_result_set().records;
9471 for (const auto &i : results)
9473 unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
9474 const dbiplus::sql_record* const record = data.at(targetRow);
9476 CVideoInfoTag musicvideo = GetDetailsForMusicVideo(record, getDetails);
9477 if (!checkLocks || m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || g_passwordManager.bMasterUser ||
9478 g_passwordManager.IsDatabasePathUnlocked(musicvideo.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
9480 CFileItemPtr item(new CFileItem(musicvideo));
9482 CVideoDbUrl itemUrl = videoUrl;
9483 std::string path = std::to_string(record->at(0).get_asInt());
9484 itemUrl.AppendPath(path);
9485 item->SetPath(itemUrl.ToString());
9486 item->SetDynPath(musicvideo.m_strFileNameAndPath);
9488 item->SetOverlayImage(musicvideo.GetPlayCount() > 0 ? CGUIListItem::ICON_OVERLAY_WATCHED
9489 : CGUIListItem::ICON_OVERLAY_UNWATCHED);
9490 items.Add(item);
9494 // cleanup
9495 m_pDS->close();
9496 if (!strArtist.empty())
9497 items.SetProperty("customtitle", strArtist);
9498 return true;
9500 catch (...)
9502 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
9504 return false;
9507 unsigned int CVideoDatabase::GetRandomMusicVideoIDs(const std::string& strWhere, std::vector<std::pair<int,int> > &songIDs)
9511 if (nullptr == m_pDB)
9512 return 0;
9513 if (nullptr == m_pDS)
9514 return 0;
9516 std::string strSQL = "select distinct idMVideo from musicvideo_view";
9517 if (!strWhere.empty())
9518 strSQL += " where " + strWhere;
9519 strSQL += PrepareSQL(" ORDER BY RANDOM()");
9521 if (!m_pDS->query(strSQL)) return 0;
9522 songIDs.clear();
9523 if (m_pDS->num_rows() == 0)
9525 m_pDS->close();
9526 return 0;
9528 songIDs.reserve(m_pDS->num_rows());
9529 while (!m_pDS->eof())
9531 songIDs.push_back(std::make_pair<int,int>(2,m_pDS->fv(0).get_asInt()));
9532 m_pDS->next();
9533 } // cleanup
9534 m_pDS->close();
9535 return songIDs.size();
9537 catch (...)
9539 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strWhere);
9541 return 0;
9544 int CVideoDatabase::GetMatchingMusicVideo(const std::string& strArtist, const std::string& strAlbum, const std::string& strTitle)
9548 if (nullptr == m_pDB)
9549 return -1;
9550 if (nullptr == m_pDS)
9551 return -1;
9553 std::string strSQL;
9554 if (strAlbum.empty() && strTitle.empty())
9555 { // we want to return matching artists only
9556 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9557 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());
9558 else
9559 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());
9561 else
9562 { // we want to return the matching musicvideo
9563 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9564 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());
9565 else
9566 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());
9568 m_pDS->query( strSQL );
9570 if (m_pDS->eof())
9571 return -1;
9573 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9574 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9576 m_pDS->close();
9577 return -1;
9580 int lResult = m_pDS->fv(0).get_asInt();
9581 m_pDS->close();
9582 return lResult;
9584 catch (...)
9586 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
9588 return -1;
9591 void CVideoDatabase::GetMoviesByName(const std::string& strSearch, CFileItemList& items)
9593 std::string strSQL;
9597 if (nullptr == m_pDB)
9598 return;
9599 if (nullptr == m_pDS)
9600 return;
9602 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9603 strSQL = PrepareSQL("SELECT movie.idMovie, movie.c%02d, path.strPath, movie.idSet FROM movie "
9604 "INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON "
9605 "path.idPath=files.idPath "
9606 "WHERE movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
9607 VIDEODB_ID_TITLE, VIDEODB_ID_TITLE, strSearch.c_str(),
9608 VIDEODB_ID_ORIGINALTITLE, strSearch.c_str());
9609 else
9610 strSQL = PrepareSQL("SELECT movie.idMovie,movie.c%02d, movie.idSet FROM movie WHERE "
9611 "movie.c%02d like '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
9612 VIDEODB_ID_TITLE, VIDEODB_ID_TITLE, strSearch.c_str(),
9613 VIDEODB_ID_ORIGINALTITLE, strSearch.c_str());
9614 m_pDS->query( strSQL );
9616 while (!m_pDS->eof())
9618 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9619 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9621 m_pDS->next();
9622 continue;
9625 int movieId = m_pDS->fv("movie.idMovie").get_asInt();
9626 int setId = m_pDS->fv("movie.idSet").get_asInt();
9627 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9628 std::string path;
9629 if (setId <= 0 || !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS))
9630 path = StringUtils::Format("videodb://movies/titles/{}", movieId);
9631 else
9632 path = StringUtils::Format("videodb://movies/sets/{}/{}", setId, movieId);
9633 pItem->SetPath(path);
9634 pItem->m_bIsFolder=false;
9635 items.Add(pItem);
9636 m_pDS->next();
9638 m_pDS->close();
9640 catch (...)
9642 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9646 void CVideoDatabase::GetTvShowsByName(const std::string& strSearch, CFileItemList& items)
9648 std::string strSQL;
9652 if (nullptr == m_pDB)
9653 return;
9654 if (nullptr == m_pDS)
9655 return;
9657 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9658 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());
9659 else
9660 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());
9661 m_pDS->query( strSQL );
9663 while (!m_pDS->eof())
9665 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9666 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9668 m_pDS->next();
9669 continue;
9672 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9673 std::string strDir =
9674 StringUtils::Format("tvshows/titles/{}/", m_pDS->fv("tvshow.idShow").get_asInt());
9676 pItem->SetPath("videodb://"+ strDir);
9677 pItem->m_bIsFolder=true;
9678 pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv("tvshow.idShow").get_asInt();
9679 items.Add(pItem);
9680 m_pDS->next();
9682 m_pDS->close();
9684 catch (...)
9686 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9690 void CVideoDatabase::GetEpisodesByName(const std::string& strSearch, CFileItemList& items)
9692 std::string strSQL;
9696 if (nullptr == m_pDB)
9697 return;
9698 if (nullptr == m_pDS)
9699 return;
9701 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9702 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());
9703 else
9704 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());
9705 m_pDS->query( strSQL );
9707 while (!m_pDS->eof())
9709 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9710 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9712 m_pDS->next();
9713 continue;
9716 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
9717 std::string path = StringUtils::Format("videodb://tvshows/titles/{}/{}/{}",
9718 m_pDS->fv("episode.idShow").get_asInt(),
9719 m_pDS->fv(2).get_asInt(), m_pDS->fv(0).get_asInt());
9720 pItem->SetPath(path);
9721 pItem->m_bIsFolder=false;
9722 items.Add(pItem);
9723 m_pDS->next();
9725 m_pDS->close();
9727 catch (...)
9729 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9733 void CVideoDatabase::GetMusicVideosByName(const std::string& strSearch, CFileItemList& items)
9735 // Alternative searching - not quite as fast though due to
9736 // retrieving all information
9737 // 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()));
9738 // GetMusicVideosByWhere("videodb://musicvideos/titles/", filter, items);
9739 std::string strSQL;
9743 if (nullptr == m_pDB)
9744 return;
9745 if (nullptr == m_pDS)
9746 return;
9748 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9749 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());
9750 else
9751 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());
9752 m_pDS->query( strSQL );
9754 while (!m_pDS->eof())
9756 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9757 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9759 m_pDS->next();
9760 continue;
9763 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9764 std::string strDir =
9765 StringUtils::Format("3/2/{}", m_pDS->fv("musicvideo.idMVideo").get_asInt());
9767 pItem->SetPath("videodb://"+ strDir);
9768 pItem->m_bIsFolder=false;
9769 items.Add(pItem);
9770 m_pDS->next();
9772 m_pDS->close();
9774 catch (...)
9776 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9780 void CVideoDatabase::GetEpisodesByPlot(const std::string& strSearch, CFileItemList& items)
9782 // Alternative searching - not quite as fast though due to
9783 // retrieving all information
9784 // Filter filter;
9785 // 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());
9786 // 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());
9787 // GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
9788 // return;
9789 std::string strSQL;
9793 if (nullptr == m_pDB)
9794 return;
9795 if (nullptr == m_pDS)
9796 return;
9798 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9799 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());
9800 else
9801 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());
9802 m_pDS->query( strSQL );
9804 while (!m_pDS->eof())
9806 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9807 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9809 m_pDS->next();
9810 continue;
9813 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
9814 std::string path = StringUtils::Format("videodb://tvshows/titles/{}/{}/{}",
9815 m_pDS->fv("episode.idShow").get_asInt(),
9816 m_pDS->fv(2).get_asInt(), m_pDS->fv(0).get_asInt());
9817 pItem->SetPath(path);
9818 pItem->m_bIsFolder=false;
9819 items.Add(pItem);
9820 m_pDS->next();
9822 m_pDS->close();
9824 catch (...)
9826 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9830 void CVideoDatabase::GetMoviesByPlot(const std::string& strSearch, CFileItemList& items)
9832 std::string strSQL;
9836 if (nullptr == m_pDB)
9837 return;
9838 if (nullptr == m_pDS)
9839 return;
9841 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9842 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());
9843 else
9844 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());
9846 m_pDS->query( strSQL );
9848 while (!m_pDS->eof())
9850 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9851 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9853 m_pDS->next();
9854 continue;
9857 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9858 std::string path =
9859 StringUtils::Format("videodb://movies/titles/{}", m_pDS->fv(0).get_asInt());
9860 pItem->SetPath(path);
9861 pItem->m_bIsFolder=false;
9863 items.Add(pItem);
9864 m_pDS->next();
9866 m_pDS->close();
9869 catch (...)
9871 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9875 void CVideoDatabase::GetMovieDirectorsByName(const std::string& strSearch, CFileItemList& items)
9877 std::string strSQL;
9881 if (nullptr == m_pDB)
9882 return;
9883 if (nullptr == m_pDS)
9884 return;
9886 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9887 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());
9888 else
9889 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());
9891 m_pDS->query( strSQL );
9893 while (!m_pDS->eof())
9895 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9896 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9898 m_pDS->next();
9899 continue;
9902 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9903 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9905 pItem->SetPath("videodb://movies/directors/"+ strDir);
9906 pItem->m_bIsFolder=true;
9907 items.Add(pItem);
9908 m_pDS->next();
9910 m_pDS->close();
9912 catch (...)
9914 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9918 void CVideoDatabase::GetTvShowsDirectorsByName(const std::string& strSearch, CFileItemList& items)
9920 std::string strSQL;
9924 if (nullptr == m_pDB)
9925 return;
9926 if (nullptr == m_pDS)
9927 return;
9929 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9930 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());
9931 else
9932 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());
9934 m_pDS->query( strSQL );
9936 while (!m_pDS->eof())
9938 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9939 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9941 m_pDS->next();
9942 continue;
9945 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9946 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9948 pItem->SetPath("videodb://tvshows/directors/"+ strDir);
9949 pItem->m_bIsFolder=true;
9950 items.Add(pItem);
9951 m_pDS->next();
9953 m_pDS->close();
9955 catch (...)
9957 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
9961 void CVideoDatabase::GetMusicVideoDirectorsByName(const std::string& strSearch, CFileItemList& items)
9963 std::string strSQL;
9967 if (nullptr == m_pDB)
9968 return;
9969 if (nullptr == m_pDS)
9970 return;
9972 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9973 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());
9974 else
9975 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());
9977 m_pDS->query( strSQL );
9979 while (!m_pDS->eof())
9981 if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9982 if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9984 m_pDS->next();
9985 continue;
9988 std::string strDir = StringUtils::Format("{}/", m_pDS->fv(0).get_asInt());
9989 CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9991 pItem->SetPath("videodb://musicvideos/albums/"+ strDir);
9992 pItem->m_bIsFolder=true;
9993 items.Add(pItem);
9994 m_pDS->next();
9996 m_pDS->close();
9998 catch (...)
10000 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
10004 void CVideoDatabase::CleanDatabase(CGUIDialogProgressBarHandle* handle,
10005 const std::set<int>& paths,
10006 bool showProgress)
10008 CGUIDialogProgress* progress = NULL;
10011 if (nullptr == m_pDB)
10012 return;
10013 if (nullptr == m_pDS)
10014 return;
10015 if (nullptr == m_pDS2)
10016 return;
10018 auto start = std::chrono::steady_clock::now();
10019 CLog::Log(LOGINFO, "{}: Starting videodatabase cleanup ..", __FUNCTION__);
10020 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary,
10021 "OnCleanStarted");
10023 if (handle)
10025 handle->SetTitle(g_localizeStrings.Get(700));
10026 handle->SetText("");
10028 else if (showProgress)
10030 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(
10031 WINDOW_DIALOG_PROGRESS);
10032 if (progress)
10034 progress->SetHeading(CVariant{700});
10035 progress->SetLine(0, CVariant{""});
10036 progress->SetLine(1, CVariant{313});
10037 progress->SetLine(2, CVariant{330});
10038 progress->SetPercentage(0);
10039 progress->Open();
10040 progress->ShowProgressBar(true);
10044 BeginTransaction();
10046 // find all the files
10047 std::string sql = "SELECT files.idFile, files.strFileName, path.strPath FROM files "
10048 "INNER JOIN path ON path.idPath=files.idPath";
10049 if (!paths.empty())
10051 std::string strPaths;
10052 for (const auto& i : paths)
10053 strPaths += StringUtils::Format(",{}", i);
10054 sql += PrepareSQL(" AND path.idPath IN (%s)", strPaths.substr(1).c_str());
10057 // For directory caching to work properly, we need to sort the files by path
10058 sql += " ORDER BY path.strPath";
10060 m_pDS2->query(sql);
10061 if (m_pDS2->num_rows() > 0)
10063 std::string filesToTestForDelete;
10064 VECSOURCES videoSources(*CMediaSourceSettings::GetInstance().GetSources("video"));
10065 CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources);
10067 int total = m_pDS2->num_rows();
10068 int current = 0;
10069 std::string lastDir;
10070 bool gotDir = true;
10072 while (!m_pDS2->eof())
10074 std::string path = m_pDS2->fv("path.strPath").get_asString();
10075 std::string fileName = m_pDS2->fv("files.strFileName").get_asString();
10076 std::string fullPath;
10077 ConstructPath(fullPath, path, fileName);
10079 // get the first stacked file
10080 if (URIUtils::IsStack(fullPath))
10081 fullPath = CStackDirectory::GetFirstStackedFile(fullPath);
10083 // get the actual archive path
10084 if (URIUtils::IsInArchive(fullPath))
10085 fullPath = CURL(fullPath).GetHostName();
10087 bool del = true;
10088 if (URIUtils::IsPlugin(fullPath))
10090 SScanSettings settings;
10091 bool foundDirectly = false;
10092 ScraperPtr scraper = GetScraperForPath(fullPath, settings, foundDirectly);
10093 if (scraper &&
10094 CPluginDirectory::CheckExists(TranslateContent(scraper->Content()), fullPath))
10095 del = false;
10097 else
10099 // Only consider keeping this file if not optical and belonging to a (matching) source
10100 bool bIsSource;
10101 if (!URIUtils::IsOnDVD(fullPath) &&
10102 CUtil::GetMatchingSource(fullPath, videoSources, bIsSource) >= 0)
10104 const std::string pathDir = URIUtils::GetDirectory(fullPath);
10106 // Cache file's directory in case it's different from the previous file
10107 if (lastDir != pathDir)
10109 lastDir = pathDir;
10110 CFileItemList items; // Dummy list
10111 gotDir = CDirectory::GetDirectory(pathDir, items, "",
10112 DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO);
10115 // Keep existing files
10116 if (gotDir && CFile::Exists(fullPath, true))
10117 del = false;
10120 if (del)
10121 filesToTestForDelete += m_pDS2->fv("files.idFile").get_asString() + ",";
10123 if (handle == NULL && progress != NULL)
10125 int percentage = current * 100 / total;
10126 if (percentage > progress->GetPercentage())
10128 progress->SetPercentage(percentage);
10129 progress->Progress();
10131 if (progress->IsCanceled())
10133 progress->Close();
10134 m_pDS2->close();
10135 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary,
10136 "OnCleanFinished");
10137 return;
10140 else if (handle != NULL)
10141 handle->SetPercentage(current * 100 / (float)total);
10143 m_pDS2->next();
10144 current++;
10146 m_pDS2->close();
10148 std::string filesToDelete;
10150 // Add any files that don't have a valid idPath entry to the filesToDelete list.
10151 m_pDS->query("SELECT files.idFile FROM files WHERE NOT EXISTS (SELECT 1 FROM path "
10152 "WHERE path.idPath = files.idPath)");
10153 while (!m_pDS->eof())
10155 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
10156 filesToTestForDelete += file;
10157 filesToDelete += file;
10159 m_pDS->next();
10161 m_pDS->close();
10163 std::map<int, bool> pathsDeleteDecisions;
10164 std::vector<int> movieIDs;
10165 std::vector<int> tvshowIDs;
10166 std::vector<int> episodeIDs;
10167 std::vector<int> musicVideoIDs;
10168 std::vector<int> videoVersionIDs;
10170 if (!filesToTestForDelete.empty())
10172 StringUtils::TrimRight(filesToTestForDelete, ",");
10174 movieIDs = CleanMediaType(MediaTypeMovie, filesToTestForDelete, pathsDeleteDecisions,
10175 filesToDelete, !showProgress);
10176 episodeIDs = CleanMediaType(MediaTypeEpisode, filesToTestForDelete, pathsDeleteDecisions,
10177 filesToDelete, !showProgress);
10178 musicVideoIDs = CleanMediaType(MediaTypeMusicVideo, filesToTestForDelete,
10179 pathsDeleteDecisions, filesToDelete, !showProgress);
10180 videoVersionIDs = CleanMediaType(MediaTypeVideoVersion, filesToTestForDelete,
10181 pathsDeleteDecisions, filesToDelete, !showProgress);
10184 if (progress != NULL)
10186 progress->SetPercentage(100);
10187 progress->Progress();
10190 if (!filesToDelete.empty())
10192 filesToDelete = "(" + StringUtils::TrimRight(filesToDelete, ",") + ")";
10194 // Clean hashes of all paths that files are deleted from
10195 // Otherwise there is a mismatch between the path contents and the hash in the
10196 // database, leading to potentially missed items on re-scan (if deleted files are
10197 // later re-added to a source)
10198 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaning path hashes");
10199 m_pDS->query("SELECT DISTINCT strPath FROM path JOIN files ON files.idPath=path.idPath "
10200 "WHERE files.idFile IN " +
10201 filesToDelete);
10202 int pathHashCount = m_pDS->num_rows();
10203 while (!m_pDS->eof())
10205 InvalidatePathHash(m_pDS->fv("strPath").get_asString());
10206 m_pDS->next();
10208 CLog::LogFC(LOGDEBUG, LOGDATABASE, "Cleaned {} path hashes", pathHashCount);
10210 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning files table", __FUNCTION__);
10211 sql = "DELETE FROM files WHERE idFile IN " + filesToDelete;
10212 m_pDS->exec(sql);
10215 if (!movieIDs.empty())
10217 std::string moviesToDelete;
10218 for (const auto& i : movieIDs)
10219 moviesToDelete += StringUtils::Format("{},", i);
10220 moviesToDelete = "(" + StringUtils::TrimRight(moviesToDelete, ",") + ")";
10222 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning movie table", __FUNCTION__);
10223 sql = "DELETE FROM movie WHERE idMovie IN " + moviesToDelete;
10224 m_pDS->exec(sql);
10227 if (!episodeIDs.empty())
10229 std::string episodesToDelete;
10230 for (const auto& i : episodeIDs)
10231 episodesToDelete += StringUtils::Format("{},", i);
10232 episodesToDelete = "(" + StringUtils::TrimRight(episodesToDelete, ",") + ")";
10234 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning episode table", __FUNCTION__);
10235 sql = "DELETE FROM episode WHERE idEpisode IN " + episodesToDelete;
10236 m_pDS->exec(sql);
10239 CLog::Log(LOGDEBUG, LOGDATABASE,
10240 "{}: Cleaning paths that don't exist and have content set...", __FUNCTION__);
10241 sql = "SELECT path.idPath, path.strPath, path.idParentPath FROM path "
10242 "WHERE NOT ((strContent IS NULL OR strContent = '') "
10243 "AND (strSettings IS NULL OR strSettings = '') "
10244 "AND (strHash IS NULL OR strHash = '') "
10245 "AND (exclude IS NULL OR exclude != 1))";
10246 m_pDS2->query(sql);
10247 std::string strIds;
10248 while (!m_pDS2->eof())
10250 auto pathsDeleteDecision = pathsDeleteDecisions.find(m_pDS2->fv(0).get_asInt());
10251 // Check if we have a decision for the parent path
10252 auto pathsDeleteDecisionByParent = pathsDeleteDecisions.find(m_pDS2->fv(2).get_asInt());
10253 std::string path = m_pDS2->fv(1).get_asString();
10255 bool exists = false;
10256 if (URIUtils::IsPlugin(path))
10258 SScanSettings settings;
10259 bool foundDirectly = false;
10260 ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
10261 if (scraper && CPluginDirectory::CheckExists(TranslateContent(scraper->Content()), path))
10262 exists = true;
10264 else
10265 exists = CDirectory::Exists(path, false);
10267 if (((pathsDeleteDecision != pathsDeleteDecisions.end() && pathsDeleteDecision->second) ||
10268 (pathsDeleteDecision == pathsDeleteDecisions.end() && !exists)) &&
10269 ((pathsDeleteDecisionByParent != pathsDeleteDecisions.end() &&
10270 pathsDeleteDecisionByParent->second) ||
10271 (pathsDeleteDecisionByParent == pathsDeleteDecisions.end())))
10272 strIds += m_pDS2->fv(0).get_asString() + ",";
10274 m_pDS2->next();
10276 m_pDS2->close();
10278 if (!strIds.empty())
10280 sql = PrepareSQL("DELETE FROM path WHERE idPath IN (%s)",
10281 StringUtils::TrimRight(strIds, ",").c_str());
10282 m_pDS->exec(sql);
10283 sql = "DELETE FROM tvshowlinkpath "
10284 "WHERE NOT EXISTS (SELECT 1 FROM path WHERE path.idPath = tvshowlinkpath.idPath)";
10285 m_pDS->exec(sql);
10288 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning tvshow table", __FUNCTION__);
10290 std::string tvshowsToDelete;
10291 sql = "SELECT idShow FROM tvshow "
10292 "WHERE NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idShow = "
10293 "tvshow.idShow)";
10294 m_pDS->query(sql);
10295 while (!m_pDS->eof())
10297 tvshowIDs.push_back(m_pDS->fv(0).get_asInt());
10298 tvshowsToDelete += m_pDS->fv(0).get_asString() + ",";
10299 m_pDS->next();
10301 m_pDS->close();
10302 if (!tvshowsToDelete.empty())
10304 sql = "DELETE FROM tvshow WHERE idShow IN (" +
10305 StringUtils::TrimRight(tvshowsToDelete, ",") + ")";
10306 m_pDS->exec(sql);
10309 if (!musicVideoIDs.empty())
10311 std::string musicVideosToDelete;
10312 for (const auto& i : musicVideoIDs)
10313 musicVideosToDelete += StringUtils::Format("{},", i);
10314 musicVideosToDelete = "(" + StringUtils::TrimRight(musicVideosToDelete, ",") + ")";
10316 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning musicvideo table", __FUNCTION__);
10317 sql = "DELETE FROM musicvideo WHERE idMVideo IN " + musicVideosToDelete;
10318 m_pDS->exec(sql);
10321 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning path table", __FUNCTION__);
10322 sql = StringUtils::Format(
10323 "DELETE FROM path "
10324 "WHERE (strContent IS NULL OR strContent = '') "
10325 "AND (strSettings IS NULL OR strSettings = '') "
10326 "AND (strHash IS NULL OR strHash = '') "
10327 "AND (exclude IS NULL OR exclude != 1) "
10328 "AND (idParentPath IS NULL OR NOT EXISTS (SELECT 1 FROM (SELECT idPath FROM path) as "
10329 "parentPath WHERE parentPath.idPath = path.idParentPath)) " // MySQL only fix (#5007)
10330 "AND NOT EXISTS (SELECT 1 FROM files WHERE files.idPath = path.idPath) "
10331 "AND NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idPath = path.idPath) "
10332 "AND NOT EXISTS (SELECT 1 FROM movie WHERE movie.c{:02} = path.idPath) "
10333 "AND NOT EXISTS (SELECT 1 FROM episode WHERE episode.c{:02} = path.idPath) "
10334 "AND NOT EXISTS (SELECT 1 FROM musicvideo WHERE musicvideo.c{:02} = path.idPath)",
10335 VIDEODB_ID_PARENTPATHID, VIDEODB_ID_EPISODE_PARENTPATHID,
10336 VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
10337 m_pDS->exec(sql);
10339 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning genre table", __FUNCTION__);
10340 sql =
10341 "DELETE FROM genre "
10342 "WHERE NOT EXISTS (SELECT 1 FROM genre_link WHERE genre_link.genre_id = genre.genre_id)";
10343 m_pDS->exec(sql);
10345 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning country table", __FUNCTION__);
10346 sql = "DELETE FROM country WHERE NOT EXISTS (SELECT 1 FROM country_link WHERE "
10347 "country_link.country_id = country.country_id)";
10348 m_pDS->exec(sql);
10350 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning actor table of actors, directors and writers",
10351 __FUNCTION__);
10352 sql =
10353 "DELETE FROM actor "
10354 "WHERE NOT EXISTS (SELECT 1 FROM actor_link WHERE actor_link.actor_id = actor.actor_id) "
10355 "AND NOT EXISTS (SELECT 1 FROM director_link WHERE director_link.actor_id = "
10356 "actor.actor_id) "
10357 "AND NOT EXISTS (SELECT 1 FROM writer_link WHERE writer_link.actor_id = actor.actor_id)";
10358 m_pDS->exec(sql);
10360 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning studio table", __FUNCTION__);
10361 sql = "DELETE FROM studio "
10362 "WHERE NOT EXISTS (SELECT 1 FROM studio_link WHERE studio_link.studio_id = "
10363 "studio.studio_id)";
10364 m_pDS->exec(sql);
10366 CLog::Log(LOGDEBUG, LOGDATABASE, "{}: Cleaning set table", __FUNCTION__);
10367 sql = "DELETE FROM sets "
10368 "WHERE NOT EXISTS (SELECT 1 FROM movie WHERE movie.idSet = sets.idSet)";
10369 m_pDS->exec(sql);
10371 CommitTransaction();
10373 if (handle)
10374 handle->SetTitle(g_localizeStrings.Get(331));
10376 Compress(false);
10378 CUtil::DeleteVideoDatabaseDirectoryCache();
10380 auto end = std::chrono::steady_clock::now();
10381 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
10383 CLog::Log(LOGINFO, "{}: Cleaning videodatabase done. Operation took {} ms", __FUNCTION__,
10384 duration.count());
10386 for (const auto& i : movieIDs)
10387 AnnounceRemove(MediaTypeMovie, i, true);
10389 for (const auto& i : episodeIDs)
10390 AnnounceRemove(MediaTypeEpisode, i, true);
10392 for (const auto& i : tvshowIDs)
10393 AnnounceRemove(MediaTypeTvShow, i, true);
10395 for (const auto& i : musicVideoIDs)
10396 AnnounceRemove(MediaTypeMusicVideo, i, true);
10398 for (const auto& i : videoVersionIDs)
10399 AnnounceRemove(MediaTypeVideoVersion, i, true);
10402 catch (...)
10404 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
10405 RollbackTransaction();
10407 if (progress)
10408 progress->Close();
10410 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnCleanFinished");
10413 std::vector<int> CVideoDatabase::CleanMediaType(const std::string &mediaType, const std::string &cleanableFileIDs,
10414 std::map<int, bool> &pathsDeleteDecisions, std::string &deletedFileIDs, bool silent)
10416 std::vector<int> cleanedIDs;
10417 if (mediaType.empty() || cleanableFileIDs.empty())
10418 return cleanedIDs;
10420 const std::string& table = mediaType;
10421 std::string idField;
10422 std::string parentPathIdField;
10423 bool isEpisode = false;
10424 if (mediaType == MediaTypeMovie)
10426 idField = "idMovie";
10427 parentPathIdField = StringUtils::Format("{}.c{:02}", table, VIDEODB_ID_PARENTPATHID);
10429 else if (mediaType == MediaTypeEpisode)
10431 idField = "idEpisode";
10432 parentPathIdField = "showPath.idParentPath";
10433 isEpisode = true;
10435 else if (mediaType == MediaTypeMusicVideo)
10437 idField = "idMVideo";
10438 parentPathIdField = StringUtils::Format("{}.c{:02}", table, VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
10440 else if (mediaType == MediaTypeVideoVersion)
10442 idField = "idMedia";
10443 parentPathIdField = "path.idPath";
10445 else
10446 return cleanedIDs;
10448 // now grab them media items
10449 std::string sql = PrepareSQL("SELECT %s.%s, %s.idFile, path.idPath, parentPath.strPath FROM %s "
10450 "JOIN files ON files.idFile = %s.idFile "
10451 "JOIN path ON path.idPath = files.idPath ",
10452 table.c_str(), idField.c_str(), table.c_str(), table.c_str(),
10453 table.c_str());
10455 if (isEpisode)
10456 sql += "JOIN tvshowlinkpath ON tvshowlinkpath.idShow = episode.idShow JOIN path AS showPath ON showPath.idPath=tvshowlinkpath.idPath ";
10458 sql += PrepareSQL("LEFT JOIN path as parentPath ON parentPath.idPath = %s "
10459 "WHERE %s.idFile IN (%s)",
10460 parentPathIdField.c_str(),
10461 table.c_str(), cleanableFileIDs.c_str());
10463 VECSOURCES videoSources(*CMediaSourceSettings::GetInstance().GetSources("video"));
10464 CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources);
10466 // map of parent path ID to boolean pair (if not exists and user choice)
10467 std::map<int, std::pair<bool, bool> > sourcePathsDeleteDecisions;
10468 m_pDS2->query(sql);
10469 while (!m_pDS2->eof())
10471 bool del = true;
10472 if (m_pDS2->fv(3).get_isNull() == false)
10474 std::string parentPath = m_pDS2->fv(3).get_asString();
10476 // try to find the source path the parent path belongs to
10477 SScanSettings scanSettings;
10478 std::string sourcePath;
10479 GetSourcePath(parentPath, sourcePath, scanSettings);
10481 bool bIsSourceName;
10482 bool sourceNotFound = (CUtil::GetMatchingSource(parentPath, videoSources, bIsSourceName) < 0);
10484 if (sourceNotFound && sourcePath.empty())
10485 sourcePath = parentPath;
10487 int sourcePathID = GetPathId(sourcePath);
10488 auto sourcePathsDeleteDecision = sourcePathsDeleteDecisions.find(sourcePathID);
10489 if (sourcePathsDeleteDecision == sourcePathsDeleteDecisions.end())
10491 bool sourcePathNotExists = (sourceNotFound || !CDirectory::Exists(sourcePath, false));
10492 // if the parent path exists, the file will be deleted without asking
10493 // if the parent path doesn't exist or does not belong to a valid media source,
10494 // ask the user whether to remove all items it contained
10495 if (sourcePathNotExists)
10497 // in silent mode assume that the files are just temporarily missing
10498 if (silent)
10499 del = false;
10500 else
10502 CGUIDialogYesNo* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO);
10503 if (pDialog != NULL)
10505 CURL sourceUrl(sourcePath);
10506 pDialog->SetHeading(CVariant{15012});
10507 pDialog->SetText(CVariant{StringUtils::Format(g_localizeStrings.Get(15013),
10508 sourceUrl.GetWithoutUserDetails())});
10509 pDialog->SetChoice(0, CVariant{15015});
10510 pDialog->SetChoice(1, CVariant{15014});
10511 pDialog->Open();
10513 del = !pDialog->IsConfirmed();
10518 sourcePathsDeleteDecisions.insert(std::make_pair(sourcePathID, std::make_pair(sourcePathNotExists, del)));
10519 pathsDeleteDecisions.insert(std::make_pair(sourcePathID, sourcePathNotExists && del));
10521 // the only reason not to delete the file is if the parent path doesn't
10522 // exist and the user decided to delete all the items it contained
10523 else if (sourcePathsDeleteDecision->second.first &&
10524 !sourcePathsDeleteDecision->second.second)
10525 del = false;
10527 if (scanSettings.parent_name)
10528 pathsDeleteDecisions.insert(std::make_pair(m_pDS2->fv(2).get_asInt(), del));
10531 if (del)
10533 deletedFileIDs += m_pDS2->fv(1).get_asString() + ",";
10534 cleanedIDs.push_back(m_pDS2->fv(0).get_asInt());
10537 m_pDS2->next();
10539 m_pDS2->close();
10541 return cleanedIDs;
10544 void CVideoDatabase::DumpToDummyFiles(const std::string &path)
10546 // get all tvshows
10547 CFileItemList items;
10548 GetTvShowsByWhere("videodb://tvshows/titles/", CDatabase::Filter(), items);
10549 std::string showPath = URIUtils::AddFileToFolder(path, "shows");
10550 CDirectory::Create(showPath);
10551 for (int i = 0; i < items.Size(); i++)
10553 // create a folder in this directory
10554 std::string showName = CUtil::MakeLegalFileName(items[i]->GetVideoInfoTag()->m_strShowTitle);
10555 std::string TVFolder = URIUtils::AddFileToFolder(showPath, showName);
10556 if (CDirectory::Create(TVFolder))
10557 { // right - grab the episodes and dump them as well
10558 CFileItemList episodes;
10559 Filter filter(PrepareSQL("idShow=%i", items[i]->GetVideoInfoTag()->m_iDbId));
10560 GetEpisodesByWhere("videodb://tvshows/titles/", filter, episodes);
10561 for (int i = 0; i < episodes.Size(); i++)
10563 CVideoInfoTag *tag = episodes[i]->GetVideoInfoTag();
10564 std::string episode =
10565 StringUtils::Format("{}.s{:02}e{:02}.avi", showName, tag->m_iSeason, tag->m_iEpisode);
10566 // and make a file
10567 std::string episodePath = URIUtils::AddFileToFolder(TVFolder, episode);
10568 CFile file;
10569 if (file.OpenForWrite(episodePath))
10570 file.Close();
10574 // get all movies
10575 items.Clear();
10576 GetMoviesByWhere("videodb://movies/titles/", CDatabase::Filter(), items);
10577 std::string moviePath = URIUtils::AddFileToFolder(path, "movies");
10578 CDirectory::Create(moviePath);
10579 for (int i = 0; i < items.Size(); i++)
10581 CVideoInfoTag *tag = items[i]->GetVideoInfoTag();
10582 std::string movie = StringUtils::Format("{}.avi", tag->m_strTitle);
10583 CFile file;
10584 if (file.OpenForWrite(URIUtils::AddFileToFolder(moviePath, movie)))
10585 file.Close();
10589 void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = true */, bool images /* = false */, bool actorThumbs /* false */, bool overwrite /*=false*/)
10591 int iFailCount = 0;
10592 CGUIDialogProgress *progress=NULL;
10595 if (nullptr == m_pDB)
10596 return;
10597 if (nullptr == m_pDS)
10598 return;
10599 if (nullptr == m_pDS2)
10600 return;
10602 // create a 3rd dataset as well as GetEpisodeDetails() etc. uses m_pDS2, and we need to do 3 nested queries on tv shows
10603 std::unique_ptr<Dataset> pDS;
10604 pDS.reset(m_pDB->CreateDataset());
10605 if (nullptr == pDS)
10606 return;
10608 std::unique_ptr<Dataset> pDS2;
10609 pDS2.reset(m_pDB->CreateDataset());
10610 if (nullptr == pDS2)
10611 return;
10613 // if we're exporting to a single folder, we export thumbs as well
10614 std::string exportRoot = URIUtils::AddFileToFolder(path, "kodi_videodb_" + CDateTime::GetCurrentDateTime().GetAsDBDate());
10615 std::string xmlFile = URIUtils::AddFileToFolder(exportRoot, "videodb.xml");
10616 std::string actorsDir = URIUtils::AddFileToFolder(exportRoot, "actors");
10617 std::string moviesDir = URIUtils::AddFileToFolder(exportRoot, "movies");
10618 std::string movieSetsDir = URIUtils::AddFileToFolder(exportRoot, "moviesets");
10619 std::string musicvideosDir = URIUtils::AddFileToFolder(exportRoot, "musicvideos");
10620 std::string tvshowsDir = URIUtils::AddFileToFolder(exportRoot, "tvshows");
10621 if (singleFile)
10623 images = true;
10624 overwrite = false;
10625 actorThumbs = true;
10626 CDirectory::Remove(exportRoot);
10627 CDirectory::Create(exportRoot);
10628 CDirectory::Create(actorsDir);
10629 CDirectory::Create(moviesDir);
10630 CDirectory::Create(movieSetsDir);
10631 CDirectory::Create(musicvideosDir);
10632 CDirectory::Create(tvshowsDir);
10635 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
10636 // find all movies
10637 std::string sql = "select * from movie_view";
10639 m_pDS->query(sql);
10641 if (progress)
10643 progress->SetHeading(CVariant{647});
10644 progress->SetLine(0, CVariant{650});
10645 progress->SetLine(1, CVariant{""});
10646 progress->SetLine(2, CVariant{""});
10647 progress->SetPercentage(0);
10648 progress->Open();
10649 progress->ShowProgressBar(true);
10652 int total = m_pDS->num_rows();
10653 int current = 0;
10655 // create our xml document
10656 CXBMCTinyXML xmlDoc;
10657 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10658 xmlDoc.InsertEndChild(decl);
10659 TiXmlNode *pMain = NULL;
10660 if (!singleFile)
10661 pMain = &xmlDoc;
10662 else
10664 TiXmlElement xmlMainElement("videodb");
10665 pMain = xmlDoc.InsertEndChild(xmlMainElement);
10666 XMLUtils::SetInt(pMain,"version", GetExportVersion());
10669 while (!m_pDS->eof())
10671 CVideoInfoTag movie = GetDetailsForMovie(m_pDS, VideoDbDetailsAll);
10672 // strip paths to make them relative
10673 if (StringUtils::StartsWith(movie.m_strTrailer, movie.m_strPath))
10674 movie.m_strTrailer = movie.m_strTrailer.substr(movie.m_strPath.size());
10675 std::map<std::string, std::string> artwork;
10676 if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && singleFile)
10678 TiXmlElement additionalNode("art");
10679 for (const auto &i : artwork)
10680 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10681 movie.Save(pMain, "movie", true, &additionalNode);
10683 else
10684 movie.Save(pMain, "movie", singleFile);
10686 // reset old skip state
10687 bool bSkip = false;
10689 if (progress)
10691 progress->SetLine(1, CVariant{movie.m_strTitle});
10692 progress->SetPercentage(current * 100 / total);
10693 progress->Progress();
10694 if (progress->IsCanceled())
10696 progress->Close();
10697 m_pDS->close();
10698 return;
10702 CFileItem item(movie.m_strFileNameAndPath,false);
10703 if (!singleFile && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
10705 if (!item.Exists(false))
10707 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10708 movie.m_strFileNameAndPath);
10709 bSkip = true;
10711 else
10713 std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
10715 if (item.IsOpticalMediaFile())
10717 nfoFile = URIUtils::AddFileToFolder(
10718 URIUtils::GetParentPath(nfoFile),
10719 URIUtils::GetFileName(nfoFile));
10722 if (overwrite || !CFile::Exists(nfoFile, false))
10724 if(!xmlDoc.SaveFile(nfoFile))
10726 CLog::Log(LOGERROR, "{}: Movie nfo export failed! ('{}')", __FUNCTION__, nfoFile);
10727 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
10728 g_localizeStrings.Get(20302),
10729 CURL::GetRedacted(nfoFile));
10730 iFailCount++;
10735 if (!singleFile)
10737 xmlDoc.Clear();
10738 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10739 xmlDoc.InsertEndChild(decl);
10742 if (images && !bSkip)
10744 if (singleFile)
10746 std::string strFileName(movie.m_strTitle);
10747 if (movie.HasYear())
10748 strFileName += StringUtils::Format("_{}", movie.GetYear());
10749 item.SetPath(GetSafeFile(moviesDir, strFileName) + ".avi");
10751 for (const auto &i : artwork)
10753 std::string savedThumb = item.GetLocalArt(i.first, false);
10754 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
10756 if (actorThumbs)
10757 ExportActorThumbs(actorsDir, movie, !singleFile, overwrite);
10759 m_pDS->next();
10760 current++;
10762 m_pDS->close();
10764 if (!singleFile)
10765 movieSetsDir = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
10766 CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER);
10767 if (images && !movieSetsDir.empty())
10769 // find all movie sets
10770 sql = "select idSet, strSet from sets";
10772 m_pDS->query(sql);
10774 total = m_pDS->num_rows();
10775 current = 0;
10777 while (!m_pDS->eof())
10779 std::string title = m_pDS->fv("strSet").get_asString();
10781 if (progress)
10783 progress->SetLine(1, CVariant{title});
10784 progress->SetPercentage(current * 100 / total);
10785 progress->Progress();
10786 if (progress->IsCanceled())
10788 progress->Close();
10789 m_pDS->close();
10790 return;
10794 std::string itemPath = URIUtils::AddFileToFolder(movieSetsDir,
10795 CUtil::MakeLegalFileName(title, LEGAL_WIN32_COMPAT));
10796 if (CDirectory::Exists(itemPath) || CDirectory::Create(itemPath))
10798 std::map<std::string, std::string> artwork;
10799 GetArtForItem(m_pDS->fv("idSet").get_asInt(), MediaTypeVideoCollection, artwork);
10800 for (const auto& art : artwork)
10802 std::string savedThumb = URIUtils::AddFileToFolder(itemPath, art.first);
10803 CServiceBroker::GetTextureCache()->Export(art.second, savedThumb, overwrite);
10806 else
10807 CLog::Log(
10808 LOGDEBUG,
10809 "CVideoDatabase::{} - Not exporting movie set '{}' as could not create folder '{}'",
10810 __FUNCTION__, title, itemPath);
10811 m_pDS->next();
10812 current++;
10814 m_pDS->close();
10817 // find all musicvideos
10818 sql = "select * from musicvideo_view";
10820 m_pDS->query(sql);
10822 total = m_pDS->num_rows();
10823 current = 0;
10825 while (!m_pDS->eof())
10827 CVideoInfoTag movie = GetDetailsForMusicVideo(m_pDS, VideoDbDetailsAll);
10828 std::map<std::string, std::string> artwork;
10829 if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && singleFile)
10831 TiXmlElement additionalNode("art");
10832 for (const auto &i : artwork)
10833 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10834 movie.Save(pMain, "musicvideo", true, &additionalNode);
10836 else
10837 movie.Save(pMain, "musicvideo", singleFile);
10839 // reset old skip state
10840 bool bSkip = false;
10842 if (progress)
10844 progress->SetLine(1, CVariant{movie.m_strTitle});
10845 progress->SetPercentage(current * 100 / total);
10846 progress->Progress();
10847 if (progress->IsCanceled())
10849 progress->Close();
10850 m_pDS->close();
10851 return;
10855 CFileItem item(movie.m_strFileNameAndPath,false);
10856 if (!singleFile && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
10858 if (!item.Exists(false))
10860 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10861 movie.m_strFileNameAndPath);
10862 bSkip = true;
10864 else
10866 std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
10868 if (overwrite || !CFile::Exists(nfoFile, false))
10870 if(!xmlDoc.SaveFile(nfoFile))
10872 CLog::Log(LOGERROR, "{}: Musicvideo nfo export failed! ('{}')", __FUNCTION__,
10873 nfoFile);
10874 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
10875 g_localizeStrings.Get(20302),
10876 CURL::GetRedacted(nfoFile));
10877 iFailCount++;
10882 if (!singleFile)
10884 xmlDoc.Clear();
10885 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10886 xmlDoc.InsertEndChild(decl);
10888 if (images && !bSkip)
10890 if (singleFile)
10892 std::string strFileName(StringUtils::Join(movie.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + "." + movie.m_strTitle);
10893 if (movie.HasYear())
10894 strFileName += StringUtils::Format("_{}", movie.GetYear());
10895 item.SetPath(GetSafeFile(musicvideosDir, strFileName) + ".avi");
10897 for (const auto &i : artwork)
10899 std::string savedThumb = item.GetLocalArt(i.first, false);
10900 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
10903 m_pDS->next();
10904 current++;
10906 m_pDS->close();
10908 // repeat for all tvshows
10909 sql = "SELECT * FROM tvshow_view";
10910 m_pDS->query(sql);
10912 total = m_pDS->num_rows();
10913 current = 0;
10915 while (!m_pDS->eof())
10917 CVideoInfoTag tvshow = GetDetailsForTvShow(m_pDS, VideoDbDetailsAll);
10918 GetTvShowNamedSeasons(tvshow.m_iDbId, tvshow.m_namedSeasons);
10920 std::map<int, std::map<std::string, std::string> > seasonArt;
10921 GetTvShowSeasonArt(tvshow.m_iDbId, seasonArt);
10923 std::map<std::string, std::string> artwork;
10924 if (GetArtForItem(tvshow.m_iDbId, tvshow.m_type, artwork) && singleFile)
10926 TiXmlElement additionalNode("art");
10927 for (const auto &i : artwork)
10928 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10929 for (const auto &i : seasonArt)
10931 TiXmlElement seasonNode("season");
10932 seasonNode.SetAttribute("num", i.first);
10933 for (const auto &j : i.second)
10934 XMLUtils::SetString(&seasonNode, j.first.c_str(), j.second);
10935 additionalNode.InsertEndChild(seasonNode);
10937 tvshow.Save(pMain, "tvshow", true, &additionalNode);
10939 else
10940 tvshow.Save(pMain, "tvshow", singleFile);
10942 // reset old skip state
10943 bool bSkip = false;
10945 if (progress)
10947 progress->SetLine(1, CVariant{tvshow.m_strTitle});
10948 progress->SetPercentage(current * 100 / total);
10949 progress->Progress();
10950 if (progress->IsCanceled())
10952 progress->Close();
10953 m_pDS->close();
10954 return;
10958 CFileItem item(tvshow.m_strPath, true);
10959 if (!singleFile && CUtil::SupportsWriteFileOperations(tvshow.m_strPath))
10961 if (!item.Exists(false))
10963 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
10964 tvshow.m_strPath);
10965 bSkip = true;
10967 else
10969 std::string nfoFile = URIUtils::AddFileToFolder(tvshow.m_strPath, "tvshow.nfo");
10971 if (overwrite || !CFile::Exists(nfoFile, false))
10973 if(!xmlDoc.SaveFile(nfoFile))
10975 CLog::Log(LOGERROR, "{}: TVShow nfo export failed! ('{}')", __FUNCTION__, nfoFile);
10976 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
10977 g_localizeStrings.Get(20302),
10978 CURL::GetRedacted(nfoFile));
10979 iFailCount++;
10984 if (!singleFile)
10986 xmlDoc.Clear();
10987 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10988 xmlDoc.InsertEndChild(decl);
10990 if (images && !bSkip)
10992 if (singleFile)
10993 item.SetPath(GetSafeFile(tvshowsDir, tvshow.m_strTitle));
10995 for (const auto &i : artwork)
10997 std::string savedThumb = item.GetLocalArt(i.first, true);
10998 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
11001 if (actorThumbs)
11002 ExportActorThumbs(actorsDir, tvshow, !singleFile, overwrite);
11004 // export season thumbs
11005 for (const auto &i : seasonArt)
11007 std::string seasonThumb;
11008 if (i.first == -1)
11009 seasonThumb = "season-all";
11010 else if (i.first == 0)
11011 seasonThumb = "season-specials";
11012 else
11013 seasonThumb = StringUtils::Format("season{:02}", i.first);
11014 for (const auto &j : i.second)
11016 std::string savedThumb(item.GetLocalArt(seasonThumb + "-" + j.first, true));
11017 if (!i.second.empty())
11018 CServiceBroker::GetTextureCache()->Export(j.second, savedThumb, overwrite);
11023 // now save the episodes from this show
11024 sql = PrepareSQL("select * from episode_view where idShow=%i order by strFileName, idEpisode",tvshow.m_iDbId);
11025 pDS->query(sql);
11026 std::string showDir(item.GetPath());
11028 while (!pDS->eof())
11030 CVideoInfoTag episode = GetDetailsForEpisode(pDS, VideoDbDetailsAll);
11031 std::map<std::string, std::string> artwork;
11032 if (GetArtForItem(episode.m_iDbId, MediaTypeEpisode, artwork) && singleFile)
11034 TiXmlElement additionalNode("art");
11035 for (const auto &i : artwork)
11036 XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
11037 episode.Save(pMain->LastChild(), "episodedetails", true, &additionalNode);
11039 else if (singleFile)
11040 episode.Save(pMain->LastChild(), "episodedetails", singleFile);
11041 else
11042 episode.Save(pMain, "episodedetails", singleFile);
11043 pDS->next();
11044 // multi-episode files need dumping to the same XML
11045 while (!singleFile && !pDS->eof() &&
11046 episode.m_iFileId == pDS->fv("idFile").get_asInt())
11048 episode = GetDetailsForEpisode(pDS, VideoDbDetailsAll);
11049 episode.Save(pMain, "episodedetails", singleFile);
11050 pDS->next();
11053 // reset old skip state
11054 bool bSkip = false;
11056 CFileItem item(episode.m_strFileNameAndPath, false);
11057 if (!singleFile && CUtil::SupportsWriteFileOperations(episode.m_strFileNameAndPath))
11059 if (!item.Exists(false))
11061 CLog::Log(LOGINFO, "{} - Not exporting item {} as it does not exist", __FUNCTION__,
11062 episode.m_strFileNameAndPath);
11063 bSkip = true;
11065 else
11067 std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
11069 if (overwrite || !CFile::Exists(nfoFile, false))
11071 if(!xmlDoc.SaveFile(nfoFile))
11073 CLog::Log(LOGERROR, "{}: Episode nfo export failed! ('{}')", __FUNCTION__, nfoFile);
11074 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
11075 g_localizeStrings.Get(20302),
11076 CURL::GetRedacted(nfoFile));
11077 iFailCount++;
11082 if (!singleFile)
11084 xmlDoc.Clear();
11085 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
11086 xmlDoc.InsertEndChild(decl);
11089 if (images && !bSkip)
11091 if (singleFile)
11093 std::string epName =
11094 StringUtils::Format("s{:02}e{:02}.avi", episode.m_iSeason, episode.m_iEpisode);
11095 item.SetPath(URIUtils::AddFileToFolder(showDir, epName));
11097 for (const auto &i : artwork)
11099 std::string savedThumb = item.GetLocalArt(i.first, false);
11100 CServiceBroker::GetTextureCache()->Export(i.second, savedThumb, overwrite);
11102 if (actorThumbs)
11103 ExportActorThumbs(actorsDir, episode, !singleFile, overwrite);
11106 pDS->close();
11107 m_pDS->next();
11108 current++;
11110 m_pDS->close();
11112 if (!singleFile && progress)
11114 progress->SetPercentage(100);
11115 progress->Progress();
11118 if (singleFile)
11120 // now dump path info
11121 std::set<std::string> paths;
11122 GetPaths(paths);
11123 TiXmlElement xmlPathElement("paths");
11124 TiXmlNode *pPaths = pMain->InsertEndChild(xmlPathElement);
11125 for (const auto &i : paths)
11127 bool foundDirectly = false;
11128 SScanSettings settings;
11129 ScraperPtr info = GetScraperForPath(i, settings, foundDirectly);
11130 if (info && foundDirectly)
11132 TiXmlElement xmlPathElement2("path");
11133 TiXmlNode *pPath = pPaths->InsertEndChild(xmlPathElement2);
11134 XMLUtils::SetString(pPath,"url", i);
11135 XMLUtils::SetInt(pPath,"scanrecursive", settings.recurse);
11136 XMLUtils::SetBoolean(pPath,"usefoldernames", settings.parent_name);
11137 XMLUtils::SetString(pPath,"content", TranslateContent(info->Content()));
11138 XMLUtils::SetString(pPath,"scraperpath", info->ID());
11141 xmlDoc.SaveFile(xmlFile);
11143 CVariant data;
11144 if (singleFile)
11146 data["root"] = exportRoot;
11147 data["file"] = xmlFile;
11148 if (iFailCount > 0)
11149 data["failcount"] = iFailCount;
11151 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnExport",
11152 data);
11154 catch (...)
11156 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
11157 iFailCount++;
11160 if (progress)
11161 progress->Close();
11163 if (iFailCount > 0)
11164 HELPERS::ShowOKDialogText(
11165 CVariant{647}, CVariant{StringUtils::Format(g_localizeStrings.Get(15011), iFailCount)});
11168 void CVideoDatabase::ExportActorThumbs(const std::string &strDir, const CVideoInfoTag &tag, bool singleFiles, bool overwrite /*=false*/)
11170 std::string strPath(strDir);
11171 if (singleFiles)
11173 strPath = URIUtils::AddFileToFolder(tag.m_strPath, ".actors");
11174 if (!CDirectory::Exists(strPath))
11176 CDirectory::Create(strPath);
11177 CFile::SetHidden(strPath, true);
11181 for (const auto &i : tag.m_cast)
11183 CFileItem item;
11184 item.SetLabel(i.strName);
11185 if (!i.thumb.empty())
11187 std::string thumbFile(GetSafeFile(strPath, i.strName));
11188 CServiceBroker::GetTextureCache()->Export(i.thumb, thumbFile, overwrite);
11193 void CVideoDatabase::ImportFromXML(const std::string &path)
11195 CGUIDialogProgress *progress=NULL;
11198 if (nullptr == m_pDB)
11199 return;
11200 if (nullptr == m_pDS)
11201 return;
11203 CXBMCTinyXML xmlDoc;
11204 if (!xmlDoc.LoadFile(URIUtils::AddFileToFolder(path, "videodb.xml")))
11205 return;
11207 TiXmlElement *root = xmlDoc.RootElement();
11208 if (!root) return;
11210 progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
11211 if (progress)
11213 progress->SetHeading(CVariant{648});
11214 progress->SetLine(0, CVariant{649});
11215 progress->SetLine(1, CVariant{330});
11216 progress->SetLine(2, CVariant{""});
11217 progress->SetPercentage(0);
11218 progress->Open();
11219 progress->ShowProgressBar(true);
11222 int iVersion = 0;
11223 XMLUtils::GetInt(root, "version", iVersion);
11225 CLog::Log(LOGINFO, "{}: Starting import (export version = {})", __FUNCTION__, iVersion);
11227 TiXmlElement *movie = root->FirstChildElement();
11228 int current = 0;
11229 int total = 0;
11230 // first count the number of items...
11231 while (movie)
11233 if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMovie, 5) == 0 ||
11234 StringUtils::CompareNoCase(movie->Value(), MediaTypeTvShow, 6) == 0 ||
11235 StringUtils::CompareNoCase(movie->Value(), MediaTypeMusicVideo, 10) == 0)
11236 total++;
11237 movie = movie->NextSiblingElement();
11240 std::string actorsDir(URIUtils::AddFileToFolder(path, "actors"));
11241 std::string moviesDir(URIUtils::AddFileToFolder(path, "movies"));
11242 std::string movieSetsDir(URIUtils::AddFileToFolder(path, "moviesets"));
11243 std::string musicvideosDir(URIUtils::AddFileToFolder(path, "musicvideos"));
11244 std::string tvshowsDir(URIUtils::AddFileToFolder(path, "tvshows"));
11245 CVideoInfoScanner scanner;
11246 // add paths first (so we have scraper settings available)
11247 TiXmlElement *path = root->FirstChildElement("paths");
11248 path = path->FirstChildElement();
11249 while (path)
11251 std::string strPath;
11252 if (XMLUtils::GetString(path,"url",strPath) && !strPath.empty())
11253 AddPath(strPath);
11255 std::string content;
11256 if (XMLUtils::GetString(path,"content", content) && !content.empty())
11257 { // check the scraper exists, if so store the path
11258 AddonPtr addon;
11259 std::string id;
11260 XMLUtils::GetString(path,"scraperpath",id);
11261 if (CServiceBroker::GetAddonMgr().GetAddon(id, addon, ADDON::OnlyEnabled::CHOICE_YES))
11263 SScanSettings settings;
11264 ScraperPtr scraper = std::dynamic_pointer_cast<CScraper>(addon);
11265 // FIXME: scraper settings are not exported?
11266 scraper->SetPathSettings(TranslateContent(content), "");
11267 XMLUtils::GetInt(path,"scanrecursive",settings.recurse);
11268 XMLUtils::GetBoolean(path,"usefoldernames",settings.parent_name);
11269 SetScraperForPath(strPath,scraper,settings);
11272 path = path->NextSiblingElement();
11274 movie = root->FirstChildElement();
11275 while (movie)
11277 CVideoInfoTag info;
11278 if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMovie, 5) == 0)
11280 info.Load(movie);
11281 CFileItem item(info);
11282 bool useFolders = info.m_basePath.empty() ? LookupByFolders(item.GetPath()) : false;
11283 std::string filename = info.m_strTitle;
11284 if (info.HasYear())
11285 filename += StringUtils::Format("_{}", info.GetYear());
11286 CFileItem artItem(item);
11287 artItem.SetPath(GetSafeFile(moviesDir, filename) + ".avi");
11288 scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
11289 item.SetArt(artItem.GetArt());
11290 if (!item.GetVideoInfoTag()->m_set.title.empty())
11292 std::string setPath = URIUtils::AddFileToFolder(movieSetsDir,
11293 CUtil::MakeLegalFileName(item.GetVideoInfoTag()->m_set.title, LEGAL_WIN32_COMPAT));
11294 if (CDirectory::Exists(setPath))
11296 CGUIListItem::ArtMap setArt;
11297 CFileItem artItem(setPath, true);
11298 for (const auto& artType : CVideoThumbLoader::GetArtTypes(MediaTypeVideoCollection))
11300 std::string artPath = CVideoThumbLoader::GetLocalArt(artItem, artType, true);
11301 if (!artPath.empty())
11303 setArt[artType] = artPath;
11306 item.AppendArt(setArt, "set");
11309 scanner.AddVideo(&item, CONTENT_MOVIES, useFolders, true, NULL, true);
11310 current++;
11312 else if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMusicVideo, 10) == 0)
11314 info.Load(movie);
11315 CFileItem item(info);
11316 bool useFolders = info.m_basePath.empty() ? LookupByFolders(item.GetPath()) : false;
11317 std::string filename = StringUtils::Join(info.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + "." + info.m_strTitle;
11318 if (info.HasYear())
11319 filename += StringUtils::Format("_{}", info.GetYear());
11320 CFileItem artItem(item);
11321 artItem.SetPath(GetSafeFile(musicvideosDir, filename) + ".avi");
11322 scanner.GetArtwork(&artItem, CONTENT_MUSICVIDEOS, useFolders, true, actorsDir);
11323 item.SetArt(artItem.GetArt());
11324 scanner.AddVideo(&item, CONTENT_MUSICVIDEOS, useFolders, true, NULL, true);
11325 current++;
11327 else if (StringUtils::CompareNoCase(movie->Value(), MediaTypeTvShow, 6) == 0)
11329 // load the TV show in. NOTE: This deletes all episodes under the TV Show, which may not be
11330 // what we desire. It may make better sense to only delete (or even better, update) the show information
11331 info.Load(movie);
11332 URIUtils::AddSlashAtEnd(info.m_strPath);
11333 DeleteTvShow(info.m_strPath);
11334 CFileItem showItem(info);
11335 bool useFolders = info.m_basePath.empty() ? LookupByFolders(showItem.GetPath(), true) : false;
11336 CFileItem artItem(showItem);
11337 std::string artPath(GetSafeFile(tvshowsDir, info.m_strTitle));
11338 artItem.SetPath(artPath);
11339 scanner.GetArtwork(&artItem, CONTENT_TVSHOWS, useFolders, true, actorsDir);
11340 showItem.SetArt(artItem.GetArt());
11341 int showID = scanner.AddVideo(&showItem, CONTENT_TVSHOWS, useFolders, true, NULL, true);
11342 // season artwork
11343 std::map<int, std::map<std::string, std::string> > seasonArt;
11344 artItem.GetVideoInfoTag()->m_strPath = artPath;
11345 scanner.GetSeasonThumbs(*artItem.GetVideoInfoTag(), seasonArt, CVideoThumbLoader::GetArtTypes(MediaTypeSeason), true);
11346 for (const auto &i : seasonArt)
11348 int seasonID = AddSeason(showID, i.first);
11349 SetArtForItem(seasonID, MediaTypeSeason, i.second);
11351 current++;
11352 // now load the episodes
11353 TiXmlElement *episode = movie->FirstChildElement("episodedetails");
11354 while (episode)
11356 // no need to delete the episode info, due to the above deletion
11357 CVideoInfoTag info;
11358 info.Load(episode);
11359 CFileItem item(info);
11360 std::string filename =
11361 StringUtils::Format("s{:02}e{:02}.avi", info.m_iSeason, info.m_iEpisode);
11362 CFileItem artItem(item);
11363 artItem.SetPath(GetSafeFile(artPath, filename));
11364 scanner.GetArtwork(&artItem, CONTENT_TVSHOWS, useFolders, true, actorsDir);
11365 item.SetArt(artItem.GetArt());
11366 scanner.AddVideo(&item,CONTENT_TVSHOWS, false, false, showItem.GetVideoInfoTag(), true);
11367 episode = episode->NextSiblingElement("episodedetails");
11370 movie = movie->NextSiblingElement();
11371 if (progress && total)
11373 progress->SetPercentage(current * 100 / total);
11374 progress->SetLine(2, CVariant{info.m_strTitle});
11375 progress->Progress();
11376 if (progress->IsCanceled())
11378 progress->Close();
11379 return;
11384 catch (...)
11386 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
11388 if (progress)
11389 progress->Close();
11392 bool CVideoDatabase::ImportArtFromXML(const TiXmlNode *node, std::map<std::string, std::string> &artwork)
11394 if (!node) return false;
11395 const TiXmlNode *art = node->FirstChild();
11396 while (art && art->FirstChild())
11398 artwork.insert(make_pair(art->ValueStr(), art->FirstChild()->ValueStr()));
11399 art = art->NextSibling();
11401 return !artwork.empty();
11404 void CVideoDatabase::ConstructPath(std::string& strDest, const std::string& strPath, const std::string& strFileName)
11406 if (URIUtils::IsStack(strFileName) ||
11407 URIUtils::IsInArchive(strFileName) || URIUtils::IsPlugin(strPath))
11408 strDest = strFileName;
11409 else
11410 strDest = URIUtils::AddFileToFolder(strPath, strFileName);
11413 void CVideoDatabase::SplitPath(const std::string& strFileNameAndPath, std::string& strPath, std::string& strFileName)
11415 if (URIUtils::IsStack(strFileNameAndPath) || StringUtils::StartsWithNoCase(strFileNameAndPath, "rar://") || StringUtils::StartsWithNoCase(strFileNameAndPath, "zip://"))
11417 URIUtils::GetParentPath(strFileNameAndPath,strPath);
11418 strFileName = strFileNameAndPath;
11420 else if (URIUtils::IsPlugin(strFileNameAndPath))
11422 CURL url(strFileNameAndPath);
11423 strPath = url.GetOptions().empty() ? url.GetWithoutFilename() : url.GetWithoutOptions();
11424 strFileName = strFileNameAndPath;
11426 else
11428 URIUtils::Split(strFileNameAndPath, strPath, strFileName);
11429 // Keep protocol options as part of the path
11430 if (URIUtils::IsURL(strFileNameAndPath))
11432 CURL url(strFileNameAndPath);
11433 if (!url.GetProtocolOptions().empty())
11434 strPath += "|" + url.GetProtocolOptions();
11439 void CVideoDatabase::InvalidatePathHash(const std::string& strPath)
11441 SScanSettings settings;
11442 bool foundDirectly;
11443 ScraperPtr info = GetScraperForPath(strPath,settings,foundDirectly);
11444 SetPathHash(strPath,"");
11445 if (!info)
11446 return;
11447 if (info->Content() == CONTENT_TVSHOWS || (info->Content() == CONTENT_MOVIES && !foundDirectly)) // if we scan by folder name we need to invalidate parent as well
11449 if (info->Content() == CONTENT_TVSHOWS || settings.parent_name_root)
11451 std::string strParent;
11452 if (URIUtils::GetParentPath(strPath, strParent) && (!URIUtils::IsPlugin(strPath) || !CURL(strParent).GetHostName().empty()))
11453 SetPathHash(strParent, "");
11458 bool CVideoDatabase::CommitTransaction()
11460 if (CDatabase::CommitTransaction())
11461 { // number of items in the db has likely changed, so recalculate
11462 GUIINFO::CLibraryGUIInfo& guiInfo = CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider();
11463 guiInfo.SetLibraryBool(LIBRARY_HAS_MOVIES, HasContent(VideoDbContentType::MOVIES));
11464 guiInfo.SetLibraryBool(LIBRARY_HAS_TVSHOWS, HasContent(VideoDbContentType::TVSHOWS));
11465 guiInfo.SetLibraryBool(LIBRARY_HAS_MUSICVIDEOS, HasContent(VideoDbContentType::MUSICVIDEOS));
11466 return true;
11468 return false;
11471 bool CVideoDatabase::SetSingleValue(VideoDbContentType type,
11472 int dbId,
11473 int dbField,
11474 const std::string& strValue)
11476 std::string strSQL;
11479 if (nullptr == m_pDB || nullptr == m_pDS)
11480 return false;
11482 std::string strTable, strField;
11483 if (type == VideoDbContentType::MOVIES)
11485 strTable = "movie";
11486 strField = "idMovie";
11488 else if (type == VideoDbContentType::TVSHOWS)
11490 strTable = "tvshow";
11491 strField = "idShow";
11493 else if (type == VideoDbContentType::EPISODES)
11495 strTable = "episode";
11496 strField = "idEpisode";
11498 else if (type == VideoDbContentType::MUSICVIDEOS)
11500 strTable = "musicvideo";
11501 strField = "idMVideo";
11504 if (strTable.empty())
11505 return false;
11507 return SetSingleValue(strTable, StringUtils::Format("c{:02}", dbField), strValue, strField,
11508 dbId);
11510 catch (...)
11512 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, strSQL);
11514 return false;
11517 bool CVideoDatabase::SetSingleValue(VideoDbContentType type,
11518 int dbId,
11519 Field dbField,
11520 const std::string& strValue)
11522 MediaType mediaType = DatabaseUtils::MediaTypeFromVideoContentType(type);
11523 if (mediaType == MediaTypeNone)
11524 return false;
11526 int dbFieldIndex = DatabaseUtils::GetField(dbField, mediaType);
11527 if (dbFieldIndex < 0)
11528 return false;
11530 return SetSingleValue(type, dbId, dbFieldIndex, strValue);
11533 bool CVideoDatabase::SetSingleValue(const std::string &table, const std::string &fieldName, const std::string &strValue,
11534 const std::string &conditionName /* = "" */, int conditionValue /* = -1 */)
11536 if (table.empty() || fieldName.empty())
11537 return false;
11539 std::string sql;
11542 if (nullptr == m_pDB || nullptr == m_pDS)
11543 return false;
11545 sql = PrepareSQL("UPDATE %s SET %s='%s'", table.c_str(), fieldName.c_str(), strValue.c_str());
11546 if (!conditionName.empty())
11547 sql += PrepareSQL(" WHERE %s=%u", conditionName.c_str(), conditionValue);
11548 if (m_pDS->exec(sql) == 0)
11549 return true;
11551 catch (...)
11553 CLog::Log(LOGERROR, "{} ({}) failed", __FUNCTION__, sql);
11555 return false;
11558 std::string CVideoDatabase::GetSafeFile(const std::string &dir, const std::string &name) const
11560 std::string safeThumb(name);
11561 StringUtils::Replace(safeThumb, ' ', '_');
11562 return URIUtils::AddFileToFolder(dir, CUtil::MakeLegalFileName(std::move(safeThumb)));
11565 void CVideoDatabase::AnnounceRemove(const std::string& content, int id, bool scanning /* = false */)
11567 CVariant data;
11568 data["type"] = content;
11569 data["id"] = id;
11570 if (scanning)
11571 data["transaction"] = true;
11572 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnRemove", data);
11575 void CVideoDatabase::AnnounceUpdate(const std::string& content, int id)
11577 CVariant data;
11578 data["type"] = content;
11579 data["id"] = id;
11580 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnUpdate", data);
11583 bool CVideoDatabase::GetItemsForPath(const std::string &content, const std::string &strPath, CFileItemList &items)
11585 const std::string& path(strPath);
11587 if(URIUtils::IsMultiPath(path))
11589 std::vector<std::string> paths;
11590 CMultiPathDirectory::GetPaths(path, paths);
11592 for(unsigned i=0;i<paths.size();i++)
11593 GetItemsForPath(content, paths[i], items);
11595 return items.Size() > 0;
11598 int pathID = GetPathId(path);
11599 if (pathID < 0)
11600 return false;
11602 if (content == "movies")
11604 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_PARENTPATHID, pathID));
11605 GetMoviesByWhere("videodb://movies/titles/", filter, items);
11607 else if (content == "episodes")
11609 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_EPISODE_PARENTPATHID, pathID));
11610 GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
11612 else if (content == "tvshows")
11614 Filter filter(PrepareSQL("idParentPath=%d", pathID));
11615 GetTvShowsByWhere("videodb://tvshows/titles/", filter, items);
11617 else if (content == "musicvideos")
11619 Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_MUSICVIDEO_PARENTPATHID, pathID));
11620 GetMusicVideosByWhere("videodb://musicvideos/titles/", filter, items);
11622 for (int i = 0; i < items.Size(); i++)
11623 items[i]->SetPath(items[i]->GetVideoInfoTag()->m_basePath);
11624 return items.Size() > 0;
11627 void CVideoDatabase::AppendIdLinkFilter(const char* field, const char *table, const MediaType& mediaType, const char *view, const char *viewKey, const CUrlOptions::UrlOptions& options, Filter &filter)
11629 auto option = options.find((std::string)field + "id");
11630 if (option == options.end())
11631 return;
11633 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()));
11634 filter.AppendWhere(PrepareSQL("%s_link.%s_id = %i", field, table, (int)option->second.asInteger()));
11637 void CVideoDatabase::AppendLinkFilter(const char* field, const char *table, const MediaType& mediaType, const char *view, const char *viewKey, const CUrlOptions::UrlOptions& options, Filter &filter)
11639 auto option = options.find(field);
11640 if (option == options.end())
11641 return;
11643 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()));
11644 filter.AppendJoin(PrepareSQL("JOIN %s ON %s.%s_id=%s_link.%s_id", table, table, field, table, field));
11645 filter.AppendWhere(PrepareSQL("%s.name like '%s'", table, option->second.asString().c_str()));
11648 bool CVideoDatabase::GetFilter(CDbUrl &videoUrl, Filter &filter, SortDescription &sorting)
11650 if (!videoUrl.IsValid())
11651 return false;
11653 std::string type = videoUrl.GetType();
11654 std::string itemType = ((const CVideoDbUrl &)videoUrl).GetItemType();
11655 const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
11657 if (type == "movies")
11659 AppendIdLinkFilter("genre", "genre", "movie", "movie", "idMovie", options, filter);
11660 AppendLinkFilter("genre", "genre", "movie", "movie", "idMovie", options, filter);
11662 AppendIdLinkFilter("country", "country", "movie", "movie", "idMovie", options, filter);
11663 AppendLinkFilter("country", "country", "movie", "movie", "idMovie", options, filter);
11665 AppendIdLinkFilter("studio", "studio", "movie", "movie", "idMovie", options, filter);
11666 AppendLinkFilter("studio", "studio", "movie", "movie", "idMovie", options, filter);
11668 AppendIdLinkFilter("director", "actor", "movie", "movie", "idMovie", options, filter);
11669 AppendLinkFilter("director", "actor", "movie", "movie", "idMovie", options, filter);
11671 auto option = options.find("year");
11672 if (option != options.end())
11673 filter.AppendWhere(PrepareSQL("movie_view.premiered like '%i%%'", (int)option->second.asInteger()));
11675 AppendIdLinkFilter("actor", "actor", "movie", "movie", "idMovie", options, filter);
11676 AppendLinkFilter("actor", "actor", "movie", "movie", "idMovie", options, filter);
11678 option = options.find("setid");
11679 if (option != options.end())
11680 filter.AppendWhere(PrepareSQL("movie_view.idSet = %i", (int)option->second.asInteger()));
11682 option = options.find("set");
11683 if (option != options.end())
11684 filter.AppendWhere(PrepareSQL("movie_view.strSet LIKE '%s'", option->second.asString().c_str()));
11686 option = options.find("videoversionid");
11687 if (option != options.end())
11689 const int idVideoVersion{static_cast<int>(option->second.asInteger())};
11690 if (idVideoVersion > 0)
11691 filter.AppendWhere(PrepareSQL("videoVersionTypeId = %i", idVideoVersion));
11692 else
11694 option = options.find("mediaid");
11695 if (option != options.end())
11697 const int mediaId{static_cast<int>(option->second.asInteger())};
11698 if (mediaId > 0)
11699 filter.AppendWhere(PrepareSQL("idMovie = %i", mediaId));
11703 else
11705 filter.AppendWhere("isDefaultVersion = 1");
11708 AppendIdLinkFilter("tag", "tag", "movie", "movie", "idMovie", options, filter);
11709 AppendLinkFilter("tag", "tag", "movie", "movie", "idMovie", options, filter);
11711 else if (type == "tvshows")
11713 if (itemType == "tvshows")
11715 AppendIdLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options, filter);
11716 AppendLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options, filter);
11718 AppendIdLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options, filter);
11719 AppendLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options, filter);
11721 AppendIdLinkFilter("director", "actor", "tvshow", "tvshow", "idShow", options, filter);
11723 auto option = options.find("year");
11724 if (option != options.end())
11725 filter.AppendWhere(PrepareSQL("tvshow_view.c%02d like '%%%i%%'", VIDEODB_ID_TV_PREMIERED, (int)option->second.asInteger()));
11727 AppendIdLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options, filter);
11728 AppendLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options, filter);
11730 AppendIdLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options, filter);
11731 AppendLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options, filter);
11733 else if (itemType == "seasons")
11735 auto option = options.find("tvshowid");
11736 if (option != options.end())
11737 filter.AppendWhere(PrepareSQL("season_view.idShow = %i", (int)option->second.asInteger()));
11739 AppendIdLinkFilter("genre", "genre", "tvshow", "season", "idShow", options, filter);
11741 AppendIdLinkFilter("director", "actor", "tvshow", "season", "idShow", options, filter);
11743 option = options.find("year");
11744 if (option != options.end())
11745 filter.AppendWhere(PrepareSQL("season_view.premiered like '%%%i%%'", (int)option->second.asInteger()));
11747 AppendIdLinkFilter("actor", "actor", "tvshow", "season", "idShow", options, filter);
11749 else if (itemType == "episodes")
11751 int idShow = -1;
11752 auto option = options.find("tvshowid");
11753 if (option != options.end())
11754 idShow = (int)option->second.asInteger();
11756 int season = -1;
11757 option = options.find("season");
11758 if (option != options.end())
11759 season = (int)option->second.asInteger();
11761 if (idShow > -1)
11763 bool condition = false;
11765 AppendIdLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options, filter);
11766 AppendLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options, filter);
11768 AppendIdLinkFilter("director", "actor", "tvshow", "episode", "idShow", options, filter);
11769 AppendLinkFilter("director", "actor", "tvshow", "episode", "idShow", options, filter);
11771 option = options.find("year");
11772 if (option != options.end())
11774 condition = true;
11775 filter.AppendWhere(PrepareSQL("episode_view.idShow = %i and episode_view.premiered like '%%%i%%'", idShow, (int)option->second.asInteger()));
11778 AppendIdLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options, filter);
11779 AppendLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options, filter);
11781 if (!condition)
11782 filter.AppendWhere(PrepareSQL("episode_view.idShow = %i", idShow));
11784 if (season > -1)
11786 if (season == 0) // season = 0 indicates a special - we grab all specials here (see below)
11787 filter.AppendWhere(PrepareSQL("episode_view.c%02d = %i", VIDEODB_ID_EPISODE_SEASON, season));
11788 else
11789 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)))",
11790 VIDEODB_ID_EPISODE_SEASON, season, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTSEASON, season));
11793 else
11795 option = options.find("year");
11796 if (option != options.end())
11797 filter.AppendWhere(PrepareSQL("episode_view.premiered like '%%%i%%'", (int)option->second.asInteger()));
11799 AppendIdLinkFilter("director", "actor", "episode", "episode", "idEpisode", options, filter);
11800 AppendLinkFilter("director", "actor", "episode", "episode", "idEpisode", options, filter);
11804 else if (type == "musicvideos")
11806 AppendIdLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options, filter);
11807 AppendLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options, filter);
11809 AppendIdLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options, filter);
11810 AppendLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options, filter);
11812 AppendIdLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options, filter);
11813 AppendLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options, filter);
11815 auto option = options.find("year");
11816 if (option != options.end())
11817 filter.AppendWhere(PrepareSQL("musicvideo_view.premiered like '%i%%'", (int)option->second.asInteger()));
11819 option = options.find("artistid");
11820 if (option != options.end())
11822 if (itemType != "albums")
11823 filter.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
11824 filter.AppendWhere(PrepareSQL("actor_link.actor_id = %i", (int)option->second.asInteger()));
11827 option = options.find("artist");
11828 if (option != options.end())
11830 if (itemType != "albums")
11832 filter.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
11833 filter.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id=actor_link.actor_id"));
11835 filter.AppendWhere(PrepareSQL("actor.name LIKE '%s'", option->second.asString().c_str()));
11838 option = options.find("albumid");
11839 if (option != options.end())
11840 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()));
11842 AppendIdLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options, filter);
11843 AppendLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options, filter);
11845 else
11846 return false;
11848 auto option = options.find("xsp");
11849 if (option != options.end())
11851 CSmartPlaylist xsp;
11852 if (!xsp.LoadFromJson(option->second.asString()))
11853 return false;
11855 // check if the filter playlist matches the item type
11856 if (xsp.GetType() == itemType ||
11857 (xsp.GetGroup() == itemType && !xsp.IsGroupMixed()) ||
11858 // handle episode listings with videodb://tvshows/titles/ which get the rest
11859 // of the path (season and episodeid) appended later
11860 (xsp.GetType() == "episodes" && itemType == "tvshows"))
11862 std::set<std::string> playlists;
11863 filter.AppendWhere(xsp.GetWhereClause(*this, playlists));
11865 if (xsp.GetLimit() > 0)
11866 sorting.limitEnd = xsp.GetLimit();
11867 if (xsp.GetOrder() != SortByNone)
11868 sorting.sortBy = xsp.GetOrder();
11869 if (xsp.GetOrderDirection() != SortOrderNone)
11870 sorting.sortOrder = xsp.GetOrderDirection();
11871 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING))
11872 sorting.sortAttributes = SortAttributeIgnoreArticle;
11876 option = options.find("filter");
11877 if (option != options.end())
11879 CSmartPlaylist xspFilter;
11880 if (!xspFilter.LoadFromJson(option->second.asString()))
11881 return false;
11883 // check if the filter playlist matches the item type
11884 if (xspFilter.GetType() == itemType)
11886 std::set<std::string> playlists;
11887 filter.AppendWhere(xspFilter.GetWhereClause(*this, playlists));
11889 // remove the filter if it doesn't match the item type
11890 else
11891 videoUrl.RemoveOption("filter");
11894 return true;
11897 bool CVideoDatabase::SetVideoUserRating(int dbId, int rating, const MediaType& mediaType)
11901 if (nullptr == m_pDB)
11902 return false;
11903 if (nullptr == m_pDS)
11904 return false;
11906 if (mediaType == MediaTypeNone)
11907 return false;
11909 std::string sql;
11910 if (mediaType == MediaTypeMovie)
11911 sql = PrepareSQL("UPDATE movie SET userrating=%i WHERE idMovie = %i", rating, dbId);
11912 else if (mediaType == MediaTypeEpisode)
11913 sql = PrepareSQL("UPDATE episode SET userrating=%i WHERE idEpisode = %i", rating, dbId);
11914 else if (mediaType == MediaTypeMusicVideo)
11915 sql = PrepareSQL("UPDATE musicvideo SET userrating=%i WHERE idMVideo = %i", rating, dbId);
11916 else if (mediaType == MediaTypeTvShow)
11917 sql = PrepareSQL("UPDATE tvshow SET userrating=%i WHERE idShow = %i", rating, dbId);
11918 else if (mediaType == MediaTypeSeason)
11919 sql = PrepareSQL("UPDATE seasons SET userrating=%i WHERE idSeason = %i", rating, dbId);
11921 m_pDS->exec(sql);
11922 return true;
11924 catch (...)
11926 CLog::Log(LOGERROR, "{} ({}, {}, {}) failed", __FUNCTION__, dbId, mediaType, rating);
11928 return false;
11931 CDateTime CVideoDatabase::GetDateAdded(const std::string& filename,
11932 CDateTime dateAdded /* = CDateTime() */)
11934 if (!dateAdded.IsValid())
11936 // suppress warnings if we have plugin source
11937 if (!URIUtils::IsPlugin(filename))
11939 const auto dateAddedSetting =
11940 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryDateAdded;
11942 // 1 prefer using the files mtime (if it's valid) and
11943 // only use the file's ctime if mtime isn't valid
11944 if (dateAddedSetting == 1)
11945 dateAdded = CFileUtils::GetModificationDate(filename, false);
11946 // 2 use the newer datetime of the file's mtime and ctime
11947 else if (dateAddedSetting == 2)
11948 dateAdded = CFileUtils::GetModificationDate(filename, true);
11951 // 0 use the current datetime if non of the above match or one returns an invalid datetime
11952 if (!dateAdded.IsValid())
11953 dateAdded = CDateTime::GetCurrentDateTime();
11956 return dateAdded;
11959 void CVideoDatabase::EraseAllForPath(const std::string& path)
11963 std::string itemsToDelete;
11964 std::string sql =
11965 PrepareSQL("SELECT files.idFile FROM files WHERE idFile IN (SELECT idFile FROM files INNER "
11966 "JOIN path ON path.idPath = files.idPath AND path.strPath LIKE \"%s%%\")",
11967 path.c_str());
11969 m_pDS->query(sql);
11970 while (!m_pDS->eof())
11972 std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
11973 itemsToDelete += file;
11974 m_pDS->next();
11976 m_pDS->close();
11978 sql = PrepareSQL("DELETE FROM path WHERE strPath LIKE \"%s%%\"", path.c_str());
11979 m_pDS->exec(sql);
11981 if (!itemsToDelete.empty())
11983 itemsToDelete = "(" + StringUtils::TrimRight(itemsToDelete, ",") + ")";
11985 sql = "DELETE FROM files WHERE idFile IN " + itemsToDelete;
11986 m_pDS->exec(sql);
11988 sql = "DELETE FROM settings WHERE idFile IN " + itemsToDelete;
11989 m_pDS->exec(sql);
11991 sql = "DELETE FROM bookmark WHERE idFile IN " + itemsToDelete;
11992 m_pDS->exec(sql);
11994 sql = "DELETE FROM streamdetails WHERE idFile IN " + itemsToDelete;
11995 m_pDS->exec(sql);
11998 catch (...)
12000 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12004 std::string CVideoDatabase::GetVideoItemTitle(VideoDbContentType itemType, int dbId)
12006 switch (itemType)
12008 case VideoDbContentType::MOVIES:
12009 return GetMovieTitle(dbId);
12010 default:
12011 return "";
12015 void CVideoDatabase::InitializeVideoVersionTypeTable(int schemaVersion)
12019 BeginTransaction();
12021 for (int id = VIDEO_VERSION_ID_BEGIN; id <= VIDEO_VERSION_ID_END; ++id)
12023 // Exclude removed pre-populated "quality" values
12024 if (id == 40405 || (id >= 40418 && id <= 40430))
12025 continue;
12027 const std::string& type{g_localizeStrings.Get(id)};
12028 if (schemaVersion < 127)
12030 m_pDS->exec(
12031 PrepareSQL("INSERT INTO videoversiontype (id, name, owner) VALUES(%i, '%s', %i)", id,
12032 type.c_str(), VideoAssetTypeOwner::SYSTEM));
12034 else
12036 m_pDS->exec(PrepareSQL(
12037 "INSERT INTO videoversiontype (id, name, owner, itemType) VALUES(%i, '%s', %i, %i)", id,
12038 type.c_str(), VideoAssetTypeOwner::SYSTEM, VideoAssetType::VERSION));
12042 CommitTransaction();
12044 catch (...)
12046 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12047 RollbackTransaction();
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);
12424 // Rename the default version
12425 ExecuteQuery(PrepareSQL("UPDATE videoversion SET idType = %i, itemType = %i WHERE idFile = %i",
12426 idVideoVersion, assetType, idFile));
12428 CommitTransaction();
12430 return true;
12433 void CVideoDatabase::SetDefaultVideoVersion(VideoDbContentType itemType, int dbId, int idFile)
12435 if (!m_pDB || !m_pDS)
12436 return;
12438 std::string path = GetFileBasePathById(idFile);
12439 if (path.empty())
12440 return;
12444 if (itemType == VideoDbContentType::MOVIES)
12445 m_pDS->exec(PrepareSQL("UPDATE movie SET idFile = %i, c%02d = '%s' WHERE idMovie = %i",
12446 idFile, VIDEODB_ID_BASEPATH, path.c_str(), dbId));
12448 catch (...)
12450 CLog::Log(LOGERROR, "{} failed for video {}", __FUNCTION__, dbId);
12454 bool CVideoDatabase::IsDefaultVideoVersion(int idFile)
12456 if (!m_pDB || !m_pDS)
12457 return false;
12461 m_pDS->query(
12462 PrepareSQL("SELECT idMedia, media_type FROM videoversion WHERE idFile = %i", idFile));
12463 if (m_pDS->num_rows() > 0)
12465 int idMedia = m_pDS->fv("idMedia").get_asInt();
12466 std::string mediaType = m_pDS->fv("media_type").get_asString();
12468 if (mediaType == MediaTypeMovie)
12470 m_pDS->query(PrepareSQL("SELECT idFile FROM movie WHERE idMovie = %i", idMedia));
12471 if (m_pDS->num_rows() > 0)
12473 if (m_pDS->fv("idFile").get_asInt() == idFile)
12474 return true;
12479 catch (...)
12481 CLog::Log(LOGERROR, "{} failed for {}", __FUNCTION__, idFile);
12484 return false;
12487 bool CVideoDatabase::DeleteVideoAsset(int idFile)
12489 if (!m_pDB || !m_pDS)
12490 return false;
12492 if (IsDefaultVideoVersion(idFile))
12493 return false;
12495 const bool inTransaction{m_pDB->in_transaction()};
12499 if (!inTransaction)
12500 BeginTransaction();
12502 const std::string path = GetSingleValue(PrepareSQL(
12503 "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i",
12504 idFile));
12505 if (!path.empty())
12506 InvalidatePathHash(path);
12508 m_pDS->exec(PrepareSQL("DELETE FROM videoversion WHERE idFile=%i", idFile));
12510 if (!inTransaction)
12511 CommitTransaction();
12513 return true;
12515 catch (...)
12517 CLog::LogF(LOGERROR, "failed for {}", idFile);
12518 if (!inTransaction)
12519 RollbackTransaction();
12520 return false;
12524 void CVideoDatabase::SetVideoVersion(int idFile, int idVideoVersion)
12526 if (!m_pDB || !m_pDS)
12527 return;
12531 m_pDS->exec(PrepareSQL("UPDATE videoversion SET idType = %i WHERE idFile = %i", idVideoVersion,
12532 idFile));
12534 catch (...)
12536 CLog::Log(LOGERROR, "{} failed for video {}", __FUNCTION__, idFile);
12540 void CVideoDatabase::AddVideoAsset(VideoDbContentType itemType,
12541 int dbId,
12542 int idVideoVersion,
12543 VideoAssetType videoAssetType,
12544 CFileItem& item)
12546 if (!m_pDB || !m_pDS)
12547 return;
12549 assert(m_pDB->in_transaction() == false);
12551 MediaType mediaType;
12552 if (itemType == VideoDbContentType::MOVIES)
12554 mediaType = MediaTypeMovie;
12556 else
12557 return;
12559 int idFile = AddFile(item.GetPath());
12560 if (idFile < 0)
12561 return;
12565 BeginTransaction();
12567 m_pDS->query(PrepareSQL("SELECT idFile FROM videoversion WHERE idFile = %i", idFile));
12569 if (m_pDS->num_rows() == 0)
12570 m_pDS->exec(PrepareSQL("INSERT INTO videoversion VALUES(%i, %i, '%s', %i, %i)", idFile, dbId,
12571 mediaType.c_str(), videoAssetType, idVideoVersion));
12572 else
12573 m_pDS->exec(PrepareSQL("UPDATE videoversion SET idMedia = %i, media_type = '%s', itemType = "
12574 "%i, idType = %i WHERE idFile = %i",
12575 dbId, mediaType.c_str(), videoAssetType, idVideoVersion, idFile));
12577 if (item.GetVideoInfoTag()->HasStreamDetails())
12578 SetStreamDetailsForFileId(item.GetVideoInfoTag()->m_streamDetails, idFile);
12580 if (videoAssetType == VideoAssetType::VERSION)
12581 SetVideoVersionDefaultArt(idFile, item.GetVideoInfoTag()->m_iDbId, itemType);
12583 CommitTransaction();
12585 catch (...)
12587 CLog::LogF(LOGERROR, "failed for video {}", dbId);
12588 RollbackTransaction();
12592 VideoAssetInfo CVideoDatabase::GetVideoVersionInfo(const std::string& filenameAndPath)
12594 VideoAssetInfo info;
12596 info.m_idFile = GetFileId(filenameAndPath);
12597 if (info.m_idFile < 0)
12598 return info;
12600 if (!m_pDB || !m_pDS)
12601 return info;
12605 m_pDS->query(PrepareSQL("SELECT videoversiontype.name,"
12606 " videoversiontype.id,"
12607 " videoversion.idMedia,"
12608 " videoversion.media_type,"
12609 " videoversion.itemType "
12610 "FROM videoversion"
12611 " JOIN videoversiontype ON "
12612 " videoversiontype.id = videoversion.idType "
12613 "WHERE videoversion.idFile = %i",
12614 info.m_idFile));
12616 if (m_pDS->num_rows() > 0)
12618 info.m_assetTypeId = m_pDS->fv("id").get_asInt();
12619 info.m_assetTypeName = m_pDS->fv("name").get_asString();
12620 info.m_idMedia = m_pDS->fv("idMedia").get_asInt();
12621 info.m_mediaType = m_pDS->fv("media_type").get_asString();
12622 info.m_assetType = static_cast<VideoAssetType>(m_pDS->fv("itemType").get_asInt());
12625 m_pDS->close();
12627 catch (...)
12629 CLog::LogF(LOGERROR, "failed for {}", filenameAndPath);
12632 return info;
12635 bool CVideoDatabase::GetVideoVersionsNav(const std::string& strBaseDir,
12636 CFileItemList& items,
12637 VideoDbContentType idContent /* = UNKNOWN */,
12638 const Filter& filter /* = Filter() */)
12640 if (!m_pDB || !m_pDS)
12641 return false;
12643 MediaType mediaType;
12645 if (idContent == VideoDbContentType::MOVIES)
12647 mediaType = MediaTypeMovie;
12649 else
12650 return false;
12652 CVideoDbUrl videoUrl;
12653 if (!videoUrl.FromString(strBaseDir))
12654 return false;
12658 m_pDS->query(PrepareSQL(
12659 "SELECT DISTINCT videoversiontype.name AS name,"
12660 " videoversiontype.id AS id "
12661 "FROM videoversiontype"
12662 " JOIN videoversion ON"
12663 " videoversion.idType = videoversiontype.id "
12664 "WHERE name != '' AND owner IN (%i, %i) AND videoversiontype.itemType = %i",
12665 VideoAssetTypeOwner::SYSTEM, VideoAssetTypeOwner::USER, VideoAssetType::VERSION));
12667 while (!m_pDS->eof())
12669 const int id{m_pDS->fv("id").get_asInt()};
12671 CVideoDbUrl itemUrl{videoUrl};
12672 itemUrl.AppendPath(StringUtils::Format("{}/", id));
12674 const auto item{std::make_shared<CFileItem>(itemUrl.ToString(), true)};
12675 item->SetLabel(m_pDS->fv("name").get_asString());
12676 auto tag{item->GetVideoInfoTag()};
12677 tag->m_type = MediaTypeVideoVersion;
12678 tag->m_iDbId = id;
12680 items.Add(item);
12681 m_pDS->next();
12683 m_pDS->close();
12684 return true;
12686 catch (...)
12688 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12690 return false;
12693 bool CVideoDatabase::GetVideoVersionTypes(VideoDbContentType idContent,
12694 VideoAssetType assetType,
12695 CFileItemList& items)
12697 if (!m_pDB || !m_pDS)
12698 return false;
12700 MediaType mediaType;
12702 if (idContent == VideoDbContentType::MOVIES)
12704 mediaType = MediaTypeMovie;
12706 else
12707 return false;
12711 m_pDS->query(
12712 PrepareSQL("SELECT name, id FROM videoversiontype WHERE name != '' AND itemType = %i "
12713 "AND owner IN (%i, %i)",
12714 assetType, VideoAssetTypeOwner::SYSTEM, VideoAssetTypeOwner::USER));
12716 while (!m_pDS->eof())
12718 std::string name = m_pDS->fv("name").get_asString();
12719 int id = m_pDS->fv("id").get_asInt();
12721 const auto item{std::make_shared<CFileItem>(name)};
12722 item->GetVideoInfoTag()->m_type = MediaTypeVideoVersion;
12723 item->GetVideoInfoTag()->m_iDbId = id;
12724 item->GetVideoInfoTag()->GetAssetInfo().SetId(id);
12725 item->GetVideoInfoTag()->GetAssetInfo().SetTitle(name);
12726 item->GetVideoInfoTag()->m_strTitle = name;
12728 item->m_strTitle = name;
12729 item->SetLabel(name);
12731 items.Add(item);
12732 m_pDS->next();
12734 m_pDS->close();
12735 return true;
12737 catch (...)
12739 CLog::Log(LOGERROR, "{} failed", __FUNCTION__);
12741 return false;
12744 std::string CVideoDatabase::GetVideoVersionById(int id)
12746 return GetSingleValue(PrepareSQL("SELECT name FROM videoversiontype WHERE id=%i", id), m_pDS2);
12749 void CVideoDatabase::SetVideoVersionDefaultArt(int dbId, int idFrom, VideoDbContentType type)
12751 MediaType mediaType;
12752 VideoContentTypeToString(type, mediaType);
12754 std::map<std::string, std::string> art;
12755 if (GetArtForItem(idFrom, mediaType, art))
12757 for (const auto& it : art)
12758 SetArtForItem(dbId, MediaTypeVideoVersion, it.first, it.second);