• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer media licenses utility functions
2  * Copyright (C) 2011 Tim-Philipp Müller <tim centricular net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 /**
21  * SECTION:gsttaglicenses
22  * @title: Licenses
23  * @short_description: utility functions for Creative Commons licenses
24  * @see_also: #GstTagList
25  *
26  * Provides information about Creative Commons media licenses, which are
27  * often expressed in media files as a license URI in tags. Also useful
28  * for applications creating media files, in case the user wants to license
29  * the content under a Creative Commons license.
30  */
31 
32 /* FIXME: add API to check obsolete-ness / replace-by */
33 
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37 
38 #include <gst/gst.h>
39 
40 #include <string.h>
41 #include <stdlib.h>
42 
43 #include "tag.h"
44 #include "licenses-tables.dat"
45 
46 #ifndef GST_DISABLE_GST_DEBUG
47 
48 #define GST_CAT_DEFAULT ensure_debug_category()
49 
50 static GstDebugCategory *
ensure_debug_category(void)51 ensure_debug_category (void)
52 {
53   static gsize cat_gonce = 0;
54 
55   if (g_once_init_enter (&cat_gonce)) {
56     gsize cat_done;
57 
58     cat_done = (gsize) _gst_debug_category_new ("tag-licenses", 0,
59         "GstTag licenses");
60 
61     g_once_init_leave (&cat_gonce, cat_done);
62   }
63 
64   return (GstDebugCategory *) cat_gonce;
65 }
66 
67 #else
68 
69 #define ensure_debug_category() /* NOOP */
70 
71 #endif /* GST_DISABLE_GST_DEBUG */
72 
73 /* -------------------------------------------------------------------------
74  *  Translations
75  * ------------------------------------------------------------------------- */
76 
77 #ifdef ENABLE_NLS
78 static GVariant *
gst_tag_get_license_translations_dictionary(void)79 gst_tag_get_license_translations_dictionary (void)
80 {
81   static gsize var_gonce = 0;
82 
83   if (g_once_init_enter (&var_gonce)) {
84     const gchar *dict_path;
85     GVariant *var = NULL;
86     GError *err = NULL;
87     gchar *data;
88     gsize len;
89 
90     /* for gst-env */
91     dict_path = g_getenv ("GST_TAG_LICENSE_TRANSLATIONS_DICT");
92 
93     if (dict_path == NULL)
94       dict_path = LICENSE_TRANSLATIONS_PATH;
95 
96     GST_INFO ("Loading license translations from '%s'", dict_path);
97     if (g_file_get_contents (dict_path, &data, &len, &err)) {
98       var = g_variant_new_from_data (G_VARIANT_TYPE ("a{sa{ss}}"), data, len,
99           TRUE, (GDestroyNotify) g_free, data);
100     } else {
101       GST_WARNING ("Could not load translation dictionary %s", err->message);
102       g_error_free (err);
103       var = g_variant_new_array (G_VARIANT_TYPE ("{sa{ss}}"), NULL, 0);
104     }
105 
106     g_once_init_leave (&var_gonce, (gsize) var);
107   }
108 
109   return (GVariant *) var_gonce;
110 }
111 #endif
112 
113 #ifdef ENABLE_NLS
114 static gboolean
gst_variant_lookup_string_value(GVariant * dict,const gchar * lang,const gchar ** translation)115 gst_variant_lookup_string_value (GVariant * dict, const gchar * lang,
116     const gchar ** translation)
117 {
118   GVariant *trans;
119 
120   trans = g_variant_lookup_value (dict, lang, G_VARIANT_TYPE ("s"));
121   if (trans == NULL)
122     return FALSE;
123 
124   *translation = g_variant_get_string (trans, NULL);
125   /* string will stay valid */
126   g_variant_unref (trans);
127   GST_TRACE ("Result: '%s' for language '%s'", *translation, lang);
128   return TRUE;
129 }
130 #endif
131 
132 static const gchar *
gst_license_str_translate(const gchar * s)133 gst_license_str_translate (const gchar * s)
134 {
135 #ifdef ENABLE_NLS
136   GVariant *v, *dict, *trans;
137 
138   v = gst_tag_get_license_translations_dictionary ();
139   g_assert (v != NULL);
140 
141   dict = g_variant_lookup_value (v, s, G_VARIANT_TYPE ("a{ss}"));
142   if (dict != NULL) {
143     const gchar *const *lang;
144     const gchar *env_lang;
145 
146     /* for unit tests */
147     if ((env_lang = g_getenv ("GST_TAG_LICENSE_TRANSLATIONS_LANG"))) {
148       if (gst_variant_lookup_string_value (dict, env_lang, &s))
149         GST_TRACE ("Result: '%s' for forced language '%s'", s, env_lang);
150       goto beach;
151     }
152 
153     lang = g_get_language_names ();
154     while (lang != NULL && *lang != NULL) {
155       GST_TRACE ("Looking up '%s' for language '%s'", s, *lang);
156       trans = g_variant_lookup_value (dict, *lang, G_VARIANT_TYPE ("s"));
157 
158       if (trans != NULL) {
159         s = g_variant_get_string (trans, NULL);
160         /* s will stay valid */
161         g_variant_unref (trans);
162         GST_TRACE ("Result: '%s'", s);
163         break;
164       }
165 
166       GST_TRACE ("No result for '%s' for language '%s'", s, *lang);
167       ++lang;
168     }
169 
170   beach:
171 
172     g_variant_unref (dict);
173   } else {
174     GST_WARNING ("No dict for string '%s'", s);
175   }
176 #endif
177 
178   return s;
179 }
180 
181 /* -------------------------------------------------------------------------
182  *  License handling
183  * ------------------------------------------------------------------------- */
184 
185 #define CC_LICENSE_REF_PREFIX "http://creativecommons.org/licenses/"
186 
187 /* is this license 'generic' (and a base for any of the supported
188  * jurisdictions), or jurisdiction-specific only? */
189 #define JURISDICTION_GENERIC (G_GUINT64_CONSTANT (1) << 63)
190 
191 static const gchar jurisdictions[] =
192     "ar\000at\000au\000be\000bg\000br\000ca\000ch\000cl\000cn\000co\000de\000"
193     "dk\000es\000fi\000fr\000hr\000hu\000il\000in\000it\000jp\000kr\000mk\000"
194     "mt\000mx\000my\000nl\000pe\000pl\000pt\000scotland\000se\000si\000tw\000"
195     "uk\000us\000za";
196 
197 /**
198  * gst_tag_get_licenses:
199  *
200  * Returns a list of known license references (in form of URIs). This is
201  * useful for UIs to build a list of available licenses for tagging purposes
202  * (e.g. to tag an audio track appropriately in a video or audio editor, or
203  * an image in a camera application).
204  *
205  * Returns: (transfer full): NULL-terminated array of license strings. Free
206  *     with g_strfreev() when no longer needed.
207  */
208 gchar **
gst_tag_get_licenses(void)209 gst_tag_get_licenses (void)
210 {
211   GPtrArray *arr;
212   int i;
213 
214   arr = g_ptr_array_new ();
215   for (i = 0; i < G_N_ELEMENTS (licenses); ++i) {
216     const gchar *jurs;
217     gboolean is_generic;
218     guint64 jbits;
219     gchar *ref;
220 
221     jbits = licenses[i].jurisdictions;
222     is_generic = (jbits & JURISDICTION_GENERIC) != 0;
223     if (is_generic) {
224       ref = g_strconcat (CC_LICENSE_REF_PREFIX, licenses[i].ref, NULL);
225       GST_LOG ("Adding %2d %s (generic)", i, ref);
226       g_ptr_array_add (arr, ref);
227       jbits &= ~JURISDICTION_GENERIC;
228     }
229 
230     jurs = jurisdictions;
231     while (jbits != 0) {
232       if ((jbits & 1)) {
233         ref = g_strconcat (CC_LICENSE_REF_PREFIX, licenses[i].ref, jurs, "/",
234             NULL);
235         GST_LOG ("Adding %2d %s (%s: %s)", i, ref,
236             (is_generic) ? "derived" : "specific", jurs);
237         g_ptr_array_add (arr, ref);
238       }
239       g_assert (jurs < (jurisdictions + sizeof (jurisdictions)));
240       jurs += strlen (jurs) + 1;
241       jbits >>= 1;
242     }
243   }
244   g_ptr_array_add (arr, NULL);
245   return (gchar **) g_ptr_array_free (arr, FALSE);
246 }
247 
248 static gint
gst_tag_get_license_idx(const gchar * license_ref,const gchar ** jurisdiction)249 gst_tag_get_license_idx (const gchar * license_ref, const gchar ** jurisdiction)
250 {
251   const gchar *ref, *jur_suffix;
252   int i;
253 
254   GST_TRACE ("Looking up '%s'", license_ref);
255 
256   if (!g_str_has_prefix (license_ref, CC_LICENSE_REF_PREFIX)) {
257     GST_WARNING ("unknown license prefix in ref '%s'", license_ref);
258     return -1;
259   }
260 
261   if (jurisdiction != NULL)
262     *jurisdiction = NULL;
263 
264   ref = license_ref + sizeof (CC_LICENSE_REF_PREFIX) - 1;
265   for (i = 0; i < G_N_ELEMENTS (licenses); ++i) {
266     guint64 jbits = licenses[i].jurisdictions;
267     const gchar *jurs, *lref = licenses[i].ref;
268     gsize lref_len = strlen (lref);
269 
270     /* table should have "foo/bar/" with trailing slash */
271     g_assert (lref[lref_len - 1] == '/');
272 
273     if ((jbits & JURISDICTION_GENERIC)) {
274       GST_TRACE ("[%2d] %s checking generic match", i, licenses[i].ref);
275 
276       /* exact match? */
277       if (strcmp (ref, lref) == 0)
278         return i;
279 
280       /* exact match but without the trailing slash in ref? */
281       if (strncmp (ref, lref, lref_len - 1) == 0 && ref[lref_len - 1] == '\0')
282         return i;
283     }
284 
285     if (!g_str_has_prefix (ref, lref))
286       continue;
287 
288     GST_TRACE ("[%2d] %s checking jurisdictions", i, licenses[i].ref);
289 
290     jbits &= ~JURISDICTION_GENERIC;
291 
292     jur_suffix = ref + lref_len;
293     if (*jur_suffix == '\0')
294       continue;
295 
296     jurs = jurisdictions;
297     while (jbits != 0) {
298       guint jur_len = strlen (jurs);
299 
300       if ((jbits & 1)) {
301         if (strncmp (jur_suffix, jurs, jur_len) == 0 &&
302             (jur_suffix[jur_len] == '\0' || jur_suffix[jur_len] == '/')) {
303           GST_LOG ("matched %s to %s with jurisdiction %s (idx %d)",
304               license_ref, licenses[i].ref, jurs, i);
305           if (jurisdiction != NULL)
306             *jurisdiction = jurs;
307           return i;
308         }
309       }
310       g_assert (jurs < (jurisdictions + sizeof (jurisdictions)));
311       jurs += jur_len + 1;
312       jbits >>= 1;
313     }
314   }
315 
316   GST_WARNING ("unhandled license ref '%s'", license_ref);
317   return -1;
318 }
319 
320 /**
321  * gst_tag_get_license_flags:
322  * @license_ref: a license reference string in form of a URI,
323  *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
324  *
325  * Get the flags of a license, which describe most of the features of
326  * a license in their most general form.
327  *
328  * Returns: the flags of the license, or 0 if the license is unknown
329  */
330 GstTagLicenseFlags
gst_tag_get_license_flags(const gchar * license_ref)331 gst_tag_get_license_flags (const gchar * license_ref)
332 {
333   int idx;
334 
335   g_return_val_if_fail (license_ref != NULL, 0);
336 
337   idx = gst_tag_get_license_idx (license_ref, NULL);
338   return (idx < 0) ? 0 : licenses[idx].flags;
339 }
340 
341 /**
342  * gst_tag_get_license_nick:
343  * @license_ref: a license reference string in form of a URI,
344  *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
345  *
346  * Get the nick name of a license, which is a short (untranslated) string
347  * such as e.g. "CC BY-NC-ND 2.0 UK".
348  *
349  * Returns: the nick name of the license, or NULL if the license is unknown
350  */
351 const gchar *
gst_tag_get_license_nick(const gchar * license_ref)352 gst_tag_get_license_nick (const gchar * license_ref)
353 {
354   GstTagLicenseFlags flags;
355   const gchar *creator_prefix, *res;
356   gchar *nick, *c;
357 
358   g_return_val_if_fail (license_ref != NULL, NULL);
359 
360   flags = gst_tag_get_license_flags (license_ref);
361 
362   if ((flags & GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE)) {
363     creator_prefix = "CC ";
364   } else if ((flags & GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE)) {
365     creator_prefix = "FSF ";
366   } else if (g_str_has_suffix (license_ref, "publicdomain/")) {
367     creator_prefix = "";
368   } else {
369     return NULL;
370   }
371 
372   nick = g_strdup_printf ("%s%s", creator_prefix,
373       license_ref + sizeof (CC_LICENSE_REF_PREFIX) - 1);
374   g_strdelimit (nick, "/", ' ');
375   g_strchomp (nick);
376   for (c = nick; *c != '\0'; ++c)
377     *c = g_ascii_toupper (*c);
378 
379   GST_LOG ("%s => nick %s", license_ref, nick);
380   res = g_intern_string (nick); /* for convenience */
381   g_free (nick);
382 
383   return res;
384 }
385 
386 /**
387  * gst_tag_get_license_title:
388  * @license_ref: a license reference string in form of a URI,
389  *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
390  *
391  * Get the title of a license, which is a short translated description
392  * of the license's features (generally not very pretty though).
393  *
394  * Returns: the title of the license, or NULL if the license is unknown or
395  *    no title is available.
396  */
397 const gchar *
gst_tag_get_license_title(const gchar * license_ref)398 gst_tag_get_license_title (const gchar * license_ref)
399 {
400   int idx;
401 
402   g_return_val_if_fail (license_ref != NULL, NULL);
403 
404   idx = gst_tag_get_license_idx (license_ref, NULL);
405 
406   if (idx < 0 || licenses[idx].title_idx < 0)
407     return NULL;
408 
409   return gst_license_str_translate (&license_strings[licenses[idx].title_idx]);
410 }
411 
412 /**
413  * gst_tag_get_license_description:
414  * @license_ref: a license reference string in form of a URI,
415  *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
416  *
417  * Get the description of a license, which is a translated description
418  * of the license's main features.
419  *
420  * Returns: the description of the license, or NULL if the license is unknown
421  *    or a description is not available.
422  */
423 const gchar *
gst_tag_get_license_description(const gchar * license_ref)424 gst_tag_get_license_description (const gchar * license_ref)
425 {
426   int idx;
427 
428   g_return_val_if_fail (license_ref != NULL, NULL);
429 
430   idx = gst_tag_get_license_idx (license_ref, NULL);
431 
432   if (idx < 0 || licenses[idx].desc_idx < 0)
433     return NULL;
434 
435   return gst_license_str_translate (&license_strings[licenses[idx].desc_idx]);
436 }
437 
438 /**
439  * gst_tag_get_license_jurisdiction:
440  * @license_ref: a license reference string in form of a URI,
441  *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
442  *
443  * Get the jurisdiction code of a license. This is usually a two-letter
444  * ISO 3166-1 alpha-2 code, but there is also the special case of Scotland,
445  * for which no code exists and which is thus represented as "scotland".
446  *
447  * Known jurisdictions: ar, at, au, be, bg, br, ca, ch, cl, cn, co, de,
448  * dk, es, fi, fr, hr, hu, il, in, it, jp, kr, mk, mt, mx, my, nl, pe, pl,
449  * pt, scotland, se, si, tw, uk, us, za.
450  *
451  * Returns: the jurisdiction code of the license, or NULL if the license is
452  *    unknown or is not specific to a particular jurisdiction.
453  */
454 const gchar *
gst_tag_get_license_jurisdiction(const gchar * license_ref)455 gst_tag_get_license_jurisdiction (const gchar * license_ref)
456 {
457   const gchar *jurisdiction;
458   int idx;
459 
460   g_return_val_if_fail (license_ref != NULL, NULL);
461 
462   idx = gst_tag_get_license_idx (license_ref, &jurisdiction);
463   return (idx < 0) ? NULL : jurisdiction;
464 }
465 
466 /**
467  * gst_tag_get_license_version:
468  * @license_ref: a license reference string in form of a URI,
469  *     e.g. "http://creativecommons.org/licenses/by-nc-nd/2.0/"
470  *
471  * Get the version of a license.
472  *
473  * Returns: the version of the license, or NULL if the license is not known or
474  *    has no version
475  */
476 const gchar *
gst_tag_get_license_version(const gchar * license_ref)477 gst_tag_get_license_version (const gchar * license_ref)
478 {
479   int idx;
480 
481   g_return_val_if_fail (license_ref != NULL, NULL);
482 
483   idx = gst_tag_get_license_idx (license_ref, NULL);
484   if (idx < 0)
485     return NULL;
486 
487 #define LICENSE_FLAG_CC_OR_FSF \
488  (GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE|\
489   GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE)
490 
491   /* e.g. publicdomain isn't versioned */
492   if (!(licenses[idx].flags & LICENSE_FLAG_CC_OR_FSF))
493     return NULL;
494 
495   /* KISS for now... */
496   if (strstr (licenses[idx].ref, "/1.0/"))
497     return "1.0";
498   else if (strstr (licenses[idx].ref, "/2.0/"))
499     return "2.0";
500   else if (strstr (licenses[idx].ref, "/2.1/"))
501     return "2.1";
502   else if (strstr (licenses[idx].ref, "/2.5/"))
503     return "2.5";
504   else if (strstr (licenses[idx].ref, "/3.0/"))
505     return "3.0";
506 
507   GST_ERROR ("Could not determine version for ref '%s'", license_ref);
508   return NULL;
509 }
510