Try to improve path handling a bit, and add a few tests.
[dcpomatic.git] / src / lib / film_state.cc
1 /*
2     Copyright (C) 2012 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 /** @file src/film_state.cc
21  *  @brief The state of a Film.  This is separate from Film so that
22  *  state can easily be copied and kept around for reference
23  *  by long-running jobs.  This avoids the jobs getting confused
24  *  by the user changing Film settings during their run.
25  */
26
27 #include <fstream>
28 #include <string>
29 #include <iomanip>
30 #include <sstream>
31 #include <boost/filesystem.hpp>
32 #include "film_state.h"
33 #include "scaler.h"
34 #include "filter.h"
35 #include "format.h"
36 #include "dcp_content_type.h"
37 #include "util.h"
38
39 using namespace std;
40 using namespace boost;
41
42 /** Write state to a stream.
43  *  @param f Stream to write to.
44  */
45 void
46 FilmState::write_metadata (ofstream& f) const
47 {
48         /* User stuff */
49         f << "name " << name << "\n";
50         f << "content " << content << "\n";
51         if (dcp_content_type) {
52                 f << "dcp_content_type " << dcp_content_type->pretty_name () << "\n";
53         }
54         f << "frames_per_second " << frames_per_second << "\n";
55         if (format) {
56                 f << "format " << format->as_metadata () << "\n";
57         }
58         f << "left_crop " << left_crop << "\n";
59         f << "right_crop " << right_crop << "\n";
60         f << "top_crop " << top_crop << "\n";
61         f << "bottom_crop " << bottom_crop << "\n";
62         for (vector<Filter const *>::const_iterator i = filters.begin(); i != filters.end(); ++i) {
63                 f << "filter " << (*i)->id () << "\n";
64         }
65         f << "scaler " << scaler->id () << "\n";
66         f << "dcp_frames " << dcp_frames << "\n";
67
68         f << "dcp_trim_action ";
69         switch (dcp_trim_action) {
70         case CUT:
71                 f << "cut\n";
72                 break;
73         case BLACK_OUT:
74                 f << "black_out\n";
75                 break;
76         }
77         
78         f << "dcp_ab " << (dcp_ab ? "1" : "0") << "\n";
79         f << "audio_gain " << audio_gain << "\n";
80         f << "audio_delay " << audio_delay << "\n";
81         f << "still_duration " << still_duration << "\n";
82
83         /* Cached stuff; this is information about our content; we could
84            look it up each time, but that's slow.
85         */
86         for (vector<int>::const_iterator i = thumbs.begin(); i != thumbs.end(); ++i) {
87                 f << "thumb " << *i << "\n";
88         }
89         f << "width " << size.width << "\n";
90         f << "height " << size.height << "\n";
91         f << "length " << length << "\n";
92         f << "audio_channels " << audio_channels << "\n";
93         f << "audio_sample_rate " << audio_sample_rate << "\n";
94         f << "audio_sample_format " << audio_sample_format_to_string (audio_sample_format) << "\n";
95         f << "content_digest " << content_digest << "\n";
96 }
97
98 /** Read state from a key / value pair.
99  *  @param k Key.
100  *  @param v Value.
101  */
102 void
103 FilmState::read_metadata (string k, string v)
104 {
105         /* User-specified stuff */
106         if (k == "name") {
107                 name = v;
108         } else if (k == "content") {
109                 content = v;
110         } else if (k == "dcp_content_type") {
111                 dcp_content_type = DCPContentType::from_pretty_name (v);
112         } else if (k == "frames_per_second") {
113                 frames_per_second = atof (v.c_str ());
114         } else if (k == "format") {
115                 format = Format::from_metadata (v);
116         } else if (k == "left_crop") {
117                 left_crop = atoi (v.c_str ());
118         } else if (k == "right_crop") {
119                 right_crop = atoi (v.c_str ());
120         } else if (k == "top_crop") {
121                 top_crop = atoi (v.c_str ());
122         } else if (k == "bottom_crop") {
123                 bottom_crop = atoi (v.c_str ());
124         } else if (k == "filter") {
125                 filters.push_back (Filter::from_id (v));
126         } else if (k == "scaler") {
127                 scaler = Scaler::from_id (v);
128         } else if (k == "dcp_frames") {
129                 dcp_frames = atoi (v.c_str ());
130         } else if (k == "dcp_trim_action") {
131                 if (v == "cut") {
132                         dcp_trim_action = CUT;
133                 } else if (v == "black_out") {
134                         dcp_trim_action = BLACK_OUT;
135                 }
136         } else if (k == "dcp_ab") {
137                 dcp_ab = (v == "1");
138         } else if (k == "audio_gain") {
139                 audio_gain = atof (v.c_str ());
140         } else if (k == "audio_delay") {
141                 audio_delay = atoi (v.c_str ());
142         } else if (k == "still_duration") {
143                 still_duration = atoi (v.c_str ());
144         }
145         
146         /* Cached stuff */
147         if (k == "thumb") {
148                 int const n = atoi (v.c_str ());
149                 /* Only add it to the list if it still exists */
150                 if (filesystem::exists (thumb_file_for_frame (n))) {
151                         thumbs.push_back (n);
152                 }
153         } else if (k == "width") {
154                 size.width = atoi (v.c_str ());
155         } else if (k == "height") {
156                 size.height = atoi (v.c_str ());
157         } else if (k == "length") {
158                 length = atof (v.c_str ());
159         } else if (k == "audio_channels") {
160                 audio_channels = atoi (v.c_str ());
161         } else if (k == "audio_sample_rate") {
162                 audio_sample_rate = atoi (v.c_str ());
163         } else if (k == "audio_sample_format") {
164                 audio_sample_format = audio_sample_format_from_string (v);
165         } else if (k == "content_digest") {
166                 content_digest = v;
167         }
168         
169         /* Itsy bitsy hack: compute digest here if don't have one (for backwards compatibility) */
170         if (content_digest.empty() && !content.empty()) {
171                 content_digest = md5_digest (file (content));
172         }
173 }
174
175
176 /** @param n A thumb index.
177  *  @return The path to the thumb's image file.
178  */
179 string
180 FilmState::thumb_file (int n) const
181 {
182         return thumb_file_for_frame (thumb_frame (n));
183 }
184
185 /** @param n A frame index within the Film.
186  *  @return The path to the thumb's image file for this frame;
187  *  we assume that it exists.
188  */
189 string
190 FilmState::thumb_file_for_frame (int n) const
191 {
192         stringstream s;
193         s.width (8);
194         s << setfill('0') << n << ".tiff";
195         
196         filesystem::path p;
197         p /= dir ("thumbs");
198         p /= s.str ();
199                 
200         return p.string ();
201 }
202
203
204 /** @param n A thumb index.
205  *  @return The frame within the Film that it is for.
206  */
207 int
208 FilmState::thumb_frame (int n) const
209 {
210         assert (n < int (thumbs.size ()));
211         return thumbs[n];
212 }
213
214 Size
215 FilmState::cropped_size (Size s) const
216 {
217         s.width -= left_crop + right_crop;
218         s.height -= top_crop + bottom_crop;
219         return s;
220 }
221
222 /** Given a directory name, return its full path within the Film's directory.
223  *  The directory (and its parents) will be created if they do not exist.
224  */
225 string
226 FilmState::dir (string d) const
227 {
228         filesystem::path p;
229         p /= directory;
230         p /= d;
231         filesystem::create_directories (p);
232         return p.string ();
233 }
234
235 /** Given a file or directory name, return its full path within the Film's directory */
236 string
237 FilmState::file (string f) const
238 {
239         filesystem::path p;
240         p /= directory;
241         p /= f;
242         return p.string ();
243 }
244
245 string
246 FilmState::content_path () const
247 {
248         if (filesystem::path(content).has_root_directory ()) {
249                 return content;
250         }
251
252         return file (content);
253 }
254
255 ContentType
256 FilmState::content_type () const
257 {
258 #if BOOST_FILESYSTEM_VERSION == 3
259         string const ext = filesystem::path(content).extension().string();
260 #else
261         string const ext = filesystem::path(content).extension();
262 #endif
263         if (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png") {
264                 return STILL;
265         }
266
267         return VIDEO;
268 }