Last active
July 4, 2019 14:49
-
-
Save artificialsoph/16fca07a56c7d902eda00e67ab37efea to your computer and use it in GitHub Desktop.
Gif demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "nbformat": 4, | |
| "nbformat_minor": 0, | |
| "metadata": { | |
| "colab": { | |
| "name": "Gif demo", | |
| "version": "0.3.2", | |
| "provenance": [], | |
| "collapsed_sections": [], | |
| "toc_visible": true, | |
| "include_colab_link": true | |
| }, | |
| "kernelspec": { | |
| "name": "python3", | |
| "display_name": "Python 3" | |
| } | |
| }, | |
| "cells": [ | |
| { | |
| "cell_type": "markdown", | |
| "metadata": { | |
| "id": "view-in-github", | |
| "colab_type": "text" | |
| }, | |
| "source": [ | |
| "<a href=\"https://colab.research.google.com/gist/artificialsoph/16fca07a56c7d902eda00e67ab37efea/gif-demo.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "ESWiNLw4G8V9", | |
| "colab_type": "text" | |
| }, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "# Making a gif from a youtube video with `moviepy`\n", | |
| "\n", | |
| "I'm going to put together something like a tutorial for a bunch of video processing tools:\n", | |
| "- automating the downloading of youtube videos using `youtube-dl`\n", | |
| "- video loading, rendering, and processing in jupyter notebooks via `moviepy`\n", | |
| "- adding captions to videos with `imagemagick`\n", | |
| "- using `ffmpeg` to efficiently convert a video to gif\n", | |
| "\n", | |
| "For my example, I'm going to make a gif of Mitski's \"I Don’t Smoke\". It's a dear song to me about addictive substances and people. Mitski hasn't recorded a video of the song but it is part of this well recorded [2017 Boston Calling Set](https://www.youtube.com/watch?v=_cz-wwxmmto) on youtube. Thing is, that video is quite long and too big for many of the \"automatically convert a youtube video to a gif\" tools you find online. Also those tools don't give you the ability to customize them. \n", | |
| "\n", | |
| "Big shoutout to [Zuklo](https://github.com/Zulko) for this [blog post](https://zulko.github.io/blog/2014/01/23/making-animated-gifs-from-video-files-with-python/) that was the basis for much of this notebook.\n", | |
| "\n", | |
| "\n", | |
| "<small> Aside: this demo is for educational purposes only. All *code* here is under the [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/) and I make no claim on the particular video content I'm using for demonstration.</small>\n", | |
| "\n", | |
| "\n", | |
| "\n", | |
| "<a rel=\"license\" href=\"http://creativecommons.org/licenses/by/4.0/\"><img alt=\"Creative Commons License\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by/4.0/80x15.png\" /></a>" | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "TNoyW7Mi3X_r", | |
| "colab_type": "text" | |
| }, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Before we get started, we'll need to install imagemagick. If we do this after we import things, it causes problems." | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "k5Yup9gOti_F", | |
| "colab_type": "code", | |
| "outputId": "70312ff6-4f16-4b3c-8be6-97df53debcdc", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/", | |
| "height": 2958 | |
| } | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "!apt install imagemagick -q" | |
| ], | |
| "execution_count": 1, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "text": [ | |
| "Reading package lists...\n", | |
| "Building dependency tree...\n", | |
| "Reading state information...\n", | |
| "The following package was automatically installed and is no longer required:\n", | |
| " libnvidia-common-410\n", | |
| "Use 'apt autoremove' to remove it.\n", | |
| "The following additional packages will be installed:\n", | |
| " fonts-droid-fallback fonts-noto-mono ghostscript gsfonts\n", | |
| " imagemagick-6-common imagemagick-6.q16 libcupsfilters1 libcupsimage2\n", | |
| " libdjvulibre-text libdjvulibre21 libgs9 libgs9-common libijs-0.35\n", | |
| " libjbig2dec0 liblqr-1-0 libmagickcore-6.q16-3 libmagickcore-6.q16-3-extra\n", | |
| " libmagickwand-6.q16-3 libnetpbm10 libwmf0.2-7 netpbm poppler-data\n", | |
| "Suggested packages:\n", | |
| " fonts-noto ghostscript-x imagemagick-doc autotrace cups-bsd | lpr | lprng\n", | |
| " enscript gimp gnuplot grads hp2xx html2ps libwmf-bin mplayer povray radiance\n", | |
| " sane-utils texlive-base-bin transfig ufraw-batch inkscape libjxr-tools\n", | |
| " libwmf0.2-7-gtk poppler-utils fonts-japanese-mincho | fonts-ipafont-mincho\n", | |
| " fonts-japanese-gothic | fonts-ipafont-gothic fonts-arphic-ukai\n", | |
| " fonts-arphic-uming fonts-nanum\n", | |
| "The following NEW packages will be installed:\n", | |
| " fonts-droid-fallback fonts-noto-mono ghostscript gsfonts imagemagick\n", | |
| " imagemagick-6-common imagemagick-6.q16 libcupsfilters1 libcupsimage2\n", | |
| " libdjvulibre-text libdjvulibre21 libgs9 libgs9-common libijs-0.35\n", | |
| " libjbig2dec0 liblqr-1-0 libmagickcore-6.q16-3 libmagickcore-6.q16-3-extra\n", | |
| " libmagickwand-6.q16-3 libnetpbm10 libwmf0.2-7 netpbm poppler-data\n", | |
| "0 upgraded, 23 newly installed, 0 to remove and 6 not upgraded.\n", | |
| "Need to get 18.4 MB of archives.\n", | |
| "After this operation, 66.2 MB of additional disk space will be used.\n", | |
| "Get:1 http://archive.ubuntu.com/ubuntu bionic/main amd64 fonts-droid-fallback all 1:6.0.1r16-1.1 [1,805 kB]\n", | |
| "Get:2 http://archive.ubuntu.com/ubuntu bionic/main amd64 liblqr-1-0 amd64 0.4.2-2.1 [27.7 kB]\n", | |
| "Get:3 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 imagemagick-6-common all 8:6.9.7.4+dfsg-16ubuntu6.4 [60.9 kB]\n", | |
| "Get:4 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libmagickcore-6.q16-3 amd64 8:6.9.7.4+dfsg-16ubuntu6.4 [1,616 kB]\n", | |
| "Get:5 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libmagickwand-6.q16-3 amd64 8:6.9.7.4+dfsg-16ubuntu6.4 [293 kB]\n", | |
| "Get:6 http://archive.ubuntu.com/ubuntu bionic/main amd64 poppler-data all 0.4.8-2 [1,479 kB]\n", | |
| "Get:7 http://archive.ubuntu.com/ubuntu bionic/main amd64 fonts-noto-mono all 20171026-2 [75.5 kB]\n", | |
| "Get:8 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libcupsimage2 amd64 2.2.7-1ubuntu2.4 [18.6 kB]\n", | |
| "Get:9 http://archive.ubuntu.com/ubuntu bionic/main amd64 libijs-0.35 amd64 0.35-13 [15.5 kB]\n", | |
| "Get:10 http://archive.ubuntu.com/ubuntu bionic/main amd64 libjbig2dec0 amd64 0.13-6 [55.9 kB]\n", | |
| "Get:11 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libgs9-common all 9.26~dfsg+0-0ubuntu0.18.04.8 [5,092 kB]\n", | |
| "Get:12 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libgs9 amd64 9.26~dfsg+0-0ubuntu0.18.04.8 [2,262 kB]\n", | |
| "Get:13 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 ghostscript amd64 9.26~dfsg+0-0ubuntu0.18.04.8 [51.2 kB]\n", | |
| "Get:14 http://archive.ubuntu.com/ubuntu bionic/main amd64 gsfonts all 1:8.11+urwcyr1.0.7~pre44-4.4 [3,120 kB]\n", | |
| "Get:15 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 imagemagick-6.q16 amd64 8:6.9.7.4+dfsg-16ubuntu6.4 [423 kB]\n", | |
| "Get:16 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 imagemagick amd64 8:6.9.7.4+dfsg-16ubuntu6.4 [14.2 kB]\n", | |
| "Get:17 http://archive.ubuntu.com/ubuntu bionic/main amd64 libcupsfilters1 amd64 1.20.2-0ubuntu3 [108 kB]\n", | |
| "Get:18 http://archive.ubuntu.com/ubuntu bionic/main amd64 libdjvulibre-text all 3.5.27.1-8 [49.1 kB]\n", | |
| "Get:19 http://archive.ubuntu.com/ubuntu bionic/main amd64 libdjvulibre21 amd64 3.5.27.1-8 [559 kB]\n", | |
| "Get:20 http://archive.ubuntu.com/ubuntu bionic/main amd64 libwmf0.2-7 amd64 0.2.8.4-12 [150 kB]\n", | |
| "Get:21 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libmagickcore-6.q16-3-extra amd64 8:6.9.7.4+dfsg-16ubuntu6.4 [62.3 kB]\n", | |
| "Get:22 http://archive.ubuntu.com/ubuntu bionic/main amd64 libnetpbm10 amd64 2:10.0-15.3build1 [58.0 kB]\n", | |
| "Get:23 http://archive.ubuntu.com/ubuntu bionic/main amd64 netpbm amd64 2:10.0-15.3build1 [1,017 kB]\n", | |
| "Fetched 18.4 MB in 2s (8,101 kB/s)\n", | |
| "Selecting previously unselected package fonts-droid-fallback.\n", | |
| "(Reading database ... 131304 files and directories currently installed.)\n", | |
| "Preparing to unpack .../00-fonts-droid-fallback_1%3a6.0.1r16-1.1_all.deb ...\n", | |
| "Unpacking fonts-droid-fallback (1:6.0.1r16-1.1) ...\n", | |
| "Selecting previously unselected package liblqr-1-0:amd64.\n", | |
| "Preparing to unpack .../01-liblqr-1-0_0.4.2-2.1_amd64.deb ...\n", | |
| "Unpacking liblqr-1-0:amd64 (0.4.2-2.1) ...\n", | |
| "Selecting previously unselected package imagemagick-6-common.\n", | |
| "Preparing to unpack .../02-imagemagick-6-common_8%3a6.9.7.4+dfsg-16ubuntu6.4_all.deb ...\n", | |
| "Unpacking imagemagick-6-common (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "Selecting previously unselected package libmagickcore-6.q16-3:amd64.\n", | |
| "Preparing to unpack .../03-libmagickcore-6.q16-3_8%3a6.9.7.4+dfsg-16ubuntu6.4_amd64.deb ...\n", | |
| "Unpacking libmagickcore-6.q16-3:amd64 (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "Selecting previously unselected package libmagickwand-6.q16-3:amd64.\n", | |
| "Preparing to unpack .../04-libmagickwand-6.q16-3_8%3a6.9.7.4+dfsg-16ubuntu6.4_amd64.deb ...\n", | |
| "Unpacking libmagickwand-6.q16-3:amd64 (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "Selecting previously unselected package poppler-data.\n", | |
| "Preparing to unpack .../05-poppler-data_0.4.8-2_all.deb ...\n", | |
| "Unpacking poppler-data (0.4.8-2) ...\n", | |
| "Selecting previously unselected package fonts-noto-mono.\n", | |
| "Preparing to unpack .../06-fonts-noto-mono_20171026-2_all.deb ...\n", | |
| "Unpacking fonts-noto-mono (20171026-2) ...\n", | |
| "Selecting previously unselected package libcupsimage2:amd64.\n", | |
| "Preparing to unpack .../07-libcupsimage2_2.2.7-1ubuntu2.4_amd64.deb ...\n", | |
| "Unpacking libcupsimage2:amd64 (2.2.7-1ubuntu2.4) ...\n", | |
| "Selecting previously unselected package libijs-0.35:amd64.\n", | |
| "Preparing to unpack .../08-libijs-0.35_0.35-13_amd64.deb ...\n", | |
| "Unpacking libijs-0.35:amd64 (0.35-13) ...\n", | |
| "Selecting previously unselected package libjbig2dec0:amd64.\n", | |
| "Preparing to unpack .../09-libjbig2dec0_0.13-6_amd64.deb ...\n", | |
| "Unpacking libjbig2dec0:amd64 (0.13-6) ...\n", | |
| "Selecting previously unselected package libgs9-common.\n", | |
| "Preparing to unpack .../10-libgs9-common_9.26~dfsg+0-0ubuntu0.18.04.8_all.deb ...\n", | |
| "Unpacking libgs9-common (9.26~dfsg+0-0ubuntu0.18.04.8) ...\n", | |
| "Selecting previously unselected package libgs9:amd64.\n", | |
| "Preparing to unpack .../11-libgs9_9.26~dfsg+0-0ubuntu0.18.04.8_amd64.deb ...\n", | |
| "Unpacking libgs9:amd64 (9.26~dfsg+0-0ubuntu0.18.04.8) ...\n", | |
| "Selecting previously unselected package ghostscript.\n", | |
| "Preparing to unpack .../12-ghostscript_9.26~dfsg+0-0ubuntu0.18.04.8_amd64.deb ...\n", | |
| "Unpacking ghostscript (9.26~dfsg+0-0ubuntu0.18.04.8) ...\n", | |
| "Selecting previously unselected package gsfonts.\n", | |
| "Preparing to unpack .../13-gsfonts_1%3a8.11+urwcyr1.0.7~pre44-4.4_all.deb ...\n", | |
| "Unpacking gsfonts (1:8.11+urwcyr1.0.7~pre44-4.4) ...\n", | |
| "Selecting previously unselected package imagemagick-6.q16.\n", | |
| "Preparing to unpack .../14-imagemagick-6.q16_8%3a6.9.7.4+dfsg-16ubuntu6.4_amd64.deb ...\n", | |
| "Unpacking imagemagick-6.q16 (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "Selecting previously unselected package imagemagick.\n", | |
| "Preparing to unpack .../15-imagemagick_8%3a6.9.7.4+dfsg-16ubuntu6.4_amd64.deb ...\n", | |
| "Unpacking imagemagick (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "Selecting previously unselected package libcupsfilters1:amd64.\n", | |
| "Preparing to unpack .../16-libcupsfilters1_1.20.2-0ubuntu3_amd64.deb ...\n", | |
| "Unpacking libcupsfilters1:amd64 (1.20.2-0ubuntu3) ...\n", | |
| "Selecting previously unselected package libdjvulibre-text.\n", | |
| "Preparing to unpack .../17-libdjvulibre-text_3.5.27.1-8_all.deb ...\n", | |
| "Unpacking libdjvulibre-text (3.5.27.1-8) ...\n", | |
| "Selecting previously unselected package libdjvulibre21:amd64.\n", | |
| "Preparing to unpack .../18-libdjvulibre21_3.5.27.1-8_amd64.deb ...\n", | |
| "Unpacking libdjvulibre21:amd64 (3.5.27.1-8) ...\n", | |
| "Selecting previously unselected package libwmf0.2-7:amd64.\n", | |
| "Preparing to unpack .../19-libwmf0.2-7_0.2.8.4-12_amd64.deb ...\n", | |
| "Unpacking libwmf0.2-7:amd64 (0.2.8.4-12) ...\n", | |
| "Selecting previously unselected package libmagickcore-6.q16-3-extra:amd64.\n", | |
| "Preparing to unpack .../20-libmagickcore-6.q16-3-extra_8%3a6.9.7.4+dfsg-16ubuntu6.4_amd64.deb ...\n", | |
| "Unpacking libmagickcore-6.q16-3-extra:amd64 (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "Selecting previously unselected package libnetpbm10.\n", | |
| "Preparing to unpack .../21-libnetpbm10_2%3a10.0-15.3build1_amd64.deb ...\n", | |
| "Unpacking libnetpbm10 (2:10.0-15.3build1) ...\n", | |
| "Selecting previously unselected package netpbm.\n", | |
| "Preparing to unpack .../22-netpbm_2%3a10.0-15.3build1_amd64.deb ...\n", | |
| "Unpacking netpbm (2:10.0-15.3build1) ...\n", | |
| "Setting up libgs9-common (9.26~dfsg+0-0ubuntu0.18.04.8) ...\n", | |
| "Setting up imagemagick-6-common (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "Setting up fonts-droid-fallback (1:6.0.1r16-1.1) ...\n", | |
| "Processing triggers for mime-support (3.60ubuntu1) ...\n", | |
| "Setting up gsfonts (1:8.11+urwcyr1.0.7~pre44-4.4) ...\n", | |
| "Setting up poppler-data (0.4.8-2) ...\n", | |
| "Setting up libdjvulibre-text (3.5.27.1-8) ...\n", | |
| "Setting up libnetpbm10 (2:10.0-15.3build1) ...\n", | |
| "Setting up fonts-noto-mono (20171026-2) ...\n", | |
| "Setting up libcupsfilters1:amd64 (1.20.2-0ubuntu3) ...\n", | |
| "Setting up libcupsimage2:amd64 (2.2.7-1ubuntu2.4) ...\n", | |
| "Setting up liblqr-1-0:amd64 (0.4.2-2.1) ...\n", | |
| "Setting up libjbig2dec0:amd64 (0.13-6) ...\n", | |
| "Processing triggers for libc-bin (2.27-3ubuntu1) ...\n", | |
| "Setting up libijs-0.35:amd64 (0.35-13) ...\n", | |
| "Setting up netpbm (2:10.0-15.3build1) ...\n", | |
| "Processing triggers for man-db (2.8.3-2ubuntu0.1) ...\n", | |
| "Processing triggers for hicolor-icon-theme (0.17-2) ...\n", | |
| "Setting up libgs9:amd64 (9.26~dfsg+0-0ubuntu0.18.04.8) ...\n", | |
| "Setting up libwmf0.2-7:amd64 (0.2.8.4-12) ...\n", | |
| "Processing triggers for fontconfig (2.12.6-0ubuntu2) ...\n", | |
| "Setting up libmagickcore-6.q16-3:amd64 (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "Setting up libdjvulibre21:amd64 (3.5.27.1-8) ...\n", | |
| "Setting up ghostscript (9.26~dfsg+0-0ubuntu0.18.04.8) ...\n", | |
| "Setting up libmagickwand-6.q16-3:amd64 (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "Setting up imagemagick-6.q16 (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "update-alternatives: using /usr/bin/compare-im6.q16 to provide /usr/bin/compare (compare) in auto mode\n", | |
| "update-alternatives: using /usr/bin/compare-im6.q16 to provide /usr/bin/compare-im6 (compare-im6) in auto mode\n", | |
| "update-alternatives: using /usr/bin/animate-im6.q16 to provide /usr/bin/animate (animate) in auto mode\n", | |
| "update-alternatives: using /usr/bin/animate-im6.q16 to provide /usr/bin/animate-im6 (animate-im6) in auto mode\n", | |
| "update-alternatives: using /usr/bin/convert-im6.q16 to provide /usr/bin/convert (convert) in auto mode\n", | |
| "update-alternatives: using /usr/bin/convert-im6.q16 to provide /usr/bin/convert-im6 (convert-im6) in auto mode\n", | |
| "update-alternatives: using /usr/bin/composite-im6.q16 to provide /usr/bin/composite (composite) in auto mode\n", | |
| "update-alternatives: using /usr/bin/composite-im6.q16 to provide /usr/bin/composite-im6 (composite-im6) in auto mode\n", | |
| "update-alternatives: using /usr/bin/conjure-im6.q16 to provide /usr/bin/conjure (conjure) in auto mode\n", | |
| "update-alternatives: using /usr/bin/conjure-im6.q16 to provide /usr/bin/conjure-im6 (conjure-im6) in auto mode\n", | |
| "update-alternatives: using /usr/bin/import-im6.q16 to provide /usr/bin/import (import) in auto mode\n", | |
| "update-alternatives: using /usr/bin/import-im6.q16 to provide /usr/bin/import-im6 (import-im6) in auto mode\n", | |
| "update-alternatives: using /usr/bin/identify-im6.q16 to provide /usr/bin/identify (identify) in auto mode\n", | |
| "update-alternatives: using /usr/bin/identify-im6.q16 to provide /usr/bin/identify-im6 (identify-im6) in auto mode\n", | |
| "update-alternatives: using /usr/bin/stream-im6.q16 to provide /usr/bin/stream (stream) in auto mode\n", | |
| "update-alternatives: using /usr/bin/stream-im6.q16 to provide /usr/bin/stream-im6 (stream-im6) in auto mode\n", | |
| "update-alternatives: using /usr/bin/display-im6.q16 to provide /usr/bin/display (display) in auto mode\n", | |
| "update-alternatives: using /usr/bin/display-im6.q16 to provide /usr/bin/display-im6 (display-im6) in auto mode\n", | |
| "update-alternatives: using /usr/bin/montage-im6.q16 to provide /usr/bin/montage (montage) in auto mode\n", | |
| "update-alternatives: using /usr/bin/montage-im6.q16 to provide /usr/bin/montage-im6 (montage-im6) in auto mode\n", | |
| "update-alternatives: using /usr/bin/mogrify-im6.q16 to provide /usr/bin/mogrify (mogrify) in auto mode\n", | |
| "update-alternatives: using /usr/bin/mogrify-im6.q16 to provide /usr/bin/mogrify-im6 (mogrify-im6) in auto mode\n", | |
| "Setting up libmagickcore-6.q16-3-extra:amd64 (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "Setting up imagemagick (8:6.9.7.4+dfsg-16ubuntu6.4) ...\n", | |
| "Processing triggers for libc-bin (2.27-3ubuntu1) ...\n" | |
| ], | |
| "name": "stdout" | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "LluwOLTukjjv", | |
| "colab_type": "text" | |
| }, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "## Download video from youtube\n", | |
| "\n", | |
| "The first thing we want to do is grab a copy of this video from youtube so that we can process it. Thankfully there's a very handy tool, [`youtube-dl`](https://github.com/ytdl-org/youtube-dl) if you're using this on something other than colab, you can follow the instructions in the documentation to install, but it should just be a `pip install ...` thing.\n", | |
| "\n", | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "iWHwHkHpGlXD", | |
| "colab_type": "text" | |
| }, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "This tool `youtube-dl` rules" | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "L3PeYsQgt23h", | |
| "colab_type": "code", | |
| "outputId": "81ea0232-6745-4543-b263-c3104912371b", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/", | |
| "height": 204 | |
| } | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "!pip install youtube-dl -U\n", | |
| "!youtube-dl https://www.youtube.com/watch?v=_cz-wwxmmto -o mitski.mp4" | |
| ], | |
| "execution_count": 4, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "text": [ | |
| "Requirement already up-to-date: youtube-dl in /usr/local/lib/python3.6/dist-packages (2019.4.7)\n", | |
| "[youtube] _cz-wwxmmto: Downloading webpage\n", | |
| "[youtube] _cz-wwxmmto: Downloading video info webpage\n", | |
| "\u001b[0;33mWARNING:\u001b[0m Requested formats are incompatible for merge and will be merged into mkv.\n", | |
| "[download] Destination: mitski.f137.mp4\n", | |
| "\u001b[K[download] 100% of 600.18MiB in 00:36\n", | |
| "[download] Destination: mitski.mp4.f251\n", | |
| "\u001b[K[download] 100% of 38.09MiB in 00:02\n", | |
| "[ffmpeg] Merging formats into \"mitski.mkv\"\n", | |
| "Deleting original file mitski.f137.mp4 (pass -k to keep)\n", | |
| "Deleting original file mitski.mp4.f251 (pass -k to keep)\n" | |
| ], | |
| "name": "stdout" | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "aa5JPFClHJdU", | |
| "colab_type": "text" | |
| }, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "## Video processing in moviepy\n", | |
| "\n", | |
| "\n", | |
| "Here, I'm using moviepy to import the original video file. I've watched it and found that I want to clip it from 8 minutes 52 seconds in to 9 minutes 4 seconds in. \n", | |
| "\n", | |
| "You can clip the video with the following code, where the input to `.subclip((8,52),(9,3))` corresponds to the timestamps above.\n", | |
| "```python\n", | |
| "clip = (original_mitski\n", | |
| " .subclip((8,52),(9,3))\n", | |
| " .resize(height=320))\n", | |
| "```\n", | |
| "\n", | |
| "To easily view the clip in jupyter, we can use `clip.ipython_display()`. This makes it easy to quickly tweak the timestamp, re-run the block, and view the result. I used this to fine-tune the timestamps to `.subclip((8,52.4),(9,3.5))`." | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "JPsLpusYuJaq", | |
| "colab_type": "code", | |
| "outputId": "655c08bc-5e17-4478-d550-da69cbe9cf77", | |
| "colab": { | |
| "base_uri": "https://localhost:8080/", | |
| "height": 34 | |
| } | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "from moviepy.editor import *\n", | |
| "\n", | |
| "original_mitski = VideoFileClip(\"mitski.mkv\")\n", | |
| "\n", | |
| "clip = (original_mitski\n", | |
| " .subclip((8,52.4),(9,3.5))\n", | |
| " .resize(height=320))\n", | |
| "\n", | |
| "clip.ipython_display()" | |
| ], | |
| "execution_count": 0, | |
| "outputs": [ | |
| { | |
| "output_type": "stream", | |
| "text": [ | |
| "\r 0%| | 0/245 [00:00<?, ?it/s]" | |
| ], | |
| "name": "stderr" | |
| } | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "qmpvVvvGHOyf", | |
| "colab_type": "text" | |
| }, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "## Adding captions\n", | |
| "\n", | |
| "Next, I want to add captions to the video. \n", | |
| "\n", | |
| "Youtube hasn't autogenerated captions (which are generally kinda bad anyway) so we'll have to make our on caption file. I looked up the lyrics and then made my own timestamps.\n", | |
| "\n", | |
| "\n", | |
| "If you want the last caption to go to the end of the video, you'll want to print the clip duration with `clip.duration`.\n", | |
| "\n", | |
| "SRT is a weird file format and very particular. If you add extra newlines anywhere (or remove the trailing newline) it won't work. So start with mine as a template." | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "keRqDM0MA-Or", | |
| "colab_type": "code", | |
| "colab": {} | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "clip.duration" | |
| ], | |
| "execution_count": 0, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "hzq9qflyyEo4", | |
| "colab_type": "code", | |
| "colab": {} | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "from pathlib import Path\n", | |
| "\n", | |
| "SRT = \"\"\"1\n", | |
| "00:00:00,000 --> 00:00:02,000\n", | |
| "If you need to \n", | |
| "be mean\n", | |
| "\n", | |
| "2\n", | |
| "00:00:02,100 --> 00:00:04,500\n", | |
| "Be mean to me\n", | |
| "\n", | |
| "3\n", | |
| "00:00:04,600 --> 00:00:11,100\n", | |
| "I can take it and \n", | |
| "put it inside of me\n", | |
| "\n", | |
| "\"\"\"\n", | |
| "\n", | |
| "Path(\"mitski.srt\").write_text(SRT)" | |
| ], | |
| "execution_count": 0, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "L4juB7Br0LzD", | |
| "colab_type": "text" | |
| }, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Next, I want to use `moviepy` to add the captions to the video. This is done in a few steps. \n", | |
| "\n", | |
| "First, though, imagemagick is required for this step, so I'll install it \n", | |
| "\n", | |
| "Then I'll adjust imagemagick's policy.xml file. That policy file limits what kinds of inputs and outputs imagemagick can use. You can read more about it [here](https://imagemagick.org/script/security-policy.php). **This is safe enough on colab but don't do this on a personal machine.** Here's a related [SO question](https://stackoverflow.com/questions/42928765/convertnot-authorized-aaaa-error-constitute-c-readimage-453) that I found helpful." | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "B1okZpYX3DqT", | |
| "colab_type": "code", | |
| "colab": {} | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "from pathlib import Path\n", | |
| "\n", | |
| "policy_path = Path(\"/etc/ImageMagick-6/policy.xml\")\n", | |
| "orig_policy = policy_path.read_text()\n", | |
| "new_policy = orig_policy.replace('<policy domain=\"path\" rights=\"none\" pattern=\"@*\"/>', '<policy domain=\"path\" rights=\"read|write\" pattern=\"@*\"/>')\n", | |
| "policy_path.write_text(new_policy)" | |
| ], | |
| "execution_count": 0, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "f7xW5N_4HbxA", | |
| "colab_type": "text" | |
| }, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Okay, now for the fun part. \n", | |
| "\n", | |
| "First, we're building a function that takes in text and returns a `TextClip` moviepy object.\n", | |
| "\n", | |
| "```python\n", | |
| "text_func = lambda txt: TextClip( txt, font=\"Georgia-Regular\", ...)\n", | |
| "```\n", | |
| "\n", | |
| "This is where you'll style the text on the video (change font, size, color, etc.)\n", | |
| "\n", | |
| "Next, we make a `SubtitlesClip` object with that funcrtion and the SRT file we just made. \n", | |
| "\n", | |
| "```python\n", | |
| "sub = SubtitlesClip(\"mitski.srt\", text_func)\n", | |
| "```\n", | |
| "\n", | |
| "Finally, we compose the subtitles with the video clip. It's really important to note that this is where you'll position the captions. I tried forever to do it above but because those `TextClip` objects won't ever see the video directly, they can't position themselves. \n", | |
| "\n", | |
| "```python\n", | |
| "CompositeVideoClip([clip, sub.set_position((\"left\",\"center\"))])\n", | |
| "```" | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "RIQJ6zSPy3e9", | |
| "colab_type": "code", | |
| "colab": {} | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "from moviepy.video.tools.subtitles import SubtitlesClip\n", | |
| "\n", | |
| "text_func = lambda txt: TextClip(\n", | |
| " txt, \n", | |
| " font=\"Georgia-Regular\", \n", | |
| " fontsize=35, \n", | |
| " color=\"White\", \n", | |
| " align=\"west\",\n", | |
| " )\n", | |
| " \n", | |
| "\n", | |
| "sub = SubtitlesClip(\"mitski.srt\", text_func)\n", | |
| "clip_sub = CompositeVideoClip([clip, sub.set_position((\"left\",\"center\"))])\n", | |
| "\n", | |
| "clip_sub.ipython_display()" | |
| ], | |
| "execution_count": 0, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "drbfRrH_HpeG", | |
| "colab_type": "text" | |
| }, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "## Convert and download\n", | |
| "\n", | |
| "If you just want the mp4 (a very efficient video format compared to gif) you can go ahead and write that file and download it. \n", | |
| "\n", | |
| "If you want a gif, we'll still need to save this file so that we can use ffmpeg to convert it in a moment.\n", | |
| "\n", | |
| "Also, I'm using Colab's files interface to easily initiate downloads. Comment those lines out if you don't want your computer to ask to download these files." | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "BtdWzstzwXaZ", | |
| "colab_type": "code", | |
| "colab": {} | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "from google.colab import files\n", | |
| "\n", | |
| "clip_sub.write_videofile(\"mitski_subtitle.mp4\", codec='mpeg4')\n", | |
| "\n", | |
| "files.download(\"mitski_subtitle.mp4\")" | |
| ], | |
| "execution_count": 0, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "bKzroOj_HtBt", | |
| "colab_type": "text" | |
| }, | |
| "cell_type": "markdown", | |
| "source": [ | |
| "Making gifs that aren't enormous files is notoriously tricky. Our mp4 is < 1MB but if we use moviepy to save the gif directly, it ends up being ~50MB (try it if you don't believe me!). \n", | |
| "\n", | |
| "My solution is to use ffmpeg and adapt the [advice here](https://cassidy.codes/blog/2017/04/25/ffmpeg-frames-to-gif-optimization/) to convert the mp4 into a smaller gif than moviepy creates.\n", | |
| "\n", | |
| "\n", | |
| "And there you go! This should result in a reasonably-sized gif for most clips around this size (320p) and length (12s). If you need to shrink it further, you can adjust the size, framerate, and other qualities." | |
| ] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "d-tPwyt6FSOc", | |
| "colab_type": "code", | |
| "colab": {} | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "!ffmpeg -v warning -i mitski_subtitle.mp4 -vf \"fps=15,scale=320:-1:flags=lanczos,palettegen=stats_mode=diff\" -y /tmp/palette.png\n", | |
| "!ffmpeg -i mitski_subtitle.mp4 -i /tmp/palette.png -lavfi \"fps=15,scale=320:-1:flags=lanczos,paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle\" -y mitski_subtitle.gif" | |
| ], | |
| "execution_count": 0, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "ulXlP8SQ5eqM", | |
| "colab_type": "code", | |
| "colab": {} | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "files.download(\"mitski_subtitle.gif\")" | |
| ], | |
| "execution_count": 0, | |
| "outputs": [] | |
| }, | |
| { | |
| "metadata": { | |
| "id": "MlS_ppATCid2", | |
| "colab_type": "code", | |
| "colab": {} | |
| }, | |
| "cell_type": "code", | |
| "source": [ | |
| "" | |
| ], | |
| "execution_count": 0, | |
| "outputs": [] | |
| } | |
| ] | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment