{"id":13448356,"url":"https://github.com/smacke/ffsubsync","last_synced_at":"2025-12-29T22:04:59.447Z","repository":{"id":37212527,"uuid":"172289097","full_name":"smacke/ffsubsync","owner":"smacke","description":"Automagically synchronize subtitles with video.","archived":false,"fork":false,"pushed_at":"2025-11-25T20:16:54.000Z","size":3877,"stargazers_count":7427,"open_issues_count":76,"forks_count":306,"subscribers_count":73,"default_branch":"master","last_synced_at":"2025-11-27T23:27:06.729Z","etag":null,"topics":["alignment","audio","caption","captions","fast-fourier-transform","ffmpeg","fft","speech-detection","srt","srt-subtitles","string-alignment","subtitle","subtitles","sync","synchronization","vad","video","vlc","vlc-media-player","voice-activity-detection"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/smacke.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.rst","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"smacke"}},"created_at":"2019-02-24T02:56:44.000Z","updated_at":"2025-11-27T16:47:21.000Z","dependencies_parsed_at":"2023-02-10T11:45:50.378Z","dependency_job_id":"c5620345-912f-4c72-823e-9807738c110a","html_url":"https://github.com/smacke/ffsubsync","commit_stats":{"total_commits":358,"total_committers":13,"mean_commits":27.53846153846154,"dds":0.5139664804469274,"last_synced_commit":"bafd232a38769951c14c05669a69b2e8c150e1ae"},"previous_names":["smacke/subsync"],"tags_count":42,"template":false,"template_full_name":null,"purl":"pkg:github/smacke/ffsubsync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smacke%2Fffsubsync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smacke%2Fffsubsync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smacke%2Fffsubsync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smacke%2Fffsubsync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smacke","download_url":"https://codeload.github.com/smacke/ffsubsync/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smacke%2Fffsubsync/sbom","scorecard":{"id":832241,"data":{"date":"2025-08-11","repo":{"name":"github.com/smacke/ffsubsync","commit":"d776a7ae8eb6ae6ef3b7d32ccbef80ddf836be56"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.3,"checks":[{"name":"Maintained","score":0,"reason":"1 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":1,"reason":"Found 3/27 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: resources/lib/win64/VCRUNTIME140_1.dll:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/smacke/ffsubsync/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/smacke/ffsubsync/ci.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:38: update your workflow using https://app.stepsecurity.io/secureworkflow/smacke/ffsubsync/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:69: update your workflow using https://app.stepsecurity.io/secureworkflow/smacke/ffsubsync/ci.yml/master?enable=pin","Warn: pipCommand not pinned by hash: gui/build-windows.sh:8","Warn: pipCommand not pinned by hash: gui/entrypoint-windows.sh:35","Warn: pipCommand not pinned by hash: .github/workflows/ci.yml:43","Warn: pipCommand not pinned by hash: .github/workflows/ci.yml:44","Warn: pipCommand not pinned by hash: .github/workflows/ci.yml:45","Warn: pipCommand not pinned by hash: .github/workflows/ci.yml:46","Warn: pipCommand not pinned by hash: .github/workflows/ci.yml:49","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   0 out of   7 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact 0.4.26 not signed: https://api.github.com/repos/smacke/ffsubsync/releases/180133713","Warn: release artifact 0.4.26 does not have provenance: https://api.github.com/repos/smacke/ffsubsync/releases/180133713"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"20 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2023-292 / GHSA-9w2p-rh8c-v9g5","Warn: Project is vulnerable to: PYSEC-2014-14 / GHSA-652x-xj99-gmcc","Warn: Project is vulnerable to: GHSA-9hjg-9r4m-mvj7","Warn: Project is vulnerable to: GHSA-9wx4-h78v-vm56","Warn: Project is vulnerable to: PYSEC-2014-13 / GHSA-cfj3-7x9c-4p3h","Warn: Project is vulnerable to: PYSEC-2018-28 / GHSA-x84v-xcm2-53pg","Warn: Project is vulnerable to: PYSEC-2024-48 / GHSA-fj7x-q9j7-g6q6","Warn: Project is vulnerable to: PYSEC-2021-142 / GHSA-8q59-q68h-6hv4","Warn: Project is vulnerable to: PYSEC-2018-49 / GHSA-rprw-h62v-c2w7","Warn: Project is vulnerable to: PYSEC-2021-856 / GHSA-5545-2q6w-2gh6","Warn: Project is vulnerable to: GHSA-6p56-wp2h-9hxr","Warn: Project is vulnerable to: PYSEC-2019-108 / GHSA-9fq2-x9r6-wfmf","Warn: Project is vulnerable to: PYSEC-2021-857 / GHSA-f7c7-j99h-c22f","Warn: Project is vulnerable to: GHSA-fpfv-jqm9-f5jm","Warn: Project is vulnerable to: PYSEC-2017-1 / GHSA-frgw-fgh6-9g52","Warn: Project is vulnerable to: PYSEC-2013-22 / GHSA-27x4-j476-jp5f","Warn: Project is vulnerable to: PYSEC-2025-49 / GHSA-5rjg-fvgr-3xxf","Warn: Project is vulnerable to: GHSA-cx63-2mw6-8hw5","Warn: Project is vulnerable to: PYSEC-2022-43012 / GHSA-r9hx-vwmv-q579","Warn: Project is vulnerable to: PYSEC-2017-74"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 6 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-23T18:03:16.206Z","repository_id":37212527,"created_at":"2025-08-23T18:03:16.206Z","updated_at":"2025-08-23T18:03:16.206Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28121449,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-29T02:00:07.021Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alignment","audio","caption","captions","fast-fourier-transform","ffmpeg","fft","speech-detection","srt","srt-subtitles","string-alignment","subtitle","subtitles","sync","synchronization","vad","video","vlc","vlc-media-player","voice-activity-detection"],"created_at":"2024-07-31T05:01:43.054Z","updated_at":"2025-12-29T22:04:59.405Z","avatar_url":"https://github.com/smacke.png","language":"Python","readme":"FFsubsync\n=======\n\n[![CI Status](https://github.com/smacke/ffsubsync/workflows/ffsubsync/badge.svg)](https://github.com/smacke/ffsubsync/actions)\n[![Support Ukraine](https://badgen.net/badge/support/UKRAINE/?color=0057B8\u0026labelColor=FFD700)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)\n[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![License: MIT](https://img.shields.io/badge/License-MIT-maroon.svg)](https://opensource.org/licenses/MIT)\n[![Python Versions](https://img.shields.io/pypi/pyversions/ffsubsync.svg)](https://pypi.org/project/ffsubsync)\n[![Documentation Status](https://readthedocs.org/projects/ffsubsync/badge/?version=latest)](https://ffsubsync.readthedocs.io/en/latest/?badge=latest)\n[![PyPI Version](https://img.shields.io/pypi/v/ffsubsync.svg)](https://pypi.org/project/ffsubsync)\n\n\nLanguage-agnostic automatic synchronization of subtitles with video, so that\nsubtitles are aligned to the correct starting point within the video.\n\nTurn this:                       |  Into this:\n:-------------------------------:|:-------------------------:\n![](https://raw.githubusercontent.com/smacke/ffsubsync/master/resources/img/tearing-me-apart-wrong.gif)  |  ![](https://raw.githubusercontent.com/smacke/ffsubsync/master/resources/img/tearing-me-apart-correct.gif)\n\nHelping Development\n-------------------\nPlease consider [supporting Ukraine](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)\nrather than donating directly to this project. That said, at the request of\nsome, you can now help cover my coffee expenses using the Github Sponsors\nbutton at the top, or using the below Paypal Donate button:\n\n[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=XJC5ANLMYECJE)\n\nInstall\n-------\nFirst, make sure ffmpeg is installed. On MacOS, this looks like:\n~~~\nbrew install ffmpeg\n~~~\n(Windows users: make sure `ffmpeg` is on your path and can be referenced\nfrom the command line!)\n\nNext, grab the package (compatible with Python \u003e= 3.6):\n~~~\npip install ffsubsync\n~~~\nIf you want to live dangerously, you can grab the latest version as follows:\n~~~\npip install git+https://github.com/smacke/ffsubsync@latest\n~~~\n\nUsage\n-----\n`ffs`, `subsync` and `ffsubsync` all work as entrypoints:\n~~~\nffs video.mp4 -i unsynchronized.srt -o synchronized.srt\n~~~\n\nThere may be occasions where you have a correctly synchronized srt file in a\nlanguage you are unfamiliar with, as well as an unsynchronized srt file in your\nnative language. In this case, you can use the correctly synchronized srt file\ndirectly as a reference for synchronization, instead of using the video as the\nreference:\n\n~~~\nffsubsync reference.srt -i unsynchronized.srt -o synchronized.srt\n~~~\n\n`ffsubsync` uses the file extension to decide whether to perform voice activity\ndetection on the audio or to directly extract speech from an srt file.\n\nSync Issues\n-----------\nIf the sync fails, the following recourses are available:\n- Try to sync assuming identical video / subtitle framerates by passing\n  `--no-fix-framerate`;\n- Try passing `--gss` to use [golden-section search](https://en.wikipedia.org/wiki/Golden-section_search)\n  to find the optimal ratio between video and subtitle framerates (by default,\n  only a few common ratios are evaluated);\n- Try a value of `--max-offset-seconds` greater than the default of 60, in the\n  event that the subtitles are out of sync by more than 60 seconds (empirically\n  unlikely in practice, but possible).\n- Try `--vad=auditok` since [auditok](https://github.com/amsehili/auditok) can\n  sometimes work better in the case of low-quality audio than WebRTC's VAD.\n  Auditok does not specifically detect voice, but instead detects all audio;\n  this property can yield suboptimal syncing behavior when a proper VAD can\n  work well, but can be effective in some cases.\n\nIf the sync still fails, consider trying one of the following similar tools:\n- [sc0ty/subsync](https://github.com/sc0ty/subsync): does speech-to-text and looks for matching word morphemes\n- [kaegi/alass](https://github.com/kaegi/alass): rust-based subtitle synchronizer with a fancy dynamic programming algorithm\n- [tympanix/subsync](https://github.com/tympanix/subsync): neural net based approach that optimizes directly for alignment when performing speech detection\n- [oseiskar/autosubsync](https://github.com/oseiskar/autosubsync): performs speech detection with bespoke spectrogram + logistic regression\n- [pums974/srtsync](https://github.com/pums974/srtsync): similar approach to ffsubsync (WebRTC's VAD + FFT to maximize signal cross correlation)\n\nSpeed\n-----\n`ffsubsync` usually finishes in 20 to 30 seconds, depending on the length of\nthe video. The most expensive step is actually extraction of raw audio. If you\nalready have a correctly synchronized \"reference\" srt file (in which case audio\nextraction can be skipped), `ffsubsync` typically runs in less than a second.\n\nHow It Works\n------------\nThe synchronization algorithm operates in 3 steps:\n1. Discretize both the video file's audio stream and the subtitles into 10ms\n   windows.\n2. For each 10ms window, determine whether that window contains speech.  This\n   is trivial to do for subtitles (we just determine whether any subtitle is\n   \"on\" during each time window); for the audio stream, use an off-the-shelf\n   voice activity detector (VAD) like\n   the one built into [webrtc](https://webrtc.org/).\n3. Now we have two binary strings: one for the subtitles, and one for the\n   video.  Try to align these strings by matching 0's with 0's and 1's with\n   1's. We score these alignments as (# video 1's matched w/ subtitle 1's) - (#\n   video 1's matched with subtitle 0's).\n\nThe best-scoring alignment from step 3 determines how to offset the subtitles\nin time so that they are properly synced with the video. Because the binary\nstrings are fairly long (millions of digits for video longer than an hour), the\nnaive O(n^2) strategy for scoring all alignments is unacceptable. Instead, we\nuse the fact that \"scoring all alignments\" is a convolution operation and can\nbe implemented with the Fast Fourier Transform (FFT), bringing the complexity\ndown to O(n log n).\n\nLimitations\n-----------\nIn most cases, inconsistencies between video and subtitles occur when starting\nor ending segments present in video are not present in subtitles, or vice versa.\nThis can occur, for example, when a TV episode recap in the subtitles was pruned\nfrom video. FFsubsync typically works well in these cases, and in my experience\nthis covers \u003e95% of use cases. Handling breaks and splits outside of the beginning\nand ending segments is left to future work (see below).\n\nFuture Work\n-----------\nBesides general stability and usability improvements, one line\nof work aims to extend the synchronization algorithm to handle splits\n/ breaks in the middle of video not present in subtitles (or vice versa).\nDeveloping a robust solution will take some time (assuming one is possible).\nSee [#10](https://github.com/smacke/ffsubsync/issues/10) for more details.\n\nHistory\n-------\nThe implementation for this project was started during HackIllinois 2019, for\nwhich it received an **_Honorable Mention_** (ranked in the top 5 projects,\nexcluding projects that won company-specific prizes).\n\nCredits\n-------\nThis project would not be possible without the following libraries:\n- [ffmpeg](https://www.ffmpeg.org/) and the [ffmpeg-python](https://github.com/kkroening/ffmpeg-python) wrapper, for extracting raw audio from video\n- VAD from [webrtc](https://webrtc.org/) and the [py-webrtcvad](https://github.com/wiseman/py-webrtcvad) wrapper, for speech detection\n- [srt](https://pypi.org/project/srt/) for operating on [SRT files](https://en.wikipedia.org/wiki/SubRip#SubRip_text_file_format)\n- [numpy](http://www.numpy.org/) and, indirectly, [FFTPACK](https://www.netlib.org/fftpack/), which powers the FFT-based algorithm for fast scoring of alignments between subtitles (or subtitles and video)\n- Other excellent Python libraries like [argparse](https://docs.python.org/3/library/argparse.html), [rich](https://github.com/willmcgugan/rich), and [tqdm](https://tqdm.github.io/), not related to the core functionality, but which enable much better experiences for developers and users.\n\n# License\nCode in this project is [MIT licensed](https://opensource.org/licenses/MIT).\n","funding_links":["https://github.com/sponsors/smacke","https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick\u0026hosted_button_id=XJC5ANLMYECJE"],"categories":["Python","HarmonyOS","语音合成","audio","Images / Videos Processing"],"sub_categories":["Windows Manager","资源传输下载","Subtitles"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmacke%2Fffsubsync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmacke%2Fffsubsync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmacke%2Fffsubsync/lists"}