1 /* GNOME thumbnailer for Office files
2 * Copyright (C) 2005 Novell, Inc.
5 * Federico Mena-Quintero <federico@novell.com>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of version 2.1 of the GNU Lesser General Public
9 * License as published by the Free Software Foundation.
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.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
22 #include <gsf/gsf-input-memory.h>
23 #include <gsf/gsf-input-stdio.h>
24 #include <gsf/gsf-infile.h>
25 #include <gsf/gsf-infile-msole.h>
26 #include <gsf/gsf-infile-zip.h>
27 #include <gsf/gsf-meta-names.h>
28 #include <gsf/gsf-msole-utils.h>
29 #include <gsf/gsf-utils.h>
30 #include <gsf/gsf-clip-data.h>
31 #include <gsf/gsf-open-pkg-utils.h>
35 #include <gsf-config.h>
37 #ifdef HAVE_GDK_PIXBUF
38 #include <gdk-pixbuf/gdk-pixbuf.h>
42 #include <sys/resource.h>
45 G_GNUC_NORETURN static void
46 show_error_string_and_exit (const char *str)
48 g_printerr ("error: %s\n", str);
52 G_GNUC_NORETURN static void
53 show_error_and_exit (GError *error)
56 show_error_string_and_exit (error->message);
58 show_error_string_and_exit ("an error happened, and we didn't get a GError. Exiting.");
62 call_convert (const char *in_filename, const char *out_filename, int thumb_size)
70 #ifdef HAVE_GDK_PIXBUF
73 pixbuf = gdk_pixbuf_new_from_file_at_scale (in_filename,
74 thumb_size, thumb_size,
77 gboolean success = gdk_pixbuf_save (pixbuf,
80 g_object_unref (pixbuf);
86 in_quote = g_shell_quote (in_filename);
87 out_quote = g_shell_quote (out_filename);
88 cmd_line = g_strdup_printf ("convert %s +matte -thumbnail %dx%d png:%s",
90 thumb_size, thumb_size,
92 g_printerr ("calling %s\n", cmd_line);
97 if (!g_spawn_command_line_sync (cmd_line, NULL, NULL, &exit_status, &error))
98 show_error_and_exit (error);
104 write_thumbnail (const char *filename, gconstpointer data, gsize size, int thumb_size)
110 tmp_name = g_strdup_printf ("%s.XXXXXX", filename);
111 fd = g_mkstemp (tmp_name);
113 perror ("Could not create temporary file");
117 file = fdopen (fd, "w");
119 show_error_string_and_exit ("Could not open temporary file for writing");
123 if (fwrite (data, 1, size, file) != size) {
124 perror ("Could not write data to output file");
128 if (fclose (file) != 0) {
129 perror ("Could not close oputput file");
133 call_convert (tmp_name, filename, thumb_size);
138 zip_thumbnail (GsfInfile *infile, const char *out_filename, int thumb_size)
142 /* Office Document thumbnail */
143 if (NULL != (thumbnail = gsf_infile_child_by_vname (infile,
144 "Thumbnails", "thumbnail.png", NULL))) {
145 gsf_off_t len = gsf_input_remaining (thumbnail);
146 guint8 const *data = gsf_input_read (thumbnail, len, NULL);
147 write_thumbnail (out_filename, data, len, thumb_size);
148 g_object_unref (thumbnail);
149 /* Check MS Office Open thumbnail */
150 } else if (NULL != (thumbnail = gsf_open_pkg_open_rel_by_type (GSF_INPUT(infile),
151 "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail",
153 gsf_off_t len = gsf_input_remaining (thumbnail);
154 guint8 const *data = gsf_input_read (thumbnail, len, NULL);
155 write_thumbnail (out_filename, data, len, thumb_size);
156 g_object_unref (thumbnail);
158 show_error_string_and_exit ("Could not find thumbnail in zip file");
162 msole_thumbnail (GsfInfile *infile, const char *out_filename, int thumb_size)
164 GsfInput *summary_stream;
165 GsfDocMetaData *meta_data;
166 GsfDocProp *thumb_doc_prop;
167 GValue const *thumb_value;
168 GsfClipData *clip_data;
169 GsfClipFormat clip_format;
174 summary_stream = gsf_infile_child_by_name (infile, "\05SummaryInformation");
176 show_error_string_and_exit ("Could not find the SummaryInformation stream");
178 meta_data = gsf_doc_meta_data_new ();
179 error = gsf_msole_metadata_read (summary_stream, meta_data);
181 show_error_and_exit (error);
183 thumb_doc_prop = gsf_doc_meta_data_lookup (meta_data, GSF_META_NAME_THUMBNAIL);
185 show_error_string_and_exit ("The metadata does not have a thumbnail property");
187 thumb_value = gsf_doc_prop_get_val (thumb_doc_prop);
189 show_error_string_and_exit ("We got the thumbnail property, but it didn't have a value!?");
191 clip_data = GSF_CLIP_DATA (g_value_get_object (thumb_value));
193 clip_format = gsf_clip_data_get_format (clip_data);
195 if (clip_format == GSF_CLIP_FORMAT_WINDOWS_CLIPBOARD) {
196 GsfClipFormatWindows win_format;
199 win_format = gsf_clip_data_get_windows_clipboard_format (clip_data, &error);
200 if (win_format == GSF_CLIP_FORMAT_WINDOWS_ERROR)
201 show_error_and_exit (error);
205 data = gsf_clip_data_peek_real_data (clip_data, &size, &error);
208 show_error_and_exit (error);
210 write_thumbnail (out_filename, data, size, thumb_size);
212 g_object_unref (clip_data);
214 g_object_unref (meta_data);
215 g_object_unref (summary_stream);
219 read_thumbnail_and_write (const char *in_filename, const char *out_filename, int thumb_size)
225 input = gsf_input_mmap_new (in_filename, NULL);
228 input = gsf_input_stdio_new (in_filename, &error);
230 show_error_and_exit (error);
233 input = gsf_input_uncompress (input);
236 if (NULL != (infile = gsf_infile_msole_new (input, &error)))
237 msole_thumbnail (infile, out_filename, thumb_size);
238 else if (NULL != (infile = gsf_infile_zip_new (input, &error)))
239 zip_thumbnail (infile, out_filename, thumb_size);
241 show_error_and_exit (error);
243 g_object_unref (infile);
244 g_object_unref (input);
247 #define MAX_HELPER_MEMORY (256 * 1024 * 1024) /* 256 MB */
248 #define MAX_HELPER_SECONDS (5) /* 5 seconds */
251 set_resource_limits (void)
256 /* We call convert(1) from ImageMagick, which is especially scary when converting
257 * WMF thumbnails into PNGs. Convert(1) is known to leak tons of memory and CPU
258 * time on certain WMFs. So, we'll put a cap on how much resources it can use.
261 limit.rlim_cur = MAX_HELPER_MEMORY;
262 limit.rlim_max = MAX_HELPER_MEMORY;
263 setrlimit (RLIMIT_AS, &limit);
265 limit.rlim_cur = MAX_HELPER_SECONDS;
266 limit.rlim_max = MAX_HELPER_SECONDS;
267 setrlimit (RLIMIT_CPU, &limit);
271 /* Command-line options */
272 static int option_size = -1;
273 static char *option_input_filename = NULL;
274 static char *option_output_filename = NULL;
276 static GOptionEntry option_entries[] = {
277 { "input", 'i', 0, G_OPTION_ARG_FILENAME, &option_input_filename,
278 "Name of file for which to create a thumbnail",
280 { "output", 'o', 0, G_OPTION_ARG_FILENAME, &option_output_filename,
281 "Name of the file to put the thumbnail",
283 { "size", 's', 0, G_OPTION_ARG_INT, &option_size,
284 "Size of thumbnail in pixels; the thumbnail will be at most N*N pixels large",
286 { NULL, 0, 0, 0, NULL, NULL, NULL }
290 main (int argc, char **argv)
292 GOptionContext *option_ctx;
294 set_resource_limits ();
296 option_ctx = g_option_context_new ("Options");
297 g_option_context_add_main_entries (option_ctx, option_entries, NULL); /* FIXME: no translation domain */
298 if (!g_option_context_parse (option_ctx, &argc, &argv, NULL)
300 || option_input_filename == NULL
301 || option_output_filename == NULL) {
302 g_printerr ("Invalid usage; type \"%s --help\" for instructions. All the options must be used.\n", argv[0]);
307 read_thumbnail_and_write (option_input_filename, option_output_filename, option_size);