Created
June 12, 2025 14:52
-
-
Save Eun/4dad712bfd3efa2b791abe19e1db48e6 to your computer and use it in GitHub Desktop.
detect resolution of video files and add the resolution in the end of of the file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
use strict; | |
use warnings; | |
use Getopt::Long; | |
use File::Basename; | |
use JSON; | |
# Resolution mapping - maps actual resolutions to category labels | |
my %RESOLUTION_MAP = ( | |
# 2160p (4K) variants | |
'3840x2160' => '2160p', | |
'4096x2160' => '2160p', | |
'3840x1604' => '2160p', | |
'3840x1600' => '2160p', | |
'4096x1714' => '2160p', | |
# 1440p variants (treat as 2160p for our purposes, or add separate category) | |
'2560x1440' => '1440p', | |
'2560x1080' => '1440p', | |
# 1080p variants | |
'1920x1080' => '1080p', | |
'1920x800' => '1080p', | |
'1920x804' => '1080p', | |
'1920x816' => '1080p', | |
'1920x960' => '1080p', | |
'1920x1040' => '1080p', | |
'1800x1080' => '1080p', | |
'1858x1080' => '1080p', | |
# 720p variants | |
'1280x720' => '720p', | |
'1280x534' => '720p', | |
'1280x536' => '720p', | |
'1280x544' => '720p', | |
'1280x694' => '720p', | |
'1366x768' => '720p', | |
# 480p variants | |
'854x480' => '480p', | |
'720x480' => '480p', | |
'640x480' => '480p', | |
'848x480' => '480p', | |
'852x480' => '480p', | |
# 360p variants | |
'640x360' => '360p', | |
'480x360' => '360p', | |
'638x360' => '360p', | |
); | |
# Fallback resolution detection based on height | |
my %HEIGHT_MAP = ( | |
2160 => '2160p', | |
1440 => '1440p', | |
1080 => '1080p', | |
720 => '720p', | |
480 => '480p', | |
360 => '360p', | |
); | |
# Valid resolution categories (excluding 1440p from your original list) | |
my @VALID_RESOLUTIONS = ('2160p', '1080p', '720p', '480p', '360p'); | |
# Command line options | |
my $dry_run = 0; | |
my $replace_existing = 0; | |
my $help = 0; | |
GetOptions( | |
'dry-run|n' => \$dry_run, | |
'replace|r' => \$replace_existing, | |
'help|h' => \$help, | |
) or die "Error in command line arguments\n"; | |
if ($help || @ARGV == 0) { | |
print_usage(); | |
exit 0; | |
} | |
sub print_usage { | |
print <<'EOF'; | |
Usage: $0 [OPTIONS] FILE [FILE...] | |
Rename video files to include resolution in square brackets. | |
Options: | |
-n, --dry-run Show what would be renamed without actually renaming | |
-r, --replace Replace existing resolution tags instead of skipping | |
-h, --help Show this help message | |
Examples: | |
$0 movie.mkv | |
$0 --dry-run *.mp4 | |
$0 --replace --dry-run video1.mkv video2.avi | |
Supported resolutions: 2160p, 1080p, 720p, 480p, 360p | |
EOF | |
} | |
sub get_video_resolution { | |
my ($file) = @_; | |
# Use ffprobe to get video stream information in JSON format | |
my $cmd = qq{ffprobe -v quiet -print_format json -show_streams "$file" 2>/dev/null}; | |
my $output = `$cmd`; | |
return undef unless $output && $? == 0; | |
my $data; | |
eval { | |
$data = decode_json($output); | |
}; | |
return undef if $@; | |
# Find the first video stream | |
for my $stream (@{$data->{streams} || []}) { | |
next unless $stream->{codec_type} && $stream->{codec_type} eq 'video'; | |
my $width = $stream->{width}; | |
my $height = $stream->{height}; | |
next unless defined $width && defined $height; | |
my $resolution_key = "${width}x${height}"; | |
# Check exact resolution mapping first | |
if (exists $RESOLUTION_MAP{$resolution_key}) { | |
my $category = $RESOLUTION_MAP{$resolution_key}; | |
# Only return if it's in our valid list | |
return $category if grep { $_ eq $category } @VALID_RESOLUTIONS; | |
} | |
# Fallback to height-based detection | |
if (exists $HEIGHT_MAP{$height}) { | |
my $category = $HEIGHT_MAP{$height}; | |
return $category if grep { $_ eq $category } @VALID_RESOLUTIONS; | |
} | |
# Try to match height within reasonable range | |
for my $target_height (sort { $b <=> $a } keys %HEIGHT_MAP) { | |
if (abs($height - $target_height) <= 50) { # Allow 50px variance | |
my $category = $HEIGHT_MAP{$target_height}; | |
return $category if grep { $_ eq $category } @VALID_RESOLUTIONS; | |
} | |
} | |
return undef; # Resolution not supported | |
} | |
return undef; # No video stream found | |
} | |
sub extract_existing_resolution { | |
my ($filename) = @_; | |
# Look for pattern like " - [1080p]" or " [720p]" at the end of filename | |
if ($filename =~ /^(.+?)(?:\s*-\s*)?\[(\d+p)\](\.[^.]+)$/) { | |
return ($1, $2, $3); # (base_name, resolution, extension) | |
} | |
return (undef, undef, undef); | |
} | |
sub rename_file { | |
my ($original_file, $resolution) = @_; | |
my ($name, $path, $suffix) = fileparse($original_file, qr/\.[^.]*/); | |
# Check if file already has resolution tag | |
my ($base_name, $existing_res, $ext) = extract_existing_resolution($name . $suffix); | |
if (defined $existing_res) { | |
if (!$replace_existing) { | |
print "SKIP: $original_file (already has resolution tag [$existing_res])\n"; | |
return 'skipped'; | |
} | |
# Use the base name without existing resolution | |
$name = $base_name; | |
$suffix = $ext; | |
} | |
my $new_name = "${name} - [${resolution}]${suffix}"; | |
my $new_file = $path . $new_name; | |
if ($original_file eq $new_file) { | |
print "SKIP: $original_file (is already correct named)\n"; | |
return 'skipped'; | |
} | |
if ($dry_run) { | |
print "DRY-RUN: $original_file -> $new_name\n"; | |
return 'renamed'; | |
} else { | |
if (rename($original_file, $new_file)) { | |
print "RENAMED: $original_file -> $new_name\n"; | |
return 'renamed'; | |
} else { | |
warn "ERROR: Failed to rename $original_file: $!\n"; | |
return 'error'; | |
} | |
} | |
} | |
# Process each file | |
my $renamed_count = 0; | |
my $error_count = 0; | |
my $skipped_count = 0; | |
my $multiple_files = @ARGV > 1; | |
for my $file (@ARGV) { | |
unless (-f $file) { | |
warn "ERROR: File not found: $file\n"; | |
$error_count++; | |
next; | |
} | |
print "Processing: $file\n" if $dry_run; | |
my $resolution = get_video_resolution($file); | |
unless (defined $resolution) { | |
warn "ERROR: Could not determine resolution for: $file\n"; | |
$error_count++; | |
next; | |
} | |
my $result = rename_file($file, $resolution); | |
if ($result eq 'renamed') { | |
$renamed_count++; | |
} elsif ($result eq 'skipped') { | |
$skipped_count++; | |
} elsif ($result eq 'error') { | |
$error_count++; | |
} | |
} | |
# Print summary only when multiple files were processed | |
if ($multiple_files) { | |
print "\n--- SUMMARY ---\n"; | |
print "Files renamed: $renamed_count\n"; | |
print "Files skipped: $skipped_count\n"; | |
print "Files errored: $error_count\n"; | |
print "Total files processed: " . ($renamed_count + $skipped_count + $error_count) . "\n"; | |
} | |
if ($error_count > 0) { | |
print STDERR "\nCompleted with $error_count error(s)\n"; | |
exit 1; | |
} | |
exit 0; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment