diff options
| author | Carl Hetherington <cth@carlh.net> | 2025-10-20 00:45:17 +0200 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2026-02-16 01:20:38 +0100 |
| commit | 4cb6ab669032ef0584fde63e62addfe8a71a484c (patch) | |
| tree | 7f9fbc6d0981b1e247c3c89545f24d5a3a6ffaaa /src | |
| parent | eb6464c1099de3967fc8d3b7de1461da85c7e827 (diff) | |
Use SQLite for show playlists.
Diffstat (limited to 'src')
28 files changed, 1163 insertions, 589 deletions
diff --git a/src/lib/config.cc b/src/lib/config.cc index a2469ed8a..974733e8a 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -31,6 +31,7 @@ #include "filter.h" #include "log.h" #include "ratio.h" +#include "show_playlist_list.h" #include "unzipper.h" #include "variant.h" #include "zipper.h" @@ -189,7 +190,7 @@ Config::set_defaults() _player_debug_log_file = boost::none; _kdm_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(); @@ -615,7 +616,15 @@ try _player_debug_log_file = f.optional_string_child("PlayerDebugLogFile"); _kdm_debug_log_file = f.optional_string_child("KDMDebugLogFile"); _player_content_directory = f.optional_string_child("PlayerContentDirectory"); - _player_playlist_directory = f.optional_string_child("PlayerPlaylistDirectory"); + if (auto spl_dir = f.optional_string_child("PlayerPlaylistDirectory")) { + ShowPlaylistList spl; + spl.read_legacy(*spl_dir); + } + if (auto spl_file = f.optional_string_child("ShowPlaylistsFile")) { + _show_playlists_file = *spl_file; + } else { + _show_playlists_file = read_path("show_playlists.sqlite3"); + } _player_kdm_directory = f.optional_string_child("PlayerKDMDirectory"); if (f.optional_node_child("AudioMapping")) { @@ -1101,10 +1110,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()); @@ -1679,7 +1686,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 f5e1e3788..c37829146 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -111,7 +111,7 @@ public: SOUND, SOUND_OUTPUT, PLAYER_CONTENT_DIRECTORY, - PLAYER_PLAYLIST_DIRECTORY, + SHOW_PLAYLISTS_FILE, PLAYER_DEBUG_LOG, KDM_DEBUG_LOG, HISTORY, @@ -582,8 +582,8 @@ public: return _player_content_directory; } - boost::optional<boost::filesystem::path> player_playlist_directory() const { - return _player_playlist_directory; + boost::filesystem::path show_playlists_file() const { + return _show_playlists_file; } boost::optional<boost::filesystem::path> player_kdm_directory() const { @@ -1157,16 +1157,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) { @@ -1489,7 +1481,7 @@ private: for playback. */ boost::optional<boost::filesystem::path> _player_content_directory; - boost::optional<boost::filesystem::path> _player_playlist_directory; + boost::filesystem::path _show_playlists_file; boost::optional<boost::filesystem::path> _player_kdm_directory; boost::optional<AudioMapping> _audio_mapping; std::vector<dcp::LanguageTag> _custom_languages; diff --git a/src/lib/show_playlist.cc b/src/lib/show_playlist.cc new file mode 100644 index 000000000..85e420d2c --- /dev/null +++ b/src/lib/show_playlist.cc @@ -0,0 +1,46 @@ +/* + Copyright (C) 2025 Carl Hetherington <cth@carlh.net> + + 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 <http://www.gnu.org/licenses/>. + +*/ + + +#include "show_playlist.h" + + +nlohmann::json +ShowPlaylist::as_json() const +{ + nlohmann::json json; + json["uuid"] = _uuid; + json["name"] = _name; + return json; +} + + +bool +operator==(ShowPlaylist const& a, ShowPlaylist const& b) +{ + return a.uuid() == b.uuid() && a.name() == b.name(); +} + + +bool +operator!=(ShowPlaylist const& a, ShowPlaylist const& b) +{ + return a.uuid() != b.uuid() || a.name() != b.name(); +} diff --git a/src/lib/show_playlist.h b/src/lib/show_playlist.h new file mode 100644 index 000000000..8be2ad380 --- /dev/null +++ b/src/lib/show_playlist.h @@ -0,0 +1,79 @@ +/* + Copyright (C) 2025 Carl Hetherington <cth@carlh.net> + + 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 <http://www.gnu.org/licenses/>. + +*/ + + +#ifndef DCPOMATIC_SHOW_PLAYLIST_H +#define DCPOMATIC_SHOW_PLAYLIST_H + + +#include <dcp/util.h> +#include <nlohmann/json.hpp> + + +/** @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 by ShowPlaylistList. + */ +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; +}; + + +bool operator==(ShowPlaylist const& a, ShowPlaylist const& b); +bool operator!=(ShowPlaylist const& a, ShowPlaylist const& b); + + +#endif diff --git a/src/lib/show_playlist_content_store.cc b/src/lib/show_playlist_content_store.cc index 97a53bb74..1a9f533af 100644 --- a/src/lib/show_playlist_content_store.cc +++ b/src/lib/show_playlist_content_store.cc @@ -26,6 +26,7 @@ #include "examine_content_job.h" #include "job_manager.h" #include "show_playlist_content_store.h" +#include "show_playlist_entry.h" #include "util.h" #include <dcp/cpl.h> #include <dcp/exceptions.h> @@ -116,27 +117,12 @@ ShowPlaylistContentStore::update(std::function<bool()> pulse) shared_ptr<Content> -ShowPlaylistContentStore::get_by_digest(string digest) const +ShowPlaylistContentStore::get(string const& uuid) const { - auto iter = std::find_if(_content.begin(), _content.end(), [digest](shared_ptr<const Content> content) { - return content->digest() == digest; - }); - - if (iter == _content.end()) { - return {}; - } - - return *iter; -} - - -shared_ptr<Content> -ShowPlaylistContentStore::get_by_cpl_id(string id) const -{ - auto iter = std::find_if(_content.begin(), _content.end(), [id](shared_ptr<const Content> content) { + auto iter = std::find_if(_content.begin(), _content.end(), [uuid](shared_ptr<const Content> content) { if (auto dcp = dynamic_pointer_cast<const DCPContent>(content)) { for (auto cpl: dcp::find_and_resolve_cpls(dcp->directories(), true)) { - if (cpl->id() == id) { + if (cpl->id() == uuid) { return true; } } @@ -145,6 +131,12 @@ ShowPlaylistContentStore::get_by_cpl_id(string id) const }); if (iter == _content.end()) { + iter = std::find_if(_content.begin(), _content.end(), [uuid](shared_ptr<const Content> content) { + return content->digest() == uuid; + }); + } + + if (iter == _content.end()) { return {}; } @@ -152,6 +144,13 @@ ShowPlaylistContentStore::get_by_cpl_id(string id) const } +shared_ptr<Content> +ShowPlaylistContentStore::get(ShowPlaylistEntry const& entry) const +{ + return get(entry.uuid()); +} + + ShowPlaylistContentStore* ShowPlaylistContentStore::instance() { diff --git a/src/lib/show_playlist_content_store.h b/src/lib/show_playlist_content_store.h index dbee39902..02e6fc17e 100644 --- a/src/lib/show_playlist_content_store.h +++ b/src/lib/show_playlist_content_store.h @@ -25,6 +25,7 @@ class Content; +class ShowPlaylistEntry; /** @class ShowPlaylistContentStore @@ -33,8 +34,12 @@ class Content; class ShowPlaylistContentStore { public: - std::shared_ptr<Content> get_by_digest(std::string digest) const; - std::shared_ptr<Content> get_by_cpl_id(std::string id) const; + /** @param uuid UUID, which can either be a CPL UUID (for a CPL in a DCP), or + * a digest, for other content. + * @return Content, or nullptr. + */ + std::shared_ptr<Content> get(std::string const& uuid) const; + std::shared_ptr<Content> get(ShowPlaylistEntry const& entry) 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. diff --git a/src/lib/show_playlist_entry.cc b/src/lib/show_playlist_entry.cc new file mode 100644 index 000000000..1e16df788 --- /dev/null +++ b/src/lib/show_playlist_entry.cc @@ -0,0 +1,158 @@ +/* + Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net> + + 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 <http://www.gnu.org/licenses/>. + +*/ + + +#include "dcp_content.h" +#include "dcpomatic_assert.h" +#include "show_playlist_entry.h" +#include <fmt/format.h> + + +using std::dynamic_pointer_cast; +using std::shared_ptr; +using std::string; +using boost::optional; + + +ShowPlaylistEntry::ShowPlaylistEntry(shared_ptr<Content> content, optional<float> crop_to_ratio) + : _kind(dcp::ContentKind::FEATURE) + , _crop_to_ratio(crop_to_ratio) +{ + if (auto dcp = dynamic_pointer_cast<DCPContent>(content)) { + DCPOMATIC_ASSERT(dcp->cpl()); + _uuid = *dcp->cpl(); + _name = dcp->name(); + _kind = dcp->content_kind().get_value_or(dcp::ContentKind::FEATURE); + _encrypted = dcp->encrypted(); + } else { + _uuid = content->digest(); + _name = content->path(0).filename().string(); + _encrypted = false; + } + + auto const hmsf = content->approximate_length().split(24); + _approximate_length = fmt::format("{:02d}:{:02d}:{:02d}", hmsf.h, hmsf.m, hmsf.s); +} + + +ShowPlaylistEntry::ShowPlaylistEntry( + string uuid, + string name, + dcp::ContentKind kind, + string approximate_length, + bool encrypted, + boost::optional<float> crop_to_ratio + ) + : _uuid(std::move(uuid)) + , _name(std::move(name)) + , _kind(std::move(kind)) + , _approximate_length(std::move(approximate_length)) + , _encrypted(encrypted) + , _crop_to_ratio(crop_to_ratio) +{ + +} + + +string +ShowPlaylistEntry::uuid() const +{ + return _uuid; +} + + +string +ShowPlaylistEntry::name() const +{ + return _name; +} + + +dcp::ContentKind +ShowPlaylistEntry::kind() const +{ + return _kind; +} + + +bool +ShowPlaylistEntry::encrypted() const +{ + return _encrypted; +} + + +string +ShowPlaylistEntry::approximate_length() const +{ + return _approximate_length; +} + + +optional<float> +ShowPlaylistEntry::crop_to_ratio() const +{ + return _crop_to_ratio; +} + + +void +ShowPlaylistEntry::set_crop_to_ratio(optional<float> ratio) +{ + _crop_to_ratio = ratio; +} + + +nlohmann::json +ShowPlaylistEntry::as_json() const +{ + nlohmann::json json; + json["uuid"] = _uuid; + json["name"] = _name; + json["kind"] = _kind.name(); + json["encrypted"] = _encrypted; + json["approximate_length"] = _approximate_length; + if (_crop_to_ratio) { + json["crop_to_ratio"] = static_cast<int>(std::round(*_crop_to_ratio * 100)); + } + return json; +} + + + +bool +operator==(ShowPlaylistEntry const& a, ShowPlaylistEntry const& b) +{ + return a.uuid() == b.uuid() && + a.name() == b.name() && + a.kind() == b.kind() && + a.approximate_length() == b.approximate_length() && + a.encrypted() == b.encrypted() && + a.crop_to_ratio() == b.crop_to_ratio(); + +} + + +bool +operator!=(ShowPlaylistEntry const& a, ShowPlaylistEntry const& b) +{ + return !(a == b); +} + diff --git a/src/lib/show_playlist_entry.h b/src/lib/show_playlist_entry.h new file mode 100644 index 000000000..77264f878 --- /dev/null +++ b/src/lib/show_playlist_entry.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net> + + 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 <http://www.gnu.org/licenses/>. + +*/ + + +#ifndef DCPOMATIC_SHOW_PLAYLIST_ENTRY_H +#define DCPOMATIC_SHOW_PLAYLIST_ENTRY_H + + +#include <dcp/content_kind.h> +#include <nlohmann/json.hpp> + + +class Content; + + +/** @class ShowPlaylistEntry + * + * @brief An entry on a show playlist (SPL). + * + * Given a UUID from the database, a ShowPlaylistEntry can be obtained from the + * ShowPlaylistList. + */ +class ShowPlaylistEntry +{ +public: + ShowPlaylistEntry(std::shared_ptr<Content> content, boost::optional<float> crop_to_ratio); + ShowPlaylistEntry( + std::string uuid, + std::string name, + dcp::ContentKind kind, + std::string approximate_length, + bool encrypted, + boost::optional<float> crop_to_ratio + ); + + 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; + boost::optional<float> crop_to_ratio() const; + + void set_crop_to_ratio(boost::optional<float> ratio); + +private: + std::string _uuid; + std::string _name; + dcp::ContentKind _kind; + std::string _approximate_length; + bool _encrypted; + boost::optional<float> _crop_to_ratio; +}; + + +bool operator==(ShowPlaylistEntry const& a, ShowPlaylistEntry const& b); +bool operator!=(ShowPlaylistEntry const& a, ShowPlaylistEntry const& b); + + +#endif diff --git a/src/lib/spl_entry.h b/src/lib/show_playlist_id.h index 6ec5c7540..489501a77 100644 --- a/src/lib/spl_entry.h +++ b/src/lib/show_playlist_id.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net> + Copyright (C) 2025 Carl Hetherington <cth@carlh.net> This file is part of DCP-o-matic. @@ -19,42 +19,28 @@ */ -#ifndef DCPOMATIC_SPL_ENTRY_H -#define DCPOMATIC_SPL_ENTRY_H +#ifndef DCPOMATIC_SHOW_PLAYLIST_ID +#define DCPOMATIC_SHOW_PLAYLIST_ID -#include <libcxml/cxml.h> -#include <dcp/content_kind.h> -#include <libcxml/cxml.h> +#include "id.h" -namespace xmlpp { - class Element; -} - -class Content; - - -class SPLEntry +/** @class ShowPlaylistID + * + * @brief The SQLite ID (not the UUID) of a ShowPlaylist. + */ +class ShowPlaylistID : public ID { public: - SPLEntry(std::shared_ptr<Content> c, cxml::ConstNodePtr node = {}); - - void as_xml(xmlpp::Element* e) const; - - std::shared_ptr<Content> content; - std::string name; - /** Digest of this content */ - std::string digest; - /** CPL ID */ - boost::optional<std::string> id; - boost::optional<dcp::ContentKind> kind; - bool encrypted = false; - boost::optional<float> crop_to_ratio; - -private: - void construct(std::shared_ptr<Content> content); + explicit ShowPlaylistID(sqlite3_int64 id) + : ID(id) {} + + bool operator<(ShowPlaylistID const& other) const { + return get() < other.get(); + } }; #endif + diff --git a/src/lib/show_playlist_list.cc b/src/lib/show_playlist_list.cc new file mode 100644 index 000000000..d8ec379ce --- /dev/null +++ b/src/lib/show_playlist_list.cc @@ -0,0 +1,417 @@ +/* + Copyright (C) 2025 Carl Hetherington <cth@carlh.net> + + 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 <http://www.gnu.org/licenses/>. + +*/ + + +#include "config.h" +#include "show_playlist.h" +#include "show_playlist_content_store.h" +#include "show_playlist_entry.h" +#include "show_playlist_list.h" +#include "sqlite_statement.h" +#include "sqlite_transaction.h" +#include <dcp/filesystem.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("name", "TEXT"); + _entries.add_column("kind", "TEXT"); + _entries.add_column("approximate_length", "TEXT"); + _entries.add_column("encrypted", "INTEGER"); + _entries.add_column("crop_to_ratio", "REAL"); + _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 ShowPlaylistID(sqlite3_last_insert_rowid(_db.db())); +} + + +void +ShowPlaylistList::update_show_playlist(ShowPlaylistID id, ShowPlaylist const& playlist) +{ + SQLiteStatement statement(_db, _show_playlists.update("WHERE id=?")); + + statement.bind_text(1, playlist.uuid()); + statement.bind_text(2, playlist.name()); + statement.bind_int64(3, id.get()); + + statement.execute(); +} + + +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<pair<ShowPlaylistID, ShowPlaylist>> +show_playlists_from_result(SQLiteStatement& statement) +{ + vector<pair<ShowPlaylistID, ShowPlaylist>> 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<pair<ShowPlaylistID, ShowPlaylist>> +ShowPlaylistList::show_playlists() const +{ + SQLiteStatement statement(_db, _show_playlists.select("ORDER BY name COLLATE unicode ASC")); + return show_playlists_from_result(statement); +} + + +optional<ShowPlaylist> +ShowPlaylistList::show_playlist(ShowPlaylistID id) const +{ + SQLiteStatement statement(_db, _show_playlists.select("WHERE id=?")); + statement.bind_int64(1, id.get()); + auto results = show_playlists_from_result(statement); + if (results.empty()) { + return {}; + } + + return results[0].second; +} + + + +vector<ShowPlaylistEntry> +ShowPlaylistList::entries(std::string const& where, std::function<void (SQLiteStatement&)> bind) const +{ + SQLiteStatement statement( + _db, + fmt::format( + "SELECT entries.uuid,entries.name,entries.kind,entries.approximate_length,entries.encrypted,entries.crop_to_ratio " + "FROM entries " + "JOIN show_playlists ON entries.show_playlist=show_playlists.id " + "{} ORDER BY entries.sort_index", where + ) + ); + + bind(statement); + + vector<ShowPlaylistEntry> output; + + statement.execute([&output](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 6); + output.push_back( + ShowPlaylistEntry( + statement.column_text(0), + statement.column_text(1), + dcp::ContentKind::from_name(statement.column_text(2)), + statement.column_text(3), + statement.column_int64(4), + statement.column_double(5) > 0 ? optional<float>(statement.column_double(5)) : optional<float>() + ) + ); + }); + + return output; +} + + +vector<ShowPlaylistEntry> +ShowPlaylistList::entries(ShowPlaylistID show_playlist_id) const +{ + return entries("WHERE show_playlists.id=?", [show_playlist_id](SQLiteStatement& statement) { statement.bind_int64(1, show_playlist_id.get()); }); +} + + +vector<ShowPlaylistEntry> +ShowPlaylistList::entries(string const& show_playlist_uuid) const +{ + return entries("WHERE show_playlists.uuid=?", [show_playlist_uuid](SQLiteStatement& statement) { statement.bind_text(1, show_playlist_uuid); }); +} + + +void +ShowPlaylistList::add_entry(ShowPlaylistID playlist_id, ShowPlaylistEntry const& entry) +{ + SQLiteTransaction transaction(_db); + + SQLiteStatement find_last_entry(_db, "SELECT MAX(sort_index) FROM entries WHERE show_playlist=?"); + find_last_entry.bind_int64(1, playlist_id.get()); + + int highest_index = 0; + find_last_entry.execute([&highest_index](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 1); + highest_index = statement.column_int64(0); + }); + + SQLiteStatement count_entries(_db, "SELECT COUNT(id) FROM entries WHERE show_playlist=?"); + count_entries.bind_int64(1, playlist_id.get()); + + count_entries.execute([&highest_index](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 1); + if (statement.column_int64(0) == 0) { + highest_index = -1; + } + }); + + SQLiteStatement add_entry(_db, _entries.insert()); + + add_entry.bind_int64(1, playlist_id.get()); + add_entry.bind_text(2, entry.uuid()); + add_entry.bind_text(3, entry.name()); + add_entry.bind_text(4, entry.kind().name()); + add_entry.bind_text(5, entry.approximate_length()); + add_entry.bind_int64(6, entry.encrypted()); + add_entry.bind_double(7, entry.crop_to_ratio().get_value_or(0)); + add_entry.bind_int64(8, highest_index + 1); + + add_entry.execute(); + + transaction.commit(); +} + + +void +ShowPlaylistList::update_entry(ShowPlaylistID playlist_id, int index, ShowPlaylistEntry const& entry) +{ + SQLiteStatement update_entry(_db, _entries.update("WHERE show_playlist=? AND sort_index=?")); + + update_entry.bind_int64(1, playlist_id.get()); + update_entry.bind_text(2, entry.uuid()); + update_entry.bind_text(3, entry.name()); + update_entry.bind_text(4, entry.kind().name()); + update_entry.bind_text(5, entry.approximate_length()); + update_entry.bind_int64(6, entry.encrypted()); + update_entry.bind_double(7, entry.crop_to_ratio().get_value_or(0)); + update_entry.bind_int64(8, index); + update_entry.bind_int64(9, playlist_id.get()); + update_entry.bind_int64(10, index); + + update_entry.execute(); +} + + +void +ShowPlaylistList::remove_entry(ShowPlaylistID playlist_id, int index) +{ + SQLiteTransaction transaction(_db); + + SQLiteStatement delete_entry(_db, _entries.remove("WHERE show_playlist=? AND sort_index=?")); + delete_entry.bind_int64(1, playlist_id.get()); + delete_entry.bind_int64(2, index); + + delete_entry.execute(); + + SQLiteStatement find(_db, "SELECT id FROM entries WHERE show_playlist=? ORDER BY sort_index"); + find.bind_int64(1, playlist_id.get()); + + vector<sqlite3_int64> ids; + find.execute([&ids](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 1); + ids.push_back(statement.column_int64(0)); + }); + + int new_index = 0; + for (auto id: ids) { + SQLiteStatement update(_db, "UPDATE entries SET sort_index=? WHERE id=?"); + update.bind_int64(1, new_index++); + update.bind_int64(2, id); + update.execute(); + } + + transaction.commit(); +} + + +/** Swap the entries at index and index + 1 */ +void +ShowPlaylistList::swap_entries(ShowPlaylistID playlist_id, int index) +{ + 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); + + vector<pair<int64_t, int64_t>> 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(); +} + + +void +ShowPlaylistList::move_entry_up(ShowPlaylistID playlist_id, int index) +{ + DCPOMATIC_ASSERT(index >= 1); + swap_entries(playlist_id, index - 1); +} + + +void +ShowPlaylistList::move_entry_down(ShowPlaylistID playlist_id, int index) +{ + swap_entries(playlist_id, index); +} + + +void +ShowPlaylistList::read_legacy(boost::filesystem::path dir) +{ + auto const store = ShowPlaylistContentStore::instance(); + + for (auto playlist: boost::filesystem::directory_iterator(dir)) { + cxml::Document doc("SPL"); + doc.read_file(dcp::filesystem::fix_long_path(playlist)); + auto const spl_id = add_show_playlist({ doc.string_child("Id"), doc.string_child("Name") }); + for (auto entry: doc.node_children("Entry")) { + auto uuid = entry->optional_string_child("CPL"); + if (!uuid) { + uuid = entry->string_child("Digest"); + } + if (auto content = store->get(*uuid)) { + add_entry(spl_id, ShowPlaylistEntry(content, entry->optional_number_child<float>("CropToRatio"))); + } + } + } +} + + +bool +ShowPlaylistList::missing(string const& playlist_uuid) const +{ + auto store = ShowPlaylistContentStore::instance(); + for (auto entry: entries(playlist_uuid)) { + if (!store->get(entry)) { + return true; + } + } + + return false; +} + + +bool +ShowPlaylistList::missing(ShowPlaylistID playlist_id) const +{ + auto store = ShowPlaylistContentStore::instance(); + for (auto entry: entries(playlist_id)) { + if (!store->get(entry)) { + return true; + } + } + + return false; +} + + +optional<ShowPlaylistID> +ShowPlaylistList::get_show_playlist_id(string const& playlist_uuid) const +{ + SQLiteStatement statement(_db, "SELECT id FROM show_playlists WHERE uuid=?"); + statement.bind_text(1, playlist_uuid); + + optional<ShowPlaylistID> id; + statement.execute([&id](SQLiteStatement& statement) { + DCPOMATIC_ASSERT(statement.data_count() == 1); + id = ShowPlaylistID(statement.column_int64(0)); + }); + + return id; +} + diff --git a/src/lib/show_playlist_list.h b/src/lib/show_playlist_list.h new file mode 100644 index 000000000..c1849f589 --- /dev/null +++ b/src/lib/show_playlist_list.h @@ -0,0 +1,97 @@ +/* + Copyright (C) 2025 Carl Hetherington <cth@carlh.net> + + 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 <http://www.gnu.org/licenses/>. + +*/ + + +#ifndef DCPOMATIC_SHOW_PLAYLIST_LIST_H +#define DCPOMATIC_SHOW_PLAYLIST_LIST_H + + +#include "show_playlist_entry.h" +#include "show_playlist_id.h" +#include "sqlite_database.h" +#include "sqlite_table.h" + + +class ShowPlaylist; +class SQLiteStatement; + + +/** @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. + * + * There are two tables: show_playlists and entries. show_playlists contains + * just playlist UUIDs with their names. + */ +class ShowPlaylistList +{ +public: + ShowPlaylistList(); + explicit ShowPlaylistList(boost::filesystem::path db_file); + + /** Write a ShowPlaylist to the database, returning its new SQLite ID */ + ShowPlaylistID add_show_playlist(ShowPlaylist const& show_playlist); + void update_show_playlist(ShowPlaylistID id, ShowPlaylist const& show_playlist); + void remove_show_playlist(ShowPlaylistID id); + + std::vector<std::pair<ShowPlaylistID, ShowPlaylist>> show_playlists() const; + boost::optional<ShowPlaylist> show_playlist(ShowPlaylistID id) const; + + boost::optional<ShowPlaylistID> get_show_playlist_id(std::string const& show_playlist_uuid) const; + + /** @return The entries on a given show playlist, given the playlist's SQLite ID */ + std::vector<ShowPlaylistEntry> entries(ShowPlaylistID show_playlist_id) const; + /** @return The entries on a given show playlist, given the playlist's UUID */ + std::vector<ShowPlaylistEntry> entries(std::string const& show_playlist_uuid) const; + + bool missing(std::string const& show_playlist_uuid) const; + bool missing(ShowPlaylistID id) const; + + /** Write a playlist entry to the database */ + void add_entry(ShowPlaylistID, ShowPlaylistEntry const& entry); + /** Set the values in the database from entry */ + void update_entry(ShowPlaylistID, int index, ShowPlaylistEntry const& entry); + /** Remove a playlist entry from the database */ + void remove_entry(ShowPlaylistID, int index); + /** Move the given playlist entry one place higher (earlier) */ + void move_entry_up(ShowPlaylistID, int index); + /** Move the given playlist entry one place lower (later) */ + void move_entry_down(ShowPlaylistID, int index); + + void read_legacy(boost::filesystem::path dir); + +private: + void setup_tables(); + void setup(); + std::vector<ShowPlaylistEntry> entries(std::string const& where, std::function<void (SQLiteStatement&)> bind) const; + void swap_entries(ShowPlaylistID playlist_id, int index); + + 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 e958bf7c9..000000000 --- a/src/lib/spl.cc +++ /dev/null @@ -1,76 +0,0 @@ -/* - Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net> - - 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 <http://www.gnu.org/licenses/>. - -*/ - - -#include "show_playlist_content_store.h" -#include "spl.h" -#include <libcxml/cxml.h> -#include <dcp/filesystem.h> -#include <dcp/warnings.h> -LIBDCP_DISABLE_WARNINGS -#include <libxml++/libxml++.h> -LIBDCP_ENABLE_WARNINGS -#include <iostream> - - -using std::cout; -using std::string; -using std::shared_ptr; - - -void -SPL::read(boost::filesystem::path path, ShowPlaylistContentStore* 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")) { - if (auto cpl = i->optional_string_child("CPL")) { - if (auto c = store->get_by_cpl_id(*cpl)) { - add(SPLEntry(c, i)); - } else { - _missing = true; - } - } else { - if (auto c = store->get_by_digest(i->string_child("Digest"))) { - add(SPLEntry(c, i)); - } 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()); -} diff --git a/src/lib/spl.h b/src/lib/spl.h deleted file mode 100644 index 9d1a9fdbc..000000000 --- a/src/lib/spl.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - Copyright (C) 2018-2020 Carl Hetherington <cth@carlh.net> - - 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 <http://www.gnu.org/licenses/>. - -*/ - - -#ifndef DCPOMATIC_SPL_H -#define DCPOMATIC_SPL_H - - -#include "spl_entry.h" -#include <dcp/util.h> -LIBDCP_DISABLE_WARNINGS -#include <boost/signals2.hpp> -LIBDCP_ENABLE_WARNINGS -#include <algorithm> - - -class ShowPlaylistContentStore; - - -class SPL -{ -public: - SPL() - : _id(dcp::make_uuid()) - {} - - 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); - } - - std::vector<SPLEntry> const & get() const { - return _spl; - } - - SPLEntry const& get(std::size_t index) const { - return _spl[index]; - } - - void set(std::size_t index, SPLEntry entry) { - _spl[index] = std::move(entry); - } - - void swap(size_t a, size_t b) { - std::iter_swap(_spl.begin() + a, _spl.begin() + b); - } - - void read(boost::filesystem::path path, ShowPlaylistContentStore* 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; - } - - bool missing() const { - return _missing; - } - -private: - std::string _id; - std::string _name; - std::vector<SPLEntry> _spl; - /** true if any content was missing when read() was last called on this SPL */ - bool _missing = false; -}; - - -class SignalSPL : public SPL -{ -public: - enum class Change { - NAME, - CONTENT, - }; - - SignalSPL() {} - - 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 swap(size_t a, size_t b) { - SPL::swap(a, b); - Changed(Change::CONTENT); - } - - void set(std::size_t index, SPLEntry entry) { - SPL::set(index, entry); - Changed(Change::CONTENT); - } - - boost::signals2::signal<void (Change)> Changed; -}; - -#endif diff --git a/src/lib/spl_entry.cc b/src/lib/spl_entry.cc deleted file mode 100644 index 2e6860235..000000000 --- a/src/lib/spl_entry.cc +++ /dev/null @@ -1,70 +0,0 @@ -/* - Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net> - - 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 <http://www.gnu.org/licenses/>. - -*/ - - -#include "dcp_content.h" -#include "dcpomatic_assert.h" -#include "spl_entry.h" -#include <dcp/warnings.h> -#include <fmt/format.h> -LIBDCP_DISABLE_WARNINGS -#include <libxml++/libxml++.h> -LIBDCP_ENABLE_WARNINGS - - -using std::dynamic_pointer_cast; -using std::shared_ptr; - - -SPLEntry::SPLEntry(shared_ptr<Content> c, cxml::ConstNodePtr node) - : content(c) - , digest(content->digest()) -{ - if (auto dcp = dynamic_pointer_cast<DCPContent>(content)) { - 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; - } - - if (node) { - if (auto crop = node->optional_number_child<float>("CropToRatio")) { - crop_to_ratio = *crop; - } - } -} - - -void -SPLEntry::as_xml(xmlpp::Element* e) const -{ - if (id) { - cxml::add_text_child(e, "CPL", *id); - } else { - cxml::add_text_child(e, "Digest", digest); - } - if (crop_to_ratio) { - cxml::add_text_child(e, "CropToRatio", fmt::to_string(*crop_to_ratio)); - } -} diff --git a/src/lib/sqlite_statement.cc b/src/lib/sqlite_statement.cc index d130da507..72d33d1b4 100644 --- a/src/lib/sqlite_statement.cc +++ b/src/lib/sqlite_statement.cc @@ -69,6 +69,16 @@ SQLiteStatement::bind_int64(int index, int64_t value) void +SQLiteStatement::bind_double(int index, double value) +{ + auto rc = sqlite3_bind_double(_stmt, index, value); + if (rc != SQLITE_OK) { + throw SQLError(_db, rc); + } +} + + +void SQLiteStatement::execute(function<void(SQLiteStatement&)> row, function<void()> busy) { while (true) { @@ -104,6 +114,13 @@ SQLiteStatement::column_int64(int index) } +double +SQLiteStatement::column_double(int index) +{ + return sqlite3_column_double(_stmt, index); +} + + string SQLiteStatement::column_text(int index) { diff --git a/src/lib/sqlite_statement.h b/src/lib/sqlite_statement.h index f1131e899..942348e1a 100644 --- a/src/lib/sqlite_statement.h +++ b/src/lib/sqlite_statement.h @@ -38,9 +38,11 @@ public: void bind_text(int index, std::string const& value); void bind_int64(int index, int64_t value); + void bind_double(int index, double value); int64_t column_int64(int index); std::string column_text(int index); + double column_double(int index); void execute(std::function<void(SQLiteStatement&)> row = std::function<void(SQLiteStatement& statement)>(), std::function<void()> busy = std::function<void()>()); diff --git a/src/lib/sqlite_table.cc b/src/lib/sqlite_table.cc index 81843ee00..79c6f99d0 100644 --- a/src/lib/sqlite_table.cc +++ b/src/lib/sqlite_table.cc @@ -76,3 +76,11 @@ SQLiteTable::select(string const& condition) const { return fmt::format("SELECT id,{} FROM {} {}", join_strings(_columns, ","), _name, condition); } + + +string +SQLiteTable::remove(string const& condition) const +{ + DCPOMATIC_ASSERT(!_columns.empty()); + return fmt::format("DELETE FROM {} {}", _name, condition); +} diff --git a/src/lib/sqlite_table.h b/src/lib/sqlite_table.h index 43c9491ed..218ab4526 100644 --- a/src/lib/sqlite_table.h +++ b/src/lib/sqlite_table.h @@ -42,6 +42,7 @@ public: std::string insert() const; std::string update(std::string const& condition) const; std::string select(std::string const& condition) const; + std::string remove(std::string const& condition) const; private: std::string _name; diff --git a/src/lib/wscript b/src/lib/wscript index 4478c2587..ed55ec4d0 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -195,10 +195,11 @@ sources = """ send_problem_report_job.cc server.cc show_playlist_content_store.cc + show_playlist_entry.cc + show_playlist_list.cc + show_playlist.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_playlist.cc b/src/tools/dcpomatic_playlist.cc index 053927232..9483bb273 100644 --- a/src/tools/dcpomatic_playlist.cc +++ b/src/tools/dcpomatic_playlist.cc @@ -24,7 +24,7 @@ #include "wx/dcpomatic_button.h" #include "wx/i18n_setup.h" #include "wx/playlist_editor_config_dialog.h" -#include "wx/spl_entry_dialog.h" +#include "wx/show_playlist_entry_dialog.h" #include "wx/wx_signal_manager.h" #include "wx/wx_util.h" #include "wx/wx_variant.h" @@ -33,8 +33,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 <dcp/filesystem.h> #include <dcp/warnings.h> LIBDCP_DISABLE_WARNINGS @@ -63,16 +64,6 @@ using namespace boost::placeholders; #endif -static -void -save_playlist(shared_ptr<const SPL> playlist) -{ - if (auto dir = Config::instance()->player_playlist_directory()) { - playlist->write(*dir / (playlist->id() + ".xml")); - } -} - - class ContentDialog : public wxDialog { public: @@ -113,9 +104,9 @@ private: class PlaylistList { public: - PlaylistList(wxPanel* parent) + PlaylistList(wxPanel* parent, shared_ptr<ShowPlaylistList> playlists) : _sizer(new wxBoxSizer(wxVERTICAL)) - , _parent(parent) + , _playlists(playlists) { auto label = new wxStaticText(parent, wxID_ANY, wxEmptyString); label->SetLabelMarkup(_("<b>Playlists</b>")); @@ -140,7 +131,7 @@ public: _sizer->Add(list); - load_playlists(); + add_playlists_to_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)); @@ -155,16 +146,16 @@ public: return _sizer; } - shared_ptr<SignalSPL> first_playlist() const + void name_changed() { - if (_playlists.empty()) { - return {}; - } - - return _playlists.front(); + /* The order might have changed, so just re-add everything */ + add_playlists_to_view(); } - boost::signals2::signal<void (shared_ptr<SignalSPL>)> Edit; + /** Signalled as a request to start editing the given playlist, or no playlist if + * the optional is empty. + */ + boost::signals2::signal<void (boost::optional<ShowPlaylistID>)> Edit; private: void setup_sensitivity() @@ -172,119 +163,63 @@ private: _delete->Enable(static_cast<bool>(selected())); } - void add_playlist_to_view(shared_ptr<const SignalSPL> playlist) + void add_playlist_to_view(ShowPlaylistID id, ShowPlaylist playlist) { wxListItem item; item.SetId(_list->GetItemCount()); + item.SetData(id.get()); long const N = _list->InsertItem(item); - _list->SetItem(N, 0, std_to_wx(playlist->name())); - } - - void add_playlist_to_model(shared_ptr<SignalSPL> playlist) - { - _playlists.push_back(playlist); - playlist->Changed.connect(bind(&PlaylistList::changed, this, weak_ptr<SignalSPL>(playlist), _1)); - } - - void changed(weak_ptr<SignalSPL> wp, SignalSPL::Change change) - { - auto playlist = wp.lock(); - if (!playlist) { - return; - } - - switch (change) { - case SignalSPL::Change::NAME: - { - int N = 0; - for (auto i: _playlists) { - if (i == playlist) { - _list->SetItem(N, 0, std_to_wx(i->name())); - } - ++N; - } - break; - } - case SignalSPL::Change::CONTENT: - save_playlist(playlist); - break; - } + _list->SetItem(N, 0, std_to_wx(playlist.name())); } - void load_playlists() + void add_playlists_to_view() { - auto path = Config::instance()->player_playlist_directory(); - if (!path) { - return; - } - _list->DeleteAllItems(); - _playlists.clear(); - try { - for (auto i: dcp::filesystem::directory_iterator(*path)) { - auto spl = make_shared<SignalSPL>(); - try { - spl->read(i, ShowPlaylistContentStore::instance()); - add_playlist_to_model(spl); - } catch (...) {} - } - } catch (...) {} - - for (auto i: _playlists) { - add_playlist_to_view(i); + for (auto playlist: _playlists->show_playlists()) { + add_playlist_to_view(playlist.first, playlist.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; - } - - auto spl = std::make_shared<SignalSPL>(wx_to_std(_("New Playlist"))); - add_playlist_to_model(spl); - add_playlist_to_view(spl); + ShowPlaylist spl(wx_to_std(_("New Playlist"))); + auto const id = _playlists->add_show_playlist(spl); + add_playlist_to_view(id, spl); _list->SetItemState(_list->GetItemCount() - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + Edit(id); } - boost::optional<int> selected() const + /** @return index and ShowPlaylistID (i.e. SQLite ID) of selected playlist */ + boost::optional<std::pair<int, ShowPlaylistID>> selected() const { long int selected = _list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - if (selected < 0 || selected >= int(_playlists.size())) { + if (selected < 0 || selected >= _list->GetItemCount()) { return {}; } - return selected; + return make_pair(static_cast<int>(selected), ShowPlaylistID(static_cast<sqlite3_int64>(_list->GetItemData(selected)))); } void delete_playlist() { - auto index = selected(); - if (!index) { + auto const id = selected(); + if (!id) { return; } - auto dir = Config::instance()->player_playlist_directory(); - if (!dir) { - return; - } + _playlists->remove_show_playlist(id->second); + _list->DeleteItem(id->first); - dcp::filesystem::remove(*dir / (_playlists[*index]->id() + ".xml")); - _list->DeleteItem(*index); - _playlists.erase(_playlists.begin() + *index); - - Edit(shared_ptr<SignalSPL>()); + Edit(optional<ShowPlaylistID>()); } 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<SignalSPL>()); + if (selected < 0 || selected >= int(_playlists->show_playlists().size())) { + Edit(optional<ShowPlaylistID>()); } else { - Edit(_playlists[selected]); + Edit(_playlists->show_playlists()[selected].first); } setup_sensitivity(); @@ -294,18 +229,19 @@ private: wxListCtrl* _list; wxButton* _new; wxButton* _delete; - vector<shared_ptr<SignalSPL>> _playlists; - wxWindow* _parent; + std::shared_ptr<ShowPlaylistList> _playlists; }; +/** Editor for the contents of a playlist */ class PlaylistContent { public: - PlaylistContent(wxPanel* parent, ContentDialog* content_dialog) + PlaylistContent(wxPanel* parent, ContentDialog* content_dialog, shared_ptr<ShowPlaylistList> playlists) : _content_dialog(content_dialog) , _parent(parent) , _sizer(new wxBoxSizer(wxVERTICAL)) + , _playlists(playlists) { auto title = new wxBoxSizer(wxHORIZONTAL); auto label = new wxStaticText(parent, wxID_ANY, wxEmptyString); @@ -365,33 +301,40 @@ public: return _sizer; } - void set(shared_ptr<SignalSPL> playlist) + /** Set the playlist to edit */ + void set(optional<ShowPlaylistID> playlist) { _playlist = playlist; _list->DeleteAllItems(); if (_playlist) { - for (auto i: _playlist->get()) { - add(i); + if (auto spl = _playlists->show_playlist(*_playlist)) { + for (auto i: _playlists->entries(*_playlist)) { + add(i); + } + _name->SetValue(std_to_wx(spl->name())); } - _name->SetValue(std_to_wx(_playlist->name())); } else { _name->SetValue({}); } setup_sensitivity(); } - shared_ptr<SignalSPL> playlist() const + optional<ShowPlaylistID> playlist() const { return _playlist; } + boost::signals2::signal<void ()> NameChanged; private: void save_name_clicked() { if (_playlist) { - _playlist->set_name(wx_to_std(_name->GetValue())); - save_playlist(_playlist); + if (auto spl = _playlists->show_playlist(*_playlist)) { + spl->set_name(wx_to_std(_name->GetValue())); + _playlists->update_show_playlist(*_playlist, *spl); + NameChanged(); + } } setup_sensitivity(); } @@ -401,21 +344,21 @@ private: setup_sensitivity(); } - void add(SPLEntry e) + void add(ShowPlaylistEntry entry) { wxListItem item; item.SetId(_list->GetItemCount()); long const N = _list->InsertItem(item); - set_item(N, e); + set_item(N, entry); } - 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.get_value_or(""))); - _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, 4, e.crop_to_ratio ? wxString::Format(char_to_wx("%.2f"), *e.crop_to_ratio) : char_to_wx("")); + _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")); + _list->SetItem(N, 4, e.crop_to_ratio() ? wxString::Format(char_to_wx("%.2f"), *e.crop_to_ratio()) : char_to_wx("")); } void setup_sensitivity() @@ -424,10 +367,18 @@ 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())); + + bool can_save_name = false; + if (_playlist) { + if (auto spl = _playlists->show_playlist(*_playlist)) { + can_save_name = spl->name() != wx_to_std(_name->GetValue()); + } + } + _save_name->Enable(can_save_name); + _list->Enable(have_list); _up->Enable(have_list && selected > 0); - _down->Enable(have_list && selected != -1 && selected <(_list->GetItemCount() - 1)); + _down->Enable(have_list && selected != -1 && selected < (_list->GetItemCount() - 1)); _add->Enable(have_list); _remove->Enable(have_list && num_selected > 0); _edit->Enable(have_list && num_selected > 0); @@ -435,14 +386,12 @@ private: void add_clicked() { - int const r = _content_dialog->ShowModal(); - if (r == wxID_OK) { - auto content = _content_dialog->selected(); - if (content) { - SPLEntry e(content); - add(e); + if (_content_dialog->ShowModal() == wxID_OK) { + if (auto content = _content_dialog->selected()) { + ShowPlaylistEntry entry(content, {}); + add(entry); DCPOMATIC_ASSERT(_playlist); - _playlist->add(e); + _playlists->add_entry(*_playlist, entry); } } } @@ -456,10 +405,11 @@ private: DCPOMATIC_ASSERT(_playlist); - _playlist->swap(s, s - 1); + _playlists->move_entry_up(*_playlist, s); - set_item(s - 1, _playlist->get(s - 1)); - set_item(s, _playlist->get(s)); + auto entries = _playlists->entries(*_playlist); + set_item(s - 1, entries[s - 1]); + set_item(s, entries[s]); } void down_clicked() @@ -471,10 +421,11 @@ private: DCPOMATIC_ASSERT(_playlist); - _playlist->swap(s, s + 1); + _playlists->move_entry_down(*_playlist, s); - set_item(s + 1, _playlist->get(s + 1)); - set_item(s, _playlist->get(s)); + auto entries = _playlists->entries(*_playlist); + set_item(s + 1, entries[s + 1]); + set_item(s, entries[s]); } void remove_clicked() @@ -485,7 +436,7 @@ private: } DCPOMATIC_ASSERT(_playlist); - _playlist->remove(s); + _playlists->remove_entry(*_playlist, s); _list->DeleteItem(s); } @@ -496,10 +447,13 @@ private: return; } - SPLEntryDialog dialog(_parent, _playlist->get(s)); + DCPOMATIC_ASSERT(_playlist); + auto const entry = _playlists->entries(*_playlist)[s]; + + ShowPlaylistEntryDialog dialog(_parent, entry); if (dialog.ShowModal() == wxID_OK) { - _playlist->set(s, dialog.get()); - set_item(s, _playlist->get(s)); + _playlists->update_entry(*_playlist, s, dialog.get()); + set_item(s, _playlists->entries(*_playlist)[s]); } } @@ -514,7 +468,8 @@ private: wxButton* _add; wxButton* _remove; wxButton* _edit; - shared_ptr<SignalSPL> _playlist; + boost::optional<ShowPlaylistID> _playlist; + std::shared_ptr<ShowPlaylistList> _playlists; }; @@ -525,6 +480,7 @@ public: : wxFrame(nullptr, wxID_ANY, title) , _content_dialog(new ContentDialog(this)) , _config_dialog(nullptr) + , _playlists(make_shared<ShowPlaylistList>()) { auto bar = new wxMenuBar; setup_menu(bar); @@ -536,8 +492,8 @@ public: auto overall_panel = new wxPanel(this, wxID_ANY); auto sizer = new wxBoxSizer(wxVERTICAL); - _playlist_list = new PlaylistList(overall_panel); - _playlist_content = new PlaylistContent(overall_panel, _content_dialog); + _playlist_list = new PlaylistList(overall_panel, _playlists); + _playlist_content = new PlaylistContent(overall_panel, _content_dialog, _playlists); sizer->Add(_playlist_list->sizer()); sizer->Add(_playlist_content->sizer()); @@ -545,6 +501,7 @@ public: overall_panel->SetSizer(sizer); _playlist_list->Edit.connect(bind(&DOMFrame::change_playlist, this, _1)); + _playlist_content->NameChanged.connect(bind(&DOMFrame::playlist_name_changed, this)); Bind(wxEVT_MENU, boost::bind(&DOMFrame::file_exit, this), wxID_EXIT); Bind(wxEVT_MENU, boost::bind(&DOMFrame::help_about, this), wxID_ABOUT); @@ -575,15 +532,17 @@ private: _config_dialog->Show(this); } - void change_playlist(shared_ptr<SignalSPL> playlist) + /** Start editing a different playlist, or no playlist if the optional is empty */ + void change_playlist(optional<ShowPlaylistID> playlist) { - auto old = _playlist_content->playlist(); - if (old) { - save_playlist(old); - } _playlist_content->set(playlist); } + void playlist_name_changed() + { + _playlist_list->name_changed(); + } + void setup_menu(wxMenuBar* m) { #ifdef DCPOMATIC_OSX @@ -625,9 +584,10 @@ private: this, wxString::Format( _("Could not write to config file at %s. Your changes have not been saved."), - std_to_wx(Config::instance()->cinemas_file().string()).data() - ) - ); + std_to_wx(Config::config_write_file().string()).data() + ) + ); + } } @@ -636,6 +596,7 @@ private: PlaylistContent* _playlist_content; wxPreferencesEditor* _config_dialog; boost::signals2::scoped_connection _config_changed_connection; + std::shared_ptr<ShowPlaylistList> _playlists; }; diff --git a/src/wx/file_picker_ctrl.h b/src/wx/file_picker_ctrl.h index 57363b0c7..df68588b8 100644 --- a/src/wx/file_picker_ctrl.h +++ b/src/wx/file_picker_ctrl.h @@ -25,6 +25,9 @@ LIBDCP_DISABLE_WARNINGS LIBDCP_ENABLE_WARNINGS #include <boost/filesystem.hpp> #include <boost/optional.hpp> +LIBDCP_DISABLE_WARNINGS +#include <boost/signals2.hpp> +LIBDCP_ENABLE_WARNINGS class FilePickerCtrl : public wxPanel @@ -45,6 +48,13 @@ public: void set_path(boost::optional<boost::filesystem::path> path); void set_wildcard(wxString); + template <typename... Args> + void bind(Args... args) { + Changed.connect(boost::bind(std::forward<Args>(args)...)); + } + + boost::signals2::signal<void ()> Changed; + private: void browse_clicked (); void set_filename(boost::optional<std::string> filename); diff --git a/src/wx/locations_preferences_page.cc b/src/wx/locations_preferences_page.cc index f77f88fb4..0eaa5dca0 100644 --- a/src/wx/locations_preferences_page.cc +++ b/src/wx/locations_preferences_page.cc @@ -19,6 +19,7 @@ */ +#include "file_picker_ctrl.h" #include "locations_preferences_page.h" #include "wx_util.h" #include <wx/filepicker.h> @@ -68,9 +69,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"), char_to_wx("*.sqlite3"), false, true, "ShowPlaylistsPath"); + table->Add(_show_playlists_file, wxGBPosition(r, 1)); ++r; add_label_to_sizer(table, _panel, _("KDM directory"), true, wxGBPosition(r, 0)); @@ -79,7 +80,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(&LocationsPage::show_playlists_file_changed, this); _kdm_directory->Bind(wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this)); } @@ -91,9 +92,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()); } @@ -106,9 +105,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/locations_preferences_page.h b/src/wx/locations_preferences_page.h index 8bb9e3e5a..cd3f3593f 100644 --- a/src/wx/locations_preferences_page.h +++ b/src/wx/locations_preferences_page.h @@ -22,6 +22,7 @@ #include "preferences_page.h" +class FilePickerCtrl; class wxDirPickerCtrl; @@ -44,11 +45,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/playlist_controls.cc b/src/wx/playlist_controls.cc index 0356738e1..380831ab8 100644 --- a/src/wx/playlist_controls.cc +++ b/src/wx/playlist_controls.cc @@ -34,6 +34,8 @@ #include "lib/internet.h" #include "lib/player_video.h" #include "lib/scoped_temporary.h" +#include "lib/show_playlist.h" +#include "lib/show_playlist_list.h" #include <dcp/exceptions.h> #include <dcp/warnings.h> LIBDCP_DISABLE_WARNINGS @@ -118,10 +120,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(); } @@ -219,7 +221,7 @@ PlaylistControls::previous_clicked() bool PlaylistControls::can_do_next() { - return _selected_playlist && (_selected_playlist_position + 1) < int(_playlists[*_selected_playlist].get().size()); + return _selected_playlist && (_selected_playlist_position + 1) < static_cast<int>(_playlists->entries(*_selected_playlist).size()); } @@ -236,15 +238,19 @@ PlaylistControls::next_clicked() void -PlaylistControls::add_playlist_to_list(SPL spl) +PlaylistControls::add_playlist_to_list(ShowPlaylist spl) { int const N = _spl_view->GetItemCount(); wxListItem it; it.SetId(N); it.SetColumn(0); + auto id = _playlists->get_show_playlist_id(spl.uuid()); + DCPOMATIC_ASSERT(id); + it.SetData(id->get()); string t = spl.name(); - if (spl.missing()) { + + if (_playlists->missing(spl.uuid())) { t += " (content missing)"; } it.SetText(std_to_wx(t)); @@ -253,33 +259,15 @@ PlaylistControls::add_playlist_to_list(SPL spl) void -PlaylistControls::update_playlist_directory() +PlaylistControls::update_playlists() { using namespace boost::filesystem; _spl_view->DeleteAllItems(); - optional<path> dir = Config::instance()->player_playlist_directory(); - if (!dir) { - return; - } - - _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(), ShowPlaylistContentStore::instance()); - _playlists.push_back(spl); - } - } catch (exception& e) { - /* Never mind */ - } - } + _playlists.reset(new ShowPlaylistList()); - sort(_playlists.begin(), _playlists.end(), [](SPL const& a, SPL const& b) { return a.name() < b.name(); }); - for (auto i: _playlists) { - add_playlist_to_list(i); + for (auto i: _playlists->show_playlists()) { + add_playlist_to_list(i.second); } _selected_playlist = boost::none; @@ -321,29 +309,32 @@ PlaylistControls::spl_selection_changed() return; } - if (_playlists[selected].missing()) { + auto const id = ShowPlaylistID(_spl_view->GetItemData(selected)); + + if (_playlists->missing(id)) { error_dialog(this, _("This playlist cannot be loaded as some content is missing.")); deselect_playlist(); return; } - if (_playlists[selected].get().empty()) { + if (_playlists->entries(id).empty()) { error_dialog(this, _("This playlist is empty.")); return; } - select_playlist(selected, 0); + select_playlist(id, 0); } void -PlaylistControls::select_playlist(int selected, int position) +PlaylistControls::select_playlist(ShowPlaylistID selected, int position) { wxProgressDialog dialog(variant::wx::dcpomatic(), _("Loading playlist and KDMs")); - for (auto const& i: _playlists[selected].get()) { + auto const store = ShowPlaylistContentStore::instance(); + for (auto const& i: _playlists->entries(selected)) { dialog.Pulse(); - auto dcp = dynamic_pointer_cast<DCPContent>(i.content); + auto dcp = dynamic_pointer_cast<DCPContent>(store->get(i)); if (dcp && dcp->needs_kdm()) { optional<dcp::EncryptedKDM> kdm; kdm = get_kdm_from_directory(dcp); @@ -367,11 +358,11 @@ PlaylistControls::select_playlist(int selected, int position) _current_spl_view->DeleteAllItems(); int N = 0; - for (auto i: _playlists[selected].get()) { + for (auto const& i: _playlists->entries(selected)) { wxListItem it; it.SetId(N); it.SetColumn(0); - it.SetText(std_to_wx(i.name)); + it.SetText(std_to_wx(i.name())); _current_spl_view->InsertItem(it); ++N; } @@ -389,10 +380,14 @@ void PlaylistControls::reset_film() { DCPOMATIC_ASSERT(_selected_playlist); + auto film = std::make_shared<Film>(optional<boost::filesystem::path>()); - auto entry = _playlists[*_selected_playlist].get(_selected_playlist_position); - film->add_content(vector<shared_ptr<Content>>{entry.content}); - ResetFilm(film, entry.crop_to_ratio); + + auto const entries = _playlists->entries(*_selected_playlist); + DCPOMATIC_ASSERT(_selected_playlist_position < static_cast<int>(entries.size())); + auto const entry = entries[_selected_playlist_position]; + film->add_content(vector<shared_ptr<Content>>{ShowPlaylistContentStore::instance()->get(entry)}); + ResetFilm(film, entry.crop_to_ratio()); } @@ -403,8 +398,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(); } } @@ -431,7 +426,7 @@ PlaylistControls::viewer_finished() } _selected_playlist_position++; - if (_selected_playlist_position < int(_playlists[*_selected_playlist].get().size())) { + if (_selected_playlist_position < int(_playlists->entries(*_selected_playlist).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 e99e54b65..ea9e79cf4 100644 --- a/src/wx/playlist_controls.h +++ b/src/wx/playlist_controls.h @@ -20,10 +20,12 @@ #include "controls.h" -#include "lib/spl.h" +#include "lib/show_playlist.h" +#include "lib/show_playlist_id.h" class DCPContent; +class ShowPlaylistList; class PlaylistControls : public Controls @@ -46,11 +48,11 @@ 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 select_playlist(ShowPlaylistID selected, int position); void started() override; void stopped() override; void setup_sensitivity() override; @@ -76,7 +78,7 @@ private: wxButton* _refresh_spl_view; wxListCtrl* _current_spl_view; - std::vector<SPL> _playlists; - boost::optional<int> _selected_playlist; + std::unique_ptr<ShowPlaylistList> _playlists; + boost::optional<ShowPlaylistID> _selected_playlist; int _selected_playlist_position; }; diff --git a/src/wx/spl_entry_dialog.cc b/src/wx/show_playlist_entry_dialog.cc index 332b7840c..07e1d2e92 100644 --- a/src/wx/spl_entry_dialog.cc +++ b/src/wx/show_playlist_entry_dialog.cc @@ -21,9 +21,9 @@ #include "check_box.h" #include "ratio_picker.h" -#include "spl_entry_dialog.h" +#include "show_playlist_entry_dialog.h" #include "wx_util.h" -#include "lib/spl.h" +#include "lib/show_playlist_entry.h" #include <wx/wx.h> @@ -34,43 +34,43 @@ using namespace boost::placeholders; -SPLEntryDialog::SPLEntryDialog(wxWindow* parent, SPLEntry entry) +ShowPlaylistEntryDialog::ShowPlaylistEntryDialog(wxWindow* parent, ShowPlaylistEntry entry) : TableDialog(parent, _("Playlist item"), 2, 1, true) , _entry(entry) { add(_("Name"), true); - auto name = _entry.name; + auto name = _entry.name(); #ifdef DCPOMATIC_LINUX boost::replace_all(name, "_", "__"); #endif add(std_to_wx(name), false); - add(_("CPL"), true); - add(std_to_wx(_entry.id.get_value_or("")), false); + add(_("UUID"), true); + add(std_to_wx(_entry.uuid()), false); add(_("Type"), true); - add(std_to_wx(_entry.kind->name()), false); + add(std_to_wx(_entry.kind().name()), false); add(_("Encrypted"), true); - add(_entry.encrypted ? _("Yes") : _("No"), false); + add(_entry.encrypted() ? _("Yes") : _("No"), false); - _crop = new RatioPicker(this, entry.crop_to_ratio); + _crop = new RatioPicker(this, entry.crop_to_ratio()); add(_crop->enable_checkbox(), false); add(_crop, false); layout(); - _crop->Changed.connect(boost::bind(&SPLEntryDialog::crop_changed, this, _1)); + _crop->Changed.connect(boost::bind(&ShowPlaylistEntryDialog::crop_changed, this, _1)); } -SPLEntry -SPLEntryDialog::get() const +ShowPlaylistEntry +ShowPlaylistEntryDialog::get() const { return _entry; } void -SPLEntryDialog::crop_changed(optional<float> ratio) +ShowPlaylistEntryDialog::crop_changed(optional<float> ratio) { - _entry.crop_to_ratio = ratio; + _entry.set_crop_to_ratio(ratio); } diff --git a/src/wx/spl_entry_dialog.h b/src/wx/show_playlist_entry_dialog.h index 63e4ba56f..3b644d5ad 100644 --- a/src/wx/spl_entry_dialog.h +++ b/src/wx/show_playlist_entry_dialog.h @@ -20,23 +20,23 @@ #include "table_dialog.h" -#include "lib/spl.h" +#include "lib/show_playlist_entry.h" class RatioPicker; -class SPLEntryDialog : public TableDialog +class ShowPlaylistEntryDialog : public TableDialog { public: - SPLEntryDialog(wxWindow* parent, SPLEntry entry); + ShowPlaylistEntryDialog(wxWindow* parent, ShowPlaylistEntry entry); - SPLEntry get() const; + ShowPlaylistEntry get() const; private: void crop_changed(boost::optional<float> ratio); - SPLEntry _entry; + ShowPlaylistEntry _entry; RatioPicker* _crop; }; diff --git a/src/wx/wscript b/src/wx/wscript index d3f3db88a..99a6f4814 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -166,7 +166,7 @@ sources = """ simple_video_view.cc smpte_metadata_dialog.cc sound_preferences_page.cc - spl_entry_dialog.cc + show_playlist_entry_dialog.cc standard_controls.cc static_text.cc subtag_list_ctrl.cc |
