Blob Blame History Raw
/* GStreamer media licenses utility functions
 * Copyright (C) 2011 Tim-Philipp Müller <tim centricular net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/**
 * SECTION:gsttaglicenses
 * @title: Licenses
 * @short_description: utility functions for Creative Commons licenses
 * @see_also: #GstTagList
 *
 * Provides information about Creative Commons media licenses, which are
 * often expressed in media files as a license URI in tags. Also useful
 * for applications creating media files, in case the user wants to license
 * the content under a Creative Commons license.
 */

/* FIXME: add API to check obsolete-ness / replace-by */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/gst.h>

#include <string.h>
#include <stdlib.h>

#include "tag.h"
#include "licenses-tables.dat"

#ifndef GST_DISABLE_GST_DEBUG

#define GST_CAT_DEFAULT ensure_debug_category()

static GstDebugCategory *
ensure_debug_category (void)
{
  static gsize cat_gonce = 0;

  if (g_once_init_enter (&cat_gonce)) {
    gsize cat_done;

    cat_done = (gsize) _gst_debug_category_new ("tag-licenses", 0,
        "GstTag licenses");

    g_once_init_leave (&cat_gonce, cat_done);
  }

  return (GstDebugCategory *) cat_gonce;
}

#else

#define ensure_debug_category() /* NOOP */

#endif /* GST_DISABLE_GST_DEBUG */

/* -------------------------------------------------------------------------
 *  Translations
 * ------------------------------------------------------------------------- */

#ifdef ENABLE_NLS
static GVariant *
gst_tag_get_license_translations_dictionary (void)
{
  static gsize var_gonce = 0;

  if (g_once_init_enter (&var_gonce)) {
    const gchar *dict_path;
    GVariant *var = NULL;
    GError *err = NULL;
    gchar *data;
    gsize len;

    /* for gst-uninstalled */
    dict_path = g_getenv ("GST_TAG_LICENSE_TRANSLATIONS_DICT");

    if (dict_path == NULL)
      dict_path = LICENSE_TRANSLATIONS_PATH;

    GST_INFO ("Loading license translations from '%s'", dict_path);
    if (g_file_get_contents (dict_path, &data, &len, &err)) {
      var = g_variant_new_from_data (G_VARIANT_TYPE ("a{sa{ss}}"), data, len,
          TRUE, (GDestroyNotify) g_free, data);
    } else {
      GST_WARNING ("Could not load translation dictionary %s", err->message);
      g_error_free (err);
      var = g_variant_new_array (G_VARIANT_TYPE ("{sa{ss}}"), NULL, 0);
    }

    g_once_init_leave (&var_gonce, (gsize) var);
  }

  return (GVariant *) var_gonce;
}
#endif

#ifdef ENABLE_NLS
static gboolean
gst_variant_lookup_string_value (GVariant * dict, const gchar * lang,
    const gchar ** translation)
{
  GVariant *trans;

  trans = g_variant_lookup_value (dict, lang, G_VARIANT_TYPE ("s"));
  if (trans == NULL)
    return FALSE;

  *translation = g_variant_get_string (trans, NULL);
  /* string will stay valid */
  g_variant_unref (trans);
  GST_TRACE ("Result: '%s' for language '%s'", *translation, lang);
  return TRUE;
}
#endif

