├── FFmpeg-Logo.svg ├── FFmpeg-in-Google-Drive.ipynb ├── Google-Drive-Logo.svg └── README.md /FFmpeg-Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /FFmpeg-in-Google-Drive.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [], 7 | "authorship_tag": "ABX9TyOoQKmSplYfNUCJsAfqQ5iD", 8 | "include_colab_link": true 9 | }, 10 | "kernelspec": { 11 | "name": "python3", 12 | "display_name": "Python 3" 13 | }, 14 | "language_info": { 15 | "name": "python" 16 | } 17 | }, 18 | "cells": [ 19 | { 20 | "cell_type": "markdown", 21 | "metadata": { 22 | "id": "view-in-github", 23 | "colab_type": "text" 24 | }, 25 | "source": [ 26 | "\"Open" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": { 32 | "id": "KAj1Zu19Q9ft" 33 | }, 34 | "source": [ 35 | "# __Mount Google Drive__" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "metadata": { 41 | "cellView": "form", 42 | "id": "3MrwsMPDRR6E" 43 | }, 44 | "source": [ 45 | "#@markdown
\"Gdrive-logo\"/
\n", 46 | "#@markdown

Mount Google Drive


\n", 47 | "\n", 48 | "from google.colab import drive\n", 49 | "\n", 50 | "mode = \"Mount\" #@param [\"Mount\", \"Unmount\"]\n", 51 | "drive.mount._DEBUG = False\n", 52 | "\n", 53 | "if mode == \"Mount\":\n", 54 | " drive.mount('/content/drive', force_remount=True)\n", 55 | "elif mode == \"Unmount\":\n", 56 | " try: drive.flush_and_unmount()\n", 57 | " except ValueError: pass\n", 58 | " !rm -rf /root/.config/Google/DriveFS" 59 | ], 60 | "execution_count": null, 61 | "outputs": [] 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "metadata": { 66 | "id": "cvpmFxFwQbUB" 67 | }, 68 | "source": [ 69 | "# __Install FFmpeg__" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "metadata": { 75 | "cellView": "form", 76 | "id": "M9QHuevAQ3nm" 77 | }, 78 | "source": [ 79 | "#@markdown
\"FFmpeg-logo\"/
\n", 80 | "#@markdown

Install FFmpeg


\n", 81 | "from IPython.display import clear_output\n", 82 | "!sudo curl -L https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz -o /usr/local/bin/ffmpeg.tar.xz\n", 83 | "clear_output()\n", 84 | "%cd /usr/local/bin/\n", 85 | "clear_output()\n", 86 | "!7z e /usr/local/bin/ffmpeg.tar.xz\n", 87 | "clear_output()\n", 88 | "!7z e /usr/local/bin/ffmpeg.tar\n", 89 | "clear_output()\n", 90 | "!sudo chmod a+rx /usr/local/bin/ffmpeg\n", 91 | "clear_output()\n", 92 | "%cd /content/\n", 93 | "!sudo curl -L https://mkvtoolnix.download/appimage/MKVToolNix_GUI-70.0.0-x86_64.AppImage -o /usr/local/bin/MKVToolNix_GUI-70.0.0-x86_64.AppImage\n", 94 | "!sudo chmod u+rx /usr/local/bin/MKVToolNix_GUI-70.0.0-x86_64.AppImage\n", 95 | "!sudo ln -s /usr/local/bin/MKVToolNix_GUI-70.0.0-x86_64.AppImage /usr/local/bin/mkvmerge\n", 96 | "!sudo chmod a+rx /usr/local/bin/mkvmerge\n", 97 | "clear_output()\n", 98 | "!ffmpeg -version" 99 | ], 100 | "execution_count": null, 101 | "outputs": [] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "source": [ 106 | "### __Remux Video Files__" 107 | ], 108 | "metadata": { 109 | "id": "bSKeErgLMe4a" 110 | } 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "source": [ 115 | "* **REMUX** video files without **RE-ENCODING**.\n", 116 | "* Make sure that `outputFormat` that you have selected is supported for all tracks in the `inputFile`.\n", 117 | "* `WebM` only support `VP9`, `VP8`, `AV1` video and `Vorbis`, `Opus` audio and `WebVTT` subtitles." 118 | ], 119 | "metadata": { 120 | "id": "7rdtqTIKMix6" 121 | } 122 | }, 123 | { 124 | "cell_type": "code", 125 | "source": [ 126 | "import os\n", 127 | "\n", 128 | "inputFile = \"\" #@param {type:\"string\"}\n", 129 | "outputFormat = \"mp4\" #@param [\"mp4\", \"mkv\", \"mov\", \"webm\"]\n", 130 | "saveToSourceLocation = True #@param {type:\"boolean\"}\n", 131 | "\n", 132 | "commandLine = '-hide_banner -i \"' + inputFile + '\" -map 0:v? -c:v copy -map 0:a? -c:a copy '\n", 133 | "\n", 134 | "if outputFormat == \"mp4\" or outputFormat == \"mov\":\n", 135 | " commandLine = commandLine + '-map 0:s? -c:s mov_text -movflags +faststart '\n", 136 | "elif outputFormat == \"webm\":\n", 137 | " commandLine = commandLine + '-map 0:s? -c:s webvtt '\n", 138 | "\n", 139 | "sourceName = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[0]\n", 140 | "sourceFolder = os.path.dirname(os.path.abspath(inputFile))\n", 141 | "\n", 142 | "if saveToSourceLocation:\n", 143 | " outputFile = os.path.join(sourceFolder, (sourceName + '.' + outputFormat))\n", 144 | " if os.path.isfile(outputFile):\n", 145 | " outputFile = os.path.join(sourceFolder, (sourceName + '_[REMUX].' + outputFormat))\n", 146 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 147 | "else:\n", 148 | " outputFolder = input(\"outputFolder: \")\n", 149 | " outputFile = os.path.join(outputFolder, (sourceName + '.' + outputFormat))\n", 150 | " if os.path.isfile(outputFile):\n", 151 | " outputFile = os.path.join(outputFolder, (sourceName + '_[REMUX].' + outputFormat))\n", 152 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 153 | "\n", 154 | "!ffmpeg {commandLine}" 155 | ], 156 | "metadata": { 157 | "cellView": "form", 158 | "id": "qu8d7FCFMmzE" 159 | }, 160 | "execution_count": null, 161 | "outputs": [] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "source": [ 166 | "### __Trim Video Files__" 167 | ], 168 | "metadata": { 169 | "id": "v9q3MG6nMroF" 170 | } 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "source": [ 175 | "* **TRIM** video files without **RE-ENCODING**.\n", 176 | "* `inputFile`: video file's path and set `startTime` and `endTime` to trim.\n", 177 | "* Trimed video will be saved as same as the **source's format**." 178 | ], 179 | "metadata": { 180 | "id": "YDyYZuqeMvOk" 181 | } 182 | }, 183 | { 184 | "cell_type": "code", 185 | "source": [ 186 | "import os\n", 187 | "\n", 188 | "inputFile = \"\" #@param {type:\"string\"}\n", 189 | "startTime = \"00:00:00.000\" #@param {type:\"string\"}\n", 190 | "endTime = \"00:00:00.000\" #@param {type:\"string\"}\n", 191 | "saveToSourceLocation = True #@param {type:\"boolean\"}\n", 192 | "\n", 193 | "commandLine = '-hide_banner -i \"' + inputFile + '\" -map 0 -c copy -ss ' + startTime + ' -to ' + endTime + ' '\n", 194 | "\n", 195 | "sourceName = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[0]\n", 196 | "sourceExtention = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[1]\n", 197 | "sourceFolder = os.path.dirname(os.path.abspath(inputFile))\n", 198 | "\n", 199 | "if saveToSourceLocation:\n", 200 | " outputFile = os.path.join(sourceFolder, (sourceName + f'_[{startTime}-{endTime}]' + sourceExtention))\n", 201 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 202 | "else:\n", 203 | " outputFolder = input(\"outputFolder: \")\n", 204 | " outputFile = os.path.join(outputFolder, (sourceName + f'_[{startTime}-{endTime}]' + sourceExtention))\n", 205 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 206 | "\n", 207 | "!ffmpeg {commandLine}" 208 | ], 209 | "metadata": { 210 | "cellView": "form", 211 | "id": "54gK9-TYMzGF" 212 | }, 213 | "execution_count": null, 214 | "outputs": [] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "source": [ 219 | "### __Extract Audio__" 220 | ], 221 | "metadata": { 222 | "id": "J5R7BtJTM3rj" 223 | } 224 | }, 225 | { 226 | "cell_type": "markdown", 227 | "source": [ 228 | "* **EXTRACT** audio tracks from video files.\n", 229 | "* `DTS`, `DTS-HD`, `DTS-MA`, `TrueHD` tracks will be muxed as a `.mka` file." 230 | ], 231 | "metadata": { 232 | "id": "JHrxRvXjM65Q" 233 | } 234 | }, 235 | { 236 | "cell_type": "code", 237 | "source": [ 238 | "import os\n", 239 | "import json\n", 240 | "import subprocess\n", 241 | "import prettytable\n", 242 | "\n", 243 | "inputFile = \"\" #@param {type:\"string\"}\n", 244 | "saveToSourceLocation = True #@param {type:\"boolean\"}\n", 245 | "\n", 246 | "jsonFFprobe = subprocess.check_output([\n", 247 | " 'ffprobe',\n", 248 | " '-hide_banner',\n", 249 | " '-loglevel',\n", 250 | " 'warning',\n", 251 | " '-print_format',\n", 252 | " 'json',\n", 253 | " '-show_entries',\n", 254 | " 'stream',\n", 255 | " '-i',\n", 256 | " os.path.abspath(inputFile),\n", 257 | " ], stderr=subprocess.DEVNULL)\n", 258 | "jsonData = json.loads(jsonFFprobe)\n", 259 | "\n", 260 | "streamCount = len(jsonData['streams'])\n", 261 | "commandLine = '-hide_banner -i \"' + inputFile + '\" '\n", 262 | "\n", 263 | "codecTable = prettytable.PrettyTable()\n", 264 | "codecTable.field_names = ['Track ID', 'Codec name', 'Channels', 'Sample rate']\n", 265 | "codecTable.align = 'l'\n", 266 | "\n", 267 | "for i in range(streamCount):\n", 268 | " codec_type = jsonData.get('streams')[int(i)].get('codec_type')\n", 269 | " if codec_type == \"audio\":\n", 270 | " index = jsonData.get('streams')[int(i)].get('index')\n", 271 | " codec_name = jsonData.get('streams')[int(i)].get('codec_name')\n", 272 | " channel_layout = jsonData.get('streams')[int(i)].get('channels')\n", 273 | " sample_rate = jsonData.get('streams')[int(i)].get('sample_rate')\n", 274 | " codecTable.add_row([index, codec_name, channel_layout, sample_rate])\n", 275 | "\n", 276 | "stringTable = '\\t' + codecTable.__str__().replace('\\n', '\\n\\t')\n", 277 | "print(stringTable)\n", 278 | "\n", 279 | "trackID = input(\"\\ntrackID: \")\n", 280 | "codec_name = jsonData.get('streams')[int(trackID)].get('codec_name')\n", 281 | "if \"opus\" in codec_name:\n", 282 | " codec_ext = \"ogg\"\n", 283 | "elif \"dts\" in codec_name:\n", 284 | " codec_ext = \"mka\"\n", 285 | "elif \"aac\" in codec_name:\n", 286 | " codec_ext = \"aac\"\n", 287 | "elif \"truehd\" in codec_name:\n", 288 | " codec_ext = \"mka\"\n", 289 | "elif codec_name == \"ac3\":\n", 290 | " codec_ext = \"ac3\"\n", 291 | "elif codec_name == \"eac3\":\n", 292 | " codec_ext = \"eac3\"\n", 293 | "elif \"pcm\" in codec_name:\n", 294 | " codec_ext = \"wav\"\n", 295 | "elif \"flac\" in codec_name:\n", 296 | " codec_ext = \"flac\"\n", 297 | "\n", 298 | "commandLine = commandLine + \"-vn -sn -map 0:\" + str(trackID) + \" -c copy \"\n", 299 | "\n", 300 | "sourceName = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[0]\n", 301 | "sourceFolder = os.path.dirname(os.path.abspath(inputFile))\n", 302 | "\n", 303 | "if saveToSourceLocation:\n", 304 | " outputFile = os.path.join(sourceFolder, (sourceName + f'_[{codec_name}].' + codec_ext))\n", 305 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 306 | "else:\n", 307 | " outputFolder = input(\"outputFolder: \")\n", 308 | " outputFile = os.path.join(outputFolder, (sourceName + f'_[{codec_name}].' + codec_ext))\n", 309 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 310 | "\n", 311 | "!ffmpeg {commandLine}" 312 | ], 313 | "metadata": { 314 | "cellView": "form", 315 | "id": "9ReL8LPWM_gJ" 316 | }, 317 | "execution_count": null, 318 | "outputs": [] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "source": [ 323 | "### __Remove Bitstream Metadata__" 324 | ], 325 | "metadata": { 326 | "id": "2HhXoSQFNDMV" 327 | } 328 | }, 329 | { 330 | "cell_type": "markdown", 331 | "source": [ 332 | "* For `H.264`/`AVC` tracks run below cell." 333 | ], 334 | "metadata": { 335 | "id": "D9bDZPonNF_7" 336 | } 337 | }, 338 | { 339 | "cell_type": "code", 340 | "source": [ 341 | "import os\n", 342 | "import json\n", 343 | "import subprocess\n", 344 | "\n", 345 | "inputFile = \"\" #@param {type:\"string\"}\n", 346 | "saveToSourceLocation = True #@param {type:\"boolean\"}\n", 347 | "\n", 348 | "jsonFFprobe = subprocess.check_output([\n", 349 | " 'ffprobe',\n", 350 | " '-hide_banner',\n", 351 | " '-loglevel',\n", 352 | " 'warning',\n", 353 | " '-print_format',\n", 354 | " 'json',\n", 355 | " '-show_entries',\n", 356 | " 'stream',\n", 357 | " '-i',\n", 358 | " os.path.abspath(inputFile),\n", 359 | " ], stderr=subprocess.DEVNULL)\n", 360 | "jsonData = json.loads(jsonFFprobe)\n", 361 | "\n", 362 | "streamCount = len(jsonData['streams'])\n", 363 | "commandLine = '-hide_banner -i \"' + inputFile + '\" '\n", 364 | "\n", 365 | "for i in range(streamCount):\n", 366 | " codec_name = jsonData.get('streams')[int(i)].get('codec_name')\n", 367 | " if codec_name == \"h264\":\n", 368 | " index = jsonData.get('streams')[int(i)].get('index')\n", 369 | " commandLine = commandLine + f'-map 0:{index} '\n", 370 | "\n", 371 | "commandLine = commandLine + '-c:v copy -map 0:a? -c:a copy -map 0:s? -c:s copy -bitexact -map_metadata -1 -vbsf filter_units=remove_types=6 '\n", 372 | "\n", 373 | "sourceName = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[0]\n", 374 | "sourceExtention = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[1]\n", 375 | "sourceFolder = os.path.dirname(os.path.abspath(inputFile))\n", 376 | "\n", 377 | "if saveToSourceLocation:\n", 378 | " outputFile = os.path.join(sourceFolder, (sourceName + f'_[NEW]' + sourceExtention))\n", 379 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 380 | "else:\n", 381 | " outputFolder = input(\"outputFolder: \")\n", 382 | " outputFile = os.path.join(outputFolder, (sourceName + f'_[NEW]' + sourceExtention))\n", 383 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 384 | "\n", 385 | "!ffmpeg {commandLine}" 386 | ], 387 | "metadata": { 388 | "cellView": "form", 389 | "id": "8Tu2aURHNJvp" 390 | }, 391 | "execution_count": null, 392 | "outputs": [] 393 | }, 394 | { 395 | "cell_type": "markdown", 396 | "source": [ 397 | "* For `H.265`/`HEVC` tracks run below cell." 398 | ], 399 | "metadata": { 400 | "id": "ZeQkfcTaNM_b" 401 | } 402 | }, 403 | { 404 | "cell_type": "code", 405 | "source": [ 406 | "import os\n", 407 | "import json\n", 408 | "import subprocess\n", 409 | "from IPython.display import clear_output\n", 410 | "\n", 411 | "inputFile = \"\" #@param {type:\"string\"}\n", 412 | "saveToSourceLocation = True #@param {type:\"boolean\"}\n", 413 | "\n", 414 | "jsonFFprobe = subprocess.check_output([\n", 415 | " 'ffprobe',\n", 416 | " '-hide_banner',\n", 417 | " '-loglevel',\n", 418 | " 'warning',\n", 419 | " '-print_format',\n", 420 | " 'json',\n", 421 | " '-show_entries',\n", 422 | " 'stream',\n", 423 | " '-i',\n", 424 | " os.path.abspath(inputFile),\n", 425 | " ], stderr=subprocess.DEVNULL)\n", 426 | "jsonData = json.loads(jsonFFprobe)\n", 427 | "\n", 428 | "streamCount = len(jsonData['streams'])\n", 429 | "commandLine = f'-hide_banner -i \"{inputFile}\" '\n", 430 | "tracksALL = []\n", 431 | "tracksHEVC = []\n", 432 | "\n", 433 | "for i in range(streamCount):\n", 434 | " indexALL = jsonData.get('streams')[int(i)].get('index')\n", 435 | " tracksALL.append(indexALL)\n", 436 | " codec_name = jsonData.get('streams')[int(i)].get('codec_name')\n", 437 | " if codec_name == \"hevc\":\n", 438 | " index = jsonData.get('streams')[int(i)].get('index')\n", 439 | " tracksHEVC.append(index)\n", 440 | "\n", 441 | "commandLine = commandLine + f'-map 0:{tracksHEVC[0]} -c:v copy -an -sn -vbsf hevc_mp4toannexb /content/source-video.hevc && ffmpeg -hide_banner -i /content/source-video.hevc -c copy -vbsf filter_units=remove_types=39 /content/output.hevc && mkvmerge --output /content/output.mkv /content/output.hevc && ffmpeg -hide_banner -i /content/output.mkv -i \"{inputFile}\" -map 0:0 '\n", 442 | "for trackID in tracksALL:\n", 443 | " if trackID != tracksHEVC[0]:\n", 444 | " commandLine = commandLine + f'-map 1:{trackID} '\n", 445 | "commandLine = commandLine + f'-c copy '\n", 446 | "\n", 447 | "sourceName = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[0]\n", 448 | "sourceExtention = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[1]\n", 449 | "sourceFolder = os.path.dirname(os.path.abspath(inputFile))\n", 450 | "\n", 451 | "if saveToSourceLocation:\n", 452 | " outputFile = os.path.join(sourceFolder, (sourceName + f'_[NEW]' + sourceExtention))\n", 453 | " commandLine = commandLine + f'\"{outputFile}\"'\n", 454 | "else:\n", 455 | " outputFolder = input(\"outputFolder: \")\n", 456 | " outputFile = os.path.join(outputFolder, (sourceName + f'_[NEW]' + sourceExtention))\n", 457 | " commandLine = commandLine + f'\"{outputFile}\"'\n", 458 | "\n", 459 | "!ffmpeg {commandLine}\n", 460 | "!rm /content/source-video.hevc\n", 461 | "!rm /content/output.hevc\n", 462 | "!rm /content/output.mkv" 463 | ], 464 | "metadata": { 465 | "cellView": "form", 466 | "id": "JzhZBR0zNP_V" 467 | }, 468 | "execution_count": null, 469 | "outputs": [] 470 | }, 471 | { 472 | "cell_type": "markdown", 473 | "source": [ 474 | "### __Encode H.264__" 475 | ], 476 | "metadata": { 477 | "id": "RsYc55qeNTGI" 478 | } 479 | }, 480 | { 481 | "cell_type": "markdown", 482 | "source": [ 483 | "* `CRF` and `2-Pass` encoding available.\n", 484 | "* This cell only encodes the **first video track** from the input file." 485 | ], 486 | "metadata": { 487 | "id": "r2msq54HNhNN" 488 | } 489 | }, 490 | { 491 | "cell_type": "code", 492 | "source": [ 493 | "inputFile = \"\" #@param {type:\"string\"}\n", 494 | "encodeMode = \"CRF\" #@param [\"CRF\", \"2-Pass\"]\n", 495 | "saveToSourceLocation = True #@param {type:\"boolean\"}\n", 496 | "#@markdown ### __CRF Settings__\n", 497 | "crf = 19 #@param {type:\"slider\", min:0, max:51, step:1}\n", 498 | "#@markdown ### __2-Pass Settings__\n", 499 | "Bitrate = \"5800\" #@param {type:\"string\"}\n", 500 | "#@markdown ### __General Settings__\n", 501 | "Preset = \"veryslow\" #@param [\"ultrafast\", \"superfast\", \"veryfast\", \"faster\", \"fast\", \"medium\", \"slow\", \"slower\", \"veryslow\", \"placebo\"]\n", 502 | "Profile = \"high\" #@param [\"baseline\", \"main\", \"high\", \"high10\", \"high422\", \"high444\"]\n", 503 | "Level = \"4\" #@param [\"1\", \"1.1\", \"1.2\", \"1.3\", \"2\", \"2.1\", \"2.2\", \"3\", \"3.1\",\"3.2\", \"4\", \"4.1\", \"4.2\", \"5\", \"5.1\", \"5.2\"]\n", 504 | "bitDepth = \"8-Bit\" #@param [\"8-Bit\", \"10-Bit\"]\n", 505 | "Tune = \"None\" #@param [\"None\", \"film\", \"animation\", \"grain\", \"stillimage\", \"psnr\", \"ssim\", \"fastdecode\", \"zerolatency\"]\n", 506 | "Maxrate = \"8700\" #@param {type:\"string\"}\n", 507 | "Bufsize = \"11600\" #@param {type:\"string\"}\n", 508 | "\n", 509 | "import os\n", 510 | "\n", 511 | "commandLine = f'-preset {Preset} -profile:v {Profile} -level {Level} -maxrate {Maxrate}k -bufsize {Bufsize}k '\n", 512 | "\n", 513 | "if Tune != \"None\":\n", 514 | " commandLine = commandLine + f'-tune {Tune} '\n", 515 | "if bitDepth == \"8-Bit\":\n", 516 | " commandLine = commandLine + f'-pix_fmt yuv420p '\n", 517 | "elif bitDepth == \"10-Bit\":\n", 518 | " commandLine = commandLine + f'-pix_fmt yuv420p10le '\n", 519 | "\n", 520 | "commandLine = commandLine + f'-partitions all -me_range 24 -nal-hrd 1 -sc_threshold 0 -x264-params \"colorprim=bt709:colormatrix=bt709:transfer=bt709:qpmin=6:qpmax=51:bframes=4:lookahead-threads=2:stitchable=1\"'\n", 521 | "\n", 522 | "sourceName = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[0]\n", 523 | "sourceExtention = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[1]\n", 524 | "sourceFolder = os.path.dirname(os.path.abspath(inputFile))\n", 525 | "\n", 526 | "if saveToSourceLocation:\n", 527 | " outputFile = os.path.join(sourceFolder, (sourceName + f'_[Encoded]' + sourceExtention))\n", 528 | "else:\n", 529 | " outputFolder = input(\"outputFolder: \")\n", 530 | " outputFile = os.path.join(outputFolder, (sourceName + f'_[Encoded]' + sourceExtention))\n", 531 | "\n", 532 | "firstPass = f'-hide_banner -i \"{os.path.abspath(inputFile)}\" -map 0:v:0 -c:v libx264 -pass 1 -b:v {Bitrate}k {commandLine} -an -sn -y -f null /dev/null'\n", 533 | "secondPass = f'-hide_banner -i \"{os.path.abspath(inputFile)}\" -map 0:v:0 -c:v libx264 -pass 2 -b:v {Bitrate}k {commandLine} -map 0:a? -c:a copy -map 0:s? -c:s copy \"{outputFile}\"'\n", 534 | "crfPass = f'-hide_banner -i \"{os.path.abspath(inputFile)}\" -map 0:v:0 -c:v libx264 -crf {crf} {commandLine} -map 0:a? -c:a copy -map 0:s? -c:s copy \"{outputFile}\"'\n", 535 | "\n", 536 | "if encodeMode == 'CRF':\n", 537 | " !ffmpeg {crfPass}\n", 538 | "else:\n", 539 | " !ffmpeg {firstPass}\n", 540 | " !ffmpeg {secondPass}" 541 | ], 542 | "metadata": { 543 | "cellView": "form", 544 | "id": "ZzhG6DYSNlk9" 545 | }, 546 | "execution_count": null, 547 | "outputs": [] 548 | }, 549 | { 550 | "cell_type": "markdown", 551 | "source": [ 552 | "### __HDR to SDR__" 553 | ], 554 | "metadata": { 555 | "id": "2k0_wCH0HOEJ" 556 | } 557 | }, 558 | { 559 | "cell_type": "markdown", 560 | "source": [ 561 | "* Convert `HDR` video to `SDR` video.\n", 562 | "* `toneMap`: Select tone mapping method." 563 | ], 564 | "metadata": { 565 | "id": "eEluAGkNHVO_" 566 | } 567 | }, 568 | { 569 | "cell_type": "code", 570 | "source": [ 571 | "import os\n", 572 | "\n", 573 | "inputFile = \"\" #@param {type:\"string\"}\n", 574 | "Codec = \"HEVC\" #@param [\"H.264\", \"HEVC\"]\n", 575 | "valueCRF = 22 #@param {type:\"slider\", min:0, max:51, step:1}\n", 576 | "bitDepth = \"10-Bit\" #@param [\"8-Bit\", \"10-Bit\"]\n", 577 | "toneMap = \"hable\" #@param [\"none\", \"clip\", \"linear\", \"gamma\", \"reinhard\", \"hable\", \"mobius\"]\n", 578 | "saveToSourceLocation = True #@param {type:\"boolean\"}\n", 579 | "\n", 580 | "if Codec == \"H.264\":\n", 581 | " Codec = \"libx264\"\n", 582 | "elif Codec == \"HEVC\":\n", 583 | " Codec = \"libx265\"\n", 584 | "\n", 585 | "if bitDepth == \"8-Bit\":\n", 586 | " bitDepth = \"yuv420p\"\n", 587 | "elif bitDepth == \"10-Bit\":\n", 588 | " bitDepth = \"yuv420p10le\"\n", 589 | "\n", 590 | "commandLine = f'-hide_banner -i \"{inputFile}\" -max_muxing_queue_size 1024 -c:v {Codec} -crf:v {valueCRF} -preset:v medium -pix_fmt {bitDepth} '\n", 591 | "\n", 592 | "if Codec == \"libx265\":\n", 593 | " commandLine = commandLine + f'-x265-params \"aq-mode=2:repeat-headers=0:strong-intra-smoothing=1:bframes=4:b-adapt=2:frame-threads=0\" '\n", 594 | "commandLine = commandLine + f'-vf zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap={toneMap}:desat=0,zscale=t=bt709:m=bt709:r=tv,format={bitDepth} -map 0:a? -c:a copy -map 0:s? -c:s copy '\n", 595 | "\n", 596 | "sourceName = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[0]\n", 597 | "sourceExtention = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[1]\n", 598 | "sourceFolder = os.path.dirname(os.path.abspath(inputFile))\n", 599 | "\n", 600 | "if saveToSourceLocation:\n", 601 | " outputFile = os.path.join(sourceFolder, (sourceName + f'_[SDR]' + sourceExtention))\n", 602 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 603 | "else:\n", 604 | " outputFolder = input(\"outputFolder: \")\n", 605 | " outputFile = os.path.join(outputFolder, (sourceName + f'_[SDR]' + sourceExtention))\n", 606 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 607 | "\n", 608 | "!ffmpeg {commandLine}" 609 | ], 610 | "metadata": { 611 | "id": "kn_Z3vq4HS5w", 612 | "cellView": "form" 613 | }, 614 | "execution_count": null, 615 | "outputs": [] 616 | }, 617 | { 618 | "cell_type": "markdown", 619 | "source": [ 620 | "### __HDR10 Encoding__" 621 | ], 622 | "metadata": { 623 | "id": "8nIA7SRf7Wp6" 624 | } 625 | }, 626 | { 627 | "cell_type": "markdown", 628 | "source": [ 629 | "* Only `x265`/`HEVC` tracks are supported.\n", 630 | "* `extractColors`: View the color information of the footage.\n", 631 | "* When encoding, make sure `extractColors` is **deselected**." 632 | ], 633 | "metadata": { 634 | "id": "R5irbiHS7a8j" 635 | } 636 | }, 637 | { 638 | "cell_type": "code", 639 | "source": [ 640 | "import os\n", 641 | "import json\n", 642 | "import subprocess\n", 643 | "\n", 644 | "inputFile = \"\" #@param {type:\"string\"}\n", 645 | "preset = \"slow\" #@param [\"ultrafast\", \"superfast\", \"veryfast\", \"faster\", \"fast\", \"medium\", \"slow\", \"slower\", \"veryslow\", \"placebo\"]\n", 646 | "profile = \"main10\" #@param [\"main\", \"main10\"]\n", 647 | "level = \"4\" #@param [\"1\", \"1.1\", \"1.2\", \"1.3\", \"2\", \"2.1\", \"2.2\", \"3\", \"3.1\",\"3.2\", \"4\", \"4.1\", \"4.2\", \"5\", \"5.1\", \"5.2\"]\n", 648 | "valueCRF = 19 #@param {type:\"slider\", min:0, max:51, step:1}\n", 649 | "extractColors = False #@param {type:\"boolean\"}\n", 650 | "saveToSourceLocation = True #@param {type:\"boolean\"}\n", 651 | "\n", 652 | "jsonFFprobe = subprocess.check_output([\n", 653 | " 'ffprobe', '-hide_banner', '-loglevel', 'warning',\n", 654 | " '-print_format', 'json', '-show_frames', '-read_intervals', '%+#1', '-show_entries',\n", 655 | " 'format:stream:frame=color_space,color_primaries,color_transfer,side_data_list,pix_fmt',\n", 656 | " '-i', os.path.abspath(inputFile)], stderr=subprocess.DEVNULL)\n", 657 | "jsonData = json.loads(jsonFFprobe)\n", 658 | "\n", 659 | "pix_fmt = jsonData.get('frames')[0].get('pix_fmt')\n", 660 | "\n", 661 | "if jsonData.get('frames')[0].get('color_space'):\n", 662 | " color_space = jsonData.get('frames')[0].get('color_space')\n", 663 | "if jsonData.get('frames')[0].get('color_primaries'):\n", 664 | " color_primaries = jsonData.get('frames')[0].get('color_primaries')\n", 665 | "if jsonData.get('frames')[0].get('color_transfer'):\n", 666 | " color_transfer = jsonData.get('frames')[0].get('color_transfer')\n", 667 | "if jsonData.get('frames')[0].get('side_data_list'):\n", 668 | " side_data_list = jsonData.get('frames')[0].get('side_data_list')[0]\n", 669 | "\n", 670 | " red_x = round(eval(side_data_list.get('red_x', '0')), 4)\n", 671 | " red_y = round(eval(side_data_list.get('red_y', '0')), 4)\n", 672 | " green_x = round(eval(side_data_list.get('green_x', '0')), 4)\n", 673 | " green_y = round(eval(side_data_list.get('green_y', '0')), 4)\n", 674 | " blue_x = round(eval(side_data_list.get('blue_x', '0')), 4)\n", 675 | " blue_y = round(eval(side_data_list.get('blue_y', '0')), 4)\n", 676 | " white_point_x = round(eval(side_data_list.get('white_point_x', '0')), 4)\n", 677 | " white_point_y = round(eval(side_data_list.get('white_point_y', '0')), 4)\n", 678 | " min_luminance = round(eval(side_data_list.get('min_luminance', '0')), 4)\n", 679 | " max_luminance = round(eval(side_data_list.get('max_luminance', '0')), 4)\n", 680 | "\n", 681 | " cll_max_content = 0\n", 682 | " cll_max_average = 0\n", 683 | "\n", 684 | " try:\n", 685 | " side_data_list = jsonData.get('frames')[0].get('side_data_list')[1]\n", 686 | " cll_max_content = round(eval(str(side_data_list.get('max_content', '0'))), 4)\n", 687 | " cll_max_average = round(eval(str(side_data_list.get('max_average', '0'))), 4)\n", 688 | " except:\n", 689 | " pass\n", 690 | "\n", 691 | " x265_red_x = round(red_x / 0.00002)\n", 692 | " x265_red_y = round(red_y / 0.00002)\n", 693 | " x265_green_x = round(green_x / 0.00002)\n", 694 | " x265_green_y = round(green_y / 0.00002)\n", 695 | " x265_blue_x = round(blue_x / 0.00002)\n", 696 | " x265_blue_y = round(blue_y / 0.00002)\n", 697 | " x265_white_point_x = round(white_point_x / 0.00002)\n", 698 | " x265_white_point_y = round(white_point_y / 0.00002)\n", 699 | " x265_min_luminance = round(min_luminance / 0.0001)\n", 700 | " x265_max_luminance = round(max_luminance / 0.0001)\n", 701 | "\n", 702 | " x265_master_display_info = 'G(' + str(x265_green_x) + ',' + str(x265_green_y) + ')B(' \\\n", 703 | " + str(x265_blue_x) + ',' + str(x265_blue_y) + ')R(' + str(x265_red_x) + ',' \\\n", 704 | " + str(x265_red_y) + ')WP(' + str(x265_white_point_x) + ',' \\\n", 705 | " + str(x265_white_point_y) + ')L(' + str(x265_max_luminance) + ',' \\\n", 706 | " + str(x265_min_luminance) + '):max-cll=' + str(cll_max_content) \\\n", 707 | " + ',' + str(cll_max_average)\n", 708 | "\n", 709 | "commandLine = f'-hide_banner -i \"{os.path.abspath(inputFile)}\" -map 0:v -c:v libx265 -crf {valueCRF} -preset {preset} '\n", 710 | "\n", 711 | "if pix_fmt == \"rgb24\":\n", 712 | " print(\"You have selected a Non-HDR footage....\")\n", 713 | "else:\n", 714 | " if extractColors:\n", 715 | " if jsonData.get('frames')[0].get('color_space'):\n", 716 | " print(\"color_space: \\t\\t\" + color_space)\n", 717 | " if jsonData.get('frames')[0].get('color_primaries'):\n", 718 | " print(\"color_primaries: \\t\" + color_primaries)\n", 719 | " if jsonData.get('frames')[0].get('color_transfer'):\n", 720 | " print(\"color_transfer: \\t\" + color_transfer)\n", 721 | " if jsonData.get('frames')[0].get('side_data_list'):\n", 722 | " print(\"master_display:\\t\\t\" + x265_master_display_info)\n", 723 | " else:\n", 724 | " if profile == \"main\":\n", 725 | " commandLine = commandLine + f'-profile:v main -pix_fmt yuv420p '\n", 726 | " elif profile == \"main10\":\n", 727 | " commandLine = commandLine + f'-profile:v main10 -pix_fmt yuv420p10le '\n", 728 | " if jsonData.get('frames')[0].get('side_data_list'):\n", 729 | " commandLine = commandLine + f'-x265-params \"level-idc={level}:aq-mode=2:no-info=1:strong-intra-smoothing=1:bframes=4:b-adapt=2:frame-threads=0:hdr10=1:chromaloc=2:repeat-headers=1:aud=1:hrd=1:colorprim={color_primaries}:colormatrix={color_space}:transfer={color_transfer}:range=limited:master-display={x265_master_display_info}\" '\n", 730 | " else:\n", 731 | " commandLine = commandLine + f'-x265-params \"level-idc={level}:aq-mode=2:no-info=1:strong-intra-smoothing=1:bframes=4:b-adapt=2:frame-threads=0:chromaloc=2:repeat-headers=1:colorprim={color_primaries}:colormatrix={color_space}:transfer={color_transfer}:range=limited\" '\n", 732 | " commandLine = commandLine + f'-map 0:a? -c:a copy -map 0:s? -c:s copy '\n", 733 | "\n", 734 | " sourceName = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[0]\n", 735 | " sourceExtention = os.path.splitext(os.path.basename(os.path.abspath(inputFile)))[1]\n", 736 | " sourceFolder = os.path.dirname(os.path.abspath(inputFile))\n", 737 | "\n", 738 | " if saveToSourceLocation:\n", 739 | " outputFile = os.path.join(sourceFolder, (sourceName + f'_[HDR]' + sourceExtention))\n", 740 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 741 | " else:\n", 742 | " outputFolder = input(\"outputFolder: \")\n", 743 | " outputFile = os.path.join(outputFolder, (sourceName + f'_[HDR]' + sourceExtention))\n", 744 | " commandLine = commandLine + '\"' + outputFile + '\"'\n", 745 | "\n", 746 | " !ffmpeg {commandLine}" 747 | ], 748 | "metadata": { 749 | "cellView": "form", 750 | "id": "7uFEw4P87e7s" 751 | }, 752 | "execution_count": null, 753 | "outputs": [] 754 | } 755 | ] 756 | } -------------------------------------------------------------------------------- /Google-Drive-Logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # __FFmpeg-for-Google-Colab__ 2 | 3 | Install latest __FFmpeg__ to the __Google Colab__ runtime. 4 | 5 | Open in Colab 6 | 7 | - Click on the __"Open in Colab"__ button to open this notebook in google colab 8 | 9 | # __Table of Contents__ 10 | 11 | - FFmpeg in Google Colaboratory 12 | 13 | ## Google-Drive-Logo __Mount Google Drive__ 14 | - Mount your google drive to access it's files in colab 15 | 16 | ## FFmpeg-Logo __FFmpeg__ 17 | - Install FFmpeg 18 | 19 | ### __Remux Video Files__ 20 | * **REMUX** video files without **RE-ENCODING**. 21 | * Make sure that `outputFormat` that you have selected is supported for all tracks in the `inputFile`. 22 | * `WebM` only support `VP9`, `VP8`, `AV1` video and `Vorbis`, `Opus` audio and `WebVTT` subtitles. 23 | ### __Trim Video Files__ 24 | * **TRIM** video files without **RE-ENCODING**. 25 | * `inputFile`: video file's path and set `startTime` and `endTime` to trim. 26 | * Trimed video will be saved as same as the **source's format**. 27 | ### __Extract Audio__ 28 | * **EXTRACT** audio tracks from video files. 29 | * `DTS`, `DTS-HD`, `DTS-MA`, `TrueHD` tracks will be muxed as a `.mka` file. 30 | ### __Remove Bitstream Metadata__ 31 | * For `H.264`/`AVC` tracks run first cell. 32 | * For `H.265`/`HEVC` tracks run second cell. 33 | ### __Encode H.264__ 34 | * `CRF` and `2-Pass` encoding available. 35 | * This cell only encodes the **first video track** from the input file. 36 | ### __HDR to SDR__ 37 | * Convert `HDR` video to `SDR` video. 38 | * `toneMap`: Select tone mapping method. 39 | ### __HDR10 Encoding__ 40 | * Only `x265`/`HEVC` tracks are supported. 41 | * `extractColors`: View the color information of the footage. 42 | * When encoding, make sure `extractColors` is **deselected**. 43 | --------------------------------------------------------------------------------