patch-torchcodec 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,228 @@
1
+ # ============================================================================
2
+ # Manually added items (project-specific)
3
+ # ============================================================================
4
+
5
+ # Version file (auto-generated by hatch-vcs)
6
+ /src/mediaref/_version.py
7
+
8
+ # ============================================================================
9
+ # Below items are from GitHub's official Python .gitignore template
10
+ # https://github.com/github/gitignore/blob/main/Python.gitignore
11
+ # ============================================================================
12
+
13
+ # Byte-compiled / optimized / DLL files
14
+ __pycache__/
15
+ *.py[codz]
16
+ *$py.class
17
+
18
+ # C extensions
19
+ *.so
20
+
21
+ # Distribution / packaging
22
+ .Python
23
+ build/
24
+ develop-eggs/
25
+ dist/
26
+ downloads/
27
+ eggs/
28
+ .eggs/
29
+ lib/
30
+ lib64/
31
+ parts/
32
+ sdist/
33
+ var/
34
+ wheels/
35
+ share/python-wheels/
36
+ *.egg-info/
37
+ .installed.cfg
38
+ *.egg
39
+ MANIFEST
40
+
41
+ # PyInstaller
42
+ # Usually these files are written by a python script from a template
43
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
44
+ *.manifest
45
+ *.spec
46
+
47
+ # Installer logs
48
+ pip-log.txt
49
+ pip-delete-this-directory.txt
50
+
51
+ # Unit test / coverage reports
52
+ htmlcov/
53
+ .tox/
54
+ .nox/
55
+ .coverage
56
+ .coverage.*
57
+ .cache
58
+ nosetests.xml
59
+ coverage.xml
60
+ *.cover
61
+ *.py.cover
62
+ .hypothesis/
63
+ .pytest_cache/
64
+ cover/
65
+
66
+ # Translations
67
+ *.mo
68
+ *.pot
69
+
70
+ # Django stuff:
71
+ *.log
72
+ local_settings.py
73
+ db.sqlite3
74
+ db.sqlite3-journal
75
+
76
+ # Flask stuff:
77
+ instance/
78
+ .webassets-cache
79
+
80
+ # Scrapy stuff:
81
+ .scrapy
82
+
83
+ # Sphinx documentation
84
+ docs/_build/
85
+
86
+ # PyBuilder
87
+ .pybuilder/
88
+ target/
89
+
90
+ # Jupyter Notebook
91
+ .ipynb_checkpoints
92
+
93
+ # IPython
94
+ profile_default/
95
+ ipython_config.py
96
+
97
+ # pyenv
98
+ # For a library or package, you might want to ignore these files since the code is
99
+ # intended to run in multiple environments; otherwise, check them in:
100
+ # .python-version
101
+
102
+ # pipenv
103
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
104
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
105
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
106
+ # install all needed dependencies.
107
+ # Pipfile.lock
108
+
109
+ # UV
110
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
111
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
112
+ # commonly ignored for libraries.
113
+ uv.lock
114
+
115
+ # poetry
116
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
117
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
118
+ # commonly ignored for libraries.
119
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
120
+ # poetry.lock
121
+ # poetry.toml
122
+
123
+ # pdm
124
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
125
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
126
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
127
+ # pdm.lock
128
+ # pdm.toml
129
+ .pdm-python
130
+ .pdm-build/
131
+
132
+ # pixi
133
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
134
+ # pixi.lock
135
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
136
+ # in the .venv directory. It is recommended not to include this directory in version control.
137
+ .pixi
138
+
139
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
140
+ __pypackages__/
141
+
142
+ # Celery stuff
143
+ celerybeat-schedule
144
+ celerybeat.pid
145
+
146
+ # Redis
147
+ *.rdb
148
+ *.aof
149
+ *.pid
150
+
151
+ # RabbitMQ
152
+ mnesia/
153
+ rabbitmq/
154
+ rabbitmq-data/
155
+
156
+ # ActiveMQ
157
+ activemq-data/
158
+
159
+ # SageMath parsed files
160
+ *.sage.py
161
+
162
+ # Environments
163
+ .env
164
+ .envrc
165
+ .venv
166
+ env/
167
+ venv/
168
+ ENV/
169
+ env.bak/
170
+ venv.bak/
171
+
172
+ # Spyder project settings
173
+ .spyderproject
174
+ .spyproject
175
+
176
+ # Rope project settings
177
+ .ropeproject
178
+
179
+ # mkdocs documentation
180
+ /site
181
+
182
+ # mypy
183
+ .mypy_cache/
184
+ .dmypy.json
185
+ dmypy.json
186
+
187
+ # Pyre type checker
188
+ .pyre/
189
+
190
+ # pytype static type analyzer
191
+ .pytype/
192
+
193
+ # Cython debug symbols
194
+ cython_debug/
195
+
196
+ # PyCharm
197
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
198
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
199
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
200
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
201
+ # .idea/
202
+
203
+ # Abstra
204
+ # Abstra is an AI-powered process automation framework.
205
+ # Ignore directories containing user credentials, local state, and settings.
206
+ # Learn more at https://abstra.io/docs
207
+ .abstra/
208
+
209
+ # Visual Studio Code
210
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
211
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
212
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
213
+ # you could uncomment the following to ignore the entire vscode folder
214
+ # .vscode/
215
+
216
+ # Ruff stuff:
217
+ .ruff_cache/
218
+
219
+ # PyPI configuration file
220
+ .pypirc
221
+
222
+ # Marimo
223
+ marimo/_static/
224
+ marimo/_lsp/
225
+ __marimo__/
226
+
227
+ # Streamlit
228
+ .streamlit/secrets.toml
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.4
2
+ Name: patch-torchcodec
3
+ Version: 0.1.0
4
+ Summary: Patch TorchCodec to use PyAV's bundled FFmpeg — one command, no system FFmpeg needed
5
+ Project-URL: Homepage, https://github.com/open-world-agents/MediaRef
6
+ Project-URL: Repository, https://github.com/open-world-agents/MediaRef
7
+ Author: MediaRef Contributors
8
+ License-Expression: MIT
9
+ Keywords: decoder,ffmpeg,patchelf,pyav,torchcodec,video
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Multimedia :: Video
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: av>=10.0.0
22
+ Requires-Dist: patchelf
23
+ Description-Content-Type: text/markdown
24
+
25
+ # patch-torchcodec
26
+
27
+ Patch TorchCodec to use PyAV's bundled FFmpeg — **one command, no system FFmpeg needed**.
28
+
29
+ ## Problem
30
+
31
+ TorchCodec requires FFmpeg shared libraries (`libavcodec.so.62`, etc.), but installing FFmpeg system-wide can be complex and may cause version conflicts.
32
+
33
+ ## Solution
34
+
35
+ PyAV already bundles FFmpeg. `patch-torchcodec` patches TorchCodec's RPATH to find them — no environment variables needed!
36
+
37
+ ## Quick Start
38
+
39
+ ```bash
40
+ pip install torchcodec patch-torchcodec # torchcodec + patcher (av & patchelf included)
41
+ patch-torchcodec # patches RPATH — done!
42
+ ```
43
+
44
+ That's it. TorchCodec now works:
45
+
46
+ ```python
47
+ from torchcodec.decoders import VideoDecoder # ✓ just works
48
+ ```
49
+
50
+ ## Command Line Options
51
+
52
+ ```bash
53
+ patch-torchcodec # Patch RPATH (default, recommended)
54
+ patch-torchcodec --env-only # Symlinks only (requires LD_LIBRARY_PATH)
55
+ patch-torchcodec --status # Check current setup status
56
+ patch-torchcodec --verify # Verify TorchCodec works
57
+ patch-torchcodec --quiet # Silent mode
58
+ ```
59
+
60
+ ## Python API
61
+
62
+ ```python
63
+ from patch_torchcodec import setup_with_patchelf, verify_torchcodec, is_rpath_patched
64
+
65
+ # Patch RPATH (recommended) — persists across sessions
66
+ setup_with_patchelf(verbose=True)
67
+
68
+ # Verify
69
+ assert verify_torchcodec(require_env=False)
70
+ ```
71
+
72
+ ## How It Works
73
+
74
+ 1. **Finds PyAV's bundled FFmpeg** in `site-packages/av.libs/`
75
+ 2. **Creates symbolic links** with standard names (e.g., `libavcodec.so.62`)
76
+ 3. **Patches TorchCodec's `.so` files** with `patchelf` to include PyAV's library path in RPATH
77
+
78
+ ## Limitations
79
+
80
+ - **Linux only** (RPATH is Linux-specific)
81
+ - **Same virtualenv**: PyAV and TorchCodec must be in the same environment
82
+ - **Re-run after reinstall**: If you reinstall TorchCodec, run `patch-torchcodec` again
83
+
84
+ ## License
85
+
86
+ MIT
87
+
@@ -0,0 +1,63 @@
1
+ # patch-torchcodec
2
+
3
+ Patch TorchCodec to use PyAV's bundled FFmpeg — **one command, no system FFmpeg needed**.
4
+
5
+ ## Problem
6
+
7
+ TorchCodec requires FFmpeg shared libraries (`libavcodec.so.62`, etc.), but installing FFmpeg system-wide can be complex and may cause version conflicts.
8
+
9
+ ## Solution
10
+
11
+ PyAV already bundles FFmpeg. `patch-torchcodec` patches TorchCodec's RPATH to find them — no environment variables needed!
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ pip install torchcodec patch-torchcodec # torchcodec + patcher (av & patchelf included)
17
+ patch-torchcodec # patches RPATH — done!
18
+ ```
19
+
20
+ That's it. TorchCodec now works:
21
+
22
+ ```python
23
+ from torchcodec.decoders import VideoDecoder # ✓ just works
24
+ ```
25
+
26
+ ## Command Line Options
27
+
28
+ ```bash
29
+ patch-torchcodec # Patch RPATH (default, recommended)
30
+ patch-torchcodec --env-only # Symlinks only (requires LD_LIBRARY_PATH)
31
+ patch-torchcodec --status # Check current setup status
32
+ patch-torchcodec --verify # Verify TorchCodec works
33
+ patch-torchcodec --quiet # Silent mode
34
+ ```
35
+
36
+ ## Python API
37
+
38
+ ```python
39
+ from patch_torchcodec import setup_with_patchelf, verify_torchcodec, is_rpath_patched
40
+
41
+ # Patch RPATH (recommended) — persists across sessions
42
+ setup_with_patchelf(verbose=True)
43
+
44
+ # Verify
45
+ assert verify_torchcodec(require_env=False)
46
+ ```
47
+
48
+ ## How It Works
49
+
50
+ 1. **Finds PyAV's bundled FFmpeg** in `site-packages/av.libs/`
51
+ 2. **Creates symbolic links** with standard names (e.g., `libavcodec.so.62`)
52
+ 3. **Patches TorchCodec's `.so` files** with `patchelf` to include PyAV's library path in RPATH
53
+
54
+ ## Limitations
55
+
56
+ - **Linux only** (RPATH is Linux-specific)
57
+ - **Same virtualenv**: PyAV and TorchCodec must be in the same environment
58
+ - **Re-run after reinstall**: If you reinstall TorchCodec, run `patch-torchcodec` again
59
+
60
+ ## License
61
+
62
+ MIT
63
+
@@ -0,0 +1,49 @@
1
+ """Patch TorchCodec to use PyAV's bundled FFmpeg — no system FFmpeg needed.
2
+
3
+ Usage:
4
+ pip install patch-torchcodec # installs av + patchelf
5
+ patch-torchcodec # patches RPATH — done!
6
+
7
+ Python API:
8
+ from patch_torchcodec import setup_with_patchelf
9
+ setup_with_patchelf() # One-time setup, persists across sessions
10
+
11
+ Alternative (no binary patching):
12
+ patch-torchcodec --env-only
13
+ export LD_LIBRARY_PATH="<shown path>:$LD_LIBRARY_PATH"
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ __version__ = "0.2.0"
19
+
20
+ from .core import (
21
+ create_all_symlinks,
22
+ create_symlinks,
23
+ find_av_libs_dir,
24
+ find_patchelf,
25
+ find_torchcodec_libs,
26
+ get_ld_library_path,
27
+ get_library_mappings,
28
+ is_rpath_patched,
29
+ patch_rpath,
30
+ setup,
31
+ setup_with_patchelf,
32
+ verify_torchcodec,
33
+ )
34
+
35
+ __all__ = [
36
+ "create_all_symlinks",
37
+ "create_symlinks",
38
+ "find_av_libs_dir",
39
+ "find_patchelf",
40
+ "find_torchcodec_libs",
41
+ "get_ld_library_path",
42
+ "get_library_mappings",
43
+ "is_rpath_patched",
44
+ "patch_rpath",
45
+ "setup",
46
+ "setup_with_patchelf",
47
+ "verify_torchcodec",
48
+ ]
49
+
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env python3
2
+ """Command-line interface for patch-torchcodec."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from .core import (
11
+ create_all_symlinks,
12
+ create_symlinks,
13
+ find_av_libs_dir,
14
+ find_patchelf,
15
+ find_torchcodec_libs,
16
+ get_library_mappings,
17
+ is_rpath_patched,
18
+ setup_with_patchelf,
19
+ verify_torchcodec,
20
+ )
21
+
22
+
23
+ def main():
24
+ parser = argparse.ArgumentParser(
25
+ prog="patch-torchcodec",
26
+ description="Patch TorchCodec to use PyAV's bundled FFmpeg libraries via RPATH.",
27
+ epilog="""
28
+ Examples:
29
+ patch-torchcodec # Patch RPATH (recommended, one-time)
30
+ patch-torchcodec --env-only # Symlinks only (requires LD_LIBRARY_PATH)
31
+ patch-torchcodec --verify # Verify TorchCodec works
32
+ patch-torchcodec --status # Check current setup status
33
+ """,
34
+ formatter_class=argparse.RawDescriptionHelpFormatter,
35
+ )
36
+ parser.add_argument(
37
+ "--env-only",
38
+ action="store_true",
39
+ help="Only create symlinks, don't patch RPATH (requires LD_LIBRARY_PATH)",
40
+ )
41
+ parser.add_argument(
42
+ "--print-env",
43
+ action="store_true",
44
+ help="Print shell command to set LD_LIBRARY_PATH",
45
+ )
46
+ parser.add_argument(
47
+ "--create-activate",
48
+ action="store_true",
49
+ help="Create an activation script (activate_torchcodec.sh)",
50
+ )
51
+ parser.add_argument(
52
+ "--verify", "--verify-only",
53
+ action="store_true",
54
+ dest="verify_only",
55
+ help="Only verify TorchCodec works, don't make changes",
56
+ )
57
+ parser.add_argument(
58
+ "--status",
59
+ action="store_true",
60
+ help="Show current setup status",
61
+ )
62
+ parser.add_argument(
63
+ "--quiet", "-q",
64
+ action="store_true",
65
+ help="Suppress verbose output",
66
+ )
67
+ args = parser.parse_args()
68
+ verbose = not args.quiet
69
+
70
+ # Find PyAV libraries
71
+ libs_dir = find_av_libs_dir()
72
+ if libs_dir is None:
73
+ print("Error: Could not find PyAV's av.libs directory.", file=sys.stderr)
74
+ print("Make sure PyAV is installed: pip install av", file=sys.stderr)
75
+ sys.exit(1)
76
+
77
+ # Status mode
78
+ if args.status:
79
+ print("patch-torchcodec: Status")
80
+ print("=" * 40)
81
+ print(f"PyAV av.libs: {libs_dir}")
82
+
83
+ torchcodec_libs = find_torchcodec_libs()
84
+ if torchcodec_libs:
85
+ print(f"TorchCodec libraries: {len(torchcodec_libs)} found")
86
+ else:
87
+ print("TorchCodec: NOT INSTALLED")
88
+
89
+ patchelf = find_patchelf()
90
+ print(f"patchelf: {'found at ' + str(patchelf) if patchelf else 'NOT FOUND'}")
91
+
92
+ rpath_patched = is_rpath_patched()
93
+ print(f"RPATH patched: {'YES' if rpath_patched else 'NO'}")
94
+
95
+ print()
96
+ if verify_torchcodec(libs_dir, require_env=False):
97
+ print("✓ TorchCodec works WITHOUT LD_LIBRARY_PATH")
98
+ elif verify_torchcodec(libs_dir, require_env=True):
99
+ print("⚠ TorchCodec works only WITH LD_LIBRARY_PATH")
100
+ else:
101
+ print("✗ TorchCodec is NOT working")
102
+ sys.exit(0)
103
+
104
+ # Verify only mode
105
+ if args.verify_only:
106
+ if verbose:
107
+ print("Verifying TorchCodec...")
108
+
109
+ # Try without LD_LIBRARY_PATH first
110
+ if verify_torchcodec(libs_dir, require_env=False):
111
+ if verbose:
112
+ print("✓ TorchCodec works without LD_LIBRARY_PATH (RPATH patched)")
113
+ sys.exit(0)
114
+ elif verify_torchcodec(libs_dir, require_env=True):
115
+ if verbose:
116
+ print("✓ TorchCodec works with LD_LIBRARY_PATH")
117
+ print(f" Run: export LD_LIBRARY_PATH=\"{libs_dir}:$LD_LIBRARY_PATH\"")
118
+ sys.exit(0)
119
+ else:
120
+ if verbose:
121
+ print("✗ TorchCodec verification failed.")
122
+ sys.exit(1)
123
+
124
+ if verbose:
125
+ print("patch-torchcodec: Patching")
126
+ print("=" * 40)
127
+ print(f"PyAV av.libs: {libs_dir}")
128
+
129
+ # Decide setup method
130
+ if args.env_only:
131
+ # LD_LIBRARY_PATH only mode
132
+ if verbose:
133
+ print("\nSetup mode: LD_LIBRARY_PATH (symlinks only)")
134
+ print("\nCreating symbolic links...")
135
+
136
+ mappings = get_library_mappings(libs_dir)
137
+ if not mappings:
138
+ print("Error: No FFmpeg libraries found in PyAV bundle.", file=sys.stderr)
139
+ sys.exit(1)
140
+
141
+ created = create_symlinks(libs_dir, mappings)
142
+ # Also create symlinks for all other libraries (libdrm, etc.)
143
+ all_created = create_all_symlinks(libs_dir)
144
+
145
+ if verbose:
146
+ print(f" Created {len(created) + len(all_created)} symbolic links")
147
+ print(f"\n✓ Symlinks created")
148
+ print(f"\nTo use TorchCodec, set LD_LIBRARY_PATH:")
149
+ print(f' export LD_LIBRARY_PATH="{libs_dir}:$LD_LIBRARY_PATH"')
150
+
151
+ if args.print_env:
152
+ print(f'\nexport LD_LIBRARY_PATH="{libs_dir}:$LD_LIBRARY_PATH"')
153
+ else:
154
+ # RPATH patching mode (default)
155
+ if verbose:
156
+ print("\nSetup mode: RPATH patching (recommended)")
157
+
158
+ patchelf = find_patchelf()
159
+ if patchelf is None:
160
+ print("\nError: patchelf not found.", file=sys.stderr)
161
+ print("Install with: pip install patchelf", file=sys.stderr)
162
+ print("\nAlternatively, use --env-only to setup with LD_LIBRARY_PATH.", file=sys.stderr)
163
+ sys.exit(1)
164
+
165
+ torchcodec_libs = find_torchcodec_libs()
166
+ if not torchcodec_libs:
167
+ print("\nError: TorchCodec not found.", file=sys.stderr)
168
+ print("Install with: pip install torchcodec", file=sys.stderr)
169
+ sys.exit(1)
170
+
171
+ if verbose:
172
+ print(f" patchelf: {patchelf}")
173
+ print(f" TorchCodec libraries: {len(torchcodec_libs)}")
174
+
175
+ success = setup_with_patchelf(verbose=verbose)
176
+
177
+ if not success:
178
+ print("\nError: RPATH patching failed.", file=sys.stderr)
179
+ sys.exit(1)
180
+
181
+ if verbose:
182
+ print("\n✓ RPATH patching complete!")
183
+ print(" TorchCodec will now automatically find FFmpeg libraries.")
184
+ print(" No LD_LIBRARY_PATH needed!")
185
+
186
+ # Create activation script if requested
187
+ if args.create_activate:
188
+ script_path = Path("activate_torchcodec.sh")
189
+ script_content = f"""#!/bin/bash
190
+ # Activation script for TorchCodec FFmpeg libraries
191
+ # Source this file: source {script_path}
192
+
193
+ export LD_LIBRARY_PATH="{libs_dir}:$LD_LIBRARY_PATH"
194
+ echo "TorchCodec FFmpeg libraries activated"
195
+ """
196
+ script_path.write_text(script_content)
197
+ script_path.chmod(0o755)
198
+ if verbose:
199
+ print(f"\n✓ Created activation script: {script_path}")
200
+ print(f" Usage: source {script_path}")
201
+
202
+ # Verify
203
+ if verbose:
204
+ print("\nVerifying TorchCodec...")
205
+
206
+ require_env = args.env_only
207
+ if verify_torchcodec(libs_dir, require_env=require_env):
208
+ if verbose:
209
+ if require_env:
210
+ print("✓ TorchCodec works (with LD_LIBRARY_PATH)")
211
+ else:
212
+ print("✓ TorchCodec works (without LD_LIBRARY_PATH)")
213
+ else:
214
+ if verbose:
215
+ print("⚠ TorchCodec verification failed.")
216
+ if require_env:
217
+ print(" Make sure to set LD_LIBRARY_PATH before running your code.")
218
+ sys.exit(1)
219
+
220
+
221
+ if __name__ == "__main__":
222
+ main()
223
+
@@ -0,0 +1,361 @@
1
+ """Core functionality for setting up FFmpeg libraries for TorchCodec."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import re
7
+ import shutil
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+
12
+
13
+ def find_av_libs_dir() -> Path | None:
14
+ """Find PyAV's bundled FFmpeg libraries directory.
15
+
16
+ Returns:
17
+ Path to av.libs directory, or None if not found.
18
+ """
19
+ try:
20
+ import av
21
+ av_path = Path(av.__file__).parent
22
+ libs_dir = av_path.parent / "av.libs"
23
+ if libs_dir.exists():
24
+ return libs_dir
25
+ except ImportError:
26
+ pass
27
+ return None
28
+
29
+
30
+ def get_library_mappings(libs_dir: Path) -> dict[str, Path]:
31
+ """Get mapping from standard library names to actual PyAV library files.
32
+
33
+ Args:
34
+ libs_dir: Path to PyAV's av.libs directory.
35
+
36
+ Returns:
37
+ Dict mapping standard names (e.g., 'libavcodec.so.62') to actual file paths.
38
+ """
39
+ required_libs = [
40
+ "libavcodec",
41
+ "libavformat",
42
+ "libavutil",
43
+ "libswscale",
44
+ "libswresample",
45
+ "libavdevice",
46
+ "libavfilter",
47
+ ]
48
+
49
+ mappings = {}
50
+
51
+ for lib_name in required_libs:
52
+ # Find the actual library file (e.g., libavcodec-e57b519c.so.62.11.100)
53
+ pattern = re.compile(rf"^{lib_name}-[a-f0-9]+\.so\.(\d+)\.\d+\.\d+$")
54
+
55
+ for file in libs_dir.iterdir():
56
+ match = pattern.match(file.name)
57
+ if match:
58
+ major_version = match.group(1)
59
+ standard_name = f"{lib_name}.so.{major_version}"
60
+ mappings[standard_name] = file
61
+ break
62
+
63
+ return mappings
64
+
65
+
66
+ def create_symlinks(libs_dir: Path, mappings: dict[str, Path] | None = None) -> list[Path]:
67
+ """Create symbolic links for FFmpeg libraries.
68
+
69
+ Args:
70
+ libs_dir: Path to PyAV's av.libs directory.
71
+ mappings: Optional pre-computed library mappings.
72
+
73
+ Returns:
74
+ List of created symlink paths.
75
+ """
76
+ if mappings is None:
77
+ mappings = get_library_mappings(libs_dir)
78
+
79
+ created = []
80
+
81
+ for standard_name, actual_file in mappings.items():
82
+ symlink_path = libs_dir / standard_name
83
+
84
+ # Remove existing symlink if present
85
+ if symlink_path.is_symlink():
86
+ symlink_path.unlink()
87
+ elif symlink_path.exists():
88
+ continue
89
+
90
+ # Create relative symlink
91
+ symlink_path.symlink_to(actual_file.name)
92
+ created.append(symlink_path)
93
+
94
+ return created
95
+
96
+
97
+ def get_ld_library_path() -> str | None:
98
+ """Get the LD_LIBRARY_PATH value needed for TorchCodec.
99
+
100
+ Returns:
101
+ Path string to add to LD_LIBRARY_PATH, or None if PyAV not found.
102
+ """
103
+ libs_dir = find_av_libs_dir()
104
+ if libs_dir is None:
105
+ return None
106
+ return str(libs_dir)
107
+
108
+
109
+ def setup(verbose: bool = False) -> bool:
110
+ """Setup FFmpeg libraries for TorchCodec.
111
+
112
+ Creates symbolic links from PyAV's bundled FFmpeg libraries to standard
113
+ library names that TorchCodec expects.
114
+
115
+ Args:
116
+ verbose: If True, print progress messages.
117
+
118
+ Returns:
119
+ True if setup was successful, False otherwise.
120
+ """
121
+ libs_dir = find_av_libs_dir()
122
+ if libs_dir is None:
123
+ if verbose:
124
+ print("Error: PyAV not found. Install with: pip install av", file=sys.stderr)
125
+ return False
126
+
127
+ mappings = get_library_mappings(libs_dir)
128
+ if not mappings:
129
+ if verbose:
130
+ print("Error: No FFmpeg libraries found in PyAV bundle.", file=sys.stderr)
131
+ return False
132
+
133
+ created = create_symlinks(libs_dir, mappings)
134
+
135
+ if verbose:
136
+ print(f"Created {len(created)} symbolic links in {libs_dir}")
137
+ print(f"\nTo use TorchCodec, set LD_LIBRARY_PATH:")
138
+ print(f' export LD_LIBRARY_PATH="{libs_dir}:$LD_LIBRARY_PATH"')
139
+
140
+ return True
141
+
142
+
143
+ def find_torchcodec_libs() -> list[Path]:
144
+ """Find TorchCodec shared libraries.
145
+
146
+ Returns:
147
+ List of paths to TorchCodec .so files.
148
+ """
149
+ try:
150
+ import torchcodec
151
+ torchcodec_dir = Path(torchcodec.__file__).parent
152
+ return list(torchcodec_dir.glob("libtorchcodec_*.so"))
153
+ except ImportError:
154
+ return []
155
+
156
+
157
+ def find_patchelf() -> Path | None:
158
+ """Find patchelf executable.
159
+
160
+ Returns:
161
+ Path to patchelf executable, or None if not found.
162
+ """
163
+ # Check in current Python environment first
164
+ try:
165
+ import site
166
+ for sp in site.getsitepackages():
167
+ patchelf = Path(sp).parent.parent / "bin" / "patchelf"
168
+ if patchelf.exists():
169
+ return patchelf
170
+ except Exception:
171
+ pass
172
+
173
+ # Check system PATH
174
+ patchelf = shutil.which("patchelf")
175
+ if patchelf:
176
+ return Path(patchelf)
177
+
178
+ return None
179
+
180
+
181
+ def patch_rpath(library_path: Path, rpath: str, patchelf: Path | None = None) -> bool:
182
+ """Set RPATH on a shared library using patchelf.
183
+
184
+ Args:
185
+ library_path: Path to the .so file to patch.
186
+ rpath: RPATH value to set.
187
+ patchelf: Path to patchelf executable. Auto-detected if None.
188
+
189
+ Returns:
190
+ True if successful, False otherwise.
191
+ """
192
+ if patchelf is None:
193
+ patchelf = find_patchelf()
194
+ if patchelf is None:
195
+ return False
196
+
197
+ try:
198
+ subprocess.run(
199
+ [str(patchelf), "--force-rpath", "--set-rpath", rpath, str(library_path)],
200
+ check=True,
201
+ capture_output=True,
202
+ )
203
+ return True
204
+ except (subprocess.CalledProcessError, Exception):
205
+ return False
206
+
207
+
208
+ def setup_with_patchelf(verbose: bool = False) -> bool:
209
+ """Setup TorchCodec to use PyAV's FFmpeg libraries via RPATH patching.
210
+
211
+ This method patches TorchCodec's shared libraries to include PyAV's av.libs
212
+ directory in their RPATH, eliminating the need for LD_LIBRARY_PATH.
213
+
214
+ Requires: patchelf (install with: pip install patchelf)
215
+
216
+ Args:
217
+ verbose: If True, print progress messages.
218
+
219
+ Returns:
220
+ True if setup was successful, False otherwise.
221
+ """
222
+ # Find dependencies
223
+ libs_dir = find_av_libs_dir()
224
+ if libs_dir is None:
225
+ if verbose:
226
+ print("Error: PyAV not found. Install with: pip install av", file=sys.stderr)
227
+ return False
228
+
229
+ torchcodec_libs = find_torchcodec_libs()
230
+ if not torchcodec_libs:
231
+ if verbose:
232
+ print("Error: TorchCodec not found. Install with: pip install torchcodec", file=sys.stderr)
233
+ return False
234
+
235
+ patchelf = find_patchelf()
236
+ if patchelf is None:
237
+ if verbose:
238
+ print("Error: patchelf not found. Install with: pip install patchelf", file=sys.stderr)
239
+ return False
240
+
241
+ # Create symlinks for all libraries in av.libs
242
+ if verbose:
243
+ print(f"Creating symbolic links in {libs_dir}...")
244
+
245
+ created_symlinks = create_all_symlinks(libs_dir)
246
+ if verbose:
247
+ print(f" Created {len(created_symlinks)} symbolic links")
248
+
249
+ # Patch TorchCodec libraries
250
+ if verbose:
251
+ print(f"\nPatching TorchCodec libraries with RPATH: {libs_dir}")
252
+
253
+ rpath = str(libs_dir.resolve())
254
+ patched = 0
255
+ for lib in torchcodec_libs:
256
+ if patch_rpath(lib, rpath, patchelf):
257
+ patched += 1
258
+ if verbose:
259
+ print(f" Patched: {lib.name}")
260
+ elif verbose:
261
+ print(f" Failed: {lib.name}", file=sys.stderr)
262
+
263
+ if verbose:
264
+ print(f"\nPatched {patched}/{len(torchcodec_libs)} libraries")
265
+
266
+ return patched == len(torchcodec_libs)
267
+
268
+
269
+ def create_all_symlinks(libs_dir: Path) -> list[Path]:
270
+ """Create symbolic links for ALL libraries in av.libs.
271
+
272
+ This creates symlinks from hash-included names to standard names,
273
+ e.g., libdrm-b0291a67.so.2.4.0 -> libdrm.so.2
274
+
275
+ Args:
276
+ libs_dir: Path to PyAV's av.libs directory.
277
+
278
+ Returns:
279
+ List of created symlink paths.
280
+ """
281
+ created = []
282
+
283
+ # Pattern: libname-hash.so.major.minor.patch
284
+ pattern = re.compile(r"^(.+)-[a-f0-9]+\.so\.(\d+)(?:\.\d+)*$")
285
+
286
+ for file in libs_dir.iterdir():
287
+ if file.is_symlink():
288
+ continue
289
+
290
+ match = pattern.match(file.name)
291
+ if match:
292
+ lib_name = match.group(1)
293
+ major_version = match.group(2)
294
+ standard_name = f"{lib_name}.so.{major_version}"
295
+ symlink_path = libs_dir / standard_name
296
+
297
+ if not symlink_path.exists():
298
+ symlink_path.symlink_to(file.name)
299
+ created.append(symlink_path)
300
+
301
+ return created
302
+
303
+
304
+ def verify_torchcodec(libs_dir: Path | None = None, require_env: bool = True) -> bool:
305
+ """Verify TorchCodec can load with the configured libraries.
306
+
307
+ Args:
308
+ libs_dir: Path to av.libs directory. If None, will be auto-detected.
309
+ require_env: If True, set LD_LIBRARY_PATH for verification.
310
+ If False, test without setting LD_LIBRARY_PATH (for RPATH-patched installs).
311
+
312
+ Returns:
313
+ True if TorchCodec loads successfully, False otherwise.
314
+ """
315
+ if libs_dir is None:
316
+ libs_dir = find_av_libs_dir()
317
+
318
+ env = os.environ.copy()
319
+ if require_env and libs_dir is not None:
320
+ env["LD_LIBRARY_PATH"] = f"{libs_dir}:{env.get('LD_LIBRARY_PATH', '')}"
321
+
322
+ try:
323
+ result = subprocess.run(
324
+ [sys.executable, "-c", "from torchcodec.decoders import VideoDecoder; print('OK')"],
325
+ env=env,
326
+ capture_output=True,
327
+ text=True,
328
+ )
329
+ return result.returncode == 0 and "OK" in result.stdout
330
+ except Exception:
331
+ return False
332
+
333
+
334
+ def is_rpath_patched() -> bool:
335
+ """Check if TorchCodec libraries have been RPATH-patched.
336
+
337
+ Returns:
338
+ True if RPATH is set to av.libs directory.
339
+ """
340
+ libs_dir = find_av_libs_dir()
341
+ if libs_dir is None:
342
+ return False
343
+
344
+ torchcodec_libs = find_torchcodec_libs()
345
+ if not torchcodec_libs:
346
+ return False
347
+
348
+ # Check RPATH of one library
349
+ try:
350
+ result = subprocess.run(
351
+ ["readelf", "-d", str(torchcodec_libs[0])],
352
+ capture_output=True,
353
+ text=True,
354
+ )
355
+ if result.returncode == 0:
356
+ return str(libs_dir) in result.stdout
357
+ except Exception:
358
+ pass
359
+
360
+ return False
361
+
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "patch-torchcodec"
7
+ version = "0.1.0"
8
+ description = "Patch TorchCodec to use PyAV's bundled FFmpeg — one command, no system FFmpeg needed"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "MediaRef Contributors" }
14
+ ]
15
+ keywords = ["torchcodec", "ffmpeg", "pyav", "video", "decoder", "patchelf"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: POSIX :: Linux",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Multimedia :: Video",
27
+ ]
28
+ dependencies = [
29
+ "av>=10.0.0",
30
+ "patchelf",
31
+ ]
32
+
33
+ [project.scripts]
34
+ patch-torchcodec = "patch_torchcodec.__main__:main"
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/open-world-agents/MediaRef"
38
+ Repository = "https://github.com/open-world-agents/MediaRef"
39
+
40
+ [tool.hatch.build.targets.wheel]
41
+ packages = ["patch_torchcodec"]
42
+