Fix missing version string when Popen communicate returns byte strings.
[libdcp.git] / src / sound_asset.cc
1 /*
2     Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34 /** @file  src/sound_mxf.cc
35  *  @brief SoundAsset class.
36  */
37
38 #include "sound_asset.h"
39 #include "util.h"
40 #include "exceptions.h"
41 #include "sound_frame.h"
42 #include "sound_asset_writer.h"
43 #include "sound_asset_reader.h"
44 #include "compose.hpp"
45 #include "dcp_assert.h"
46 #include <asdcp/KM_fileio.h>
47 #include <asdcp/AS_DCP.h>
48 #include <libxml++/nodes/element.h>
49 #include <boost/filesystem.hpp>
50 #include <stdexcept>
51
52 using std::string;
53 using std::vector;
54 using std::list;
55 using boost::shared_ptr;
56 using boost::dynamic_pointer_cast;
57 using namespace dcp;
58
59 SoundAsset::SoundAsset (boost::filesystem::path file)
60         : Asset (file)
61 {
62         ASDCP::PCM::MXFReader reader;
63         Kumu::Result_t r = reader.OpenRead (file.string().c_str());
64         if (ASDCP_FAILURE (r)) {
65                 boost::throw_exception (MXFFileError ("could not open MXF file for reading", file.string(), r));
66         }
67
68         ASDCP::PCM::AudioDescriptor desc;
69         if (ASDCP_FAILURE (reader.FillAudioDescriptor (desc))) {
70                 boost::throw_exception (DCPReadError ("could not read audio MXF information"));
71         }
72
73         _sampling_rate = desc.AudioSamplingRate.Numerator / desc.AudioSamplingRate.Denominator;
74         _channels = desc.ChannelCount;
75         _edit_rate = Fraction (desc.EditRate.Numerator, desc.EditRate.Denominator);
76
77         _intrinsic_duration = desc.ContainerDuration;
78
79         ASDCP::WriterInfo info;
80         if (ASDCP_FAILURE (reader.FillWriterInfo (info))) {
81                 boost::throw_exception (DCPReadError ("could not read audio MXF information"));
82         }
83
84         _id = read_writer_info (info);
85 }
86
87 SoundAsset::SoundAsset (Fraction edit_rate, int sampling_rate, int channels, Standard standard)
88         : MXF (standard)
89         , _edit_rate (edit_rate)
90         , _intrinsic_duration (0)
91         , _channels (channels)
92         , _sampling_rate (sampling_rate)
93 {
94
95 }
96
97 bool
98 SoundAsset::equals (shared_ptr<const Asset> other, EqualityOptions opt, NoteHandler note) const
99 {
100         ASDCP::PCM::MXFReader reader_A;
101         DCP_ASSERT (file ());
102         Kumu::Result_t r = reader_A.OpenRead (file()->string().c_str());
103         if (ASDCP_FAILURE (r)) {
104                 boost::throw_exception (MXFFileError ("could not open MXF file for reading", file()->string(), r));
105         }
106
107         ASDCP::PCM::MXFReader reader_B;
108         r = reader_B.OpenRead (other->file()->string().c_str());
109         if (ASDCP_FAILURE (r)) {
110                 boost::throw_exception (MXFFileError ("could not open MXF file for reading", other->file()->string(), r));
111         }
112
113         ASDCP::PCM::AudioDescriptor desc_A;
114         if (ASDCP_FAILURE (reader_A.FillAudioDescriptor (desc_A))) {
115                 boost::throw_exception (DCPReadError ("could not read audio MXF information"));
116         }
117         ASDCP::PCM::AudioDescriptor desc_B;
118         if (ASDCP_FAILURE (reader_B.FillAudioDescriptor (desc_B))) {
119                 boost::throw_exception (DCPReadError ("could not read audio MXF information"));
120         }
121
122         if (desc_A.EditRate != desc_B.EditRate) {
123                 note (
124                         DCP_ERROR,
125                         String::compose (
126                                 "audio edit rates differ: %1/%2 cf %3/%4",
127                                 desc_A.EditRate.Numerator, desc_A.EditRate.Denominator, desc_B.EditRate.Numerator, desc_B.EditRate.Denominator
128                                 )
129                         );
130                 return false;
131         } else if (desc_A.AudioSamplingRate != desc_B.AudioSamplingRate) {
132                 note (
133                         DCP_ERROR,
134                         String::compose (
135                                 "audio sampling rates differ: %1 cf %2",
136                                 desc_A.AudioSamplingRate.Numerator, desc_A.AudioSamplingRate.Denominator,
137                                 desc_B.AudioSamplingRate.Numerator, desc_B.AudioSamplingRate.Numerator
138                                 )
139                         );
140                 return false;
141         } else if (desc_A.Locked != desc_B.Locked) {
142                 note (DCP_ERROR, String::compose ("audio locked flags differ: %1 cf %2", desc_A.Locked, desc_B.Locked));
143                 return false;
144         } else if (desc_A.ChannelCount != desc_B.ChannelCount) {
145                 note (DCP_ERROR, String::compose ("audio channel counts differ: %1 cf %2", desc_A.ChannelCount, desc_B.ChannelCount));
146                 return false;
147         } else if (desc_A.QuantizationBits != desc_B.QuantizationBits) {
148                 note (DCP_ERROR, String::compose ("audio bits per sample differ: %1 cf %2", desc_A.QuantizationBits, desc_B.QuantizationBits));
149                 return false;
150         } else if (desc_A.BlockAlign != desc_B.BlockAlign) {
151                 note (DCP_ERROR, String::compose ("audio bytes per sample differ: %1 cf %2", desc_A.BlockAlign, desc_B.BlockAlign));
152                 return false;
153         } else if (desc_A.AvgBps != desc_B.AvgBps) {
154                 note (DCP_ERROR, String::compose ("audio average bps differ: %1 cf %2", desc_A.AvgBps, desc_B.AvgBps));
155                 return false;
156         } else if (desc_A.LinkedTrackID != desc_B.LinkedTrackID) {
157                 note (DCP_ERROR, String::compose ("audio linked track IDs differ: %1 cf %2", desc_A.LinkedTrackID, desc_B.LinkedTrackID));
158                 return false;
159         } else if (desc_A.ContainerDuration != desc_B.ContainerDuration) {
160                 note (DCP_ERROR, String::compose ("audio container durations differ: %1 cf %2", desc_A.ContainerDuration, desc_B.ContainerDuration));
161                 return false;
162         } else if (desc_A.ChannelFormat != desc_B.ChannelFormat) {
163                 /* XXX */
164         }
165
166         shared_ptr<const SoundAsset> other_sound = dynamic_pointer_cast<const SoundAsset> (other);
167
168         shared_ptr<const SoundAssetReader> reader = start_read ();
169         shared_ptr<const SoundAssetReader> other_reader = other_sound->start_read ();
170
171         for (int i = 0; i < _intrinsic_duration; ++i) {
172
173                 shared_ptr<const SoundFrame> frame_A = reader->get_frame (i);
174                 shared_ptr<const SoundFrame> frame_B = other_reader->get_frame (i);
175
176                 if (frame_A->size() != frame_B->size()) {
177                         note (DCP_ERROR, String::compose ("sizes of audio data for frame %1 differ", i));
178                         return false;
179                 }
180
181                 if (memcmp (frame_A->data(), frame_B->data(), frame_A->size()) != 0) {
182                         for (int i = 0; i < frame_A->size(); ++i) {
183                                 int const d = abs (frame_A->data()[i] - frame_B->data()[i]);
184                                 if (d > opt.max_audio_sample_error) {
185                                         note (DCP_ERROR, String::compose ("PCM data difference of %1", d));
186                                         return false;
187                                 }
188                         }
189                 }
190         }
191
192         return true;
193 }
194
195 shared_ptr<SoundAssetWriter>
196 SoundAsset::start_write (boost::filesystem::path file)
197 {
198         /* XXX: can't we use a shared_ptr here? */
199         return shared_ptr<SoundAssetWriter> (new SoundAssetWriter (this, file));
200 }
201
202 shared_ptr<SoundAssetReader>
203 SoundAsset::start_read () const
204 {
205         return shared_ptr<SoundAssetReader> (new SoundAssetReader (this, key(), standard()));
206 }
207
208 string
209 SoundAsset::static_pkl_type (Standard standard)
210 {
211         switch (standard) {
212         case INTEROP:
213                 return "application/x-smpte-mxf;asdcpKind=Sound";
214         case SMPTE:
215                 return "application/mxf";
216         default:
217                 DCP_ASSERT (false);
218         }
219 }
220
221 bool
222 SoundAsset::valid_mxf (boost::filesystem::path file)
223 {
224         ASDCP::PCM::MXFReader reader;
225         Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
226         return !ASDCP_FAILURE (r);
227 }