Skip to content

Commit d958783

Browse files
committed
Support H.265 codec output
It reduces half of the video size without loosing quality
1 parent 4485109 commit d958783

File tree

2 files changed

+81
-35
lines changed

2 files changed

+81
-35
lines changed

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ Script to reduce the **size of video files** using FFMPEG.
66
Idea of this script: https://coderunner.io/shrink-videos-with-ffmpeg-and-preserve-metadata/
77

88

9-
Which codecs are supported?
9+
Which input codecs are supported?
1010
--------------------------------------
1111
- **H.264**:
1212
- **CRF** (Constant Rate Factor). Basically translates as *"try to keep this quality overall"*, and will use more or less bits at different parts of the video, depending on the content. (the **bitrate* is variable**).
13+
- **Output codec**. Possible options are **H.264** or **H.265** codecs. When using H.265 video is reduced half of its size maintaining the same video quality.
1314
- **Rest of video properties**. They are not modified.
1415
- **Videos with different codecs**:
1516
- They are copied to another folder without being modified
@@ -18,6 +19,9 @@ Which codecs are supported?
1819
How to use
1920
------------
2021
- Install **Python** (3.7 version recommended)
22+
- [Optional] Configure virtualenv:
23+
- Install virtual env: `python3.7 -m venv venv`
24+
- Activate it: `source venv/bin/activate`
2125
- Install **FFmpeg** and add it to system PATH
2226
- Install **FFprobe** (*This is installed on ffmpeg installation by default*)
2327
- Look at script options: `python main.py -h`

main.py

+76-34
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,71 @@ def get_video_metadata(video_path):
4848
'audio': audio_stream}
4949

5050

51+
def reduce_video_using_h264(video_source_path, video_destination_path, crf='23'):
52+
# "copy_unknown" -> "", //if there are streams ffmpeg doesn't know about, still copy them (e.g some GoPro data stuff)
53+
# "map_metadata" -> "0", //copy over the global metadata from the first (only) input
54+
# "map" -> "0", //copy *all* streams found in the file, not just the best audio and video as is the default (e.g. including data)
55+
# "codec" -> "copy", //for all streams, default to just copying as it with no transcoding
56+
# "preset" -> "medium" //fmpeg speed preset to use
57+
#
58+
# "codec:v" -> "libx264", //specifically for the video stream, reencode to x264
59+
# "pix_fmt" -> "yuv420p", //default pix_fmt
60+
# "crf" -> "23" //default constant rate factor for quality. 0-52 where 18 is near visually lossless
61+
#
62+
# "codec:a" -> "libfdk_aac", //specifically for the audio stream, reencode to aac
63+
# "vbr" -> "4" //variable bit rate quality setting
64+
65+
# Use the same pix_fmt than the source video
66+
pix_fmt = video_metadata['video']['pix_fmt']
67+
68+
# Default CRF value
69+
crf = crf or '23'
70+
71+
subprocess.call([FFMPEG_BIN, '-i', video_source_path,
72+
'-copy_unknown',
73+
'-map_metadata', '0',
74+
'-map', '0',
75+
'-codec', 'copy',
76+
'-codec:v', 'libx264',
77+
'-pix_fmt', pix_fmt,
78+
'-preset', 'slow',
79+
'-crf', crf, video_destination_path])
80+
81+
# Preserve file dates that are not in the video metadata. Example: modification_time
82+
preserve_file_dates(source_file=video_source_path,
83+
destination_file=video_destination_path)
84+
85+
86+
def reduce_video_using_h265(video_source_path, video_destination_path, crf='28'):
87+
# Use the same pix_fmt than the source video
88+
pix_fmt = video_metadata['video']['pix_fmt']
89+
90+
# Default CRF value
91+
crf = crf or '28'
92+
93+
subprocess.call([FFMPEG_BIN, '-i', video_source_path,
94+
'-copy_unknown',
95+
'-map_metadata', '0',
96+
'-map', '0',
97+
'-codec', 'copy',
98+
'-codec:v', 'libx265',
99+
'-pix_fmt', pix_fmt,
100+
'-preset', 'slow',
101+
'-crf', crf, video_destination_path])
102+
103+
# Preserve file dates that are not in the video metadata. Example: modification_time
104+
preserve_file_dates(source_file=video_source_path,
105+
destination_file=video_destination_path)
106+
107+
51108
if __name__ == '__main__':
52109
"""
53110
Main operation of this script:
54111
1. NON-H.264 videos are copied to the other_codecs folder without being modified
55112
2. H.264 videos:
56113
2.1 Reduce video quality
57114
2.1.1 Use a fixed quality that the human eye can not detect
115+
2.1.2 Use H.264 or H.265 output code. If H.265 is used, 50 percent of the video is reduced maintaining the same quality
58116
2.2 Preserve FILE metadata (dates...)
59117
3. Invalid video files are copied to failures folder
60118
"""
@@ -65,24 +123,26 @@ def get_video_metadata(video_path):
65123
# Source folder is mandatory
66124
parser.add_argument('source_folder',
67125
help='videos source folder')
126+
parser.add_argument('codec_output',
127+
choices=['h264', 'h265'],
128+
help='output codec to use')
68129
parser.add_argument('--destination_folder',
69130
help='videos destination folder. Default is `source_folder/results`')
70131
parser.add_argument('--failures_folder',
71132
help='videos destination folder. Default is `destination_folder/failures`')
72133
parser.add_argument('--crf',
73-
type=int,
74-
choices=range(0, 51),
75-
default=23,
76-
help='video crf between 1-51`. Default is 23')
134+
type=str,
135+
help='video crf between 0-51`. Default is 23 for h264 and 28 for h265')
77136

