]>
Commit | Line | Data |
---|---|---|
1 | #!/usr/bin/env python3 | |
2 | import os | |
3 | import platform | |
4 | import sys | |
5 | ||
6 | from PyInstaller.__main__ import run as run_pyinstaller | |
7 | ||
8 | ||
9 | OS_NAME, ARCH = sys.platform, platform.architecture()[0][:2] | |
10 | ||
11 | ||
12 | def main(): | |
13 | opts = parse_options() | |
14 | version = read_version('yt_dlp/version.py') | |
15 | ||
16 | onedir = '--onedir' in opts or '-D' in opts | |
17 | if not onedir and '-F' not in opts and '--onefile' not in opts: | |
18 | opts.append('--onefile') | |
19 | ||
20 | name, final_file = exe(onedir) | |
21 | print(f'Building yt-dlp v{version} {ARCH}bit for {OS_NAME} with options {opts}') | |
22 | print('Remember to update the version using "devscripts/update-version.py"') | |
23 | if not os.path.isfile('yt_dlp/extractor/lazy_extractors.py'): | |
24 | print('WARNING: Building without lazy_extractors. Run ' | |
25 | '"devscripts/make_lazy_extractors.py" to build lazy extractors', file=sys.stderr) | |
26 | print(f'Destination: {final_file}\n') | |
27 | ||
28 | opts = [ | |
29 | f'--name={name}', | |
30 | '--icon=devscripts/logo.ico', | |
31 | '--upx-exclude=vcruntime140.dll', | |
32 | '--noconfirm', | |
33 | # NB: Modules that are only imported dynamically must be added here. | |
34 | # --collect-submodules may not work correctly if user has a yt-dlp installed via PIP | |
35 | '--hidden-import=yt_dlp.compat._legacy', | |
36 | *dependency_options(), | |
37 | *opts, | |
38 | 'yt_dlp/__main__.py', | |
39 | ] | |
40 | ||
41 | print(f'Running PyInstaller with {opts}') | |
42 | run_pyinstaller(opts) | |
43 | set_version_info(final_file, version) | |
44 | ||
45 | ||
46 | def parse_options(): | |
47 | # Compatability with older arguments | |
48 | opts = sys.argv[1:] | |
49 | if opts[0:1] in (['32'], ['64']): | |
50 | if ARCH != opts[0]: | |
51 | raise Exception(f'{opts[0]}bit executable cannot be built on a {ARCH}bit system') | |
52 | opts = opts[1:] | |
53 | return opts | |
54 | ||
55 | ||
56 | # Get the version from yt_dlp/version.py without importing the package | |
57 | def read_version(fname): | |
58 | with open(fname, encoding='utf-8') as f: | |
59 | exec(compile(f.read(), fname, 'exec')) | |
60 | return locals()['__version__'] | |
61 | ||
62 | ||
63 | def exe(onedir): | |
64 | """@returns (name, path)""" | |
65 | name = '_'.join(filter(None, ( | |
66 | 'yt-dlp', | |
67 | OS_NAME == 'darwin' and 'macos', | |
68 | ARCH == '32' and 'x86' | |
69 | ))) | |
70 | return name, ''.join(filter(None, ( | |
71 | 'dist/', | |
72 | onedir and f'{name}/', | |
73 | name, | |
74 | OS_NAME == 'win32' and '.exe' | |
75 | ))) | |
76 | ||
77 | ||
78 | def version_to_list(version): | |
79 | version_list = version.split('.') | |
80 | return list(map(int, version_list)) + [0] * (4 - len(version_list)) | |
81 | ||
82 | ||
83 | def dependency_options(): | |
84 | # Due to the current implementation, these are auto-detected, but explicitly add them just in case | |
85 | dependencies = [pycryptodome_module(), 'mutagen', 'brotli', 'certifi', 'websockets'] | |
86 | excluded_modules = ['test', 'ytdlp_plugins', 'youtube_dl', 'youtube_dlc'] | |
87 | ||
88 | yield from (f'--hidden-import={module}' for module in dependencies) | |
89 | yield '--collect-submodules=websockets' | |
90 | yield from (f'--exclude-module={module}' for module in excluded_modules) | |
91 | ||
92 | ||
93 | def pycryptodome_module(): | |
94 | try: | |
95 | import Cryptodome # noqa: F401 | |
96 | except ImportError: | |
97 | try: | |
98 | import Crypto # noqa: F401 | |
99 | print('WARNING: Using Crypto since Cryptodome is not available. ' | |
100 | 'Install with: pip install pycryptodomex', file=sys.stderr) | |
101 | return 'Crypto' | |
102 | except ImportError: | |
103 | pass | |
104 | return 'Cryptodome' | |
105 | ||
106 | ||
107 | def set_version_info(exe, version): | |
108 | if OS_NAME == 'win32': | |
109 | windows_set_version(exe, version) | |
110 | ||
111 | ||
112 | def windows_set_version(exe, version): | |
113 | from PyInstaller.utils.win32.versioninfo import ( | |
114 | FixedFileInfo, | |
115 | SetVersion, | |
116 | StringFileInfo, | |
117 | StringStruct, | |
118 | StringTable, | |
119 | VarFileInfo, | |
120 | VarStruct, | |
121 | VSVersionInfo, | |
122 | ) | |
123 | ||
124 | version_list = version_to_list(version) | |
125 | suffix = '_x86' if ARCH == '32' else '' | |
126 | SetVersion(exe, VSVersionInfo( | |
127 | ffi=FixedFileInfo( | |
128 | filevers=version_list, | |
129 | prodvers=version_list, | |
130 | mask=0x3F, | |
131 | flags=0x0, | |
132 | OS=0x4, | |
133 | fileType=0x1, | |
134 | subtype=0x0, | |
135 | date=(0, 0), | |
136 | ), | |
137 | kids=[ | |
138 | StringFileInfo([StringTable('040904B0', [ | |
139 | StringStruct('Comments', 'yt-dlp%s Command Line Interface.' % suffix), | |
140 | StringStruct('CompanyName', 'https://github.com/yt-dlp'), | |
141 | StringStruct('FileDescription', 'yt-dlp%s' % (' (32 Bit)' if ARCH == '32' else '')), | |
142 | StringStruct('FileVersion', version), | |
143 | StringStruct('InternalName', f'yt-dlp{suffix}'), | |
144 | StringStruct('LegalCopyright', 'pukkandan.ytdlp@gmail.com | UNLICENSE'), | |
145 | StringStruct('OriginalFilename', f'yt-dlp{suffix}.exe'), | |
146 | StringStruct('ProductName', f'yt-dlp{suffix}'), | |
147 | StringStruct( | |
148 | 'ProductVersion', f'{version}{suffix} on Python {platform.python_version()}'), | |
149 | ])]), VarFileInfo([VarStruct('Translation', [0, 1200])]) | |
150 | ] | |
151 | )) | |
152 | ||
153 | ||
154 | if __name__ == '__main__': | |
155 | main() |