Skip to content

Commit 9e63a86

Browse files
committed
completed all functionality
1 parent a1342e6 commit 9e63a86

File tree

9 files changed

+262
-77
lines changed

9 files changed

+262
-77
lines changed

src/ffmpeg_downloader/__init__.py

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,112 @@
11
__version__ = "0.0.0"
2+
__all__ = [
3+
"ffmpeg_dir",
4+
"ffmpeg_version",
5+
"ffmpeg_path",
6+
"ffprobe_path",
7+
"has_update",
8+
"update",
9+
"remove",
10+
]
211

12+
from os import listdir, path, rmdir, name as os_name
13+
from shutil import rmtree
314
import sys
15+
from appdirs import user_data_dir
16+
417

518
if sys.platform in ("win32", "cygwin"):
6-
from ._win32 import download_n_install, get_version
19+
from ._win32 import (
20+
download_n_install,
21+
get_version as get_latest_version,
22+
get_bindir,
23+
home_url,
24+
)
725
elif sys.platform == "darwin":
8-
from ._macos import download_n_install, get_version
26+
from ._macos import (
27+
download_n_install,
28+
get_version as get_latest_version,
29+
get_bindir,
30+
home_url,
31+
)
932
else:
10-
from ._linux import download_n_install, get_version
33+
from ._linux import (
34+
download_n_install,
35+
get_version as get_latest_version,
36+
get_bindir,
37+
home_url,
38+
)
39+
40+
disclaimer_text = f"""
41+
You are about to download the latest FFmpeg release build from {home_url}.
42+
Proceeding to download the file is done at your own discretion and risk and
43+
with agreement that you will be solely responsible for any damage to your
44+
computer system or loss of data that results from such activities.
45+
46+
Do you wish to proceed to download? [yN] """
47+
48+
donation_text = f"""
49+
Start downloading...
50+
51+
Please remember that to maintain and host the FFmpeg binaries is not free.
52+
If you appreciate their effort, please consider donating to help them with
53+
the upkeep of their website via {home_url}.
54+
"""
55+
56+
57+
def get_dir():
58+
return user_data_dir("ffmpeg_downloader", "ffmpegio")
59+
60+
61+
def _ffmpeg_dir():
62+
return get_bindir(get_dir())
63+
64+
65+
def _ffmpeg_version():
66+
try:
67+
with open(path.join(get_dir(), "VERSION"), "rt") as f:
68+
return f.read()
69+
except:
70+
return None
71+
72+
73+
def __getattr__(name): # per PEP 562
74+
try:
75+
return {
76+
"ffmpeg_dir": _ffmpeg_dir(),
77+
"ffmpeg_path": lambda: _ffmpeg_version()
78+
and path.join(_ffmpeg_dir(), "ffmpeg" if os_name != "nt" else "ffmpeg.exe"),
79+
"ffprobe_path": lambda: _ffmpeg_version()
80+
and path.join(_ffmpeg_dir(), "ffprobe" if os_name != "nt" else "ffprobe.exe"),
81+
"ffmpeg_version": _ffmpeg_version,
82+
}[name]()
83+
except:
84+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
85+
86+
87+
def has_update():
88+
current = __getattr__("ffmpeg_version")
89+
latest = get_latest_version()
90+
return current != latest
91+
92+
93+
def update(skip_disclaimer=False, progress=None, **options):
94+
if has_update():
95+
96+
if not skip_disclaimer:
97+
ans = input(disclaimer_text)
98+
if ans.lower() not in ("y", "yes"):
99+
print("\ndownload canceled")
100+
return
101+
print(donation_text)
102+
download_n_install(get_dir(), progress, **options)
103+
return False
104+
return True
105+
106+
107+
def remove(ignore_errors=True):
108+
dir = get_dir()
109+
rmtree(dir, ignore_errors=ignore_errors)
110+
dir = path.dirname(dir)
111+
if not len(listdir(dir)):
112+
rmdir(dir)

