From 351c9a6a87df18a6048ee8da541cde2efb1ce6f0 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Thu, 6 Feb 2025 00:53:22 +0100 Subject: wip: use sqlite3 for playlists --- src/lib/config.cc | 12 +- src/lib/config.h | 20 +-- src/lib/content_store.cc | 132 --------------- src/lib/content_store.h | 53 ------ src/lib/http_server.cc | 71 +++----- src/lib/show_playlist.cc | 33 ++++ src/lib/show_playlist.h | 75 +++++++++ src/lib/show_playlist_content_store.cc | 133 +++++++++++++++ src/lib/show_playlist_content_store.h | 54 ++++++ src/lib/show_playlist_entry.cc | 103 ++++++++++++ src/lib/show_playlist_entry.h | 66 ++++++++ src/lib/show_playlist_list.cc | 223 +++++++++++++++++++++++++ src/lib/show_playlist_list.h | 83 ++++++++++ src/lib/spl.cc | 107 ------------ src/lib/spl.h | 156 ------------------ src/lib/spl_entry.cc | 75 --------- src/lib/spl_entry.h | 61 ------- src/lib/wscript | 7 +- src/tools/dcpomatic_player.cc | 3 +- src/tools/dcpomatic_playlist.cc | 293 ++++++++++++++++++--------------- src/wx/config_dialog.cc | 19 ++- src/wx/config_dialog.h | 4 +- src/wx/content_view.cc | 41 ++--- src/wx/content_view.h | 12 +- src/wx/playlist_controls.cc | 120 +++++++------- src/wx/playlist_controls.h | 9 +- src/wx/wx_util.cc | 6 +- src/wx/wx_util.h | 2 +- 28 files changed, 1080 insertions(+), 893 deletions(-) delete mode 100644 src/lib/content_store.cc delete mode 100644 src/lib/content_store.h create mode 100644 src/lib/show_playlist.cc create mode 100644 src/lib/show_playlist.h create mode 100644 src/lib/show_playlist_content_store.cc create mode 100644 src/lib/show_playlist_content_store.h create mode 100644 src/lib/show_playlist_entry.cc create mode 100644 src/lib/show_playlist_entry.h create mode 100644 src/lib/show_playlist_list.cc create mode 100644 src/lib/show_playlist_list.h delete mode 100644 src/lib/spl.cc delete mode 100644 src/lib/spl.h delete mode 100644 src/lib/spl_entry.cc delete mode 100644 src/lib/spl_entry.h diff --git a/src/lib/config.cc b/src/lib/config.cc index 4440aab4d..10c4e4e01 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -192,7 +192,7 @@ Config::set_defaults() _respect_kdm_validity_periods = true; _player_debug_log_file = boost::none; _player_content_directory = boost::none; - _player_playlist_directory = boost::none; + _show_playlists_file = read_path("show_playlists.sqlite3"); _player_kdm_directory = boost::none; _audio_mapping = boost::none; _custom_languages.clear(); @@ -617,7 +617,7 @@ try _respect_kdm_validity_periods = f.optional_bool_child("RespectKDMValidityPeriods").get_value_or(true); _player_debug_log_file = f.optional_string_child("PlayerDebugLogFile"); _player_content_directory = f.optional_string_child("PlayerContentDirectory"); - _player_playlist_directory = f.optional_string_child("PlayerPlaylistDirectory"); + _show_playlists_file = f.string_child("ShowPlaylistsFile"); _player_kdm_directory = f.optional_string_child("PlayerKDMDirectory"); if (f.optional_node_child("AudioMapping")) { @@ -1093,10 +1093,8 @@ Config::write_config() const /* [XML] PlayerContentDirectory Directory to use for player content in the dual-screen mode. */ cxml::add_text_child(root, "PlayerContentDirectory", _player_content_directory->string()); } - if (_player_playlist_directory) { - /* [XML] PlayerPlaylistDirectory Directory to use for player playlists in the dual-screen mode. */ - cxml::add_text_child(root, "PlayerPlaylistDirectory", _player_playlist_directory->string()); - } + /* [XML] ShowPlaylistsFile Filename of SQLite3 database containing show playlists for the player dual-screen mode */ + cxml::add_text_child(root, "ShowPlaylistsFile", _show_playlists_file.string()); if (_player_kdm_directory) { /* [XML] PlayerKDMDirectory Directory to use for player KDMs in the dual-screen mode. */ cxml::add_text_child(root, "PlayerKDMDirectory", _player_kdm_directory->string()); @@ -1697,7 +1695,7 @@ Config::load_from_zip(boost::filesystem::path zip_file, CinemasAction action) changed(Property::SOUND); changed(Property::SOUND_OUTPUT); changed(Property::PLAYER_CONTENT_DIRECTORY); - changed(Property::PLAYER_PLAYLIST_DIRECTORY); + changed(Property::SHOW_PLAYLISTS_FILE); changed(Property::PLAYER_DEBUG_LOG); changed(Property::HISTORY); changed(Property::SHOW_EXPERIMENTAL_AUDIO_PROCESSORS); diff --git a/src/lib/config.h b/src/lib/config.h index f598ffbc6..e0727fe57 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -108,7 +108,7 @@ public: SOUND, SOUND_OUTPUT, PLAYER_CONTENT_DIRECTORY, - PLAYER_PLAYLIST_DIRECTORY, + SHOW_PLAYLISTS_FILE, PLAYER_DEBUG_LOG, HISTORY, SHOW_EXPERIMENTAL_AUDIO_PROCESSORS, @@ -580,8 +580,8 @@ public: return _player_content_directory; } - boost::optional player_playlist_directory() const { - return _player_playlist_directory; + boost::filesystem::path show_playlists_file() const { + return _show_playlists_file; } boost::optional player_kdm_directory() const { @@ -1134,16 +1134,8 @@ public: changed(PLAYER_CONTENT_DIRECTORY); } - void set_player_playlist_directory(boost::filesystem::path p) { - maybe_set(_player_playlist_directory, p, PLAYER_PLAYLIST_DIRECTORY); - } - - void unset_player_playlist_directory() { - if (!_player_playlist_directory) { - return; - } - _player_playlist_directory = boost::none; - changed(PLAYER_PLAYLIST_DIRECTORY); + void set_show_playlists_file(boost::filesystem::path p) { + maybe_set(_show_playlists_file, p, SHOW_PLAYLISTS_FILE); } void set_player_kdm_directory(boost::filesystem::path p) { @@ -1462,7 +1454,7 @@ private: for playback. */ boost::optional _player_content_directory; - boost::optional _player_playlist_directory; + boost::filesystem::path _show_playlists_file; boost::optional _player_kdm_directory; boost::optional _audio_mapping; std::vector _custom_languages; diff --git a/src/lib/content_store.cc b/src/lib/content_store.cc deleted file mode 100644 index 10f3fff04..000000000 --- a/src/lib/content_store.cc +++ /dev/null @@ -1,132 +0,0 @@ -/* - Copyright (C) 2024 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - - -#include "config.h" -#include "content_factory.h" -#include "content_store.h" -#include "cross.h" -#include "dcp_content.h" -#include "examine_content_job.h" -#include "job_manager.h" -#include "util.h" -#include -#include - - -using std::make_shared; -using std::pair; -using std::shared_ptr; -using std::string; -using std::vector; - - -ContentStore* ContentStore::_instance = nullptr; - - -vector> -ContentStore::update(std::function pulse) -{ - _content.clear(); - auto dir = Config::instance()->player_content_directory(); - if (!dir || !dcp::filesystem::is_directory(*dir)) { - return {}; - } - - auto jm = JobManager::instance(); - - vector> jobs; - - for (auto i: boost::filesystem::directory_iterator(*dir)) { - try { - pulse(); - - shared_ptr content; - if (is_directory(i) && contains_assetmap(i)) { - content = make_shared(i); - } else if (i.path().extension() == ".mp4") { - auto all_content = content_factory(i); - if (!all_content.empty()) { - content = all_content[0]; - } - } - - if (content) { - auto job = make_shared(shared_ptr(), content, true); - jm->add(job); - jobs.push_back(job); - } - } catch (boost::filesystem::filesystem_error& e) { - /* Never mind */ - } catch (dcp::ReadError& e) { - /* Never mind */ - } - } - - while (jm->work_to_do()) { - if (!pulse()) { - /* user pressed cancel */ - for (auto i: jm->get()) { - i->cancel(); - } - return {}; - } - dcpomatic_sleep_seconds(1); - } - - /* Add content from successful jobs and report errors */ - vector> errors; - for (auto i: jobs) { - if (i->finished_in_error()) { - errors.push_back({String::compose("%1.\n", i->error_summary()), i->error_details()}); - } else { - _content.push_back(i->content()); - } - } - - return errors; -} - - -shared_ptr -ContentStore::get(string digest) const -{ - auto iter = std::find_if(_content.begin(), _content.end(), [digest](shared_ptr content) { - return content->digest() == digest; - }); - - if (iter == _content.end()) { - return {}; - } - - return *iter; -} - - -ContentStore* -ContentStore::instance() -{ - if (!_instance) { - _instance = new ContentStore(); - } - - return _instance; -} - diff --git a/src/lib/content_store.h b/src/lib/content_store.h deleted file mode 100644 index dcf31b5a4..000000000 --- a/src/lib/content_store.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright (C) 2018 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - - -#include -#include -#include - - -class Content; - -/** @class ContentStore - * @brief Class to maintain details of what content we have available to play - */ -class ContentStore -{ -public: - std::shared_ptr get(std::string digest) const; - - /** Examine content in the configured directory and update our list. - * @param pulse Called every so often to indicate progress. Return false to cancel the scan. - * @return Errors (first of the pair is the summary, second is the detail). - */ - std::vector> update(std::function pulse); - - static ContentStore* instance(); - - std::vector> const& all() const { - return _content; - } - -private: - std::vector> _content; - - static ContentStore* _instance; -}; diff --git a/src/lib/http_server.cc b/src/lib/http_server.cc index c69a8b496..495532704 100644 --- a/src/lib/http_server.cc +++ b/src/lib/http_server.cc @@ -20,12 +20,12 @@ #include "config.h" -#include "content_store.h" #include "cross.h" #include "dcpomatic_log.h" #include "dcpomatic_socket.h" #include "http_server.h" -#include "spl.h" +#include "show_playlist_content_store.h" +#include "show_playlist_list.h" #include "util.h" #include "variant.h" #include @@ -98,27 +98,6 @@ Response::send(shared_ptr socket) } -vector> -get_playlists() -{ - vector> playlists; - - if (auto path = Config::instance()->player_playlist_directory()) { - try { - for (auto i: dcp::filesystem::directory_iterator(*path)) { - auto spl = make_shared(); - try { - spl->read(i, ContentStore::instance()); - playlists.push_back(spl); - } catch (...) {} - } - } catch (...) {} - } - - return playlists; -} - - Response HTTPServer::get(string const& url) { @@ -138,9 +117,10 @@ HTTPServer::get(string const& url) response.set_type(Response::Type::JSON); return response; } else if (url == "/api/v1/playlists") { + ShowPlaylistList spl_list; nlohmann::json json; - for (auto spl: get_playlists()) { - json.push_back(spl->as_json_without_content()); + for (auto const& spl: spl_list.show_playlists()) { + json.push_back(spl.second.as_json()); } auto response = Response(200, json.dump()); response.set_type(Response::Type::JSON); @@ -151,18 +131,20 @@ HTTPServer::get(string const& url) if (parts.size() != 5) { return Response::ERROR_404; } - for (auto spl: get_playlists()) { - if (spl->id() == parts[4]) { - auto response = Response(200, spl->as_json_with_content().dump()); - response.set_type(Response::Type::JSON); - return response; + ShowPlaylistList spl_list; + for (auto const& spl: spl_list.show_playlists()) { + if (spl.second.uuid() == parts[4]) { + // XXX + // auto response = Response(200, spl->as_json_with_content().dump()); + // response.set_type(Response::Type::JSON); + // return response; } } return Response::ERROR_404; } else if (url == "/api/v1/content") { nlohmann::json json; - for (auto i: ContentStore::instance()->all()) { - json.push_back(SPLEntry(i).as_json()); + for (auto i: ShowPlaylistContentStore::instance()->all()) { + json.push_back(i.as_json()); } auto response = Response(200, json.dump()); response.set_type(Response::Type::JSON); @@ -173,11 +155,11 @@ HTTPServer::get(string const& url) if (parts.size() != 5) { return Response::ERROR_404; } - auto content = ContentStore::instance()->get(parts[4]); + auto content = ShowPlaylistContentStore::instance()->get(parts[4]); if (!content) { return Response::ERROR_404; } - auto json = SPLEntry(content).as_json(); + auto json = content->as_json(); auto response = Response(200, json.dump()); response.set_type(Response::Type::JSON); return response; @@ -208,16 +190,18 @@ HTTPServer::post(string const& url, vector const& body) return Response::ERROR_404; } bool found = false; - for (auto spl: get_playlists()) { - if (spl->id() == parts[4]) { + ShowPlaylistList spl_list; + for (auto const& spl: spl_list.show_playlists()) { + if (spl.second.uuid() == parts[4]) { nlohmann::json details = nlohmann::json::parse(body); - spl->insert( - SPLEntry(ContentStore::instance()->get(details["digest"])), - details["before"].is_null() ? optional() : details["before"].get() - ); - if (auto dir = Config::instance()->player_playlist_directory()) { - spl->write(*dir / (spl->id() + ".xml")); - } + // XXX + // spl->insert( + // SPLEntry(ContentStore::instance()->get(details["digest"])), + // details["before"].is_null() ? optional() : details["before"].get() + // ); + // if (auto dir = Config::instance()->player_playlist_directory()) { + // spl->write(*dir / (spl->id() + ".xml")); + // } found = true; } } @@ -225,7 +209,6 @@ HTTPServer::post(string const& url, vector const& body) return Response::ERROR_404; } return Response(201); - } else { return Response::ERROR_404; } diff --git a/src/lib/show_playlist.cc b/src/lib/show_playlist.cc new file mode 100644 index 000000000..b10757d5a --- /dev/null +++ b/src/lib/show_playlist.cc @@ -0,0 +1,33 @@ +/* + Copyright (C) 2018-2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "show_playlist.h" + + +nlohmann::json +ShowPlaylist::as_json() const +{ + nlohmann::json json; + json["uuid"] = _uuid; + json["name"] = _name; + return json; +} + diff --git a/src/lib/show_playlist.h b/src/lib/show_playlist.h new file mode 100644 index 000000000..e7f145d37 --- /dev/null +++ b/src/lib/show_playlist.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2018-2020 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#ifndef DCPOMATIC_SHOW_PLAYLIST_H +#define DCPOMATIC_SHOW_PLAYLIST_H + + +#include +#include + + +/** @class ShowPlaylist + * + * @brief A "show playlist": what a projection system might play for an entire cinema "show". + * + * For example, it might contain some adverts, some trailers and a feature. + * Each SPL has unique ID, a name, and some ordered entries (the content). The content + * is not stored in this class, but can be read from the database via SPLList. + */ +class ShowPlaylist +{ +public: + ShowPlaylist() + : _uuid(dcp::make_uuid()) + {} + + explicit ShowPlaylist(std::string name) + : _uuid(dcp::make_uuid()) + , _name(name) + {} + + ShowPlaylist(std::string uuid, std::string name) + : _uuid(uuid) + , _name(name) + {} + + std::string uuid() const { + return _uuid; + } + + std::string name() const { + return _name; + } + + void set_name(std::string name) { + _name = name; + } + + nlohmann::json as_json() const; + +private: + std::string _uuid; + std::string _name; +}; + + +#endif diff --git a/src/lib/show_playlist_content_store.cc b/src/lib/show_playlist_content_store.cc new file mode 100644 index 000000000..41d9db245 --- /dev/null +++ b/src/lib/show_playlist_content_store.cc @@ -0,0 +1,133 @@ +/* + Copyright (C) 2024 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "config.h" +#include "content_factory.h" +#include "cross.h" +#include "dcp_content.h" +#include "examine_content_job.h" +#include "job_manager.h" +#include "show_playlist_content_store.h" +#include "util.h" +#include +#include + + +using std::make_shared; +using std::pair; +using std::shared_ptr; +using std::string; +using std::vector; +using boost::optional; + + +ShowPlaylistContentStore* ShowPlaylistContentStore::_instance = nullptr; + + +vector> +ShowPlaylistContentStore::update(std::function pulse) +{ + _content.clear(); + auto dir = Config::instance()->player_content_directory(); + if (!dir || !dcp::filesystem::is_directory(*dir)) { + return {}; + } + + auto jm = JobManager::instance(); + + vector> jobs; + + for (auto i: boost::filesystem::directory_iterator(*dir)) { + try { + pulse(); + + shared_ptr content; + if (is_directory(i) && contains_assetmap(i)) { + content = make_shared(i); + } else if (i.path().extension() == ".mp4") { + auto all_content = content_factory(i); + if (!all_content.empty()) { + content = all_content[0]; + } + } + + if (content) { + auto job = make_shared(shared_ptr(), content, true); + jm->add(job); + jobs.push_back(job); + } + } catch (boost::filesystem::filesystem_error& e) { + /* Never mind */ + } catch (dcp::ReadError& e) { + /* Never mind */ + } + } + + while (jm->work_to_do()) { + if (!pulse()) { + /* user pressed cancel */ + for (auto i: jm->get()) { + i->cancel(); + } + return {}; + } + dcpomatic_sleep_seconds(1); + } + + /* Add content from successful jobs and report errors */ + vector> errors; + for (auto i: jobs) { + if (i->finished_in_error()) { + errors.push_back({String::compose("%1.\n", i->error_summary()), i->error_details()}); + } else { + _content.push_back(ShowPlaylistEntry(i->content())); + } + } + + return errors; +} + + +optional +ShowPlaylistContentStore::get(string uuid) const +{ + auto iter = std::find_if(_content.begin(), _content.end(), [uuid](ShowPlaylistEntry const& entry) { + return entry.uuid() == uuid; + }); + + if (iter == _content.end()) { + return {}; + } + + return *iter; +} + + +ShowPlaylistContentStore* +ShowPlaylistContentStore::instance() +{ + if (!_instance) { + _instance = new ShowPlaylistContentStore(); + } + + return _instance; +} + diff --git a/src/lib/show_playlist_content_store.h b/src/lib/show_playlist_content_store.h new file mode 100644 index 000000000..dcf19cebe --- /dev/null +++ b/src/lib/show_playlist_content_store.h @@ -0,0 +1,54 @@ +/* + Copyright (C) 2018 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "show_playlist_entry.h" +#include +#include +#include + + +class Content; + +/** @class ShowPlaylistContentStore + * @brief Class to maintain details of what content we have available to play + */ +class ShowPlaylistContentStore +{ +public: + boost::optional get(std::string uuid) const; + + /** Examine content in the configured directory and update our list. + * @param pulse Called every so often to indicate progress. Return false to cancel the scan. + * @return Errors (first of the pair is the summary, second is the detail). + */ + std::vector> update(std::function pulse); + + static ShowPlaylistContentStore* instance(); + + std::vector const& all() const { + return _content; + } + +private: + std::vector _content; + + static ShowPlaylistContentStore* _instance; +}; diff --git a/src/lib/show_playlist_entry.cc b/src/lib/show_playlist_entry.cc new file mode 100644 index 000000000..a684de1d2 --- /dev/null +++ b/src/lib/show_playlist_entry.cc @@ -0,0 +1,103 @@ +/* + Copyright (C) 2018-2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "dcp_content.h" +#include "dcpomatic_assert.h" +#include "show_playlist_entry.h" +#include + + +using std::dynamic_pointer_cast; +using std::shared_ptr; +using std::string; + + +ShowPlaylistEntry::ShowPlaylistEntry(shared_ptr content) + : _content(content) +{ + +} + + +string +ShowPlaylistEntry::uuid() const +{ + if (auto dcp = dynamic_pointer_cast(_content)) { + DCPOMATIC_ASSERT(dcp->cpl()); + return *dcp->cpl(); + } else { + return _content->digest(); + } +} + + +string +ShowPlaylistEntry::name() const +{ + if (auto dcp = dynamic_pointer_cast(_content)) { + return dcp->name(); + } else { + return _content->path(0).filename().string(); + } +} + + +dcp::ContentKind +ShowPlaylistEntry::kind() const +{ + if (auto dcp = dynamic_pointer_cast(_content)) { + return dcp->content_kind().get_value_or(dcp::ContentKind::FEATURE); + } else { + return dcp::ContentKind::FEATURE; + } +} + + +bool +ShowPlaylistEntry::encrypted() const +{ + if (auto dcp = dynamic_pointer_cast(_content)) { + return dcp->encrypted(); + } else { + return false; + } +} + + +string +ShowPlaylistEntry::approximate_length() const +{ + auto const hmsf = _content->approximate_length().split(24); + return fmt::format("{:02d}:{:02d}:{:02d}", hmsf.h, hmsf.m, hmsf.s); +} + + +nlohmann::json +ShowPlaylistEntry::as_json() const +{ + nlohmann::json json; + json["uuid"] = uuid(); + json["name"] = name(); + json["kind"] = kind().name(); + json["encrypted"] = encrypted(); + return json; +} + diff --git a/src/lib/show_playlist_entry.h b/src/lib/show_playlist_entry.h new file mode 100644 index 000000000..9a20ca3b7 --- /dev/null +++ b/src/lib/show_playlist_entry.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2018-2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#ifndef DCPOMATIC_SHOW_PLAYLIST_ENTRY_H +#define DCPOMATIC_SHOW_PLAYLIST_ENTRY_H + + +#include +#include + + +class Content; + + +/** @class ShowPlaylistEntry + * + * @brief An entry on a show playlist (SPL). + * + * In the ShowPlaylist database we only store the UUID of the content. This class + * holds a pointer to a Content object, and provides a simpler interface to information + * that is needed by the ShowPlaylist code. + * + * Given a UUID from the database, a ShowPlaylistEntry can be obtained from the + * ShowPlaylistContentStore. + */ +class ShowPlaylistEntry +{ +public: + explicit ShowPlaylistEntry(std::shared_ptr content); + + nlohmann::json as_json() const; + + std::string uuid() const; + std::string name() const; + dcp::ContentKind kind() const; + std::string approximate_length() const; + bool encrypted() const; + + std::shared_ptr content() const { + return _content; + } + +private: + std::shared_ptr _content; +}; + + +#endif diff --git a/src/lib/show_playlist_list.cc b/src/lib/show_playlist_list.cc new file mode 100644 index 000000000..a45d42d12 --- /dev/null +++ b/src/lib/show_playlist_list.cc @@ -0,0 +1,223 @@ +/* + Copyright (C) 2025 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "config.h" +#include "show_playlist.h" +#include "show_playlist.h" +#include "show_playlist_entry.h" +#include "show_playlist_list.h" +#include "sqlite_statement.h" +#include "sqlite_transaction.h" + + +using std::make_pair; +using std::pair; +using std::string; +using std::vector; +using boost::optional; + + +ShowPlaylistList::ShowPlaylistList() + : _show_playlists("show_playlists") + , _entries("entries") + , _db(Config::instance()->show_playlists_file()) +{ + setup_tables(); + setup(); +} + + +ShowPlaylistList::ShowPlaylistList(boost::filesystem::path db_file) + : _show_playlists("show_playlists") + , _entries("entries") + , _db(db_file) +{ + setup_tables(); + setup(); +} + + +void +ShowPlaylistList::setup_tables() +{ + _show_playlists.add_column("uuid", "TEXT"); + _show_playlists.add_column("name", "TEXT"); + + _entries.add_column("show_playlist", "INTEGER"); + _entries.add_column("uuid", "TEXT"); + _entries.add_column("sort_index", "INTEGER"); +} + + +void +ShowPlaylistList::setup() +{ + SQLiteStatement show_playlists(_db, _show_playlists.create()); + show_playlists.execute(); + + SQLiteStatement entries(_db, _entries.create()); + entries.execute(); +} + + +ShowPlaylistID +ShowPlaylistList::add_show_playlist(ShowPlaylist const& playlist) +{ + SQLiteStatement statement(_db, _show_playlists.insert()); + + statement.bind_text(1, playlist.uuid()); + statement.bind_text(2, playlist.name()); + + statement.execute(); + + return sqlite3_last_insert_rowid(_db.db()); +} + + +void +ShowPlaylistList::remove_show_playlist(ShowPlaylistID id) +{ + SQLiteStatement statement(_db, "DELETE FROM show_playlists WHERE ID=?"); + statement.bind_int64(1, id.get()); + statement.execute(); +} + + +static +vector> +show_playlists_from_result(SQLiteStatement& statement) +{ + vector> output; + + statement.execute([&output](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 3); + ShowPlaylistID const id = statement.column_int64(0); + auto const uuid = statement.column_text(1); + auto const name = statement.column_text(2); + output.push_back(make_pair(id, ShowPlaylist(uuid, name))); + }); + + return output; +} + + +vector> +ShowPlaylistList::show_playlists() const +{ + SQLiteStatement statement(_db, _show_playlists.select("ORDER BY name COLLATE unicode ASC")); + return show_playlists_from_result(statement); +} + + + +vector +ShowPlaylistList::entries(ShowPlaylistID show_playlist_id) const +{ + SQLiteStatement statement(_db, "SELECT entries.uuid FROM entries JOIN show_playlists ON entries.show_playlist=show_playlists.id WHERE show_playlists.id=? ORDER BY entries.sort_index"); + statement.bind_int64(1, show_playlist_id.get()); + + vector output; + + statement.execute([&output](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 1); + output.push_back(statement.column_text(0)); + }); + + return output; +} + + +vector +ShowPlaylistList::entries(string const& show_playlist_uuid) const +{ + SQLiteStatement statement(_db, "SELECT entries.uuid FROM entries JOIN show_playlists ON entries.show_playlist=show_playlists.id WHERE show_playlists.uuid=? ORDER BY entries.sort_index"); + statement.bind_text(1, show_playlist_uuid); + + vector output; + + statement.execute([&output](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 1); + output.push_back(statement.column_text(0)); + }); + + return output; +} + + +void +ShowPlaylistList::add_entry(ShowPlaylistID playlist_id, ShowPlaylistEntry entry) +{ + SQLiteTransaction transaction(_db); + + SQLiteStatement find_last_screen(_db, "SELECT MAX(sort_index) FROM entries WHERE show_playlist=?"); + find_last_screen.bind_int64(1, playlist_id.get()); + + optional highest_index; + find_last_screen.execute([&highest_index](SQLiteStatement& statement) { + if (statement.data_count() == 1) { + highest_index = statement.column_int64(0); + } + }); + + SQLiteStatement add_entry(_db, _entries.insert()); + + add_entry.bind_int64(1, playlist_id.get()); + add_entry.bind_text(2, entry.uuid()); + add_entry.bind_int64(3, highest_index ? *highest_index + 1 : 0); + + add_entry.execute(); + + transaction.commit(); +} + + +void +ShowPlaylistList::move_entry_up(ShowPlaylistID playlist_id, int index) +{ + DCPOMATIC_ASSERT(index >= 1); + + SQLiteTransaction transaction(_db); + + SQLiteStatement find(_db, "SELECT id,sort_index FROM entries WHERE show_playlist=? ORDER BY sort_index LIMIT 2 OFFSET ?"); + find.bind_int64(1, playlist_id.get()); + find.bind_int64(2, index - 1); + + vector> rows; + find.execute([&rows](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 2); + rows.push_back({statement.column_int64(0), statement.column_int64(1)}); + }); + + DCPOMATIC_ASSERT(rows.size() == 2); + + SQLiteStatement swap1(_db, "UPDATE entries SET sort_index=? WHERE id=?"); + swap1.bind_int64(1, rows[0].second); + swap1.bind_int64(2, rows[1].first); + swap1.execute(); + + SQLiteStatement swap2(_db, "UPDATE entries SET sort_index=? WHERE id=?"); + swap2.bind_int64(1, rows[1].second); + swap2.bind_int64(2, rows[0].first); + swap2.execute(); + + transaction.commit(); +} + diff --git a/src/lib/show_playlist_list.h b/src/lib/show_playlist_list.h new file mode 100644 index 000000000..08414661d --- /dev/null +++ b/src/lib/show_playlist_list.h @@ -0,0 +1,83 @@ +/* + Copyright (C) 2025 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#ifndef DCPOMATIC_SHOW_PLAYLIST_LIST_H +#define DCPOMATIC_SHOW_PLAYLIST_LIST_H + + +#include "id.h" +#include "show_playlist.h" +#include "sqlite_database.h" +#include "sqlite_table.h" + + +/** @class ShowPlaylistID + * + * @brief The SQLite ID (not the UUID) of a ShowPlaylist. + */ +class ShowPlaylistID : public ID +{ +public: + ShowPlaylistID(sqlite3_int64 id) + : ID(id) {} + + bool operator<(ShowPlaylistID const& other) const { + return get() < other.get(); + } +}; + + +/** @class ShowPlaylistList + * + * @brief A list of SPLs (show playlists) stored in a SQLite database. + * + * A SPL (show playlist) is a list of content (and maybe later automation cues) + * that make up a "show" in a cinema/theater. For example, a SPL might contain + * some adverts, some trailers, and a feature. + */ +class ShowPlaylistList +{ +public: + ShowPlaylistList(); + explicit ShowPlaylistList(boost::filesystem::path db_file); + + ShowPlaylistID add_show_playlist(ShowPlaylist const& show_playlist); + void remove_show_playlist(ShowPlaylistID id); + std::vector> show_playlists() const; + + std::vector entries(ShowPlaylistID show_playlist_id) const; + std::vector entries(std::string const& show_playlist_uuid) const; + + void add_entry(ShowPlaylistID, ShowPlaylistEntry entry); + void move_entry_up(ShowPlaylistID, int index); + +private: + void setup_tables(); + void setup(); + + SQLiteTable _show_playlists; + SQLiteTable _entries; + mutable SQLiteDatabase _db; +}; + + +#endif + diff --git a/src/lib/spl.cc b/src/lib/spl.cc deleted file mode 100644 index 89cb1d0c5..000000000 --- a/src/lib/spl.cc +++ /dev/null @@ -1,107 +0,0 @@ -/* - Copyright (C) 2018-2021 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - - -#include "content_store.h" -#include "spl.h" -#include -#include -#include -LIBDCP_DISABLE_WARNINGS -#include -LIBDCP_ENABLE_WARNINGS -#include - - -using std::cout; -using std::string; -using std::shared_ptr; -using boost::optional; - - -void -SPL::read (boost::filesystem::path path, ContentStore* store) -{ - _spl.clear (); - _missing = false; - cxml::Document doc ("SPL"); - doc.read_file(dcp::filesystem::fix_long_path(path)); - _id = doc.string_child("Id"); - _name = doc.string_child("Name"); - for (auto i: doc.node_children("Entry")) { - auto c = store->get(i->string_child("Digest")); - if (c) { - add (SPLEntry(c)); - } else { - _missing = true; - } - } -} - - -void -SPL::write (boost::filesystem::path path) const -{ - xmlpp::Document doc; - auto root = doc.create_root_node ("SPL"); - cxml::add_text_child(root, "Id", _id); - cxml::add_text_child(root, "Name", _name); - for (auto i: _spl) { - i.as_xml(cxml::add_child(root, "Entry")); - } - doc.write_to_file_formatted (path.string()); -} - - -void -SPL::insert(SPLEntry entry, optional before_id) -{ - if (before_id) { - auto iter = std::find_if(_spl.begin(), _spl.end(), [before_id](SPLEntry const& e) { - return e.id == before_id; - }); - if (iter != _spl.end()) { - _spl.insert(iter, entry); - } - } else { - _spl.push_back(entry); - } -} - - -nlohmann::json -SPL::as_json_without_content() const -{ - nlohmann::json json; - json["id"] = _id; - json["name"] = _name; - return json; -} - - -nlohmann::json -SPL::as_json_with_content() const -{ - auto json = as_json_without_content(); - for (auto i: _spl) { - json["content"].push_back(i.as_json()); - } - return json; -} diff --git a/src/lib/spl.h b/src/lib/spl.h deleted file mode 100644 index ca81ff5af..000000000 --- a/src/lib/spl.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - Copyright (C) 2018-2020 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - - -#ifndef DCPOMATIC_SPL_H -#define DCPOMATIC_SPL_H - - -#include "spl_entry.h" -#include -#include -#include -#include - - -class ContentStore; - - -/** @class SPL - * - * @brief A "show playlist": what a projection system might play for an entire cinema "show". - * - * For example, it might contain some adverts, some trailers and a feature. - * Each SPL has unique ID, a name, and some SPLEntry objects. - */ -class SPL -{ -public: - SPL() - : _id(dcp::make_uuid()) - {} - - explicit SPL(std::string name) - : _id(dcp::make_uuid()) - , _name(name) - {} - - - void add(SPLEntry e) { - _spl.push_back(e); - } - - void remove(std::size_t index) { - _spl.erase(_spl.begin() + index); - } - - void insert(SPLEntry entry, boost::optional before_id); - - std::vector const& get() const { - return _spl; - } - - SPLEntry const& operator[](std::size_t index) const { - return _spl[index]; - } - - void swap(size_t a, size_t b) { - std::iter_swap(_spl.begin() + a, _spl.begin() + b); - } - - void read(boost::filesystem::path path, ContentStore* store); - void write(boost::filesystem::path path) const; - - std::string id() const { - return _id; - } - - std::string name() const { - return _name; - } - - void set_name(std::string name) { - _name = name; - } - - /** @return true if any content was missing when read() was last called on this SPL */ - bool missing() const { - return _missing; - } - - nlohmann::json as_json_without_content() const; - nlohmann::json as_json_with_content() const; - -private: - std::string _id; - std::string _name; - std::vector _spl; - /** true if any content was missing when read() was last called on this SPL */ - bool _missing = false; -}; - - -/** @class SignalSPL - * - * @brief A wrapper around SPL that signals changes. - */ -class SignalSPL : public SPL -{ -public: - enum class Change { - NAME, - CONTENT, - }; - - SignalSPL() = default; - - explicit SignalSPL(std::string name) - : SPL(name) - {} - - void set_name(std::string name) { - SPL::set_name (name); - Changed(Change::NAME); - } - - void add(SPLEntry e) { - SPL::add(e); - Changed(Change::CONTENT); - } - - void remove(std::size_t index) { - SPL::remove(index); - Changed(Change::CONTENT); - } - - void insert(SPLEntry e, boost::optional before_id) { - SPL::insert(e, before_id); - Changed(Change::CONTENT); - } - - void swap(size_t a, size_t b) { - SPL::swap(a, b); - Changed(Change::CONTENT); - } - - boost::signals2::signal Changed; -}; - -#endif diff --git a/src/lib/spl_entry.cc b/src/lib/spl_entry.cc deleted file mode 100644 index 476744252..000000000 --- a/src/lib/spl_entry.cc +++ /dev/null @@ -1,75 +0,0 @@ -/* - Copyright (C) 2018-2021 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - - -#include "dcp_content.h" -#include "dcpomatic_assert.h" -#include "spl_entry.h" -#include -LIBDCP_DISABLE_WARNINGS -#include -LIBDCP_ENABLE_WARNINGS - - -using std::shared_ptr; -using std::dynamic_pointer_cast; - - -SPLEntry::SPLEntry (shared_ptr c) - : content (c) - , digest (content->digest()) -{ - auto dcp = dynamic_pointer_cast (content); - if (dcp) { - name = dcp->name (); - DCPOMATIC_ASSERT (dcp->cpl()); - id = *dcp->cpl(); - kind = dcp->content_kind().get_value_or(dcp::ContentKind::FEATURE); - encrypted = dcp->encrypted (); - } else { - name = content->path(0).filename().string(); - kind = dcp::ContentKind::FEATURE; - } -} - - -void -SPLEntry::as_xml(xmlpp::Element* e) const -{ - cxml::add_text_child(e, "Digest", digest); -} - - -nlohmann::json -SPLEntry::as_json() const -{ - nlohmann::json json; - json["name"] = name; - json["digest"] = digest; - if (!id.empty()) { - json["cpl-id"] = id; - } - if (kind) { - json["kind"] = kind->name(); - } - json["encrypted"] = encrypted; - return json; -} - diff --git a/src/lib/spl_entry.h b/src/lib/spl_entry.h deleted file mode 100644 index 11f2742bf..000000000 --- a/src/lib/spl_entry.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright (C) 2018-2021 Carl Hetherington - - This file is part of DCP-o-matic. - - DCP-o-matic is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - DCP-o-matic is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with DCP-o-matic. If not, see . - -*/ - - -#ifndef DCPOMATIC_SPL_ENTRY_H -#define DCPOMATIC_SPL_ENTRY_H - - -#include -#include -#include - - -namespace xmlpp { - class Element; -} - -class Content; - - -/** An entry on a show playlist (SPL) */ -class SPLEntry -{ -public: - SPLEntry (std::shared_ptr c); - - void as_xml(xmlpp::Element* e) const; - nlohmann::json as_json() const; - - std::shared_ptr content; - std::string name; - /** Digest of this content */ - std::string digest; - /** CPL ID */ - std::string id; - boost::optional kind; - bool encrypted; - -private: - void construct (std::shared_ptr content); -}; - - -#endif diff --git a/src/lib/wscript b/src/lib/wscript index f9ee45ed5..250771108 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -57,7 +57,6 @@ sources = """ config.cc content.cc content_factory.cc - content_store.cc combine_dcp_job.cc copy_dcp_details_to_film.cc cpu_j2k_encoder_thread.cc @@ -189,10 +188,12 @@ sources = """ send_notification_email_job.cc send_problem_report_job.cc server.cc + show_playlist.cc + show_playlist_content_store.cc + show_playlist_entry.cc + show_playlist_list.cc shuffler.cc state.cc - spl.cc - spl_entry.cc sqlite_database.cc sqlite_statement.cc sqlite_table.cc diff --git a/src/tools/dcpomatic_player.cc b/src/tools/dcpomatic_player.cc index 071329ae4..c7b4de9e0 100644 --- a/src/tools/dcpomatic_player.cc +++ b/src/tools/dcpomatic_player.cc @@ -253,7 +253,8 @@ public: Bind(wxEVT_CLOSE_WINDOW, boost::bind(&DOMFrame::close, this, _1)); if (Config::instance()->player_mode() == Config::PLAYER_MODE_DUAL || Config::instance()->enable_player_http_server()) { - update_content_store(); + std::cout << "OK FUCKS\n"; + update_show_playlist_content_store(); } if (Config::instance()->player_mode() == Config::PLAYER_MODE_DUAL) { diff --git a/src/tools/dcpomatic_playlist.cc b/src/tools/dcpomatic_playlist.cc index 4f6ea59e7..40084f899 100644 --- a/src/tools/dcpomatic_playlist.cc +++ b/src/tools/dcpomatic_playlist.cc @@ -31,8 +31,9 @@ #include "lib/cross.h" #include "lib/dcp_content.h" #include "lib/film.h" -#include "lib/spl.h" -#include "lib/spl_entry.h" +#include "lib/show_playlist.h" +#include "lib/show_playlist_entry.h" +#include "lib/show_playlist_list.h" #include #include LIBDCP_DISABLE_WARNINGS @@ -49,6 +50,7 @@ using std::exception; using std::make_pair; using std::make_shared; using std::map; +using std::pair; using std::shared_ptr; using std::string; using std::vector; @@ -61,14 +63,15 @@ using namespace boost::placeholders; #endif -static -void -save_playlist(shared_ptr playlist) -{ - if (auto dir = Config::instance()->player_playlist_directory()) { - playlist->write(*dir / (playlist->id() + ".xml")); - } -} +// XXX +// static +// void +// save_playlist(shared_ptr playlist) +// { +// if (auto dir = Config::instance()->player_playlist_directory()) { +// playlist->write(*dir / (playlist->id() + ".xml")); +// } +// } class ContentDialog : public wxDialog @@ -96,9 +99,9 @@ public: _config_changed_connection = Config::instance()->Changed.connect(boost::bind(&ContentView::update, _content_view)); } - shared_ptr selected () const + optional selected() const { - return _content_view->selected (); + return _content_view->selected(); } private: @@ -138,7 +141,7 @@ public: _sizer->Add (list); - load_playlists (); + add_playlists_to_model_and_view(); _list->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, bind(&PlaylistList::selection_changed, this)); _list->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, bind(&PlaylistList::selection_changed, this)); @@ -153,16 +156,16 @@ public: return _sizer; } - shared_ptr first_playlist () const + ShowPlaylist first_playlist() const { if (_playlists.empty()) { return {}; } - return _playlists.front (); + return _playlists.front().second; } - boost::signals2::signal)> Edit; + boost::signals2::signal>)> Edit; private: void setup_sensitivity() @@ -170,20 +173,21 @@ private: _delete->Enable(static_cast(selected())); } - void add_playlist_to_view (shared_ptr playlist) + void add_playlist_to_view(ShowPlaylist playlist) { wxListItem item; - item.SetId (_list->GetItemCount()); - long const N = _list->InsertItem (item); - _list->SetItem (N, 0, std_to_wx(playlist->name())); + item.SetId(_list->GetItemCount()); + long const N = _list->InsertItem(item); + _list->SetItem(N, 0, std_to_wx(playlist.name())); } - void add_playlist_to_model (shared_ptr playlist) + void add_playlist_to_model(ShowPlaylistID id, ShowPlaylist playlist) { - _playlists.push_back (playlist); - playlist->Changed.connect(bind(&PlaylistList::changed, this, weak_ptr(playlist), _1)); + _playlists.push_back(make_pair(id, playlist)); } +#if 0 + XXX void changed(weak_ptr wp, SignalSPL::Change change) { auto playlist = wp.lock (); @@ -208,43 +212,33 @@ private: break; } } +#endif - void load_playlists () + void add_playlists_to_model_and_view() { - auto path = Config::instance()->player_playlist_directory(); - if (!path) { - return; - } + ShowPlaylistList spl_list; - _list->DeleteAllItems (); - _playlists.clear (); - try { - for (auto i: dcp::filesystem::directory_iterator(*path)) { - auto spl = make_shared(); - try { - spl->read(i, ContentStore::instance()); - add_playlist_to_model (spl); - } catch (...) {} - } - } catch (...) {} + _list->DeleteAllItems(); + _playlists.clear(); - for (auto i: _playlists) { - add_playlist_to_view (i); + for (auto const& spl: spl_list.show_playlists()) { + add_playlist_to_model(spl.first, spl.second); + } + + for (auto const& i: _playlists) { + add_playlist_to_view(i.second); } } void new_playlist () { - auto dir = Config::instance()->player_playlist_directory(); - if (!dir) { - error_dialog(_parent, _("No playlist folder is specified in preferences. Please set one and then try again.")); - return; - } + ShowPlaylistList spl_list; + auto playlist = ShowPlaylist(wx_to_std(_("New Playlist"))); + auto id = spl_list.add_show_playlist(playlist); - shared_ptr spl (new SignalSPL(wx_to_std(_("New Playlist")))); - add_playlist_to_model (spl); - add_playlist_to_view (spl); - _list->SetItemState (_list->GetItemCount() - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + add_playlist_to_model(id, playlist); + add_playlist_to_view(playlist); + _list->SetItemState(_list->GetItemCount() - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); } boost::optional selected() const @@ -264,25 +258,23 @@ private: return; } - auto dir = Config::instance()->player_playlist_directory(); - if (!dir) { - return; - } + // ShowPlaylistList spl_list; + // spl_list.remove_show_playlist(_playlists[*index].first); - dcp::filesystem::remove(*dir / (_playlists[*index]->id() + ".xml")); - _list->DeleteItem(*index); - _playlists.erase(_playlists.begin() + *index); + // _list->DeleteItem(*index); + // _playlists.erase(_playlists.begin() + *index); - Edit(shared_ptr()); + // XXX + // Edit(shared_ptr()); } void selection_changed () { long int selected = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (selected < 0 || selected >= int(_playlists.size())) { - Edit (shared_ptr()); + Edit({}); } else { - Edit (_playlists[selected]); + Edit(_playlists[selected]); } setup_sensitivity(); @@ -292,38 +284,44 @@ private: wxListCtrl* _list; wxButton* _new; wxButton* _delete; - vector> _playlists; + vector> _playlists; wxWindow* _parent; }; +/** @class PlaylistContent + * + * @brief List showing the contents of a playlist + * + * This includes a heading, and buttons to re-order, add and remove items. + */ class PlaylistContent { public: - PlaylistContent (wxPanel* parent, ContentDialog* content_dialog) - : _content_dialog (content_dialog) - , _sizer (new wxBoxSizer(wxVERTICAL)) - { - auto title = new wxBoxSizer (wxHORIZONTAL); - auto label = new wxStaticText (parent, wxID_ANY, wxEmptyString); - label->SetLabelMarkup (_("Playlist:")); - title->Add (label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_GAP); - _name = new wxTextCtrl (parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(400, -1)); - title->Add (_name, 0, wxRIGHT, DCPOMATIC_SIZER_GAP); + PlaylistContent(wxPanel* parent, ContentDialog* content_dialog) + : _content_dialog(content_dialog) + , _sizer(new wxBoxSizer(wxVERTICAL)) + { + auto title = new wxBoxSizer(wxHORIZONTAL); + auto label = new wxStaticText(parent, wxID_ANY, wxEmptyString); + label->SetLabelMarkup(_("Playlist:")); + title->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_GAP); + _name = new wxTextCtrl(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(400, -1)); + title->Add(_name, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP); _save_name = new Button(parent, _("Save")); title->Add(_save_name); - _sizer->Add (title, 0, wxTOP | wxLEFT, DCPOMATIC_SIZER_GAP * 2); + _sizer->Add(title, 0, wxTOP | wxLEFT, DCPOMATIC_SIZER_GAP * 2); - auto list = new wxBoxSizer (wxHORIZONTAL); + auto list = new wxBoxSizer(wxHORIZONTAL); - _list = new wxListCtrl ( + _list = new wxListCtrl( parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL ); - _list->AppendColumn (_("Name"), wxLIST_FORMAT_LEFT, 400); - _list->AppendColumn (_("CPL"), wxLIST_FORMAT_LEFT, 350); - _list->AppendColumn (_("Type"), wxLIST_FORMAT_LEFT, 100); - _list->AppendColumn (_("Encrypted"), wxLIST_FORMAT_CENTRE, 90); + _list->AppendColumn(_("Name"), wxLIST_FORMAT_LEFT, 400); + _list->AppendColumn(_("CPL"), wxLIST_FORMAT_LEFT, 350); + _list->AppendColumn(_("Type"), wxLIST_FORMAT_LEFT, 100); + _list->AppendColumn(_("Encrypted"), wxLIST_FORMAT_CENTRE, 90); list->Add (_list, 1, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP); @@ -358,22 +356,27 @@ public: return _sizer; } - void set (shared_ptr playlist) + void set(pair playlist) { _playlist = playlist; - _list->DeleteAllItems (); + _list->DeleteAllItems(); + + ShowPlaylistList spl_list; + ShowPlaylistContentStore content; if (_playlist) { - for (auto i: _playlist->get()) { - add (i); + for (auto uuid: spl_list.entries(_playlist->second.uuid())) { + if (auto entry = content.get(uuid)) { + add(*entry); + } } - _name->SetValue (std_to_wx(_playlist->name())); + _name->SetValue(std_to_wx(_playlist->second.name())); } else { - _name->SetValue({}); + _name->SetValue(wxT("")); } - setup_sensitivity (); + setup_sensitivity(); } - shared_ptr playlist () const + optional> playlist() const { return _playlist; } @@ -382,11 +385,12 @@ public: private: void save_name_clicked() { - if (_playlist) { - _playlist->set_name(wx_to_std(_name->GetValue())); - save_playlist(_playlist); - } - setup_sensitivity(); + // XXX + // if (_playlist) { + // _playlist->set_name(wx_to_std(_name->GetValue())); + // save_playlist(_playlist); + // } + // setup_sensitivity(); } void name_changed () @@ -394,20 +398,20 @@ private: setup_sensitivity(); } - void add (SPLEntry e) + void add(ShowPlaylistEntry e) { wxListItem item; - item.SetId (_list->GetItemCount()); - long const N = _list->InsertItem (item); - set_item (N, e); + item.SetId(_list->GetItemCount()); + long const N = _list->InsertItem(item); + set_item(N, e); } - void set_item (long N, SPLEntry e) + void set_item(long N, ShowPlaylistEntry e) { - _list->SetItem (N, 0, std_to_wx(e.name)); - _list->SetItem (N, 1, std_to_wx(e.id)); - _list->SetItem (N, 2, std_to_wx(e.kind->name())); - _list->SetItem (N, 3, e.encrypted ? S_("Question|Y") : S_("Question|N")); + _list->SetItem(N, 0, std_to_wx(e.name())); + _list->SetItem(N, 1, std_to_wx(e.uuid())); + _list->SetItem(N, 2, std_to_wx(e.kind().name())); + _list->SetItem(N, 3, e.encrypted() ? S_("Question|Y") : S_("Question|N")); } void setup_sensitivity () @@ -416,7 +420,7 @@ private: int const num_selected = _list->GetSelectedItemCount (); long int selected = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); _name->Enable (have_list); - _save_name->Enable(_playlist && _playlist->name() != wx_to_std(_name->GetValue())); + // _save_name->Enable(_playlist && _playlist->name() != wx_to_std(_name->GetValue())); _list->Enable (have_list); _up->Enable (have_list && selected > 0); _down->Enable (have_list && selected != -1 && selected < (_list->GetItemCount() - 1)); @@ -426,58 +430,68 @@ private: void add_clicked () { - int const r = _content_dialog->ShowModal (); + auto const r = _content_dialog->ShowModal(); if (r == wxID_OK) { - auto content = _content_dialog->selected (); - if (content) { - SPLEntry e (content); - add (e); - DCPOMATIC_ASSERT (_playlist); - _playlist->add (e); + if (auto entry = _content_dialog->selected()) { + add(*entry); + DCPOMATIC_ASSERT(_playlist); + ShowPlaylistList spl_list; + spl_list.add_entry(_playlist->first, *entry); } } } void up_clicked () { - long int s = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + auto s = _list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (s < 1) { return; } - DCPOMATIC_ASSERT (_playlist); + DCPOMATIC_ASSERT(_playlist); + + auto content = ShowPlaylistContentStore::instance(); - _playlist->swap(s, s - 1); + ShowPlaylistList spl_list; + spl_list.move_entry_up(_playlist->first, s); + auto entries = spl_list.entries(_playlist->first); + std::cout << "UP!!!!!\n"; + for (auto i: entries) { + std::cout << "E:" << i << " " << content->get(i)->name() << "\n"; + } - set_item (s - 1, (*_playlist)[s-1]); - set_item (s, (*_playlist)[s]); + std::cout << "let's get it the fucker " << entries[s-1] << " " << content->get(entries[s])->name() << "\n"; + set_item(s - 1, *content->get(entries[s])); + set_item(s, *content->get(entries[s - 1])); } void down_clicked () { - long int s = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - if (s > (_list->GetItemCount() - 1)) { - return; - } + // XXX + // long int s = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + // if (s > (_list->GetItemCount() - 1)) { + // return; + // } - DCPOMATIC_ASSERT (_playlist); + // DCPOMATIC_ASSERT (_playlist); - _playlist->swap(s, s + 1); + // _playlist->swap(s, s + 1); - set_item (s + 1, (*_playlist)[s+1]); - set_item (s, (*_playlist)[s]); + // set_item (s + 1, (*_playlist)[s+1]); + // set_item (s, (*_playlist)[s]); } void remove_clicked () { - long int s = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - if (s == -1) { - return; - } + // XXX + // long int s = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + // if (s == -1) { + // return; + // } - DCPOMATIC_ASSERT (_playlist); - _playlist->remove (s); - _list->DeleteItem (s); + // DCPOMATIC_ASSERT (_playlist); + // _playlist->remove (s); + // _list->DeleteItem (s); } ContentDialog* _content_dialog; @@ -489,7 +503,7 @@ private: wxButton* _down; wxButton* _add; wxButton* _remove; - shared_ptr _playlist; + optional> _playlist; }; @@ -519,7 +533,7 @@ public: overall_panel->SetSizer (sizer); - _playlist_list->Edit.connect (bind(&DOMFrame::change_playlist, this, _1)); + _playlist_list->Edit.connect(bind(&DOMFrame::change_playlist, this, _1)); Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_exit, this), wxID_EXIT); Bind (wxEVT_MENU, boost::bind (&DOMFrame::help_about, this), wxID_ABOUT); @@ -550,13 +564,11 @@ private: _config_dialog->Show (this); } - void change_playlist (shared_ptr playlist) + void change_playlist(optional> playlist) { - auto old = _playlist_content->playlist (); - if (old) { - save_playlist (old); + if (playlist) { + _playlist_content->set(*playlist); } - _playlist_content->set (playlist); } void setup_menu (wxMenuBar* m) @@ -665,7 +677,7 @@ private: */ Config::drop (); - update_content_store(); + update_show_playlist_content_store(); _frame = new DOMFrame(variant::wx::dcpomatic_playlist_editor()); SetTopWindow (_frame); @@ -679,7 +691,14 @@ private: } catch (exception& e) { - error_dialog(nullptr, variant::wx::insert_dcpomatic_playlist_editor(_("%s could not start %s")), std_to_wx(e.what())); + error_dialog( + nullptr, + wxString::Format( + wxT("%s: %s"), + variant::wx::insert_dcpomatic_playlist_editor(_("%s could not start")), + std_to_wx(e.what()) + ) + ); return true; } diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc index 17ee0fb21..bca2e20b9 100644 --- a/src/wx/config_dialog.cc +++ b/src/wx/config_dialog.cc @@ -24,6 +24,7 @@ #include "check_box.h" #include "config_dialog.h" #include "dcpomatic_button.h" +#include "file_picker_ctrl.h" #include "nag_dialog.h" #include "static_text.h" #include "wx_variant.h" @@ -1041,9 +1042,9 @@ LocationsPage::setup () table->Add (_content_directory, wxGBPosition (r, 1)); ++r; - add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0)); - _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, char_to_wx(wxDirSelectorPromptStr), wxDefaultPosition, wxSize (300, -1)); - table->Add (_playlist_directory, wxGBPosition (r, 1)); + add_label_to_sizer(table, _panel, _("Show playlists file"), true, wxGBPosition (r, 0)); + _show_playlists_file = new FilePickerCtrl(_panel, _("Select show playlists file"), _("SQLite3 files (.sqlite3)|*.sqlite3)"), false, true, "ShowPlaylistsFile"); + table->Add(_show_playlists_file, wxGBPosition (r, 1)); ++r; add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0)); @@ -1052,7 +1053,7 @@ LocationsPage::setup () ++r; _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this)); - _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this)); + _show_playlists_file->Bind(wxEVT_FILEPICKER_CHANGED, bind(&LocationsPage::show_playlists_file_changed, this)); _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this)); } @@ -1064,9 +1065,7 @@ LocationsPage::config_changed () if (config->player_content_directory()) { checked_set (_content_directory, *config->player_content_directory()); } - if (config->player_playlist_directory()) { - checked_set (_playlist_directory, *config->player_playlist_directory()); - } + checked_set(_show_playlists_file, config->show_playlists_file()); if (config->player_kdm_directory()) { checked_set (_kdm_directory, *config->player_kdm_directory()); } @@ -1079,9 +1078,11 @@ LocationsPage::content_directory_changed () } void -LocationsPage::playlist_directory_changed () +LocationsPage::show_playlists_file_changed() { - Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath())); + if (auto path = _show_playlists_file->path()) { + Config::instance()->set_show_playlists_file(*path); + } } void diff --git a/src/wx/config_dialog.h b/src/wx/config_dialog.h index ce2686864..62cee0145 100644 --- a/src/wx/config_dialog.h +++ b/src/wx/config_dialog.h @@ -235,11 +235,11 @@ private: void setup () override; void config_changed () override; void content_directory_changed (); - void playlist_directory_changed (); + void show_playlists_file_changed(); void kdm_directory_changed (); wxDirPickerCtrl* _content_directory; - wxDirPickerCtrl* _playlist_directory; + FilePickerCtrl* _show_playlists_file; wxDirPickerCtrl* _kdm_directory; }; diff --git a/src/wx/content_view.cc b/src/wx/content_view.cc index f3c11fa04..46da832a0 100644 --- a/src/wx/content_view.cc +++ b/src/wx/content_view.cc @@ -62,55 +62,50 @@ ContentView::ContentView (wxWindow* parent) } -shared_ptr -ContentView::selected () const +optional +ContentView::selected() const { - long int s = GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + long int s = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if (s == -1) { return {}; } - DCPOMATIC_ASSERT(s < int(_content_digests.size())); - return ContentStore::instance()->get(_content_digests[s]); + DCPOMATIC_ASSERT(s < int(_uuids.size())); + return ShowPlaylistContentStore::instance()->get(_uuids[s]); } void -ContentView::update () +ContentView::update() { - DeleteAllItems (); - _content_digests.clear(); - for (auto content: ContentStore::instance()->all()) { - add(content); + DeleteAllItems(); + _uuids.clear(); + for (auto entry: ShowPlaylistContentStore::instance()->all()) { + add(entry); } } void -ContentView::add(shared_ptr content) +ContentView::add(ShowPlaylistEntry entry) { int const N = GetItemCount(); wxListItem it; it.SetId(N); it.SetColumn(0); - auto length = content->approximate_length (); - auto const hmsf = length.split (24); - it.SetText(wxString::Format(char_to_wx("%02d:%02d:%02d"), hmsf.h, hmsf.m, hmsf.s)); + it.SetText(std_to_wx(entry.approximate_length())); InsertItem(it); - auto dcp = dynamic_pointer_cast(content); - if (dcp && dcp->content_kind()) { - it.SetId(N); - it.SetColumn(1); - it.SetText(std_to_wx(dcp->content_kind()->name())); - SetItem(it); - } + it.SetId(N); + it.SetColumn(1); + it.SetText(std_to_wx(entry.kind().name())); + SetItem(it); it.SetId(N); it.SetColumn(2); - it.SetText(std_to_wx(content->summary())); + it.SetText(std_to_wx(entry.name())); SetItem(it); - _content_digests.push_back(content->digest()); + _uuids.push_back(entry.uuid()); } diff --git a/src/wx/content_view.h b/src/wx/content_view.h index 65f806340..e27b14132 100644 --- a/src/wx/content_view.h +++ b/src/wx/content_view.h @@ -19,7 +19,7 @@ */ -#include "lib/content_store.h" +#include "lib/show_playlist_content_store.h" #include LIBDCP_DISABLE_WARNINGS #include @@ -31,17 +31,21 @@ class Content; class Film; +/** @class ContentView + * + * @brief A GUI list of content from our ShowPlaylistContentStore. + */ class ContentView : public wxListCtrl { public: ContentView (wxWindow* parent); - std::shared_ptr selected () const; + boost::optional selected() const; void update (); private: - void add (std::shared_ptr content); + void add(ShowPlaylistEntry entry); std::weak_ptr _film; - std::vector _content_digests; + std::vector _uuids; }; diff --git a/src/wx/playlist_controls.cc b/src/wx/playlist_controls.cc index 0f88f2881..488215fe9 100644 --- a/src/wx/playlist_controls.cc +++ b/src/wx/playlist_controls.cc @@ -35,6 +35,7 @@ #include "lib/internet.h" #include "lib/player_video.h" #include "lib/scoped_temporary.h" +#include "lib/show_playlist_list.h" #include #include LIBDCP_DISABLE_WARNINGS @@ -118,10 +119,10 @@ PlaylistControls::PlaylistControls(wxWindow* parent, FilmViewer& viewer) _spl_view->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind(&PlaylistControls::spl_selection_changed, this)); _spl_view->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&PlaylistControls::spl_selection_changed, this)); _viewer.Finished.connect(boost::bind(&PlaylistControls::viewer_finished, this)); - _refresh_spl_view->Bind (wxEVT_BUTTON, boost::bind(&PlaylistControls::update_playlist_directory, this)); + _refresh_spl_view->Bind (wxEVT_BUTTON, boost::bind(&PlaylistControls::update_playlists, this)); _refresh_content_view->Bind (wxEVT_BUTTON, boost::bind(&ContentView::update, _content_view)); - update_playlist_directory (); + update_playlists(); } void @@ -209,7 +210,9 @@ PlaylistControls::previous_clicked () bool PlaylistControls::can_do_next () { - return _selected_playlist && (_selected_playlist_position + 1) < int(_playlists[*_selected_playlist].get().size()); + ShowPlaylistList spl_list; + auto entries = spl_list.entries(_playlists[*_selected_playlist].uuid()); + return _selected_playlist && (_selected_playlist_position + 1) < int(entries.size()); } void @@ -225,61 +228,44 @@ PlaylistControls::next_clicked () void -PlaylistControls::add_playlist_to_list (SPL spl) +PlaylistControls::add_playlist_to_list(ShowPlaylist show_playlist) { int const N = _spl_view->GetItemCount(); wxListItem it; it.SetId(N); it.SetColumn(0); - string t = spl.name(); - if (spl.missing()) { - t += " (content missing)"; - } + string t = show_playlist.name(); + // XXX + // if (spl.missing()) { + // t += " (content missing)"; + // } it.SetText (std_to_wx(t)); _spl_view->InsertItem (it); } -struct SPLComparator -{ - bool operator() (SPL const & a, SPL const & b) { - return a.name() < b.name(); - } -}; void -PlaylistControls::update_playlist_directory () +PlaylistControls::update_playlists() { using namespace boost::filesystem; _spl_view->DeleteAllItems (); - optional dir = Config::instance()->player_playlist_directory(); - if (!dir) { - return; - } + _playlists.clear(); - _playlists.clear (); - - for (directory_iterator i = directory_iterator(*dir); i != directory_iterator(); ++i) { - try { - if (is_regular_file(i->path()) && i->path().extension() == ".xml") { - SPL spl; - spl.read(i->path(), ContentStore::instance()); - _playlists.push_back (spl); - } - } catch (exception& e) { - /* Never mind */ - } + ShowPlaylistList spl_list; + for (auto const& spl: spl_list.show_playlists()) { + _playlists.push_back(spl.second); } - sort (_playlists.begin(), _playlists.end(), SPLComparator()); for (auto i: _playlists) { - add_playlist_to_list (i); + add_playlist_to_list(i); } _selected_playlist = boost::none; } + optional PlaylistControls::get_kdm_from_directory (shared_ptr dcp) { @@ -313,13 +299,17 @@ PlaylistControls::spl_selection_changed () return; } - if (_playlists[selected].missing()) { - error_dialog(this, _("This playlist cannot be loaded as some content is missing.")); - deselect_playlist (); - return; - } + // XXX + // if (_playlists[selected].missing()) { + // error_dialog(this, _("This playlist cannot be loaded as some content is missing.")); + // deselect_playlist (); + // return; + // } + + ShowPlaylistList spl_list; + auto const entries = spl_list.entries(_playlists[selected].uuid()); - if (_playlists[selected].get().empty()) { + if (entries.empty()) { error_dialog(this, _("This playlist is empty.")); return; } @@ -332,13 +322,20 @@ PlaylistControls::select_playlist (int selected, int position) { wxProgressDialog dialog(variant::wx::dcpomatic(), _("Loading playlist and KDMs")); - for (auto const& i: _playlists[selected].get()) { + _playlist.clear(); + + ShowPlaylistList spl_list; + for (auto const& uuid: spl_list.entries(_playlists[selected].uuid())) { dialog.Pulse (); - shared_ptr dcp = dynamic_pointer_cast (i.content); + + auto entry = ShowPlaylistContentStore::instance()->get(uuid); + if (!entry) { + continue; + } + + auto dcp = dynamic_pointer_cast(entry->content()); if (dcp && dcp->needs_kdm()) { - optional kdm; - kdm = get_kdm_from_directory (dcp); - if (kdm) { + if (auto kdm = get_kdm_from_directory(dcp)) { try { dcp->add_kdm (*kdm); dcp->examine(_film, shared_ptr(), true); @@ -358,13 +355,15 @@ PlaylistControls::select_playlist (int selected, int position) _current_spl_view->DeleteAllItems (); int N = 0; - for (auto i: _playlists[selected].get()) { - wxListItem it; - it.SetId (N); - it.SetColumn (0); - it.SetText (std_to_wx(i.name)); - _current_spl_view->InsertItem (it); - ++N; + for (auto const& uuid: spl_list.entries(_playlists[selected].uuid())) { + if (auto entry = ShowPlaylistContentStore::instance()->get(uuid)) { + wxListItem it; + it.SetId(N); + it.SetColumn(0); + it.SetText(std_to_wx(entry->name())); + _current_spl_view->InsertItem(it); + ++N; + } } _selected_playlist = selected; @@ -379,9 +378,13 @@ void PlaylistControls::reset_film () { DCPOMATIC_ASSERT (_selected_playlist); - shared_ptr film (new Film(optional())); - film->add_content (_playlists[*_selected_playlist].get()[_selected_playlist_position].content); - ResetFilm (film); + auto film = std::make_shared(optional()); + ShowPlaylistList spl_list; + auto uuid = spl_list.entries(_playlists[*_selected_playlist].uuid())[_selected_playlist_position]; + if (auto entry = ShowPlaylistContentStore::instance()->get(uuid)) { + film->add_content(entry->content()); + } + ResetFilm(film); } void @@ -391,8 +394,8 @@ PlaylistControls::config_changed (int property) if (property == Config::PLAYER_CONTENT_DIRECTORY) { _content_view->update (); - } else if (property == Config::PLAYER_PLAYLIST_DIRECTORY) { - update_playlist_directory (); + } else if (property == Config::SHOW_PLAYLISTS_FILE) { + update_playlists(); } } @@ -417,8 +420,11 @@ PlaylistControls::viewer_finished () return; } + ShowPlaylistList spl_list; + auto entries = spl_list.entries(_playlists[*_selected_playlist].uuid()); + _selected_playlist_position++; - if (_selected_playlist_position < int(_playlists[*_selected_playlist].get().size())) { + if (_selected_playlist_position < int(entries.size())) { /* Next piece of content on the SPL */ update_current_content (); _viewer.start(); diff --git a/src/wx/playlist_controls.h b/src/wx/playlist_controls.h index 76ec63824..a9516ef3c 100644 --- a/src/wx/playlist_controls.h +++ b/src/wx/playlist_controls.h @@ -20,7 +20,7 @@ #include "controls.h" -#include "lib/spl.h" +#include "lib/show_playlist.h" class DCPContent; @@ -46,9 +46,9 @@ private: void stop_clicked (); void next_clicked (); void previous_clicked (); - void add_playlist_to_list (SPL spl); + void add_playlist_to_list(ShowPlaylist spl); void update_content_directory (); - void update_playlist_directory (); + void update_playlists(); void spl_selection_changed (); void select_playlist (int selected, int position); void started () override; @@ -76,7 +76,8 @@ private: wxButton* _refresh_spl_view; wxListCtrl* _current_spl_view; - std::vector _playlists; + std::vector _playlists; boost::optional _selected_playlist; + std::vector> _playlist; int _selected_playlist_position; }; diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc index bc2d593f1..cb231eb5f 100644 --- a/src/wx/wx_util.cc +++ b/src/wx/wx_util.cc @@ -33,7 +33,7 @@ #include "wx_util.h" #include "wx_variant.h" #include "lib/config.h" -#include "lib/content_store.h" +#include "lib/show_playlist_content_store.h" #include "lib/cross.h" #include "lib/job.h" #include "lib/job_manager.h" @@ -845,7 +845,7 @@ layout_for_short_screen(wxWindow* reference) void -update_content_store() +update_show_playlist_content_store() { auto dir = Config::instance()->player_content_directory(); if (!dir || !dcp::filesystem::is_directory(*dir)) { @@ -854,7 +854,7 @@ update_content_store() wxProgressDialog progress(variant::wx::dcpomatic(), _("Reading content directory")); - auto store = ContentStore::instance(); + auto store = ShowPlaylistContentStore::instance(); auto errors = store->update([&progress]() { return progress.Pulse(); diff --git a/src/wx/wx_util.h b/src/wx/wx_util.h index 6c3f12ef2..3f513f0a3 100644 --- a/src/wx/wx_util.h +++ b/src/wx/wx_util.h @@ -126,7 +126,7 @@ extern double dpi_scale_factor (wxWindow* window); extern int search_ctrl_height (); extern void report_config_load_failure(wxWindow* parent, Config::LoadFailure what); extern bool layout_for_short_screen(wxWindow* reference); -extern void update_content_store(); +extern void update_show_playlist_content_store(); struct Offset -- cgit v1.2.3