1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670
|
/* GStreamer Editing Services
*
* Copyright (C) 2012-2015 Thibault Saunier <thibault.saunier@collabora.com>
* Copyright (C) 2012 Volodymyr Rudyi <vladimir.rudoy@gmail.com>
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION: gesasset
* @title: GESAsset
* @short_description: Represents usable resources inside the GStreamer
* Editing Services
*
* A #GESAsset in the GStreamer Editing Services represents a resources
* that can be used. In particular, any class that implements the
* #GESExtractable interface may have some associated assets with a
* corresponding #GESAsset:extractable-type, from which its objects can be
* extracted using ges_asset_extract(). Some examples would be
* #GESClip, #GESFormatter and #GESTrackElement.
*
* All assets that are created within GES are stored in a cache; one per
* each #GESAsset:id and #GESAsset:extractable-type pair. These assets can
* be fetched, and initialized if they do not yet exist in the cache,
* using ges_asset_request().
*
* ``` c
* GESAsset *effect_asset;
* GESEffect *effect;
*
* // You create an asset for an effect
* effect_asset = ges_asset_request (GES_TYPE_EFFECT, "agingtv", NULL);
*
* // And now you can extract an instance of GESEffect from that asset
* effect = GES_EFFECT (ges_asset_extract (effect_asset));
*
* ```
*
* The advantage of using assets, rather than simply creating the object
* directly, is that the currently loaded resources can be listed with
* ges_list_assets() and displayed to an end user. For example, to show
* which media files have been loaded, and a standard list of effects. In
* fact, the GES library already creates assets for #GESTransitionClip and
* #GESFormatter, which you can use to list all the available transition
* types and supported formats.
*
* The other advantage is that #GESAsset implements #GESMetaContainer, so
* metadata can be set on the asset, with some subclasses automatically
* creating this metadata on initiation.
*
* For example, to display information about the supported formats, you
* could do the following:
* |[
* GList *formatter_assets, *tmp;
*
* // List all the transitions
* formatter_assets = ges_list_assets (GES_TYPE_FORMATTER);
*
* // Print some infos about the formatter GESAsset
* for (tmp = formatter_assets; tmp; tmp = tmp->next) {
* gst_print ("Name of the formatter: %s, file extension it produces: %s",
* ges_meta_container_get_string (
* GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_NAME),
* ges_meta_container_get_string (
* GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_EXTENSION));
* }
*
* g_list_free (transition_assets);
*
* ]|
*
* ## ID
*
* Each asset is uniquely defined in the cache by its
* #GESAsset:extractable-type and #GESAsset:id. Depending on the
* #GESAsset:extractable-type, the #GESAsset:id can be used to parametrise
* the creation of the object upon extraction. By default, a class that
* implements #GESExtractable will only have a single associated asset,
* with an #GESAsset:id set to the type name of its objects. However, this
* is overwritten by some implementations, which allow a class to have
* multiple associated assets. For example, for #GESTransitionClip the
* #GESAsset:id will be a nickname of the #GESTransitionClip:vtype. You
* should check the documentation for each extractable type to see if they
* differ from the default.
*
* Moreover, each #GESAsset:extractable-type may also associate itself
* with a specific asset subclass. In such cases, when their asset is
* requested, an asset of this subclass will be returned instead.
*
* ## Managing
*
* You can use a #GESProject to easily manage the assets of a
* #GESTimeline.
*
* ## Proxies
*
* Some assets can (temporarily) act as the #GESAsset:proxy of another
* asset. When the original asset is requested from the cache, the proxy
* will be returned in its place. This can be useful if, say, you want
* to substitute a #GESUriClipAsset corresponding to a high resolution
* media file with the asset of a lower resolution stand in.
*
* An asset may even have several proxies, the first of which will act as
* its default and be returned on requests, but the others will be ordered
* to take its place once it is removed. You can add a proxy to an asset,
* or set its default, using ges_asset_set_proxy(), and you can remove
* them with ges_asset_unproxy().
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "ges.h"
#include "ges-internal.h"
#define GLIB_DISABLE_DEPRECATION_WARNINGS
#include <gst/gst.h>
GST_DEBUG_CATEGORY_STATIC (ges_asset_debug);
#undef GST_CAT_DEFAULT
#define GST_CAT_DEFAULT ges_asset_debug
enum
{
PROP_0,
PROP_TYPE,
PROP_ID,
PROP_PROXY,
PROP_PROXY_TARGET,
PROP_LAST
};
typedef enum
{
ASSET_NOT_INITIALIZED,
ASSET_INITIALIZING, ASSET_INITIALIZED_WITH_ERROR,
ASSET_PROXIED,
ASSET_NEEDS_RELOAD,
ASSET_INITIALIZED
} GESAssetState;
static GParamSpec *_properties[PROP_LAST];
struct _GESAssetPrivate
{
gchar *id;
GESAssetState state;
GType extractable_type;
/* used internally by try_proxy to pre-set a proxy whilst an asset is
* still loading. It can be used later to set the proxy for the asset
* once it has finished loading */
char *proxied_asset_id;
/* actual list of proxies */
GList *proxies;
/* the asset whose proxies list we belong to */
GESAsset *proxy_target;
/* The error that occurred when an asset has been initialized with error */
GError *error;
};
/* Internal structure to help avoid full loading
* of one asset several times
*/
typedef struct
{
GList *results;
GESAsset *asset;
} GESAssetCacheEntry;
/* We are mapping entries by types and ID, such as:
*
* {
* first_extractable_type_name1 :
* {
* "some ID": GESAssetCacheEntry,
* "some other ID": GESAssetCacheEntry 2
* },
* second_extractable_type_name :
* {
* "some ID": GESAssetCacheEntry,
* "some other ID": GESAssetCacheEntry 2
* }
* }
*
* (The first extractable type is the type of the class that implemented
* the GESExtractable interface ie: GESClip, GESTimeline,
* GESFomatter, etc... but not subclasses)
*
* This is in order to be able to have 2 Asset with the same ID but
* different extractable types.
**/
static GHashTable *type_entries_table = NULL;
/* Protect all the entries in the cache */
static GRecMutex asset_cache_lock;
#define LOCK_CACHE (g_rec_mutex_lock (&asset_cache_lock))
#define UNLOCK_CACHE (g_rec_mutex_unlock (&asset_cache_lock))
static gchar *
_check_and_update_parameters (GType * extractable_type, const gchar * id,
GError ** error)
{
gchar *real_id;
GType old_type = *extractable_type;
*extractable_type =
ges_extractable_get_real_extractable_type_for_id (*extractable_type, id);
if (*extractable_type == G_TYPE_NONE) {
GST_WARNING ("No way to create a Asset for ID: %s, type: %s", id,
g_type_name (old_type));
if (error && *error == NULL)
g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID,
"Wrong ID, can not find any extractable_type");
return NULL;
}
real_id = ges_extractable_type_check_id (*extractable_type, id, error);
if (real_id == NULL) {
GST_WARNING ("Wrong ID %s, can not create asset", id);
g_free (real_id);
if (error && *error == NULL)
g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID, "Wrong ID");
return NULL;
}
return real_id;
}
/* FIXME: why are we not accepting a GError ** error argument, which we
* could pass to ges_asset_cache_set_loaded ()? Which would allow the
* error to be set for the GInitable init method below */
static gboolean
start_loading (GESAsset * asset)
{
GInitableIface *iface;
iface = g_type_interface_peek (GES_ASSET_GET_CLASS (asset), G_TYPE_INITABLE);
if (!iface->init) {
GST_INFO_OBJECT (asset, "Can not start loading sync, as no ->init vmethod");
return FALSE;
}
ges_asset_cache_put (gst_object_ref (asset), NULL);
return ges_asset_cache_set_loaded (asset->priv->extractable_type,
asset->priv->id, NULL);
}
static gboolean
initable_init (GInitable * initable, GCancellable * cancellable,
GError ** error)
{
/* FIXME: Is there actually a reason to be freeing the GError that
* error points to? */
g_clear_error (error);
return start_loading (GES_ASSET (initable));
}
static void
async_initable_init_async (GAsyncInitable * initable, gint io_priority,
GCancellable * cancellable, GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GError *error = NULL;
GESAsset *asset = GES_ASSET (initable);
task = g_task_new (asset, cancellable, callback, user_data);
ges_asset_cache_put (gst_object_ref (asset), task);
switch (GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error)) {
case GES_ASSET_LOADING_ERROR:
{
if (error == NULL)
g_set_error (&error, GES_ERROR, GES_ERROR_ASSET_LOADING,
"Could not start loading asset");
/* FIXME Define error code */
ges_asset_cache_set_loaded (asset->priv->extractable_type,
asset->priv->id, error);
g_error_free (error);
return;
}
case GES_ASSET_LOADING_OK:
{
ges_asset_cache_set_loaded (asset->priv->extractable_type,
asset->priv->id, error);
return;
}
case GES_ASSET_LOADING_ASYNC:
/* If Async.... let it go */
/* FIXME: how are user subclasses that implement ->start_loading
* to return GES_ASSET_LOADING_ASYNC meant to invoke the private
* method ges_asset_cache_set_loaded once they finish initializing?
*/
break;
}
}
static void
async_initable_iface_init (GAsyncInitableIface * async_initable_iface)
{
async_initable_iface->init_async = async_initable_init_async;
}
static void
initable_iface_init (GInitableIface * initable_iface)
{
initable_iface->init = initable_init;
}
G_DEFINE_TYPE_WITH_CODE (GESAsset, ges_asset, G_TYPE_OBJECT,
G_ADD_PRIVATE (GESAsset)
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL));
/* GESAsset virtual methods default implementation */
static GESAssetLoadingReturn
ges_asset_start_loading_default (GESAsset * asset, GError ** error)
{
return GES_ASSET_LOADING_OK;
}
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
static GESExtractable *
ges_asset_extract_default (GESAsset * asset, GError ** error)
{
guint n_params;
GParameter *params;
GESAssetPrivate *priv = asset->priv;
GESExtractable *n_extractable;
gint i;
GValue *values;
const gchar **names;
params = ges_extractable_type_get_parameters_from_id (priv->extractable_type,
priv->id, &n_params);
values = g_malloc0 (sizeof (GValue) * n_params);
names = g_malloc0 (sizeof (gchar *) * n_params);
for (i = 0; i < n_params; i++) {
values[i] = params[i].value;
names[i] = params[i].name;
}
n_extractable =
GES_EXTRACTABLE (g_object_new_with_properties (priv->extractable_type,
n_params, names, values));
g_free (names);
g_free (values);
while (n_params--)
g_value_unset (¶ms[n_params].value);
g_free (params);
return n_extractable;
}
G_GNUC_END_IGNORE_DEPRECATIONS;
static gboolean
ges_asset_request_id_update_default (GESAsset * self, gchar ** proposed_new_id,
GError * error)
{
return FALSE;
}
/* GObject virtual methods implementation */
static void
ges_asset_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
GESAsset *asset = GES_ASSET (object);
switch (property_id) {
case PROP_TYPE:
g_value_set_gtype (value, asset->priv->extractable_type);
break;
case PROP_ID:
g_value_set_string (value, asset->priv->id);
break;
case PROP_PROXY:
g_value_set_object (value, ges_asset_get_proxy (asset));
break;
case PROP_PROXY_TARGET:
g_value_set_object (value, ges_asset_get_proxy_target (asset));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
ges_asset_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
GESAsset *asset = GES_ASSET (object);
switch (property_id) {
case PROP_TYPE:
asset->priv->extractable_type = g_value_get_gtype (value);
/* NOTE: we calling this in the setter so metadata is set on the
* asset upon initiation, but before it has been loaded. */
ges_extractable_register_metas (asset->priv->extractable_type, asset);
break;
case PROP_ID:
asset->priv->id = g_value_dup_string (value);
break;
case PROP_PROXY:
ges_asset_set_proxy (asset, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
ges_asset_finalize (GObject * object)
{
GESAssetPrivate *priv = GES_ASSET (object)->priv;
GST_LOG_OBJECT (object, "finalizing");
if (priv->id)
g_free (priv->id);
if (priv->proxied_asset_id)
g_free (priv->proxied_asset_id);
if (priv->error)
g_error_free (priv->error);
if (priv->proxies)
g_list_free (priv->proxies);
G_OBJECT_CLASS (ges_asset_parent_class)->finalize (object);
}
void
ges_asset_class_init (GESAssetClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = ges_asset_get_property;
object_class->set_property = ges_asset_set_property;
object_class->finalize = ges_asset_finalize;
/**
* GESAsset:extractable-type:
*
* The #GESExtractable object type that can be extracted from the asset.
*/
_properties[PROP_TYPE] =
g_param_spec_gtype ("extractable-type", "Extractable type",
"The type of the Object that can be extracted out of the asset",
G_TYPE_OBJECT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
/**
* GESAsset:id:
*
* The ID of the asset. This should be unique amongst all assets with
* the same #GESAsset:extractable-type. Depending on the associated
* #GESExtractable implementation, this id may convey some information
* about the #GObject that should be extracted. Note that, as such, the
* ID will have an expected format, and you can not choose this value
* arbitrarily. By default, this will be set to the type name of the
* #GESAsset:extractable-type, but you should check the documentation
* of the extractable type to see whether they differ from the
* default behaviour.
*/
_properties[PROP_ID] =
g_param_spec_string ("id", "Identifier",
"The unique identifier of the asset", NULL,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
/**
* GESAsset:proxy:
*
* The default proxy for this asset, or %NULL if it has no proxy. A
* proxy will act as a substitute for the original asset when the
* original is requested (see ges_asset_request()).
*
* Setting this property will not usually remove the existing proxy, but
* will replace it as the default (see ges_asset_set_proxy()).
*/
_properties[PROP_PROXY] =
g_param_spec_object ("proxy", "Proxy",
"The asset default proxy.", GES_TYPE_ASSET, G_PARAM_READWRITE);
/**
* GESAsset:proxy-target:
*
* The asset that this asset is a proxy for, or %NULL if it is not a
* proxy for another asset.
*
* Note that even if this asset is acting as a proxy for another asset,
* but this asset is not the default #GESAsset:proxy, then @proxy-target
* will *still* point to this other asset. So you should check the
* #GESAsset:proxy property of @target-proxy before assuming it is the
* current default proxy for the target.
*
* Note that the #GObject::notify for this property is emitted after
* the #GESAsset:proxy #GObject::notify for the corresponding (if any)
* asset it is now the proxy of/no longer the proxy of.
*/
_properties[PROP_PROXY_TARGET] =
g_param_spec_object ("proxy-target", "Proxy target",
"The target of a proxy asset.", GES_TYPE_ASSET, G_PARAM_READABLE);
g_object_class_install_properties (object_class, PROP_LAST, _properties);
klass->start_loading = ges_asset_start_loading_default;
klass->extract = ges_asset_extract_default;
klass->request_id_update = ges_asset_request_id_update_default;
klass->inform_proxy = NULL;
GST_DEBUG_CATEGORY_INIT (ges_asset_debug, "ges-asset",
GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GES Asset");
}
void
ges_asset_init (GESAsset * self)
{
self->priv = ges_asset_get_instance_private (self);
self->priv->state = ASSET_INITIALIZING;
self->priv->proxied_asset_id = NULL;
}
/* Internal methods */
static inline const gchar *
_extractable_type_name (GType type)
{
/* We can use `ges_asset_request (GES_TYPE_FORMATTER);` */
if (g_type_is_a (type, GES_TYPE_FORMATTER))
return g_type_name (GES_TYPE_FORMATTER);
return g_type_name (type);
}
static void
ges_asset_cache_init_unlocked (void)
{
if (type_entries_table)
return;
type_entries_table = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) g_hash_table_unref);
_init_formatter_assets ();
_init_standard_transition_assets ();
}
/* WITH LOCK_CACHE */
static GHashTable *
_get_type_entries (void)
{
if (type_entries_table)
return type_entries_table;
ges_asset_cache_init_unlocked ();
return type_entries_table;
}
/* WITH LOCK_CACHE */
static inline GESAssetCacheEntry *
_lookup_entry (GType extractable_type, const gchar * id)
{
GHashTable *entries_table;
entries_table = g_hash_table_lookup (_get_type_entries (),
_extractable_type_name (extractable_type));
if (entries_table)
return g_hash_table_lookup (entries_table, id);
return NULL;
}
static void
_free_entries (gpointer entry)
{
GESAssetCacheEntry *data = (GESAssetCacheEntry *) entry;
if (data->asset)
gst_object_unref (data->asset);
g_slice_free (GESAssetCacheEntry, entry);
}
static void
_gtask_return_error (GTask * task, GError * error)
{
g_task_return_error (task, g_error_copy (error));
}
static void
_gtask_return_true (GTask * task, gpointer udata)
{
g_task_return_boolean (task, TRUE);
}
/**
* ges_asset_cache_lookup:
*
* @id String identifier of asset
*
* Looks for asset with specified id in cache and it's completely loaded.
*
* Returns: (transfer none) (nullable): The #GESAsset found or %NULL
*/
GESAsset *
ges_asset_cache_lookup (GType extractable_type, const gchar * id)
{
GESAsset *asset = NULL;
GESAssetCacheEntry *entry = NULL;
g_return_val_if_fail (id, NULL);
LOCK_CACHE;
entry = _lookup_entry (extractable_type, id);
if (entry)
asset = entry->asset;
UNLOCK_CACHE;
return asset;
}
static void
ges_asset_cache_append_task (GType extractable_type,
const gchar * id, GTask * task)
{
GESAssetCacheEntry *entry = NULL;
LOCK_CACHE;
if ((entry = _lookup_entry (extractable_type, id)))
entry->results = g_list_append (entry->results, task);
UNLOCK_CACHE;
}
gboolean
ges_asset_cache_set_loaded (GType extractable_type, const gchar * id,
GError * error)
{
GESAsset *asset;
GESAssetCacheEntry *entry = NULL;
GList *results = NULL;
GFunc user_func = NULL;
gpointer user_data = NULL;
LOCK_CACHE;
if ((entry = _lookup_entry (extractable_type, id)) == NULL) {
UNLOCK_CACHE;
GST_ERROR ("Calling but type %s ID: %s not in cached, "
"something massively screwed", g_type_name (extractable_type), id);
return FALSE;
}
asset = entry->asset;
GST_DEBUG_OBJECT (entry->asset, ": (extractable type: %s) loaded, calling %i "
"callback (Error: %s)", g_type_name (asset->priv->extractable_type),
g_list_length (entry->results), error ? error->message : "");
results = entry->results;
entry->results = NULL;
if (error) {
asset->priv->state = ASSET_INITIALIZED_WITH_ERROR;
if (asset->priv->error)
g_error_free (asset->priv->error);
asset->priv->error = g_error_copy (error);
/* In case of error we do not want to emit in idle as we need to recover
* if possible */
user_func = (GFunc) _gtask_return_error;
user_data = error;
GST_DEBUG_OBJECT (asset, "initialized with error");
} else {
asset->priv->state = ASSET_INITIALIZED;
user_func = (GFunc) _gtask_return_true;
GST_DEBUG_OBJECT (asset, "initialized");
}
UNLOCK_CACHE;
g_list_foreach (results, user_func, user_data);
g_list_free_full (results, g_object_unref);
return TRUE;
}
/* transfer full for both @asset and @task */
void
ges_asset_cache_put (GESAsset * asset, GTask * task)
{
GType extractable_type;
const gchar *asset_id;
GESAssetCacheEntry *entry;
/* Needing to work with the cache, taking the lock */
asset_id = ges_asset_get_id (asset);
extractable_type = asset->priv->extractable_type;
LOCK_CACHE;
if (!(entry = _lookup_entry (extractable_type, asset_id))) {
GHashTable *entries_table;
entries_table = g_hash_table_lookup (_get_type_entries (),
_extractable_type_name (extractable_type));
if (entries_table == NULL) {
entries_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
_free_entries);
g_hash_table_insert (_get_type_entries (),
g_strdup (_extractable_type_name (extractable_type)), entries_table);
}
entry = g_slice_new0 (GESAssetCacheEntry);
/* transfer asset to entry */
entry->asset = asset;
if (task)
entry->results = g_list_prepend (entry->results, task);
g_hash_table_insert (entries_table, (gpointer) g_strdup (asset_id),
(gpointer) entry);
} else {
/* give up the reference we were given */
gst_object_unref (asset);
if (task) {
GST_DEBUG ("%s already in cache, adding result %p", asset_id, task);
entry->results = g_list_prepend (entry->results, task);
}
}
UNLOCK_CACHE;
}
void
ges_asset_cache_init (void)
{
LOCK_CACHE;
ges_asset_cache_init_unlocked ();
UNLOCK_CACHE;
}
void
ges_asset_cache_deinit (void)
{
_deinit_formatter_assets ();
LOCK_CACHE;
g_hash_table_destroy (type_entries_table);
type_entries_table = NULL;
UNLOCK_CACHE;
}
gboolean
ges_asset_request_id_update (GESAsset * asset, gchar ** proposed_id,
GError * error)
{
g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
return GES_ASSET_GET_CLASS (asset)->request_id_update (asset, proposed_id,
error);
}
/* pre-set a proxy id whilst the asset is still loading. Once the proxy
* is loaded, call ges_asset_finish_proxy (proxy) */
gboolean
ges_asset_try_proxy (GESAsset * asset, const gchar * new_id)
{
GESAssetClass *class;
g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
if (g_strcmp0 (asset->priv->id, new_id) == 0) {
GST_WARNING_OBJECT (asset, "Trying to proxy to itself (%s),"
" NOT possible", new_id);
return FALSE;
} else if (g_strcmp0 (asset->priv->proxied_asset_id, new_id) == 0) {
GST_WARNING_OBJECT (asset,
"Trying to proxy to same currently set proxy: %s -- %s",
asset->priv->proxied_asset_id, new_id);
return FALSE;
}
g_free (asset->priv->proxied_asset_id);
asset->priv->state = ASSET_PROXIED;
asset->priv->proxied_asset_id = g_strdup (new_id);
/* FIXME: inform_proxy is not used consistently. For example, it is
* not called in set_proxy. However, it is still used by GESUriAsset.
* We should find some other method */
class = GES_ASSET_GET_CLASS (asset);
if (class->inform_proxy)
GES_ASSET_GET_CLASS (asset)->inform_proxy (asset, new_id);
GST_DEBUG_OBJECT (asset, "Trying to proxy to %s", new_id);
return TRUE;
}
static gboolean
_lookup_proxied_asset (const gchar * id, GESAssetCacheEntry * entry,
const gchar * asset_id)
{
return !g_strcmp0 (asset_id, entry->asset->priv->proxied_asset_id);
}
/* find the assets that called try_proxy for the asset id of @proxy
* and set @proxy as their proxy */
gboolean
ges_asset_finish_proxy (GESAsset * proxy)
{
GESAsset *proxied_asset;
GHashTable *entries_table;
GESAssetCacheEntry *entry;
LOCK_CACHE;
entries_table = g_hash_table_lookup (_get_type_entries (),
_extractable_type_name (proxy->priv->extractable_type));
entry = g_hash_table_find (entries_table, (GHRFunc) _lookup_proxied_asset,
(gpointer) ges_asset_get_id (proxy));
if (!entry) {
UNLOCK_CACHE;
GST_DEBUG_OBJECT (proxy, "Not proxying any asset %s", proxy->priv->id);
return FALSE;
}
proxied_asset = entry->asset;
UNLOCK_CACHE;
/* If the asset with the matching ->proxied_asset_id is already proxied
* by another asset, we actually want @proxy to proxy this instead */
while (proxied_asset->priv->proxies)
proxied_asset = proxied_asset->priv->proxies->data;
/* unless it is ourselves. I.e. it is already proxied by us */
if (proxied_asset == proxy)
return FALSE;
GST_INFO_OBJECT (proxied_asset,
"%s Making sure the proxy chain is fully set.",
ges_asset_get_id (entry->asset));
if (g_strcmp0 (proxied_asset->priv->proxied_asset_id, proxy->priv->id) ||
g_strcmp0 (proxied_asset->priv->id, proxy->priv->proxied_asset_id))
ges_asset_finish_proxy (proxied_asset);
return ges_asset_set_proxy (proxied_asset, proxy);
}
static gboolean
_contained_in_proxy_tree (GESAsset * node, GESAsset * search)
{
GList *tmp;
if (node == search)
return TRUE;
for (tmp = node->priv->proxies; tmp; tmp = tmp->next) {
if (_contained_in_proxy_tree (tmp->data, search))
return TRUE;
}
return FALSE;
}
/**
* ges_asset_set_proxy:
* @asset: The #GESAsset to proxy
* @proxy: (allow-none): A new default proxy for @asset
*
* Sets the #GESAsset:proxy for the asset.
*
* If @proxy is among the existing proxies of the asset (see
* ges_asset_list_proxies()) it will be moved to become the default
* proxy. Otherwise, if @proxy is not %NULL, it will be added to the list
* of proxies, as the new default. The previous default proxy will become
* 'next in line' for if the new one is removed, and so on. As such, this
* will **not** actually remove the previous default proxy (use
* ges_asset_unproxy() for that).
*
* Note that an asset can only act as a proxy for one other asset.
*
* As a special case, if @proxy is %NULL, then this method will actually
* remove **all** proxies from the asset.
*
* Returns: %TRUE if @proxy was successfully set as the default for
* @asset.
*/
gboolean
ges_asset_set_proxy (GESAsset * asset, GESAsset * proxy)
{
GESAsset *current_target;
g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
g_return_val_if_fail (proxy == NULL || GES_IS_ASSET (proxy), FALSE);
g_return_val_if_fail (asset != proxy, FALSE);
if (!proxy) {
GList *tmp, *proxies;
if (asset->priv->error) {
GST_ERROR_OBJECT (asset,
"Asset was loaded with error (%s), it should not be 'unproxied'",
asset->priv->error->message);
return FALSE;
}
GST_DEBUG_OBJECT (asset, "Removing all proxies");
proxies = asset->priv->proxies;
asset->priv->proxies = NULL;
for (tmp = proxies; tmp; tmp = tmp->next) {
GESAsset *proxy = tmp->data;
proxy->priv->proxy_target = NULL;
}
asset->priv->state = ASSET_INITIALIZED;
g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
for (tmp = proxies; tmp; tmp = tmp->next)
g_object_notify_by_pspec (G_OBJECT (tmp->data),
_properties[PROP_PROXY_TARGET]);
g_list_free (proxies);
return TRUE;
}
current_target = proxy->priv->proxy_target;
if (current_target && current_target != asset) {
GST_ERROR_OBJECT (asset,
"Trying to use '%s' as a proxy, but it is already proxying '%s'",
proxy->priv->id, current_target->priv->id);
return FALSE;
}
if (_contained_in_proxy_tree (proxy, asset)) {
GST_ERROR_OBJECT (asset, "Trying to setup a circular proxying dependency!");
return FALSE;
}
if (g_list_find (asset->priv->proxies, proxy)) {
GST_INFO_OBJECT (asset,
"%" GST_PTR_FORMAT " already marked as proxy, moving to first", proxy);
asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
}
GST_INFO ("%s is now proxied by %s", asset->priv->id, proxy->priv->id);
asset->priv->proxies = g_list_prepend (asset->priv->proxies, proxy);
proxy->priv->proxy_target = asset;
asset->priv->state = ASSET_PROXIED;
g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
if (current_target != asset)
g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
/* FIXME: ->inform_proxy is not called. We should figure out what the
* purpose of ->inform_proxy should be generically. Currently, it is
* only called in ges_asset_try_proxy! */
return TRUE;
}
/**
* ges_asset_unproxy:
* @asset: The #GESAsset to no longer proxy with @proxy
* @proxy: An existing proxy of @asset
*
* Removes the proxy from the available list of proxies for the asset. If
* the given proxy is the default proxy of the list, then the next proxy
* in the available list (see ges_asset_list_proxies()) will become the
* default. If there are no other proxies, then the asset will no longer
* have a default #GESAsset:proxy.
*
* Returns: %TRUE if @proxy was successfully removed from @asset's proxy
* list.
*/
gboolean
ges_asset_unproxy (GESAsset * asset, GESAsset * proxy)
{
gboolean removing_default;
gboolean last_proxy;
g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
g_return_val_if_fail (GES_IS_ASSET (proxy), FALSE);
g_return_val_if_fail (asset != proxy, FALSE);
/* also tests if the list is NULL */
if (!g_list_find (asset->priv->proxies, proxy)) {
GST_INFO_OBJECT (asset, "'%s' is not a proxy.", proxy->priv->id);
return FALSE;
}
last_proxy = (asset->priv->proxies->next == NULL);
if (last_proxy && asset->priv->error) {
GST_ERROR_OBJECT (asset,
"Asset was loaded with error (%s), its last proxy '%s' should "
"not be removed", asset->priv->error->message, proxy->priv->id);
return FALSE;
}
removing_default = (asset->priv->proxies->data == proxy);
asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
if (last_proxy)
asset->priv->state = ASSET_INITIALIZED;
proxy->priv->proxy_target = NULL;
if (removing_default)
g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
return TRUE;
}
/**
* ges_asset_list_proxies:
* @asset: A #GESAsset
*
* Get all the proxies that the asset has. The first item of the list will
* be the default #GESAsset:proxy. The second will be the proxy that is
* 'next in line' to be default, and so on.
*
* Returns: (element-type GESAsset) (transfer none): The list of proxies
* that @asset has.
*/
GList *
ges_asset_list_proxies (GESAsset * asset)
{
g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
return asset->priv->proxies;
}
/**
* ges_asset_get_proxy:
* @asset: A #GESAsset
*
* Gets the default #GESAsset:proxy of the asset.
*
* Returns: (transfer none) (nullable): The default proxy of @asset.
*/
GESAsset *
ges_asset_get_proxy (GESAsset * asset)
{
g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
if (asset->priv->state == ASSET_PROXIED && asset->priv->proxies) {
return asset->priv->proxies->data;
}
return NULL;
}
/**
* ges_asset_get_proxy_target:
* @proxy: A #GESAsset
*
* Gets the #GESAsset:proxy-target of the asset.
*
* Note that the proxy target may have loaded with an error, so you should
* call ges_asset_get_error() on the returned target.
*
* Returns: (transfer none) (nullable): The asset that @proxy is a proxy
* of.
*/
GESAsset *
ges_asset_get_proxy_target (GESAsset * proxy)
{
g_return_val_if_fail (GES_IS_ASSET (proxy), NULL);
return proxy->priv->proxy_target;
}
/* Caution, this method should be used in rare cases (ie: for the project
* as we can change its ID from a useless one to a proper URI). In most
* cases you want to update the ID creating a proxy
*/
void
ges_asset_set_id (GESAsset * asset, const gchar * id)
{
GHashTable *entries;
gpointer orig_id = NULL;
GESAssetCacheEntry *entry = NULL;
GESAssetPrivate *priv = NULL;
g_return_if_fail (GES_IS_ASSET (asset));
priv = asset->priv;
if (priv->state != ASSET_INITIALIZED) {
GST_WARNING_OBJECT (asset, "Trying to rest ID on an object that is"
" not properly loaded");
return;
}
if (g_strcmp0 (id, priv->id) == 0) {
GST_DEBUG_OBJECT (asset, "ID is already %s", id);
return;
}
LOCK_CACHE;
entries = g_hash_table_lookup (_get_type_entries (),
_extractable_type_name (asset->priv->extractable_type));
g_return_if_fail (g_hash_table_lookup_extended (entries, priv->id, &orig_id,
(gpointer *) & entry));
g_hash_table_steal (entries, priv->id);
g_hash_table_insert (entries, g_strdup (id), entry);
GST_DEBUG_OBJECT (asset, "Changing id from %s to %s", priv->id, id);
g_free (priv->id);
g_free (orig_id);
priv->id = g_strdup (id);
UNLOCK_CACHE;
}
static GESAsset *
_ensure_asset_for_wrong_id (const gchar * wrong_id, GType extractable_type,
GError * error)
{
GESAsset *asset;
if ((asset = ges_asset_cache_lookup (extractable_type, wrong_id)))
return asset;
/* It is a dummy GESAsset, we just bruteforce its creation */
asset = g_object_new (GES_TYPE_ASSET, "id", wrong_id,
"extractable-type", extractable_type, NULL);
/* transfer ownership to the cache */
ges_asset_cache_put (asset, NULL);
ges_asset_cache_set_loaded (extractable_type, wrong_id, error);
return asset;
}
/**********************************
* *
* API implementation *
* *
**********************************/
/**
* ges_asset_get_extractable_type:
* @self: The #GESAsset
*
* Gets the #GESAsset:extractable-type of the asset.
*
* Returns: The extractable type of @self.
*/
GType
ges_asset_get_extractable_type (GESAsset * self)
{
g_return_val_if_fail (GES_IS_ASSET (self), G_TYPE_INVALID);
return self->priv->extractable_type;
}
/**
* ges_asset_request:
* @extractable_type: The #GESAsset:extractable-type of the asset
* @id: (allow-none): The #GESAsset:id of the asset
* @error: (allow-none): An error to be set if the requested asset has
* loaded with an error, or %NULL to ignore
*
* Returns an asset with the given properties. If such an asset already
* exists in the cache (it has been previously created in GES), then a
* reference to the existing asset is returned. Otherwise, a newly created
* asset is returned, and also added to the cache.
*
* If the requested asset has been loaded with an error, then @error is
* set, if given, and %NULL will be returned instead.
*
* Note that the given @id may not be exactly the #GESAsset:id that is
* set on the returned asset. For instance, it may be adjusted into a
* standard format. Or, if a #GESExtractable type does not have its
* extraction parametrised, as is the case by default, then the given @id
* may be ignored entirely and the #GESAsset:id set to some standard, in
* which case a %NULL @id can be given.
*
* Similarly, the given @extractable_type may not be exactly the
* #GESAsset:extractable-type that is set on the returned asset. Instead,
* the actual extractable type may correspond to a subclass of the given
* @extractable_type, depending on the given @id.
*
* Moreover, depending on the given @extractable_type, the returned asset
* may belong to a subclass of #GESAsset.
*
* Finally, if the requested asset has a #GESAsset:proxy, then the proxy
* that is found at the end of the chain of proxies is returned (a proxy's
* proxy will take its place, and so on, unless it has no proxy).
*
* Some asset subclasses only support asynchronous construction of its
* assets, such as #GESUriClip. For such assets this method will fail, and
* you should use ges_asset_request_async() instead. In the case of
* #GESUriClip, you can use ges_uri_clip_asset_request_sync() if you only
* want to wait for the request to finish.
*
* Returns: (transfer full) (allow-none): A reference to the requested
* asset, or %NULL if an error occurred.
*/
GESAsset *
ges_asset_request (GType extractable_type, const gchar * id, GError ** error)
{
gchar *real_id;
GError *lerr = NULL;
GESAsset *asset = NULL, *proxy;
gboolean proxied = TRUE;
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
g_return_val_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT), NULL);
g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
NULL);
real_id = _check_and_update_parameters (&extractable_type, id, &lerr);
if (real_id == NULL) {
/* We create an asset for that wrong ID so we have a reference that the
* user requested it */
_ensure_asset_for_wrong_id (id, extractable_type, lerr);
real_id = g_strdup (id);
}
if (lerr)
g_error_free (lerr);
/* asset owned by cache */
asset = ges_asset_cache_lookup (extractable_type, real_id);
if (asset) {
while (proxied) {
proxied = FALSE;
switch (asset->priv->state) {
case ASSET_INITIALIZED:
break;
case ASSET_INITIALIZING:
asset = NULL;
break;
case ASSET_PROXIED:
proxy = ges_asset_get_proxy (asset);
if (proxy == NULL) {
GST_ERROR ("Proxied against an asset we do not"
" have in cache, something massively screwed");
asset = NULL;
} else {
proxied = TRUE;
do {
asset = proxy;
} while ((proxy = ges_asset_get_proxy (asset)));
}
break;
case ASSET_NEEDS_RELOAD:
GST_DEBUG_OBJECT (asset, "Asset in cache and needs reload");
if (!start_loading (asset)) {
GST_ERROR ("Failed to reload the asset for id %s", id);
asset = NULL;
}
break;
case ASSET_INITIALIZED_WITH_ERROR:
GST_WARNING_OBJECT (asset, "Initialized with error, not returning");
if (asset->priv->error && error)
*error = g_error_copy (asset->priv->error);
asset = NULL;
break;
default:
GST_WARNING ("Case %i not handle, returning", asset->priv->state);
asset = NULL;
break;
}
}
if (asset)
gst_object_ref (asset);
} else {
GObjectClass *klass;
GInitableIface *iface;
GType asset_type = ges_extractable_type_get_asset_type (extractable_type);
klass = g_type_class_ref (asset_type);
iface = g_type_interface_peek (klass, G_TYPE_INITABLE);
if (iface->init) {
/* FIXME: allow the error to be set, which GInitable is designed
* for! */
asset = g_initable_new (asset_type,
NULL, NULL, "id", real_id, "extractable-type",
extractable_type, NULL);
} else {
GST_WARNING ("Tried to create an Asset for type %s but no ->init method",
g_type_name (extractable_type));
}
g_type_class_unref (klass);
}
if (real_id)
g_free (real_id);
GST_DEBUG ("New asset created synchronously: %p", asset);
return asset;
}
/**
* ges_asset_request_async:
* @extractable_type: The #GESAsset:extractable-type of the asset
* @id: (allow-none): The #GESAsset:id of the asset
* @cancellable: (allow-none): An object to allow cancellation of the
* asset request, or %NULL to ignore
* @callback: A function to call when the initialization is finished
* @user_data: Data to be passed to @callback
*
* Requests an asset with the given properties asynchronously (see
* ges_asset_request()). When the asset has been initialized or fetched
* from the cache, the given callback function will be called. The
* asset can then be retrieved in the callback using the
* ges_asset_request_finish() method on the given #GAsyncResult.
*
* Note that the source object passed to the callback will be the
* #GESAsset corresponding to the request, but it may not have loaded
* correctly and therefore can not be used as is. Instead,
* ges_asset_request_finish() should be used to fetch a usable asset, or
* indicate that an error occurred in the asset's creation.
*
* Note that the callback will be called in the #GMainLoop running under
* the same #GMainContext that ges_init() was called in. So, if you wish
* the callback to be invoked outside the default #GMainContext, you can
* call g_main_context_push_thread_default() in a new thread before
* calling ges_init().
*
* Example of an asynchronous asset request:
* ``` c
* // The request callback
* static void
* asset_loaded_cb (GESAsset * source, GAsyncResult * res, gpointer user_data)
* {
* GESAsset *asset;
* GError *error = NULL;
*
* asset = ges_asset_request_finish (res, &error);
* if (asset) {
* gst_print ("The file: %s is usable as a GESUriClip",
* ges_asset_get_id (asset));
* } else {
* gst_print ("The file: %s is *not* usable as a GESUriClip because: %s",
* ges_asset_get_id (source), error->message);
* }
*
* gst_object_unref (asset);
* }
*
* // The request:
* ges_asset_request_async (GES_TYPE_URI_CLIP, some_uri, NULL,
* (GAsyncReadyCallback) asset_loaded_cb, user_data);
* ```
*/
void
ges_asset_request_async (GType extractable_type,
const gchar * id, GCancellable * cancellable, GAsyncReadyCallback callback,
gpointer user_data)
{
gchar *real_id;
GESAsset *asset;
GError *error = NULL;
GTask *task = NULL;
g_return_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT));
g_return_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE));
g_return_if_fail (callback);
GST_DEBUG ("Creating asset with extractable type %s and ID=%s",
g_type_name (extractable_type), id);
real_id = _check_and_update_parameters (&extractable_type, id, &error);
if (error) {
_ensure_asset_for_wrong_id (id, extractable_type, error);
real_id = g_strdup (id);
}
/* Check if we already have an asset for this ID */
asset = ges_asset_cache_lookup (extractable_type, real_id);
if (asset) {
task = g_task_new (asset, NULL, callback, user_data);
/* In the case of proxied asset, we will loop until we find the
* last asset of the chain of proxied asset */
while (TRUE) {
switch (asset->priv->state) {
case ASSET_INITIALIZED:
GST_DEBUG_OBJECT (asset, "Asset in cache and initialized, "
"using it");
/* Takes its own references to @asset */
g_task_return_boolean (task, TRUE);
goto done;
case ASSET_INITIALIZING:
GST_DEBUG_OBJECT (asset, "Asset in cache and but not "
"initialized, setting a new callback");
ges_asset_cache_append_task (extractable_type, real_id, task);
task = NULL;
goto done;
case ASSET_PROXIED:{
GESAsset *target = ges_asset_get_proxy (asset);
if (target == NULL) {
GST_ERROR ("Asset %s proxied against an asset (%s) we do not"
" have in cache, something massively screwed",
asset->priv->id, asset->priv->proxied_asset_id);
goto done;
}
asset = target;
break;
}
case ASSET_NEEDS_RELOAD:
GST_DEBUG_OBJECT (asset, "Asset in cache and needs reload");
ges_asset_cache_append_task (extractable_type, real_id, task);
task = NULL;
GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error);
goto done;
case ASSET_INITIALIZED_WITH_ERROR:
g_task_return_error (task,
error ? g_error_copy (error) : g_error_copy (asset->priv->error));
g_clear_error (&error);
goto done;
default:
GST_WARNING ("Case %i not handle, returning", asset->priv->state);
return;
}
}
}
g_async_initable_new_async (ges_extractable_type_get_asset_type
(extractable_type), G_PRIORITY_DEFAULT, cancellable, callback, user_data,
"id", real_id, "extractable-type", extractable_type, NULL);
done:
if (task)
gst_object_unref (task);
if (real_id)
g_free (real_id);
}
/**
* ges_asset_needs_reload
* @extractable_type: The #GESAsset:extractable-type of the asset that
* needs reloading
* @id: (allow-none): The #GESAsset:id of the asset asset that needs
* reloading
*
* Indicate that an existing #GESAsset in the cache should be reloaded
* upon the next request. This can be used when some condition has
* changed, which may require that an existing asset should be updated.
* For example, if an external resource has changed or now become
* available.
*
* Note, the asset is not immediately changed, but will only actually
* reload on the next call to ges_asset_request() or
* ges_asset_request_async().
*
* Returns: %TRUE if the specified asset exists in the cache and could be
* marked for reloading.
*/
gboolean
ges_asset_needs_reload (GType extractable_type, const gchar * id)
{
gchar *real_id;
GESAsset *asset;
GError *error = NULL;
g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
FALSE);
real_id = _check_and_update_parameters (&extractable_type, id, &error);
if (error) {
_ensure_asset_for_wrong_id (id, extractable_type, error);
real_id = g_strdup (id);
}
asset = ges_asset_cache_lookup (extractable_type, real_id);
if (real_id) {
g_free (real_id);
}
if (asset) {
GST_DEBUG_OBJECT (asset,
"Asset with id %s switch state to ASSET_NEEDS_RELOAD",
ges_asset_get_id (asset));
asset->priv->state = ASSET_NEEDS_RELOAD;
g_clear_error (&asset->priv->error);
return TRUE;
}
GST_DEBUG ("Asset with id %s not found in cache", id);
return FALSE;
}
/**
* ges_asset_get_id:
* @self: A #GESAsset
*
* Gets the #GESAsset:id of the asset.
*
* Returns: The ID of @self.
*/
const gchar *
ges_asset_get_id (GESAsset * self)
{
g_return_val_if_fail (GES_IS_ASSET (self), NULL);
return self->priv->id;
}
/**
* ges_asset_extract:
* @self: The #GESAsset to extract an object from
* @error: An error to be set in case something goes wrong,
* or %NULL to ignore
*
* Extracts a new #GESAsset:extractable-type object from the asset. The
* #GESAsset:id of the asset may determine the properties and state of the
* newly created object.
*
* Returns: (transfer floating): A newly created object, or %NULL if an
* error occurred.
*/
GESExtractable *
ges_asset_extract (GESAsset * self, GError ** error)
{
GESExtractable *extractable;
g_return_val_if_fail (GES_IS_ASSET (self), NULL);
g_return_val_if_fail (GES_ASSET_GET_CLASS (self)->extract, NULL);
GST_DEBUG_OBJECT (self, "Extracting asset of type %s",
g_type_name (self->priv->extractable_type));
extractable = GES_ASSET_GET_CLASS (self)->extract (self, error);
if (extractable == NULL)
return NULL;
if (ges_extractable_get_asset (extractable) == NULL)
ges_extractable_set_asset (extractable, self);
return extractable;
}
/**
* ges_asset_request_finish:
* @res: The task result to fetch the asset from
* @error: An error to be set in case something goes wrong, or %NULL to ignore
*
* Fetches an asset requested by ges_asset_request_async(), which
* finalises the request.
*
* Returns: (transfer full): The requested asset, or %NULL if an error
* occurred.
*/
GESAsset *
ges_asset_request_finish (GAsyncResult * res, GError ** error)
{
GObject *object;
GObject *source_object;
g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
source_object = g_async_result_get_source_object (res);
g_assert (source_object != NULL);
object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
res, error);
gst_object_unref (source_object);
return GES_ASSET (object);
}
/**
* ges_list_assets:
* @filter: The type of object that can be extracted from the asset
*
* List all the assets in the current cache whose
* #GESAsset:extractable-type are of the given type (including
* subclasses).
*
* Note that, since only a #GESExtractable can be extracted from an asset,
* using `GES_TYPE_EXTRACTABLE` as @filter will return all the assets in
* the current cache.
*
* Returns: (transfer container) (element-type GESAsset): A list of all
* #GESAsset-s currently in the cache whose #GESAsset:extractable-type is
* of the @filter type.
*/
GList *
ges_list_assets (GType filter)
{
GList *ret = NULL;
GESAsset *asset;
GHashTableIter iter, types_iter;
gpointer key, value, typename, assets;
g_return_val_if_fail (g_type_is_a (filter, GES_TYPE_EXTRACTABLE), NULL);
LOCK_CACHE;
g_hash_table_iter_init (&types_iter, _get_type_entries ());
while (g_hash_table_iter_next (&types_iter, &typename, &assets)) {
if (g_type_is_a (filter, g_type_from_name ((gchar *) typename)) == FALSE)
continue;
g_hash_table_iter_init (&iter, (GHashTable *) assets);
while (g_hash_table_iter_next (&iter, &key, &value)) {
asset = ((GESAssetCacheEntry *) value)->asset;
if (g_type_is_a (asset->priv->extractable_type, filter))
ret = g_list_append (ret, asset);
}
}
UNLOCK_CACHE;
return ret;
}
/**
* ges_asset_get_error:
* @self: A #GESAsset
*
* Retrieve the error that was set on the asset when it was loaded.
*
* Returns: (transfer none) (nullable): The error set on @asset, or
* %NULL if no error occurred when @asset was loaded.
*
* Since: 1.8
*/
GError *
ges_asset_get_error (GESAsset * self)
{
g_return_val_if_fail (GES_IS_ASSET (self), NULL);
return self->priv->error;
}
|