src/ffmpeg_downloader/__main__.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import argparse
2+
import ffmpeg_downloader as ffdl
3+
import tqdm
4+
5+
parser = argparse.ArgumentParser(description="Download latest FFmpeg release build")
6+
parser.add_argument(
7+
"--update", "-U", "-u", action="store_true", help="Update to the latest release"
8+
)
9+
parser.add_argument(
10+
"--remove",
11+
"-r",
12+
"-R",
13+
"--delete",
14+
"-d",
15+
"-D",
16+
action="store_true",
17+
help="Remove downloaded FFmpeg build",
18+
)
19+
20+
args = parser.parse_args()
21+
22+
ver = ffdl.ffmpeg_version
23+
24+
if args.remove:
25+
if ver is None:
26+
print("\nNo FFmpeg build has been downloaded.")
27+
elif input(f"\nAre you sure to remove the FFmpeg build v{ver}? [yN]: ").lower() in (
28+
"y",
29+
"yes",
30+
):
31+
try:
32+
ffdl.remove(ignore_errors=False)
33+
print("\nFFmpeg build successfully removed.")
34+
except:
35+
print(
36+
f"\nFailed to delete FFmpeg build in {ffdl.ffmpeg_dir}. Please manually remove the directory."
37+
)
38+
else:
39+
print("\nFFmpeg build removal canceled.")
40+
elif ver is None or args.update or ffdl.has_update():
41+
prog_data = {}
42+
try:
43+
44+
def progress(nread, nbytes):
45+
if not len(prog_data):
46+
prog_data["tqdm"] = tqdm.tqdm(
47+
desc="downloading", total=nbytes, unit=" bytes", leave=False
48+
)
49+
prog_data["last"] = 0
50+
pbar = prog_data["tqdm"]
51+
pbar.update(nread - prog_data["last"])
52+
prog_data["last"] = nread
53+
54+
if ffdl.update(progress=progress):
55+
print("\nLatest version")
56+
else:
57+
print(f"FFmpeg v{ffdl.ffmpeg_version} successfully installed in {ffdl.ffmpeg_dir}\n")
58+
finally:
59+
pbar = prog_data.get("tqdm", None)
60+
if pbar:
61+
pbar.close()
62+
else:
63+
print(f"FFmpeg v{ffdl.ffmpeg_version} already installed")

src/ffmpeg_downloader/_download_helper.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@ def download_info(url, content_type, ctx=None):
2424

2525
def download_file(outfile, url, content_type, ctx=None, progress=None):
2626

27-
if progress:
28-
progress(0, 1)
29-
3027
with download_base(url, content_type, ctx) as (response, nbytes):
28+
if progress:
29+
progress(0, nbytes)
3130

3231
blksz = nbytes // 32 or 1024 * 1024
3332
with open(outfile, "wb") as f:
@@ -41,7 +40,7 @@ def download_file(outfile, url, content_type, ctx=None, progress=None):
4140
if progress:
4241
progress(nread, nbytes)
4342

44-
return outfile
43+
return nbytes
4544

4645

4746
def chmod(binfile):

src/ffmpeg_downloader/_linux.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33
import re, ssl, tarfile, os, shutil
44
from os import path
55

6-
ctx = ssl.create_default_context()
7-
ctx.check_hostname = False
8-
ctx.verify_mode = ssl.CERT_NONE
6+
home_url = "https://johnvansickle.com/ffmpeg"
97

108

119
def get_version():
1210

