├── FFmpeg-Logo.svg
├── FFmpeg-in-Google-Drive.ipynb
├── Google-Drive-Logo.svg
└── README.md
/FFmpeg-Logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | "
"
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
\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
\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 |
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 | ##
__Mount Google Drive__
14 | - Mount your google drive to access it's files in colab
15 |
16 | ##
__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 |
--------------------------------------------------------------------------------