Coverage Report

Created: 2025-09-27 07:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/glib/poppler-media.cc
Line
Count
Source
1
/* poppler-media.cc: glib interface to MediaRendition
2
 *
3
 * Copyright (C) 2010 Carlos Garcia Campos <carlosgc@gnome.org>
4
 * Copyright (C) 2025 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2, or (at your option)
9
 * any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
19
 */
20
21
#include "config.h"
22
23
#include <cerrno>
24
25
#include <goo/gfile.h>
26
27
#include "poppler-media.h"
28
#include "poppler-private.h"
29
30
/**
31
 * SECTION: poppler-media
32
 * @short_description: Media
33
 * @title: PopplerMedia
34
 */
35
36
typedef struct _PopplerMediaClass PopplerMediaClass;
37
38
struct _PopplerMedia
39
{
40
    GObject parent_instance;
41
42
    gchar *filename;
43
    gboolean auto_play;
44
    gboolean show_controls;
45
    gfloat repeat_count;
46
47
    gchar *mime_type;
48
    Object stream;
49
};
50
51
struct _PopplerMediaClass
52
{
53
    GObjectClass parent_class;
54
};
55
56
0
G_DEFINE_TYPE(PopplerMedia, poppler_media, G_TYPE_OBJECT)
57
0
58
0
static void poppler_media_finalize(GObject *object)
59
0
{
60
0
    PopplerMedia *media = POPPLER_MEDIA(object);
61
62
0
    if (media->filename) {
63
0
        g_free(media->filename);
64
0
        media->filename = nullptr;
65
0
    }
66
67
0
    if (media->mime_type) {
68
0
        g_free(media->mime_type);
69
0
        media->mime_type = nullptr;
70
0
    }
71
72
0
    media->stream = Object();
73
74
0
    G_OBJECT_CLASS(poppler_media_parent_class)->finalize(object);
75
0
}
76
77
static void poppler_media_class_init(PopplerMediaClass *klass)
78
0
{
79
0
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
80
81
0
    gobject_class->finalize = poppler_media_finalize;
82
0
}
83
84
0
static void poppler_media_init(PopplerMedia *media) { }
85
86
PopplerMedia *_poppler_media_new(const MediaRendition *poppler_media)
87
0
{
88
0
    PopplerMedia *media;
89
90
0
    g_assert(poppler_media != nullptr);
91
92
0
    media = POPPLER_MEDIA(g_object_new(POPPLER_TYPE_MEDIA, nullptr));
93
94
0
    if (poppler_media->getIsEmbedded()) {
95
0
        const GooString *mime_type;
96
97
0
        media->stream = poppler_media->getEmbbededStreamObject()->copy();
98
0
        mime_type = poppler_media->getContentType();
99
0
        if (mime_type) {
100
0
            media->mime_type = g_strdup(mime_type->c_str());
101
0
        }
102
0
    } else {
103
0
        media->filename = g_strdup(poppler_media->getFileName()->c_str());
104
0
    }
105
106
0
    const MediaParameters *mp = poppler_media->getBEParameters();
107
0
    mp = mp ? mp : poppler_media->getMHParameters();
108
109
0
    media->auto_play = mp ? mp->autoPlay : false;
110
0
    media->show_controls = mp ? mp->showControls : false;
111
0
    media->repeat_count = mp ? mp->repeatCount : 1.f;
112
113
0
    return media;
114
0
}
115
116
/**
117
 * poppler_media_get_filename:
118
 * @poppler_media: a #PopplerMedia
119
 *
120
 * Returns the media clip filename, in case of non-embedded media. filename might be
121
 * a local relative or absolute path or a URI
122
 *
123
 * Return value: a filename, return value is owned by #PopplerMedia and should not be freed
124
 *
125
 * Since: 0.14
126
 */
127
const gchar *poppler_media_get_filename(PopplerMedia *poppler_media)
128
0
{
129
0
    g_return_val_if_fail(POPPLER_IS_MEDIA(poppler_media), NULL);
130
0
    g_return_val_if_fail(!poppler_media->stream.isStream(), NULL);
131
132
0
    return poppler_media->filename;
133
0
}
134
135
/**
136
 * poppler_media_is_embedded:
137
 * @poppler_media: a #PopplerMedia
138
 *
139
 * Whether the media clip is embedded in the PDF. If the result is %TRUE, the embedded stream
140
 * can be saved with poppler_media_save() or poppler_media_save_to_callback() function.
141
 * If the result is %FALSE, the media clip filename can be retrieved with
142
 * poppler_media_get_filename() function.
143
 *
144
 * Return value: %TRUE if media clip is embedded, %FALSE otherwise
145
 *
146
 * Since: 0.14
147
 */
148
gboolean poppler_media_is_embedded(PopplerMedia *poppler_media)
149
0
{
150
0
    g_return_val_if_fail(POPPLER_IS_MEDIA(poppler_media), FALSE);
151
152
0
    return poppler_media->stream.isStream();
153
0
}
154
155
/**
156
 * poppler_media_get_auto_play:
157
 * @poppler_media: a #PopplerMedia
158
 *
159
 * Returns the auto-play parameter.
160
 *
161
 * Return value: %TRUE if media should auto-play, %FALSE otherwise
162
 *
163
 * Since: 20.04.0
164
 */
165
gboolean poppler_media_get_auto_play(PopplerMedia *poppler_media)
166
0
{
167
0
    g_return_val_if_fail(POPPLER_IS_MEDIA(poppler_media), FALSE);
168
169
0
    return poppler_media->auto_play;
170
0
}
171
172
/**
173
 * poppler_media_get_show_controls:
174
 * @poppler_media: a #PopplerMedia
175
 *
176
 * Returns the show controls parameter.
177
 *
178
 * Return value: %TRUE if media should show controls, %FALSE otherwise
179
 *
180
 * Since: 20.04.0
181
 */
182
gboolean poppler_media_get_show_controls(PopplerMedia *poppler_media)
183
0
{
184
0
    g_return_val_if_fail(POPPLER_IS_MEDIA(poppler_media), FALSE);
185
186
0
    return poppler_media->show_controls;
187
0
}
188
189
/**
190
 * poppler_media_get_repeat_count:
191
 * @poppler_media: a #PopplerMedia
192
 *
193
 * Returns the repeat count parameter.
194
 *
195
 * Return value: Repeat count parameter (float)
196
 *
197
 * Since: 20.04.0
198
 */
199
gfloat poppler_media_get_repeat_count(PopplerMedia *poppler_media)
200
0
{
201
0
    g_return_val_if_fail(POPPLER_IS_MEDIA(poppler_media), FALSE);
202
203
0
    return poppler_media->repeat_count;
204
0
}
205
206
/**
207
 * poppler_media_get_mime_type:
208
 * @poppler_media: a #PopplerMedia
209
 *
210
 * Returns the media clip mime-type
211
 *
212
 * Return value: the mime-type, return value is owned by #PopplerMedia and should not be freed
213
 *
214
 * Since: 0.14
215
 */
216
const gchar *poppler_media_get_mime_type(PopplerMedia *poppler_media)
217
0
{
218
0
    g_return_val_if_fail(POPPLER_IS_MEDIA(poppler_media), NULL);
219
220
0
    return poppler_media->mime_type;
221
0
}
222
223
static gboolean save_helper(const gchar *buf, gsize count, gpointer data, GError **error)
224
0
{
225
0
    FILE *f = (FILE *)data;
226
0
    gsize n;
227
228
0
    n = fwrite(buf, 1, count, f);
229
0
    if (n != count) {
230
0
        int errsv = errno;
231
0
        g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errsv), "Error writing to media file: %s", g_strerror(errsv));
232
0
        return FALSE;
233
0
    }
234
235
0
    return TRUE;
236
0
}
237
238
/**
239
 * poppler_media_save:
240
 * @poppler_media: a #PopplerMedia
241
 * @filename: name of file to save
242
 * @error: (allow-none): return location for error, or %NULL.
243
 *
244
 * Saves embedded stream of @poppler_media to a file indicated by @filename.
245
 * If @error is set, %FALSE will be returned.
246
 * Possible errors include those in the #G_FILE_ERROR domain
247
 * and whatever the save function generates.
248
 *
249
 * Return value: %TRUE, if the file successfully saved
250
 *
251
 * Since: 0.14
252
 */
253
gboolean poppler_media_save(PopplerMedia *poppler_media, const char *filename, GError **error)
254
0
{
255
0
    gboolean result;
256
0
    FILE *f;
257
258
0
    g_return_val_if_fail(POPPLER_IS_MEDIA(poppler_media), FALSE);
259
0
    g_return_val_if_fail(poppler_media->stream.isStream(), FALSE);
260
261
0
    f = openFile(filename, "wb");
262
263
0
    if (f == nullptr) {
264
0
        gchar *display_name = g_filename_display_name(filename);
265
0
        g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errno), "Failed to open '%s' for writing: %s", display_name, g_strerror(errno));
266
0
        g_free(display_name);
267
0
        return FALSE;
