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.
9 #include "VideoDatabase.h"
12 #include "FileItemList.h"
13 #include "GUIInfoManager.h"
14 #include "GUIPassword.h"
15 #include "ServiceBroker.h"
16 #include "TextureCache.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"
66 #include <unordered_set>
69 using namespace dbiplus
;
70 using namespace XFILE
;
71 using namespace ADDON
;
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");
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
++)
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
);
155 column
= StringUtils::Format(",c{:02} text", i
);
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, "
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'; "
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'; "
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'; "
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'; "
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'; "
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'; "
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'); "
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); "
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'; "
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; "
380 void CVideoDatabase::CreateViews()
382 CLog::Log(LOGINFO
, "create episode_view");
383 std::string episodeview
= PrepareSQL("CREATE VIEW episode_view AS SELECT "
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 "
405 " files.idFile=episode.idFile"
407 " tvshow.idShow=episode.idShow"
409 " seasons.idSeason=episode.idSeason"
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");
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 "
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");
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 "
454 " min(idPath) AS idPath "
455 "FROM tvshowlinkpath "
457 m_pDS
->exec(tvshowlinkpathview
);
459 CLog::Log(LOGINFO
, "create tvshow_view");
461 std::string tvshowview
= PrepareSQL("CREATE VIEW tvshow_view AS SELECT "
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 "
474 " LEFT JOIN tvshowlinkpath_minview ON "
475 " tvshowlinkpath_minview.idShow=tvshow.idShow"
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
);
486 m_pDS
->exec(tvshowview
);
488 CLog::Log(LOGINFO
, "create season_view");
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 "
508 " JOIN tvshow_view ON"
509 " tvshow_view.idShow = seasons.idShow"
511 " episode.idShow = seasons.idShow AND episode.c%02d = seasons.season"
513 " files.idFile = episode.idFile "
514 " LEFT JOIN bookmark ON"
515 " bookmark.idFile = files.idFile AND bookmark.type = 1 "
516 "GROUP BY seasons.idSeason,"
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
);
534 m_pDS
->exec(seasonview
);
536 CLog::Log(LOGINFO
, "create musicvideo_view");
537 m_pDS
->exec(PrepareSQL(
538 "CREATE VIEW musicvideo_view AS SELECT"
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 "
552 " files.idFile=musicvideo.idFile"
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"
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, "
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, "
591 " FROM videoversion vv "
592 " WHERE vv.idMedia = movie.idMovie "
593 " AND vv.media_type = '%s' "
594 " AND vv.itemType = %i "
595 " ) AS hasVideoExtras, "
597 " WHEN vv.idFile = movie.idFile AND vv.itemType = %i THEN 1 "
599 " END AS isDefaultVersion, "
600 " vv.idFile AS videoVersionIdFile, "
601 " vvt.id AS videoVersionTypeId,"
602 " vvt.name AS videoVersionTypeName,"
603 " vvt.itemType AS videoVersionTypeItemType "
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"
616 " files.idFile = vv.idFile"
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
)
634 if (nullptr == m_pDB
)
636 if (nullptr == m_pDS
)
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
);
648 idPath
= m_pDS
->fv("path.idPath").get_asInt();
655 CLog::Log(LOGERROR
, "{} unable to getpath ({})", __FUNCTION__
, strSQL
);
660 bool CVideoDatabase::GetPaths(std::set
<std::string
> &paths
)
664 if (nullptr == m_pDB
)
666 if (nullptr == m_pDS
)
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"))
678 while (!m_pDS
->eof())
680 if (!m_pDS
->fv("noUpdate").get_asBool())
681 paths
.insert(m_pDS
->fv("strPath").get_asString());
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"))
694 while (!m_pDS
->eof())
696 if (!m_pDS
->fv("noUpdate").get_asBool())
697 paths
.insert(m_pDS
->fv("strPath").get_asString());
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"))
716 while (!m_pDS
->eof())
718 if (!m_pDS
->fv("noUpdate").get_asBool())
719 paths
.insert(m_pDS
->fv("strPath").get_asString());
727 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
732 bool CVideoDatabase::GetPathsLinkedToTvShow(int idShow
, std::vector
<std::string
> &paths
)
737 sql
= PrepareSQL("SELECT strPath FROM path JOIN tvshowlinkpath ON tvshowlinkpath.idPath=path.idPath WHERE idShow=%i", idShow
);
739 while (!m_pDS
->eof())
741 paths
.emplace_back(m_pDS
->fv(0).get_asString());
748 CLog::Log(LOGERROR
, "{} error during query: {}", __FUNCTION__
, sql
);
753 bool CVideoDatabase::GetPathsForTvShow(int idShow
, std::set
<int>& paths
)
758 if (nullptr == m_pDB
)
760 if (nullptr == m_pDS
)
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());
781 CLog::Log(LOGERROR
, "{} error during query: {}", __FUNCTION__
, strSQL
);
786 int CVideoDatabase::RunQuery(const std::string
&sql
)
788 auto start
= std::chrono::steady_clock::now();
791 if (m_pDS
->query(sql
))
793 rows
= m_pDS
->num_rows();
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
);
807 bool CVideoDatabase::GetSubPaths(const std::string
&basepath
, std::vector
<std::pair
<int, std::string
>>& subpaths
)
812 if (!m_pDB
|| !m_pDS
)
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());
823 while (!m_pDS
->eof())
825 subpaths
.emplace_back(m_pDS
->fv(0).get_asInt(), m_pDS
->fv(1).get_asString());
833 CLog::Log(LOGERROR
, "{} error during query: {}", __FUNCTION__
, sql
);
838 int CVideoDatabase::AddPath(const std::string
& strPath
, const std::string
&parentPath
/*= "" */, const CDateTime
& dateAdded
/* = CDateTime() */)
843 int idPath
= GetPathId(strPath
);
845 return idPath
; // already have the path
847 if (nullptr == m_pDB
)
849 if (nullptr == m_pDS
)
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
);
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());
866 strSQL
=PrepareSQL("insert into path (idPath, strPath) values (NULL, '%s')", strPath1
.c_str());
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
);
873 strSQL
=PrepareSQL("insert into path (idPath, strPath, idParentPath) values (NULL, '%s', %i)", strPath1
.c_str(), idParentPath
);
876 idPath
= (int)m_pDS
->lastinsertid();
881 CLog::Log(LOGERROR
, "{} unable to addpath ({})", __FUNCTION__
, strSQL
);
886 bool CVideoDatabase::GetPathHash(const std::string
&path
, std::string
&hash
)
890 if (nullptr == m_pDB
)
892 if (nullptr == m_pDS
)
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)
899 hash
= m_pDS
->fv("strHash").get_asString();
904 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, path
);
910 bool CVideoDatabase::GetSourcePath(const std::string
&path
, std::string
&sourcePath
)
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)
923 std::string strPath2
;
925 if (URIUtils::IsMultiPath(path
))
926 strPath2
= CMultiPathDirectory::GetFirstPath(path
);
930 std::string strPath1
= URIUtils::GetDirectory(strPath2
);
931 int idPath
= GetPathId(strPath1
);
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();
953 // look for parent paths until there is one which is a source
954 std::string strParent
;
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();
974 strPath1
= strParent
;
980 sourcePath
= strParent
;
986 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
= "";
1002 if (nullptr == m_pDB
)
1004 if (nullptr == m_pDS
)
1007 const auto finalDateAdded
= GetDateAdded(strFileNameAndPath
, dateAdded
);
1009 std::string strFileName
, strPath
;
1010 SplitPath(strFileNameAndPath
,strPath
,strFileName
);
1012 int idPath
= AddPath(strPath
, parentPath
, finalDateAdded
);
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() ;
1027 std::string strPlaycount
= "NULL";
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();
1043 CLog::Log(LOGERROR
, "{} unable to addfile ({})", __FUNCTION__
, strSQL
);
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
;
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)
1072 CDateTime finalDateAdded
;
1075 if (nullptr == m_pDB
)
1077 if (nullptr == m_pDS
)
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
));
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
)
1098 if (nullptr == m_pDS
)
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
);
1111 CLog::Log(LOGERROR
, "{} ({}, {}) failed", __FUNCTION__
, path
, hash
);
1117 bool CVideoDatabase::LinkMovieToTvshow(int idMovie
, int idShow
, bool bRemove
)
1121 if (nullptr == m_pDB
)
1123 if (nullptr == m_pDS
)
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
);
1133 std::string strSQL
=PrepareSQL("insert into movielinktvshow (idShow,idMovie) values (%i,%i)", idShow
,idMovie
);
1134 m_pDS
->exec(strSQL
);
1140 CLog::Log(LOGERROR
, "{} ({}, {}) failed", __FUNCTION__
, idMovie
, idShow
);
1146 bool CVideoDatabase::IsLinkedToTvshow(int idMovie
)
1150 if (nullptr == m_pDB
)
1152 if (nullptr == m_pDS
)
1155 std::string strSQL
=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie
);
1156 m_pDS
->query(strSQL
);
1168 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idMovie
);
1174 bool CVideoDatabase::GetLinksToTvShow(int idMovie
, std::vector
<int>& ids
)
1178 if (nullptr == m_pDB
)
1180 if (nullptr == m_pDS
)
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());
1196 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idMovie
);
1203 //********************************************************************************************************************************
1204 int CVideoDatabase::GetFileId(const std::string
& strFilenameAndPath
)
1208 if (nullptr == m_pDB
)
1210 if (nullptr == m_pDS
)
1212 std::string strPath
, strFileName
;
1213 SplitPath(strFilenameAndPath
,strPath
,strFileName
);
1215 int idPath
= GetPathId(strPath
);
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();
1231 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1236 int CVideoDatabase::GetFileId(const CFileItem
&item
)
1239 if (item
.HasVideoInfoTag())
1240 fileId
= GetFileId(*item
.GetVideoInfoTag());
1243 fileId
= GetFileId(item
.GetPath());
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())
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
)
1273 if (nullptr == m_pDS
)
1277 // needed for query parameters
1278 int idFile
= GetFileId(strFilenameAndPath
);
1280 std::string strPath
;
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
)
1292 if (idFile
== -1 && strPath
!= strFilenameAndPath
)
1297 strSQL
= PrepareSQL("SELECT idMovie FROM movie "
1298 " JOIN files ON files.idFile=movie.idFile "
1299 "WHERE files.idPath=%i",
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();
1317 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1322 int CVideoDatabase::GetTvShowId(const std::string
& strPath
)
1326 if (nullptr == m_pDB
)
1328 if (nullptr == m_pDS
)
1332 // have to join movieinfo table for correct results
1333 int idPath
= GetPathId(strPath
);
1338 std::string strPath1
=strPath
;
1339 std::string strParent
;
1342 strSQL
=PrepareSQL("select idShow from tvshowlinkpath where tvshowlinkpath.idPath=%i",idPath
);
1343 m_pDS
->query(strSQL
);
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
);
1353 int idShow
= m_pDS
->fv("idShow").get_asInt();
1357 strPath1
= strParent
;
1360 if (m_pDS
->num_rows() > 0)
1361 idTvShow
= m_pDS
->fv("idShow").get_asInt();
1368 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
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
)
1379 if (nullptr == m_pDS
)
1382 // need this due to the nested GetEpisodeInfo query
1383 std::unique_ptr
<Dataset
> pDS
;
1384 pDS
.reset(m_pDB
->CreateDataset());
1388 int idFile
= GetFileId(strFilenameAndPath
);
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
);
1397 if (pDS
->num_rows() > 0)
1399 if (idEpisode
== -1)
1400 idEpisode
= pDS
->fv("episode.idEpisode").get_asInt();
1401 else // use the hint!
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
;
1428 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1433 int CVideoDatabase::GetMusicVideoId(const std::string
& strFilenameAndPath
)
1437 if (nullptr == m_pDB
)
1439 if (nullptr == m_pDS
)
1442 int idFile
= GetFileId(strFilenameAndPath
);
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
);
1452 if (m_pDS
->num_rows() > 0)
1453 idMVideo
= m_pDS
->fv("idMVideo").get_asInt();
1460 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
1465 //********************************************************************************************************************************
1466 int CVideoDatabase::AddNewMovie(CVideoInfoTag
& details
)
1468 const auto filePath
= details
.GetPath();
1472 if (nullptr == m_pDB
)
1474 if (nullptr == m_pDS
)
1477 if (details
.m_iFileId
<= 0)
1479 details
.m_iFileId
= AddFile(details
);
1480 if (details
.m_iFileId
<= 0)
1487 PrepareSQL("INSERT INTO movie (idMovie, idFile) VALUES (NULL, %i)", details
.m_iFileId
));
1488 details
.m_iDbId
= static_cast<int>(m_pDS
->lastinsertid());
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
;
1501 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
1502 RollbackTransaction();
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
);
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();
1524 //********************************************************************************************************************************
1525 int CVideoDatabase::AddNewEpisode(int idShow
, CVideoInfoTag
& details
)
1527 const auto filePath
= details
.GetPath();
1531 if (nullptr == m_pDB
|| nullptr == m_pDS
)
1534 if (details
.m_iFileId
<= 0)
1536 details
.m_iFileId
= AddFile(details
);
1537 if (details
.m_iFileId
<= 0)
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
;
1551 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
1556 int CVideoDatabase::AddNewMusicVideo(CVideoInfoTag
& details
)
1558 const auto filePath
= details
.GetPath();
1562 if (nullptr == m_pDB
)
1564 if (nullptr == m_pDS
)
1567 if (details
.m_iFileId
<= 0)
1569 details
.m_iFileId
= AddFile(details
);
1570 if (details
.m_iFileId
<= 0)
1574 std::string strSQL
= PrepareSQL("INSERT INTO musicvideo (idMVideo, idFile) VALUES (NULL, %i)",
1576 m_pDS
->exec(strSQL
);
1577 details
.m_iDbId
= static_cast<int>(m_pDS
->lastinsertid());
1579 return details
.m_iDbId
;
1583 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
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
)
1595 if (nullptr == m_pDS
)
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)
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();
1611 int id
= m_pDS
->fv(firstField
.c_str()).get_asInt();
1618 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, value
);
1624 int CVideoDatabase::UpdateRatings(int mediaId
, const char *mediaType
, const RatingMap
& values
, const std::string
& defaultRating
)
1628 if (nullptr == m_pDB
)
1630 if (nullptr == m_pDS
)
1633 std::string sql
= PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='%s'", mediaId
, mediaType
);
1636 return AddRatings(mediaId
, mediaType
, values
, defaultRating
);
1640 CLog::Log(LOGERROR
, "{} unable to update ratings of ({})", __FUNCTION__
, mediaType
);
1645 int CVideoDatabase::AddRatings(int mediaId
, const char *mediaType
, const RatingMap
& values
, const std::string
& defaultRating
)
1650 if (nullptr == m_pDB
)
1652 if (nullptr == m_pDS
)
1655 for (const auto& i
: values
)
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)
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();
1673 id
= m_pDS
->fv(0).get_asInt();
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
)
1687 CLog::Log(LOGERROR
, "{} ({} - {}) failed", __FUNCTION__
, mediaId
, mediaType
);
1693 int CVideoDatabase::UpdateUniqueIDs(int mediaId
, const char *mediaType
, const CVideoInfoTag
& details
)
1697 if (nullptr == m_pDB
)
1699 if (nullptr == m_pDS
)
1702 std::string sql
= PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='%s'", mediaId
, mediaType
);
1705 return AddUniqueIDs(mediaId
, mediaType
, details
);
1709 CLog::Log(LOGERROR
, "{} unable to update unique ids of ({})", __FUNCTION__
, mediaType
);
1714 int CVideoDatabase::AddUniqueIDs(int mediaId
, const char *mediaType
, const CVideoInfoTag
& details
)
1719 if (nullptr == m_pDB
)
1721 if (nullptr == m_pDS
)
1724 for (const auto& i
: details
.GetUniqueIDs())
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)
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();
1739 id
= m_pDS
->fv(0).get_asInt();
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())
1752 CLog::Log(LOGERROR
, "{} ({} - {}) failed", __FUNCTION__
, mediaId
, mediaType
);
1758 int CVideoDatabase::AddSet(const std::string
& strSet
,
1759 const std::string
& strOverview
/* = "" */,
1760 const bool updateOverview
/* = true */)
1767 if (m_pDB
== nullptr || m_pDS
== nullptr)
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)
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());
1782 int id
= m_pDS
->fv("idSet").get_asInt();
1788 strSQL
= PrepareSQL("UPDATE sets SET strOverview = '%s' WHERE idSet = %i",
1789 strOverview
.c_str(), id
);
1790 m_pDS
->exec(strSQL
);
1798 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSet
);
1804 int CVideoDatabase::AddTag(const std::string
& name
)
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
)
1818 if (nullptr == m_pDS
)
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)
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();
1838 idActor
= m_pDS
->fv(0).get_asInt();
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
);
1849 SetArtForItem(idActor
, "actor", "thumb", thumb
);
1854 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, name
);
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
);
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());
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());
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
)
1900 int idValue
= AddToTable(field
, field
+ "_id", "name", i
);
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());
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
)
1921 int idValue
= AddActor(i
, "");
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());
1933 AddActorLinksToItem(mediaId
, mediaType
, field
, values
);
1937 void CVideoDatabase::AddTagToItem(int media_id
, int tag_id
, const std::string
&type
)
1942 AddToLinkTable(media_id
, type
, "tag", tag_id
);
1945 void CVideoDatabase::RemoveTagFromItem(int media_id
, int tag_id
, const std::string
&type
)
1950 RemoveFromLinkTable(media_id
, type
, "tag", tag_id
);
1953 void CVideoDatabase::RemoveTagsFromItem(int media_id
, const std::string
&type
)
1958 m_pDS2
->exec(PrepareSQL("DELETE FROM tag_link WHERE media_id=%d AND media_type='%s'", media_id
, type
.c_str()));
1962 void CVideoDatabase::AddCast(int mediaId
, const char *mediaType
, const std::vector
< SActorInfo
> &cast
)
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
))
1980 if (GetEpisodeInfo(strFilenameAndPath
, details
))
1982 if (GetMusicVideoInfo(strFilenameAndPath
, details
))
1984 if (GetFileInfo(strFilenameAndPath
, details
))
1990 bool CVideoDatabase::HasMovieInfo(const std::string
& strFilenameAndPath
)
1994 if (nullptr == m_pDB
)
1996 if (nullptr == m_pDS
)
1998 int idMovie
= GetMovieId(strFilenameAndPath
);
1999 return (idMovie
> 0); // index of zero is also invalid
2003 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2008 bool CVideoDatabase::HasTvShowInfo(const std::string
& strPath
)
2012 if (nullptr == m_pDB
)
2014 if (nullptr == m_pDS
)
2016 int idTvShow
= GetTvShowId(strPath
);
2017 return (idTvShow
> 0); // index of zero is also invalid
2021 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
2026 bool CVideoDatabase::HasEpisodeInfo(const std::string
& strFilenameAndPath
)
2030 if (nullptr == m_pDB
)
2032 if (nullptr == m_pDS
)
2034 int idEpisode
= GetEpisodeId(strFilenameAndPath
);
2035 return (idEpisode
> 0); // index of zero is also invalid
2039 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2044 bool CVideoDatabase::HasMusicVideoInfo(const std::string
& strFilenameAndPath
)
2048 if (nullptr == m_pDB
)
2050 if (nullptr == m_pDS
)
2052 int idMVideo
= GetMusicVideoId(strFilenameAndPath
);
2053 return (idMVideo
> 0); // index of zero is also invalid
2057 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2062 void CVideoDatabase::DeleteDetailsForTvShow(int idTvShow
)
2066 if (nullptr == m_pDB
)
2068 if (nullptr == m_pDS
)
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
);
2104 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idTvShow
);
2108 //********************************************************************************************************************************
2109 void CVideoDatabase::GetMoviesByActor(const std::string
& name
, CFileItemList
& items
)
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
)
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
)
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
)
2149 if (nullptr == m_pDB
)
2151 if (nullptr == m_pDS
)
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
));
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)
2195 idMovie
= GetMovieId(strFilenameAndPath
);
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
)};
2212 sql
= PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND videoVersionIdFile = %i",
2217 sql
= PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND isDefaultVersion = 1",
2220 if (!m_pDS
->query(sql
))
2223 details
= GetDetailsForMovie(m_pDS
, getDetails
);
2224 return !details
.IsEmpty();
2228 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2233 std::string
CVideoDatabase::GetMovieTitle(int idMovie
)
2235 if (!m_pDB
|| !m_pDS
)
2238 m_pDS
->query(PrepareSQL("SELECT c%02d from movie where idMovie = %i", VIDEODB_ID_TITLE
, idMovie
));
2241 return m_pDS
->fv(0).get_asString();
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)
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
))
2261 details
= GetDetailsForTvShow(m_pDS
, getDetails
, item
);
2262 return !details
.IsEmpty();
2266 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
2271 bool CVideoDatabase::GetSeasonInfo(const std::string
& path
,
2273 CVideoInfoTag
& details
,
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
);
2282 CLog::LogF(LOGERROR
, "Failed to obtain seasonId for path={}, season={}", path
, season
);
2286 const int idSeason
= static_cast<int>(std::strtol(id
.c_str(), nullptr, 10));
2287 return GetSeasonInfo(idSeason
, details
, item
);
2292 CLog::LogF(LOGERROR
, "Exception while trying to to obtain seasonId for path={}, season={}",
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
,
2318 if (!m_pDB
|| !m_pDS
)
2321 std::string sql
= PrepareSQL("SELECT idSeason, idShow, season, name, userrating FROM seasons WHERE idSeason=%i", idSeason
);
2322 if (!m_pDS
->query(sql
))
2325 if (m_pDS
->num_rows() != 1)
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
2338 CFileItemList seasons
;
2339 if (!GetSeasonsNav(StringUtils::Format("videodb://tvshows/titles/{}/", idShow
), seasons
, -1,
2340 -1, -1, -1, idShow
, false) ||
2341 seasons
.Size() <= 0)
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();
2359 const int season
= m_pDS
->fv(2).get_asInt();
2360 std::string name
= m_pDS
->fv(3).get_asString();
2365 name
= g_localizeStrings
.Get(20381);
2367 name
= StringUtils::Format(g_localizeStrings
.Get(20358), season
);
2370 details
.m_strTitle
= name
;
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();
2384 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSeason
);
2389 bool CVideoDatabase::GetEpisodeBasicInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idEpisode
/* = -1 */)
2394 idEpisode
= GetEpisodeId(strFilenameAndPath
);
2399 std::string sql
= PrepareSQL("select * from episode where idEpisode=%i",idEpisode
);
2400 if (!m_pDS
->query(sql
))
2402 details
= GetBasicDetailsForEpisode(m_pDS
);
2403 return !details
.IsEmpty();
2407 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2412 bool CVideoDatabase::GetEpisodeInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idEpisode
/* = -1 */, int getDetails
/* = VideoDbDetailsAll */)
2416 if (m_pDB
== nullptr || m_pDS
== nullptr)
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
))
2426 details
= GetDetailsForEpisode(m_pDS
, getDetails
);
2427 return !details
.IsEmpty();
2431 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2436 bool CVideoDatabase::GetMusicVideoInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idMVideo
/* = -1 */, int getDetails
/* = VideoDbDetailsAll */)
2440 if (m_pDB
== nullptr || m_pDS
== nullptr)
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
))
2450 details
= GetDetailsForMusicVideo(m_pDS
, getDetails
);
2451 return !details
.IsEmpty();
2455 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
2460 bool CVideoDatabase::GetSetInfo(int idSet
, CVideoInfoTag
& details
, CFileItem
* item
/* = nullptr */)
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())
2475 details
= *(items
[0]->GetVideoInfoTag());
2478 return !details
.IsEmpty();
2482 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSet
);
2487 bool CVideoDatabase::GetFileInfo(const std::string
& strFilenameAndPath
, CVideoInfoTag
& details
, int idFile
/* = -1 */)
2492 idFile
= GetFileId(strFilenameAndPath
);
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
))
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();
2528 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strFilenameAndPath
);
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()));
2543 case VIDEODB_TYPE_INT
:
2544 conditions
.emplace_back(PrepareSQL("c%02d='%i'", i
, *(const int*)(((const char*)&details
)+offsets
[i
].offset
)));
2546 case VIDEODB_TYPE_COUNT
:
2548 int value
= *(const int*)(((const char*)&details
)+offsets
[i
].offset
);
2550 conditions
.emplace_back(PrepareSQL("c%02d=%i", i
, value
));
2552 conditions
.emplace_back(PrepareSQL("c%02d=NULL", i
));
2555 case VIDEODB_TYPE_BOOL
:
2556 conditions
.emplace_back(PrepareSQL("c%02d='%s'", i
, *(const bool*)(((const char*)&details
)+offsets
[i
].offset
)?"true":"false"));
2558 case VIDEODB_TYPE_FLOAT
:
2559 conditions
.emplace_back(PrepareSQL(
2560 "c%02d='%f'", i
, *(const double*)(((const char*)&details
) + offsets
[i
].offset
)));
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()));
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()));
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()));
2572 case VIDEODB_TYPE_UNUSED
: // Skip the unused field to avoid populating unused data
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
)
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
))
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
);
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();
2623 idMovie
= GetMovieId(filePath
);
2626 DeleteMovie(idMovie
, true); // true to keep the table entry
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
);
2635 RollbackTransaction();
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
);
2653 details
.m_iIdRating
= AddRatings(idMovie
, MediaTypeMovie
, details
.m_ratings
, details
.GetDefaultRating());
2656 details
.m_iIdUniqueID
= AddUniqueIDs(idMovie
, MediaTypeMovie
, details
);
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 "
2683 " INNER JOIN files "
2684 " ON files.idFile=movie.idFile "
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
);
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
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
);
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
);
2711 sql
+= PrepareSQL(", idSet = %i", idSet
);
2713 sql
+= ", idSet = NULL";
2714 if (details
.m_iUserRating
> 0 && details
.m_iUserRating
< 11)
2715 sql
+= PrepareSQL(", userrating = %i", details
.m_iUserRating
);
2717 sql
+= ", userrating = NULL";
2718 if (details
.HasPremiered())
2719 sql
+= PrepareSQL(", premiered = '%s'", details
.GetPremiered().GetAsDBDate().c_str());
2721 sql
+= PrepareSQL(", premiered = '%i'", details
.GetYear());
2722 sql
+= PrepareSQL(" where idMovie=%i", idMovie
);
2724 CommitTransaction();
2730 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
2732 RollbackTransaction();
2736 int CVideoDatabase::UpdateDetailsForMovie(int idMovie
, CVideoInfoTag
& details
, const std::map
<std::string
, std::string
> &artwork
, const std::set
<std::string
> &updatedDetails
)
2743 CLog::Log(LOGINFO
, "{}: Starting updates for movie {}", __FUNCTION__
, idMovie
);
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
2771 if (updatedDetails
.find("set") != updatedDetails
.end())
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);
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
);
2804 sql
+= PrepareSQL(", idSet = %i", idSet
);
2806 sql
+= ", idSet = NULL";
2807 if (details
.m_iUserRating
> 0 && details
.m_iUserRating
< 11)
2808 sql
+= PrepareSQL(", userrating = %i", details
.m_iUserRating
);
2810 sql
+= ", userrating = NULL";
2811 if (details
.HasPremiered())
2812 sql
+= PrepareSQL(", premiered = '%s'", details
.GetPremiered().GetAsDBDate().c_str());
2814 sql
+= PrepareSQL(", premiered = '%i'", details
.GetYear());
2815 sql
+= PrepareSQL(" where idMovie=%i", idMovie
);
2818 CommitTransaction();
2820 CLog::Log(LOGINFO
, "{}: Finished updates for movie {}", __FUNCTION__
, idMovie
);
2826 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idMovie
);
2828 RollbackTransaction();
2832 int CVideoDatabase::SetDetailsForMovieSet(const CVideoInfoTag
& details
, const std::map
<std::string
, std::string
> &artwork
, int idSet
/* = -1 */)
2834 if (details
.m_strTitle
.empty())
2842 idSet
= AddSet(details
.m_strTitle
, details
.m_strPlot
);
2845 RollbackTransaction();
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
);
2855 CommitTransaction();
2861 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSet
);
2863 RollbackTransaction();
2867 int CVideoDatabase::GetMatchingTvShow(const CVideoInfoTag
&details
)
2869 // first try matching on uniqueid, then on title + year
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()));
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()));
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.
2894 for (const auto &i
: paths
)
2896 idTvShow
= GetTvShowId(i
.first
);
2902 idTvShow
= GetMatchingTvShow(details
);
2905 idTvShow
= AddTvShow();
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
);
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
)
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
);
2933 details
.m_iIdRating
= AddRatings(idTvShow
, MediaTypeTvShow
, details
.m_ratings
, details
.GetDefaultRating());
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
)
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
);
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
);
2969 sql
+= ", userrating = NULL";
2970 if (details
.GetDuration() > 0)
2971 sql
+= PrepareSQL(", duration = %i", details
.GetDuration());
2973 sql
+= ", duration = NULL";
2974 sql
+= PrepareSQL(" WHERE idShow=%i", idTvShow
);
2975 if (ExecuteQuery(sql
))
2977 CommitTransaction();
2980 RollbackTransaction();
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)
2995 idSeason
= AddSeason(idShow
, details
.m_iSeason
);
2998 RollbackTransaction();
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
);
3012 sql
+= ", userrating = NULL";
3013 sql
+= PrepareSQL(" WHERE idSeason=%i", idSeason
);
3014 m_pDS
->exec(sql
.c_str());
3015 CommitTransaction();
3021 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSeason
);
3023 RollbackTransaction();
3027 bool CVideoDatabase::SetFileForEpisode(const std::string
& fileAndPath
, int idEpisode
, int idFile
)
3032 std::string sql
= PrepareSQL("UPDATE episode SET c18='%s', idFile=%i WHERE idEpisode=%i",
3033 fileAndPath
.c_str(), idFile
, idEpisode
);
3035 CommitTransaction();
3041 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idEpisode
);
3043 RollbackTransaction();
3047 bool CVideoDatabase::SetFileForMovie(const std::string
& fileAndPath
, int idMovie
, int idFile
)
3052 std::string sql
= PrepareSQL("UPDATE movie SET c22='%s', idFile=%i WHERE idMovie=%i",
3053 fileAndPath
.c_str(), idFile
, idMovie
);
3055 sql
= PrepareSQL("UPDATE videoversion SET idFile=%i WHERE idMedia=%i AND media_type='movie'",
3058 CommitTransaction();
3064 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idMovie
);
3066 RollbackTransaction();
3070 int CVideoDatabase::SetDetailsForEpisode(CVideoInfoTag
& details
,
3071 const std::map
<std::string
, std::string
>& artwork
,
3073 int idEpisode
/* = -1 */)
3075 const auto filePath
= details
.GetPath();
3081 idEpisode
= GetEpisodeId(filePath
);
3084 DeleteEpisode(idEpisode
, true); // true to keep the table entry
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
);
3093 RollbackTransaction();
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
);
3107 details
.m_iIdRating
= AddRatings(idEpisode
, MediaTypeEpisode
, details
.m_ratings
, details
.GetDefaultRating());
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
);
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
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
);
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
);
3151 sql
+= ", userrating = NULL";
3152 sql
+= PrepareSQL(", idSeason = %i", idSeason
);
3153 sql
+= PrepareSQL(" where idEpisode=%i", idEpisode
);
3155 CommitTransaction();
3161 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
3163 RollbackTransaction();
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
);
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
);
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();
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();
3198 idMVideo
= GetMusicVideoId(filePath
);
3201 DeleteMusicVideo(idMVideo
, true); // Keep id
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
);
3210 RollbackTransaction();
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
);
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
);
3240 sql
+= ", userrating = NULL";
3241 if (details
.HasPremiered())
3242 sql
+= PrepareSQL(", premiered = '%s'", details
.GetPremiered().GetAsDBDate().c_str());
3244 sql
+= PrepareSQL(", premiered = '%i'", details
.GetYear());
3245 sql
+= PrepareSQL(" where idMVideo=%i", idMVideo
);
3247 CommitTransaction();
3253 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
3255 RollbackTransaction();
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
);
3266 SetStreamDetailsForFileId(details
, idFile
);
3270 void CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails
& details
, int idFile
)
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, "
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
);
3328 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idFile
);
3332 //********************************************************************************************************************************
3333 void CVideoDatabase::GetFilePathById(int idMovie
, std::string
& filePath
, VideoDbContentType iType
)
3337 if (nullptr == m_pDB
)
3339 if (nullptr == m_pDS
)
3342 if (idMovie
< 0) return ;
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
);
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
);
3363 filePath
= m_pDS
->fv("path.strPath").get_asString();
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
);
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));
3391 int idFile
= GetFileId(strFilenameAndPath
);
3392 if (idFile
< 0) return ;
3394 bookmarks
.erase(bookmarks
.begin(), bookmarks
.end());
3395 if (nullptr == m_pDB
)
3397 if (nullptr == m_pDS
)
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())
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();
3420 bookmarks
.push_back(bookmark
);
3423 //sort(bookmarks.begin(), bookmarks.end(), SortBookmarks);
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];
3445 void CVideoDatabase::DeleteResumeBookMark(const CFileItem
& item
)
3447 if (!m_pDB
|| !m_pDS
)
3450 int fileID
= item
.GetVideoInfoTag()->m_iFileId
;
3453 fileID
= GetFileId(item
.GetPath());
3460 std::string sql
= PrepareSQL("delete from bookmark where idFile=%i and type=%i", fileID
, CBookmark::RESUME
);
3463 VideoDbContentType iType
= static_cast<VideoDbContentType
>(item
.GetVideoContentType());
3464 std::string content
;
3467 case VideoDbContentType::MOVIES
:
3468 content
= MediaTypeMovie
;
3470 case VideoDbContentType::EPISODES
:
3471 content
= MediaTypeEpisode
;
3473 case VideoDbContentType::TVSHOWS
:
3474 content
= MediaTypeTvShow
;
3476 case VideoDbContentType::MUSICVIDEOS
:
3477 content
= MediaTypeMusicVideo
;
3483 if (!content
.empty())
3485 AnnounceUpdate(content
, item
.GetVideoInfoTag()->m_iDbId
);
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
));
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
);
3523 if (nullptr == m_pDB
)
3525 if (nullptr == m_pDS
)
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
)
3545 m_pDS
->query( strSQL
);
3546 if (m_pDS
->num_rows() != 0)
3547 idBookmark
= m_pDS
->get_field_value("idBookmark").get_asInt();
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
);
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
);
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
)
3572 if (nullptr == m_pDS
)
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
);
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
);
3607 return ClearBookMarksOfFile(idFile
, type
);
3610 void CVideoDatabase::ClearBookMarksOfFile(int idFile
, CBookmark::EType type
/*= CBookmark::STANDARD*/)
3617 if (nullptr == m_pDB
)
3619 if (nullptr == m_pDS
)
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
);
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
);
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();
3661 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
);
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
);
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 */)
3712 if (nullptr == m_pDB
)
3714 if (nullptr == m_pDS
)
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
3727 const std::string path
= GetSingleValue(PrepareSQL(
3728 "SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i",
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()};
3745 PrepareSQL("SELECT idFile FROM videoversion WHERE idMedia=%i AND media_type='%s'",
3746 idMovie
, MediaTypeMovie
));
3750 if (!DeleteVideoAsset(pDS
->fv(0).get_asInt()))
3752 RollbackTransaction();
3762 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3764 AnnounceRemove(MediaTypeMovie
, idMovie
);
3766 CommitTransaction();
3771 CLog::LogF(LOGERROR
, "failed");
3772 RollbackTransaction();
3776 void CVideoDatabase::DeleteTvShow(const std::string
& strPath
)
3778 int idTvShow
= GetTvShowId(strPath
);
3780 DeleteTvShow(idTvShow
);
3783 void CVideoDatabase::DeleteTvShow(int idTvShow
, bool bKeepId
/* = false */)
3790 if (nullptr == m_pDB
)
3792 if (nullptr == m_pDS
)
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
);
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
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
));
3823 InvalidatePathHash(path
);
3827 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3829 AnnounceRemove(MediaTypeTvShow
, idTvShow
);
3831 CommitTransaction();
3836 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idTvShow
);
3837 RollbackTransaction();
3841 void CVideoDatabase::DeleteSeason(int idSeason
, bool bKeepId
/* = false */)
3848 if (m_pDB
== nullptr || m_pDS
== nullptr || m_pDS2
== nullptr)
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
);
3863 ExecuteQuery(PrepareSQL("DELETE FROM seasons WHERE idSeason = %i", idSeason
));
3865 CommitTransaction();
3869 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idSeason
);
3870 RollbackTransaction();
3874 void CVideoDatabase::DeleteEpisode(int idEpisode
, bool bKeepId
/* = false */)
3881 if (nullptr == m_pDB
)
3883 if (nullptr == m_pDS
)
3886 //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
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
3897 std::string path
= GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile
));
3899 InvalidatePathHash(path
);
3901 std::string strSQL
= PrepareSQL("delete from episode where idEpisode=%i", idEpisode
);
3902 m_pDS
->exec(strSQL
);
3908 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idEpisode
);
3912 void CVideoDatabase::DeleteMusicVideo(int idMVideo
, bool bKeepId
/* = false */)
3919 if (nullptr == m_pDB
)
3921 if (nullptr == m_pDS
)
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
3933 std::string path
= GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile
));
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
3943 AnnounceRemove(MediaTypeMusicVideo
, idMVideo
);
3945 CommitTransaction();
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);
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
)
3978 if (nullptr == m_pDS
)
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
);
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
)
4001 ExecuteQuery(PrepareSQL("update movie set idSet = %i where idMovie = %i", idSet
, idMovie
));
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
)
4013 m_pDS
->query(PrepareSQL(
4014 "SELECT strPath FROM path JOIN files ON path.idPath = files.idPath WHERE idFile = %i",
4019 return m_pDS
->fv("strPath").get_asString();
4025 CLog::Log(LOGERROR
, "{} failed for file {}", __FUNCTION__
, idFile
);
4031 int CVideoDatabase::GetFileIdByMovie(int idMovie
)
4033 if (!m_pDB
|| !m_pDS
)
4040 m_pDS
->query(PrepareSQL("SELECT idFile FROM movie WHERE idMovie = %i", idMovie
));
4044 idFile
= m_pDS
->fv("idFile").get_asInt();
4051 CLog::Log(LOGERROR
, "{} failed for movie {}", __FUNCTION__
, idMovie
);
4057 void CVideoDatabase::GetSameVideoItems(const CFileItem
& item
, CFileItemList
& items
)
4059 if (!m_pDB
|| !m_pDS
)
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 "
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());
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())
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()));
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
);
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
))
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)
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
;
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
);
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();
4174 case VIDEODB_TYPE_INT
:
4175 case VIDEODB_TYPE_COUNT
:
4176 *(int*)(((char*)&details
)+offsets
[i
].offset
) = record
->at(i
+idxOffset
).get_asInt();
4178 case VIDEODB_TYPE_BOOL
:
4179 *(bool*)(((char*)&details
)+offsets
[i
].offset
) = record
->at(i
+idxOffset
).get_asBool();
4181 case VIDEODB_TYPE_FLOAT
:
4182 *(float*)(((char*)&details
)+offsets
[i
].offset
) = record
->at(i
+idxOffset
).get_asFloat();
4184 case VIDEODB_TYPE_STRINGARRAY
:
4186 std::string value
= record
->at(i
+idxOffset
).get_asString();
4188 *(std::vector
<std::string
>*)(((char*)&details
)+offsets
[i
].offset
) = StringUtils::Split(value
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator
);
4191 case VIDEODB_TYPE_DATE
:
4192 ((CDateTime
*)(((char*)&details
)+offsets
[i
].offset
))->SetFromDBDate(record
->at(i
+idxOffset
).get_asString());
4194 case VIDEODB_TYPE_DATETIME
:
4195 ((CDateTime
*)(((char*)&details
)+offsets
[i
].offset
))->SetFromDBDateTime(record
->at(i
+idxOffset
).get_asString());
4197 case VIDEODB_TYPE_UNUSED
: // Skip the unused field to avoid populating unused data
4203 bool CVideoDatabase::GetDetailsByTypeAndId(CFileItem
& item
, VideoDbContentType type
, int id
)
4205 CVideoInfoTag details
;
4210 case VideoDbContentType::MOVIES
:
4211 GetMovieInfo("", details
, id
);
4213 case VideoDbContentType::TVSHOWS
:
4214 GetTvShowInfo("", details
, id
, &item
);
4216 case VideoDbContentType::EPISODES
:
4217 GetEpisodeInfo("", details
, id
);
4219 case VideoDbContentType::MUSICVIDEOS
:
4220 GetMusicVideoInfo("", details
, id
);
4226 item
.SetFromVideoInfoTag(details
);
4230 CVideoInfoTag
CVideoDatabase::GetDetailsByTypeAndId(VideoDbContentType type
, int id
)
4233 if (GetDetailsByTypeAndId(item
, type
, id
))
4234 return CVideoInfoTag(*item
.GetVideoInfoTag());
4239 bool CVideoDatabase::GetStreamDetails(CFileItem
& item
)
4241 // Note that this function (possibly) creates VideoInfoTags for items that don't have one yet!
4244 if (item
.HasVideoInfoTag())
4245 fileId
= item
.GetVideoInfoTag()->m_iFileId
;
4247 fileId
= GetFileId(item
);
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
;
4262 if (URIUtils::GetExtension(path
) == ".mpls")
4263 fileId
= GetFileId(path
);
4265 fileId
= tag
.m_iFileId
;
4270 bool retVal
= false;
4272 CStreamDetails
& details
= tag
.m_streamDetails
;
4275 std::unique_ptr
<Dataset
> pDS(m_pDB
->CreateDataset());
4278 std::string strSQL
= PrepareSQL("SELECT * FROM streamdetails WHERE idFile = %i", fileId
);
4283 CStreamDetail::StreamType e
= (CStreamDetail::StreamType
)pDS
->fv(1).get_asInt();
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
);
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;
4308 p
->m_iChannels
= pDS
->fv(7).get_asInt();
4309 p
->m_strLanguage
= pDS
->fv(8).get_asString();
4310 details
.AddStream(p
);
4314 case CStreamDetail::SUBTITLE
:
4316 CStreamDetailSubtitle
*p
= new CStreamDetailSubtitle();
4317 p
->m_strLanguage
= pDS
->fv(9).get_asString();
4318 details
.AddStream(p
);
4331 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, tag
.m_iFileId
);
4333 details
.DetermineBestStreams();
4335 if (details
.GetVideoDuration() > 0)
4336 tag
.SetDuration(details
.GetVideoDuration());
4341 bool CVideoDatabase::GetResumePoint(CVideoInfoTag
& tag
)
4343 if (tag
.m_iFileId
< 0)
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
--)
4360 if (GetResumeBookMark(fileList
[i
]->GetPath(), bookmark
))
4362 bookmark
.partNumber
= (i
+1); /* store part number in here */
4363 tag
.SetResumePoint(bookmark
);
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
);
4375 tag
.SetResumePoint(m_pDS2
->fv(0).get_asDouble(), m_pDS2
->fv(1).get_asDouble(), "");
4383 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, tag
.m_strFileNameAndPath
);
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
;
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());
4437 details
.SetPremieredFromDBDate(premieredString
);
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
);
4464 details
.m_showLink
.emplace_back(m_pDS2
->fv(0).get_asString());
4469 if (getDetails
& VideoDbDetailsStream
)
4470 GetStreamDetails(details
);
4472 details
.m_parsedDetails
= getDetails
;
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
;
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();
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
;
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
)
4547 details
.SetPlayCount((details
.m_iEpisode
<= details
.GetPlayCount()) ? 1 : 0);
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)
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();
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)
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);
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
;
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
;
4647 if (record
== nullptr)
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());
4673 details
.SetPremieredFromDBDate(premieredString
);
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
;
4696 void CVideoDatabase::GetCast(int media_id
, const std::string
&media_type
, std::vector
<SActorInfo
> &cast
)
4705 std::string sql
= PrepareSQL("SELECT actor.name,"
4707 " actor_link.cast_order,"
4712 " actor_link.actor_id=actor.actor_id"
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());
4718 while (!m_pDS2
->eof())
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
));
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
)
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());
4756 while (!m_pDS2
->eof())
4758 tags
.emplace_back(m_pDS2
->fv(0).get_asString());
4765 CLog::Log(LOGERROR
, "{}({},{}) failed", __FUNCTION__
, media_id
, media_type
);
4769 void CVideoDatabase::GetRatings(int media_id
, const std::string
&media_type
, RatingMap
&ratings
)
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());
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());
4789 CLog::Log(LOGERROR
, "{}({},{}) failed", __FUNCTION__
, media_id
, media_type
);
4793 void CVideoDatabase::GetUniqueIDs(int media_id
, const std::string
&media_type
, CVideoInfoTag
& details
)
4802 std::string sql
= PrepareSQL("SELECT type, value FROM uniqueid WHERE media_id = %i AND media_type = '%s'", media_id
, media_type
.c_str());
4804 while (!m_pDS2
->eof())
4806 details
.SetUniqueID(m_pDS2
->fv(1).get_asString(), m_pDS2
->fv(0).get_asString());
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
)
4836 if (nullptr == m_pDS
)
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();
4879 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
4897 if (nullptr == m_pDS
)
4901 std::string strSQL
= PrepareSQL("select * from settings where idFile=%i", idFile
);
4902 m_pDS
->query( strSQL
);
4903 if (m_pDS
->num_rows() > 0)
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="
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
);
4933 m_pDS
->exec(strSQL
);
4939 strSQL
= "INSERT INTO settings (idFile,Deinterlace,ViewMode,ZoomAmount,PixelRatio, VerticalShift, "
4940 "AudioStream,SubtitleStream,SubtitleDelay,SubtitlesOn,Brightness,"
4941 "Contrast,Gamma,VolumeAmplification,AudioDelay,"
4943 "Sharpness,NoiseReduction,NonLinStretch,PostProcess,ScalingMethod,StereoMode,StereoInvert,VideoStream,TonemapMethod,TonemapParam,Orientation,CenterMixLevel) "
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
);
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
)
4982 if (nullptr == m_pDS
)
4985 // don't set <foo>.<bar> art types - these are derivative types from parent items
4986 if (artType
.find('.') != std::string::npos
)
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());
4993 int artId
= m_pDS
->fv(0).get_asInt();
4994 std::string oldUrl
= m_pDS
->fv(1).get_asString();
4998 sql
= PrepareSQL("UPDATE art SET url='%s' where art_id=%d", url
.c_str(), artId
);
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());
5011 CLog::Log(LOGERROR
, "{}({}, '{}', '{}', '{}') failed", __FUNCTION__
, mediaId
, mediaType
,
5016 bool CVideoDatabase::GetArtForItem(int mediaId
, const MediaType
&mediaType
, std::map
<std::string
, std::string
> &art
)
5020 if (nullptr == m_pDB
)
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());
5028 while (!m_pDS2
->eof())
5030 art
.insert(make_pair(m_pDS2
->fv(0).get_asString(), m_pDS2
->fv(1).get_asString()));
5034 return !art
.empty();
5038 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, mediaId
);
5043 bool CVideoDatabase::GetArtForAsset(int assetId
,
5044 ArtFallbackOptions fallback
,
5045 std::map
<std::string
, std::string
>& art
)
5049 if (nullptr == m_pDB
)
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 "
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 "
5063 " JOIN videoversion as vv "
5064 " ON art.media_id = vv.idMedia AND art.media_type = vv.media_type "
5065 "WHERE idFile = %i",
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()));
5084 return !art
.empty();
5088 CLog::LogF(LOGERROR
, "retrieval failed ({})", assetId
);
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
)
5107 for (const auto &i
: artTypes
)
5108 result
&= RemoveArtForItem(mediaId
, mediaType
, i
);
5113 bool CVideoDatabase::HasArtForItem(int mediaId
, const MediaType
&mediaType
)
5117 if (nullptr == m_pDB
)
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());
5124 bool result
= !m_pDS2
->eof();
5130 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, mediaId
);
5135 bool CVideoDatabase::GetTvShowSeasons(int showId
, std::map
<int, int> &seasons
)
5139 if (nullptr == m_pDB
)
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
);
5149 while (!m_pDS2
->eof())
5151 seasons
.insert(std::make_pair(m_pDS2
->fv(1).get_asInt(), m_pDS2
->fv(0).get_asInt()));
5159 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, showId
);
5164 bool CVideoDatabase::GetTvShowNamedSeasons(int showId
, std::map
<int, std::string
> &seasons
)
5168 if (nullptr == m_pDB
)
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
);
5178 while (!m_pDS2
->eof())
5180 seasons
.insert(std::make_pair(m_pDS2
->fv(0).get_asInt(), m_pDS2
->fv(1).get_asString()));
5188 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, showId
);
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
)
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
));
5221 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, showId
);
5226 bool CVideoDatabase::GetArtTypes(const MediaType
&mediaType
, std::vector
<std::string
> &artTypes
)
5230 if (nullptr == m_pDB
)
5232 if (nullptr == m_pDS
)
5235 std::string sql
= PrepareSQL("SELECT DISTINCT type FROM art WHERE media_type='%s'", mediaType
.c_str());
5236 int numRows
= RunQuery(sql
);
5238 return numRows
== 0;
5240 while (!m_pDS
->eof())
5242 artTypes
.emplace_back(m_pDS
->fv(0).get_asString());
5250 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, mediaType
);
5257 std::vector
<std::string
> GetBasicItemAvailableArtTypes(int mediaId
,
5258 VideoDbContentType dbType
,
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");
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
);
5286 std::vector
<std::string
> GetSeasonAvailableArtTypes(int mediaId
, CVideoDatabase
& db
)
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())
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
);
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."))
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
);
5336 std::vector
<CScraperUrl::SUrlEntry
> GetBasicItemAvailableArt(int mediaId
,
5337 VideoDbContentType dbType
,
5338 const std::string
& artType
,
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
);
5371 std::vector
<CScraperUrl::SUrlEntry
> GetSeasonAvailableArt(
5372 int mediaId
, const std::string
& artType
, CVideoDatabase
& db
)
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
);
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
);
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
;
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);
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);
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> ×
)
5475 // obtain the FileID (if it exists)
5476 int idFile
= GetFileId(filePath
);
5477 if (idFile
< 0) return false;
5478 if (nullptr == m_pDB
)
5480 if (nullptr == m_pDS
)
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(), ",");
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
;
5497 return (timeTotal
> 0);
5503 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
5508 /// \brief Sets the stack times for a particular video file
5509 void CVideoDatabase::SetStackTimes(const std::string
& filePath
, const std::vector
<uint64_t> ×
)
5513 if (nullptr == m_pDB
)
5515 if (nullptr == m_pDS
)
5517 int idFile
= AddFile(filePath
);
5521 // delete any existing items
5522 m_pDS
->exec( PrepareSQL("delete from stacktimes where idFile=%i", idFile
) );
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()) );
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
)
5552 if (nullptr == m_pDS
)
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);
5563 progress
->ShowProgressBar(true);
5565 std::vector
<std::pair
<int, std::string
> > paths
;
5566 GetSubPaths(strPath
, paths
);
5568 for (const auto &i
: paths
)
5570 bool bMvidsChecked
=false;
5573 progress
->SetPercentage((int)((float)(iCurr
++)/paths
.size()*100.f
));
5574 progress
->Progress();
5577 const auto tvshowId
= GetTvShowId(i
.second
);
5579 DeleteTvShow(tvshowId
);
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
);
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
);
5597 DeleteMovie(movieId
);
5600 const auto musicvideoId
= GetMusicVideoId(strMoviePath
);
5601 if (musicvideoId
> 0)
5602 DeleteMusicVideo(musicvideoId
);
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;
5613 m_pDS2
->exec(PrepareSQL("update path set strContent='', strScraper='', strHash='',strSettings='',useFolderNames=0,scanRecursive=0 where idPath=%i", i
.first
));
5619 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strPath
);
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
);
5641 if (nullptr == m_pDB
)
5643 if (nullptr == m_pDS
)
5646 int idPath
= AddPath(filePath
);
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
);
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
);
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
);
5679 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, filePath
);
5683 bool CVideoDatabase::ScraperInUse(const std::string
&scraperID
) const
5687 if (nullptr == m_pDB
)
5689 if (nullptr == m_pDS
)
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)
5695 bool found
= m_pDS
->fv(0).get_asInt() > 0;
5701 CLog::Log(LOGERROR
, "{}({}) failed", __FUNCTION__
, scraperID
);
5709 CArtItem() { art_id
= 0; media_id
= 0; };
5711 std::string art_type
;
5712 std::string art_url
;
5714 std::string media_type
;
5717 // used for database update to v83
5721 bool operator==(const CShowItem
&r
) const
5723 return (!ident
.empty() && ident
== r
.ident
) || (title
== r
.title
&& year
== r
.year
);
5732 // used for database update to v84
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
5747 m_pDS
->exec("ALTER TABLE settings ADD StereoMode integer");
5748 m_pDS
->exec("ALTER TABLE settings ADD StereoInvert bool");
5751 m_pDS
->exec("ALTER TABLE streamdetails ADD strStereoMode text");
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()));
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
));
5775 // drop parent path id and basePath from tvshow table
5776 m_pDS
->exec("UPDATE tvshow SET c16=NULL,c17=NULL");
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
);
5783 std::vector
<CShowItem
> shows
;
5784 while (!m_pDS
->eof())
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
));
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
);
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
));
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)");
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())
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
));
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
));
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://%'");
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)");
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");
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");
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");
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");
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");
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");
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");
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");
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)
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
;
5973 m_pDS
->exec(PrepareSQL("UPDATE %s SET name = TRIM(name)",
5976 // shrink name to length 255
5977 m_pDS
->exec(PrepareSQL("UPDATE %s SET name = SUBSTR(name, 1, 255) WHERE LENGTH(name) > 255",
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()));
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();
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");
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",
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()));
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");
6069 m_pDS
->exec("ALTER TABLE sets ADD strOverview TEXT");
6072 m_pDS
->exec("ALTER TABLE seasons ADD name text");
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 "
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()));
6094 m_pDS
->exec("ALTER TABLE seasons ADD userrating INTEGER");
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
);
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()));
6115 sql
= PrepareSQL("SELECT DISTINCT idShow, c%02d, c%02d FROM tvshow", VIDEODB_ID_TV_RATING_ID
, VIDEODB_ID_TV_VOTES
);
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()));
6130 sql
= PrepareSQL("SELECT DISTINCT idEpisode, c%02d, c%02d FROM episode", VIDEODB_ID_EPISODE_RATING_ID
, VIDEODB_ID_EPISODE_VOTES
);
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()));
6148 m_pDS
->exec("ALTER TABLE settings ADD VideoStream integer");
6149 m_pDS
->exec("ALTER TABLE streamdetails ADD strVideoLanguage text");
6154 m_pDS
->exec("ALTER TABLE tvshow ADD duration INTEGER");
6156 std::string sql
= PrepareSQL( "SELECT episode.idShow, MAX(episode.c%02d) "
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
);
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()));
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
));
6186 // need this due to the nested GetScraperPath query
6187 std::unique_ptr
<Dataset
> pDS
;
6188 pDS
.reset(m_pDB
->CreateDataset());
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
;
6201 mediatype
= "movie";
6202 columnID
= "idMovie";
6203 columnUniqueID
= VIDEODB_ID_IDENT_ID
;
6206 mediatype
= "tvshow";
6207 columnID
= "idShow";
6208 columnUniqueID
= VIDEODB_ID_TV_IDENT_ID
;
6211 mediatype
= "episode";
6212 columnID
= "idEpisode";
6213 columnUniqueID
= VIDEODB_ID_EPISODE_IDENT_ID
;
6218 pDS
->query(PrepareSQL("SELECT %s, c%02d FROM %s", columnID
.c_str(), columnUniqueID
, mediatype
.c_str()));
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()));
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
));
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");
6252 m_pDS
->exec("ALTER TABLE settings ADD TonemapMethod integer");
6253 m_pDS
->exec("ALTER TABLE settings ADD TonemapParam float");
6257 m_pDS
->exec("ALTER TABLE settings ADD Orientation integer");
6260 m_pDS
->exec("ALTER TABLE settings ADD CenterMixLevel integer");
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())
6274 m_pDS2
->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", path
.c_str()));
6276 pathid
= m_pDS2
->fv(0).get_asInt();
6280 std::string parent
= URIUtils::GetParentPath(path
);
6282 m_pDS2
->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", parent
.c_str()));
6284 parentid
= m_pDS2
->fv(0).get_asInt();
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();
6298 m_pDS2
->exec(PrepareSQL("DELETE FROM files WHERE idFile=%i", m_pDS
->fv(0).get_asInt()));
6300 m_pDS2
->exec(PrepareSQL("UPDATE files SET idPath=%i WHERE idFile=%i", pathid
, m_pDS
->fv(0).get_asInt()));
6308 m_pDS
->exec("ALTER TABLE path ADD allAudio bool");
6311 m_pDS
->exec("ALTER TABLE streamdetails ADD strHdrType text");
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()));
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
));
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
));
6374 // system type used for an extra. copy as extras item type.
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())};
6388 PrepareSQL("UPDATE videoversion SET idType = %i WHERE itemType = 1 AND idType = %i",
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) "
6417 "AND itemType = %i",
6418 VideoAssetTypeOwner::USER
, VideoAssetType::VERSION
));
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 "
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())};
6448 PrepareSQL("UPDATE videoversion SET idType = %i WHERE idType = %i", newId
, typeId
));
6450 m_pDS2
->exec(PrepareSQL("DELETE FROM videoversiontype WHERE id = %i", typeId
));
6457 int CVideoDatabase::GetSchemaVersion() const
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
);
6480 for(unsigned i
=0;i
<paths
.size();i
++)
6481 ret
|= GetPlayCounts(paths
[i
], items
);
6486 if (!URIUtils::IsPlugin(strPath
))
6488 pathID
= GetPathId(strPath
);
6490 return false; // path (and thus files) aren't in the database
6496 if (nullptr == m_pDB
)
6498 if (nullptr == m_pDS
)
6503 " files.strFilename, files.playCount,"
6504 " bookmark.timeInSeconds, bookmark.totalTimeInSeconds "
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())
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()));
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(), "");
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)
6541 items
.SetFastLookup(true); // note: it's possibly quicker the other way around (map on db returned items)?
6542 while (!m_pDS
->eof())
6545 ConstructPath(path
, strPath
, m_pDS
->fv(0).get_asString());
6546 CFileItemPtr item
= items
.Get(path
);
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(), "");
6565 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
6570 int CVideoDatabase::GetPlayCount(int iFileId
)
6573 return 0; // not in db, so not watched
6578 if (nullptr == m_pDB
)
6580 if (nullptr == m_pDS
)
6583 std::string strSQL
= PrepareSQL("select playCount from files WHERE idFile=%i", iFileId
);
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();
6596 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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()));
6611 return GetPlayCount(GetFileId(item
));
6614 CDateTime
CVideoDatabase::GetLastPlayed(int iFileId
)
6617 return {}; // not in db, so not watched
6622 if (nullptr == m_pDB
)
6624 if (nullptr == m_pDS
)
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());
6640 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
6654 if (nullptr == m_pDS
)
6656 if (!item
.HasVideoInfoTag() || item
.GetVideoInfoTag()->m_iDbId
< 0) return;
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
);
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
);
6675 CLog::Log(LOGERROR
, "{} - error updating fanart for {}", __FUNCTION__
, item
.GetPath());
6679 CDateTime
CVideoDatabase::SetPlayCount(const CFileItem
& item
, int count
, const CDateTime
& date
)
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
);
6696 // and mark as watched
6699 const CDateTime
lastPlayed(date
.IsValid() ? date
: CDateTime::GetCurrentDateTime());
6701 if (nullptr == m_pDB
)
6703 if (nullptr == m_pDS
)
6709 strSQL
= PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", count
,
6710 lastPlayed
.GetAsDBDateTime().c_str(), id
);
6714 if (!date
.IsValid())
6715 strSQL
= PrepareSQL("update files set playCount=NULL,lastPlayed=NULL where idFile=%i", id
);
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)
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
);
6740 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
6764 if (nullptr == m_pDS
)
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
);
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
)
6817 if (iType
!= VideoDbContentType::MOVIES
&& iType
!= VideoDbContentType::TVSHOWS
)
6820 std::string content
= MediaTypeMovie
;
6821 if (iType
== VideoDbContentType::TVSHOWS
)
6822 content
= MediaTypeTvShow
;
6824 if (SetSingleValue(iType
, idDb
, FieldSortTitle
, strNewSortTitle
))
6826 AnnounceUpdate(content
, idDb
);
6833 "{} (int idDb, const std::string& strNewSortTitle, VIDEODB_CONTENT_TYPE iType) "
6834 "failed on ID: {} and Sort Title: {}",
6835 __FUNCTION__
, idDb
, strNewSortTitle
);
6841 /// \brief EraseVideoSettings() Erases the videoSettings table and reconstructs it
6842 void CVideoDatabase::EraseVideoSettings(const CFileItem
&item
)
6844 int idFile
= GetFileId(item
);
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()));
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");
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());
6885 while (!m_pDS
->eof())
6887 std::string file
= m_pDS
->fv("files.idFile").get_asString() + ",";
6888 itemsToDelete
+= file
;
6893 if (!itemsToDelete
.empty())
6895 itemsToDelete
= "(" + StringUtils::TrimRight(itemsToDelete
, ",") + ")";
6897 sql
= "DELETE FROM settings WHERE idFile IN " + itemsToDelete
;
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
,
6937 VideoDbContentType idContent
/* = UNKNOWN */,
6938 const Filter
& filter
/* = Filter() */,
6939 bool countOnly
/* = false */)
6943 if (nullptr == m_pDB
)
6945 if (nullptr == m_pDS
)
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
;
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";
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
);
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
;
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());
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
));
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
))
7038 int iRowsFound
= RunQuery(strSQL
);
7039 if (iRowsFound
<= 0)
7040 return iRowsFound
== 0;
7044 CFileItemPtr
pItem(new CFileItem());
7045 pItem
->SetProperty("total", iRowsFound
== 1 ? m_pDS
->fv(0).get_asInt() : iRowsFound
);
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())
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)));
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);
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);
7128 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
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
)
7160 if (nullptr == m_pDS
)
7163 CVideoDbUrl videoUrl
;
7164 if (!videoUrl
.FromString(strBaseDir
))
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
))
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
;
7188 groupingAttributes
=
7189 ignoreSingleMovieSets
? GroupAttributeIgnoreSingleItems
: GroupAttributeNone
;
7192 if (!GroupUtils::Group(GroupBySet
, strBaseDir
, items
, sets
, groupingAttributes
))
7202 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
7213 if (nullptr == m_pDS
)
7216 CVideoDbUrl videoUrl
;
7217 if (!videoUrl
.FromString(strBaseDir
))
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
));
7236 videoUrl
.AddOption("artistid", idArtist
);
7238 extFilter
.AppendGroup(PrepareSQL(" CASE WHEN musicvideo_view.c09 !='' THEN musicvideo_view.c09 "
7239 "ELSE musicvideo_view.c00 END"));
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
))
7252 int iRowsFound
= RunQuery(strSQL
);
7253 /* fields returned by query are :-
7254 (0) - Album title (if any)
7257 (3) - Music video title
7258 (4) - Music video plot
7259 (5) - Music Video artist
7262 if (iRowsFound
<= 0)
7263 return iRowsFound
== 0;
7265 std::string strArtist
;
7267 strArtist
= m_pDS
->fv("actor.name").get_asString();
7271 CFileItemPtr
pItem(new CFileItem());
7272 pItem
->SetProperty("total", iRowsFound
== 1 ? m_pDS
->fv(0).get_asInt() : iRowsFound
);
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
7293 CFileItemPtr
pItem(new CFileItem(strAlbum
));
7295 CVideoDbUrl itemUrl
= videoUrl
;
7296 std::string path
= StringUtils::Format("{}/", idMVideo
);
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'",
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());
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
;
7326 idMVideoList
.push_back(idMVideo
);
7327 idData
.emplace_back(m_pDS
->fv(0).get_asString(), m_pDS
->fv(5).get_asString());
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();
7354 GetMusicVideoInfo("", details
, idMVideoList
.front());
7355 items
[i
]->SetFromVideoInfoTag(details
);
7356 idMVideoList
.pop_front();
7361 if (!strArtist
.empty())
7362 items
.SetProperty("customtitle",strArtist
); // change displayed path from eg /23 to /Artist
7368 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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");
7405 pItem
->SetArt("icon", "DefaultActor.png");
7412 bool CVideoDatabase::GetPeopleNav(const std::string
& strBaseDir
,
7413 CFileItemList
& items
,
7415 VideoDbContentType idContent
/* = UNKNOWN */,
7416 const Filter
& filter
/* = Filter() */,
7417 bool countOnly
/* = false */)
7419 if (nullptr == m_pDB
)
7421 if (nullptr == m_pDS
)
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
7433 // General routine that the other actor/director/writer routines call
7435 // get primary genres for movies
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
;
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
)
7477 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
7478 group
= "actor.actor_id";
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
);
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
;
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
)
7534 PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
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");
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
))
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)
7581 CFileItemPtr
pItem(new CFileItem());
7582 pItem
->SetProperty("total", iRowsFound
== 1 ? m_pDS
->fv(0).get_asInt() : iRowsFound
);
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();
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())
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
++;
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
);
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
);
7680 CLog::Log(LOGERROR
, "{}: out of memory - retrieved {} items", __FUNCTION__
, items
.Size());
7681 return items
.Size() > 0;
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__
,
7698 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
7712 if (nullptr == m_pDS
)
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");
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");
7761 CVideoDbUrl videoUrl
;
7762 if (!BuildSQL(strBaseDir
, strSQL
, extFilter
, strSQL
, videoUrl
))
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())
7775 std::string dateString
= m_pDS
->fv(0).get_asString();
7776 if (dateString
.size() == 4)
7777 lYear
= m_pDS
->fv(0).get_asInt();
7781 time
.SetFromDateString(dateString
);
7783 lYear
= time
.GetYear();
7785 auto it
= mapYears
.find(lYear
);
7786 if (it
== mapYears
.end())
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())));
7796 mapYears
.insert(std::pair
<int, std::pair
<std::string
,int> >(lYear
, std::pair
<std::string
,int>(year
,0)));
7803 for (const auto &i
: mapYears
)
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
);
7822 while (!m_pDS
->eof())
7825 std::string strLabel
= m_pDS
->fv(0).get_asString();
7826 if (strLabel
.size() == 4)
7827 lYear
= m_pDS
->fv(0).get_asInt();
7831 time
.SetFromDateString(strLabel
);
7834 lYear
= time
.GetYear();
7835 strLabel
= std::to_string(lYear
);
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()))
7871 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
))
7884 videoUrl
.AddOption("tvshowid", idShow
);
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))
7897 // now add any linked movies
7898 if (getLinkedMovies
&& idShow
!= -1)
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
);
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
)
7919 if (nullptr == m_pDS
)
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
))
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
)
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())
7972 strLabel
= g_localizeStrings
.Get(20381);
7974 strLabel
= StringUtils::Format(g_localizeStrings
.Get(20358), iSeason
);
7976 CFileItemPtr
pItem(new CFileItem(strLabel
));
7978 CVideoDbUrl itemUrl
= videoUrl
;
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
;
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);
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
);
8044 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
8054 if (mediaType
!= MediaTypeMovie
&& mediaType
!= MediaTypeTvShow
&& mediaType
!= MediaTypeEpisode
&& mediaType
!= MediaTypeMusicVideo
)
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
);
8078 items
.SetContent(CMediaTypes::ToPlural(mediaType
));
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
))
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
;
8101 contentType
= VideoDbContentType::TVSHOWS
;
8103 else if (StringUtils::EqualsNoCase(mediaType
, "musicvideos"))
8104 contentType
= VideoDbContentType::MUSICVIDEOS
;
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
);
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
);
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
))
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
);
8217 videoUrl
.AddOption("setid", idSet
);
8219 videoUrl
.AddOption("tagid", idTag
);
8222 return GetMoviesByWhere(videoUrl
.ToString(), filter
, items
, sortDescription
, getDetails
);
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
)};
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
)
8248 if (nullptr == m_pDS
)
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
))
8260 std::string strSQL
= "select %s from movie_view ";
8261 std::string strSQLExtra
;
8262 if (!CDatabase::BuildSQL(strSQLExtra
, extFilter
, strSQLExtra
))
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
)
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
))
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
));
8308 CVideoDbUrl itemUrl
{videoUrl
};
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);
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
);
8344 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
))
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
);
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
)
8382 if (nullptr == m_pDS
)
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
))
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
)
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
))
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
);
8456 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
))
8469 videoUrl
.AddOption("tvshowid", idShow
);
8471 videoUrl
.AddOption("season", idSeason
);
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
);
8487 bool ret
= GetEpisodesByWhere(videoUrl
.ToString(), filter
, items
, false, sortDescription
, getDetails
);
8489 if (idSeason
== -1 && idShow
!= -1)
8490 { // add any linked movies
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
);
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
)
8510 if (nullptr == m_pDS
)
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
))
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
)
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
))
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
;
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
);
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
;
8594 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
))
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
);
8618 videoUrl
.AddOption("albumid", idAlbum
);
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 */)
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 */)
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 */)
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 */)
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
)
8707 if (nullptr == m_pDS
)
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);
8720 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
8725 int CVideoDatabase::GetTvShowForEpisode(int idEpisode
)
8729 if (nullptr == m_pDB
)
8731 if (nullptr == m_pDS2
)
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
);
8740 result
=m_pDS2
->fv(0).get_asInt();
8747 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, idEpisode
);
8752 int CVideoDatabase::GetSeasonForEpisode(int idEpisode
)
8755 snprintf(column
, sizeof(column
), "c%0d", VIDEODB_ID_EPISODE_SEASON
);
8756 std::string id
= GetSingleValue("episode", column
, PrepareSQL("idEpisode=%i", idEpisode
));
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
)
8775 if (nullptr == m_pDS
)
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
);
8788 result
= (m_pDS
->fv(0).get_asInt() > 0);
8794 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
)
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();
8820 std::string strPath2
;
8822 if (URIUtils::IsMultiPath(strPath
))
8823 strPath2
= CMultiPathDirectory::GetFirstPath(strPath
);
8827 std::string strPath1
= URIUtils::GetDirectory(strPath2
);
8828 int idPath
= GetPathId(strPath1
);
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",
8836 m_pDS
->query( strSQL
);
8840 CONTENT_TYPE content
= CONTENT_NONE
;
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;
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();
8864 if (!scraperID
.empty() &&
8865 CServiceBroker::GetAddonMgr().GetAddon(scraperID
, addon
, ADDON::OnlyEnabled::CHOICE_YES
))
8867 scraper
= std::dynamic_pointer_cast
<CScraper
>(addon
);
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
))
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'",
8892 m_pDS
->query(strSQL
);
8894 CONTENT_TYPE content
= CONTENT_NONE
;
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;
8908 content
= TranslateContent(strcontent
);
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;
8924 strPath1
= strParent
;
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);
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);
8957 return ScraperPtr();
8959 foundDirectly
= (iFound
== 1);
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",
8975 m_pDS
->query(strSQL
);
8978 return m_pDS
->fv("allAudio").get_asBool();
8983 std::string
CVideoDatabase::GetContentForPath(const std::string
& strPath
)
8985 SScanSettings settings
;
8986 bool foundDirectly
= false;
8987 ScraperPtr scraper
= GetScraperForPath(strPath
, settings
, foundDirectly
);
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 ";
8999 sql
+= PrepareSQL("WHERE strPath = '%s'", strPath
.c_str());
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)
9006 return foundDirectly
? "tvshows" : "seasons";
9008 return TranslateContent(scraper
->Content());
9013 void CVideoDatabase::GetMovieGenresByName(const std::string
& strSearch
, CFileItemList
& items
)
9019 if (nullptr == m_pDB
)
9021 if (nullptr == m_pDS
)
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());
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")))
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;
9051 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9055 void CVideoDatabase::GetMovieCountriesByName(const std::string
& strSearch
, CFileItemList
& items
)
9061 if (nullptr == m_pDB
)
9063 if (nullptr == m_pDS
)
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());
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")))
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;
9093 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9097 void CVideoDatabase::GetTvShowGenresByName(const std::string
& strSearch
, CFileItemList
& items
)
9103 if (nullptr == m_pDB
)
9105 if (nullptr == m_pDS
)
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());
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")))
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;
9134 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9138 void CVideoDatabase::GetMovieActorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9144 if (nullptr == m_pDB
)
9146 if (nullptr == m_pDS
)
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());
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")))
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;
9175 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9179 void CVideoDatabase::GetTvShowsActorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9185 if (nullptr == m_pDB
)
9187 if (nullptr == m_pDS
)
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());
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")))
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;
9216 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9220 void CVideoDatabase::GetMusicVideoArtistsByName(const std::string
& strSearch
, CFileItemList
& items
)
9226 if (nullptr == m_pDB
)
9228 if (nullptr == m_pDS
)
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());
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")))
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;
9260 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9264 void CVideoDatabase::GetMusicVideoGenresByName(const std::string
& strSearch
, CFileItemList
& items
)
9270 if (nullptr == m_pDB
)
9272 if (nullptr == m_pDS
)
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());
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")))
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;
9301 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9305 void CVideoDatabase::GetMusicVideoAlbumsByName(const std::string
& strSearch
, CFileItemList
& items
)
9311 if (nullptr == m_pDB
)
9313 if (nullptr == m_pDS
)
9316 strSQL
= StringUtils::Format("SELECT DISTINCT"
9317 " musicvideo.c{:02},"
9318 " musicvideo.idMVideo,"
9323 " files.idFile=musicvideo.idFile"
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())
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")))
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;
9358 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9362 void CVideoDatabase::GetMusicVideosByAlbum(const std::string
& strSearch
, CFileItemList
& items
)
9368 if (nullptr == m_pDB
)
9370 if (nullptr == m_pDS
)
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());
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")))
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;
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
)
9412 if (nullptr == m_pDS
)
9417 std::string strSQL
= "select %s from musicvideo_view ";
9418 CVideoDbUrl videoUrl
;
9419 if (!videoUrl
.FromString(baseDir
))
9422 std::string strSQLExtra
;
9423 const CUrlOptions::UrlOptions
& options
= videoUrl
.GetOptions();
9424 std::string strArtist
;
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
)
9436 Filter extFilter
= filter
;
9437 SortDescription sorting
= sortDescription
;
9438 if (!BuildSQL(baseDir
, strSQLExtra
, extFilter
, strSQLExtra
, videoUrl
, sorting
))
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
)
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
))
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
);
9496 if (!strArtist
.empty())
9497 items
.SetProperty("customtitle", strArtist
);
9502 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
9507 unsigned int CVideoDatabase::GetRandomMusicVideoIDs(const std::string
& strWhere
, std::vector
<std::pair
<int,int> > &songIDs
)
9511 if (nullptr == m_pDB
)
9513 if (nullptr == m_pDS
)
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;
9523 if (m_pDS
->num_rows() == 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()));
9535 return songIDs
.size();
9539 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strWhere
);
9544 int CVideoDatabase::GetMatchingMusicVideo(const std::string
& strArtist
, const std::string
& strAlbum
, const std::string
& strTitle
)
9548 if (nullptr == m_pDB
)
9550 if (nullptr == m_pDS
)
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());
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());
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());
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
);
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")))
9580 int lResult
= m_pDS
->fv(0).get_asInt();
9586 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
9591 void CVideoDatabase::GetMoviesByName(const std::string
& strSearch
, CFileItemList
& items
)
9597 if (nullptr == m_pDB
)
9599 if (nullptr == m_pDS
)
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());
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")))
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()));
9629 if (setId
<= 0 || !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS
))
9630 path
= StringUtils::Format("videodb://movies/titles/{}", movieId
);
9632 path
= StringUtils::Format("videodb://movies/sets/{}/{}", setId
, movieId
);
9633 pItem
->SetPath(path
);
9634 pItem
->m_bIsFolder
=false;
9642 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9646 void CVideoDatabase::GetTvShowsByName(const std::string
& strSearch
, CFileItemList
& items
)
9652 if (nullptr == m_pDB
)
9654 if (nullptr == m_pDS
)
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());
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")))
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();
9686 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9690 void CVideoDatabase::GetEpisodesByName(const std::string
& strSearch
, CFileItemList
& items
)
9696 if (nullptr == m_pDB
)
9698 if (nullptr == m_pDS
)
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());
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")))
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;
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);
9743 if (nullptr == m_pDB
)
9745 if (nullptr == m_pDS
)
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());
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")))
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;
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
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);
9793 if (nullptr == m_pDB
)
9795 if (nullptr == m_pDS
)
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());
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")))
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;
9826 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9830 void CVideoDatabase::GetMoviesByPlot(const std::string
& strSearch
, CFileItemList
& items
)
9836 if (nullptr == m_pDB
)
9838 if (nullptr == m_pDS
)
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());
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")))
9857 CFileItemPtr
pItem(new CFileItem(m_pDS
->fv(1).get_asString()));
9859 StringUtils::Format("videodb://movies/titles/{}", m_pDS
->fv(0).get_asInt());
9860 pItem
->SetPath(path
);
9861 pItem
->m_bIsFolder
=false;
9871 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9875 void CVideoDatabase::GetMovieDirectorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9881 if (nullptr == m_pDB
)
9883 if (nullptr == m_pDS
)
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());
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")))
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;
9914 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9918 void CVideoDatabase::GetTvShowsDirectorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9924 if (nullptr == m_pDB
)
9926 if (nullptr == m_pDS
)
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());
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")))
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;
9957 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
9961 void CVideoDatabase::GetMusicVideoDirectorsByName(const std::string
& strSearch
, CFileItemList
& items
)
9967 if (nullptr == m_pDB
)
9969 if (nullptr == m_pDS
)
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());
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")))
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;
10000 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
10004 void CVideoDatabase::CleanDatabase(CGUIDialogProgressBarHandle
* handle
,
10005 const std::set
<int>& paths
,
10008 CGUIDialogProgress
* progress
= NULL
;
10011 if (nullptr == m_pDB
)
10013 if (nullptr == m_pDS
)
10015 if (nullptr == m_pDS2
)
10018 auto start
= std::chrono::steady_clock::now();
10019 CLog::Log(LOGINFO
, "{}: Starting videodatabase cleanup ..", __FUNCTION__
);
10020 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
,
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
);
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);
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();
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();
10088 if (URIUtils::IsPlugin(fullPath
))
10090 SScanSettings settings
;
10091 bool foundDirectly
= false;
10092 ScraperPtr scraper
= GetScraperForPath(fullPath
, settings
, foundDirectly
);
10094 CPluginDirectory::CheckExists(TranslateContent(scraper
->Content()), fullPath
))
10099 // Only consider keeping this file if not optical and belonging to a (matching) source
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
)
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))
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())
10135 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
,
10136 "OnCleanFinished");
10140 else if (handle
!= NULL
)
10141 handle
->SetPercentage(current
* 100 / (float)total
);
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
;
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 " +
10202 int pathHashCount
= m_pDS
->num_rows();
10203 while (!m_pDS
->eof())
10205 InvalidatePathHash(m_pDS
->fv("strPath").get_asString());
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
;
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
;
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
;
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
))
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() + ",";
10278 if (!strIds
.empty())
10280 sql
= PrepareSQL("DELETE FROM path WHERE idPath IN (%s)",
10281 StringUtils::TrimRight(strIds
, ",").c_str());
10283 sql
= "DELETE FROM tvshowlinkpath "
10284 "WHERE NOT EXISTS (SELECT 1 FROM path WHERE path.idPath = tvshowlinkpath.idPath)";
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 = "
10295 while (!m_pDS
->eof())
10297 tvshowIDs
.push_back(m_pDS
->fv(0).get_asInt());
10298 tvshowsToDelete
+= m_pDS
->fv(0).get_asString() + ",";
10302 if (!tvshowsToDelete
.empty())
10304 sql
= "DELETE FROM tvshow WHERE idShow IN (" +
10305 StringUtils::TrimRight(tvshowsToDelete
, ",") + ")";
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
;
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
);
10339 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning genre table", __FUNCTION__
);
10341 "DELETE FROM genre "
10342 "WHERE NOT EXISTS (SELECT 1 FROM genre_link WHERE genre_link.genre_id = genre.genre_id)";
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)";
10350 CLog::Log(LOGDEBUG
, LOGDATABASE
, "{}: Cleaning actor table of actors, directors and writers",
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 = "
10357 "AND NOT EXISTS (SELECT 1 FROM writer_link WHERE writer_link.actor_id = actor.actor_id)";
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)";
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)";
10371 CommitTransaction();
10374 handle
->SetTitle(g_localizeStrings
.Get(331));
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__
,
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);
10404 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
10405 RollbackTransaction();
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())
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";
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";
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(),
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())
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
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});
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
)
10527 if (scanSettings
.parent_name
)
10528 pathsDeleteDecisions
.insert(std::make_pair(m_pDS2
->fv(2).get_asInt(), del
));
10533 deletedFileIDs
+= m_pDS2
->fv(1).get_asString() + ",";
10534 cleanedIDs
.push_back(m_pDS2
->fv(0).get_asInt());
10544 void CVideoDatabase::DumpToDummyFiles(const std::string
&path
)
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
);
10567 std::string episodePath
= URIUtils::AddFileToFolder(TVFolder
, episode
);
10569 if (file
.OpenForWrite(episodePath
))
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
);
10584 if (file
.OpenForWrite(URIUtils::AddFileToFolder(moviePath
, movie
)))
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
)
10597 if (nullptr == m_pDS
)
10599 if (nullptr == m_pDS2
)
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
)
10608 std::unique_ptr
<Dataset
> pDS2
;
10609 pDS2
.reset(m_pDB
->CreateDataset());
10610 if (nullptr == pDS2
)
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");
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
);
10637 std::string sql
= "select * from movie_view";
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);
10649 progress
->ShowProgressBar(true);
10652 int total
= m_pDS
->num_rows();
10655 // create our xml document
10656 CXBMCTinyXML xmlDoc
;
10657 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10658 xmlDoc
.InsertEndChild(decl
);
10659 TiXmlNode
*pMain
= NULL
;
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
);
10684 movie
.Save(pMain
, "movie", singleFile
);
10686 // reset old skip state
10687 bool bSkip
= false;
10691 progress
->SetLine(1, CVariant
{movie
.m_strTitle
});
10692 progress
->SetPercentage(current
* 100 / total
);
10693 progress
->Progress();
10694 if (progress
->IsCanceled())
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
);
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
));
10738 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10739 xmlDoc
.InsertEndChild(decl
);
10742 if (images
&& !bSkip
)
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
);
10757 ExportActorThumbs(actorsDir
, movie
, !singleFile
, overwrite
);
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";
10774 total
= m_pDS
->num_rows();
10777 while (!m_pDS
->eof())
10779 std::string title
= m_pDS
->fv("strSet").get_asString();
10783 progress
->SetLine(1, CVariant
{title
});
10784 progress
->SetPercentage(current
* 100 / total
);
10785 progress
->Progress();
10786 if (progress
->IsCanceled())
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
);
10809 "CVideoDatabase::{} - Not exporting movie set '{}' as could not create folder '{}'",
10810 __FUNCTION__
, title
, itemPath
);
10817 // find all musicvideos
10818 sql
= "select * from musicvideo_view";
10822 total
= m_pDS
->num_rows();
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
);
10837 movie
.Save(pMain
, "musicvideo", singleFile
);
10839 // reset old skip state
10840 bool bSkip
= false;
10844 progress
->SetLine(1, CVariant
{movie
.m_strTitle
});
10845 progress
->SetPercentage(current
* 100 / total
);
10846 progress
->Progress();
10847 if (progress
->IsCanceled())
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
);
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__
,
10874 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error
,
10875 g_localizeStrings
.Get(20302),
10876 CURL::GetRedacted(nfoFile
));
10885 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10886 xmlDoc
.InsertEndChild(decl
);
10888 if (images
&& !bSkip
)
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
);
10908 // repeat for all tvshows
10909 sql
= "SELECT * FROM tvshow_view";
10912 total
= m_pDS
->num_rows();
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
);
10940 tvshow
.Save(pMain
, "tvshow", singleFile
);
10942 // reset old skip state
10943 bool bSkip
= false;
10947 progress
->SetLine(1, CVariant
{tvshow
.m_strTitle
});
10948 progress
->SetPercentage(current
* 100 / total
);
10949 progress
->Progress();
10950 if (progress
->IsCanceled())
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__
,
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
));
10987 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
10988 xmlDoc
.InsertEndChild(decl
);
10990 if (images
&& !bSkip
)
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
);
11002 ExportActorThumbs(actorsDir
, tvshow
, !singleFile
, overwrite
);
11004 // export season thumbs
11005 for (const auto &i
: seasonArt
)
11007 std::string seasonThumb
;
11009 seasonThumb
= "season-all";
11010 else if (i
.first
== 0)
11011 seasonThumb
= "season-specials";
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
);
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
);
11042 episode
.Save(pMain
, "episodedetails", singleFile
);
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
);
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
);
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
));
11085 TiXmlDeclaration
decl("1.0", "UTF-8", "yes");
11086 xmlDoc
.InsertEndChild(decl
);
11089 if (images
&& !bSkip
)
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
);
11103 ExportActorThumbs(actorsDir
, episode
, !singleFile
, overwrite
);
11112 if (!singleFile
&& progress
)
11114 progress
->SetPercentage(100);
11115 progress
->Progress();
11120 // now dump path info
11121 std::set
<std::string
> 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
);
11146 data
["root"] = exportRoot
;
11147 data
["file"] = xmlFile
;
11148 if (iFailCount
> 0)
11149 data
["failcount"] = iFailCount
;
11151 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
, "OnExport",
11156 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
);
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
)
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
)
11200 if (nullptr == m_pDS
)
11203 CXBMCTinyXML xmlDoc
;
11204 if (!xmlDoc
.LoadFile(URIUtils::AddFileToFolder(path
, "videodb.xml")))
11207 TiXmlElement
*root
= xmlDoc
.RootElement();
11210 progress
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogProgress
>(WINDOW_DIALOG_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);
11219 progress
->ShowProgressBar(true);
11223 XMLUtils::GetInt(root
, "version", iVersion
);
11225 CLog::Log(LOGINFO
, "{}: Starting import (export version = {})", __FUNCTION__
, iVersion
);
11227 TiXmlElement
*movie
= root
->FirstChildElement();
11230 // first count the number of items...
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)
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();
11251 std::string strPath
;
11252 if (XMLUtils::GetString(path
,"url",strPath
) && !strPath
.empty())
11255 std::string content
;
11256 if (XMLUtils::GetString(path
,"content", content
) && !content
.empty())
11257 { // check the scraper exists, if so store the path
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();
11277 CVideoInfoTag info
;
11278 if (StringUtils::CompareNoCase(movie
->Value(), MediaTypeMovie
, 5) == 0)
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);
11312 else if (StringUtils::CompareNoCase(movie
->Value(), MediaTypeMusicVideo
, 10) == 0)
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);
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
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);
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
);
11352 // now load the episodes
11353 TiXmlElement
*episode
= movie
->FirstChildElement("episodedetails");
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())
11386 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
;
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
;
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
,"");
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
));
11471 bool CVideoDatabase::SetSingleValue(VideoDbContentType type
,
11474 const std::string
& strValue
)
11476 std::string strSQL
;
11479 if (nullptr == m_pDB
|| nullptr == m_pDS
)
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())
11507 return SetSingleValue(strTable
, StringUtils::Format("c{:02}", dbField
), strValue
, strField
,
11512 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, strSQL
);
11517 bool CVideoDatabase::SetSingleValue(VideoDbContentType type
,
11520 const std::string
& strValue
)
11522 MediaType mediaType
= DatabaseUtils::MediaTypeFromVideoContentType(type
);
11523 if (mediaType
== MediaTypeNone
)
11526 int dbFieldIndex
= DatabaseUtils::GetField(dbField
, mediaType
);
11527 if (dbFieldIndex
< 0)
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())
11542 if (nullptr == m_pDB
|| nullptr == m_pDS
)
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)
11553 CLog::Log(LOGERROR
, "{} ({}) failed", __FUNCTION__
, sql
);
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 */)
11568 data
["type"] = content
;
11571 data
["transaction"] = true;
11572 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary
, "OnRemove", data
);
11575 void CVideoDatabase::AnnounceUpdate(const std::string
& content
, int id
)
11578 data
["type"] = content
;
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
);
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())
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())
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())
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
));
11694 option
= options
.find("mediaid");
11695 if (option
!= options
.end())
11697 const int mediaId
{static_cast<int>(option
->second
.asInteger())};
11699 filter
.AppendWhere(PrepareSQL("idMovie = %i", mediaId
));
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")
11752 auto option
= options
.find("tvshowid");
11753 if (option
!= options
.end())
11754 idShow
= (int)option
->second
.asInteger();
11757 option
= options
.find("season");
11758 if (option
!= options
.end())
11759 season
= (int)option
->second
.asInteger();
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())
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
);
11782 filter
.AppendWhere(PrepareSQL("episode_view.idShow = %i", idShow
));
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
));
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
));
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
);
11848 auto option
= options
.find("xsp");
11849 if (option
!= options
.end())
11851 CSmartPlaylist xsp
;
11852 if (!xsp
.LoadFromJson(option
->second
.asString()))
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()))
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
11891 videoUrl
.RemoveOption("filter");
11897 bool CVideoDatabase::SetVideoUserRating(int dbId
, int rating
, const MediaType
& mediaType
)
11901 if (nullptr == m_pDB
)
11903 if (nullptr == m_pDS
)
11906 if (mediaType
== MediaTypeNone
)
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
);
11926 CLog::Log(LOGERROR
, "{} ({}, {}, {}) failed", __FUNCTION__
, dbId
, mediaType
, rating
);
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();
11959 void CVideoDatabase::EraseAllForPath(const std::string
& path
)
11963 std::string itemsToDelete
;
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%%\")",
11970 while (!m_pDS
->eof())
11972 std::string file
= m_pDS
->fv("files.idFile").get_asString() + ",";
11973 itemsToDelete
+= file
;
11978 sql
= PrepareSQL("DELETE FROM path WHERE strPath LIKE \"%s%%\"", path
.c_str());
11981 if (!itemsToDelete
.empty())
11983 itemsToDelete
= "(" + StringUtils::TrimRight(itemsToDelete
, ",") + ")";
11985 sql
= "DELETE FROM files WHERE idFile IN " + itemsToDelete
;
11988 sql
= "DELETE FROM settings WHERE idFile IN " + itemsToDelete
;
11991 sql
= "DELETE FROM bookmark WHERE idFile IN " + itemsToDelete
;
11994 sql
= "DELETE FROM streamdetails WHERE idFile IN " + itemsToDelete
;
12000 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
12004 std::string
CVideoDatabase::GetVideoItemTitle(VideoDbContentType itemType
, int dbId
)
12008 case VideoDbContentType::MOVIES
:
12009 return GetMovieTitle(dbId
);
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))
12027 const std::string
& type
{g_localizeStrings
.Get(id
)};
12028 if (schemaVersion
< 127)
12031 PrepareSQL("INSERT INTO videoversiontype (id, name, owner) VALUES(%i, '%s', %i)", id
,
12032 type
.c_str(), VideoAssetTypeOwner::SYSTEM
));
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();
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();
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())
12084 if (!m_pDB
|| !m_pDS
)
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());
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
));
12112 CLog::Log(LOGERROR
, "{} failed to add video version {}", __FUNCTION__
, typeVideoVersion
);
12118 void CVideoDatabase::GetVideoVersions(VideoDbContentType itemType
,
12120 CFileItemList
& items
,
12121 VideoAssetType videoAssetType
)
12123 if (!m_pDB
|| !m_pDS2
)
12126 MediaType mediaType
;
12128 if (itemType
== VideoDbContentType::MOVIES
)
12129 mediaType
= MediaTypeMovie
;
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());
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
);
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
)
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"
12223 " movie.idFile = videoversion.idFile "
12224 "WHERE movie.idMovie = %i",
12232 m_pDS
->query(strSQL
);
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
);
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()));
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
);
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
);
12295 bool CVideoDatabase::GetAssetsForVideo(VideoDbContentType itemType
,
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
);
12307 if (!m_pDB
|| !m_pDS
)
12310 MediaType mediaType
;
12312 if (itemType
== VideoDbContentType::MOVIES
)
12313 mediaType
= MediaTypeMovie
;
12316 CLog::LogF(LOGERROR
, "Unsupported item type '{}'!", static_cast<int>(itemType
));
12323 PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND videoVersionTypeItemType = %i",
12324 mediaId
, assetType
));
12328 CLog::LogF(LOGERROR
, "Query returned no data!");
12332 while (!m_pDS
->eof())
12334 const auto item
{std::make_shared
<CFileItem
>()};
12335 if (FillMovieItem(m_pDS
, mediaId
, *item
))
12344 CLog::LogF(LOGERROR
, "Execution failed for {} {}", mediaType
, mediaId
);
12350 bool CVideoDatabase::GetDefaultVersionForVideo(VideoDbContentType itemType
,
12354 if (!m_pDB
|| !m_pDS
)
12357 MediaType mediaType
;
12359 if (itemType
== VideoDbContentType::MOVIES
)
12360 mediaType
= MediaTypeMovie
;
12363 CLog::LogF(LOGERROR
, "Unsupported item type '{}'!", static_cast<int>(itemType
));
12369 m_pDS
->query(PrepareSQL("SELECT * FROM movie_view WHERE idMovie = %i AND "
12370 "videoVersionTypeItemType = %i AND isDefaultVersion = 1",
12371 mediaId
, VideoAssetType::VERSION
));
12374 CLog::LogF(LOGERROR
, "Query returned no data!");
12378 if (!FillMovieItem(m_pDS
, mediaId
, item
))
12383 CLog::LogF(LOGERROR
, "Execution failed for {} {}", mediaType
, mediaId
);
12389 bool CVideoDatabase::ConvertVideoToVersion(VideoDbContentType itemType
,
12392 int idVideoVersion
,
12393 VideoAssetType assetType
)
12396 MediaType mediaType
;
12397 VideoContentTypeToString(itemType
, mediaType
);
12399 if (itemType
== VideoDbContentType::MOVIES
)
12401 idFile
= GetFileIdByMovie(dbIdSource
);
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();
12433 void CVideoDatabase::SetDefaultVideoVersion(VideoDbContentType itemType
, int dbId
, int idFile
)
12435 if (!m_pDB
|| !m_pDS
)
12438 std::string path
= GetFileBasePathById(idFile
);
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
));
12450 CLog::Log(LOGERROR
, "{} failed for video {}", __FUNCTION__
, dbId
);
12454 bool CVideoDatabase::IsDefaultVideoVersion(int idFile
)
12456 if (!m_pDB
|| !m_pDS
)
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
)
12481 CLog::Log(LOGERROR
, "{} failed for {}", __FUNCTION__
, idFile
);
12487 bool CVideoDatabase::DeleteVideoAsset(int idFile
)
12489 if (!m_pDB
|| !m_pDS
)
12492 if (IsDefaultVideoVersion(idFile
))
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",
12506 InvalidatePathHash(path
);
12508 m_pDS
->exec(PrepareSQL("DELETE FROM videoversion WHERE idFile=%i", idFile
));
12510 if (!inTransaction
)
12511 CommitTransaction();
12517 CLog::LogF(LOGERROR
, "failed for {}", idFile
);
12518 if (!inTransaction
)
12519 RollbackTransaction();
12524 void CVideoDatabase::SetVideoVersion(int idFile
, int idVideoVersion
)
12526 if (!m_pDB
|| !m_pDS
)
12531 m_pDS
->exec(PrepareSQL("UPDATE videoversion SET idType = %i WHERE idFile = %i", idVideoVersion
,
12536 CLog::Log(LOGERROR
, "{} failed for video {}", __FUNCTION__
, idFile
);
12540 void CVideoDatabase::AddVideoAsset(VideoDbContentType itemType
,
12542 int idVideoVersion
,
12543 VideoAssetType videoAssetType
,
12546 if (!m_pDB
|| !m_pDS
)
12549 assert(m_pDB
->in_transaction() == false);
12551 MediaType mediaType
;
12552 if (itemType
== VideoDbContentType::MOVIES
)
12554 mediaType
= MediaTypeMovie
;
12559 int idFile
= AddFile(item
.GetPath());
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
));
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();
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)
12600 if (!m_pDB
|| !m_pDS
)
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",
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());
12629 CLog::LogF(LOGERROR
, "failed for {}", filenameAndPath
);
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
)
12643 MediaType mediaType
;
12645 if (idContent
== VideoDbContentType::MOVIES
)
12647 mediaType
= MediaTypeMovie
;
12652 CVideoDbUrl videoUrl
;
12653 if (!videoUrl
.FromString(strBaseDir
))
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
;
12688 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
12693 bool CVideoDatabase::GetVideoVersionTypes(VideoDbContentType idContent
,
12694 VideoAssetType assetType
,
12695 CFileItemList
& items
)
12697 if (!m_pDB
|| !m_pDS
)
12700 MediaType mediaType
;
12702 if (idContent
== VideoDbContentType::MOVIES
)
12704 mediaType
= MediaTypeMovie
;
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
);
12739 CLog::Log(LOGERROR
, "{} failed", __FUNCTION__
);
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
);