Basics of noting subtitle times in FFmpegSubtitleStreams.
[dcpomatic.git] / src / lib / kdm.cc
1 /*
2     Copyright (C) 2013 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 <list>
21 #include <boost/shared_ptr.hpp>
22 #include <quickmail.h>
23 #include <zip.h>
24 #include <dcp/encrypted_kdm.h>
25 #include "kdm.h"
26 #include "cinema.h"
27 #include "exceptions.h"
28 #include "util.h"
29 #include "film.h"
30 #include "config.h"
31
32 using std::list;
33 using std::string;
34 using std::stringstream;
35 using boost::shared_ptr;
36
37 struct ScreenKDM
38 {
39         ScreenKDM (shared_ptr<Screen> s, dcp::EncryptedKDM k)
40                 : screen (s)
41                 , kdm (k)
42         {}
43         
44         shared_ptr<Screen> screen;
45         dcp::EncryptedKDM kdm;
46 };
47
48 static string
49 kdm_filename (shared_ptr<const Film> film, ScreenKDM kdm)
50 {
51         return tidy_for_filename (film->name()) + "_" + tidy_for_filename (kdm.screen->cinema->name) + "_" + tidy_for_filename (kdm.screen->name) + ".kdm.xml";
52 }
53
54 struct CinemaKDMs
55 {
56         shared_ptr<Cinema> cinema;
57         list<ScreenKDM> screen_kdms;
58
59         void make_zip_file (shared_ptr<const Film> film, boost::filesystem::path zip_file) const
60         {
61                 int error;
62                 struct zip* zip = zip_open (zip_file.string().c_str(), ZIP_CREATE | ZIP_EXCL, &error);
63                 if (!zip) {
64                         if (error == ZIP_ER_EXISTS) {
65                                 throw FileError ("ZIP file already exists", zip_file);
66                         }
67                         throw FileError ("could not create ZIP file", zip_file);
68                 }
69                 
70                 list<shared_ptr<string> > kdm_strings;
71                 
72                 for (list<ScreenKDM>::const_iterator i = screen_kdms.begin(); i != screen_kdms.end(); ++i) {
73                         shared_ptr<string> kdm (new string (i->kdm.as_xml ()));
74                         kdm_strings.push_back (kdm);
75                         
76                         struct zip_source* source = zip_source_buffer (zip, kdm->c_str(), kdm->length(), 0);
77                         if (!source) {
78                                 throw StringError ("could not create ZIP source");
79                         }
80                         
81                         if (zip_add (zip, kdm_filename (film, *i).c_str(), source) == -1) {
82                                 throw StringError ("failed to add KDM to ZIP archive");
83                         }
84                 }
85                 
86                 if (zip_close (zip) == -1) {
87                         throw StringError ("failed to close ZIP archive");
88                 }
89         }
90 };
91
92 /* Not complete but sufficient for our purposes (we're using
93    ScreenKDM in a list where all the screens will be unique).
94 */
95 bool
96 operator== (ScreenKDM const & a, ScreenKDM const & b)
97 {
98         return a.screen == b.screen;
99 }
100
101 static list<ScreenKDM>
102 make_screen_kdms (
103         shared_ptr<const Film> film,
104         list<shared_ptr<Screen> > screens,
105         boost::filesystem::path dcp,
106         dcp::LocalTime from,
107         dcp::LocalTime to
108         )
109 {
110         list<dcp::EncryptedKDM> kdms = film->make_kdms (screens, dcp, from, to);
111            
112         list<ScreenKDM> screen_kdms;
113         
114         list<shared_ptr<Screen> >::iterator i = screens.begin ();
115         list<dcp::EncryptedKDM>::iterator j = kdms.begin ();
116         while (i != screens.end() && j != kdms.end ()) {
117                 screen_kdms.push_back (ScreenKDM (*i, *j));
118                 ++i;
119                 ++j;
120         }
121
122         return screen_kdms;
123 }
124
125 static list<CinemaKDMs>
126 make_cinema_kdms (
127         shared_ptr<const Film> film,
128         list<shared_ptr<Screen> > screens,
129         boost::filesystem::path dcp,
130         dcp::LocalTime from,
131         dcp::LocalTime to
132         )
133 {
134         list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, dcp, from, to);
135         list<CinemaKDMs> cinema_kdms;
136
137         while (!screen_kdms.empty ()) {
138                 
139                 /* Get all the screens from a single cinema */
140
141                 CinemaKDMs ck;
142                 
143                 list<ScreenKDM>::iterator i = screen_kdms.begin ();
144                 ck.cinema = i->screen->cinema;
145                 ck.screen_kdms.push_back (*i);
146                 list<ScreenKDM>::iterator j = i;
147                 ++i;
148                 screen_kdms.remove (*j);
149                 
150                 while (i != screen_kdms.end ()) {
151                         if (i->screen->cinema == ck.cinema) {
152                                 ck.screen_kdms.push_back (*i);
153                                 list<ScreenKDM>::iterator j = i;
154                                 ++i;
155                                 screen_kdms.remove (*j);
156                         } else {
157                                 ++i;
158                         }
159                 }
160
161                 cinema_kdms.push_back (ck);
162         }
163
164         return cinema_kdms;
165 }
166
167 /** @param from KDM from time in local time.
168  *  @param to KDM to time in local time.
169  */
170 void
171 write_kdm_files (
172         shared_ptr<const Film> film,
173         list<shared_ptr<Screen> > screens,
174         boost::filesystem::path dcp,
175         dcp::LocalTime from,
176         dcp::LocalTime to,
177         boost::filesystem::path directory
178         )
179 {
180         list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, dcp, from, to);
181
182         /* Write KDMs to the specified directory */
183         for (list<ScreenKDM>::iterator i = screen_kdms.begin(); i != screen_kdms.end(); ++i) {
184                 boost::filesystem::path out = directory;
185                 out /= kdm_filename (film, *i);
186                 i->kdm.as_xml (out);
187         }
188 }
189
190 void
191 write_kdm_zip_files (
192         shared_ptr<const Film> film,
193         list<shared_ptr<Screen> > screens,
194         boost::filesystem::path dcp,
195         dcp::LocalTime from,
196         dcp::LocalTime to,
197         boost::filesystem::path directory
198         )
199 {
200         list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, dcp, from, to);
201
202         for (list<CinemaKDMs>::const_iterator i = cinema_kdms.begin(); i != cinema_kdms.end(); ++i) {
203                 boost::filesystem::path path = directory;
204                 path /= tidy_for_filename (i->cinema->name) + ".zip";
205                 i->make_zip_file (film, path);
206         }
207 }
208
209 void
210 email_kdms (
211         shared_ptr<const Film> film,
212         list<shared_ptr<Screen> > screens,
213         boost::filesystem::path dcp,
214         dcp::LocalTime from,
215         dcp::LocalTime to
216         )
217 {
218         list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, dcp, from, to);
219
220         for (list<CinemaKDMs>::const_iterator i = cinema_kdms.begin(); i != cinema_kdms.end(); ++i) {
221                 
222                 boost::filesystem::path zip_file = boost::filesystem::temp_directory_path ();
223                 zip_file /= boost::filesystem::unique_path().string() + ".zip";
224                 i->make_zip_file (film, zip_file);
225                 
226                 /* Send email */
227                 
228                 quickmail_initialize ();
229                 quickmail mail = quickmail_create (Config::instance()->kdm_from().c_str(), "KDM delivery");
230                 quickmail_add_to (mail, i->cinema->email.c_str ());
231                 
232                 string body = Config::instance()->kdm_email().c_str();
233                 boost::algorithm::replace_all (body, "$CPL_NAME", film->dcp_name ());
234                 stringstream start;
235                 start << from.date() << " " << from.time_of_day();
236                 boost::algorithm::replace_all (body, "$START_TIME", start.str ());
237                 stringstream end;
238                 end << to.date() << " " << to.time_of_day();
239                 boost::algorithm::replace_all (body, "$END_TIME", end.str ());
240
241                 quickmail_set_body (mail, body.c_str());
242                 quickmail_add_attachment_file (mail, zip_file.string().c_str(), "application/zip");
243
244                 int const port = Config::instance()->mail_user().empty() ? 25 : 587;
245
246                 char const* error = quickmail_send (
247                         mail,
248                         Config::instance()->mail_server().c_str(),
249                         port,
250                         Config::instance()->mail_user().c_str(),
251                         Config::instance()->mail_password().c_str()
252                         );
253                 
254                 if (error) {
255                         quickmail_destroy (mail);
256                         throw KDMError (String::compose ("Failed to send KDM email (%1)", error));
257                 }
258                 quickmail_destroy (mail);
259         }
260 }