Initialize Tizen 2.3
[framework/multimedia/gst-plugins-base0.10.git] / mobile / gst-libs / gst / tag / mklicensestables.c
1 /* GStreamer License 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., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /* mklicensestables.c:
21  * little program that reads liblicense's license RDF files and outputs tables
22  * with the most important information, so we don't have to parse megabytes
23  * of mostly redundant RDF files to get some basic information (and vendors
24  * don't have to ship it all).
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include "tag.h"
32
33 #include <string.h>
34 #include <stdlib.h>
35 #include <stdio.h>
36
37 /* TODO: we can merge some of the jurisdiction-only license table entries
38  * into one entry with multiple jurisdictions and without the 'generic' flag,
39  * .e.g. by-nc-nd/2.5/es + by-nc-nd/2.5/au => by-nc-nd/2.5/{es,au} */
40
41 #define LIBLICENSE_DATA_PREFIX "/usr/share/liblicense/licenses"
42
43 static GHashTable *unknown_sources;     /* NULL */
44
45 static GList *licenses;         /* NULL */
46
47 /* list of languages used for translations */
48 static GList *langs;            /* NULL */
49
50 /* keep in sync with licenses.c */
51 static const gchar jurisdictions[] =
52     "ar\000at\000au\000be\000bg\000br\000ca\000ch\000cl\000cn\000co\000de\000"
53     "dk\000es\000fi\000fr\000hr\000hu\000il\000in\000it\000jp\000kr\000mk\000"
54     "mt\000mx\000my\000nl\000pe\000pl\000pt\000scotland\000se\000si\000tw\000"
55     "uk\000us\000za";
56
57 /* keep in sync with gst_tag_get_license_version() */
58 static const gchar known_versions[] = "1.0/2.0/2.1/2.5/3.0/";
59
60 /* is this license 'generic' (and a base for any of the supported
61  * jurisdictions), or jurisdiction-specific only? */
62 #define JURISDICTION_GENERIC (G_GUINT64_CONSTANT (1) << 63)
63
64 typedef struct
65 {
66   gchar *ref;
67   guint64 jurisdiction;
68   gchar *jurisdiction_suffix;   /* if not generic (e.g. "jp/") */
69   gchar *legalcode;
70   gchar *version;
71   gchar *replaced_by;
72   gchar *source;
73
74   GstTagLicenseFlags flags;
75
76   gboolean deprecated;
77
78   GHashTable *titles;
79   GHashTable *descriptions;
80
81   /* for processing */
82   const gchar *cur_lang;
83   gboolean packed_into_source;
84
85   /* list of licenses packed into this one (ie. this is the source of those) */
86   GList *derived;
87 } License;
88
89 static GstTagLicenseFlags
90 ref_to_flag (const gchar * ref)
91 {
92   if (strcmp (ref, "http://creativecommons.org/ns#Reproduction") == 0)
93     return GST_TAG_LICENSE_PERMITS_REPRODUCTION;
94   if (strcmp (ref, "http://creativecommons.org/ns#Distribution") == 0)
95     return GST_TAG_LICENSE_PERMITS_DISTRIBUTION;
96   if (strcmp (ref, "http://creativecommons.org/ns#DerivativeWorks") == 0)
97     return GST_TAG_LICENSE_PERMITS_DERIVATIVE_WORKS;
98   if (strcmp (ref, "http://creativecommons.org/ns#Sharing") == 0)
99     return GST_TAG_LICENSE_PERMITS_SHARING;
100   if (strcmp (ref, "http://creativecommons.org/ns#Notice") == 0)
101     return GST_TAG_LICENSE_REQUIRES_NOTICE;
102   if (strcmp (ref, "http://creativecommons.org/ns#Attribution") == 0)
103     return GST_TAG_LICENSE_REQUIRES_ATTRIBUTION;
104   if (strcmp (ref, "http://creativecommons.org/ns#ShareAlike") == 0)
105     return GST_TAG_LICENSE_REQUIRES_SHARE_ALIKE;
106   if (strcmp (ref, "http://creativecommons.org/ns#SourceCode") == 0)
107     return GST_TAG_LICENSE_REQUIRES_SOURCE_CODE;
108   if (strcmp (ref, "http://creativecommons.org/ns#Copyleft") == 0)
109     return GST_TAG_LICENSE_REQUIRES_COPYLEFT;
110   if (strcmp (ref, "http://creativecommons.org/ns#LesserCopyleft") == 0)
111     return GST_TAG_LICENSE_REQUIRES_LESSER_COPYLEFT;
112   if (strcmp (ref, "http://creativecommons.org/ns#CommercialUse") == 0)
113     return GST_TAG_LICENSE_PROHIBITS_COMMERCIAL_USE;
114   if (strcmp (ref, "http://creativecommons.org/ns#HighIncomeNationUse") == 0)
115     return GST_TAG_LICENSE_PROHIBITS_HIGH_INCOME_NATION_USE;
116
117   g_error ("Unknown permits/requires/prohibits: %s\n", ref);
118   return 0;
119 };
120
121 static guint64
122 ref_to_jurisdiction (const gchar * ref)
123 {
124   const gchar *j = jurisdictions;
125   gchar *jur;
126   guint64 bit = 1;
127
128   jur = g_strdup (ref + strlen ("http://creativecommons.org/international/"));
129   g_strdelimit (jur, "/", '\0');
130   while (j < jurisdictions + sizeof (jurisdictions)) {
131     if (strcmp (j, jur) == 0) {
132       g_free (jur);
133       g_assert (bit != 0 && bit != JURISDICTION_GENERIC);
134       return bit;
135     }
136     j += strlen (j) + 1;
137     bit <<= 1;
138   }
139   g_error ("Unknown jurisdiction '%s'\n", ref);
140 }
141
142 typedef enum
143 {
144   TAG_CC_LICENSE,
145   TAG_CC_JURISDICTION,
146   TAG_CC_LEGALCODE,
147   TAG_CC_PROHIBITS,
148   TAG_CC_REQUIRES,
149   TAG_CC_PERMITS,
150   TAG_CC_DEPRECATED_ON,
151   TAG_DC_CREATOR,
152   TAG_DC_SOURCE,
153   TAG_DC_TITLE,
154   TAG_DC_DESCRIPTION,
155   TAG_DCQ_HAS_VERSION,
156   TAG_DCQ_IS_REPLACED_BY,
157   TAG_RDF_RDF,
158   TAG_RDF_DESCRIPTION,
159 } Tag;
160
161 static const struct
162 {
163   const gchar *element_name;
164   const gchar *attribute;
165   const Tag element_tag;
166 } tag_map[] = {
167   {
168   "cc:License", "rdf:about", TAG_CC_LICENSE}, {
169   "cc:deprecatedOn", "rdf:datatype", TAG_CC_DEPRECATED_ON}, {
170   "cc:jurisdiction", "rdf:resource", TAG_CC_JURISDICTION}, {
171   "cc:legalcode", "rdf:resource", TAG_CC_LEGALCODE}, {
172   "cc:prohibits", "rdf:resource", TAG_CC_PROHIBITS}, {
173   "cc:requires", "rdf:resource", TAG_CC_REQUIRES}, {
174   "cc:permits", "rdf:resource", TAG_CC_PERMITS}, {
175   "dc:creator", "rdf:resource", TAG_DC_CREATOR}, {
176   "dc:source", "rdf:resource", TAG_DC_SOURCE}, {
177   "dc:title", "xml:lang", TAG_DC_TITLE}, {
178   "dc:description", "xml:lang", TAG_DC_DESCRIPTION}, {
179   "dcq:hasVersion", NULL, TAG_DCQ_HAS_VERSION}, {
180   "dcq:isReplacedBy", "rdf:resource", TAG_DCQ_IS_REPLACED_BY}, {
181   "rdf:RDF", NULL, TAG_RDF_RDF}, {
182   "rdf:Description", "rdf:about", TAG_RDF_DESCRIPTION},
183       /* these three are just for by-nc-nd_2.0_jp_.rdf */
184   {
185   "dc:isBasedOn", "rdf:resource", TAG_DC_SOURCE}, {
186   "dc:hasVersion", NULL, TAG_DCQ_HAS_VERSION}, {
187   "dc:isReplacedBy", "rdf:resource", TAG_DCQ_IS_REPLACED_BY}
188 };
189
190 static void
191 parse_start (GMarkupParseContext * ctx, const gchar * element_name,
192     const gchar ** attr_names, const gchar ** attr_vals,
193     gpointer user_data, GError ** err)
194 {
195   License *license = user_data;
196   const gchar *ref = NULL;
197   int i;
198
199   for (i = 0; i < G_N_ELEMENTS (tag_map); ++i) {
200     if (strcmp (element_name, tag_map[i].element_name) == 0)
201       break;
202   }
203
204   if (i == G_N_ELEMENTS (tag_map))
205     g_error ("Unexpected tag '%s'\n", element_name);
206
207   if (tag_map[i].attribute == NULL)
208     return;
209
210   if (!g_markup_collect_attributes (element_name, attr_names, attr_vals,
211           err, G_MARKUP_COLLECT_STRING, tag_map[i].attribute, &ref,
212           G_MARKUP_COLLECT_INVALID)) {
213     return;
214   }
215
216   switch (tag_map[i].element_tag) {
217     case TAG_CC_LICENSE:
218       if (!g_str_has_prefix (ref, "http://creativecommons.org/licenses/"))
219         g_error ("Unexpected license reference: %s\n", ref);
220       /* we assume one license per file, and CC license ref */
221       g_assert (license->ref == NULL);
222       license->ref = g_strdup (ref);
223       break;
224     case TAG_CC_JURISDICTION:
225       if (!g_str_has_prefix (ref, "http://creativecommons.org/international/"))
226         g_error ("Unknown license jurisdiction: %s\n", ref);
227       /* we assume one jurisdiction per license */
228       g_assert (license->jurisdiction == JURISDICTION_GENERIC);
229       license->jurisdiction = ref_to_jurisdiction (ref);
230       license->jurisdiction_suffix =
231           g_strdup (ref + strlen ("http://creativecommons.org/international/"));
232       break;
233     case TAG_CC_LEGALCODE:
234       if (!g_str_has_prefix (ref, "http://creativecommons.org/licenses/"))
235         g_error ("Unexpected legalcode reference: %s\n", ref);
236       /* we assume one legalcode per license */
237       g_assert (license->legalcode == NULL);
238       license->legalcode = g_strdup (ref);
239       break;
240     case TAG_DC_CREATOR:
241       if (strcmp (ref, "http://creativecommons.org") == 0) {
242         license->flags |= GST_TAG_LICENSE_CREATIVE_COMMONS_LICENSE;
243       } else if (strcmp (ref, "http://fsf.org") == 0) {
244         license->flags |= GST_TAG_LICENSE_FREE_SOFTWARE_FOUNDATION_LICENSE;
245       } else {
246         g_error ("Unknown license creator: %s\n", ref);
247       }
248       break;
249     case TAG_CC_DEPRECATED_ON:
250       break;
251     case TAG_CC_PROHIBITS:
252     case TAG_CC_REQUIRES:
253     case TAG_CC_PERMITS:
254       license->flags |= ref_to_flag (ref);
255       break;
256     case TAG_DC_TITLE:{
257       gchar *cur_lang;
258
259       cur_lang = g_strdelimit (g_strdup (ref), "-", '_');
260       license->cur_lang = g_intern_string (cur_lang);
261       if (!g_list_find_custom (langs, cur_lang, (GCompareFunc) strcmp))
262         langs = g_list_prepend (langs, (gpointer) license->cur_lang);
263
264       g_free (cur_lang);
265       break;
266     }
267     case TAG_DC_DESCRIPTION:{
268       gchar *cur_lang;
269
270       cur_lang = g_strdelimit (g_strdup (ref), "-", '_');
271       license->cur_lang = g_intern_string (cur_lang);
272       if (!g_list_find_custom (langs, cur_lang, (GCompareFunc) strcmp))
273         langs = g_list_prepend (langs, (gpointer) license->cur_lang);
274
275       g_free (cur_lang);
276       break;
277     }
278     case TAG_DCQ_IS_REPLACED_BY:
279       /* we assume one replacer per license for now */
280       g_assert (license->replaced_by == NULL);
281       license->replaced_by = g_strdup (ref);
282       break;
283     case TAG_RDF_DESCRIPTION:
284       if (!g_str_has_prefix (ref, "http://creativecommons.org/licenses/"))
285         g_error ("Unexpected license reference: %s\n", ref);
286       if (license->ref != NULL && strcmp (license->ref, ref) != 0) {
287         gchar *f, *r = g_strdup (ref);
288
289         /* work around bug in some of the RDFs ... */
290         if ((f = strstr (r, "by-nc-nd"))) {
291           memcpy (f, "by-nd-nc", 8);
292         }
293         if (strcmp (license->ref, r) != 0) {
294           g_error ("rdf:Description chunk for other than current license");
295         }
296         g_free (r);
297       }
298       break;
299     case TAG_DC_SOURCE:
300       if (!g_str_has_prefix (ref, "http://creativecommons.org/licenses/"))
301         g_error ("Unexpected source reference: %s\n", ref);
302       /* we assume one source (for jurisdiction-specific versions) */
303       g_assert (license->source == NULL);
304       license->source = g_strdup (ref);
305       break;
306     default:
307       g_printerr ("unhandled start tag: %s\n", element_name);
308       break;
309   }
310 }
311
312 static void
313 parse_text (GMarkupParseContext * ctx, const gchar * text, gsize text_len,
314     gpointer user_data, GError ** err)
315 {
316   License *license = user_data;
317   const gchar *element_name, *found;
318   int i;
319
320   element_name = g_markup_parse_context_get_element (ctx);
321   for (i = 0; i < G_N_ELEMENTS (tag_map); ++i) {
322     if (strcmp (element_name, tag_map[i].element_name) == 0)
323       break;
324   }
325
326   if (i == G_N_ELEMENTS (tag_map))
327     g_error ("Unexpected tag '%s'\n", element_name);
328
329   switch (tag_map[i].element_tag) {
330     case TAG_CC_LICENSE:
331     case TAG_CC_JURISDICTION:
332     case TAG_CC_LEGALCODE:
333     case TAG_DC_CREATOR:
334     case TAG_CC_PROHIBITS:
335     case TAG_CC_REQUIRES:
336     case TAG_CC_PERMITS:
337     case TAG_RDF_RDF:
338     case TAG_RDF_DESCRIPTION:
339       break;
340     case TAG_DC_TITLE:
341       if (license->titles == NULL) {
342         license->titles = g_hash_table_new (g_str_hash, g_str_equal);
343       }
344       g_hash_table_insert (license->titles, (gpointer) license->cur_lang,
345           (gpointer) g_intern_string (text));
346       break;
347     case TAG_DC_DESCRIPTION:{
348       gchar *txt = g_strdup (text);
349
350       if (license->descriptions == NULL) {
351         license->descriptions = g_hash_table_new (g_str_hash, g_str_equal);
352       }
353       g_strdelimit (txt, "\n", ' ');
354       g_hash_table_insert (license->descriptions, (gpointer) license->cur_lang,
355           (gpointer) g_intern_string (txt));
356       g_free (txt);
357       break;
358     }
359     case TAG_DCQ_HAS_VERSION:
360       /* we assume one version per license */
361       g_assert (license->version == NULL);
362       license->version = g_strdup (text);
363       found = strstr (known_versions, license->version);
364       if (found == NULL || found[strlen (license->version)] != '/')
365         g_error ("Unexpected version '%s', please add to table.", text);
366       break;
367     case TAG_CC_DEPRECATED_ON:
368       license->deprecated = TRUE;
369       break;
370     case TAG_DC_SOURCE:        // FIXME
371     default:
372       g_print ("text (%s) (%s): '%s'\n", element_name, license->cur_lang, text);
373   }
374 }
375
376 static void
377 parse_passthrough (GMarkupParseContext * ctx, const gchar * text, gsize len,
378     gpointer user_data, GError ** err)
379 {
380   if (!g_str_has_prefix (text, "<?xml ")) {
381     g_error ("Unexpected passthrough text: %s\n", text);
382   }
383 }
384
385 static void
386 parse_error (GMarkupParseContext * ctx, GError * err, gpointer data)
387 {
388   g_error ("parse error: %s\n", err->message);
389 }
390
391 static const GMarkupParser license_rdf_parser = {
392   parse_start, NULL, parse_text, parse_passthrough, parse_error
393 };
394
395 static void
396 parse_license_rdf (const gchar * fn, const gchar * rdf)
397 {
398   GMarkupParseContext *ctx;
399   License *license;
400   GError *err = NULL;
401
402   if (!g_utf8_validate (rdf, -1, NULL)) {
403     g_error ("%s is not valid UTF-8\n", fn);
404   }
405
406   license = g_new0 (License, 1);
407
408   /* mark as generic until proven otherwise */
409   license->jurisdiction = JURISDICTION_GENERIC;
410
411   ctx = g_markup_parse_context_new (&license_rdf_parser,
412       G_MARKUP_TREAT_CDATA_AS_TEXT, license, NULL);
413
414   /* g_print ("Parsing %s\n", fn); */
415
416   if (!g_markup_parse_context_parse (ctx, rdf, -1, &err)) {
417     g_error ("Error parsing file %s: %s\n", fn, err->message);
418   }
419
420   licenses = g_list_append (licenses, license);
421
422   g_markup_parse_context_free (ctx);
423 }
424
425 static void
426 read_licenses (const gchar * licenses_dir)
427 {
428   const gchar *name;
429   GError *err = NULL;
430   GDir *dir;
431
432   dir = g_dir_open (licenses_dir, 0, &err);
433
434   if (dir == NULL)
435     g_error ("Failed to g_dir_open('%s'): %s", licenses_dir, err->message);
436
437   while ((name = g_dir_read_name (dir))) {
438     gchar *fn, *rdf;
439
440     fn = g_build_filename (licenses_dir, name, NULL);
441     if (g_file_get_contents (fn, &rdf, NULL, &err)) {
442       parse_license_rdf (fn, rdf);
443       g_free (rdf);
444     } else {
445       g_printerr ("Could not read file '%s': %s\n", fn, err->message);
446       g_error_free (err);
447       err = NULL;
448     }
449     g_free (fn);
450   }
451
452   g_dir_close (dir);
453 }
454
455 static License *
456 find_license (const gchar * ref)
457 {
458   GList *l;
459
460   if (!g_str_has_prefix (ref, "http://creativecommons.org/"))
461     return NULL;
462
463   for (l = licenses; l != NULL; l = l->next) {
464     License *license = l->data;
465
466     if (strcmp (license->ref, ref) == 0)
467       return license;
468   }
469
470   return NULL;
471 }
472
473 static int
474 license_ref_cmp (License * a, License * b)
475 {
476   return strcmp (a->ref, b->ref);
477 }
478
479 #define STRING_TABLE_MAX_STRINGS 100
480 typedef struct
481 {
482   GString *s;
483   guint num_escaped;
484   guint num_strings;
485   guint indices[STRING_TABLE_MAX_STRINGS];
486   gchar *strings[STRING_TABLE_MAX_STRINGS];     /* unescaped strings */
487 } StringTable;
488
489 static StringTable *
490 string_table_new (void)
491 {
492   StringTable *t = g_new0 (StringTable, 1);
493
494   t->s = g_string_new (NULL);
495   return t;
496 }
497
498 static void
499 string_table_free (StringTable * t)
500 {
501   int i;
502
503   for (i = 0; i < t->num_strings; ++i)
504     g_free (t->strings[i]);
505
506   g_string_free (t->s, TRUE);
507   g_free (t);
508 }
509
510 static guint
511 string_table_add_string (StringTable * t, const gchar * str)
512 {
513   const gchar *s;
514   guint idx, i;
515
516   /* check if we already have this string */
517   for (i = 0; i < t->num_strings; ++i) {
518     if (strcmp (t->strings[i], str) == 0)
519       return t->indices[i];
520   }
521
522   /* save current offset */
523   idx = t->s->len;
524
525   /* adjust for fact that \000 is 4 chars now but will take up only 1 later */
526   idx -= t->num_escaped * 3;
527
528   /* append one char at a time, making sure to escape UTF-8 characters */
529   for (s = str; s != NULL && *s != '\0'; ++s) {
530     if (g_ascii_isprint (*s) && *s != '"' && *s != '\\') {
531       g_string_append_c (t->s, *s);
532     } else {
533       g_string_append_printf (t->s, "\\%03o", (unsigned char) *s);
534       t->num_escaped++;
535     }
536   }
537   g_string_append (t->s, "\\000");
538   t->num_escaped++;
539
540   t->indices[t->num_strings] = idx;
541   t->strings[t->num_strings] = g_strdup (str);
542   ++t->num_strings;
543
544   return idx;
545 }
546
547 static void
548 string_table_print (StringTable * t)
549 {
550   const gchar *s;
551
552   s = t->s->str;
553   while (s != NULL && *s != '\0') {
554     gchar line[74], *lastesc;
555     guint left;
556
557     left = strlen (s);
558     g_strlcpy (line, s, MIN (left, sizeof (line)));
559     s += sizeof (line) - 1;
560     /* avoid partial escaped codes at the end of a line */
561     if ((lastesc = strrchr (line, '\\')) && strlen (lastesc) < 4) {
562       s -= strlen (lastesc);
563       *lastesc = '\0';
564     }
565     g_print ("  \"%s\"", line);
566     if (left < 74)
567       break;
568     g_print ("\n");
569   }
570   g_print (";\n");
571 }
572
573 /* skip translation if translated string for e.g. "fr_ca" is same as for "fr" */
574 static gboolean
575 skip_translation (GHashTable * ht_strings, const gchar * lang,
576     const gchar * trans)
577 {
578   const gchar *simple_trans;
579   gchar *simple_lang;
580
581   if (strchr (lang, '_') == NULL)
582     return FALSE;
583
584   simple_lang = g_strdup (lang);
585   g_strdelimit (simple_lang, "_", '\0');
586
587   simple_trans = g_hash_table_lookup (ht_strings, (gpointer) simple_lang);
588   g_free (simple_lang);
589
590   return (simple_trans != NULL && strcmp (trans, simple_trans) == 0);
591 }
592
593 static GVariant *
594 create_translation_dict (GHashTable * ht_strings, const gchar * en)
595 {
596   GVariantBuilder array;
597   guint count = 0;
598   GList *l;
599
600   g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY);
601
602   for (l = langs; l != NULL; l = l->next) {
603     const gchar *trans, *lang;
604
605     lang = (const gchar *) l->data;
606     trans = g_hash_table_lookup (ht_strings, (gpointer) lang);
607     if (trans != NULL && *trans != '\0' && strcmp (en, trans) != 0 &&
608         !skip_translation (ht_strings, lang, trans)) {
609       /* g_print ("%s (%s) => %s\n", en, lang, trans); */
610       g_variant_builder_add_value (&array,
611           g_variant_new_dict_entry (g_variant_new_string (lang),
612               g_variant_new_string (trans)));
613       ++count;
614     }
615   }
616
617   if (count == 0) {
618     g_variant_builder_clear (&array);
619     return NULL;
620   }
621
622   return g_variant_builder_end (&array);
623 }
624
625 static void
626 write_translations_dictionary (GList * licenses, const gchar * dict_filename)
627 {
628   /* maps C string => (dictionary of: locale => translation) */
629   GVariantBuilder array;
630   /* maps C string => boolean (if it's in the dictionary already */
631   GHashTable *translations;
632   GVariant *var;
633   GList *l;
634   FILE *f;
635
636   /* sort langs for prettiness / to make variant dumps easier to read */
637   langs = g_list_sort (langs, (GCompareFunc) strcmp);
638
639   g_variant_builder_init (&array, G_VARIANT_TYPE_ARRAY);
640
641   translations = g_hash_table_new (g_str_hash, g_str_equal);
642
643   for (l = licenses; l != NULL; l = l->next) {
644     const gchar *en;
645     License *license;
646
647     license = l->data;
648
649     if (license->packed_into_source)
650       continue;
651
652     /* add title + translations */
653     en = g_hash_table_lookup (license->titles, "en");
654     g_assert (en != NULL);
655
656     /* check if we already have added translations for this string */
657     if (!g_hash_table_lookup (translations, (gpointer) en)) {
658       GVariant *trans;
659
660       trans = create_translation_dict (license->titles, en);
661       if (trans != NULL) {
662         g_variant_builder_add_value (&array,
663             g_variant_new_dict_entry (g_variant_new_string (en), trans));
664         g_hash_table_insert (translations, (gpointer) en,
665             GINT_TO_POINTER (TRUE));
666       }
667     }
668
669     /* add description + translations */
670     if (license->descriptions == NULL)
671       continue;
672
673     en = g_hash_table_lookup (license->descriptions, "en");
674     g_assert (en != NULL);
675
676     /* check if we already have added translations for this string */
677     if (!g_hash_table_lookup (translations, (gpointer) en)) {
678       GVariant *trans;
679
680       trans = create_translation_dict (license->descriptions, en);
681       if (trans != NULL) {
682         g_variant_builder_add_value (&array,
683             g_variant_new_dict_entry (g_variant_new_string (en), trans));
684         g_hash_table_insert (translations, (gpointer) en,
685             GINT_TO_POINTER (TRUE));
686       }
687     }
688   }
689
690   var = g_variant_builder_end (&array);
691
692   f = fopen (dict_filename, "wb");
693   if (fwrite (g_variant_get_data (var), g_variant_get_size (var), 1, f) != 1) {
694     g_error ("failed to write dict to file: %s", g_strerror (errno));
695   }
696   fclose (f);
697
698   g_printerr ("Wrote dictionary to %s, size: %u, type: %s\n", dict_filename,
699       (guint) g_variant_get_size (var), (gchar *) g_variant_get_type (var));
700
701   g_variant_unref (var);
702   g_hash_table_destroy (translations);
703 }
704
705 int
706 main (int argc, char **argv)
707 {
708   gchar *translation_dict_fn = NULL;
709   GOptionContext *ctx;
710   GOptionEntry options[] = {
711     {"translation-dictionary", 0, 0, G_OPTION_ARG_FILENAME,
712           &translation_dict_fn, "Filename of translations dictionary to write",
713         NULL},
714     {NULL}
715   };
716   StringTable *string_table;
717   GError *err = NULL;
718   GList *l;
719   int idx = 0;
720
721   g_type_init ();
722
723   ctx = g_option_context_new ("");
724   g_option_context_add_main_entries (ctx, options, NULL);
725   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
726     g_printerr ("Error initializing: %s\n", err->message);
727     exit (1);
728   }
729   g_option_context_free (ctx);
730
731   read_licenses (LIBLICENSE_DATA_PREFIX);
732
733   g_printerr ("%d licenses\n", g_list_length (licenses));
734
735   unknown_sources = g_hash_table_new (g_str_hash, g_str_equal);
736
737   for (l = licenses; l != NULL; l = l->next) {
738     License *license = l->data;
739
740     /* if the license has as source, check if we can 'pack' it into the
741      * original license as a jurisdiction-specific variant */
742     if (license->source != NULL) {
743       License *source = find_license (license->source);
744
745       if (source != NULL) {
746         if (source->flags != license->flags) {
747           g_printerr ("Source and derived license have different flags:\n"
748               "\t0x%08x : %s\n\t0x%08x : %s\n", source->flags, source->ref,
749               license->flags, license->ref);
750           source = NULL;
751         } else {
752           if (source->descriptions == NULL) {
753             /* neither should the derived one then */
754             g_assert (license->descriptions == NULL);
755           } else {
756             /* make sure we're not settling for fewer descriptions than
757              * there are */
758             g_assert (g_hash_table_size (license->titles) <=
759                 g_hash_table_size (source->titles));
760             g_assert (g_hash_table_size (license->descriptions) <=
761                 g_hash_table_size (source->descriptions));
762           }
763         }
764       } else {
765         /* a source is referenced that we haven't encountered
766          * (possibly a referencing bug? seems to happen e.g. when there's a
767          * 2.1 version of a jurisdiction license and it refers to a 2.1
768          * source version, but there's only a 2.0 or 2.5 source version. So
769          * maybe it's supposed to refer to the 2.0 source then, who knows) */
770         if (!g_hash_table_lookup (unknown_sources, license->source)) {
771           g_printerr ("Unknown source license %s\n", license->source);
772           g_hash_table_insert (unknown_sources, g_strdup (license->source),
773               GUINT_TO_POINTER (TRUE));
774         }
775         /* g_print ("Unknown source license %s referenced from %s\n",
776          * license->source, license->ref); */
777       }
778
779       /* should we pack this into the source or not */
780       if (source != NULL) {
781         source->jurisdiction |= license->jurisdiction;
782         source->derived = g_list_insert_sorted (source->derived, license,
783             (GCompareFunc) license_ref_cmp);
784         license->packed_into_source = TRUE;
785       }
786     } else {
787       /* no source license */
788       if (license->titles == NULL)
789         g_error ("License has no titles: %s\n", license->ref);
790       if (license->descriptions == NULL);
791       g_printerr ("License %s has no descriptions!\n", license->ref);
792     }
793   }
794
795   licenses = g_list_sort (licenses, (GCompareFunc) license_ref_cmp);
796
797   string_table = string_table_new ();
798
799   g_print ("/* created by mklicensestables.c */\n");
800   g_print ("static const struct {\n"
801       "  /* jurisdictions in addition to the generic version, bitfield */\n"
802       "  const guint64             jurisdictions;\n"
803       "  const GstTagLicenseFlags  flags;\n"
804       "  /* the bit after http://creativecommons.org/licenses/ */\n"
805       "  const gchar               ref[18];\n"
806       "  gint16                    title_idx;  /* index in string table */\n"
807       "  gint16                    desc_idx;   /* index in string table */\n"
808       "} licenses[] = {\n");
809
810   for (l = licenses; l != NULL; l = l->next) {
811     const gchar *title_en, *desc_en;
812     int idx_title, idx_desc;
813     License *license;
814
815     license = l->data;
816
817     if (license->packed_into_source)
818       continue;
819
820     title_en = g_hash_table_lookup (license->titles, "en");
821     g_assert (title_en != NULL);
822     idx_title = string_table_add_string (string_table, title_en);
823     g_assert (idx_title <= G_MAXINT16);
824
825     if (license->descriptions != NULL) {
826       desc_en = g_hash_table_lookup (license->descriptions, "en");
827       g_assert (desc_en != NULL);
828       idx_desc = string_table_add_string (string_table, desc_en);
829       g_assert (idx_desc <= G_MAXINT16);
830     } else {
831       idx_desc = -1;
832     }
833
834     /* output comments with license refs covered by the next stanza */
835     if (license->derived != NULL) {
836       GList *d;
837
838       g_print ("  /* %2d %s\n", idx, license->ref);
839
840       for (d = license->derived; d != NULL; d = d->next) {
841         License *derived_license = d->data;
842
843         g_print ("   * %2d %s%s\n", idx, derived_license->ref,
844             (d->next == NULL) ? " */" : "");
845       }
846     } else {
847       g_print ("  /* %2d %s */\n", idx, license->ref);
848     }
849     /* output essential data */
850     {
851       gchar *ref;
852
853       ref =
854           g_strdup (license->ref +
855           strlen ("http://creativecommons.org/licenses/"));
856
857       /* remove jurisdiction suffix from ref if this is non-generic, since
858        * the suffix is already contained in the jurisdiction flags */
859       if (license->jurisdiction_suffix != NULL) {
860         gsize suffix_len = strlen (license->jurisdiction_suffix);
861         gchar *cutoff;
862
863         cutoff = ref + strlen (ref) - suffix_len;
864         g_assert (!strncmp (cutoff, license->jurisdiction_suffix, suffix_len));
865         g_assert (cutoff[suffix_len - 1] == '/');
866         g_assert (cutoff[suffix_len] == '\0');
867         *cutoff = '\0';
868       }
869
870       g_print ("  { 0x%016" G_GINT64_MODIFIER "x, 0x%08x, \"%s\", %d, %d }%s\n",
871           license->jurisdiction, license->flags, ref, idx_title, idx_desc,
872           (l->next != NULL) ? "," : "");
873
874       g_free (ref);
875     }
876     ++idx;
877   }
878   g_print ("};\n");
879
880   g_print ("\nstatic const gchar license_strings[] =\n");
881   string_table_print (string_table);
882   string_table_free (string_table);
883   string_table = NULL;
884
885   if (translation_dict_fn != NULL) {
886     write_translations_dictionary (licenses, translation_dict_fn);
887   }
888
889   return 0;
890 }