Coverage Report

Created: 2026-09-28 08:21

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