1. from __future__ import print_function
2.
3. from distutils.ccompiler import new_compiler as _new_compiler
4. from distutils.command.clean import clean, log
5. from distutils.core import Command
6. from distutils.dir_util import remove_tree
7. from distutils.errors import DistutilsExecError
8. from distutils.msvccompiler import MSVCCompiler
9. from setuptools import setup, find_packages, Extension, Distribution
10. from setuptools.command.build_ext import build_ext
11. from subprocess import Popen, PIPE
12. import argparse
13. import errno
14. import os
15. import platform
16. import re
17. import shlex
18. import sys
19.
20. # We don't need six...
21. PY3 = sys.version_info[0] >= 3
22.
23. if PY3:
24. from shlex import quote as shell_quote
25. else:
26. from pipes import quote as shell_quote
27.
28. try:
29. # This depends on _winreg, which is not availible on not-Windows.
30. from distutils.msvc9compiler import MSVCCompiler as MSVC9Compiler
31. except ImportError:
32. MSVC9Compiler = None
33. try:
34. from distutils._msvccompiler import MSVCCompiler as MSVC14Compiler
35. except ImportError:
36. MSVC14Compiler = None
37.
38. try:
39. from Cython import __version__ as cython_version
40. from Cython.Build import cythonize
41. except ImportError:
42. cythonize = None
43. else:
44. # We depend upon some features in Cython 0.27; reject older ones.
45. if tuple(map(int, cython_version.split('.'))) < (0, 27):
46. print("Cython {} is too old for PyAV; ignoring it.".format(cython_version))
47. cythonize = None
48.
49.
50. # We will embed this metadata into the package so it can be recalled for debugging.
51. version = open('VERSION.txt').read().strip()
52. try:
53. git_commit, _ = Popen(['git', 'describe', '--tags'], stdout=PIPE, stderr=PIPE).communicate()
54. except OSError:
55. git_commit = None
56. else:
57. git_commit = git_commit.decode().strip()
58.
59.
60. _cflag_parser = argparse.ArgumentParser(add_help=False)
61. _cflag_parser.add_argument('-I', dest='include_dirs', action='append')
62. _cflag_parser.add_argument('-L', dest='library_dirs', action='append')
63. _cflag_parser.add_argument('-l', dest='libraries', action='append')
64. _cflag_parser.add_argument('-D', dest='define_macros', action='append')
65. _cflag_parser.add_argument('-R', dest='runtime_library_dirs', action='append')
66. def parse_cflags(raw_cflags):
67. raw_args = shlex.split(raw_cflags.strip())
68. args, unknown = _cflag_parser.parse_known_args(raw_args)
69. config = {k: v or [] for k, v in args.__dict__.items()}
70. for i, x in enumerate(config['define_macros']):
71. parts = x.split('=', 1)
72. value = x[1] or None if len(x) == 2 else None
73. config['define_macros'][i] = (parts[0], value)
74. return config, ' '.join(shell_quote(x) for x in unknown)
75.
76. def get_library_config(name):
77. """Get distutils-compatible extension extras for the given library.
78.
79. This requires ``pkg-config``.
80.
81. """
82. try:
83. proc = Popen(['pkg-config', '--cflags', '--libs', name], stdout=PIPE, stderr=PIPE)
84. except OSError:
85. print('pkg-config is required for building PyAV')
86. exit(1)
87.
88. raw_cflags, err = proc.communicate()
89. if proc.wait():
90. return
91.
92. known, unknown = parse_cflags(raw_cflags.decode('utf8'))
93. if unknown:
94. print("pkg-config returned flags we don't understand: {}".format(unknown))
95. exit(1)
96.
97. return known
98.
99.
100. def update_extend(dst, src):
101. """Update the `dst` with the `src`, extending values where lists.
102.
103. Primiarily useful for integrating results from `get_library_config`.
104.
105. """
106. for k, v in src.items():
107. existing = dst.setdefault(k, [])
108. for x in v:
109. if x not in existing:
110. existing.append(x)
111.
112.
113. def unique_extend(a, *args):
114. a[:] = list(set().union(a, *args))
115.
116.
117. # Obtain the ffmpeg dir from the "--ffmpeg-dir=<dir>" argument
118. # FFMPEG_DIR = None
119. FFMPEG_DIR = 'D://Program Files//ffmpeg'
120. for i, arg in enumerate(sys.argv):
121. if arg.startswith('--ffmpeg-dir='):
122. FFMPEG_DIR = arg.split('=')[1]
123. break
124.
125. if FFMPEG_DIR is not None:
126. # delete the --ffmpeg-dir arg so that distutils does not see it
127. del sys.argv[i]
128. if not os.path.isdir(FFMPEG_DIR):
129. print('The specified ffmpeg directory does not exist')
130. exit(1)
131. else:
132. # Check the environment variable FFMPEG_DIR
133. FFMPEG_DIR = os.environ.get('FFMPEG_DIR')
134. if FFMPEG_DIR is not None:
135. if not os.path.isdir(FFMPEG_DIR):
136. FFMPEG_DIR = None
137.
138. if FFMPEG_DIR is not None:
139. ffmpeg_lib = os.path.join(FFMPEG_DIR, 'lib')
140. ffmpeg_include = os.path.join(FFMPEG_DIR, 'include')
141. if os.path.exists(ffmpeg_lib):
142. ffmpeg_lib = [ffmpeg_lib]
143. else:
144. ffmpeg_lib = [FFMPEG_DIR]
145. if os.path.exists(ffmpeg_include):
146. ffmpeg_include = [ffmpeg_include]
147. else:
148. ffmpeg_include = [FFMPEG_DIR]
149. else:
150. ffmpeg_lib = []
151. ffmpeg_include = []
152.
153.
154. # The "extras" to be supplied to every one of our modules.
155. # This is expanded heavily by the `config` command.
156. extension_extra = {
157. 'include_dirs': ['include'] + ffmpeg_include, # The first are PyAV's includes.
158. 'libraries' : [],
159. 'library_dirs': ffmpeg_lib,
160. }
161.
162. # The macros which describe the current PyAV version.
163. config_macros = {
164. "PYAV_VERSION": version,
165. "PYAV_VERSION_STR": '"%s"' % version,
166. "PYAV_COMMIT_STR": '"%s"' % (git_commit or 'unknown-commit'),
167. }
168.
169.
170. def dump_config():
171. """Print out all the config information we have so far (for debugging)."""
172. print('PyAV:', version, git_commit or '(unknown commit)')
173. print('Python:', sys.version.encode('unicode_escape' if PY3 else 'string-escape'))
174. print('platform:', platform.platform())
175. print('extension_extra:')
176. for k, vs in extension_extra.items():
177. print('\t%s: %s' % (k, [x.encode('utf8') for x in vs]))
178. print('config_macros:')
179. for x in sorted(config_macros.items()):
180. print('\t%s=%s' % x)
181.
182.
183. # Monkey-patch for CCompiler to be silent.
184. def _CCompiler_spawn_silent(cmd, dry_run=None):
185. """Spawn a process, and eat the stdio."""
186. proc = Popen(cmd, stdout=PIPE, stderr=PIPE)
187. out, err = proc.communicate()
188. if proc.returncode:
189. raise DistutilsExecError(err)
190.
191. def new_compiler(*args, **kwargs):
192. """Create a C compiler.
193.
194. :param bool silent: Eat all stdio? Defaults to ``True``.
195.
196. All other arguments passed to ``distutils.ccompiler.new_compiler``.
197.
198. """
199. make_silent = kwargs.pop('silent', True)
200. cc = _new_compiler(*args, **kwargs)
201. # If MSVC10, initialize the compiler here and add /MANIFEST to linker flags.
202. # See Python issue 4431 (https://bugs.python.org/issue4431)
203. if is_msvc(cc):
204. from distutils.msvc9compiler import get_build_version
205. if get_build_version() == 10:
206. cc.initialize()
207. for ldflags in [cc.ldflags_shared, cc.ldflags_shared_debug]:
208. unique_extend(ldflags, ['/MANIFEST'])
209. # If MSVC14, do not silence. As msvc14 requires some custom
210. # steps before the process is spawned, we can't monkey-patch this.
211. elif get_build_version() == 14:
212. make_silent = False
213. # monkey-patch compiler to suppress stdout and stderr.
214. if make_silent:
215. cc.spawn = _CCompiler_spawn_silent
216. return cc
217.
218.
219. _msvc_classes = tuple(filter(None, (MSVCCompiler, MSVC9Compiler, MSVC14Compiler)))
220. def is_msvc(cc=None):
221. cc = _new_compiler() if cc is None else cc
222. return isinstance(cc, _msvc_classes)
223.
224.
225. if os.name == 'nt':
226.
227. if is_msvc():
228. config_macros['inline'] = '__inline'
229.
230. # Since we're shipping a self contained unit on Windows, we need to mark
231. # the package as such. On other systems, let it be universal.
232. class BinaryDistribution(Distribution):
233. def is_pure(self):
234. return False
235.
236. distclass = BinaryDistribution
237.
238. else:
239.
240. # Nothing to see here.
241. distclass = Distribution
242.
243.
244. # Monkey-patch Cython to not overwrite embedded signatures.
245. if cythonize:
246.
247. from Cython.Compiler.AutoDocTransforms import EmbedSignature
248.
249. old_embed_signature = EmbedSignature._embed_signature
250. def new_embed_signature(self, sig, doc):
251.
252. # Strip any `self` parameters from the front.
253. sig = re.sub(r'\(self(,\s+)?', '(', sig)
254.
255. # If they both start with the same signature; skip it.
256. if sig and doc:
257. new_name = sig.split('(')[0].strip()
258. old_name = doc.split('(')[0].strip()
259. if new_name == old_name:
260. return doc
261. if new_name.endswith('.' + old_name):
262. return doc
263.
264. return old_embed_signature(self, sig, doc)
265.
266. EmbedSignature._embed_signature = new_embed_signature
267.
268.
269. # Construct the modules that we find in the "av" directory.
270. ext_modules = []
271. for dirname, dirnames, filenames in os.walk('av'):
272. for filename in filenames:
273.
274. # We are looing for Cython sources.
275. if filename.startswith('.') or os.path.splitext(filename)[1] != '.pyx':
276. continue
277.
278. pyx_path = os.path.join(dirname, filename)
279. base = os.path.splitext(pyx_path)[0]
280.
281. # Need to be a little careful because Windows will accept / or \
282. # (where os.sep will be \ on Windows).
283. mod_name = base.replace('/', '.').replace(os.sep, '.')
284.
285. c_path = os.path.join('src', base + '.c')
286.
287. # We go with the C sources if Cython is not installed, and fail if
288. # those also don't exist. We can't `cythonize` here though, since the
289. # `pyav/include.h` must be generated (by `build_ext`) first.
290. if not cythonize and not os.path.exists(c_path):
291. print('Cython is required to build PyAV from raw sources.')
292. print('Please `pip install Cython`.')
293. exit(3)
294. ext_modules.append(Extension(
295. mod_name,
296. sources=[c_path if not cythonize else pyx_path],
297. ))
298.
299.
300. class ConfigCommand(Command):
301.
302. user_options = [
303. ('no-pkg-config', None,
304. "do not use pkg-config to configure dependencies"),
305. ('compiler=', 'c',
306. "specify the compiler type"), ]
307.
308. boolean_options = ['no-pkg-config']
309.
310. def initialize_options(self):
311. self.compiler = None
312. self.no_pkg_config = None
313.
314. def finalize_options(self):
315. self.set_undefined_options('build',
316. ('compiler', 'compiler'),)
317. self.set_undefined_options('build_ext',
318. ('no_pkg_config', 'no_pkg_config'),)
319.
320. def run(self):
321.
322. # For some reason we get the feeling that CFLAGS is not respected, so we parse
323. # it here. TODO: Leave any arguments that we can't figure out.
324. for name in 'CFLAGS', 'LDFLAGS':
325. known, unknown = parse_cflags(os.environ.pop(name, ''))
326. if unknown:
327. print("Warning: We don't understand some of {} (and will leave it in the envvar): {}".format(name, unknown))
328. os.environ[name] = unknown
329. update_extend(extension_extra, known)
330.
331. if is_msvc(new_compiler(compiler=self.compiler)):
332. # Assume we have to disable /OPT:REF for MSVC with ffmpeg
333. config = {
334. 'extra_link_args': ['/OPT:NOREF'],
335. }
336. update_extend(extension_extra, config)
337.
338. # Check if we're using pkg-config or not
339. if self.no_pkg_config:
340. # Simply assume we have everything we need!
341. config = {
342. 'libraries': ['avformat', 'avcodec', 'avdevice', 'avutil', 'avfilter',
343. 'swscale', 'swresample'],
344. 'library_dirs': [],
345. 'include_dirs': []
346. }
347. update_extend(extension_extra, config)
348. for ext in self.distribution.ext_modules:
349. for key, value in extension_extra.items():
350. setattr(ext, key, value)
351. return
352.
353. # We're using pkg-config:
354. errors = []
355.
356. # Get the config for the libraries that we require.
357. for name in 'libavformat', 'libavcodec', 'libavdevice', 'libavutil', 'libavfilter', 'libswscale', 'libswresample':
358. config = get_library_config(name)
359. if config:
360. update_extend(extension_extra, config)
361. # We don't need macros for these, since they all must exist.
362. else:
363. errors.append('Could not find ' + name + ' with pkg-config.')
364.
365. # Don't continue if we have errors.
366. # TODO: Warn Ubuntu 12 users that they can't satisfy requirements with the
367. # default package sources.
368. if errors:
369. print('\n'.join(errors))
370. exit(1)
371.
372. # Normalize the extras.
373. extension_extra.update(
374. dict((k, sorted(set(v))) for k, v in extension_extra.items())
375. )
376.
377. # Apply them.
378. for ext in self.distribution.ext_modules:
379. for key, value in extension_extra.items():
380. setattr(ext, key, value)
381.
382.
383. class CleanCommand(clean):
384.
385. user_options = clean.user_options + [
386. ('sources', None,
387. "remove Cython build output (C sources)")]
388.
389. boolean_options = clean.boolean_options + ['sources']
390.
391. def initialize_options(self):
392. clean.initialize_options(self)
393. self.sources = None
394.
395. def run(self):
396. clean.run(self)
397. if self.sources:
398. if os.path.exists('src'):
399. remove_tree('src', dry_run=self.dry_run)
400. else:
401. log.info("'%s' does not exist -- can't clean it", 'src')
402.
403.
404. class CythonizeCommand(Command):
405.
406. user_options = []
407. def initialize_options(self):
408. pass
409. def finalize_options(self):
410. pass
411.
412. def run(self):
413.
414. # Cythonize, if required. We do it individually since we must update
415. # the existing extension instead of replacing them all.
416. for i, ext in enumerate(self.distribution.ext_modules):
417. if any(s.endswith('.pyx') for s in ext.sources):
418. if is_msvc():
419. ext.define_macros.append(('inline', '__inline'))
420. new_ext = cythonize(
421. ext,
422. compiler_directives=dict(
423. c_string_type='str',
424. c_string_encoding='ascii',
425. embedsignature=True,
426. language_level=2,
427. ),
428. build_dir='src',
429. include_path=ext.include_dirs,
430. )[0]
431. ext.sources = new_ext.sources
432.
433.
434. class BuildExtCommand(build_ext):
435.
436. if os.name != 'nt':
437. user_options = build_ext.user_options + [
438. ('no-pkg-config', None,
439. "do not use pkg-config to configure dependencies")]
440.
441. boolean_options = build_ext.boolean_options + ['no-pkg-config']
442.
443. def initialize_options(self):
444. build_ext.initialize_options(self)
445. self.no_pkg_config = None
446. else:
447. no_pkg_config = 1
448.
449. def run(self):
450.
451. # Propagate build options to config
452. obj = self.distribution.get_command_obj('config')
453. obj.compiler = self.compiler
454. obj.no_pkg_config = self.no_pkg_config
455. obj.include_dirs = self.include_dirs
456. obj.libraries = self.libraries
457. obj.library_dirs = self.library_dirs
458.
459. self.run_command('config')
460.
461. # We write a header file containing everything we have discovered by
462. # inspecting the libraries which exist. This is the main mechanism we
463. # use to detect differenced between FFmpeg and Libav.
464.
465. include_dir = os.path.join(self.build_temp, 'include')
466. pyav_dir = os.path.join(include_dir, 'pyav')
467. try:
468. os.makedirs(pyav_dir)
469. except OSError as e:
470. if e.errno != errno.EEXIST:
471. raise
472. header_path = os.path.join(pyav_dir, 'config.h')
473. print('writing', header_path)
474. with open(header_path, 'w') as fh:
475. fh.write('#ifndef PYAV_COMPAT_H\n')
476. fh.write('#define PYAV_COMPAT_H\n')
477. for k, v in sorted(config_macros.items()):
478. fh.write('#define %s %s\n' % (k, v))
479. fh.write('#endif\n')
480.
481. self.include_dirs = self.include_dirs or []
482. self.include_dirs.append(include_dir)
483. # Propagate config to cythonize.
484. for i, ext in enumerate(self.distribution.ext_modules):
485. unique_extend(ext.include_dirs, self.include_dirs)
486. unique_extend(ext.library_dirs, self.library_dirs)
487. unique_extend(ext.libraries, self.libraries)
488.
489. self.run_command('cythonize')
490. build_ext.run(self)
491.
492.
493. setup(
494.
495. name='av',
496. version=version,
497. description="Pythonic bindings for FFmpeg's libraries.",
498.
499. author="Mike Boers",
500. author_email="pyav@mikeboers.com",
501.
502. url="https://github.com/mikeboers/PyAV",
503.
504. packages=find_packages(exclude=['build*', 'examples*', 'scratchpad*', 'tests*']),
505.
506. zip_safe=False,
507. ext_modules=ext_modules,
508.
509. cmdclass={
510. 'build_ext': BuildExtCommand,
511. 'clean': CleanCommand,
512. 'config': ConfigCommand,
513. 'cythonize': CythonizeCommand,
514. },
515.
516. test_suite='tests',
517.
518. entry_points={
519. 'console_scripts': [
520. 'pyav = av.__main__:main',
521. ],
522. },
523.
524. classifiers=[
525. 'Development Status :: 5 - Production/Stable',
526. 'Intended Audience :: Developers',
527. 'License :: OSI Approved :: BSD License',
528. 'Natural Language :: English',
529. 'Operating System :: MacOS :: MacOS X',
530. 'Operating System :: POSIX',
531. 'Operating System :: Unix',
532. 'Operating System :: Microsoft :: Windows',
533. 'Programming Language :: Cython',
534. 'Programming Language :: Python :: 2.7',
535. 'Programming Language :: Python :: 3.4',
536. 'Programming Language :: Python :: 3.5',
537. 'Programming Language :: Python :: 3.6',
538. 'Programming Language :: Python :: 3.7',
539. 'Topic :: Software Development :: Libraries :: Python Modules',
540. 'Topic :: Multimedia :: Sound/Audio',
541. 'Topic :: Multimedia :: Sound/Audio :: Conversion',
542. 'Topic :: Multimedia :: Video',
543. 'Topic :: Multimedia :: Video :: Conversion',
544. ],
545.
546. distclass=distclass,
547.
548. )