Split audio; builds.
[dcpomatic.git] / src / wx / content_menu.cc
1 /*
2     Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include "content_menu.h"
21 #include "repeat_dialog.h"
22 #include "wx_util.h"
23 #include "timeline_video_content_view.h"
24 #include "timeline_audio_content_view.h"
25 #include "content_properties_dialog.h"
26 #include "lib/playlist.h"
27 #include "lib/film.h"
28 #include "lib/image_content.h"
29 #include "lib/content_factory.h"
30 #include "lib/examine_content_job.h"
31 #include "lib/job_manager.h"
32 #include "lib/exceptions.h"
33 #include "lib/dcp_content.h"
34 #include "lib/ffmpeg_content.h"
35 #include "lib/audio_content.h"
36 #include <wx/wx.h>
37 #include <wx/dirdlg.h>
38 #include <boost/foreach.hpp>
39 #include <iostream>
40
41 using std::cout;
42 using std::vector;
43 using std::exception;
44 using boost::shared_ptr;
45 using boost::weak_ptr;
46 using boost::dynamic_pointer_cast;
47
48 enum {
49         ID_repeat = 1,
50         ID_join,
51         ID_find_missing,
52         ID_properties,
53         ID_re_examine,
54         ID_kdm,
55         ID_remove
56 };
57
58 ContentMenu::ContentMenu (wxWindow* p)
59         : _menu (new wxMenu)
60         , _parent (p)
61 {
62         _repeat = _menu->Append (ID_repeat, _("Repeat..."));
63         _join = _menu->Append (ID_join, _("Join"));
64         _find_missing = _menu->Append (ID_find_missing, _("Find missing..."));
65         _properties = _menu->Append (ID_properties, _("Properties..."));
66         _re_examine = _menu->Append (ID_re_examine, _("Re-examine..."));
67         _kdm = _menu->Append (ID_kdm, _("Add KDM..."));
68         _menu->AppendSeparator ();
69         _remove = _menu->Append (ID_remove, _("Remove"));
70
71         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::repeat, this), ID_repeat);
72         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::join, this), ID_join);
73         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::find_missing, this), ID_find_missing);
74         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::properties, this), ID_properties);
75         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::re_examine, this), ID_re_examine);
76         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::kdm, this), ID_kdm);
77         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::remove, this), ID_remove);
78 }
79
80 ContentMenu::~ContentMenu ()
81 {
82         delete _menu;
83 }
84
85 void
86 ContentMenu::popup (weak_ptr<Film> film, ContentList c, TimelineContentViewList v, wxPoint p)
87 {
88         _film = film;
89         _content = c;
90         _views = v;
91         _repeat->Enable (!_content.empty ());
92
93         int n = 0;
94         BOOST_FOREACH (shared_ptr<Content> i, _content) {
95                 if (dynamic_pointer_cast<FFmpegContent> (i)) {
96                         ++n;
97                 }
98         }
99
100         _join->Enable (n > 1);
101
102         _find_missing->Enable (_content.size() == 1 && !_content.front()->paths_valid ());
103         _properties->Enable (_content.size() == 1);
104         _re_examine->Enable (!_content.empty ());
105
106         if (_content.size() == 1) {
107                 shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (_content.front ());
108                 _kdm->Enable (dcp && dcp->encrypted ());
109         } else {
110                 _kdm->Enable (false);
111         }
112
113         _remove->Enable (!_content.empty ());
114         _parent->PopupMenu (_menu, p);
115 }
116
117 void
118 ContentMenu::repeat ()
119 {
120         if (_content.empty ()) {
121                 return;
122         }
123
124         RepeatDialog* d = new RepeatDialog (_parent);
125         if (d->ShowModal() != wxID_OK) {
126                 d->Destroy ();
127                 return;
128         }
129
130         shared_ptr<Film> film = _film.lock ();
131         if (!film) {
132                 return;
133         }
134
135         film->repeat_content (_content, d->number ());
136         d->Destroy ();
137
138         _content.clear ();
139         _views.clear ();
140 }
141
142 void
143 ContentMenu::join ()
144 {
145         vector<shared_ptr<Content> > fc;
146         BOOST_FOREACH (shared_ptr<Content> i, _content) {
147                 shared_ptr<FFmpegContent> f = dynamic_pointer_cast<FFmpegContent> (i);
148                 if (f) {
149                         fc.push_back (f);
150                 }
151         }
152
153         DCPOMATIC_ASSERT (fc.size() > 1);
154
155         shared_ptr<Film> film = _film.lock ();
156         if (!film) {
157                 return;
158         }
159
160         try {
161                 shared_ptr<FFmpegContent> joined (new FFmpegContent (film, fc));
162                 BOOST_FOREACH (shared_ptr<Content> i, _content) {
163                         film->remove_content (i);
164                 }
165                 film->add_content (joined);
166         } catch (JoinError& e) {
167                 error_dialog (_parent, std_to_wx (e.what ()));
168         }
169 }
170
171 void
172 ContentMenu::remove ()
173 {
174         if (_content.empty ()) {
175                 return;
176         }
177
178         shared_ptr<Film> film = _film.lock ();
179         if (!film) {
180                 return;
181         }
182
183         /* We are removing from the timeline if _views is not empty */
184         bool handled = false;
185         if (!_views.empty ()) {
186                 /* Special case: we only remove FFmpegContent if its video view is selected;
187                    if not, and its audio view is selected, we unmap the audio.
188                 */
189                 BOOST_FOREACH (shared_ptr<Content> i, _content) {
190                         shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (i);
191                         if (!fc) {
192                                 continue;
193                         }
194
195                         shared_ptr<TimelineVideoContentView> video;
196                         shared_ptr<TimelineAudioContentView> audio;
197
198                         BOOST_FOREACH (shared_ptr<TimelineContentView> j, _views) {
199                                 shared_ptr<TimelineVideoContentView> v = dynamic_pointer_cast<TimelineVideoContentView> (j);
200                                 shared_ptr<TimelineAudioContentView> a = dynamic_pointer_cast<TimelineAudioContentView> (j);
201                                 if (v && v->content() == fc) {
202                                         video = v;
203                                 } else if (a && a->content() == fc) {
204                                         audio = a;
205                                 }
206                         }
207
208                         if (!video && audio) {
209                                 AudioMapping m = fc->audio->audio_mapping ();
210                                 m.unmap_all ();
211                                 fc->audio->set_audio_mapping (m);
212                                 handled = true;
213                         }
214                 }
215         }
216
217         if (!handled) {
218                 film->remove_content (_content);
219         }
220
221         _content.clear ();
222         _views.clear ();
223 }
224
225 void
226 ContentMenu::find_missing ()
227 {
228         if (_content.size() != 1) {
229                 return;
230         }
231
232         shared_ptr<const Film> film = _film.lock ();
233         if (!film) {
234                 return;
235         }
236
237         shared_ptr<Content> content;
238
239         /* XXX: a bit nasty */
240         shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (_content.front ());
241         shared_ptr<DCPContent> dc = dynamic_pointer_cast<DCPContent> (_content.front ());
242
243         int r = wxID_CANCEL;
244         boost::filesystem::path path;
245
246         if ((ic && !ic->still ()) || dc) {
247                 wxDirDialog* d = new wxDirDialog (0, _("Choose a folder"), wxT (""), wxDD_DIR_MUST_EXIST);
248                 r = d->ShowModal ();
249                 path = wx_to_std (d->GetPath ());
250                 d->Destroy ();
251         } else {
252                 wxFileDialog* d = new wxFileDialog (0, _("Choose a file"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE);
253                 r = d->ShowModal ();
254                 path = wx_to_std (d->GetPath ());
255                 d->Destroy ();
256         }
257
258         if (r == wxID_OK) {
259                 content = content_factory (film, path);
260         }
261
262         if (!content) {
263                 return;
264         }
265
266         shared_ptr<Job> j (new ExamineContentJob (film, content));
267
268         _job_connection = j->Finished.connect (
269                 bind (
270                         &ContentMenu::maybe_found_missing,
271                         this,
272                         boost::weak_ptr<Job> (j),
273                         boost::weak_ptr<Content> (_content.front ()),
274                         boost::weak_ptr<Content> (content)
275                         )
276                 );
277
278         JobManager::instance()->add (j);
279 }
280
281 void
282 ContentMenu::re_examine ()
283 {
284         shared_ptr<Film> film = _film.lock ();
285         if (!film) {
286                 return;
287         }
288
289         BOOST_FOREACH (shared_ptr<Content> i, _content) {
290                 film->examine_content (i);
291         }
292 }
293
294 void
295 ContentMenu::maybe_found_missing (weak_ptr<Job> j, weak_ptr<Content> oc, weak_ptr<Content> nc)
296 {
297         shared_ptr<Job> job = j.lock ();
298         if (!job || !job->finished_ok ()) {
299                 return;
300         }
301
302         shared_ptr<Content> old_content = oc.lock ();
303         shared_ptr<Content> new_content = nc.lock ();
304         DCPOMATIC_ASSERT (old_content);
305         DCPOMATIC_ASSERT (new_content);
306
307         if (new_content->digest() != old_content->digest()) {
308                 error_dialog (0, _("The content file(s) you specified are not the same as those that are missing.  Either try again with the correct content file or remove the missing content."));
309                 return;
310         }
311
312         old_content->set_path (new_content->path (0));
313 }
314
315 void
316 ContentMenu::kdm ()
317 {
318         DCPOMATIC_ASSERT (!_content.empty ());
319         shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (_content.front ());
320         DCPOMATIC_ASSERT (dcp);
321
322         wxFileDialog* d = new wxFileDialog (_parent, _("Select KDM"));
323
324         if (d->ShowModal() == wxID_OK) {
325                 try {
326                         dcp->add_kdm (dcp::EncryptedKDM (dcp::file_to_string (wx_to_std (d->GetPath ()))));
327                 } catch (exception& e) {
328                         error_dialog (_parent, wxString::Format (_("Could not load KDM (%s)"), e.what ()));
329                         d->Destroy ();
330                         return;
331                 }
332
333                 shared_ptr<Film> film = _film.lock ();
334                 DCPOMATIC_ASSERT (film);
335                 film->examine_content (dcp);
336         }
337
338         d->Destroy ();
339 }
340
341 void
342 ContentMenu::properties ()
343 {
344         ContentPropertiesDialog* d = new ContentPropertiesDialog (_parent, _content.front ());
345         d->ShowModal ();
346         d->Destroy ();
347 }