/*
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, 2017 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 .
*/
/*!
\file odr-padenc.cpp
\brief Generate PAD data for MOT Slideshow and DLS
\author Sergio Sagliocco
\author Matthias P. Braendli
\author Stefan Pöschel
*/
#include "common.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "pad_common.h"
#include "dls.h"
#include "sls.h"
static const int SLEEPDELAY_DEFAULT = 10; // seconds
static const int DLS_REPETITION_WHILE_SLS = 50;
static bool do_exit = false;
static void break_handler(int) {
fprintf(stderr, "...ODR-PadEnc exits...\n");
do_exit = true;
}
static void usage(const 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: %d\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"
" If specified more than once, use next file after DELAY seconds.\n"
" -p, --pad=LENGTH Set the pad length.\n"
" Possible values: %s\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",
SLEEPDELAY_DEFAULT, PADPacketizer::ALLOWED_PADLEN.c_str()
);
}
static std::string list_dls_files(std::vector dls_files) {
std::string result = "";
for(std::string dls_file : dls_files) {
if(!result.empty())
result += "/";
result += "'" + dls_file + "'";
}
return result;
}
static int filter_slides(const struct dirent* file) {
std::string name = file->d_name;
// skip '.'/'..' dirs
if(name == "." || name == "..")
return 0;
// skip slide params files
if(SLSManager::isSlideParamFileFilename(name))
return 0;
return 1;
}
static bool read_slides_dir(const std::string& dir, History& history, std::list& slides) {
struct dirent** dir_entries;
int dir_count = scandir(dir.c_str(), &dir_entries, filter_slides, alphasort);
if(dir_count < 0) {
fprintf(stderr, "ODR-PadEnc Error: cannot open directory '%s'\n", dir.c_str());
return false;
}
// add new slides to transmit to list
for(int i = 0; i < dir_count; i++) {
std::string imagepath = dir + "/" + std::string(dir_entries[i]->d_name);
free(dir_entries[i]);
slide_metadata_t md;
md.filepath = imagepath;
md.fidx = history.get_fidx(imagepath.c_str());
slides.push_back(md);
if (verbose)
fprintf(stderr, "ODR-PadEnc found slide '%s', fidx %d\n", imagepath.c_str(), md.fidx);
}
free(dir_entries);
#ifdef DEBUG
slides_history.disp_database();
#endif
return true;
}
int main(int argc, char *argv[]) {
// handle signals
if(signal(SIGINT, break_handler) == SIG_ERR) {
perror("ODR-PadEnc Error: could not set SIGINT handler");
return 1;
}
if(signal(SIGTERM, break_handler) == SIG_ERR) {
perror("ODR-PadEnc Error: could not set SIGTERM handler");
return 1;
}
size_t padlen = 58;
bool erase_after_tx = false;
int sleepdelay = SLEEPDELAY_DEFAULT;
bool raw_slides = false;
DL_PARAMS dl_params;
const char* sls_dir = NULL;
const char* output = "/tmp/pad.fifo";
std::vector dls_files;
int curr_dls_file = 0;
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;
while((ch = getopt_long(argc, argv, "eChRrc:d:o:p:s:t:v", longopts, NULL)) != -1) {
switch (ch) {
case 'c':
dl_params.charset = (DABCharset) atoi(optarg);
break;
case 'C':
dl_params.raw_dls = true;
break;
case 'r':
dl_params.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': // can be used more than once!
dls_files.push_back(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 (!PADPacketizer::CheckPADLen(padlen)) {
fprintf(stderr, "ODR-PadEnc Error: PAD length %zu invalid: Possible values: %s\n",
padlen, PADPacketizer::ALLOWED_PADLEN.c_str());
return 2;
}
if (sls_dir && not dls_files.empty()) {
fprintf(stderr, "ODR-PadEnc encoding Slideshow from '%s' and DLS from %s to '%s'\n",
sls_dir, list_dls_files(dls_files).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_files.empty()) {
fprintf(stderr, "ODR-PadEnc encoding DLS from %s to '%s'. No Slideshow.\n",
list_dls_files(dls_files).c_str(), output);
}
else {
fprintf(stderr, "ODR-PadEnc Error: Neither DLS nor Slideshow to encode !\n");
usage(argv[0]);
return 1;
}
const char* user_charset;
switch (dl_params.charset) {
case DABCharset::COMPLETE_EBU_LATIN:
user_charset = "Complete EBU Latin";
break;
case DABCharset::EBU_LATIN_CY_GR:
user_charset = "EBU Latin core, Cyrillic, Greek";
break;
case DABCharset::EBU_LATIN_AR_HE_CY_GR:
user_charset = "EBU Latin core, Arabic, Hebrew, Cyrillic, Greek";
break;
case DABCharset::ISO_LATIN_ALPHABET_2:
user_charset = "ISO Latin Alphabet 2";
break;
case DABCharset::UCS2_BE:
user_charset = "UCS-2 BE";
break;
case DABCharset::UTF8:
user_charset = "UTF-8";
break;
default:
fprintf(stderr, "ODR-PadEnc Error: Invalid charset!\n");
usage(argv[0]);
return 1;
}
fprintf(stderr, "ODR-PadEnc using charset %s (%d)\n",
user_charset, (int) dl_params.charset);
if (not dl_params.raw_dls) {
switch (dl_params.charset) {
case DABCharset::COMPLETE_EBU_LATIN:
// no conversion needed
break;
case DABCharset::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();
if (verbose)
fprintf(stderr, "ODR-PadEnc using ImageMagick version '%s'\n", GetMagickVersion(NULL));
#endif
PADPacketizer pad_packetizer(padlen);
DLSManager dls_manager(&pad_packetizer);
SLSManager sls_manager(&pad_packetizer);
std::list slides_to_transmit;
History slides_history;
std::chrono::steady_clock::time_point next_run = std::chrono::steady_clock::now();
while(!do_exit) {
// try to read slides dir (if present)
if (sls_dir && slides_to_transmit.empty()) {
if(!read_slides_dir(sls_dir, slides_history, slides_to_transmit))
return 1;
// sort the list in fidx order
slides_to_transmit.sort();
}
// if slides available, encode the first one
if (!slides_to_transmit.empty()) {
slide_metadata_t slide = slides_to_transmit.front();
slides_to_transmit.pop_front();
if (!sls_manager.encodeFile(slide.filepath, slide.fidx, raw_slides))
fprintf(stderr, "ODR-PadEnc Error: cannot encode file '%s'\n", slide.filepath.c_str());
if (erase_after_tx) {
if (unlink(slide.filepath.c_str()) == -1) {
fprintf(stderr, "ODR-PadEnc Error: erasing file '%s' failed: ", slide.filepath.c_str());
perror("");
}
}
// while flushing, insert DLS (if present) after a certain PAD amout
while (pad_packetizer.QueueFilled()) {
if (not dls_files.empty())
dls_manager.writeDLS(dls_files[curr_dls_file], dl_params);
pad_packetizer.WriteAllPADs(output_fd, DLS_REPETITION_WHILE_SLS);
}
}
// encode (a last) DLS (if present)
if (not dls_files.empty()) {
dls_manager.writeDLS(dls_files[curr_dls_file], dl_params);
// switch to next DLS file
curr_dls_file = (curr_dls_file + 1) % dls_files.size();
}
// flush all remaining PADs
pad_packetizer.WriteAllPADs(output_fd);
// sleep until next run
next_run += std::chrono::seconds(sleepdelay);
std::this_thread::sleep_until(next_run);
}
// cleanup
if(close(output_fd)) {
perror("ODR-PadEnc Error: failed to close output");
return 1;
}
#if HAVE_MAGICKWAND
MagickWandTerminus();
#endif
return 0;
}