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