+string
+careful_string_filter (string s)
+{
+ /* Filter out `bad' characters which `may' cause problems with some systems (either for DCP name or filename).
+ There's no apparent list of what really is allowed, so this is a guess.
+ Safety first and all that.
+ */
+
+ /* First transliterate using libicu to try to remove accents in a "nice" way */
+ auto icu_utf16 = icu::UnicodeString::fromUTF8(icu::StringPiece(s));
+ auto status = U_ZERO_ERROR;
+ auto transliterator = icu::Transliterator::createInstance("NFD; [:M:] Remove; NFC", UTRANS_FORWARD, status);
+ transliterator->transliterate(icu_utf16);
+ s.clear ();
+ icu_utf16.toUTF8String(s);
+
+ /* Then remove anything that's not in a very limited character set */
+ wstring ws = boost::locale::conv::utf_to_utf<wchar_t>(s);
+ string out;
+ string const allowed = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_%.+";
+ for (size_t i = 0; i < ws.size(); ++i) {
+ wchar_t c = ws[i];
+ if (allowed.find(c) != string::npos) {
+ out += c;
+ }
+ }
+
+ return boost::locale::conv::utf_to_utf<char>(out);
+}
+
+/** @param mapped List of mapped audio channels from a Film.
+ * @param channels Total number of channels in the Film.
+ * @return First: number of non-LFE soundtrack channels (L/R/C/Ls/Rs/Lc/Rc/Bsl/Bsr), second: number of LFE channels.
+ */
+pair<int, int>
+audio_channel_types (list<int> mapped, int channels)
+{
+ int non_lfe = 0;
+ int lfe = 0;
+
+ for (auto i: mapped) {
+ if (i >= channels) {
+ /* This channel is mapped but is not included in the DCP */
+ continue;
+ }
+
+ switch (static_cast<dcp::Channel>(i)) {
+ case dcp::Channel::LFE:
+ ++lfe;
+ break;
+ case dcp::Channel::LEFT:
+ case dcp::Channel::RIGHT:
+ case dcp::Channel::CENTRE:
+ case dcp::Channel::LS:
+ case dcp::Channel::RS:
+ case dcp::Channel::BSL:
+ case dcp::Channel::BSR:
+ ++non_lfe;
+ break;
+ case dcp::Channel::HI:
+ case dcp::Channel::VI:
+ case dcp::Channel::MOTION_DATA:
+ case dcp::Channel::SYNC_SIGNAL:
+ case dcp::Channel::SIGN_LANGUAGE:
+ case dcp::Channel::CHANNEL_COUNT:
+ break;
+ }
+ }
+
+ 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;
+}
+
+void
+checked_fwrite (void const * ptr, size_t size, FILE* stream, boost::filesystem::path path)
+{
+ size_t N = fwrite (ptr, 1, size, stream);
+ if (N != size) {
+ if (ferror(stream)) {
+ fclose (stream);
+ throw FileError (String::compose("fwrite error %1", errno), path);
+ } else {
+ fclose (stream);
+ throw FileError ("Unexpected short write", path);
+ }
+ }
+}
+
+void
+checked_fread (void* ptr, size_t size, FILE* stream, boost::filesystem::path path)
+{
+ size_t N = fread (ptr, 1, size, stream);
+ if (N != size) {
+ if (ferror(stream)) {
+ fclose (stream);
+ throw FileError (String::compose("fread error %1", errno), path);
+ } else {
+ fclose (stream);
+ throw FileError ("Unexpected short read", path);
+ }
+ }
+}
+
+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;
+}
+
+string
+day_of_week_to_string (boost::gregorian::greg_weekday d)
+{
+ switch (d.as_enum()) {
+ case boost::date_time::Sunday:
+ return _("Sunday");
+ case boost::date_time::Monday:
+ return _("Monday");
+ case boost::date_time::Tuesday:
+ return _("Tuesday");
+ case boost::date_time::Wednesday:
+ return _("Wednesday");
+ case boost::date_time::Thursday:
+ return _("Thursday");
+ case boost::date_time::Friday:
+ return _("Friday");
+ case boost::date_time::Saturday:
+ return _("Saturday");
+ }
+
+ return d.as_long_string ();
+}
+
+/** @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(false).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);
+}
+