+ return make_pair (non_lfe, lfe);
+}
+
+shared_ptr<AudioBuffers>
+remap (shared_ptr<const AudioBuffers> input, int output_channels, AudioMapping map)
+{
+ auto mapped = make_shared<AudioBuffers>(output_channels, input->frames());
+ mapped->make_silent ();
+
+ int to_do = min (map.input_channels(), input->channels());
+
+ for (int i = 0; i < to_do; ++i) {
+ for (int j = 0; j < mapped->channels(); ++j) {
+ if (map.get(i, j) > 0) {
+ mapped->accumulate_channel(
+ input.get(),
+ i,
+ j,
+ map.get(i, j)
+ );
+ }
+ }
+ }
+
+ return mapped;
+}
+
+Eyes
+increment_eyes (Eyes e)
+{
+ if (e == Eyes::LEFT) {
+ return Eyes::RIGHT;
+ }
+
+ return Eyes::LEFT;
+}
+
+
+size_t
+utf8_strlen (string s)
+{
+ size_t const len = s.length ();
+ int N = 0;
+ for (size_t i = 0; i < len; ++i) {
+ unsigned char c = s[i];
+ if ((c & 0xe0) == 0xc0) {
+ ++i;
+ } else if ((c & 0xf0) == 0xe0) {
+ i += 2;
+ } else if ((c & 0xf8) == 0xf0) {
+ i += 3;
+ }
+ ++N;
+ }
+ return N;
+}
+
+
+/** @param size Size of picture that the subtitle will be overlaid onto */
+void
+emit_subtitle_image (ContentTimePeriod period, dcp::SubtitleImage sub, dcp::Size size, shared_ptr<TextDecoder> decoder)
+{
+ /* XXX: this is rather inefficient; decoding the image just to get its size */
+ FFmpegImageProxy proxy (sub.png_image());
+ auto image = proxy.image(Image::Alignment::PADDED).image;
+ /* set up rect with height and width */
+ dcpomatic::Rect<double> rect(0, 0, image->size().width / double(size.width), image->size().height / double(size.height));
+
+ /* add in position */
+
+ switch (sub.h_align()) {
+ case dcp::HAlign::LEFT:
+ rect.x += sub.h_position();
+ break;
+ case dcp::HAlign::CENTER:
+ rect.x += 0.5 + sub.h_position() - rect.width / 2;
+ break;
+ case dcp::HAlign::RIGHT:
+ rect.x += 1 - sub.h_position() - rect.width;
+ break;
+ }
+
+ switch (sub.v_align()) {
+ case dcp::VAlign::TOP:
+ rect.y += sub.v_position();
+ break;
+ case dcp::VAlign::CENTER:
+ rect.y += 0.5 + sub.v_position() - rect.height / 2;
+ break;
+ case dcp::VAlign::BOTTOM:
+ rect.y += 1 - sub.v_position() - rect.height;
+ break;
+ }
+
+ decoder->emit_bitmap (period, image, rect);
+}
+
+
+/** XXX: could use mmap? */
+void
+copy_in_bits (boost::filesystem::path from, boost::filesystem::path to, std::function<void (float)> progress)
+{
+ dcp::File f(from, "rb");
+ if (!f) {
+ throw OpenFileError (from, errno, OpenFileError::READ);
+ }
+ dcp::File t(to, "wb");
+ if (!t) {
+ throw OpenFileError (to, errno, OpenFileError::WRITE);
+ }
+
+ /* on the order of a second's worth of copying */
+ boost::uintmax_t const chunk = 20 * 1024 * 1024;
+
+ std::vector<uint8_t> buffer(chunk);
+
+ auto const total = dcp::filesystem::file_size(from);
+ boost::uintmax_t remaining = total;
+
+ while (remaining) {
+ boost::uintmax_t this_time = min (chunk, remaining);
+ size_t N = f.read(buffer.data(), 1, chunk);
+ if (N < this_time) {
+ throw ReadFileError (from, errno);
+ }
+
+ N = t.write(buffer.data(), 1, this_time);
+ if (N < this_time) {
+ throw WriteFileError (to, errno);
+ }
+
+ progress (1 - float(remaining) / total);
+ remaining -= this_time;
+ }
+}
+
+
+dcp::Size
+scale_for_display (dcp::Size s, dcp::Size display_container, dcp::Size film_container, PixelQuanta quanta)
+{
+ /* Now scale it down if the display container is smaller than the film container */
+ if (display_container != film_container) {
+ float const scale = min (
+ float (display_container.width) / film_container.width,
+ float (display_container.height) / film_container.height
+ );
+
+ s.width = lrintf (s.width * scale);
+ s.height = lrintf (s.height * scale);
+ s = quanta.round (s);