static void
on_got_metadata_event (BaconVideoWidget *bvw, callback_data *data)
{
	GValue value = { 0, };
	GdkPixbuf *pixbuf;

	PROGRESS_DEBUG("Got metadata, checking if we have a cover");
	bacon_video_widget_get_metadata (bvw, BVW_INFO_COVER, &value);
	pixbuf = g_value_dup_object (&value);
	g_value_unset (&value);

	if (pixbuf) {
		PROGRESS_DEBUG("Saving cover image");

		bacon_video_widget_close (bvw);
		totem_resources_monitor_stop ();
		gtk_widget_destroy (GTK_WIDGET (bvw));

		save_pixbuf (pixbuf, data->output, data->input, output_size, TRUE);
		g_object_unref (pixbuf);

		exit (0);
	} else if (has_video (bvw) == FALSE) {
		PROGRESS_DEBUG("No covers, and no video, exiting");
		exit (0);
	}
}
static GdkPixbuf *
capture_interesting_frame (BaconVideoWidget *bvw,
			   const char *input,
			   const char *output) 
{
	GdkPixbuf* pixbuf;
	guint current;
	GError *err = NULL;
	const double frame_locations[] = {
		1.0 / 3.0,
		2.0 / 3.0,
		0.1,
		0.9,
		0.5
	};

	/* Test at multiple points in the file to see if we can get an 
	 * interesting frame */
	for (current = 0; current < G_N_ELEMENTS(frame_locations); current++)
	{
		PROGRESS_DEBUG("About to seek to %f", frame_locations[current]);
		if (bacon_video_widget_seek (bvw, frame_locations[current], NULL) == FALSE) {
			PROGRESS_DEBUG("Couldn't seek to %f", frame_locations[current]);
			bacon_video_widget_play (bvw, NULL);
		}

		if (bacon_video_widget_can_get_frames (bvw, &err) == FALSE)
		{
			g_print ("totem-video-thumbnailer: '%s' isn't thumbnailable\n"
				 "Reason: %s\n",
				 input, err ? err->message : "programming error");
			bacon_video_widget_close (bvw);
			gtk_widget_destroy (GTK_WIDGET (bvw));
			g_error_free (err);

			exit (1);
		}

		/* Pull the frame, if it's interesting we bail early */
		PROGRESS_DEBUG("About to get frame for iter %d", current);
		pixbuf = bacon_video_widget_get_current_frame (bvw);
		if (pixbuf != NULL && is_image_interesting (pixbuf) != FALSE) {
			PROGRESS_DEBUG("Frame for iter %d is interesting", current);
			break;
		}

		/* If we get to the end of this loop, we'll end up using
		 * the last image we pulled */
		if (current + 1 < G_N_ELEMENTS(frame_locations)) {
			if (pixbuf != NULL) {
				g_object_unref (pixbuf);
				pixbuf = NULL;
			}
		}
		PROGRESS_DEBUG("Frame for iter %d was not interesting", current);
	}
	return pixbuf;
}
예제 #3
0
static void
thumb_app_check_for_cover (ThumbApp *app)
{
	PROGRESS_DEBUG ("Checking whether file has cover");
	check_cover_for_stream (app, "get-audio-tags");
	check_cover_for_stream (app, "get-video-tags");
}
예제 #4
0
static void
check_cover_for_stream (ThumbApp   *app,
			const char *signal_name)
{
	GdkPixbuf *pixbuf;
	GstTagList *tags = NULL;

	g_signal_emit_by_name (G_OBJECT (app->play), signal_name, 0, &tags);

	if (!tags)
		return;

	pixbuf = xplayer_gst_tag_list_get_cover (tags);
	if (!pixbuf) {
		gst_tag_list_unref (tags);
		return;
	}

	PROGRESS_DEBUG("Saving cover image");
	thumb_app_cleanup (app);
	save_pixbuf (pixbuf, app->output, app->input, output_size, TRUE);
	g_object_unref (pixbuf);

	exit (0);
}
예제 #5
0
static GdkPixbuf *
capture_interesting_frame (ThumbApp *app)
{
	GdkPixbuf* pixbuf;
	guint current;
	const double frame_locations[] = {
		1.0 / 3.0,
		2.0 / 3.0,
		0.1,
		0.9,
		0.5
	};

	if (app->duration == -1) {
		PROGRESS_DEBUG("Video has no duration, so capture 1st frame");
		return capture_frame_at_time (app, 0);
	}

	/* Test at multiple points in the file to see if we can get an
	 * interesting frame */
	for (current = 0; current < G_N_ELEMENTS(frame_locations); current++)
	{
		PROGRESS_DEBUG("About to seek to %f", frame_locations[current]);
		thumb_app_seek (app, frame_locations[current] * app->duration);

		/* Pull the frame, if it's interesting we bail early */
		PROGRESS_DEBUG("About to get frame for iter %d", current);
		pixbuf = xplayer_gst_playbin_get_frame (app->play);
		if (pixbuf != NULL && is_image_interesting (pixbuf) != FALSE) {
			PROGRESS_DEBUG("Frame for iter %d is interesting", current);
			break;
		}

		/* If we get to the end of this loop, we'll end up using
		 * the last image we pulled */
		if (current + 1 < G_N_ELEMENTS(frame_locations)) {
			if (pixbuf != NULL) {
				g_object_unref (pixbuf);
				pixbuf = NULL;
			}
		}
		PROGRESS_DEBUG("Frame for iter %d was not interesting", current);
	}
	return pixbuf;
}
예제 #6
0
static void
thumb_app_set_filename (ThumbApp *app)
{
	GFile *file;
	char *uri;

	if (is_special_uri (app->input)) {
		g_object_set (app->play, "uri", app->input, NULL);
		return;
	}

	file = g_file_new_for_commandline_arg (app->input);
	uri = get_special_url (file);
	if (uri == NULL)
		uri = g_file_get_uri (file);
	g_object_unref (file);

	PROGRESS_DEBUG("setting URI %s", uri);

	g_object_set (app->play, "uri", uri, NULL);
	g_free (uri);
}
예제 #7
0
int main (int argc, char *argv[])
{
	GOptionGroup *options;
	GOptionContext *context;
	GError *err = NULL;
	GdkPixbuf *pixbuf;
	const char *input, *output;
	ThumbApp app;

	context = g_option_context_new ("Thumbnail movies");
	options = gst_init_get_option_group ();
	g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
	g_option_context_add_group (context, options);
	g_option_context_add_group (context, gtk_get_option_group (TRUE));

	if (g_option_context_parse (context, &argc, &argv, &err) == FALSE) {
		g_print ("couldn't parse command-line options: %s\n", err->message);
		g_error_free (err);
		return 1;
	}

#ifdef G_OS_UNIX
	if (time_limit != FALSE) {
		errno = 0;
		if (nice (20) != 20 && errno != 0)
			g_warning ("Couldn't change nice value of process.");
	}
#endif

	if (print_progress) {
		fcntl (fileno (stdout), F_SETFL, O_NONBLOCK);
		setbuf (stdout, NULL);
	}

	if (g_fatal_warnings) {
		GLogLevelFlags fatal_mask;

		fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK);
		fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL;
		g_log_set_always_fatal (fatal_mask);
	}

	if (raw_output == FALSE && output_size == -1)
		output_size = DEFAULT_OUTPUT_SIZE;

	if (filenames == NULL || g_strv_length (filenames) != 2 ||
	    (second_index != -1 && gallery != -1) ||
	    (print_progress == TRUE && verbose == TRUE)) {
		char *help;
		help = g_option_context_get_help (context, FALSE, NULL);
		g_print ("%s", help);
		g_free (help);
		return 1;
	}
	input = filenames[0];
	output = filenames[1];

	PROGRESS_DEBUG("Initialised libraries, about to create video widget");
	PRINT_PROGRESS (2.0);

	app.input = input;
	app.output = output;

	thumb_app_setup_play (&app);
	thumb_app_set_filename (&app);

	PROGRESS_DEBUG("Video widget created");
	PRINT_PROGRESS (6.0);

	if (time_limit != FALSE)
		xplayer_resources_monitor_start (input, 0);

	PROGRESS_DEBUG("About to open video file");

	if (thumb_app_start (&app) == FALSE) {
		g_print ("xplayer-video-thumbnailer couldn't open file '%s'\n", input);
		exit (1);
	}
	thumb_app_set_error_handler (&app);

	/* We don't need covers when we're in gallery mode */
	if (gallery == -1)
		thumb_app_check_for_cover (&app);
	if (thumb_app_get_has_video (&app) == FALSE) {
		PROGRESS_DEBUG ("xplayer-video-thumbnailer couldn't find a video track in '%s'\n", input);
		exit (1);
	}
	thumb_app_set_duration (&app);

	PROGRESS_DEBUG("Opened video file: '%s'", input);
	PRINT_PROGRESS (10.0);

	if (gallery == -1) {
		/* If the user has told us to use a frame at a specific second
		 * into the video, just use that frame no matter how boring it
		 * is */
		if (second_index != -1) {
			assert_duration (&app);
			pixbuf = capture_frame_at_time (&app, second_index * 1000);
		} else {
			pixbuf = capture_interesting_frame (&app);
		}
		PRINT_PROGRESS (90.0);
	} else {
		assert_duration (&app);
		/* We're producing a gallery of screenshots from throughout the file */
		pixbuf = create_gallery (&app);
	}

	/* Cleanup */
	xplayer_resources_monitor_stop ();
	thumb_app_cleanup (&app);
	PRINT_PROGRESS (92.0);

	if (pixbuf == NULL) {
		g_print ("xplayer-video-thumbnailer couldn't get a picture from '%s'\n", input);
		exit (1);
	}

	PROGRESS_DEBUG("Saving captured screenshot");
	save_pixbuf (pixbuf, output, input, output_size, FALSE);
	g_object_unref (pixbuf);
	PRINT_PROGRESS (100.0);

	return 0;
}
예제 #8
0
static GdkPixbuf *
create_gallery (ThumbApp *app)
{
	GdkPixbuf *screenshot, *pixbuf = NULL;
	cairo_t *cr;
	cairo_surface_t *surface;
	PangoLayout *layout;
	PangoFontDescription *font_desc;
	gint64 stream_length, screenshot_interval, pos;
	guint columns = 3, rows, current_column, current_row, x, y;
	gint screenshot_width = 0, screenshot_height = 0, x_padding = 0, y_padding = 0;
	gfloat scale = 1.0;
	gchar *header_text, *duration_text, *filename;

	/* Calculate how many screenshots we're going to take */
	stream_length = app->duration;

	/* As a default, we have one screenshot per minute of stream,
	 * but adjusted so we don't have any gaps in the resulting gallery. */
	if (gallery == 0) {
		gallery = stream_length / 60000;

		while (gallery % 3 != 0 &&
		       gallery % 4 != 0 &&
		       gallery % 5 != 0) {
			gallery++;
		}
	}

	if (gallery < GALLERY_MIN)
		gallery = GALLERY_MIN;
	if (gallery > GALLERY_MAX)
		gallery = GALLERY_MAX;
	screenshot_interval = stream_length / gallery;

	/* Put a lower bound on the screenshot interval so we can't enter an infinite loop below */
	if (screenshot_interval == 0)
		screenshot_interval = 1;

	PROGRESS_DEBUG ("Producing gallery of %u screenshots, taken at %" G_GINT64_FORMAT " millisecond intervals throughout a %" G_GINT64_FORMAT " millisecond-long stream.",
			gallery, screenshot_interval, stream_length);

	/* Calculate how to arrange the screenshots so we don't get ones orphaned on the last row.
	 * At this point, only deal with arrangements of 3, 4 or 5 columns. */
	y = G_MAXUINT;
	for (x = 3; x <= 5; x++) {
		if (gallery % x == 0 || x - gallery % x < y) {
			y = x - gallery % x;
			columns = x;

			/* Have we found an optimal solution already? */
			if (y == x)
				break;
		}
	}

	rows = ceil ((gfloat) gallery / (gfloat) columns);

	PROGRESS_DEBUG ("Outputting as %u rows and %u columns.", rows, columns);

	/* Take the screenshots and composite them into a pixbuf */
	current_column = current_row = x = y = 0;
	for (pos = screenshot_interval; pos <= stream_length; pos += screenshot_interval) {
		if (pos == stream_length)
			screenshot = capture_frame_at_time (app, pos - 1);
		else
			screenshot = capture_frame_at_time (app, pos);

		if (pixbuf == NULL) {
			screenshot_width = gdk_pixbuf_get_width (screenshot);
			screenshot_height = gdk_pixbuf_get_height (screenshot);

			/* Calculate a scaling factor so that screenshot_width -> output_size */
			scale = (float) output_size / (float) screenshot_width;

			x_padding = x = MAX (output_size * 0.05, 1);
			y_padding = y = MAX (scale * screenshot_height * 0.05, 1);

			PROGRESS_DEBUG ("Scaling each screenshot by %f.", scale);

			/* Create our massive pixbuf */
			pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
						 columns * output_size + (columns + 1) * x_padding,
						 (guint) (rows * scale * screenshot_height + (rows + 1) * y_padding));
			gdk_pixbuf_fill (pixbuf, 0x000000ff);

			PROGRESS_DEBUG ("Created output pixbuf (%ux%u).", gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf));
		}

		/* Composite the screenshot into our gallery */
		gdk_pixbuf_composite (screenshot, pixbuf,
				      x, y, output_size, scale * screenshot_height,
				      (gdouble) x, (gdouble) y, scale, scale,
				      GDK_INTERP_BILINEAR, 255);
		g_object_unref (screenshot);

		PROGRESS_DEBUG ("Composited screenshot from %" G_GINT64_FORMAT " milliseconds (address %u) at (%u,%u).",
				pos, GPOINTER_TO_UINT (screenshot), x, y);

		/* We print progress in the range 10% (MIN_PROGRESS) to 50% (MAX_PROGRESS - MIN_PROGRESS) / 2.0 */
		PRINT_PROGRESS (MIN_PROGRESS + (current_row * columns + current_column) * (((MAX_PROGRESS - MIN_PROGRESS) / gallery) / 2.0));

		current_column = (current_column + 1) % columns;
		x += output_size + x_padding;
		if (current_column == 0) {
			x = x_padding;
			y += scale * screenshot_height + y_padding;
			current_row++;
		}
	}

	PROGRESS_DEBUG ("Converting pixbuf to a Cairo surface.");

	/* Load the pixbuf into a Cairo surface and overlay the text. The height is the height of
	 * the gallery plus the necessary height for 3 lines of header (at ~18px each), plus some
	 * extra padding. */
	surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, gdk_pixbuf_get_width (pixbuf),
					      gdk_pixbuf_get_height (pixbuf) + GALLERY_HEADER_HEIGHT + y_padding);
	cr = cairo_create (surface);
	cairo_surface_destroy (surface);

	/* First, copy across the gallery pixbuf */
	gdk_cairo_set_source_pixbuf (cr, pixbuf, 0.0, GALLERY_HEADER_HEIGHT + y_padding);
	cairo_rectangle (cr, 0.0, GALLERY_HEADER_HEIGHT + y_padding, gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf));
	cairo_fill (cr);
	g_object_unref (pixbuf);

	/* Build the header information */
	duration_text = xplayer_time_to_string (stream_length);
	filename = NULL;
	if (strstr (app->input, "://")) {
		char *local;
		local = g_filename_from_uri (app->input, NULL, NULL);
		filename = g_path_get_basename (local);
		g_free (local);
	}
	if (filename == NULL)
		filename = g_path_get_basename (app->input);

	/* Translators: The first string is "Filename" (as translated); the second is an actual filename.
			The third string is "Resolution" (as translated); the fourth and fifth are screenshot height and width, respectively.
			The sixth string is "Duration" (as translated); the seventh is the movie duration in words. */
	header_text = g_markup_printf_escaped (_("<b>%s</b>: %s\n<b>%s</b>: %d\303\227%d\n<b>%s</b>: %s"),
					       _("Filename"),
					       filename,
					       _("Resolution"),
					       screenshot_width,
					       screenshot_height,
					       _("Duration"),
					       duration_text);
	g_free (duration_text);
	g_free (filename);

	PROGRESS_DEBUG ("Writing header text with Pango.");

	/* Write out some header information */
	layout = pango_cairo_create_layout (cr);
	font_desc = pango_font_description_from_string ("Sans 18px");
	pango_layout_set_font_description (layout, font_desc);
	pango_font_description_free (font_desc);

	pango_layout_set_markup (layout, header_text, -1);
	g_free (header_text);

	cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* white */
	cairo_move_to (cr, (gdouble) x_padding, (gdouble) y_padding);
	pango_cairo_show_layout (cr, layout);

	/* Go through each screenshot and write its timestamp */
	current_column = current_row = 0;
	x = x_padding + output_size;
	y = y_padding * 2 + GALLERY_HEADER_HEIGHT + scale * screenshot_height;

	font_desc = pango_font_description_from_string ("Sans 10px");
	pango_layout_set_font_description (layout, font_desc);
	pango_font_description_free (font_desc);

	PROGRESS_DEBUG ("Writing screenshot timestamps with Pango.");

	for (pos = screenshot_interval; pos <= stream_length; pos += screenshot_interval) {
		gchar *timestamp_text;
		gint layout_width, layout_height;

		timestamp_text = xplayer_time_to_string (pos);

		pango_layout_set_text (layout, timestamp_text, -1);
		pango_layout_get_pixel_size (layout, &layout_width, &layout_height);

		/* Display the timestamp in the bottom-right corner of the current screenshot */
		cairo_move_to (cr, x - layout_width - 0.02 * output_size, y - layout_height - 0.02 * scale * screenshot_height);

		/* We have to stroke the text so it's visible against screenshots of the same
		 * foreground color. */
		pango_cairo_layout_path (cr, layout);
		cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
		cairo_stroke_preserve (cr);
		cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* white */
		cairo_fill (cr);

		PROGRESS_DEBUG ("Writing timestamp \"%s\" at (%f,%f).", timestamp_text,
				x - layout_width - 0.02 * output_size,
				y - layout_height - 0.02 * scale * screenshot_height);

		/* We print progress in the range 50% (MAX_PROGRESS - MIN_PROGRESS) / 2.0) to 90% (MAX_PROGRESS) */
		PRINT_PROGRESS (MIN_PROGRESS + (MAX_PROGRESS - MIN_PROGRESS) / 2.0 + (current_row * columns + current_column) * (((MAX_PROGRESS - MIN_PROGRESS) / gallery) / 2.0));

		g_free (timestamp_text);

		current_column = (current_column + 1) % columns;
		x += output_size + x_padding;
		if (current_column == 0) {
			x = x_padding + output_size;
			y += scale * screenshot_height + y_padding;
			current_row++;
		}
	}

	g_object_unref (layout);

	PROGRESS_DEBUG ("Converting Cairo surface back to pixbuf.");

	/* Create a new pixbuf from the Cairo context */
	pixbuf = cairo_surface_to_pixbuf (cairo_get_target (cr));
	cairo_destroy (cr);

	return pixbuf;
}
int main (int argc, char *argv[])
{
	GOptionGroup *options;
	GOptionContext *context;
	GError *err = NULL;
	BaconVideoWidget *bvw;
	GdkPixbuf *pixbuf;
	const char *input, *output;
	callback_data data;

	g_thread_init (NULL);

	context = g_option_context_new ("Thumbnail movies");
	options = bacon_video_widget_get_option_group ();
	g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
	g_option_context_add_group (context, options);
#ifndef THUMB_DEBUG
	g_type_init ();
#else
	g_option_context_add_group (context, gtk_get_option_group (TRUE));
#endif

	if (g_option_context_parse (context, &argc, &argv, &err) == FALSE) {
		g_print ("couldn't parse command-line options: %s\n", err->message);
		g_error_free (err);
		return 1;
	}

#ifdef G_OS_UNIX
	if (time_limit != FALSE) {
		errno = 0;
		if (nice (20) != 20 && errno != 0)
			g_warning ("Couldn't change nice value of process.");
	}
#endif

	if (print_progress) {
		fcntl (fileno (stdout), F_SETFL, O_NONBLOCK);
		setbuf (stdout, NULL);
	}

	if (g_fatal_warnings) {
		GLogLevelFlags fatal_mask;

		fatal_mask = g_log_set_always_fatal (G_LOG_FATAL_MASK);
		fatal_mask |= G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL;
		g_log_set_always_fatal (fatal_mask);
	}

	if (raw_output == FALSE && output_size == -1)
		output_size = DEFAULT_OUTPUT_SIZE;

	if (filenames == NULL || g_strv_length (filenames) != 2 ||
	    (second_index != -1 && gallery != -1) ||
	    (print_progress == TRUE && verbose == TRUE)) {
		char *help;
		help = g_option_context_get_help (context, FALSE, NULL);
		g_print ("%s", help);
		g_free (help);
		return 1;
	}
	input = filenames[0];
	output = filenames[1];

	PROGRESS_DEBUG("Initialised libraries, about to create video widget");
	PRINT_PROGRESS (2.0);

	bvw = BACON_VIDEO_WIDGET (bacon_video_widget_new (-1, -1, BVW_USE_TYPE_CAPTURE, &err));
	if (err != NULL) {
		g_print ("totem-video-thumbnailer couldn't create the video "
				"widget.\nReason: %s.\n", err->message);
		g_error_free (err);
		exit (1);
	}
	data.input = input;
	data.output = output;
	g_signal_connect (G_OBJECT (bvw), "got-metadata",
			  G_CALLBACK (on_got_metadata_event),
			  &data);

	PROGRESS_DEBUG("Video widget created");
	PRINT_PROGRESS (6.0);

	if (time_limit != FALSE)
		totem_resources_monitor_start (input, 0);

	PROGRESS_DEBUG("About to open video file");

	if (bacon_video_widget_open (bvw, input, NULL, &err) == FALSE) {
		g_print ("totem-video-thumbnailer couldn't open file '%s'\n"
				"Reason: %s.\n",
				input, err->message);
		g_error_free (err);
		exit (1);
	}

	PROGRESS_DEBUG("Opened video file: '%s'", input);
	PRINT_PROGRESS (10.0);

	if (gallery == -1) {
		/* If the user has told us to use a frame at a specific second 
		 * into the video, just use that frame no matter how boring it
		 * is */
		if (second_index != -1)
			pixbuf = capture_frame_at_time (bvw, input, output, second_index);
		else
			pixbuf = capture_interesting_frame (bvw, input, output);
		PRINT_PROGRESS (90.0);
	} else {
		/* We're producing a gallery of screenshots from throughout the file */
		pixbuf = create_gallery (bvw, input, output);
	}

	/* Cleanup */
	bacon_video_widget_close (bvw);
	totem_resources_monitor_stop ();
	gtk_widget_destroy (GTK_WIDGET (bvw));
	PRINT_PROGRESS (92.0);

	if (pixbuf == NULL) {
		g_print ("totem-video-thumbnailer couldn't get a picture from "
					"'%s'\n", input);
		exit (1);
	}

	PROGRESS_DEBUG("Saving captured screenshot");
	save_pixbuf (pixbuf, output, input, output_size, FALSE);
	g_object_unref (pixbuf);
	PRINT_PROGRESS (100.0);

	return 0;
}
예제 #10
0
bool ToEnglishTranslationVisitor::_translate(const ElementPtr& e,
                                             const QString toTranslateTagKey)
{
  bool translationMade = false;

  _toTranslateTagKey = toTranslateTagKey;
  _toTranslateVal = e->getTags().get(toTranslateTagKey).trimmed();
  LOG_VART(_toTranslateVal);
  _element = e;

  _translatedText = _translatorClient->translate(_toTranslateVal).trimmed();
  LOG_VART(_translatedText);
  const int strComparison = _translatedText.compare(_toTranslateVal, Qt::CaseInsensitive);
  LOG_VART(strComparison);
  //If the translator merely returned the same string we passed in as the translated text, then
  //no point in using it.
  if (!_translatedText.trimmed().isEmpty() && strComparison != 0)
  {
    LOG_TRACE("Translated: " << _toTranslateVal << " to: " << _translatedText);

    _element->getTags()
      .appendValue("hoot:translated:" + _toTranslateTagKey + ":en", _translatedText);

    QString sourceLang;
    LOG_VART(_translatorClient->getSourceLanguages().size());
    if (_translatorClient->getDetectedLanguage().trimmed().isEmpty() &&
        _translatorClient->getSourceLanguages().at(0) != "detect")
    {
      assert(_translatorClient->getSourceLanguages().size() == 1);
      sourceLang = _translatorClient->getSourceLanguages().at(0);
    }
    else
    {
      sourceLang = _translatorClient->getDetectedLanguage();
    }
    _element->getTags()
      .appendValue("hoot:translated:" + _toTranslateTagKey + ":en:source:language", sourceLang);

    _numTagTranslationsMade++;
    if (_numTagTranslationsMade % _taskStatusUpdateInterval == 0)
    {
      PROGRESS_DEBUG("Translated " << _numTagTranslationsMade << " tags.");
    }
    _currentElementHasSuccessfulTagTranslation = true;

    translationMade = true;
  }
  else
  {
    if (!_translatedText.trimmed().isEmpty())
    {
      LOG_TRACE(
        "Translator returned translation with same value as text passed in.  Discarding " <<
        "translation; text: " << _translatedText);
    }
    else
    {
      LOG_TRACE("Unable to translate text: " << _toTranslateVal);
    }
  }

  _numProcessedTags++;
  if (_numProcessedTags % _taskStatusUpdateInterval == 0)
  {
    PROGRESS_DEBUG("Processed " << _numProcessedTags << " tags.");
  }

  return translationMade;
}