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