diff --git a/README.rst b/README.rst index 1d4a152..3038d5f 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ ======================================= -syncstart(1) Version 1.1.1 \| syncstart +syncstart(1) Version 1.1.2 \| syncstart ======================================= SYNOPSIS @@ -7,15 +7,16 @@ SYNOPSIS Command line help:: - usage: syncstart [-h] [--version] [-v] [-b BEGIN] [-t TAKE] [-n] [-d] [-l LOWPASS] [-c] [-s] [-q] in1 in2 + usage: syncstart [options] in1 in2 - CLI interface to sync two media files using their audio or video streams. - ffmpeg needs to be available. + CLI program to compute timing offset (seconds) of media file 1 (in1) + in referenceto media file 2 (in2) using their audio or video streams. + ffmpeg is required. positional arguments: - in1 First media file to sync with second. - in2 Second media file to sync with first. + in1 Offset media file. + in2 Reference media file. options: -h, --help show this help message and exit @@ -44,9 +45,9 @@ The steps taken by ``syncstart``: - process and extract sample audio/video clips using ffmpeg with some default and optional filters - read the two clips into a 1D array and apply optional z-score normalization - compute offset via correlation using scipy ifft/fft -- print ffmpeg/ffprobe output or optionally quiet that -- show diagrams to allow MANUAL correction using ZOOM or optionally suppress that -- print result +- print ffmpeg/ffprobe output (optional) +- show diagrams to allow MANUAL correction using ZOOM (optional) +- print result as human readable, or print and return result as CSV MANUAL correction with ZOOM: @@ -79,28 +80,44 @@ INSTALLATION To install for user only, do:: - pip install --user syncstart + pip install --user syncstart Or activate a virtualenv and do:: - pip install syncstart + pip install syncstart + +To make syncstart an executable in your PATH on Windows, do:: + + # Install pip if you don't have it + py -m ensurepip + # Upgrade and ensure pip prefers official wheels + py -m pip install --upgrade pip setuptools wheel + # Install/update pipx + py -m pip install --user --upgrade pipx + # Ensure python binaries are in PATH + py -m pipx ensurepath + pipx install syncstart + # Upgrade numpy inside pipx venv + pipx runpip syncstart install --upgrade numpy + # Test syncstart.exe by reading the version + syncstart --version EXAMPLES -------- :: - # compute audio offset with default settings: - syncstart from_s10.m4a from_gopro.m4p - - # compute audio offset using first 10 seconds with denoising, normalization and a 300 Hz lowpass filter: - syncstart video1.mp4 video2.mkv -t 10 -dnl 300 - - # compute video offset using first 20 seconds, don't show plots, only output final result: - syncstart video1.mp4 video2.mkv -vsq - - # compute video offset using seconds 15 to 25 with denoising, cropping and normalization: - syncstart video1.mp4 video2.mkv -b 15 -t 10 -vdcn + # compute audio offset with default settings: + syncstart from_s10.m4a from_gopro.m4p + + # compute audio offset using first 10 seconds with denoising, normalization and a 300 Hz lowpass filter: + syncstart -t 10 -dnl 300 video1.mp4 video2.mkv + + # compute video offset using first 20 seconds, don\[aq]t show plots, only output final result: + syncstart -vsq video1.mp4 video2.mkv + + # compute video offset using seconds 15 to 25 with denoising, cropping and normalization: + syncstart -b 15 -t 10 -vdcn video1.mp4 video2.mkv License ------- diff --git a/syncstart.py b/syncstart.py index 4011aee..b0130ed 100755 --- a/syncstart.py +++ b/syncstart.py @@ -263,8 +263,7 @@ def on_zoom(event_ax): else: return ff,noff else: - return ff, toff - + return ff, round(toff, 3) def cli_parser(**ka): import argparse @@ -277,11 +276,11 @@ def cli_parser(**ka): if 'in1' not in ka: parser.add_argument( 'in1', - help='First media file to sync with second.') + help='Offset media file.') if 'in2' not in ka: parser.add_argument( 'in2', - help='Second media file to sync with first.') + help='Reference media file.') if 'video' not in ka: parser.add_argument( '-v','--video', @@ -380,7 +379,28 @@ def file_offset(**ka): """Sync two media files using their audio or video streams. ffmpeg is required. """ + ls1 = len(s1) + ls2 = len(s2) + padsize = ls1+ls2+1 + padsize = 2**(int(np.log(padsize)/np.log(2))+1) + s1pad = np.zeros(padsize) + s1pad[:ls1] = s1 + s2pad = np.zeros(padsize) + s2pad[:ls2] = s2 + corr = scipy.fft.ifft(scipy.fft.fft(s1pad)*np.conj(scipy.fft.fft(s2pad))) + ca = np.absolute(corr) + xmax = np.argmax(ca) + offset_samples = xmax + if xmax > padsize // 2: + offset_samples = xmax-padsize + return offset_samples, ca + +def file_offset(**ka): + """CLI program to compute timing offset (seconds) of media file 1 (in1) +in referenceto media file 2 (in2) using their audio or video streams. + ffmpeg is required. + """ parser = cli_parser(**ka) args = parser.parse_args().__dict__ ka.update(args) @@ -398,26 +418,21 @@ def file_offset(**ka): padsize,xmax,correlation = padsize_xmax_correlation(s1,s2) if show: show1(sr,correlation,title='Correlation',v=xmax/sr) sync_text = """ -============================================================================== -%s needs 'ffmpeg -ss %s' cut to get in sync -============================================================================== +============ +The first input is offset %s' seconds. +============ """ - if xmax > padsize // 2: - if show: - file,offset = show2(sr,s1,s2,-(padsize-xmax),in1,in2) - else: - file,offset = in2,(padsize-xmax)/sr + if show: + file, offset_seconds = show2(sr, s1, s2, offset_samples, in1, in2) else: - if show: - file,offset = show2(sr,s1,s2,xmax,in1,in2) - else: - file,offset = in1,xmax/sr + file,offset_seconds = in1,round(offset_samples/sr, 3) if not quiet: #default - print(sync_text%(file,offset)) + print(sync_text%(offset_seconds)) else: #quiet ## print csv: file_to_advance,seconds_to_advance - print("%s,%s"%(file,offset)) - return file,offset + print("%s,%s"%(file,offset_seconds)) + return file,offset_seconds + main = file_offset if __name__ == '__main__':