{"id":938,"date":"2023-03-11T15:41:57","date_gmt":"2023-03-11T07:41:57","guid":{"rendered":"https:\/\/swordofmorning.com\/?p=938"},"modified":"2025-10-09T13:55:19","modified_gmt":"2025-10-09T05:55:19","slug":"gstreamer-plugin-01","status":"publish","type":"post","link":"https:\/\/swordofmorning.com\/index.php\/2023\/03\/11\/gstreamer-plugin-01\/","title":{"rendered":"GStreamer Plugin 01 Basic"},"content":{"rendered":"<p><div class=\"has-toc have-toc\"><\/div><\/p>\n<h2>\u4e00\u3001\u7b80\u8ff0<\/h2>\n<p>&emsp;&emsp;\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u5982\u4e0b\u642d\u5efa\u4e00\u6761\u7ba1\u7ebf\uff1a<\/p>\n<pre><code class=\"language-c\">gst_bin_add_many(GST_BIN(pipeline), source, capsfilter, myele, convert, sink, NULL);<\/code><\/pre>\n<p>\u5728\u8fd9\u6761\u7ba1\u7ebf\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecev4l2src\u4e2d\u83b7\u53d6\u89c6\u9891\u6d41\uff0c\u7ecf\u8fc7\u6211\u4eec\u81ea\u5df1\u7684\u63d2\u4ef6<code>myele<\/code>\u540e\uff0c\u5c06\u6570\u636e\u63a8\u9001\u7ed9waylandsink\u3002<\/p>\n<h2>\u4e8c\u3001\u6e90\u7801<\/h2>\n<h3>2.1 main<\/h3>\n<p>&emsp;&emsp;\u6211\u4eec\u9996\u5148\u6765\u770b\u4e00\u4e0bmain\u662f\u600e\u4e48\u5199\u7684\u3002\u8fd9\u548c\u666e\u901a\u7684v4l2\u6355\u83b7\u4e00\u6837\uff0c\u53ea\u662f\u6211\u4eec\u5728caps\u4e4b\u540e\u94fe\u63a5\u4e0a\u6211\u4eec\u81ea\u5df1\u7684\u63d2\u4ef6\u3002<\/p>\n<pre><code class=\"language-c\">int my_main(int argc, char** argv)\n{\n    GstElement *pipeline;\n    GstElement *source, *capsfilter, *convert, *sink;\n    GstElement *myele;\n    GstCaps *caps;\n    GstBus *bus;\n    GstMessage *msg;\n    GstStateChangeReturn ret;\n    gboolean terminate = FALSE;\n    int retval = 0;\n\n    \/* Initialize GStreamer *\/\n    gst_init(&amp;argc, &amp;argv);\n\n    \/* Create the elements *\/\n    source = gst_element_factory_make(&quot;v4l2src&quot;, &quot;source&quot;);\n    convert = gst_element_factory_make(&quot;videoconvert&quot;, &quot;convert&quot;);\n    sink = gst_element_factory_make(&quot;waylandsink&quot;, &quot;sink&quot;);\n    capsfilter = gst_element_factory_make(&quot;capsfilter&quot;, &quot;capsfilter&quot;);\n\/\/debug myele\n    myele = gst_element_factory_make(&quot;rgb2grey&quot;, &quot;rgb2grey&quot;);\n\n    \/* Create the empty pipeline *\/\n    pipeline = gst_pipeline_new(&quot;test-pipeline&quot;);\n\n    \/* Check creation *\/\n    if (!pipeline || !source || !convert || !sink || !capsfilter) {\n        g_printerr(&quot;Not all elements could be created.\\n&quot;);\n        retval = -1;\n        goto out_return;\n    }\n\/\/debug myele\n    if (!myele) {\n        g_printerr(&quot;myele not created.\\n&quot;);\n        retval = -1;\n        goto out_return;\n    }\n    g_object_set(G_OBJECT(myele), &quot;width&quot;, 1920, &quot;height&quot;, 1080, NULL);\n\n    \/* Set caps on source *\/\n    g_object_set(G_OBJECT(source), &quot;device&quot;, &quot;\/dev\/video0&quot;, NULL);\n    caps = gst_caps_new_simple(\n        &quot;video\/x-raw&quot;, \n        &quot;format&quot;, G_TYPE_STRING, &quot;NV12&quot;,\n        &quot;width&quot;, G_TYPE_INT, 1920, \n        &quot;height&quot;, G_TYPE_INT, 1080, \n        &quot;framerate&quot;, GST_TYPE_FRACTION, 30, 1,\n        NULL\n    );\n    g_object_set(G_OBJECT(capsfilter), &quot;caps&quot;, caps, NULL);\n    gst_caps_unref(caps);\n\n    \/* Build the pipeline *\/\n    gst_bin_add_many(GST_BIN(pipeline), source, capsfilter, myele, convert, sink, NULL);\n    if (!gst_element_link_many(source, capsfilter, myele, convert, sink, NULL))\n    {\n        g_printerr(&quot;Elements could not be linked.\\n&quot;);\n        retval = -2;\n        goto out_unref;\n    }\n\n    \/* Set the pipeline to &quot;playing&quot; state *\/\n    ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);\n    if (ret == GST_STATE_CHANGE_FAILURE) {\n        g_printerr(&quot;Unable to set the pipeline to the playing state.\\n&quot;);\n        retval = -3;\n        goto out_unref;\n    }\n\n    \/* Wait until error or EOS *\/\n    bus = gst_element_get_bus(pipeline);\n    do {\n        msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);\n\n        \/* Parse message *\/\n        if (msg != NULL) {\n            GError *err;\n            gchar *debug_info;\n\n            switch (GST_MESSAGE_TYPE(msg)) {\n                case GST_MESSAGE_ERROR:\n                    gst_message_parse_error(msg, &amp;err, &amp;debug_info);\n                    g_printerr(&quot;Error received from element %s: %s\\n&quot;, GST_OBJECT_NAME(msg-&gt;src), err-&gt;message);\n                    g_printerr(&quot;Debugging information: %s\\n&quot;, debug_info ? debug_info : &quot;none&quot;);\n                    g_clear_error(&amp;err);\n                    g_free(debug_info);\n                    terminate = TRUE;\n                    break;\n                case GST_MESSAGE_EOS:\n                    g_print(&quot;End-Of-Stream reached.\\n&quot;);\n                    terminate = TRUE;\n                    break;\n                default:\n                    \/* Should not be reached *\/\n                    g_printerr(&quot;Unexpected message received.\\n&quot;);\n                    break;\n            }\n\n            gst_message_unref(msg);\n        }\n    } while (!terminate);\n\n    gst_object_unref(bus);\n    gst_element_set_state(pipeline, GST_STATE_NULL);\nout_unref:\n    gst_object_unref(pipeline);\nout_return:\n    return retval;\n}<\/code><\/pre>\n<h3>2.2 plugin.h<\/h3>\n<p>&emsp;&emsp;\u8fd9\u91cc\u53c2\u7167gst\u7c7b\u7684\u6a21\u677f\u5b8c\u6210\uff0c\u9700\u8981\u6ce8\u610f\uff1a\u6210\u5458\u53d8\u91cf\u7684\u679a\u4e3e\uff0c\u6709\u6548\u6570\u5b57\u5e94\u8be5\u4ece<code>1<\/code>\u5f00\u59cb\u3002<\/p>\n<pre><code class=\"language-c\">#pragma once\n\n#include &lt;gstreamer-1.0\/gst\/gst.h&gt;\n#include &lt;gstreamer-1.0\/gst\/gstcontext.h&gt;\n#include &lt;gstreamer-1.0\/gst\/video\/video-info.h&gt;\n\n#define VERSION &quot;1.0&quot;\n#define PACKAGE &quot;rgb2grey&quot;\n\nG_BEGIN_DECLS\n\nenum{\n  PROP_0 = 0,\n  PROP_WIDTH,\n  PROP_HEIGHT,\n};\n\ntypedef struct _GstRGB2GreyFilter {\n    GstElement element;\n    GstPad *sinkpad;\n    GstPad *srcpad;\n\n    guint width;\n    guint height;\n}GstRGB2GreyFilter;\n\ntypedef struct _GstRGB2GreyFilterClass {\n    GstElementClass parent_class;\n}GstRGB2GreyFilterClass;\n\n\/* declare get_type Func, defined in G_DEFINE_TYPE *\/\nGType gst_rgb2grey_filter_get_type(void);\n\n\/* ===== Standard macros for defining types for this element. ===== *\/\n\n\/\/return GType\n#define GST_TYPE_RGB2GREY_FILTER (gst_rgb2grey_filter_get_type())\n\n\/\/get a GstObtFilter ptr from any ptr #obj ,if obj is GST_TYPE_OBT_FILTER GType , return result , otherwise return null\n#define GST_RGB2GREY_FILTER(obj) \\\n    (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_RGB2GREY_FILTER, GstRGB2GreyFilter))\n\n\/\/get a GstObtFilterClass ptr from any ptr #obj ,if obj is GST_TYPE_OBT_FILTER GType , return result , otherwise return null\n#define GST_RGB2GREY_FILTER_CLASS(klass) \\\n    (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_RGB2GREY_FILTER, GstRGB2GreyFilterClass))\n\n#define GST_IS_RGB2GREY_FILTER(obj) \\\n    (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_RGB2GREY_FILTER))\n\n#define GST_IS_RGB2GREY_FILTER_CLASS(klass) \\\n    (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_RGB2GREY_FILTER))\n\nG_END_DECLS<\/code><\/pre>\n<h3>2.3 plugin.c<\/h3>\n<pre><code class=\"language-c\">#include &quot;rgb2grey.h&quot;\n\nstatic GstBuffer* gst_rgb2grey_filter_process_data(GstRGB2GreyFilter* filter, GstBuffer* buf);\nstatic gboolean gst_rgb2grey_filter_stop_processing(GstRGB2GreyFilter* filter);\nstatic gboolean gst_rgb2grey_filter_allocate_memory(GstRGB2GreyFilter* filter);\nstatic gboolean gst_rgb2grey_filter_free_memory(GstRGB2GreyFilter* filter);\n\n\/**\n * ==============================\n * @par boilerplate Funcs [START]\n * ==============================\n *\/\n\nG_DEFINE_TYPE(GstRGB2GreyFilter, gst_rgb2grey_filter, GST_TYPE_ELEMENT);\n\nstatic GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE(\n    &quot;sink&quot;,\n    GST_PAD_SINK,\n    GST_PAD_ALWAYS,\n    GST_STATIC_CAPS(&quot;ANY&quot;)\n);\n\nstatic GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE(\n    &quot;src&quot;,\n    GST_PAD_SRC,\n    GST_PAD_ALWAYS,\n    GST_STATIC_CAPS(&quot;ANY&quot;)\n);\n\n\/**\n * @brief \n * \n * @param pad pad reveive buffer\n * @param parent \n * @param buf to be precessed\n * @return GstFlowReturn \n *\/\nstatic GstFlowReturn gst_rgb2grey_filter_chain(GstPad* pad, GstObject *parent, GstBuffer *buf)\n{\n    \/\/ g_print(&quot;gst_rgb2grey_filter_chain\\n&quot;);\n    GstRGB2GreyFilter *filter = GST_RGB2GREY_FILTER(parent);\n\n    \/* Get input Buffer *\/\n    GstMapInfo in_map;\n    gst_buffer_map(buf, &amp;in_map, GST_MAP_READ | GST_MAP_WRITE);\n    guint in_width = filter-&gt;width;\n    guint in_height = filter-&gt;height;\n    guint in_pixel_size = in_width * in_height;\n    guint in_full_size = in_pixel_size * 1.5;\n    gsize in_size = in_map.size;\n\n    \/* TODO: set UV to 0 *\/\n    \/\/ input format is NV12\n\n    for (int i = in_pixel_size; i &lt; in_full_size; ++i)\n    {\n        in_map.data[i] = (guint8)(0);\n    }\n\n    \/\/ g_print(&quot;in_size = %d\\n&quot;, in_size);\n\n    gst_buffer_unmap(buf, &amp;in_map);\n\n    return gst_pad_push(filter-&gt;srcpad, buf);\n}\n\n\/**\n * @brief \n * \n * @param pad pad reveive event\n * @param parent \n * @param event to be preocessed\n * @return gboolean \n *\/\nstatic gboolean gst_rgb2grey_filter_event(GstPad* pad, GstObject *parent, GstEvent *event)\n{\n    gboolean ret;\n    GstRGB2GreyFilter *filter = GST_RGB2GREY_FILTER(parent);\n\n    g_print(&quot;event : %s \\n&quot;,GST_EVENT_TYPE_NAME(event));\n\n    switch (GST_EVENT_TYPE(event)) {\n    case GST_EVENT_CAPS:\n        ret = gst_pad_push_event(filter-&gt;srcpad, event); \/* push the event downstream *\/\n        break;\n    case GST_EVENT_EOS:\n        gst_rgb2grey_filter_stop_processing(filter);\n        ret = gst_pad_event_default(pad, parent, event);  \/\/default deal transaction\n        break;\n    default:\n        ret = gst_pad_event_default(pad, parent, event);\n        break;\n    }\n\n    return ret;\n}\n\nstatic gboolean gst_rgb2grey_filter_src_query(GstPad* pad, GstObject* parent, GstQuery* query)\n{\n    gboolean ret;\n    GstRGB2GreyFilter *filter = GST_RGB2GREY_FILTER(parent);\n\n    switch (GST_QUERY_TYPE(query)) {\n    case GST_QUERY_POSITION:\n        \/* we should report the current position *\/\n        break;\n    case GST_QUERY_DURATION:\n        \/* we should report the duration here *\/\n        break;\n    case GST_QUERY_CAPS:\n        \/* we should report the supported caps here *\/\n        break;\n    default:\n        \/* just call the default handler *\/\n        ret = gst_pad_query_default(pad, parent, query);\n        break;\n    }\n  return ret;\n}\n\nstatic GstStateChangeReturn gst_rgb2grey_filter_change_state(GstElement *element, GstStateChange transition)\n{\n    GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;\n    GstRGB2GreyFilter *filter = GST_RGB2GREY_FILTER(element);\n\n    \/\/process upwards state change\n    switch (transition) {\n    case GST_STATE_CHANGE_NULL_TO_READY:\n        if (!gst_rgb2grey_filter_allocate_memory(filter))  \/\/require for resource, memory \/ libs \/ ... are included\n        return GST_STATE_CHANGE_FAILURE;\n        break;\n    case GST_STATE_CHANGE_READY_TO_PAUSED:\n        \/\/dosomething\n        break;\n    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:\n        \/\/dosomething\n        break;\n    default:\n        break;\n    }\n\n    \/\/call state changed Func of parent class.\n    ret = GST_ELEMENT_CLASS (gst_rgb2grey_filter_parent_class)-&gt;change_state (element, transition);\n    if (ret == GST_STATE_CHANGE_FAILURE) return ret;\n\n    \/\/process downwards state change\n    switch (transition) {\n    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:\n        \/\/dosomething\n        break;\n    case GST_STATE_CHANGE_PAUSED_TO_READY:\n        \/\/dosomething\n        break;\n    case GST_STATE_CHANGE_READY_TO_NULL:\n        gst_rgb2grey_filter_free_memory(filter);\n        break;\n    default:\n        break;\n    }\n\n  return ret;\n}\n\nstatic void gst_rgb2grey_filter_set_property(GObject* object, guint prop_id, const GValue * value, GParamSpec *pspec)\n{\n    GstRGB2GreyFilter* filter = GST_RGB2GREY_FILTER(object);\n\n    switch (prop_id) {\n    case PROP_WIDTH:\n        filter-&gt;width = g_value_get_uint(value);\n        break;\n    case PROP_HEIGHT:\n        filter-&gt;height = g_value_get_uint(value);\n        break;\n    default:\n        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);\n        break;\n    }\n}\n\nstatic void gst_rgb2grey_filter_get_property(GObject* object, guint prop_id, GValue * value, GParamSpec *pspec)\n{\n    GstRGB2GreyFilter *filter = GST_RGB2GREY_FILTER(object);\n\n    switch (prop_id) {\n    \/\/ do sth\n    default:\n        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);\n        break;\n    }\n}\n\nstatic void gst_rgb2grey_filter_class_init(GstRGB2GreyFilterClass *klass)\n{\n    g_print(&quot;gst_rgb2grey_filter_class_init\\n&quot;);\n    GstElementClass *element_class = GST_ELEMENT_CLASS(klass);  \/\/ for others\n    GObjectClass *object_class = G_OBJECT_CLASS(klass);     \/\/ for property setter and getter\n\n    \/\/ meta data\n    gst_element_class_set_static_metadata(element_class,\n    &quot;[meta data]Filter Demo of rgb2grey Plugin&quot;,\n    &quot;[meta data]rgb2grey\/Filter Demo&quot;,\n    &quot;[meta data]Shows the basic structure of a plugin&quot;,\n    &quot;[meta data]xjt&quot;);\n\n    \/\/ register state change funcution\n    element_class-&gt;change_state = gst_rgb2grey_filter_change_state;\n\n    \/\/ register property setter and getter\n    object_class-&gt;set_property = gst_rgb2grey_filter_set_property;\n    object_class-&gt;get_property = gst_rgb2grey_filter_get_property;\n\n    \/\/ register pads\n    gst_element_class_add_pad_template(element_class,\n        gst_static_pad_template_get(&amp;src_factory));\n    gst_element_class_add_pad_template(element_class,\n        gst_static_pad_template_get(&amp;sink_factory));\n\n    \/\/ width and height\n    GParamSpec *param_spec;\n    param_spec = g_param_spec_uint(\n        &quot;width&quot;, &quot;Width&quot;, &quot;The width of the input video&quot;, \n        0, G_MAXUINT, 0, \n        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS\n    );\n    g_object_class_install_property(object_class, PROP_WIDTH, param_spec);\n\n    param_spec = g_param_spec_uint(\n        &quot;height&quot;, &quot;Height&quot;, \n        &quot;The height of the input video&quot;, \n        0, G_MAXUINT, 0, \n        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS\n    );\n    g_object_class_install_property(object_class, PROP_HEIGHT, param_spec);\n}\n\nstatic void gst_rgb2grey_filter_init(GstRGB2GreyFilter *filter)\n{\n    g_print(&quot;gst_rgb2grey_filter_init\\n&quot;);\n    \/\/instantiates and assigns pads\n    filter-&gt;srcpad = gst_pad_new_from_static_template(&amp;src_factory, &quot;src&quot;);\n    filter-&gt;sinkpad = gst_pad_new_from_static_template(&amp;sink_factory, &quot;sink&quot;);\n\n    \/\/add pads to element\n    gst_element_add_pad(GST_ELEMENT(filter), filter-&gt;srcpad);\n    gst_element_add_pad(GST_ELEMENT(filter), filter-&gt;sinkpad);\n\n    \/\/set chain function for sink pad\n    gst_pad_set_chain_function(filter-&gt;sinkpad, gst_rgb2grey_filter_chain);\n\n    \/\/set event function\n    gst_pad_set_event_function(filter-&gt;sinkpad, gst_rgb2grey_filter_event);\n\n    \/\/set query function\n    gst_pad_set_query_function(filter-&gt;srcpad, gst_rgb2grey_filter_src_query);\n}\n\nstatic gboolean plugin_init(GstPlugin* plugin)\n{\n    g_print(&quot;rgb2grey_plugin_init\\n&quot;);\n    gboolean ret;\n\n    \/\/register plugin feature\n    \/\/[todo]\n\n    \/\/register element into plugin\n    ret = gst_element_register(plugin, &quot;rgb2grey&quot;, GST_RANK_MARGINAL, GST_TYPE_RGB2GREY_FILTER);\n\n    return ret;\n}\n\nGST_PLUGIN_DEFINE(\n    GST_VERSION_MAJOR,\n    GST_VERSION_MINOR,\n    rgb2grey,\n    &quot;rgb2grey filter plugin&quot;,\n    plugin_init,\n    VERSION,\n    &quot;LGPL&quot;,\n    &quot;LocalPlugin&quot;,\n    &quot;n\/a&quot;\n)\n\n\/**\n * ========================\n * @par Local Funcs [START]\n * ========================\n *\/\n\nstatic GstBuffer* gst_rgb2grey_filter_process_data(GstRGB2GreyFilter* filter, GstBuffer* buf)\n{\n    return buf;\n}\n\nstatic gboolean gst_rgb2grey_filter_stop_processing(GstRGB2GreyFilter* filter)\n{\n    return TRUE;\n}\n\nstatic gboolean gst_rgb2grey_filter_allocate_memory(GstRGB2GreyFilter* filter)\n{\n    return TRUE;\n}\n\nstatic gboolean gst_rgb2grey_filter_free_memory(GstRGB2GreyFilter* filter)\n{\n    return TRUE;\n}<\/code><\/pre>\n<h3>\u4e09\u3001\u4f7f\u7528<\/h3>\n<p>&emsp;&emsp;\u8fd9\u91cc\u6211\u7684\u63d2\u4ef6\u540d\u79f0\u4e3argb2grey\uff0c\u901a\u8fc7cmake\u521b\u5efa\u4e00\u4e2a\u52a8\u6001\u5e93\u3002<\/p>\n<pre><code class=\"language-cmake\">ADD_LIBRARY(rgb2grey SHARED rgb2grey.c rgb2grey.h)<\/code><\/pre>\n<p>\u7f16\u8bd1\u597d\u4e4b\u540e\u6211\u4eec\u5c06\u6587\u4ef6\u4f20\u5165<code>\/lib\/<\/code>\u6216\u8005<code>usr\/lib\/gstreamer-1.0\/<\/code>\u4e2d\uff0c\u4e4b\u540e\u5c31\u53ef\u4ee5\u5728\u4e3b\u7a0b\u5e8f\u4e2d\u8c03\u7528<code>gst_element_factory_make<\/code>\u6765\u521b\u5efa\u5143\u7d20\u4e86\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u4e00\u3001\u7b80\u8ff0 &emsp;&emsp;\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u5982\u4e0b\u642d\u5efa\u4e00\u6761\u7ba1\u7ebf\uff1a gst_bin_add_many(GST_BIN(pipel &#8230;<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[54],"tags":[239],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/swordofmorning.com\/index.php\/wp-json\/wp\/v2\/posts\/938"}],"collection":[{"href":"https:\/\/swordofmorning.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/swordofmorning.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/swordofmorning.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/swordofmorning.com\/index.php\/wp-json\/wp\/v2\/comments?post=938"}],"version-history":[{"count":3,"href":"https:\/\/swordofmorning.com\/index.php\/wp-json\/wp\/v2\/posts\/938\/revisions"}],"predecessor-version":[{"id":941,"href":"https:\/\/swordofmorning.com\/index.php\/wp-json\/wp\/v2\/posts\/938\/revisions\/941"}],"wp:attachment":[{"href":"https:\/\/swordofmorning.com\/index.php\/wp-json\/wp\/v2\/media?parent=938"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/swordofmorning.com\/index.php\/wp-json\/wp\/v2\/categories?post=938"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/swordofmorning.com\/index.php\/wp-json\/wp\/v2\/tags?post=938"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}