[
{
"attrs": {},
"bound": "full",
"children": [
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "ffmpeg GIF Maker Command (2023 Edition)",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
}
],
"end_section": null,
"flags": [],
"kind": "title",
"template_list": [
"sections/title/default.neoj",
"sections/basic/default.neoj"
],
"type": "basic"
},
{
"attrs": {},
"bound": "full",
"children": [
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "Introduction",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
},
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "I love making GIFs. It\u0027s one of my go-to hobbies when I\u0027ve got enough energy to do ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "emphasis-shorthand",
"text": "something",
"type": "emphasis-shorthand"
},
{
"kind": "plain-text",
"text": " but don\u0027t want to code or do anything that requires much thinking.",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
},
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "My first step is to use DaVinci Resolve",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "footnote-shorthand",
"text": "1",
"type": "footnote-shorthand"
},
{
"kind": "plain-text",
"text": " to create an mp4 that\u0027s clipped and cropped the way I want. Once that\u0027s done, I set the options in this script and run it to produce the GIF:",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
}
],
"end_section": null,
"flags": [],
"kind": "h2",
"template_list": [
"sections/h2/default.neoj",
"sections/basic/default.neoj"
],
"type": "basic"
},
{
"attrs": {},
"body": "#!/bin/bash\n\n################################################\n# The Options \n\n\n################################################\n# The input video file to use. I generally use\n# the full path to the input file. Relative\n# paths also work (e.g. just \"hackers.mp4\"\n# if you\u0027re running the script from the same\n# directory the .mp4 file is in)\n\nINPUT_PATH=\"/Users/alan/Desktop/input.mp4\"\n\n\n################################################\n# The GIF file to output to\n\nOUTPUT_PATH=\"/Users/alan/Desktop/output.gif\"\n\n\n################################################\n# How wide to make the GIF. (The height is\n# set automatically in the command itself)\n\nWIDTH=400\n\n\n################################################\n# How many Frames Per Second the GIF should be.\n# I find 10-15 works great for most GIFs. The\n# higher the number the bigger the GIF will \n# be though so it\u0027s something to pay \n# attention to\n\nFRAMES_PER_SECOND=20\n\n\n################################################\n# How many colors to use in the color palette.\n# The max number is 255. With the other options\n# that are set in this script I rarely find\n# it necessary to go that high to get a good\n# looking GIF. As with FRAMES_PER_SECOND\n# the high the number the bigger the GIF\u0027s\n# file size will be. \n\nCOLORS=70\n\n\n################################################\n# Optional time to start from in the input video. \n# I generally leave this blank since I\u0027ve already \n# clipped the input video to where I want it, \n# but you can use something like:\n#\n# START_TIME=\"-ss 00:01:53.6\"\n#\n# The format for the time is: \n#\n# HOURS:MINUTES:SECONDS.SUBSECOND\n#\n# So, the above example will start\n# at 1 minute 53.6 seconds into the video\n# \n# Note that the GIF may not start at the exact\n# time you specify because of the way keyframes\n# work in videos. If you run into that you can \n# cut your input video to exactly what you want. \n# (There are ways to deal with keyframe offsets\n# directly in ffmpeg, but that\u0027s not in scope\n# for this script)\n\nSTART_TIME=\"\"\n\n\n################################################\n# Optional length for the GIF. I generally leave\n# this blank since I\u0027ve already cut my clips\n# in DaVinci, but you can do something like \n# this:\n#\n# LENGTH=\"-t 2.3\"\n# \n# which will make a 2.3 second GIF. As with \n# START_TIME the length may not be exact because\n# of the way video keyframes work. \n\nLENGTH=\"\"\n\n\n################################################\n# Optional offset for cropdetect to cover videos \n# that start with totally black frames. I leave \n# it empty most of the time since I\u0027ve done my\n# clipping in DaVinci. To use it, change it\n# to something like: \n#\n# CROP_OFFSET=\"-ss 0.2\"\n#\n# That will skip 0.2 seconds of the clip before \n# starting to detect the cropping. Just make \n# sure to keep the offset less than the total \n# length of your clip otherwise it\u0027ll miss the \n# cropping\n\nCROP_OFFSET=\"\"\n\n\n################################################\n# The \"stat_mode\" to use in \"palettegen\". \n# Options are \"full\" and \"diff\". Where \"full\" \n# optimizes for the overall scene and \"diff\" \n# optimizes for just what\u0027s moving. (I don\u0027t\n# generally see much of a difference with \n# this one, but keep it around just in case)\n\nSTAT_MODE=\"diff\"\n\n\n################################################\n# The scaler to use. The default is \"bilinear\" \n# but \"lanczos\" and \"bicubic\" generally produce\n# better results.\n\nSCALER=\"lanczos\"\n\n\n################################################\n# The dithering option to use. I generally find \n# \"sierra2_4a\" (which is also the default) to be \n# a good balance of quality and output size. The \n# other options to explore are:\n#\n# \"bayer:bayer_scale=0\"\n# \"bayer:bayer_scale=1\"\n# \"bayer:bayer_scale=2\"\n# \"bayer:bayer_scale=3\"\n# \"bayer:bayer_scale=4\"\n# \"bayer:bayer_scale=5\"\n# \"floyd_steinberg\"\n# \"sierra2\"\n# \"sierra2_4a\"\n# \"sierra3\"\n# \"burkes\"\n# \"atkinson\"\n# \"none\"\n\nDITHER=\"sierra2_4a\"\n\n\n################################################\n# The \"diff_mode\" defines the zone to \n# process for parts of the scene that \n# move. The default is none, but setting\n# to \"rectangle\" can help keep backgrounds\n# from changing as the color palette changes\n# during processing. Change to:\n# \n# DIFF_MODE=\"none\"\n#\n# if you want to turn it off. Setting it\n# either way might not have much of an effect\n# depending on the input video\n\nDIFF_MODE=\"rectangle\"\n\n\n################################################\n# The Command Itself\n################################################\n# This is what collections all the options\n# from above and actually makes the GIF\n################################################\n\nffmpeg -i \"${INPUT_PATH}\" ${CROP_OFFSET} \\\n-t 0.1 -vf cropdetect -f null - 2\u003e\u00261 | \\\nawk \u0027/crop/ { print $NF }\u0027 | \\\ntail -1 | xargs -I{CROP} \\\nffmpeg -loglevel error -hide_banner \\\n-i \"${INPUT_PATH}\" $START_TIME $LENGTH \\\n-vf \"{CROP},fps=${FRAMES_PER_SECOND},\\\nscale=${WIDTH}:-2:flags=${SCALER},split[s0][s1];\\\n[s0]palettegen=max_colors=${COLORS}:reserve_transparent=0[p];\\\n[s1][p]paletteuse=dither=${DITHER}:diff_mode=${DIFF_MODE}\" \\\n-y \"${OUTPUT_PATH}\"\n\nopen ${OUTPUT_PATH}",
"bound": "full",
"end_section": null,
"flags": [
"bash"
],
"kind": "code",
"template_list": [
"sections/code/default.neoj",
"sections/raw/default.neoj"
],
"type": "raw"
},
{
"attrs": {},
"body": "",
"bound": "start",
"end_section": {
"attrs": {},
"bound": "end",
"children": [],
"flags": [],
"kind": "results-end",
"template_list": [
"sections/results-end/default.neoj",
"sections/end/default.neoj"
],
"type": "end"
},
"flags": [],
"kind": "results",
"template_list": [
"sections/results/default.neoj",
"sections/raw/default.neoj"
],
"type": "raw"
},
{
"attrs": {},
"body": "",
"bound": "start",
"end_section": {
"attrs": {},
"bound": "end",
"children": [],
"flags": [],
"kind": "results-end",
"template_list": [
"sections/results-end/default.neoj",
"sections/end/default.neoj"
],
"type": "end"
},
"flags": [],
"kind": "results",
"template_list": [
"sections/results/default.neoj",
"sections/raw/default.neoj"
],
"type": "raw"
},
{
"attrs": {},
"bound": "full",
"children": [
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "The script uses ffmpeg",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "footnote-shorthand",
"text": "2",
"type": "footnote-shorthand"
},
{
"kind": "plain-text",
"text": " which you\u0027ll need to have installed. This version runs on my mac and will probably work on must linux distros. I\u0027m not sure what would need to be tweaked to run on windows. The first four lines (which remove any black bars from cropping) would likely need to be redone somehow.",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
},
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "Choosing what settings to use for the various options generally comes down to \"it depends\" based on the input video you\u0027re working with. The settings above are my defaults that work fine for me most of the time.",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
},
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "The three things I\u0027m most likely to change are the WIDTH, FRAMES_PER_SECOND, and COLORS in order to balance the quality with the file size.",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
}
],
"end_section": null,
"flags": [],
"kind": "p",
"template_list": [
"sections/p/default.neoj",
"sections/basic/default.neoj"
],
"type": "basic"
},
{
"attrs": {},
"bound": "full",
"children": [
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "Some Details",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
}
],
"end_section": null,
"flags": [],
"kind": "h2",
"template_list": [
"sections/h2/default.neoj",
"sections/basic/default.neoj"
],
"type": "basic"
},
{
"attrs": {},
"bound": "start",
"children": [
{
"bound": "full",
"children": [
{
"children": [],
"kind": "notes-item-paragraph",
"spans": [
{
"kind": "plain-text",
"text": "First off, to give very deserving credit where it\u0027s due, I learned most of this from this post: ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [
"https://blog.pkh.me/p/21-high-quality-gif-with-ffmpeg.html"
],
"kind": "link-shorthand",
"text": "High Quality GIFs with FFMpeg",
"type": "link-shorthand"
},
{
"kind": "plain-text",
"text": " cross-referenced and supplemented with diving into ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [
"https://ffmpeg.org/documentation.html"
],
"kind": "link-shorthand",
"text": "the ffmpeg docs",
"type": "link-shorthand"
},
{
"kind": "plain-text",
"text": " which have all the detail you could want but are pretty cryptic when you first start with them",
"type": "plain-text"
}
],
"template_list": [
"sections/notes-item-paragraph/default.neoj",
"sections/list-item-paragraph/default.neoj"
],
"type": "block"
}
],
"kind": "notes-item",
"template_list": [
"sections/notes-item/default.neoj",
"sections/list-item/default.neoj"
],
"type": "listitem"
},
{
"bound": "full",
"children": [
{
"children": [],
"kind": "notes-item-paragraph",
"spans": [
{
"kind": "plain-text",
"text": "I usually end up cropping the input videos in DaVinci which leaves black bars around them. For example:",
"type": "plain-text"
}
],
"template_list": [
"sections/notes-item-paragraph/default.neoj",
"sections/list-item-paragraph/default.neoj"
],
"type": "block"
},
{
"attrs": {},
"body": "\u003cimg \n width=\"400\" height=\"226\"\n src=\"https://res.cloudinary.com/awsimages/image/upload/hackers-uncropped.gif\" /\u003e",
"bound": "start",
"end_section": {
"attrs": {},
"bound": "end",
"children": [],
"flags": [],
"kind": "html-end",
"template_list": [
"sections/html-end/default.neoj",
"sections/end/default.neoj"
],
"type": "end"
},
"flags": [],
"kind": "html",
"template_list": [
"sections/html/default.neoj"
],
"type": "html"
}
],
"kind": "notes-item",
"template_list": [
"sections/notes-item/default.neoj",
"sections/list-item/default.neoj"
],
"type": "listitem"
},
{
"bound": "full",
"children": [
{
"children": [],
"kind": "notes-item-paragraph",
"spans": [
{
"kind": "plain-text",
"text": "The first four lines of the script (which include the line with ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "code-shorthand",
"text": "cropdetect",
"type": "code-shorthand"
},
{
"kind": "plain-text",
"text": ") generate a string with cropping parameters to remove the black bars that\u0027s feed back into ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "code-shorthand",
"text": "ffmpeg",
"type": "code-shorthand"
},
{
"kind": "plain-text",
"text": " starting on the fifth line. The result is that the above turns into this:",
"type": "plain-text"
}
],
"template_list": [
"sections/notes-item-paragraph/default.neoj",
"sections/list-item-paragraph/default.neoj"
],
"type": "block"
},
{
"attrs": {},
"body": "\u003cimg\n width=\"400\"\n height=\"370\" \n src=\"https://res.cloudinary.com/awsimages/image/upload/v1731479722/hackers-cropped.gif\" /\u003e",
"bound": "start",
"end_section": {
"attrs": {},
"bound": "end",
"children": [],
"flags": [],
"kind": "html-end",
"template_list": [
"sections/html-end/default.neoj",
"sections/end/default.neoj"
],
"type": "end"
},
"flags": [],
"kind": "html",
"template_list": [
"sections/html/default.neoj"
],
"type": "html"
}
],
"kind": "notes-item",
"template_list": [
"sections/notes-item/default.neoj",
"sections/list-item/default.neoj"
],
"type": "listitem"
},
{
"bound": "full",
"children": [
{
"children": [],
"kind": "notes-item-paragraph",
"spans": [
{
"kind": "plain-text",
"text": "The ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "code-shorthand",
"text": "-loglevel error -hide_banner",
"type": "code-shorthand"
},
{
"kind": "plain-text",
"text": " settings turn off the command line status message output unless there\u0027s an error. I do this because I use this script inside other processes as well and don\u0027t want the noise. Remove them if you want to see the details of what the process is doing",
"type": "plain-text"
}
],
"template_list": [
"sections/notes-item-paragraph/default.neoj",
"sections/list-item-paragraph/default.neoj"
],
"type": "block"
}
],
"kind": "notes-item",
"template_list": [
"sections/notes-item/default.neoj",
"sections/list-item/default.neoj"
],
"type": "listitem"
},
{
"bound": "full",
"children": [
{
"children": [],
"kind": "notes-item-paragraph",
"spans": [
{
"kind": "plain-text",
"text": "The ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "code-shorthand",
"text": "-2",
"type": "code-shorthand"
},
{
"kind": "plain-text",
"text": " at the end of ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "code-shorthand",
"text": "scale=${WIDTH}:-2",
"type": "code-shorthand"
},
{
"kind": "plain-text",
"text": " sets up the height of the GIF based on the input aspect ratio while making sure it\u0027s an even number of pixels. You can also use ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "code-shorthand",
"text": "-1",
"type": "code-shorthand"
},
{
"kind": "plain-text",
"text": " which could result in and odd number of pixels. Probably doesn\u0027t really matter, but I like keeping it even",
"type": "plain-text"
}
],
"template_list": [
"sections/notes-item-paragraph/default.neoj",
"sections/list-item-paragraph/default.neoj"
],
"type": "block"
}
],
"kind": "notes-item",
"template_list": [
"sections/notes-item/default.neoj",
"sections/list-item/default.neoj"
],
"type": "listitem"
},
{
"bound": "full",
"children": [
{
"children": [],
"kind": "notes-item-paragraph",
"spans": [
{
"kind": "plain-text",
"text": "The ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "code-shorthand",
"text": "reserve_transparent=0",
"type": "code-shorthand"
},
{
"kind": "plain-text",
"text": " means the GIF won\u0027t use up a color for transparency. Leave this at ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "code-shorthand",
"text": "0",
"type": "code-shorthand"
},
{
"kind": "plain-text",
"text": " if your GIF doesn\u0027t have transparency. Otherwise, set it to ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "code-shorthand",
"text": "1",
"type": "code-shorthand"
}
],
"template_list": [
"sections/notes-item-paragraph/default.neoj",
"sections/list-item-paragraph/default.neoj"
],
"type": "block"
}
],
"kind": "notes-item",
"template_list": [
"sections/notes-item/default.neoj",
"sections/list-item/default.neoj"
],
"type": "listitem"
},
{
"bound": "full",
"children": [
{
"children": [],
"kind": "notes-item-paragraph",
"spans": [
{
"kind": "plain-text",
"text": "The ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "code-shorthand",
"text": "-y",
"type": "code-shorthand"
},
{
"kind": "plain-text",
"text": " option tells ffmpeg to overwrite the output file if it already exist (which is nice becuase it makes it easier to play with settings)",
"type": "plain-text"
}
],
"template_list": [
"sections/notes-item-paragraph/default.neoj",
"sections/list-item-paragraph/default.neoj"
],
"type": "block"
}
],
"kind": "notes-item",
"template_list": [
"sections/notes-item/default.neoj",
"sections/list-item/default.neoj"
],
"type": "listitem"
},
{
"bound": "full",
"children": [
{
"children": [],
"kind": "notes-item-paragraph",
"spans": [
{
"kind": "plain-text",
"text": "The last line with the ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "code-shorthand",
"text": "open",
"type": "code-shorthand"
},
{
"kind": "plain-text",
"text": " command opens the GIF when it\u0027s done in whatever app is set to deal with them. That\u0027s chrome on my machine. Fair warning that if you run the script multiple times it\u0027s not smart enough to use the same tab so you end up with a bunch of tabs showing the GIF",
"type": "plain-text"
}
],
"template_list": [
"sections/notes-item-paragraph/default.neoj",
"sections/list-item-paragraph/default.neoj"
],
"type": "block"
}
],
"kind": "notes-item",
"template_list": [
"sections/notes-item/default.neoj",
"sections/list-item/default.neoj"
],
"type": "listitem"
}
],
"end_section": {
"attrs": {},
"bound": "end",
"children": [],
"flags": [],
"kind": "notes-end",
"template_list": [
"sections/notes-end/default.neoj",
"sections/end/default.neoj"
],
"type": "end"
},
"flags": [],
"kind": "notes",
"template_list": [
"sections/notes/default.neoj",
"sections/list/default.neoj"
],
"type": "list"
},
{
"attrs": {},
"bound": "full",
"children": [
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "Go Forth And Animate",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
},
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "A bunch of hours of research went into that command. For any given GIF it would be way easier to use one of the many online GIF makers. Except, most of those feel shady and gross with the number of ads on them. Plus, I like making things myself and with this script I can automate things so GIFs are made as soon as I export an mp4 from DaVinci.",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
},
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "That\u0027s a topic for a future post though. For now, running this when I need it works just fine.",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
}
],
"end_section": null,
"flags": [],
"kind": "h2",
"template_list": [
"sections/h2/default.neoj",
"sections/basic/default.neoj"
],
"type": "basic"
},
{
"attrs": {
"id": [
{
"spans": [
{
"kind": "plain-text",
"text": "1",
"type": "plain-text"
}
],
"text": "1"
}
],
"title": [
{
"spans": [
{
"kind": "plain-text",
"text": "DaVinci Resolve",
"type": "plain-text"
}
],
"text": "DaVinci Resolve"
}
],
"url": [
{
"spans": [
{
"kind": "plain-text",
"text": "https://www.blackmagicdesign.com/products/davinciresolve",
"type": "plain-text"
}
],
"text": "https://www.blackmagicdesign.com/products/davinciresolve"
}
]
},
"bound": "full",
"children": [
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "This is one of those apps that I can\u0027t believe is free. It\u0027s serious prop level stuff. It\u0027s way more than what I need. Took a little youtubing to figure out how to do stuff. Totally worth the time spent.",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
}
],
"end_section": null,
"flags": [],
"kind": "footnote",
"template_list": [
"sections/footnote/default.neoj",
"sections/basic/default.neoj"
],
"type": "basic"
},
{
"attrs": {
"id": [
{
"spans": [
{
"kind": "plain-text",
"text": "2",
"type": "plain-text"
}
],
"text": "2"
}
],
"title": [
{
"spans": [
{
"kind": "plain-text",
"text": "ffmpeg",
"type": "plain-text"
}
],
"text": "ffmpeg"
}
],
"url": [
{
"spans": [
{
"kind": "plain-text",
"text": "https://ffmpeg.org/",
"type": "plain-text"
}
],
"text": "https://ffmpeg.org/"
}
]
},
"bound": "full",
"children": [
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "\"A complete, cross-platform solution to record, convert and stream audio and video.\"",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
},
{
"children": [],
"kind": "paragraph",
"spans": [
{
"kind": "plain-text",
"text": "This is ",
"type": "plain-text"
},
{
"attrs": {},
"flags": [],
"kind": "emphasis-shorthand",
"text": "the",
"type": "emphasis-shorthand"
},
{
"kind": "plain-text",
"text": " command line tool for messing with video. Also free, but way more cryptic to figure out.",
"type": "plain-text"
}
],
"template_list": [
"sections/paragraph/default.neoj"
],
"type": "block"
}
],
"end_section": null,
"flags": [],
"kind": "footnote",
"template_list": [
"sections/footnote/default.neoj",
"sections/basic/default.neoj"
],
"type": "basic"
},
{
"attrs": {
"created": [
{
"spans": [
{
"kind": "plain-text",
"text": "2023-10-26T17:11:42-04:00",
"type": "plain-text"
}
],
"text": "2023-10-26T17:11:42-04:00"
}
],
"id": [
{
"spans": [
{
"kind": "plain-text",
"text": "2x/jk/jc/yl",
"type": "plain-text"
}
],
"text": "2x/jk/jc/yl"
}
],
"status": [
{
"spans": [
{
"kind": "plain-text",
"text": "done",
"type": "plain-text"
}
],
"text": "done"
}
],
"tag": [
{
"spans": [
{
"kind": "plain-text",
"text": "ffmpeg",
"type": "plain-text"
}
],
"text": "ffmpeg"
}
],
"type": [
{
"spans": [
{
"kind": "plain-text",
"text": "post",
"type": "plain-text"
}
],
"text": "post"
}
]
},
"bound": "full",
"data": {},
"end_section": null,
"error": null,
"flags": [],
"kind": "page",
"template_list": [
"sections/page/default.neoj",
"sections/json5/default.neoj"
],
"type": "json5"
}
]