78137
args = parser.parse_args()
79138

80139
source_folder = args.source_folder
140+
codec_output = args.codec_output
81141
destination_folder = args.destination_folder or f'{source_folder}/results'
82142
failures_folder = args.failures_folder or f'{destination_folder}/failures'
83143
other_codecs_folder = f'{destination_folder}/other_codecs'
84144
# Because ffmpeg needs a str for CRF
85-
crf = str(args.crf)
145+
crf = args.crf
86146
#
87147
###
88148

@@ -117,35 +177,17 @@ def get_video_metadata(video_path):
117177
if video_metadata['video']['codec_name'] == 'h264':
118178
print(f"Video format detected: {video_metadata['video']['codec_name']}")
119179

120-
# "copy_unknown" -> "", //if there are streams ffmpeg doesn't know about, still copy them (e.g some GoPro data stuff)
121-
# "map_metadata" -> "0", //copy over the global metadata from the first (only) input
122-
# "map" -> "0", //copy *all* streams found in the file, not just the best audio and video as is the default (e.g. including data)
123-
# "codec" -> "copy", //for all streams, default to just copying as it with no transcoding
124-
# "preset" -> "medium" //fmpeg speed preset to use
125-
#
126-
# "codec:v" -> "libx264", //specifically for the video stream, reencode to x264
127-
# "pix_fmt" -> "yuv420p", //default pix_fmt
128-
# "crf" -> "23" //default constant rate factor for quality. 0-52 where 18 is near visually lossless
129-
#
130-
# "codec:a" -> "libfdk_aac", //specifically for the audio stream, reencode to aac
131-
# "vbr" -> "4" //variable bit rate quality setting
132-
133-
# Use the same pix_fmt than the source video
134-
pix_fmt = video_metadata['video']['pix_fmt']
135-
136-
subprocess.call([FFMPEG_BIN, '-i', video_source_path,
137-
'-copy_unknown',
138-
'-map_metadata', '0',
139-
'-map', '0',
140-
'-codec', 'copy',
141-
'-codec:v', 'libx264',
142-
'-pix_fmt', pix_fmt,
143-
'-preset', 'slow',
144-
'-crf', crf, video_destination_path])
145-
146-
# Preserve file dates that are not in the video metadata. Example: modification_time
147-
preserve_file_dates(source_file=video_source_path,
148-
destination_file=video_destination_path)
180+
if codec_output == 'h264':
181+
reduce_video_using_h264(video_source_path=video_source_path,
182+
video_destination_path=video_destination_path,
183+
crf=crf)
184+
elif codec_output == 'h265':
185+
reduce_video_using_h265(video_source_path=video_source_path,
186+
video_destination_path=video_destination_path,
187+
crf=crf)
188+
else:
189+
raise Exception('Output codec not supported')
190+
149191
else:
150192
print(f"Non supported video format detected: {video_metadata['video']['codec_name']}")
151193

0 commit comments

Comments
 (0)