static const gchar *
gst_license_str_translate (const gchar * s)
{
#ifdef ENABLE_NLS
  GVariant *v, *dict, *trans;

  v = gst_tag_get_license_translations_dictionary ();
  g_assert (v != NULL);

  dict = g_variant_lookup_value (v, s, G_VARIANT_TYPE ("a{ss}"));
  if (dict != NULL) {
    const gchar *const *lang;
    const gchar *env_lang;

    /* for unit tests */
    if ((env_lang = g_getenv ("GST_TAG_LICENSE_TRANSLATIONS_LANG"))) {
      if (gst_variant_lookup_string_value (dict, env_lang, &s))
        GST_TRACE ("Result: '%s' for forced language '%s'", s, env_lang);
      goto beach;
    }

    lang = g_get_language_names ();
    while (lang != NULL && *lang != NULL) {
      GST_TRACE ("Looking up '%s' for language '%s'", s, *lang);
      trans = g_variant_lookup_value (dict, *lang, G_VARIANT_TYPE ("s"));

      if (trans != NULL) {
        s = g_variant_get_string (trans, NULL);
        /* s will stay valid */
        g_variant_unref (trans);
        GST_TRACE ("Result: '%s'", s);
        break;
      }

      GST_TRACE ("No result for '%s' for language '%s'", s, *lang);
      ++lang;
    }

  beach:

    g_variant_unref (dict);
  } else {
    GST_WARNING ("No dict for string '%s'", s);
  }
#endif

  return s;
}

/* -------------------------------------------------------------------------
 *  License handling
 * ------------------------------------------------------------------------- */

#define CC_LICENSE_REF_PREFIX "http://creativecommons.org/licenses/"

/* is this license 'generic' (and a base for any of the supported
 * jurisdictions), or jurisdiction-specific only? */
#define JURISDICTION_GENERIC (G_GUINT64_CONSTANT (1) << 63)

static const gchar jurisdictions[] =
    "ar\000at\000au\000be\000bg\000br\000ca\000ch\000cl\000cn\000co\000de\000"
    "dk\000es\000fi\000fr\000hr\000hu\000il\000in\000it\000jp\000kr\000mk\000"
    "mt\000mx\000my\000nl\000pe\000pl\000pt\000scotland\000se\000si\000tw\000"
    "uk\000us\000za";

/**
 * gst_tag_get_licenses:
 *
 * Returns a list of known license references (in form of URIs). This is
 * useful for UIs to build a list of available licenses for tagging purposes
 * (e.g. to tag an audio track appropriately in a video or audio editor, or
 * an image in a camera application).
 *
 * Returns: (transfer full): NULL-terminated array of license strings. Free
 *     with g_strfreev() when no longer needed.
 */
gchar **
gst_tag_get_licenses (void)
{
  GPtrArray *arr;
  int i;

  arr = g_ptr_array_new ();
  for (i = 0; i < G_N_ELEMENTS (licenses); ++i) {
    const gchar *jurs;
    gboolean is_generic;
    guint64 jbits;
    gchar *ref;

    jbits = licenses[i].jurisdictions;
    is_generic = (jbits & JURISDICTION_GENERIC) != 0;
    if (is_generic) {
      ref = g_strconcat (CC_LICENSE_REF_PREFIX, licenses[i].ref, NULL);
      GST_LOG ("Adding %2d %s (generic)", i, ref);
      g_ptr_array_add (arr, ref);
      jbits &= ~JURISDICTION_GENERIC;
    }

    jurs = jurisdictions;
    while (jbits != 0) {
      if ((jbits & 1)) {
        ref = g_strconcat (CC_LICENSE_REF_PREFIX, licenses[i].ref, jurs, "/",
            NULL);
        GST_LOG ("Adding %2d %s (%s: %s)", i, ref,
            (is_generic) ? "derived" : "specific", jurs);
        g_ptr_array_add (arr, ref);
      }
      g_assert (jurs < (jurisdictions + sizeof (jurisdictions)));
      jurs += strlen (jurs) + 1;
      jbits >>= 1;
    }
  }
  g_ptr_array_add (arr, NULL);
  return (gchar **) g_ptr_array_free (arr, FALSE);
}

static gint
gst_tag_get_license_idx (const gchar * license_ref, const gchar ** jurisdiction)
{
  const gchar *ref, *jur_suffix;
  int i;

  GST_TRACE ("Looking up '%s'", license_ref);

  if (!g_str_has_prefix (license_ref, CC_LICENSE_REF_PREFIX)) {
    GST_WARNING ("unknown license prefix in ref '%s'", license_ref);
    return -1;
  }

  if (jurisdiction != NULL)
    *jurisdiction = NULL;

  ref = license_ref + sizeof (CC_LICENSE_REF_PREFIX) - 1;
  for (i = 0; i < G_N_ELEMENTS (licenses); ++i) {
    guint64 jbits = licenses[i].jurisdictions;
    const gchar *jurs, *lref = licenses[i].ref;
    gsize lref_len = strlen (lref);

    /* table should have "foo/bar/" with trailing slash */
    g_assert (lref[lref_len - 1] == '/');

    if ((jbits & JURISDICTION_GENERIC)) {
      GST_TRACE ("[%2d] %s checking generic match", i, licenses[i].ref);

      /* exact match? */
      if (strcmp (ref, lref) == 0)
        return i;

      /* exact match but without the trailing slash in ref? */
      if (strncmp (ref, lref, lref_len - 1) == 0 && ref[lref_len - 1] == '\0')
        return i;
    }

    if (!g_str_has_prefix (ref, lref))
      continue;

    GST_TRACE ("[%2d] %s checking jurisdictions", i, licenses[i].ref);

    jbits &= ~JURISDICTION_GENERIC;

    jur_suffix = ref + lref_len;
    if (*jur_suffix == '\0')
      continue;

    jurs = jurisdictions;
    while (jbits != 0) {
      guint jur_len = strlen (jurs);

      if ((jbits & 1)) {
        if (strncmp (jur_suffix, jurs, jur_len) == 0 &&
            (jur_suffix[jur_len] == '\0' || jur_suffix[jur_len] == '/')) {
          GST_LOG ("matched %s to %s with jurisdiction %s (idx %d)",
              license_ref, licenses[i].ref, jurs, i);
          if (jurisdiction != NULL)
            *jurisdiction = jurs;
          return i;
        }
      }
      g_assert (jurs < (jurisdictions + sizeof (jurisdictions)));
      jurs += jur_len + 1;
      jbits >>= 1;
    }
  }

  GST_WARNING ("unhandled license ref '%s'", license_ref);
  return -1;
}

/**
 * gst_tag_get_license_flags:
 * @license_ref: a license reference string in form of a URI,
 *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
 *
 * Get the flags of a license, which describe most of the features of
 * a license in their most general form.
 *
 * Returns: the flags of the license, or 0 if the license is unknown
 */
GstTagLicenseFlags
gst_tag_get_license_flags (const gchar * license_ref)
{
  int idx;

  g_return_val_if_fail (license_ref != NULL, 0);

  idx = gst_tag_get_license_idx (license_ref, NULL);
  return (idx < 0) ? 0 : licenses[idx].flags;
}

/**
 * gst_tag_get_license_nick:
 * @license_ref: a license reference string in form of a URI,
 *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
 *
 * Get the nick name of a license, which is a short (untranslated) string
 * such as e.g. "CC BY-NC-ND 2.0 UK".
 *
 * Returns: the nick name of the license, or NULL if the license is unknown
 */
const gchar *
gst_tag_get_license_nick (const gchar * license_ref)
{
  GstTagLicenseFlags flags;
  const gchar *creator_prefix, *res;
  gchar *nick, *c;

  g_return_val_if_fail (license_ref != NULL, NULL);

  flags = gst_tag_get_license_flags (license_ref);

  if ((flags & GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE)) {
    creator_prefix = "CC ";
  } else if ((flags & GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE)) {
    creator_prefix = "FSF ";
  } else if (g_str_has_suffix (license_ref, "publicdomain/")) {
    creator_prefix = "";
  } else {
    return NULL;
  }

  nick = g_strdup_printf ("%s%s", creator_prefix,
      license_ref + sizeof (CC_LICENSE_REF_PREFIX) - 1);
  g_strdelimit (nick, "/", ' ');
  g_strchomp (nick);
  for (c = nick; *c != '\0'; ++c)
    *c = g_ascii_toupper (*c);

  GST_LOG ("%s => nick %s", license_ref, nick);
  res = g_intern_string (nick); /* for convenience */
  g_free (nick);

  return res;
}

/**
 * gst_tag_get_license_title:
 * @license_ref: a license reference string in form of a URI,
 *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
 *
 * Get the title of a license, which is a short translated description
 * of the license's features (generally not very pretty though).
 *
 * Returns: the title of the license, or NULL if the license is unknown or
 *    no title is available.
 */
const gchar *
gst_tag_get_license_title (const gchar * license_ref)
{
  int idx;

  g_return_val_if_fail (license_ref != NULL, NULL);

  idx = gst_tag_get_license_idx (license_ref, NULL);

  if (idx < 0 || licenses[idx].title_idx < 0)
    return NULL;

  return gst_license_str_translate (&license_strings[licenses[idx].title_idx]);
}

/**
 * gst_tag_get_license_description:
 * @license_ref: a license reference string in form of a URI,
 *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
 *
 * Get the description of a license, which is a translated description
 * of the license's main features.
 *
 * Returns: the description of the license, or NULL if the license is unknown
 *    or a description is not available.
 */
const gchar *
gst_tag_get_license_description (const gchar * license_ref)
{
  int idx;

  g_return_val_if_fail (license_ref != NULL, NULL);

  idx = gst_tag_get_license_idx (license_ref, NULL);

  if (idx < 0 || licenses[idx].desc_idx < 0)
    return NULL;

  return gst_license_str_translate (&license_strings[licenses[idx].desc_idx]);
}

/**
 * gst_tag_get_license_jurisdiction:
 * @license_ref: a license reference string in form of a URI,
 *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
 *
 * Get the jurisdiction code of a license. This is usually a two-letter
 * ISO 3166-1 alpha-2 code, but there is also the special case of Scotland,
 * for which no code exists and which is thus represented as "scotland".
 *
 * Known jurisdictions: ar, at, au, be, bg, br, ca, ch, cl, cn, co, de,
 * dk, es, fi, fr, hr, hu, il, in, it, jp, kr, mk, mt, mx, my, nl, pe, pl,
 * pt, scotland, se, si, tw, uk, us, za.
 *
 * Returns: the jurisdiction code of the license, or NULL if the license is
 *    unknown or is not specific to a particular jurisdiction.
 */
const gchar *
gst_tag_get_license_jurisdiction (const gchar * license_ref)
{
  const gchar *jurisdiction;
  int idx;

  g_return_val_if_fail (license_ref != NULL, NULL);

  idx = gst_tag_get_license_idx (license_ref, &jurisdiction);
  return (idx < 0) ? NULL : jurisdiction;
}

/**
 * gst_tag_get_license_version:
 * @license_ref: a license reference string in form of a URI,
 *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
 *
 * Get the version of a license.
 *
 * Returns: the version of the license, or NULL if the license is not known or
 *    has no version
 */
const gchar *
gst_tag_get_license_version (const gchar * license_ref)
{
  int idx;

  g_return_val_if_fail (license_ref != NULL, NULL);

  idx = gst_tag_get_license_idx (license_ref, NULL);
  if (idx < 0)
    return NULL;

#define LICENSE_FLAG_CC_OR_FSF \
 (GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE|\
  GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE)

  /* e.g. publicdomain isn't versioned */
  if (!(licenses[idx].flags & LICENSE_FLAG_CC_OR_FSF))
    return NULL;

  /* KISS for now... */
  if (strstr (licenses[idx].ref, "/1.0/"))
    return "1.0";
  else if (strstr (licenses[idx].ref, "/2.0/"))
    return "2.0";
  else if (strstr (licenses[idx].ref, "/2.1/"))
    return "2.1";
  else if (strstr (licenses[idx].ref, "/2.5/"))
    return "2.5";
  else if (strstr (licenses[idx].ref, "/3.0/"))
    return "3.0";

  GST_ERROR ("Could not determine version for ref '%s'", license_ref);
  return NULL;
}