11+
ctx = ssl.create_default_context()
12+
ctx.check_hostname = False
13+
ctx.verify_mode = ssl.CERT_NONE
14+
1315
return re.search(
1416
r"version: (\d+\.\d+(?:\.\d+)?)",
1517
download_info(
16-
"https://johnvansickle.com/ffmpeg/release-readme.txt",
18+
f"{home_url}/release-readme.txt",
1719
"text/plain",
1820
ctx,
1921
),
@@ -27,9 +29,13 @@ def download_n_install(install_dir, progress=None, arch=None):
2729
elif arch not in archs:
2830
raise ValueError(f"Invalid arch specified. Must be one of {arch}")
2931

32+
ctx = ssl.create_default_context()
33+
ctx.check_hostname = False
34+
ctx.verify_mode = ssl.CERT_NONE
35+
3036
with TemporaryDirectory() as tmpdir:
3137
tarpath = path.join(tmpdir, "ffmpeg_linux.tar.xz")
32-
url = f"https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-{arch}-static.tar.xz"
38+
url = f"{home_url}/releases/ffmpeg-release-{arch}-static.tar.xz"
3339

3440
with TemporaryDirectory() as tmpdir:
3541
download_file(tarpath, url, "application/x-xz", ctx, progress=progress)
@@ -50,9 +56,9 @@ def download_n_install(install_dir, progress=None, arch=None):
5056

5157
shutil.move(src_dir_path, install_dir)
5258

53-
ffmpegpath = path.join(install_dir, "ffmpeg")
54-
ffprobepath = path.join(install_dir, "ffprobe")
55-
chmod(ffmpegpath)
56-
chmod(ffprobepath)
59+
for cmd in ("ffmpeg", "ffprobe"):
60+
chmod(path.join(install_dir, cmd))
61+
5762

58-
return ffmpegpath, ffprobepath
63+
def get_bindir(install_dir):
64+
return install_dir

src/ffmpeg_downloader/_macos.py

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,58 @@
11
from tempfile import TemporaryDirectory
22
from os import path
33
import json, os, zipfile
4-
from ._download_helper import download_info, download_file, chmod
4+
from ._download_helper import download_info, download_file, chmod, download_base
5+
6+
home_url = "https://evermeet.cx/ffmpeg"
57

68

79
def get_version():
810
return json.loads(
9-
download_info(
10-
"https://evermeet.cx/ffmpeg/info/ffmpeg/release", "application/json"
11-
)
11+
download_info(f"{home_url}/info/ffmpeg/release", "application/json")
1212
)["version"]
1313

1414

1515
def download_n_install(install_dir, progress=None):
1616

17+
ntotal = 0
18+
19+
nfiles = [
20+
download_base(f"{home_url}/getrelease/{cmd}/zip")
21+
for cmd in ("ffmpeg", "ffprobe")
22+
]
23+
ntotal = sum(nfiles)
24+
n1 = nfiles[0]
25+
prog = (
26+
lambda nread, nbytes: progress((nread + n1) if nbytes != n1 else nread, ntotal)
27+
if progress is not None
28+
else None
29+
)
30+
1731
with TemporaryDirectory() as tmpdir:
18-
zipffmpegpath = path.join(tmpdir, "ffmpeg_macos.zip")
19-
download_file(
20-
zipffmpegpath,
21-
"https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip",
22-
"application/zip",
23-
progress=progress,
24-
)
25-
zipffprobepath = path.join(tmpdir, "ffprobe_macos.zip")
26-
download_file(
27-
zipffprobepath,
28-
"https://evermeet.cx/ffmpeg/getrelease/ffprobe/zip",
29-
"application/zip",
30-
progress=progress,
31-
)
32-
33-
with zipfile.ZipFile(zipffmpegpath, "r") as f:
34-
f.extractall(tmpdir)
35-
with zipfile.ZipFile(zipffprobepath, "r") as f:
36-
f.extractall(tmpdir)
37-
38-
dst_ffmpeg = path.join(install_dir, "ffmpeg")
39-
try:
40-
os.remove(dst_ffmpeg)
41-
except:
42-
pass
43-
os.rename(path.join(tmpdir, "ffmpeg"), dst_ffmpeg)
44-
chmod(dst_ffmpeg)
45-
46-
dst_ffprobe = path.join(install_dir, "ffprobe")
47-
try:
48-
os.remove(dst_ffprobe)
49-
except:
50-
pass
51-
os.rename(path.join(tmpdir, "ffprobe"), dst_ffprobe)
52-
chmod(dst_ffprobe)
32+
33+
for cmd in ("ffmpeg", "ffprobe"):
34+
zippath = path.join(tmpdir, f"{cmd}.zip")
35+
download_file(
36+
zippath,
37+
f"{home_url}/getrelease/{cmd}/zip",
38+
"application/zip",
39+
progress=prog,
40+
)
41+
42+
with zipfile.ZipFile(zippath, "r") as f:
43+
f.extractall(tmpdir)
44+
45+
dst_path = path.join(install_dir, cmd)
46+
try:
47+
os.remove(dst_path)
48+
except:
49+
pass
50+
os.rename(path.join(tmpdir, cmd), dst_path)
51+
chmod(dst_path)
5352

5453
with open(path.join(install_dir, "VERSION"), "wt") as f:
5554
f.write(get_version())
5655

57-
return dst_ffmpeg, dst_ffprobe
56+
57+
def get_bindir(install_dir):
58+
return install_dir

src/ffmpeg_downloader/_win32.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
from os import path
66
import zipfile
77

8+
home_url = "https://www.gyan.dev/ffmpeg/builds"
9+
810

911
def get_version():
1012
return download_info(
11-
"https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip.ver",
13+
f"{home_url}/ffmpeg-release-essentials.zip.ver",
1214
"text/plain",
1315
)
1416

@@ -23,7 +25,7 @@ def download_n_install(install_dir, progress=None, build_type=None):
2325

2426
with TemporaryDirectory() as tmpdir:
2527
zippath = path.join(tmpdir, "ffmpeg_win32.zip")
26-
url = f"https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-{build_type}.zip"
28+
url = f"{home_url}/ffmpeg-release-{build_type}.zip"
2729
download_file(zippath, url, "application/zip", progress=progress)
2830

2931
with zipfile.ZipFile(zippath, "r") as f:
@@ -42,6 +44,6 @@ def download_n_install(install_dir, progress=None, build_type=None):
4244

4345
shutil.move(src_dir_path, install_dir)
4446

45-
return path.join(install_dir, "bin", "ffmpeg"), path.join(
46-
install_dir, "bin", "ffprobe"
47-
)
47+
48+
def get_bindir(install_dir):
49+
return path.join(install_dir, "bin")

tests/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest

0 commit comments

Comments
 (0)