268
0
    }
269
270
0
    result = poppler_media_save_to_callback(poppler_media, save_helper, f, error);
271
272
0
    if (fclose(f) < 0) {
273
0
        gchar *display_name = g_filename_display_name(filename);
274
0
        g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errno), "Failed to close '%s', all data may not have been saved: %s", display_name, g_strerror(errno));
275
0
        g_free(display_name);
276
0
        return FALSE;
277
0
    }
278
279
0
    return result;
280
0
}
281
282
#ifndef G_OS_WIN32
283
284
/**
285
 * poppler_media_save_to_fd:
286
 * @poppler_media: a #PopplerMedia
287
 * @fd: a valid file descriptor open for writing
288
 * @error: (allow-none): return location for error, or %NULL.
289
 *
290
 * Saves embedded stream of @poppler_media to a file referred to by @fd.
291
 * If @error is set, %FALSE will be returned.
292
 * Possible errors include those in the #G_FILE_ERROR domain
293
 * and whatever the save function generates.
294
 * Note that this function takes ownership of @fd; you must not operate on it
295
 * again, nor close it.
296
 *
297
 * Return value: %TRUE, if the file successfully saved
298
 *
299
 * Since: 21.12.0
300
 */
301
gboolean poppler_media_save_to_fd(PopplerMedia *poppler_media, int fd, GError **error)
302
0
{
303
0
    gboolean result;
304
0
    FILE *f;
305
306
0
    g_return_val_if_fail(POPPLER_IS_MEDIA(poppler_media), FALSE);
307
0
    g_return_val_if_fail(poppler_media->stream.isStream(), FALSE);
308
309
0
    f = fdopen(fd, "wb");
310
0
    if (f == nullptr) {
311
0
        int errsv = errno;
312
0
        g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errsv), "Failed to open FD %d for writing: %s", fd, g_strerror(errsv));
313
0
        close(fd);
314
0
        return FALSE;
315
0
    }
316
317
0
    result = poppler_media_save_to_callback(poppler_media, save_helper, f, error);
318
319
0
    if (fclose(f) < 0) {
320
0
        int errsv = errno;
321
0
        g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(errsv), "Failed to close FD %d, all data may not have been saved: %s", fd, g_strerror(errsv));
322
0
        return FALSE;
323
0
    }
324
325
0
    return result;
326
0
}
327
328
#endif /* !G_OS_WIN32 */
329
330
0
#define BUF_SIZE 1024
331
332
/**
333
 * poppler_media_save_to_callback:
334
 * @poppler_media: a #PopplerMedia
335
 * @save_func: (scope call): a function that is called to save each block of data that the save routine generates.
336
 * @user_data: user data to pass to the save function.
337
 * @error: (allow-none): return location for error, or %NULL.
338
 *
339
 * Saves embedded stream of @poppler_media by feeding the produced data to @save_func. Can be used
340
 * when you want to store the media clip stream to something other than a file, such as
341
 * an in-memory buffer or a socket. If @error is set, %FALSE will be
342
 * returned. Possible errors include those in the #G_FILE_ERROR domain and
343
 * whatever the save function generates.
344
 *
345
 * Return value: %TRUE, if the save successfully completed
346
 *
347
 * Since: 0.14
348
 */
349
gboolean poppler_media_save_to_callback(PopplerMedia *poppler_media, PopplerMediaSaveFunc save_func, gpointer user_data, GError **error)
350
0
{
351
0
    Stream *stream;
352
0
    gchar buf[BUF_SIZE];
353
0
    int i;
354
0
    gboolean eof_reached = FALSE;
355
356
0
    g_return_val_if_fail(POPPLER_IS_MEDIA(poppler_media), FALSE);
357
0
    g_return_val_if_fail(poppler_media->stream.isStream(), FALSE);
358
359
0
    stream = poppler_media->stream.getStream();
360
0
    if (!stream->reset()) {
361
0
        return FALSE;
362
0
    }
363
364
0
    do {
365
0
        int data;
366
367
0
        for (i = 0; i < BUF_SIZE; i++) {
368
0
            data = stream->getChar();
369
0
            if (data == EOF) {
370
0
                eof_reached = TRUE;
371
0
                break;
372
0
            }
373
0
            buf[i] = data;
374
0
        }
375
376
0
        if (i > 0) {
377
0
            if (!(save_func)(buf, i, user_data, error)) {
378
0
                stream->close();
379
0
                return FALSE;
380
0
            }
381
0
        }
382
0
    } while (!eof_reached);
383
384
0
    stream->close();
385
386
    return TRUE;
387
0
}