#!/usr/bin/perl use strict; # This scripts paves the way for manual fixup of audio sync problems. # It goes into the individual tempdir directories and extracts the # PCM .wav files from the .avi files, adding extra silence then # trimming to insure the length is as close as possible to the video # duration as measured by reading through the video with ffprobe # (doesn't rely on the header which might be wrong, especially when # I clearly have problems in this video file or I wouldn't be resorting # to this nonsense). # # Related scripts: show-packets, find-glitch, make-silence # # After getting videos split where glitches exist, you can use this # to extract the audio and sox to join the audio files as well as any # extra silence needed to get the audio to sync, and ffmpeg to mux # the audio and video back together. # Get PATH set to include this script's directory and other useful bits my $newpath=`dirname $0`; chomp($newpath); $newpath=`$newpath/echo-path`; chomp($newpath); $ENV{'PATH'}=$newpath; sub stream_duration { my $type = shift; my $vid = shift; my $vh; my %attrs; my $rval; if (open($vh, '-|', "ffprobe -count_frames -show_streams $vid 2>/dev/null")) { while (<$vh>) { if (/^\[STREAM\]$/) { undef %attrs; } elsif (/^\[\/STREAM\]$/) { my $codec_type = $attrs{'codec_type'}; my $duration = $attrs{'duration'}; if (defined($codec_type) && ($codec_type eq $type)) { if ($codec_type eq 'video') { my $nb_read_frames=$attrs{'nb_read_frames'}; if (defined($nb_read_frames) && ($nb_read_frames ne 'N/A')) { my $alt_duration = (($nb_read_frames + 0.0) * 1001.0) / 30000.0; if ((! defined($duration)) || ($alt_duration != $duration)) { print "Correcting duration from $duration to $alt_duration\n"; $duration = $alt_duration; } } } if (defined($duration)) { $rval = $duration; } } } elsif (/^([a-zA-Z0-9_]+)=(.+)$/) { my $key = $1; my $val = $2; if (! (($val eq '') || ($val eq 'N/A'))) { $attrs{$key} = $val; } } } } close($vh); return $rval; } sub video_duration { my $vid = shift; return stream_duration('video', $vid); } sub audio_duration { my $vid = shift; return stream_duration('audio', $vid); } # I'm expecting to be run in a directory which has several tempdir-* # subdirectories containing pcm-*.avi files. Find them all. my $dh; opendir($dh, '.') || die "Cannot read current directory\n"; my @dirents = readdir($dh); closedir($dh); my $sd; my @dirnums; foreach $sd (@dirents) { if (-d $sd) { if ($sd=~/^tempdir-(\d+)$/) { push(@dirnums,$1); } } } # Make a handy 10 seconds of silence. unlink('silence.wav'); system('sox', '-n', '-r', '48000', '-c', '2', '-e', 'signed-integer', '-b', '16', 'silence.wav', 'trim', '0', '10'); if (! -f 'silence.wav') { die "Failed to create silence.wav\n"; } # Now process files in each temp directory my $dn; foreach $dn (sort { $a <=> $b } @dirnums) { undef $dh; opendir($dh, "tempdir-$dn") || die "Cannot read directory tempdir-$dn\n"; my @files = readdir($dh); closedir($dh); my $fn; foreach $fn (@files) { if (-f "tempdir-$dn/$fn") { if ($fn=~/^pcm-(.+).avi$/) { my $basename=$1; my $duration = video_duration("tempdir-$dn/pcm-$basename.avi"); print "Extracting audio\n"; unlink("tempdir-$dn/short.wav"); system('ffmpeg', '-i', "tempdir-$dn/pcm-$basename.avi", '-vn', "tempdir-$dn/short.wav"); print "Padding audio\n"; unlink("tempdir-$dn/long.wav"); system('sox', "tempdir-$dn/short.wav", 'silence.wav', "tempdir-$dn/long.wav"); print "Clipping audio to exact length\n"; unlink("tempdir-$dn/pcm-$basename.wav"); system('sox', "tempdir-$dn/long.wav", "tempdir-$dn/pcm-$basename.wav", 'trim', '0', "$duration"); my $audlen = audio_duration("tempdir-$dn/pcm-$basename.wav"); print "tempdir-$dn/pcm-$basename.avi duration is $duration\n"; print "tempdir-$dn/pcm-$basename.wav duration is $audlen\n"; unlink("tempdir-$dn/short.wav"); unlink("tempdir-$dn/long.wav"); } } } } unlink('silence.wav');