dumb by default


dir: Home / Posts / ffmpeg - I Have No Admin Privilege …
published-date: 29 Nov 2024 13:22 +0700
categories: [misc]
tags: [ffmpeg]

ffmpeg - I Have No Admin Privilege and I Have To Transcode


A bit of background. A while ago I had to make a powerpoint slides with sets of video and audio sequence for certain event backdrop. Low-cost, readily available software (powerpoint), and easy handover to operators. Problem was, one video with only 3 min runtime, is a humongous 13gb .mov file. On playback it freezes my workstation, and then goes blank indefinitely with no sound playing.

I might’ve bid my adieu from any video production long long ago and my knowledge to the matter is still fairly limited. But a quick glance it’s already telling something clearly had gone wrong on post. Or it might be deliberate for high fidelity viewing on high-end player? Let’s find out.

Needless to say I need to bring the size down to a reasonable number and make it playable on low spec devices. And here’s the catch:

Palms began to sweat, knees weak, arms heavy as I sip cold bitter coffee I brewed that morning. “is 3 PM man you’re done fuuucked” the clock humiliated me as I only have till noon the next day before handover. As I close my eyes I muttered o’ lord x264 AVC codec, dark shikari with yukari pfp god of gensokyo, please give me wisdom on fast reliable portable free handwritten in assembly transcoder. Then it struck me: ffmpeg.

Jest aside I think ffmpeg is underrated, especially it’s most likely lives in most streaming services backends such as youtube (or at least until certain point). No other person I know ever use the command line tool at least once.

That’s why I want to share 2 case where ffmpeg could be useful.

1. General transcoding on your desktop

This is the scenario I had to deal with as in previous story, where I can’t install shit on my work computer.

You may notice on the ffmpeg website (https://ffmpeg.org) that the windows release is on another website as prebuilt binary. I’m using gyan.dev essentials build. If you’re being paranoid on running random .exe on your computer, you can check the hash in powershell

1Get-FileHash path/to/ffmpeg-<version>-essentials_build.7z # or zip

and validate the sha256 accordingly. No malware or any malicious program is being distributed from the site (so far). But hey I trust my sources.

Extract the archive wherever, most conveniently on root C: drive under subfolder. If you have the privilege then add to environment variables. But if you don’t (me) then we’d have to refer the program using full path whenever we need them.

In this example I extracted ffmpeg under directory:

1C:/ffmpeg/bin/ffmpeg.exe

with other program inside such as ffprobe and and ffplay, but we don’t need those at the moment.

A little tangent with ffprobe, we can fetch media info to check the metrics and possibly answer our first question.

1# a little snippet to extract info into a txt file
2# note stderr also piped into the file, I had a
3# problem before that error raised while fetching
4# and the info is being printed out instead
5ffprobe redacted.mov > redacted_info.txt 2> &1

Here are the excerpt of the 13 gig video I mentioned before

 1Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'redacted.mov':
 2  Metadata:
 3    major_brand     : qt  
 4    minor_version   : 537199360
 5    compatible_brands: qt  
 6    creation_time   : 2024-10-07T09:19:55.000000Z
 7  Duration: 00:03:08.24, start: 0.000000, bitrate: 593979 kb/s
 8  Stream #0:0[0x1](eng): Video: prores (HQ) (apch / 0x68637061), yuv422p10le(bt709, progressive), 3840x2160, 592398 kb/s, SAR 1:1 DAR 16:9, 25 fps, 25 tbr, 25 tbn (default)  
 9    Metadata:
10      creation_time   : 2024-10-07T09:19:55.000000Z
11      handler_name    : Apple Video Media Handler
12      vendor_id       : appl
13      encoder         : Apple ProRes 422 HQ
14      timecode        : 00:00:00:00
15  Stream #0:1[0x2](eng): Audio: pcm_s16le (sowt / 0x74776F73), 48000 Hz, stereo, s16, 1536 kb/s (default)
16    Metadata:
17      creation_time   : 2024-10-07T09:19:55.000000Z
18      handler_name    : Apple Sound Media Handler
19      vendor_id       :     
20      timecode        : 00:00:00:00
21  Stream #0:2[0x3](eng): Data: none (tmcd / 0x64636D74) (default)
22    Metadata:
23      creation_time   : 2024-10-07T09:19:55.000000Z
24      handler_name    : Time Code Media Handler
25      timecode        : 00:00:00:00
26Unsupported codec with id 0 for input stream 2

We can get some interesting insights, mainly:

To put into perspective, a generic 4k x265/HEVC rip would’t need to exceed 60mb/s.

I presumed to the whole ordeal was this is a version to be displayed under high-res displays. But now I firmly believe we asked the vendor to export with ultra high preset, and instead we got bloated lossy mpeg-4 media.

To change file format while delegating optimization to ffmpeg, just run the following from your preferred terminal.

1# substitute C:/ffmpeg/bin/ffmpeg.exe with ffmpeg if you have it in env variable
2# also enclosing double quote on path is optional, i think it renders better with quote  
3
4C:/ffmpeg/bin/ffmpeg.exe -y -i "path/to/target/video.mov" "path/to/target/video.mp4"
5
6# where:
7#   -y : overwrite output file if already exists
8#   -i : input url

This was the whole setup I used to compress the video.

1C:/ffmpeg/bin/ffmpeg.exe \
2    -i "path/to/target/redacted.mov" \      # input filepath
3    -y \                                    # set overwrite
4    -vf "setsar=1:1,scale=1920:-2" \        # set video stream filter; downsize 1920 width infer height with -2 while preserving aspect ratio of 16:9, the set aspect ratio "setsar" is optional afaik  
5    -c:v libx264 \                          # set video encoder to x264
6    -c:a aac \                              # set audio encoder to aac
7    -crf 28 \                               # for x264, you can specify constant quality "index"?? ranging from 0-51, where 0 is lossless and 51 is equivalent to youtubepoop deepfried videos  
8    "path/to/target/redacted.mp4"           # output filepath

The resulting transcoded video eats my storage with ONLY 90mb.

In case anyone wondering, here’s ffprobe output:

 1Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'redacted.mp4':
 2  Metadata:
 3    major_brand     : isom
 4    minor_version   : 512
 5    compatible_brands: isomiso2avc1mp41
 6    encoder         : Lavf61.9.100
 7  Duration: 00:03:09.06, start: 0.000000, bitrate: 3784 kb/s
 8  Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], 3651 kb/s, 25 fps, 25 tbr, 12800 tbn (default)  
 9    Metadata:
10      handler_name    : VideoHandler
11      vendor_id       : [0][0][0][0]
12      encoder         : Lavc61.22.100 libx264
13  Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 127 kb/s (default)
14    Metadata:
15      handler_name    : SoundHandler
16      vendor_id       : [0][0][0][0]

It could be better optimized but at this point I’ll take it.

2. Batch transcoding from google drive with colab

Lots of linux distribution is shipped with ffmpeg as far as I know, and vm under google colab is no exception.

The nice thing about colab is that you can mount your google drive to it. With a quick script + subprocess or even with a ffmpeg wrapper, we can get those videos into colab, transcode/demux it, and put it back to drive seamlessly.

This is a trick that I used a few months ago, where I need to re-encode raws into batches of smaller-ish stocks uniformly which I can later efficiently edit them on my pc. Peers who were taking these footages already uploaded theirs to my drive. Problem is that sometimes the raw file sizes can be gigs big, and different phone can have varying quality, resolution, frame-rate, formats, and so on. Transcoding into one agreeable format saves a lot of production time. Moreover it can reduce headaches if suddenly the video editor (Premiere) refuses to work with mov-quicktime due to missing proprietary codecs installed on my system.

The following is a simple script I made to scan a directory on a drive, dupes it, do stuff, and spits back out into destination folder.

 1# I had to rewrite from the original notebook
 2# and I may have miss something, this might not run.
 3# but the gist still stays the same
 4import os
 5import cv2
 6import shutil
 7import traceback
 8from glob import glob
 9from google.colab import drive
10
11drive_dir = '/content/drive'
12drive.mount(drive_dir)
13
14src_dir = os.path.join(drive_dir, 'vidyas/raws')
15res_dir = os.path.join(drive_dir, 'vidyas/preprocessed')
16# use mkdtemp from tempfile if you prefer
17tmp_src_dir = 'temp_src'
18tmp_dst_dir = 'temp_res'
19os.makedirs(tmp_src_dir, exist_ok=True)
20os.makedirs(tmp_dst_dir, exist_ok=True)
21
22for src in glob(src_dir + '/*.*'):
23  filename = ''.join(src.split('.')[:-1])
24  dst = filename + '.mp4'
25  dst_temp = os.path.join(tmp_dst_dir, dst)
26
27  # this is bad practice but I only need this to run successfully once so I'm leaving this here
28  src_temp = src
29  try:
30    src_temp = shutil.copy2(src, tmp_src_dir)
31  except Exception:
32    print(traceback.format_exc())
33    continue
34
35  # optional, in a way this is very much overkill but footages comes from phone and people often record vertically, so...  
36  probe = cv2.VideoCapture(src) # overkill because this part scans the whole media, and it takes time
37  height = probe.get(cv2.CAP_PROP_FRAME_HEIGHT) # at this point you may be wondering why not use opencv for the whole ordeal  
38  width = probe.get(cv2.CAP_PROP_FRAME_WIDTH) # to this day I'm asking myself the same question too
39  is_vertical = height > width
40
41  command = ['ffmpeg']
42  command.append('-y')
43  command.extend(['-i', '"{}"'.format(src_temp)])
44  if is_vertical:
45    print('found vertical')
46    command.extend(['-vf', '"setsar=1:1,scale=-2:1280"'])
47  else:
48    command.extend(['-vf', '"setsar=1:1,scale=1280:-2"'])
49  command.extend(['-r', '30'])
50  command.extend(['-c:v', 'libx264'])
51  command.extend(['-c:a', 'copy'])
52  command.extend(['-crf', '24'])
53  command.append('"{}"'.format(dst_temp))
54  errno = os.system(' '.join(command)) # or use subprocess, whatever suits you
55  
56  if errno == 0:
57    print('success:', dst_temp)
58    shutil.copy2(dst_temp, res_dir)
59  else:
60    raise RuntimeError('somethign had gone horribly wrong: {}'.format(' '.join(command)))
61
62# once you're done, flush and unmount
63drive.flush_and_unmount()

I’m just scratching the surface here, and no way I can memorize all flags and parameter ffmpeg provides. And also other uses other than transcoder to use ffmpeg to its fullest potential. It’s an awesome tool and it deserves more love. You can donate to the project here.





Built with Hugo | previoip (c) 2024