Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Idea of this script: https://coderunner.io/shrink-videos-with-ffmpeg-and-preser

Which input codecs are supported?
--------------------------------------
- **H.264**:
- **H.264 & H.265**:
- **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**).
- **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.
- **Rest of video properties**. They are not modified.
Expand Down Expand Up @@ -44,9 +44,9 @@ Original video metadata will be copied to the new modified video:
- **Container metadata**. All the original container metadata is copied using ffmpeg `-map_metadata` option
- **FILE dates**. Access and modification file dates.

### What happens if I have h.264 videos and another videos which use different codecs in the same folder?
### What happens if I have h.264 & h.265 videos and another videos which use different codecs in the same folder?

Non-h.264 videos will be copied to the destination_folder/other_codecs by default **without being modified**
Non `h.264` or `h.265` videos will be copied to the destination_folder/other_codecs by default **without being modified**

### What happens if in the middle of the process there is a failure with one video?

Expand Down Expand Up @@ -117,3 +117,8 @@ If there were frames with variable delta, than it will also show min and max del

#### FFMPEG uses CFR (Constant Frame Rate) by default for MP4 output
https://trac.ffmpeg.org/wiki/ChangingFrameRate

### H.265

#### Tips about H.265
https://trac.ffmpeg.org/wiki/Encode/H.265
134 changes: 79 additions & 55 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def get_video_metadata(video_path):
'audio': audio_stream}


def reduce_video_using_h264(video_source_path, video_destination_path, crf='23'):
def reduce_video_using_h264(video_source_path, video_destination_path, pix_fmt, crf='23'):
# "copy_unknown" -> "", //if there are streams ffmpeg doesn't know about, still copy them (e.g some GoPro data stuff)
# "map_metadata" -> "0", //copy over the global metadata from the first (only) input
# "map" -> "0", //copy *all* streams found in the file, not just the best audio and video as is the default (e.g. including data)
Expand All @@ -62,16 +62,14 @@ def reduce_video_using_h264(video_source_path, video_destination_path, crf='23')
# "codec:a" -> "libfdk_aac", //specifically for the audio stream, reencode to aac
# "vbr" -> "4" //variable bit rate quality setting

# Use the same pix_fmt than the source video
pix_fmt = video_metadata['video']['pix_fmt']

# Default CRF value
crf = crf or '23'

subprocess.call([FFMPEG_BIN, '-i', video_source_path,
'-copy_unknown',
'-map_metadata', '0',
'-map', '0',
'-map', '-0:d',
'-codec', 'copy',
'-codec:v', 'libx264',
'-pix_fmt', pix_fmt,
Expand All @@ -83,9 +81,7 @@ def reduce_video_using_h264(video_source_path, video_destination_path, crf='23')
destination_file=video_destination_path)


def reduce_video_using_h265(video_source_path, video_destination_path, crf='28'):
# Use the same pix_fmt than the source video
pix_fmt = video_metadata['video']['pix_fmt']
def reduce_video_using_h265(video_source_path, video_destination_path, pix_fmt, crf='28'):

# Default CRF value
crf = crf or '28'
Expand All @@ -94,6 +90,7 @@ def reduce_video_using_h265(video_source_path, video_destination_path, crf='28')
'-copy_unknown',
'-map_metadata', '0',
'-map', '0',
'-map', '-0:d',
'-codec', 'copy',
'-codec:v', 'libx265',
'-pix_fmt', pix_fmt,
Expand All @@ -105,6 +102,80 @@ def reduce_video_using_h265(video_source_path, video_destination_path, crf='28')
destination_file=video_destination_path)


def reduce_video(source_folder, codec_output, destination_folder, failures_folder, other_codecs_folder, crf, entry):
try:
print(entry.name)

video_source_path = f'{source_folder}/{entry.name}'
video_destination_path = f'{destination_folder}/{entry.name}'

video_metadata = get_video_metadata(video_source_path)
pix_fmt = video_metadata['video']['pix_fmt']

# Only process videos with these codecs (at this moment)
if video_metadata['video']['codec_name'] in ['h264', 'hevc']:
print(f"Video format detected: {video_metadata['video']['codec_name']}")


# Use the same pix_fmt than the source video
if codec_output == 'h264':
reduce_video_using_h264(
video_source_path=video_source_path,
video_destination_path=video_destination_path,
pix_fmt=pix_fmt,
crf=crf
)
elif codec_output == 'h265':
reduce_video_using_h265(
video_source_path=video_source_path,
video_destination_path=video_destination_path,
pix_fmt=pix_fmt,
crf=crf
)
else:
raise Exception('Output codec not supported')

else:
print(f"Non supported video format detected: {video_metadata['video']['codec_name']}")

# Create folder if it does not exist
if not os.path.exists(other_codecs_folder):
os.makedirs(other_codecs_folder)

video_other_codecs_path = f'{other_codecs_folder}/{entry.name}'
# Copy files with other video formats
shutil.copy2(video_source_path, video_other_codecs_path)
except Exception as exception:
# Create failures folder if it does not exist
if not os.path.exists(failures_folder):
os.makedirs(failures_folder)

video_failure_path = f'{failures_folder}/{entry.name}'
# Copy files that have raised an exception to the failure folder
shutil.copy2(video_source_path, video_failure_path)

# Show exception stack trace
traceback.print_exc()


def crawl(source_folder, codec_output, destination_folder, failures_folder, other_codecs_folder, crf):
with os.scandir(source_folder) as entries:
for entry in entries:
if entry.is_file():
reduce_video(
source_folder,
codec_output,
destination_folder,
failures_folder,
other_codecs_folder,
crf,
entry
)
elif entry.is_dir() and entry.path != destination_folder:
crawl(entry.path, codec_output, destination_folder, failures_folder, other_codecs_folder, crf)



if __name__ == '__main__':
"""
Main operation of this script:
Expand Down Expand Up @@ -161,51 +232,4 @@ def reduce_video_using_h265(video_source_path, video_destination_path, crf='28')
os.makedirs(destination_folder)

# Process videos
with os.scandir(source_folder) as entries:
for entry in entries:
if entry.is_file():
try:

print(entry.name)

video_source_path = f'{source_folder}/{entry.name}'
video_destination_path = f'{destination_folder}/{entry.name}'

video_metadata = get_video_metadata(video_source_path)

# Only process videos with this codec (at this moment)
if video_metadata['video']['codec_name'] == 'h264':
print(f"Video format detected: {video_metadata['video']['codec_name']}")

if codec_output == 'h264':
reduce_video_using_h264(video_source_path=video_source_path,
video_destination_path=video_destination_path,
crf=crf)
elif codec_output == 'h265':
reduce_video_using_h265(video_source_path=video_source_path,
video_destination_path=video_destination_path,
crf=crf)
else:
raise Exception('Output codec not supported')

else:
print(f"Non supported video format detected: {video_metadata['video']['codec_name']}")

# Create folder if it does not exist
if not os.path.exists(other_codecs_folder):
os.makedirs(other_codecs_folder)

video_other_codecs_path = f'{other_codecs_folder}/{entry.name}'
# Copy files with other video formats
shutil.copy2(video_source_path, video_other_codecs_path)
except Exception as exception:
# Create failures folder if it does not exist
if not os.path.exists(failures_folder):
os.makedirs(failures_folder)

video_failure_path = f'{failures_folder}/{entry.name}'
# Copy files that have raised an exception to the failure folder
shutil.copy2(video_source_path, video_failure_path)

# Show exception stack trace
traceback.print_exc()
crawl(source_folder, codec_output, destination_folder, failures_folder, other_codecs_folder, crf)