diff options
Diffstat (limited to 'src/odr-padenc.cpp')
-rw-r--r-- | src/odr-padenc.cpp | 1830 |
1 files changed, 1830 insertions, 0 deletions
diff --git a/src/odr-padenc.cpp b/src/odr-padenc.cpp new file mode 100644 index 0000000..06076af --- /dev/null +++ b/src/odr-padenc.cpp @@ -0,0 +1,1830 @@ +/* + Copyright (C) 2014 CSP Innovazione nelle ICT s.c.a r.l. (http://rd.csp.it/) + + Copyright (C) 2014, 2015 Matthias P. Braendli (http://opendigitalradio.org) + + Copyright (C) 2015, 2016 Stefan Pöschel (http://opendigitalradio.org) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +/*! + \file odr-padenc.cpp + \brief Generete PAD data for MOT Slideshow and DLS + + \author Sergio Sagliocco <sergio.sagliocco@csp.it> + \author Matthias P. Braendli <matthias@mpb.li> + \author Stefan Pöschel <odr@basicmaster.de> +*/ + +#include <cstdio> +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> +#include <cstring> +#include <string> +#include <sstream> +#include <iostream> +#include <fstream> +#include <algorithm> +#include <vector> +#include <deque> +#include <list> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <getopt.h> +#include "config.h" +#include "charset.h" + + +#if HAVE_MAGICKWAND +# include <wand/magick_wand.h> +#endif + +#define DEBUG 0 + +#define SLEEPDELAY_DEFAULT 10 //seconds + +extern "C" { +#include "crc.h" +} + +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + +#define XSTR(x) #x +#define STR(x) XSTR(x) + +#define MAXSEGLEN 8189 // Bytes (EN 301 234 v2.1.1, ch. 5.1.1) +#define MAXDLS 128 // chars +#define MAXSLIDESIZE 51200 // Bytes (TS 101 499 v3.1.1, ch. 9.1.2) + +// Roll-over value for fidx +#define MAXSLIDEID 9999 + +// How many slides to keep in history +#define MAXHISTORYLEN 50 + +// Do not allow the image compressor to go below +// JPEG quality 40 +#define MINQUALITY 40 + +// Charsets from TS 101 756 +#define CHARSET_COMPLETE_EBU_LATIN 0 //!< Complete EBU Latin based repertoire +#define CHARSET_EBU_LATIN_CY_GR 1 //!< EBU Latin based common core, Cyrillic, Greek +#define CHARSET_EBU_LATIN_AR_HE_CY_GR 2 //!< EBU Latin based core, Arabic, Hebrew, Cyrillic and Greek +#define CHARSET_ISO_LATIN_ALPHABET_2 3 //!< ISO Latin Alphabet No 2 +#define CHARSET_UCS2_BE 6 //!< ISO/IEC 10646 using UCS-2 transformation format, big endian byte order +#define CHARSET_UTF8 15 //!< ISO Latin Alphabet No 2 + + +typedef std::vector<uint8_t> uint8_vector_t; +static int verbose = 0; + + +struct MSCDG { + // MSC Data Group Header (extension field not supported) + unsigned char extflag; // 1 bit + unsigned char crcflag; // 1 bit + unsigned char segflag; // 1 bit + unsigned char accflag; // 1 bit + unsigned char dgtype; // 4 bits + unsigned char cindex; // 4 bits + unsigned char rindex; // 4 bits + /// Session header - Segment field + unsigned char last; // 1 bit + unsigned short int segnum; // 16 bits + // Session header - User access field + unsigned char rfa; // 3 bits + unsigned char tidflag; // 1 bit + unsigned char lenid; // 4 bits - Fixed to value 2 in this implemntation + unsigned short int tid; // 16 bits + // MSC data group data field + // Mot Segmentation header + unsigned char rcount; // 3 bits + unsigned short int seglen; // 13 bits + // Mot segment + unsigned char* segdata; + // MSC data group CRC + unsigned short int crc; // 16 bits +}; + +/*! Between collection of slides and transmission, the slide data is saved + * in this structure. + */ +struct slide_metadata_t { + // complete path to slide + std::string filepath; + + // index, values from 0 to MAXSLIDEID, rolls over + int fidx; + + // This is used to define the order in which several discovered + // slides are transmitted + bool operator<(const slide_metadata_t& other) const { + return this->fidx < other.fidx; + } +}; + +/*! A simple fingerprint for each slide transmitted. + * Allows us to reuse the same fidx if the same slide + * is transmitted more than once. + */ +struct fingerprint_t { + // file name + std::string s_name; + // file size, in bytes + off_t s_size; + // time of last modification + unsigned long s_mtime; + + // assigned fidx, -1 means invalid + int fidx; + + /*! The comparison is not done on fidx, only + * on the file-specific data + */ + bool operator==(const fingerprint_t& other) const { + return (((s_name == other.s_name && + s_size == other.s_size) && + s_mtime == other.s_mtime)); + } + + void disp(void) { + printf("%s_%ld_%lu:%d\n", s_name.c_str(), s_size, s_mtime, fidx); + } + + void load_from_file(const char* filepath) + { + struct stat file_attribue; + const char * final_slash; + + stat(filepath, &file_attribue); + final_slash = strrchr(filepath, '/'); + + // load filename, size and mtime + // Save only the basename of the filepath + this->s_name.assign((final_slash == NULL) ? filepath : final_slash + 1); + this->s_size = file_attribue.st_size; + this->s_mtime = file_attribue.st_mtime; + + this->fidx = -1; + } +}; + +/*! We keep track of transmitted files so that we can retransmit + * identical slides with the same index, in case the receivers cache + * them. + * + * \c MAXHISTORYLEN defines for how how many slides we want to keep this + * history. + */ +class History { + public: + History(size_t hist_size) : + m_hist_size(hist_size), + m_last_given_fidx(0) {} + void disp_database(); + // controller of id base on database + int get_fidx(const char* filepath); + + private: + std::deque<fingerprint_t> m_database; + + size_t m_hist_size; + + int m_last_given_fidx; + + // find the fingerprint fp in database. + // returns the fidx when found, + // or -1 if not found + int find(const fingerprint_t& fp) const; + + // add a new fingerprint into database + // returns its fidx + void add(fingerprint_t& fp); +}; + + +int encodeFile(int output_fd, const std::string& fname, int fidx, bool raw_slides); + +uint8_vector_t createMotHeader( + size_t blobsize, + int fidx, + bool jfif_not_png); + +void createMscDG(MSCDG* msc, unsigned short int dgtype, int *cindex, unsigned short int segnum, + unsigned short int lastseg, unsigned short int tid, unsigned char* data, + unsigned short int datalen); + +struct DATA_GROUP; +DATA_GROUP* packMscDG(MSCDG* msc); + +struct DL_STATE; +void prepend_dl_dgs(const DL_STATE& dl_state, uint8_t charset); +void writeDLS(int output_fd, const std::string& dls_file, uint8_t charset, bool raw_dls, bool remove_dls); + +// PAD related +#define CRC_LEN 2 + +struct DATA_GROUP { + uint8_vector_t data; + int apptype_start; + int apptype_cont; + size_t written; + + DATA_GROUP(size_t len, int apptype_start, int apptype_cont) { + this->data.resize(len); + this->apptype_start = apptype_start; + this->apptype_cont = apptype_cont; + written = 0; + } + + void AppendCRC() { + uint16_t crc = 0xFFFF; + crc = crc16(crc, &data[0], data.size()); + crc = ~crc; +#if DEBUG + fprintf(stderr, "crc=%04x ~crc=%04x\n", crc, ~crc); +#endif + + data.push_back((crc & 0xFF00) >> 8); + data.push_back((crc & 0x00FF)); + } + + size_t Available() { + return data.size() - written; + } + + int Write(uint8_t *write_data, size_t len, int *cont_apptype) { + size_t written_now = std::min(len, Available()); + + // fill up remaining bytes with zero padding + memcpy(write_data, &data[written], written_now); + memset(write_data + written_now, 0x00, len - written_now); + + // set app type depending on progress + int apptype = written > 0 ? apptype_cont : apptype_start; + + written += written_now; + + // prevent continuation of a different DG having the same type + if (cont_apptype) + *cont_apptype = Available() > 0 ? apptype_cont : -1; + + return apptype; + } +}; + +#define SHORT_PAD 6 // F-PAD + 1x CI + 1x 3 bytes data sub-field +#define VARSIZE_PAD_MIN 8 // F-PAD + 1x CI + end marker + 1x 4 bytes data sub-field +#define VARSIZE_PAD_MAX 196 // F-PAD + 4x CI + 4x 48 bytes data sub-field +#define ALLOWED_PADLEN "6 (short X-PAD), 8 to 196 (variable size X-PAD)" + + + +// MOT Slideshow related +static int cindex_header = 0; +static int cindex_body = 0; + + +class MOTHeader { +private: + size_t header_size; + uint8_vector_t data; + + void IncrementHeaderSize(size_t size); + void AddParamHeader(int pli, int param_id) {data.push_back((pli << 6) | (param_id & 0x3F));} +public: + MOTHeader(size_t body_size, int content_type, int content_subtype); + + void AddExtension(int param_id); + void AddExtension8Bit(int param_id, uint8_t data_field); + void AddExtension32Bit(int param_id, uint32_t data_field); + void AddExtensionVarSize(int param_id, const uint8_t* data_field, size_t data_field_len); + const uint8_vector_t GetData() {return data;} +}; + +MOTHeader::MOTHeader(size_t body_size, int content_type, int content_subtype) +: header_size(0), data(uint8_vector_t(7, 0x00)) { + // init header core + + // body size + data[0] = (body_size >> 20) & 0xFF; + data[1] = (body_size >> 12) & 0xFF; + data[2] = (body_size >> 4) & 0xFF; + data[3] = (body_size << 4) & 0xF0; + + // header size + IncrementHeaderSize(data.size()); + + // content type + data[5] |= (content_type << 1) & 0x7E; + + // content subtype + data[5] |= (content_subtype >> 8) & 0x01; + data[6] |= content_subtype & 0xFF; +} + +void MOTHeader::IncrementHeaderSize(size_t size) { + header_size += size; + + data[3] &= 0xF0; + data[3] |= (header_size >> 9) & 0x0F; + + data[4] = (header_size >> 1) & 0xFF; + + data[5] &= 0x7F; + data[5] |= (header_size << 7) & 0x80; +} + +void MOTHeader::AddExtension(int param_id) { + AddParamHeader(0b00, param_id); + + IncrementHeaderSize(1); +} + +void MOTHeader::AddExtension8Bit(int param_id, uint8_t data_field) { + AddParamHeader(0b01, param_id); + data.push_back(data_field); + + IncrementHeaderSize(2); +} + +void MOTHeader::AddExtension32Bit(int param_id, uint32_t data_field) { + AddParamHeader(0b10, param_id); + data.push_back((data_field >> 24) & 0xFF); + data.push_back((data_field >> 16) & 0xFF); + data.push_back((data_field >> 8) & 0xFF); + data.push_back( data_field & 0xFF); + + IncrementHeaderSize(5); +} + +void MOTHeader::AddExtensionVarSize(int param_id, const uint8_t* data_field, size_t data_field_len) { + AddParamHeader(0b11, param_id); + + // longer field lens use 15 instead of 7 bits + bool ext = data_field_len > 127; + if (ext) { + data.push_back(0x80 | ((data_field_len >> 8) & 0x7F)); + data.push_back(data_field_len & 0xFF); + } else { + data.push_back(data_field_len & 0x7F); + } + + for (size_t i = 0; i < data_field_len; i++) + data.push_back(data_field[i]); + + IncrementHeaderSize(1 + (ext ? 2 : 1) + data_field_len); +} + + + +// DLS related +#define FPAD_LEN 2 +#define DLS_SEG_LEN_PREFIX 2 +#define DLS_SEG_LEN_CHAR_MAX 16 +#define DLS_CMD_REMOVE_LABEL 0b0001 +#define DLS_CMD_DL_PLUS 0b0010 +#define DL_PLUS_CMD_TAGS 0b0000 + +#define DL_PARAMS_OPEN "##### parameters { #####" +#define DL_PARAMS_CLOSE "##### parameters } #####" + +CharsetConverter charset_converter; + +struct DL_PLUS_TAG { + int content_type; + int start_marker; + int length_marker; + + DL_PLUS_TAG() : + content_type(0), // = DUMMY + start_marker(0), + length_marker(0) + {} + + DL_PLUS_TAG(int content_type, int start_marker, int length_marker) : + content_type(content_type), + start_marker(start_marker), + length_marker(length_marker) + {} + + bool operator==(const DL_PLUS_TAG& other) const { + return + content_type == other.content_type && + start_marker == other.start_marker && + length_marker == other.length_marker; + } + bool operator!=(const DL_PLUS_TAG& other) const { + return !(*this == other); + } +}; + +typedef std::vector<DL_PLUS_TAG> dl_plus_tags_t; + +struct DL_STATE { + std::string dl_text; + + bool dl_plus_enabled; + bool dl_plus_item_toggle; + bool dl_plus_item_running; + dl_plus_tags_t dl_plus_tags; + + DL_STATE() : + dl_plus_enabled(false), + dl_plus_item_toggle(false), + dl_plus_item_running(false) + {} + + bool operator==(const DL_STATE& other) const { + if (dl_text != other.dl_text) + return false; + if (dl_plus_enabled != other.dl_plus_enabled) + return false; + if (dl_plus_enabled) { + if (dl_plus_item_toggle != other.dl_plus_item_toggle) + return false; + if (dl_plus_item_running != other.dl_plus_item_running) + return false; + if (dl_plus_tags != other.dl_plus_tags) + return false; + } + return true; + } + bool operator!=(const DL_STATE& other) const { + return !(*this == other); + } +}; + + +typedef uint8_vector_t pad_t; +static bool dls_toggle = false; +static DL_STATE dl_state_prev; + + +class PADPacketizer { +private: + static const size_t subfield_lens[]; + + const size_t xpad_size_max; + const bool short_xpad; + const size_t max_cis; + + size_t xpad_size; + uint8_t subfields[4*48]; + size_t subfields_size; + + // PAD w/ CI list + int ci_type[4]; + size_t ci_len_index[4]; + size_t used_cis; + + // PAD w/o CI list + int last_ci_type; + size_t last_ci_size; + + size_t AddCINeededBytes(); + void AddCI(int apptype, int len_index); + + int OptimalSubFieldSizeIndex(size_t available_bytes); + int WriteDGToSubField(DATA_GROUP* dg, size_t len); + + bool AppendDG(DATA_GROUP* dg); + void AppendDGWithCI(DATA_GROUP* dg); + void AppendDGWithoutCI(DATA_GROUP* dg); + + void ResetPAD(); + pad_t* FlushPAD(); +public: + std::deque<DATA_GROUP*> queue; + + PADPacketizer(size_t pad_size); + ~PADPacketizer(); + + pad_t* GetPAD(); + + // will be removed, when pull (instead of push) approach is implemented! + void WriteAllPADs(int output_fd); +}; + + +const size_t PADPacketizer::subfield_lens[] = {4, 6, 8, 12, 16, 24, 32, 48}; + +PADPacketizer::PADPacketizer(size_t pad_size) : + xpad_size_max(pad_size - FPAD_LEN), + short_xpad(pad_size == SHORT_PAD), + max_cis(short_xpad ? 1 : 4), + last_ci_type(-1) +{ + ResetPAD(); +} + +PADPacketizer::~PADPacketizer() { + while (!queue.empty()) { + delete queue.front(); + queue.pop_front(); + } +} + + +pad_t* PADPacketizer::GetPAD() { + bool pad_flushable = false; + + // process DG queue + while (!pad_flushable && !queue.empty()) { + DATA_GROUP* dg = queue.front(); + + // repeatedly append DG + while (!pad_flushable && dg->Available() > 0) + pad_flushable = AppendDG(dg); + + if (dg->Available() == 0) { + delete dg; + queue.pop_front(); + } + } + + // (possibly empty) PAD + return FlushPAD(); +} + +void PADPacketizer::WriteAllPADs(int output_fd) { + for (;;) { + pad_t* pad = GetPAD(); + + // if only F-PAD present, abort + if (pad->back() == FPAD_LEN) { + delete pad; + break; + } + + if (write(output_fd, &(*pad)[0], pad->size()) != (signed) pad->size()) + fprintf(stderr, "ODR-PadEnc Error: Could not write PAD\n"); + + delete pad; + } +} + + +size_t PADPacketizer::AddCINeededBytes() { + // returns the amount of additional bytes needed for the next CI + + // special cases: end marker added/replaced + if (!short_xpad && used_cis == 0) + return 2; + if (!short_xpad && used_cis == (max_cis - 1)) + return 0; + return 1; +} + +void PADPacketizer::AddCI(int apptype, int len_index) { + ci_type[used_cis] = apptype; + ci_len_index[used_cis] = len_index; + + xpad_size += AddCINeededBytes(); + used_cis++; +} + + +int PADPacketizer::OptimalSubFieldSizeIndex(size_t available_bytes) { + /*! Return the index of the optimal sub-field size by stepwise search (regards only Variable Size X-PAD): + * - find the smallest sub-field able to hold (at least) all available bytes + * - find the biggest regarding sub-field we have space for (which definitely exists - otherwise previously the PAD would have been flushed) + * - if the wasted space is at least as big as the smallest possible sub-field, use a sub-field one size smaller + */ + int len_index = 0; + + while ((len_index + 1) < 8 && subfield_lens[len_index] < available_bytes) + len_index++; + while ((len_index - 1) >= 0 && (subfield_lens[len_index] + AddCINeededBytes()) > (xpad_size_max - xpad_size)) + len_index--; + if ((len_index - 1) >= 0 && ((int) subfield_lens[len_index] - (int) available_bytes) >= (int) subfield_lens[0]) + len_index--; + + return len_index; +} + +int PADPacketizer::WriteDGToSubField(DATA_GROUP* dg, size_t len) { + int apptype = dg->Write(&subfields[subfields_size], len, &last_ci_type); + subfields_size += len; + xpad_size += len; + return apptype; +} + + +bool PADPacketizer::AppendDG(DATA_GROUP* dg) { + /*! use X-PAD w/o CIs instead of X-PAD w/ CIs, if we can save some bytes or at least do not waste additional bytes + * + * Omit CI list in case: + * 1. no pending data sub-fields + * 2. last CI type valid + * 3. last CI type matching current (continuity) CI type + * 4a. short X-PAD; OR + * 4ba. size of the last X-PAD being at least as big as the available X-PAD payload in case all CIs are used AND + * 4bb. the amount of available DG bytes being at least as big as the size of the last X-PAD in case all CIs are used + */ + if ( + used_cis == 0 && + last_ci_type != -1 && + last_ci_type == dg->apptype_cont && + (short_xpad || + (last_ci_size >= (xpad_size_max - max_cis) && + dg->Available() >= (last_ci_size - max_cis))) + ) { + AppendDGWithoutCI(dg); + return true; + } else { + AppendDGWithCI(dg); + + // if no further sub-fields could be added, PAD must be flushed + if (used_cis == max_cis || subfield_lens[0] + AddCINeededBytes() > (xpad_size_max - xpad_size)) + return true; + } + return false; +} + + +void PADPacketizer::AppendDGWithCI(DATA_GROUP* dg) { + int len_index = short_xpad ? 0 : OptimalSubFieldSizeIndex(dg->Available()); + size_t len_size = short_xpad ? 3 : subfield_lens[len_index]; + + int apptype = WriteDGToSubField(dg, len_size); + AddCI(apptype, len_index); + +#if DEBUG + fprintf(stderr, "PADPacketizer: added sub-field w/ CI - type: %2d, size: %2zu\n", apptype, len_size); +#endif +} + +void PADPacketizer::AppendDGWithoutCI(DATA_GROUP* dg) { +#if DEBUG + int old_last_ci_type = last_ci_type; +#endif + + WriteDGToSubField(dg, last_ci_size); + +#if DEBUG + fprintf(stderr, "PADPacketizer: added sub-field w/o CI - type: %2d, size: %2zu\n", old_last_ci_type, last_ci_size); +#endif +} + +void PADPacketizer::ResetPAD() { + xpad_size = 0; + subfields_size = 0; + used_cis = 0; +} + +pad_t* PADPacketizer::FlushPAD() { + pad_t* result = new pad_t(xpad_size_max + FPAD_LEN + 1); + pad_t &pad = *result; + + size_t pad_offset = xpad_size_max; + + if (subfields_size > 0) { + if (used_cis > 0) { + // X-PAD: CIs + for (size_t i = 0; i < used_cis; i++) + pad[--pad_offset] = (short_xpad ? 0 : ci_len_index[i]) << 5 | ci_type[i]; + + // X-PAD: end marker (if needed) + if (used_cis < max_cis) + pad[--pad_offset] = 0x00; + } + + // X-PAD: sub-fields (reversed on-the-fly) + for (size_t off = 0; off < subfields_size; off++) + pad[--pad_offset] = subfields[off]; + } else { + // no X-PAD + last_ci_type = -1; + } + + // zero padding + memset(&pad[0], 0x00, pad_offset); + + // F-PAD + pad[xpad_size_max + 0] = subfields_size > 0 ? (short_xpad ? 0x10 : 0x20) : 0x00; + pad[xpad_size_max + 1] = subfields_size > 0 ? (used_cis > 0 ? 0x02 : 0x00) : 0x00; + + // used PAD len + pad[xpad_size_max + FPAD_LEN] = xpad_size + FPAD_LEN; + + last_ci_size = xpad_size; + ResetPAD(); + return result; +} + + +static PADPacketizer *pad_packetizer; + + + + + + + +void usage(char* name) +{ + fprintf(stderr, "DAB PAD encoder %s for MOT Slideshow and DLS\n\n" + "By CSP Innovazione nelle ICT s.c.a r.l. (http://rd.csp.it/) and\n" + "Opendigitalradio.org\n\n" + "Reads image data from the specified directory, DLS text from a file,\n" + "and outputs PAD data to the given FIFO.\n" + " http://opendigitalradio.org\n\n", +#if defined(GITVERSION) + GITVERSION +#else + PACKAGE_VERSION +#endif + ); + fprintf(stderr, "Usage: %s [OPTIONS...]\n", name); + fprintf(stderr, " -d, --dir=DIRNAME Directory to read images from.\n" + " -e, --erase Erase slides from DIRNAME once they have\n" + " been encoded.\n" + " -s, --sleep=DELAY Wait DELAY seconds between each slide\n" + " Default: " STR(SLEEPDELAY_DEFAULT) "\n" + " -o, --output=FILENAME Fifo to write PAD data into.\n" + " Default: /tmp/pad.fifo\n" + " -t, --dls=FILENAME Fifo or file to read DLS text from.\n" + " -p, --pad=LENGTH Set the pad length.\n" + " Possible values: " ALLOWED_PADLEN "\n" + " Default: 58\n" + " -c, --charset=ID ID of the character set encoding used for DLS text input.\n" + " ID = 0: Complete EBU Latin based repertoire\n" + " ID = 6: ISO/IEC 10646 using UCS-2 BE\n" + " ID = 15: ISO/IEC 10646 using UTF-8\n" + " Default: 15\n" + " -r, --remove-dls Always insert a DLS Remove Label command when replacing a DLS text.\n" + " -C, --raw-dls Do not convert DLS texts to Complete EBU Latin based repertoire\n" + " character set encoding.\n" + " -R, --raw-slides Do not process slides. Integrity checks and resizing\n" + " slides is skipped. Use this if you know what you are doing !\n" + " It is useful only when -d is used\n" + " -v, --verbose Print more information to the console\n" + ); +} + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 +int main(int argc, char *argv[]) +{ + int ret; + struct dirent *pDirent; + int padlen = 58; + bool erase_after_tx = false; + int sleepdelay = SLEEPDELAY_DEFAULT; + bool raw_slides = false; + int charset = CHARSET_UTF8; + bool raw_dls = false; + bool remove_dls = false; + + const char* sls_dir = NULL; + const char* output = "/tmp/pad.fifo"; + std::string dls_file; + + const struct option longopts[] = { + {"charset", required_argument, 0, 'c'}, + {"raw-dls", no_argument, 0, 'C'}, + {"remove-dls", no_argument, 0, 'r'}, + {"dir", required_argument, 0, 'd'}, + {"erase", no_argument, 0, 'e'}, + {"output", required_argument, 0, 'o'}, + {"dls", required_argument, 0, 't'}, + {"pad", required_argument, 0, 'p'}, + {"sleep", required_argument, 0, 's'}, + {"raw-slides", no_argument, 0, 'R'}, + {"help", no_argument, 0, 'h'}, + {"verbose", no_argument, 0, 'v'}, + {0,0,0,0}, + }; + + int ch=0; + int index; + while(ch != -1) { + ch = getopt_long(argc, argv, "eChRrc:d:o:p:s:t:v", longopts, &index); + switch (ch) { + case 'c': + charset = atoi(optarg); + break; + case 'C': + raw_dls = true; + break; + case 'r': + remove_dls = true; + break; + case 'd': + sls_dir = optarg; + break; + case 'e': + erase_after_tx = true; + break; + case 'o': + output = optarg; + break; + case 's': + sleepdelay = atoi(optarg); + break; + case 't': + dls_file = optarg; + break; + case 'p': + padlen = atoi(optarg); + break; + case 'R': + raw_slides = true; + break; + case 'v': + verbose++; + break; + case '?': + case 'h': + usage(argv[0]); + return 0; + } + } + + if (padlen != SHORT_PAD && (padlen < VARSIZE_PAD_MIN || padlen > VARSIZE_PAD_MAX)) { + fprintf(stderr, "ODR-PadEnc Error: pad length %d invalid: Possible values: " + ALLOWED_PADLEN "\n", + padlen); + return 2; + } + + if (sls_dir && not dls_file.empty()) { + fprintf(stderr, "ODR-PadEnc encoding Slideshow from '%s' and DLS from '%s' to '%s'\n", + sls_dir, dls_file.c_str(), output); + } + else if (sls_dir) { + fprintf(stderr, "ODR-PadEnc encoding Slideshow from '%s' to '%s'. No DLS.\n", + sls_dir, output); + } + else if (not dls_file.empty()) { + fprintf(stderr, "ODR-PadEnc encoding DLS from '%s' to '%s'. No Slideshow.\n", + dls_file.c_str(), output); + } + else { + fprintf(stderr, "ODR-PadEnc Error: No DLS nor slideshow to encode !\n"); + usage(argv[0]); + return 1; + } + + const char* user_charset; + switch (charset) { + case CHARSET_COMPLETE_EBU_LATIN: + user_charset = "Complete EBU Latin"; + break; + case CHARSET_EBU_LATIN_CY_GR: + user_charset = "EBU Latin core, Cyrillic, Greek"; + break; + case CHARSET_EBU_LATIN_AR_HE_CY_GR: + user_charset = "EBU Latin core, Arabic, Hebrew, Cyrillic, Greek"; + break; + case CHARSET_ISO_LATIN_ALPHABET_2: + user_charset = "ISO Latin Alphabet 2"; + break; + case CHARSET_UCS2_BE: + user_charset = "UCS-2 BE"; + break; + case CHARSET_UTF8: + user_charset = "UTF-8"; + break; + default: + user_charset = "Invalid"; + charset = -1; + break; + } + + if (charset == -1) { + fprintf(stderr, "ODR-PadEnc Error: Invalid charset!\n"); + usage(argv[0]); + return 1; + } + else { + fprintf(stderr, "ODR-PadEnc using charset %s (%d)\n", + user_charset, charset); + } + + if (not raw_dls) { + switch (charset) { + case CHARSET_COMPLETE_EBU_LATIN: + // no conversion needed + break; + case CHARSET_UTF8: + fprintf(stderr, "ODR-PadEnc converting DLS texts to Complete EBU Latin\n"); + break; + default: + fprintf(stderr, "ODR-PadEnc Error: DLS conversion to EBU is currently only supported for UTF-8 input!\n"); + return 1; + } + } + + int output_fd = open(output, O_WRONLY); + if (output_fd == -1) { + perror("ODR-PadEnc Error: failed to open output"); + return 3; + } + +#if HAVE_MAGICKWAND + MagickWandGenesis(); +#endif + + pad_packetizer = new PADPacketizer(padlen); + + std::list<slide_metadata_t> slides_to_transmit; + History slides_history(MAXHISTORYLEN); + + while(1) { + if (sls_dir) { // slide + possibly DLS + DIR *pDir = opendir(sls_dir); + if (pDir == NULL) { + fprintf(stderr, "ODR-PadEnc Error: cannot open directory '%s'\n", sls_dir); + return 1; + } + + // Add new slides to transmit to list + while ((pDirent = readdir(pDir)) != NULL) { + if (pDirent->d_name[0] != '.') { + char imagepath[256]; + sprintf(imagepath, "%s/%s", sls_dir, pDirent->d_name); + + slide_metadata_t md; + md.filepath = imagepath; + md.fidx = slides_history.get_fidx(imagepath); + slides_to_transmit.push_back(md); + + if (verbose) { + fprintf(stderr, "ODR-PadEnc found slide '%s', fidx %d\n", imagepath, md.fidx); + } + } + } + + closedir(pDir); + +#if DEBUG + slides_history.disp_database(); +#endif + + // if ATM no slides, transmit at least DLS + if (slides_to_transmit.empty()) { + if (not dls_file.empty()) + writeDLS(output_fd, dls_file, charset, raw_dls, remove_dls); + + sleep(sleepdelay); + } else { + // Sort the list in fidx order + slides_to_transmit.sort(); + + // Encode the slides + for (std::list<slide_metadata_t>::const_iterator it = slides_to_transmit.cbegin(); + it != slides_to_transmit.cend(); + ++it) { + + ret = encodeFile(output_fd, it->filepath, it->fidx, raw_slides); + if (ret != 1) + fprintf(stderr, "ODR-PadEnc Error: cannot encode file '%s'\n", it->filepath.c_str()); + + if (erase_after_tx) { + if (unlink(it->filepath.c_str()) == -1) { + fprintf(stderr, "ODR-PadEnc Error: erasing file '%s' failed: ", it->filepath.c_str()); + perror(""); + } + } + + // Always retransmit DLS after each slide, we want it to be updated frequently + if (not dls_file.empty()) + writeDLS(output_fd, dls_file, charset, raw_dls, remove_dls); + + sleep(sleepdelay); + } + + slides_to_transmit.resize(0); + } + } else { // only DLS + // Always retransmit DLS, we want it to be updated frequently + writeDLS(output_fd, dls_file, charset, raw_dls, remove_dls); + + sleep(sleepdelay); + } + } + + delete pad_packetizer; + + return 1; +} + + +DATA_GROUP* createDataGroupLengthIndicator(size_t len) { + DATA_GROUP* dg = new DATA_GROUP(2, 1, 1); // continuation never used (except for comparison at short X-PAD) + uint8_vector_t &data = dg->data; + + // Data Group length + data[0] = (len & 0x3F00) >> 8; + data[1] = (len & 0x00FF); + + // CRC + dg->AppendCRC(); + + return dg; +} + + +void warnOnSmallerImage(size_t height, size_t width, const std::string& fname) { + if (height < 240 || width < 320) + fprintf(stderr, "ODR-PadEnc Warning: Image '%s' smaller than recommended size (%zu x %zu < 320 x 240 px)\n", fname.c_str(), width, height); +} + + +/*! Scales the image down if needed, + * so that it is 320x240 pixels. + * Automatically reduces the quality to make sure the + * blobsize is not too large. + * + * \return the blobsize + */ +#if HAVE_MAGICKWAND +size_t resizeImage(MagickWand* m_wand, unsigned char** blob, const std::string& fname, bool* jfif_not_png) +{ + unsigned char* blob_png; + unsigned char* blob_jpg; + size_t blobsize_png; + size_t blobsize_jpg; + + size_t height = MagickGetImageHeight(m_wand); + size_t width = MagickGetImageWidth(m_wand); + + while (height > 240 || width > 320) { + if (height/240.0 > width/320.0) { + width = width * 240.0 / height; + height = 240; + } + else { + height = height * 320.0 / width; + width = 320; + } + MagickResizeImage(m_wand, width, height, LanczosFilter, 1); + } + + height = MagickGetImageHeight(m_wand); + width = MagickGetImageWidth(m_wand); + + // try PNG (zlib level 9 / possibly adaptive filtering) + MagickSetImageFormat(m_wand, "png"); + MagickSetImageCompressionQuality(m_wand, 95); + blob_png = MagickGetImageBlob(m_wand, &blobsize_png); + + // try JPG + MagickSetImageFormat(m_wand, "jpg"); + + blob_jpg = NULL; + int quality_jpg = 100; + do { + free(blob_jpg); + quality_jpg -= 5; + + MagickSetImageCompressionQuality(m_wand, quality_jpg); + blob_jpg = MagickGetImageBlob(m_wand, &blobsize_jpg); + } while (blobsize_jpg > MAXSLIDESIZE && quality_jpg > MINQUALITY); + + + // check for max size + if (blobsize_png > MAXSLIDESIZE && blobsize_jpg > MAXSLIDESIZE) { + fprintf(stderr, "ODR-PadEnc: Image Size too large after compression: %zu bytes (PNG), %zu bytes (JPEG)\n", + blobsize_png, blobsize_jpg); + free(blob_png); + free(blob_jpg); + return 0; + } + + // choose the smaller one (at least one does not exceed the max size) + *jfif_not_png = blobsize_jpg < blobsize_png; + + if (verbose) { + if (*jfif_not_png) + fprintf(stderr, "ODR-PadEnc resized image to %zu x %zu. Size after compression %zu bytes (JPEG, q=%d; PNG was %zu bytes)\n", + width, height, blobsize_jpg, quality_jpg, blobsize_png); + else + fprintf(stderr, "ODR-PadEnc resized image to %zu x %zu. Size after compression %zu bytes (PNG; JPEG was %zu bytes)\n", + width, height, blobsize_png, blobsize_jpg); + } + + // warn if resized image smaller than default dimension + warnOnSmallerImage(height, width, fname); + + free(*jfif_not_png ? blob_png : blob_jpg); + *blob = *jfif_not_png ? blob_jpg : blob_png; + return *jfif_not_png ? blobsize_jpg : blobsize_png; +} +#endif + +int encodeFile(int output_fd, const std::string& fname, int fidx, bool raw_slides) +{ + int ret = 0; + int nseg, lastseglen, i, last, curseglen; +#if HAVE_MAGICKWAND + MagickWand *m_wand = NULL; + MagickBooleanType err; +#endif + size_t blobsize, height, width; + bool jpeg_progr; + unsigned char *blob = NULL; + unsigned char *curseg = NULL; + MSCDG msc; + DATA_GROUP* dgli; + DATA_GROUP* mscdg; + + size_t orig_quality; + char* orig_format = NULL; + /*! We handle JPEG differently, because we want to avoid recompressing the + * image if it is suitable as is + */ + bool orig_is_jpeg = false; + + /*! If the original is a PNG, we transmit it as is, if the resolution is correct + * and the file is not too large. Otherwise it gets resized and sent as JPEG. + */ + bool orig_is_png = false; + + /*! By default, we do resize the image to 320x240, with a quality such that + * the blobsize is at most MAXSLIDESIZE. + * + * For JPEG input files that are already at the right resolution and at the + * right blobsize, we disable this to avoid quality loss due to recompression + * + * As device support of this feature is optional, we furthermore require JPEG input + * files to not have progressive coding. + */ + bool resize_required = true; + + bool jfif_not_png = true; + + if (!raw_slides) { +#if HAVE_MAGICKWAND + + m_wand = NewMagickWand(); + + err = MagickReadImage(m_wand, fname.c_str()); + if (err == MagickFalse) { + fprintf(stderr, "ODR-PadEnc Error: Unable to load image '%s'\n", + fname.c_str()); + + goto encodefile_out; + } + + height = MagickGetImageHeight(m_wand); + width = MagickGetImageWidth(m_wand); + orig_format = MagickGetImageFormat(m_wand); + jpeg_progr = MagickGetImageInterlaceScheme(m_wand) == JPEGInterlace; + + // By default assume that the image has full quality and can be reduced + orig_quality = 100; + + // strip unneeded information (profiles, meta data) + MagickStripImage(m_wand); + + if (orig_format) { + if (strcmp(orig_format, "JPEG") == 0) { + orig_quality = MagickGetImageCompressionQuality(m_wand); + orig_is_jpeg = true; + + if (verbose) { + fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d)." + " Original size: %zu x %zu. (%s, q=%zu, progr=%s)\n", + fname.c_str(), fidx, width, height, orig_format, orig_quality, jpeg_progr ? "y" : "n"); + } + } + else if (strcmp(orig_format, "PNG") == 0) { + orig_is_png = true; + jfif_not_png = false; + + if (verbose) { + fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d)." + " Original size: %zu x %zu. (%s)\n", + fname.c_str(), fidx, width, height, orig_format); + } + } + else if (verbose) { + fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d)." + " Original size: %zu x %zu. (%s)\n", + fname.c_str(), fidx, width, height, orig_format); + } + + free(orig_format); + } + else { + fprintf(stderr, "ODR-PadEnc Warning: Unable to detect image format of '%s'\n", + fname.c_str()); + + fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d). Original size: %zu x %zu.\n", + fname.c_str(), fidx, width, height); + } + + if ((orig_is_jpeg || orig_is_png) && height <= 240 && width <= 320 && not jpeg_progr) { + // Don't recompress the image and check if the blobsize is suitable + blob = MagickGetImageBlob(m_wand, &blobsize); + + if (blobsize <= MAXSLIDESIZE) { + if (verbose) { + fprintf(stderr, "ODR-PadEnc image: '%s' (id=%d). No resize needed: %zu Bytes\n", + fname.c_str(), fidx, blobsize); + } + resize_required = false; + } + } + + if (resize_required) { + blobsize = resizeImage(m_wand, &blob, fname, &jfif_not_png); + } + else { + // warn if unresized image smaller than default dimension + warnOnSmallerImage(height, width, fname); + } + +#else + fprintf(stderr, "ODR-PadEnc has not been compiled with MagickWand, only RAW slides are supported!\n"); + ret = -1; + goto encodefile_out; +#endif + } + else { // Use RAW data, it might not even be a jpg ! + // read file + FILE* pFile = fopen(fname.c_str(), "rb"); + if (pFile == NULL) { + fprintf(stderr, "ODR-PadEnc Error: Unable to load file '%s'\n", + fname.c_str()); + goto encodefile_out; + } + + // obtain file size: + fseek(pFile, 0, SEEK_END); + blobsize = ftell(pFile); + rewind(pFile); + + if (blobsize > MAXSLIDESIZE) { + fprintf(stderr, "ODR-PadEnc Warning: blob in raw-slide '%s' too large\n", + fname.c_str()); + } + + // allocate memory to contain the whole file: + blob = (unsigned char*)malloc(sizeof(char) * blobsize); + if (blob == NULL) { + fprintf(stderr, "ODR-PadEnc Error: Memory allocation error\n"); + goto encodefile_out; + } + + // copy the file into the buffer: + if (fread(blob, blobsize, 1, pFile) != 1) { + fprintf(stderr, "ODR-PadEnc Error: Could not read file\n"); + goto encodefile_out; + } + + size_t last_dot = fname.rfind("."); + + // default: + jfif_not_png = true; // This is how we did it in the past. + // It's wrong anyway, so we're at least compatible + + if (last_dot != std::string::npos) { + std::string file_extension = fname.substr(last_dot, std::string::npos); + + std::transform(file_extension.begin(), file_extension.end(), file_extension.begin(), ::tolower); + + if (file_extension == ".png") { + jfif_not_png = false; + } + } + + if (pFile != NULL) { + fclose(pFile); + } + } + + if (blobsize) { + nseg = blobsize / MAXSEGLEN; + lastseglen = blobsize % MAXSEGLEN; + if (lastseglen != 0) { + nseg++; + } + + uint8_vector_t mothdr = createMotHeader(blobsize, fidx, jfif_not_png); + // Create the MSC Data Group C-Structure + createMscDG(&msc, 3, &cindex_header, 0, 1, fidx, &mothdr[0], mothdr.size()); + // Generate the MSC DG frame (Figure 9 en 300 401) + mscdg = packMscDG(&msc); + dgli = createDataGroupLengthIndicator(mscdg->data.size()); + + pad_packetizer->queue.push_back(dgli); + pad_packetizer->queue.push_back(mscdg); + + for (i = 0; i < nseg; i++) { + curseg = blob + i * MAXSEGLEN; + if (i == nseg-1) { + curseglen = lastseglen; + last = 1; + } + else { + curseglen = MAXSEGLEN; + last = 0; + } + + createMscDG(&msc, 4, &cindex_body, i, last, fidx, curseg, curseglen); + mscdg = packMscDG(&msc); + dgli = createDataGroupLengthIndicator(mscdg->data.size()); + + pad_packetizer->queue.push_back(dgli); + pad_packetizer->queue.push_back(mscdg); + } + + pad_packetizer->WriteAllPADs(output_fd); + + ret = 1; + } + +encodefile_out: +#if HAVE_MAGICKWAND + if (m_wand) { + m_wand = DestroyMagickWand(m_wand); + } +#endif + + if (blob) { + free(blob); + } + return ret; +} + + +uint8_vector_t createMotHeader(size_t blobsize, int fidx, bool jfif_not_png) +{ + // prepare ContentName + uint8_t cntemp[10]; // = 1 + 8 + 1 = charset + name + terminator + cntemp[0] = 0x0 << 4; // charset: 0 (Complete EBU Latin based) - doesn't really matter here + snprintf((char*) (cntemp + 1), sizeof(cntemp) - 1, "%04d.%s", fidx, jfif_not_png ? "jpg" : "png"); + if (verbose) + fprintf(stderr, "ODR-PadEnc writing image as '%s'\n", cntemp + 1); + + // MOT header - content type: image, content subtype: JFIF / PNG + MOTHeader header(blobsize, 0x02, jfif_not_png ? 0x001 : 0x003); + + // TriggerTime: NOW + header.AddExtension32Bit(0x05, 0x00000000); + + // ContentName: XXXX.jpg / XXXX.png + header.AddExtensionVarSize(0x0C, cntemp, sizeof(cntemp) - 1); // omit terminator + + return header.GetData(); +} + + +void createMscDG(MSCDG* msc, unsigned short int dgtype, + int *cindex, unsigned short int segnum, unsigned short int lastseg, + unsigned short int tid, unsigned char* data, + unsigned short int datalen) +{ + msc->extflag = 0; + msc->crcflag = 1; + msc->segflag = 1; + msc->accflag = 1; + msc->dgtype = dgtype; + msc->cindex = *cindex; + msc->rindex = 0; + msc->last = lastseg; + msc->segnum = segnum; + msc->rfa = 0; + msc->tidflag = 1; + msc->lenid = 2; + msc->tid = tid; + msc->segdata = data; + msc->rcount = 0; + msc->seglen = datalen; + + *cindex = (*cindex + 1) % 16; // increment continuity index +} + + +DATA_GROUP* packMscDG(MSCDG* msc) +{ + DATA_GROUP* dg = new DATA_GROUP(9 + msc->seglen, 12, 13); + uint8_vector_t &b = dg->data; + + // headers + b[0] = (msc->extflag<<7) | (msc->crcflag<<6) | (msc->segflag<<5) | + (msc->accflag<<4) | msc->dgtype; + + b[1] = (msc->cindex<<4) | msc->rindex; + b[2] = (msc->last<<7) | ((msc->segnum & 0x7F00) >> 8); + b[3] = msc->segnum & 0x00FF; + b[4] = 0; + b[4] = (msc->rfa << 5) | (msc->tidflag << 4) | msc->lenid; + b[5] = (msc->tid & 0xFF00) >> 8; + b[6] = msc->tid & 0x00FF; + b[7] = (msc->rcount << 5) | ((msc->seglen & 0x1F00)>>8); + b[8] = msc->seglen & 0x00FF; + + // data field + memcpy(&b[9], msc->segdata, msc->seglen); + + // CRC + dg->AppendCRC(); + + return dg; +} + + +DATA_GROUP* createDynamicLabelCommand(uint8_t command) { + DATA_GROUP* dg = new DATA_GROUP(2, 2, 3); + uint8_vector_t &seg_data = dg->data; + + // prefix: toggle? + first seg + last seg + command flag + command + seg_data[0] = + (dls_toggle ? (1 << 7) : 0) + + (1 << 6) + + (1 << 5) + + (1 << 4) + + command; + + // prefix: charset (though irrelevant here) + seg_data[1] = CHARSET_COMPLETE_EBU_LATIN; + + // CRC + dg->AppendCRC(); + + return dg; +} + +DATA_GROUP* createDynamicLabelPlus(const DL_STATE& dl_state) { + size_t tags_size = dl_state.dl_plus_tags.size(); + size_t len_dl_plus_cmd_field = 1 + 3 * tags_size; + DATA_GROUP* dg = new DATA_GROUP(2 + len_dl_plus_cmd_field, 2, 3); + uint8_vector_t &seg_data = dg->data; + + // prefix: toggle? + first seg + last seg + command flag + command + seg_data[0] = + (dls_toggle ? (1 << 7) : 0) + + (1 << 6) + + (1 << 5) + + (1 << 4) + + DLS_CMD_DL_PLUS; + + // prefix: link bit + length + seg_data[1] = + (dls_toggle ? (1 << 7) : 0) + + (len_dl_plus_cmd_field - 1); // -1 ! + + // DL Plus tags command: CId + IT + IR + NT + seg_data[2] = + (DL_PLUS_CMD_TAGS << 4) + + (dl_state.dl_plus_item_toggle ? (1 << 3) : 0) + + (dl_state.dl_plus_item_running ? (1 << 2) : 0) + + (tags_size - 1); // -1 ! + + for (size_t i = 0; i < tags_size; i++) { + // DL Plus tags command: Content Type + Start Marker + Length Marker + seg_data[3 + 3 * i] = dl_state.dl_plus_tags[i].content_type & 0x7F; + seg_data[4 + 3 * i] = dl_state.dl_plus_tags[i].start_marker & 0x7F; + seg_data[5 + 3 * i] = dl_state.dl_plus_tags[i].length_marker & 0x7F; + } + + // CRC + dg->AppendCRC(); + + return dg; +} + + +std::vector<std::string> split_string(const std::string &s, const char delimiter) { + std::vector<std::string> result; + std::stringstream ss(s); + std::string part; + + while (std::getline(ss, part, delimiter)) + result.push_back(part); + return result; +} + +bool parse_dl_param_bool(const std::string &key, const std::string &value, bool &target) { + if (value == "0") { + target = 0; + return true; + } + if (value == "1") { + target = 1; + return true; + } + fprintf(stderr, "ODR-PadEnc Warning: DL parameter '%s' has unsupported value '%s' - ignored\n", key.c_str(), value.c_str()); + return false; +} + +bool parse_dl_param_int_dl_plus_tag(const std::string &key, const std::string &value, int &target) { + int value_int = atoi(value.c_str()); + if (value_int >= 0x00 && value_int <= 0x7F) { + target = value_int; + return true; + } + fprintf(stderr, "ODR-PadEnc Warning: DL Plus tag parameter '%s' %d out of range - ignored\n", key.c_str(), value_int); + return false; +} + +void parse_dl_params(std::ifstream &dls_fstream, DL_STATE &dl_state) { + std::string line; + while (std::getline(dls_fstream, line)) { + // return on params close + if (line == DL_PARAMS_CLOSE) + return; + + // ignore empty lines and comments + if (line.empty() || line[0] == '#') + continue; + + // parse key/value pair + size_t separator_pos = line.find('='); + if (separator_pos == std::string::npos) { + fprintf(stderr, "ODR-PadEnc Warning: DL parameter line '%s' without separator - ignored\n", line.c_str()); + continue; + } + std::string key = line.substr(0, separator_pos); + std::string value = line.substr(separator_pos + 1); +#if DEBUG + fprintf(stderr, "parse_dl_params: key: '%s', value: '%s'\n", key.c_str(), value.c_str()); +#endif + + if (key == "DL_PLUS") { + parse_dl_param_bool(key, value, dl_state.dl_plus_enabled); + continue; + } + if (key == "DL_PLUS_ITEM_TOGGLE") { + parse_dl_param_bool(key, value, dl_state.dl_plus_item_toggle); + continue; + } + if (key == "DL_PLUS_ITEM_RUNNING") { + parse_dl_param_bool(key, value, dl_state.dl_plus_item_running); + continue; + } + if (key == "DL_PLUS_TAG") { + if (dl_state.dl_plus_tags.size() == 4) { + fprintf(stderr, "ODR-PadEnc Warning: DL Plus tag ignored, as already four tags present\n"); + continue; + } + + // split value + std::vector<std::string> params = split_string(value, ' '); + if (params.size() != 3) { + fprintf(stderr, "ODR-PadEnc Warning: DL Plus tag value '%s' does not have three parts - ignored\n", value.c_str()); + continue; + } + + int content_type, start_marker, length_marker; + if (parse_dl_param_int_dl_plus_tag("content_type", params[0], content_type) & + parse_dl_param_int_dl_plus_tag("start_marker", params[1], start_marker) & + parse_dl_param_int_dl_plus_tag("length_marker", params[2], length_marker)) + dl_state.dl_plus_tags.push_back(DL_PLUS_TAG(content_type, start_marker, length_marker)); + continue; + } + + fprintf(stderr, "ODR-PadEnc Warning: DL parameter '%s' unknown - ignored\n", key.c_str()); + } + + fprintf(stderr, "ODR-PadEnc Warning: no param closing tag, so the DLS text will be empty\n"); +} + + +void writeDLS(int output_fd, const std::string& dls_file, uint8_t charset, bool raw_dls, bool remove_dls) +{ + std::ifstream dls_fstream(dls_file); + if (!dls_fstream.is_open()) { + std::cerr << "Could not open " << dls_file << std::endl; + return; + } + + DL_STATE dl_state; + + std::vector<std::string> dls_lines; + + std::string line; + // Read and convert lines one by one because the converter doesn't understand + // line endings + while (std::getline(dls_fstream, line)) { + if (line.empty()) + continue; + if (line == DL_PARAMS_OPEN) { + parse_dl_params(dls_fstream, dl_state); + } else { + if (not raw_dls && charset == CHARSET_UTF8) { + dls_lines.push_back(charset_converter.convert(line)); + } + else { + dls_lines.push_back(line); + } + // TODO handle the other charsets accordingly + } + } + + std::stringstream ss; + for (size_t i = 0; i < dls_lines.size(); i++) { + if (i != 0) { + if (charset == CHARSET_UCS2_BE) + ss << '\0' << '\n'; + else + ss << '\n'; + } + + // UCS-2 BE: if from file the first byte of \0\n remains, remove it + if (charset == CHARSET_UCS2_BE && dls_lines[i].size() % 2) { + dls_lines[i].resize(dls_lines[i].size() - 1); + } + + ss << dls_lines[i]; + } + + dl_state.dl_text = ss.str(); + if (dl_state.dl_text.size() > MAXDLS) + dl_state.dl_text.resize(MAXDLS); + + + // if DL Plus enabled, but no DL Plus tags were added, add the required DUMMY tag + if (dl_state.dl_plus_enabled && dl_state.dl_plus_tags.empty()) + dl_state.dl_plus_tags.push_back(DL_PLUS_TAG()); + + if (not raw_dls) + charset = CHARSET_COMPLETE_EBU_LATIN; + + + // toggle the toggle bit only on new DL state + bool dl_state_is_new = dl_state != dl_state_prev; + if (verbose) { + fprintf(stderr, "ODR-PadEnc writing %s DLS text \"%s\"\n", dl_state_is_new ? "new" : "old", dl_state.dl_text.c_str()); + if (dl_state.dl_plus_enabled) { + fprintf( + stderr, "ODR-PadEnc writing %s DL Plus tags (IT/IR: %d/%d): ", + dl_state_is_new ? "new" : "old", + dl_state.dl_plus_item_toggle ? 1 : 0, + dl_state.dl_plus_item_running ? 1 : 0); + for (dl_plus_tags_t::const_iterator it = dl_state.dl_plus_tags.begin(); it != dl_state.dl_plus_tags.end(); it++) { + if (it != dl_state.dl_plus_tags.begin()) + fprintf(stderr, ", "); + fprintf(stderr, "%d (S/L: %d/%d)", it->content_type, it->start_marker, it->length_marker); + } + fprintf(stderr, "\n"); + } + } + + DATA_GROUP *remove_label_dg = NULL; + if (dl_state_is_new) { + if (remove_dls) + remove_label_dg = createDynamicLabelCommand(DLS_CMD_REMOVE_LABEL); + + dls_toggle = !dls_toggle; // indicate changed text + + dl_state_prev = dl_state; + } + + prepend_dl_dgs(dl_state, charset); + if (remove_label_dg) + pad_packetizer->queue.push_front(remove_label_dg); + pad_packetizer->WriteAllPADs(output_fd); +} + + +int dls_count(const std::string& text) { + size_t text_len = text.size(); + return text_len / DLS_SEG_LEN_CHAR_MAX + (text_len % DLS_SEG_LEN_CHAR_MAX ? 1 : 0); +} + + +DATA_GROUP* dls_get(const std::string& text, uint8_t charset, int seg_index) { + bool first_seg = seg_index == 0; + bool last_seg = seg_index == dls_count(text) - 1; + + int seg_text_offset = seg_index * DLS_SEG_LEN_CHAR_MAX; + const char *seg_text_start = text.c_str() + seg_text_offset; + size_t seg_text_len = MIN(text.size() - seg_text_offset, DLS_SEG_LEN_CHAR_MAX); + + DATA_GROUP* dg = new DATA_GROUP(DLS_SEG_LEN_PREFIX + seg_text_len, 2, 3); + uint8_vector_t &seg_data = dg->data; + + // prefix: toggle? + first seg? + last seg? + (seg len - 1) + seg_data[0] = + (dls_toggle ? (1 << 7) : 0) + + (first_seg ? (1 << 6) : 0) + + (last_seg ? (1 << 5) : 0) + + (seg_text_len - 1); + + // prefix: charset / seg index + seg_data[1] = (first_seg ? charset : seg_index) << 4; + + // character field + memcpy(&seg_data[DLS_SEG_LEN_PREFIX], seg_text_start, seg_text_len); + + // CRC + dg->AppendCRC(); + +#if DEBUG + fprintf(stderr, "DL segment:"); + for (int i = 0; i < seg_data.size(); i++) + fprintf(stderr, " %02x", seg_data[i]); + fprintf(stderr, "\n"); +#endif + return dg; +} + + +void prepend_dl_dgs(const DL_STATE& dl_state, uint8_t charset) { + // process all DL segments + int seg_count = dls_count(dl_state.dl_text); + std::vector<DATA_GROUP*> segs; + for (int seg_index = 0; seg_index < seg_count; seg_index++) { +#if DEBUG + fprintf(stderr, "Segment number %d\n", seg_index + 1); +#endif + segs.push_back(dls_get(dl_state.dl_text, charset, seg_index)); + } + + // if enabled, add DL Plus data group + if (dl_state.dl_plus_enabled) + segs.push_back(createDynamicLabelPlus(dl_state)); + + // prepend to packetizer + pad_packetizer->queue.insert(pad_packetizer->queue.begin(), segs.begin(), segs.end()); + +#if DEBUG + fprintf(stderr, "PAD length: %d\n", padlen); + fprintf(stderr, "DLS text: %s\n", text.c_str()); + fprintf(stderr, "Number of DL segments: %d\n", seg_count); +#endif +} + +int History::find(const fingerprint_t& fp) const +{ + size_t i; + for (i = 0; i < m_database.size(); i++) { + if (m_database[i] == fp) { + // return the id of fingerprint found + return m_database[i].fidx; + } + } + + // return -1 when the database doesn't contain this fingerprint + return -1; +} + +void History::add(fingerprint_t& fp) +{ + m_database.push_back(fp); + + if (m_database.size() > m_hist_size) { + m_database.pop_front(); + } +} + +void History::disp_database() +{ + size_t id; + printf("HISTORY DATABASE:\n"); + if (m_database.empty()) { + printf(" empty\n"); + } + else { + for (id = 0; id < m_database.size(); id++) { + printf(" id %4zu: ", id); + m_database[id].disp(); + } + } + printf("-----------------\n"); +} + +int History::get_fidx(const char* filepath) +{ + fingerprint_t fp; + + fp.load_from_file(filepath); + + int idx = find(fp); + + if (idx < 0) { + idx = m_last_given_fidx++; + fp.fidx = idx; + + if (m_last_given_fidx > MAXSLIDEID) { + m_last_given_fidx = 0; + } + + add(fp); + } + + return idx; +} + |