Skip to content

Instantly share code, notes, and snippets.

@krzemienski
Forked from ShannonScott/readme.md
Created March 2, 2023 15:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krzemienski/9bc4f547f1d99c9c7bc89a930ff78837 to your computer and use it in GitHub Desktop.
Save krzemienski/9bc4f547f1d99c9c7bc89a930ff78837 to your computer and use it in GitHub Desktop.
[Transcode h265] Use ffmpeg to transcode a video to h.265 / hvc1 which will play natively on a Mac (and Linux). #tags: video, python, ffmpeg

Transcode Video to h.265

Links

What seems to work

ffmpeg -i <input.???> -c:v libx265 -c:a aac -crf 25 -tag:v hvc1 <output.h265.mp4>

Note the need for the -tag:v hvc1 (see H.265 HEVC support in High Sierra for details). Without the tag, VLC opens the video with out issue, but QuickTime Player does not. The tag fixes the issue with QuickTime.

Probe codecs used

Get audio encoder

ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 input.mp4

Get video encoder

ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 input.mp4

Copy audio

ffmpeg -i <infile> -c:v libx265 -crf 28 -c:a copy -tag:v hvc1 -preset slow <outfile>

Transcode audio

ffmpeg -i <infile> -c:v libx265 -crf 28 -c:a aac -b:a 128k -tag:v hvc1 -preset slow <outfile>

Example

Transcode a folder of AVI files:

for filename in *.avi; do
	ffmpeg -i $filename -c:v libx265 -crf 28 -c:a aac -b:a 128k -tag:v hvc1 -preset slow "${filename%.*}.hvc1.mp4"
done

Smart conversion

Avoid converting videos already encoded as HVC1.

for filename in *.{avi,mpeg,mp4,webm}; do
  # Get codecs
  video_codec=$(ffprobe -v error -select_streams 'v:0' -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "${filename}")
  audio_codec=$(ffprobe -v error -select_streams 'a:0' -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "${filename}")

  if [ "$video_codec" = "hevc" ]; then
      echo "Skipping (already HVC1): ${filename}"
  else
      #video_opts="-c:v libx265 -crf 28 -tag:v hvc1 -preset slow"
      if [ "$audio_codec" = "aac" ]; then
          #audio_opts="-c:a copy"
          ffmpeg -i "${filename}" -c:v libx265 -crf 28 -tag:v hvc1 -preset slow -c:a copy "${filename%.*}.hvc1.mp4"
      else
          #audio_opts="-c:a aac -b:a 128k"
          ffmpeg -i "${filename}" -c:v libx265 -crf 28 -tag:v hvc1 -preset slow -c:a aac -b:a 128k "${filename%.*}.hvc1.mp4"
      fi
  fi
done

Links

'''
Transcode videos to H265 / hvc1. Skip any file which is already h265 encoded.
https://trac.ffmpeg.org/wiki/Encode/H.265
'''
import os
import sys
import json
from subprocess import call, check_output
def get_codec(filepath, channel='v:0'):
''' Return the codec and codec-tag for a given channel '''
output = check_output(['ffprobe', '-v', 'error', '-select_streams', channel,
'-show_entries', 'stream=codec_name,codec_tag_string', '-of',
'default=nokey=1:noprint_wrappers=1', filepath])
return output.decode('utf-8').split()
for filepath in sys.argv[1:]:
basefilepath, extension = os.path.splitext(filepath)
output_filepath = basefilepath + '.hvc1' + '.mp4'
assert(output_filepath != filepath)
if os.path.isfile(output_filepath):
print('Skipping "{}": file already exists'.format(output_filepath))
continue
print(filepath)
# Get the video channel codec
video_codec = get_codec(filepath, channel='v:0')
if video_codec == []:
print('Skipping: no video codec reported')
continue
# Video transcode options
if video_codec[0] == 'hevc':
if video_codec[1] == 'hvc1':
print('Skipping: already h265 / hvc1')
continue
else:
# Copy stream to hvc1
video_opts = '-c:v copy -tag:v hvc1'
else:
# Transcode to h265 / hvc1
video_opts = '-c:v libx265 -crf 28 -tag:v hvc1 -preset slow -threads 8'
# Get the audio channel codec
audio_codec = get_codec(filepath, channel='a:0')
if audio_codec == []:
audio_opts = ''
elif audio_codec[0] == 'aac':
audio_opts = '-c:a copy'
else:
audio_opts = '-c:a aac -b:a 128k'
call(['ffmpeg', '-i', filepath] + video_opts.split() + audio_opts.split() + [output_filepath])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment