UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
export.c
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2017-2024 CESNET
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, is permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * 3. Neither the name of CESNET nor the names of its contributors may be
21 * used to endorse or promote products derived from this software without
22 * specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
26 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
28 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
35 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
37
38#include <dirent.h>
39#include <errno.h> // for errno, EEXIST
40#include <limits.h>
41#include <stdbool.h>
42#include <stdlib.h> // for NULL, free, calloc, strtol
43#include <string.h> // for strdup
44#include <sys/types.h>
45#include <time.h>
46
47#include "export.h"
48
49#include "audio/export.h"
50#define WANT_MKDIR
51#include "compat/misc.h" // for mkdir
52#include "compat/time.h" // for localtime_s
53#include "debug.h"
54#include "host.h"
55#include "messaging.h"
56#include "module.h"
57#include "utils/color_out.h"
58#include "utils/fs.h" // MAX_PATH_SIZE
59#include "utils/misc.h"
60#include "video_export.h"
61
62#define MOD_NAME "[export] "
63
64struct exporter {
65 struct module mod;
66 char *dir;
67 bool override;
72 bool noaudio;
73 bool novideo;
74 pthread_mutex_t lock;
75
76 long long int limit;
78};
79
80static bool create_dir(struct exporter *s);
81static bool enable_export(struct exporter *s);
82static void disable_export(struct exporter *s);
83static void process_messages(struct exporter *s);
84
85static void new_msg(struct module *mod) {
87}
88
89static void usage() {
90 color_printf("Usage:\n");
92 TRED("--record") "[=<dir>[:limit=<n>[:exit_on_limit]][:noaudio]"
93 "[:novideo][:override][:paused]] ") "\n" "\t" TBOLD(TRED("-E")
94 "[<dir>[:<opts>]]") "\n\t" TBOLD("--record=help | -Ehelp") "\n");
95 color_printf("where\n");
96 color_printf(TERM_BOLD "\tlimit=<n>" TERM_RESET " - write at "
97 " most <n> video frames (with optional exit)\n");
99 " - export even if it would override "
100 "existing files in the given directory\n");
101 color_printf(TERM_BOLD "\tnoaudio | novideo" TERM_RESET " - do not export audio/video\n");
102 color_printf(TERM_BOLD "\tpaused" TERM_RESET " - use specified directory but do not export immediately (can be started with a key or through control socket)\n");
103}
104
105static bool
106parse_options(struct exporter *s, const char *ccfg, bool *should_export)
107{
108 if (ccfg == NULL) {
109 return true;
110 }
111 char buf[STR_LEN];
112 snprintf(buf, sizeof buf, "%s", ccfg);
113 char *cfg = buf;
114 char *save_ptr = NULL;
115 char *item = NULL;
116 while ((item = strtok_r(cfg, ":", &save_ptr)) != NULL) {
117 if (strstr(item, "help") == item) {
118 usage();
119 return false;
120 } else if (strstr(item, "noaudio") == item) {
121 s->noaudio = true;
122 } else if (strstr(item, "novideo") == item) {
123 s->novideo = true;
124 } else if (strstr(item, "override") == item) {
125 s->override = true;
126 } else if (strstr(item, "paused") == item) {
127 *should_export = false; // start paused
128 } else if (strstr(item, "limit=") == item) {
129 s->limit = strtoll(item + strlen("limit="), NULL, 0);
130 if (s->limit < 0) {
131 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Wrong limit: %s!\n", item + strlen("limit="));
132 return false;
133 }
134 } else if (strcmp(item, "exit_on_limit") == 0) {
135 s->exit_on_limit = true;
136 } else if (s->dir == NULL && cfg != NULL) {
137 s->dir = strdup(item);
138 } else {
139 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Wrong option: %s!\n", item);
140 return false;
141 }
142 cfg = NULL;
143 }
144 return true;
145}
146
147#define HANDLE_ERROR export_destroy(s); return NULL;
148
149struct exporter *export_init(struct module *parent, const char *cfg, bool should_export)
150{
151 struct exporter *s = calloc(1, sizeof(struct exporter));
152 pthread_mutex_init(&s->lock, NULL);
153 s->limit = -1;
154
155 if (!parse_options(s, cfg, &should_export)) {
157 }
158 if (s->dir == NULL) {
159 s->dir_auto = true;
160 }
161
162 if (should_export) {
163 if (!enable_export(s)) {
165 }
166 }
167
171 s->mod.priv_data = s;
173
174 return s;
175}
176
177static bool enable_export(struct exporter *s)
178{
179 if (!create_dir(s)) {
180 goto error;
181 }
182
183 if (!s->novideo) {
185 if (!s->video_export) {
186 goto error;
187 }
188 }
189
190 if (!s->noaudio) {
191 char name[MAX_PATH_SIZE];
192 snprintf(name, sizeof name, "%s/sound.wav", s->dir);
194 if (!s->audio_export) {
195 goto error;
196 }
197 }
198
199 s->exporting = true;
200
201 return true;
202
203error:
205 s->video_export = NULL;
206 return false;
207}
208
214static char *
215create_implicit_dir(const char *prefix)
216{
217 enum {
218 MAX_EXPORTS = 9999,
219 };
220 for (int i = 1; i <= MAX_EXPORTS; i++) {
221 char name[MAX_PATH_SIZE];
222 time_t t = time(NULL);
223 struct tm tm_buf = { 0 };
224 localtime_s(&t, &tm_buf);
225 snprintf(name, sizeof name, "%s/", prefix);
226 strftime(name + strlen(name), sizeof name - strlen(name),
227 "export.%Y%m%d", &tm_buf);
228 if (i > 1) {
229 snprintf(name + strlen(name),
230 sizeof name - strlen(name), "-%d", i);
231 }
232 int ret = mkdir(name, S_IRWXU | S_IRWXG | S_IRWXO);
233 if(ret == -1) {
234 if(errno == EEXIST) { // record exists, try next directory
235 continue;
236 }
238 MOD_NAME "Directory creation failed: %s\n",
239 ug_strerror(errno));
240 return false;
241 }
242 return strdup(name);
243 }
244 return NULL;
245}
246
247static bool dir_is_empty(const char *dir) {
248 DIR *d = opendir(dir);
249 if (!d) {
250 return false;
251 }
252 readdir(d); // skip . and ..
253 readdir(d);
254 bool ret = readdir(d) == NULL;
255 closedir(d);
256
257 return ret;
258}
259
260static bool create_dir(struct exporter *s)
261{
262 if (!s->dir) {
263 s->dir = create_implicit_dir(".");
264 } else {
265 int ret = mkdir(s->dir, S_IRWXU | S_IRWXG | S_IRWXO);
266 if(ret == -1) {
267 if(errno != EEXIST) {
268 perror("[Export] Directory creation failed");
269 return false;
270 }
271 if (dir_is_empty(s->dir)) {
272 log_msg(LOG_LEVEL_NOTICE, "[Export] Warning: directory %s exists but is an empty directory - using for export.\n", s->dir);
273 } else if (s->override) {
274 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Warning: directory %s exists and is not an empty directory but using as requested.\n", s->dir);
275 } else {
276 MSG(ERROR,
277 "Warning: directory %s exists and is not "
278 "an empty directory! Trying to create "
279 "subdir.\n",
280 s->dir);
281 char *prefix = s->dir;
282 s->dir = create_implicit_dir(prefix);
283 free(prefix);
284 }
285 }
286 }
287
288 if (s->dir) {
289 color_printf(TERM_BOLD TERM_FG_YELLOW "Using export directory: %s\n" TERM_RESET, s->dir);
290 return true;
291 } else {
292 return false;
293 }
294}
295
296static void disable_export(struct exporter *s) {
299 s->audio_export = NULL;
300 s->video_export = NULL;
301 if (s->dir_auto) {
302 free(s->dir);
303 s->dir = NULL;
304 }
305 s->exporting = false;
306}
307
308void export_destroy(struct exporter *s) {
310
311 pthread_mutex_destroy(&s->lock);
312 module_done(&s->mod);
313 free(s->dir);
314 free(s);
315}
316
317static void process_messages(struct exporter *s) {
318 struct message *m;
319 while ((m = check_message(&s->mod))) {
320 struct response *r;
321 pthread_mutex_lock(&s->lock);
322 struct msg_universal *msg = (struct msg_universal *) m;
323 if (strcmp(msg->text, "toggle") == 0) {
324 if (s->exporting) {
326 } else {
327 enable_export(s);
328 }
329 log_msg(LOG_LEVEL_NOTICE, "Exporting: %s\n", s->exporting ? "ON" : "OFF");
330 r = new_response(RESPONSE_OK, NULL);
331 } else if (strcmp(msg->text, "status") == 0) {
332 r = new_response(RESPONSE_OK, s->exporting ? "true" : "false");
333 } else {
335 }
336 pthread_mutex_unlock(&s->lock);
337 free_message(m, r);
338 }
339}
340
341void export_audio(struct exporter *s, struct audio_frame *frame)
342{
343 if(!s){
344 return;
345 }
346
348
349 pthread_mutex_lock(&s->lock);
350 if (s->exporting) {
352 }
353 pthread_mutex_unlock(&s->lock);
354}
355
356void export_video(struct exporter *s, struct video_frame *frame)
357{
358 if(!s){
359 return;
360 }
361
363
364 pthread_mutex_lock(&s->lock);
365 if (s->exporting) {
367 }
368 if (s->limit > 0) {
369 if (--s->limit == 0) {
370 log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Stopping export - limit reached.\n");
372 if (s->exit_on_limit) {
373 exit_uv(0);
374 }
375 }
376 }
377 pthread_mutex_unlock(&s->lock);
378}
379
struct video_frame * frame
struct audio_export * audio_export_init(const char *filename)
Definition export.c:159
void audio_export_destroy(struct audio_export *s)
Definition export.c:182
void audio_export(struct audio_export *s, const struct audio_frame *frame)
Definition export.c:221
int color_printf(const char *format,...)
#define TERM_BOLD
Definition color_out.h:48
#define TERM_FG_YELLOW
Definition color_out.h:52
#define TERM_RESET
Definition color_out.h:47
#define TRED(x)
Definition color_out.h:75
#define TBOLD(x)
Definition color_out.h:70
#define localtime_s
Definition time.h:51
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define MSG(l, fmt,...)
Definition debug.h:96
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
void export_audio(struct exporter *s, struct audio_frame *frame)
Definition export.c:341
static bool enable_export(struct exporter *s)
Definition export.c:177
static void process_messages(struct exporter *s)
Definition export.c:317
void export_video(struct exporter *s, struct video_frame *frame)
Definition export.c:356
static void usage()
Definition export.c:89
static bool parse_options(struct exporter *s, const char *ccfg, bool *should_export)
Definition export.c:106
#define HANDLE_ERROR
Definition export.c:147
static void disable_export(struct exporter *s)
Definition export.c:296
static bool create_dir(struct exporter *s)
Definition export.c:260
void export_destroy(struct exporter *s)
Definition export.c:308
static void new_msg(struct module *mod)
Definition export.c:85
static bool dir_is_empty(const char *dir)
Definition export.c:247
#define MOD_NAME
Definition export.c:62
struct exporter * export_init(struct module *parent, const char *cfg, bool should_export)
Definition export.c:149
static char * create_implicit_dir(const char *prefix)
Definition export.c:215
#define MAX_PATH_SIZE
Definition fs.h:48
void module_init_default(struct module *module_data)
Definition module.c:48
void module_register(struct module *module_data, struct module *parent)
Definition module.c:82
void module_done(struct module *module_data)
Definition module.c:93
@ MODULE_CLASS_EXPORTER
Definition module.h:96
void exit_uv(int status)
Definition host.cpp:625
@ STR_LEN
"standard" string length placeholder
Definition macros.h:134
void free_message(struct message *msg, struct response *r)
struct response * new_response(int status, const char *text)
struct message * check_message(struct module *mod)
#define RESPONSE_OK
Definition messaging.h:57
#define RESPONSE_NOT_FOUND
Definition messaging.h:61
const char * ug_strerror(int errnum)
Definition misc.cpp:203
const char * parent
Definition settings.cpp:198
const char * name
Definition settings.cpp:193
struct used to comunicate with audio capture/playback device
Definition types.h:99
pthread_mutex_t lock
Definition export.c:74
bool override
Definition export.c:67
struct module mod
Definition export.c:65
bool exit_on_limit
Definition export.c:77
bool novideo
Definition export.c:73
struct video_export * video_export
Definition export.c:69
struct audio_export * audio_export
Definition export.c:70
long long int limit
number of video frames to record, -1 == unlimited (default)
Definition export.c:76
bool noaudio
Definition export.c:72
bool dir_auto
Definition export.c:68
bool exporting
Definition export.c:71
char * dir
Definition export.c:66
Definition hd-rum.c:27
enum module_class cls
Definition module.h:115
void * priv_data
Definition module.h:126
notify_t new_message
if set, notifies module that new message is in queue, receiver lock is hold during the call
Definition module.h:119
struct message m
Definition messaging.h:156
Struct video_frame represents a video frame and contains video description.
Definition types.h:305
void video_export(struct video_export *s, struct video_frame *frame)
void video_export_destroy(struct video_export *s)
struct video_export * video_export_init(const char *path)