Simplify streaming API a bit.
[libdcp.git] / src / picture_asset_writer.cc
1 /*
2     Copyright (C) 2012-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 "AS_DCP.h"
21 #include "KM_fileio.h"
22 #include "picture_asset_writer.h"
23 #include "exceptions.h"
24 #include "picture_asset.h"
25
26 using std::istream;
27 using std::ostream;
28 using std::string;
29 using boost::shared_ptr;
30 using namespace libdcp;
31
32 FrameInfo::FrameInfo (istream& s)
33 {
34         s >> offset >> size >> hash;
35 }
36
37 void
38 FrameInfo::write (ostream& s)
39 {
40         s << offset << " " << size << " " << hash;
41 }
42
43
44 PictureAssetWriter::PictureAssetWriter (PictureAsset* asset, bool overwrite, MXFMetadata const & metadata)
45         : _asset (asset)
46         , _frames_written (0)
47         , _started (false)
48         , _finalized (false)
49         , _overwrite (overwrite)
50         , _metadata (metadata)
51 {
52         
53 }
54
55 struct ASDCPStateBase
56 {
57         ASDCPStateBase ()
58                 : frame_buffer (4 * Kumu::Megabyte)
59         {}
60         
61         ASDCP::JP2K::CodestreamParser j2k_parser;
62         ASDCP::JP2K::FrameBuffer frame_buffer;
63         ASDCP::WriterInfo writer_info;
64         ASDCP::JP2K::PictureDescriptor picture_descriptor;
65 };
66
67 struct MonoPictureAssetWriter::ASDCPState : public ASDCPStateBase
68 {
69         ASDCP::JP2K::MXFWriter mxf_writer;
70 };
71
72 struct StereoPictureAssetWriter::ASDCPState : public ASDCPStateBase
73 {
74         ASDCP::JP2K::MXFSWriter mxf_writer;
75 };
76
77 /** @param a Asset to write to.  `a' must not be deleted while
78  *  this writer class still exists, or bad things will happen.
79  */
80 MonoPictureAssetWriter::MonoPictureAssetWriter (PictureAsset* asset, bool overwrite, MXFMetadata const & metadata)
81         : PictureAssetWriter (asset, overwrite, metadata)
82         , _state (new MonoPictureAssetWriter::ASDCPState)
83 {
84
85 }
86
87 StereoPictureAssetWriter::StereoPictureAssetWriter (PictureAsset* asset, bool overwrite, MXFMetadata const & metadata)
88         : PictureAssetWriter (asset, overwrite, metadata)
89         , _state (new StereoPictureAssetWriter::ASDCPState)
90         , _next_eye (EYE_LEFT)
91 {
92
93 }
94
95 template <class P, class Q>
96 void libdcp::start (PictureAssetWriter* writer, shared_ptr<P> state, Q* asset, uint8_t* data, int size)
97 {
98         if (ASDCP_FAILURE (state->j2k_parser.OpenReadFrame (data, size, state->frame_buffer))) {
99                 boost::throw_exception (MiscError ("could not parse J2K frame"));
100         }
101
102         state->j2k_parser.FillPictureDescriptor (state->picture_descriptor);
103         state->picture_descriptor.EditRate = ASDCP::Rational (asset->edit_rate(), 1);
104         
105         asset->fill_writer_info (&state->writer_info, asset->uuid(), writer->_metadata);
106         
107         if (ASDCP_FAILURE (state->mxf_writer.OpenWrite (
108                                    asset->path().string().c_str(),
109                                    state->writer_info,
110                                    state->picture_descriptor,
111                                    16384,
112                                    writer->_overwrite)
113                     )) {
114                 
115                 boost::throw_exception (MXFFileError ("could not open MXF file for writing", asset->path().string()));
116         }
117
118         writer->_started = true;
119 }
120
121 void
122 MonoPictureAssetWriter::start (uint8_t* data, int size)
123 {
124         libdcp::start (this, _state, _asset, data, size);
125 }
126
127 void
128 StereoPictureAssetWriter::start (uint8_t* data, int size)
129 {
130         libdcp::start (this, _state, _asset, data, size);
131 }
132
133 FrameInfo
134 MonoPictureAssetWriter::write (uint8_t* data, int size)
135 {
136         assert (!_finalized);
137
138         if (!_started) {
139                 start (data, size);
140         }
141
142         if (ASDCP_FAILURE (_state->j2k_parser.OpenReadFrame (data, size, _state->frame_buffer))) {
143                 boost::throw_exception (MiscError ("could not parse J2K frame"));
144         }
145
146         uint64_t const before_offset = _state->mxf_writer.Tell ();
147
148         string hash;
149         if (ASDCP_FAILURE (_state->mxf_writer.WriteFrame (_state->frame_buffer, 0, 0, &hash))) {
150                 boost::throw_exception (MXFFileError ("error in writing video MXF", _asset->path().string()));
151         }
152
153         ++_frames_written;
154         return FrameInfo (before_offset, _state->mxf_writer.Tell() - before_offset, hash);
155 }
156
157 /** Write a frame for one eye.  Frames must be written left, then right, then left etc.
158  *  @param data JPEG2000 data.
159  *  @param size Size of data.
160  */
161 FrameInfo
162 StereoPictureAssetWriter::write (uint8_t* data, int size)
163 {
164         assert (!_finalized);
165
166         if (!_started) {
167                 start (data, size);
168         }
169
170         if (ASDCP_FAILURE (_state->j2k_parser.OpenReadFrame (data, size, _state->frame_buffer))) {
171                 boost::throw_exception (MiscError ("could not parse J2K frame"));
172         }
173
174         uint64_t const before_offset = _state->mxf_writer.Tell ();
175
176         string hash;
177         if (ASDCP_FAILURE (
178                     _state->mxf_writer.WriteFrame (
179                             _state->frame_buffer,
180                             _next_eye == EYE_LEFT ? ASDCP::JP2K::SP_LEFT : ASDCP::JP2K::SP_RIGHT,
181                             0,
182                             0,
183                             &hash)
184                     )) {
185                 
186                 boost::throw_exception (MXFFileError ("error in writing video MXF", _asset->path().string()));
187         }
188
189         _next_eye = _next_eye == EYE_LEFT ? EYE_RIGHT : EYE_LEFT;
190
191         return FrameInfo (before_offset, _state->mxf_writer.Tell() - before_offset, hash);
192 }
193
194 void
195 MonoPictureAssetWriter::fake_write (int size)
196 {
197         assert (_started);
198         assert (!_finalized);
199
200         if (ASDCP_FAILURE (_state->mxf_writer.FakeWriteFrame (size))) {
201                 boost::throw_exception (MXFFileError ("error in writing video MXF", _asset->path().string()));
202         }
203
204         ++_frames_written;
205 }
206
207 void
208 StereoPictureAssetWriter::fake_write (int size)
209 {
210         assert (_started);
211         assert (!_finalized);
212
213         if (ASDCP_FAILURE (_state->mxf_writer.FakeWriteFrame (size))) {
214                 boost::throw_exception (MXFFileError ("error in writing video MXF", _asset->path().string()));
215         }
216
217         _next_eye = _next_eye == EYE_LEFT ? EYE_RIGHT : EYE_LEFT;
218         ++_frames_written;
219 }
220
221 void
222 MonoPictureAssetWriter::finalize ()
223 {
224         assert (!_finalized);
225         
226         if (ASDCP_FAILURE (_state->mxf_writer.Finalize())) {
227                 boost::throw_exception (MXFFileError ("error in finalizing video MXF", _asset->path().string()));
228         }
229
230         _finalized = true;
231         _asset->set_intrinsic_duration (_frames_written);
232         _asset->set_duration (_frames_written);
233 }
234
235 void
236 StereoPictureAssetWriter::finalize ()
237 {
238         assert (!_finalized);
239         
240         if (ASDCP_FAILURE (_state->mxf_writer.Finalize())) {
241                 boost::throw_exception (MXFFileError ("error in finalizing video MXF", _asset->path().string()));
242         }
243
244         _finalized = true;
245         _asset->set_intrinsic_duration (_frames_written / 2);
246         _asset->set_duration (_frames_written / 2);
247 }