Add Unlicense and ship ffmpeg's GPL license in release archives

The project is now public domain under the Unlicense (LICENSE, README).

The packaged archives bundle GPL ffmpeg/ffprobe builds but did not include
their license. fetch_ffmpeg.py now extracts the build's LICENSE.txt into
ffmpeg/LICENSE.txt, and build.py --ffmpeg-dir copies it into the archive as
ffmpeg-LICENSE.txt, failing before the PyInstaller run if it can't find one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 18:18:55 +00:00
parent bf0d661eed
commit fb2b5ce91f
4 changed files with 94 additions and 29 deletions

View File

@@ -2,7 +2,8 @@
Uses the GPL builds from https://github.com/BtbN/FFmpeg-Builds (newest stable
release branch). The app finds ./ffmpeg/bin automatically when run from source,
and build.py --ffmpeg-dir ffmpeg/bin bundles them with the executable.
and build.py --ffmpeg-dir ffmpeg/bin bundles them with the executable, together
with the build's license, which is saved as ./ffmpeg/LICENSE.txt.
"""
from __future__ import annotations
@@ -52,27 +53,50 @@ def main() -> int:
archive = Path(tmp) / asset["name"]
with urllib.request.urlopen(asset["browser_download_url"], timeout=60) as r, open(archive, "wb") as f:
shutil.copyfileobj(r, f)
DEST.mkdir(parents=True, exist_ok=True)
wanted = {f"ffmpeg{exe}", f"ffprobe{exe}"}
if archive.suffix == ".zip":
with zipfile.ZipFile(archive) as z:
for info in z.infolist():
name = Path(info.filename).name
if name in wanted and "/bin/" in info.filename:
(DEST / name).write_bytes(z.read(info))
else:
with tarfile.open(archive) as t:
for m in t.getmembers():
name = Path(m.name).name
if name in wanted and "/bin/" in m.name:
with t.extractfile(m) as src, open(DEST / name, "wb") as out:
shutil.copyfileobj(src, out)
(DEST / name).chmod(0o755)
missing = [n for n in wanted if not (DEST / n).is_file()]
extract(archive, exe, DEST)
print(f"Installed ffmpeg and ffprobe into {DEST}, and their license as {DEST.parent / 'LICENSE.txt'}")
return 0
def extract(archive: Path, exe: str, dest: Path) -> None:
"""Copy ffmpeg + ffprobe from the archive's bin/ into dest, and its LICENSE.txt into dest's parent.
A BtbN archive holds one top folder with bin/ and LICENSE.txt in it. ffmpeg is GPL, so the
license has to ship with the binaries; build.py picks it up from there.
"""
dest.mkdir(parents=True, exist_ok=True)
license_out = dest.parent / "LICENSE.txt"
license_out.unlink(missing_ok=True)
wanted = {f"ffmpeg{exe}", f"ffprobe{exe}"}
def target(member: str) -> Path | None:
name = Path(member).name
if name in wanted and "/bin/" in member:
return dest / name
if member.strip("/").count("/") == 1 and name == "LICENSE.txt":
return license_out
return None
if archive.suffix == ".zip":
with zipfile.ZipFile(archive) as z:
for info in z.infolist():
out = target(info.filename)
if out:
out.write_bytes(z.read(info))
else:
with tarfile.open(archive) as t:
for m in t.getmembers():
out = target(m.name) if m.isfile() else None
if out:
with t.extractfile(m) as src, open(out, "wb") as f:
shutil.copyfileobj(src, f)
if out.name in wanted:
out.chmod(0o755)
missing = [n for n in sorted(wanted) if not (dest / n).is_file()]
if not license_out.is_file():
missing.append("LICENSE.txt")
if missing:
raise SystemExit(f"Archive did not contain {missing}")
print(f"Installed ffmpeg and ffprobe into {DEST}")
return 0
if __name__ == "__main__":