# **Better DXcam**
> ***Fastest Python Screenshot for Windows, Forked, and Maintained***
```python
import betterdxcam
camera = betterdxcam.create()
camera.grab()
```
## Introduction
BetterDXcam is a fork of [DXcam](https://github.com/ra1nty/DXcam), a Python high-performance screenshot library for Windows using Desktop Duplication API. Capable of 240Hz+ capturing. It was originally built as a part of deep learning pipeline for FPS games to perform better than existed python solutions ([python-mss](https://github.com/BoboTiG/python-mss), [D3DShot](https://github.com/SerpentAI/D3DShot/)).
BetterDXcam provides these improvements over DXcam:
- Fixed crashing when screen changes resolution
Compared to these existed solutions, DXcam provides:
- Way faster screen capturing speed (> 240Hz)
- Capturing of Direct3D exclusive full-screen application without interrupting, even when alt+tab.
- Automatic handling of scaled / stretched resolution.
- Accurate FPS targeting when in capturing mode, makes it suitable for Video output.
- Seamless integration with NumPy, OpenCV, PyTorch, etc.
## Installation
### From PyPI:
```bash
pip install betterdxcam
```
**Note:** OpenCV is required by betterDXcam for colorspace conversion. If you don't already have OpenCV, install it easily with command `pip install betterdxcam[cv2]`.
### From source:
```bash
pip install --editable .
# for installing OpenCV also
pip install --editable .[cv2]
```
## Usage
In betterDXCam, each output (monitor) is asscociated to a ```betterDXCamera``` instance.
To create a betterDXCamera instance:
```python
import betterdxcam
camera = betterdxcam.create() # returns a betterDXCamera instance on primary monitor
```
### Screenshot
For screenshot, simply use ```.grab```:
```python
frame = camera.grab()
```
The returned ```frame``` will be a ```numpy.ndarray``` in the shape of ```(Height, Width, 3[RGB])```. This is the default and the only supported format (**for now**). It is worth noting that ```.grab``` will return ```None``` if there is no new frame since the last time you called ```.grab```. Usually it means there's nothing new to render since last time (E.g. You are idling).
To view the captured screenshot:
```python
from PIL import Image
Image.fromarray(frame).show()
```
To screenshot a specific region, use the ```region``` parameter: it takes ```tuple[int, int, int, int]``` as the left, top, right, bottom coordinates of the bounding box. Similar to [PIL.ImageGrab.grab](https://pillow.readthedocs.io/en/stable/reference/ImageGrab.html).
```python
left, top = (1920 - 640) // 2, (1080 - 640) // 2
right, bottom = left + 640, top + 640
region = (left, top, right, bottom)
frame = camera.grab(region=region) # numpy.ndarray of size (640x640x3) -> (HXWXC)
```
The above code will take a screenshot of the center ```640x640``` portion of a ```1920x1080``` monitor.
### Screen Capture
To start a screen capture, simply use ```.start```: the capture will be started in a separated thread, default at 60Hz. Use ```.stop``` to stop the capture.
```python
camera.start(region=(left, top, right, bottom)) # Optional argument to capture a region
camera.is_capturing # True
# ... Do Something
camera.stop()
camera.is_capturing # False
```
### Consume the Screen Capture Data
While the ```betterDXCamera``` instance is in capture mode, you can use ```.get_latest_frame``` to get the latest frame in the frame buffer:
```python
camera.start()
for i in range(1000):
image = camera.get_latest_frame() # Will block until new frame available
camera.stop()
```
Notice that ```.get_latest_frame``` by default will block until there is a new frame available since the last call to ```.get_latest_frame```. To change this behavior, use ```video_mode=True```.
## Advanced Usage and Remarks
### Multiple monitors / GPUs
```python
cam1 = betterdxcam.create(device_idx=0, output_idx=0)
cam2 = betterdxcam.create(device_idx=0, output_idx=1)
cam3 = betterdxcam.create(device_idx=1, output_idx=1)
img1 = cam1.grab()
img2 = cam2.grab()
img2 = cam3.grab()
```
The above code creates three ```betterDXCamera``` instances for: ```[monitor0, GPU0], [monitor1, GPU0], [monitor1, GPU1]```, and subsequently takes three full-screen screenshots. (cross GPU untested, but I hope it works.) To get a complete list of devices and outputs:
```pycon
>>> import betterdxcam
>>> betterdxcam.device_info()
'Device[0]:<Device Name:NVIDIA GeForce RTX 3090 Dedicated VRAM:24348Mb VendorId:4318>\n'
>>> betterdxcam.output_info()
'Device[0] Output[0]: Res:(1920, 1080) Rot:0 Primary:True\nDevice[0] Output[1]: Res:(1920, 1080) Rot:0 Primary:False\n'
```
### Output Format
You can specify the output color mode upon creation of the betterDXCamera instance:
```python
betterdxcam.create(output_idx=0, output_color="BGRA")
```
We currently support "RGB", "RGBA", "BGR", "BGRA", "GRAY", with "GRAY being the gray scale. As for the data format, ```betterDXCamera``` only supports ```numpy.ndarray``` in shape of ```(Height, Width, Channels)``` right now. ***We will soon add support for other output formats.***
### Video Buffer
The captured frames will be insert into a fixed-size ring buffer, and when the buffer is full the newest frame will replace the oldest frame. You can specify the max buffer length (defualt to 64) using the argument ```max_buffer_len``` upon creation of the ```betterDXCamera``` instance.
```python
camera = betterdxcam.create(max_buffer_len=512)
```
***Note: Right now to consume frames during capturing there is only `get_latest_frame` available which assume the user to process frames in a LIFO pattern. This is a read-only action and won't pop the processed frame from the buffer. we will make changes to support various of consuming pattern soon.***
### Target FPS
To make ```betterDXCamera``` capture close to the user specified ```target_fps```, we used the undocumented ```CREATE_WAITABLE_TIMER_HIGH_RESOLUTION ``` flag to create a Windows [Waitable Timer Object](https://docs.microsoft.com/en-us/windows/win32/sync/waitable-timer-objects). This is far more accurate (+/- 1ms) than Python (<3.11) ```time.sleep``` (min resolution 16ms). The implementation is done through ```ctypes``` creating a perodic timer. Python 3.11 used a similar approach[^2].
```python
camera.start(target_fps=120) # Should not be made greater than 160.
```
However, due to Windows itself is a preemptive OS[^1] and the overhead of Python calls, the target FPS can not be guarenteed accurate when greater than 160. (See Benchmarks)
### Video Mode
The default behavior of ```.get_latest_frame``` only put newly rendered frame in the buffer, which suits the usage scenario of a object detection/machine learning pipeline. However, when recording a video that is not ideal since we aim to get the frames at a constant framerate: When the ```video_mode=True``` is specified when calling ```.start``` method of a ```betterDXCamera``` instance, the frame buffer will be feeded at the target fps, using the last frame if there is no new frame available. For example, the following code output a 5-second, 120Hz screen capture:
```python
target_fps = 120
camera = betterdxcam.create(output_idx=0, output_color="BGR")
camera.start(target_fps=target_fps, video_mode=True)
writer = cv2.VideoWriter(
"video.mp4", cv2.VideoWriter_fourcc(*"mp4v"), target_fps, (1920, 1080)
)
for i in range(600):
writer.write(camera.get_latest_frame())
camera.stop()
writer.release()
```
> You can do interesting stuff with libraries like ```pyav``` and ```pynput```: see examples/instant_replay.py for a ghetto implementation of instant replay using hot-keys
### Safely Releasing of Resource
Upon calling ```.release``` on a betterDXCamera instance, it will stop any active capturing, free the buffer and release the duplicator and staging resource. Upon calling ```.stop()```, betterDXCamera will stop the active capture and free the frame buffer. If you want to manually recreate a ```betterDXCamera``` instance on the same output with different parameters, you can also manully delete it:
```python
camera1 = betterdxcam.create(output_idx=0, output_color="BGR")
camera2 = betterdxcam.create(output_idx=0) # Not allowed, camera1 will be returned
camera1 is camera2 # True
del camera1
del camera2
camera2 = betterdxcam.create(output_idx=0) # Allowed
```
## Benchmarks
### For Max FPS Capability:
```python
start_time, fps = time.perf_counter(), 0
cam = betterdxcam.create()
start = time.perf_counter()
while fps < 1000:
frame = cam.grab()
if frame is not None: # New frame
fps += 1
end_time = time.perf_counter() - start_time
print(f"{title}: {fps/end_time}")
```
When using a similar logistic (only captured new frame counts), ```betterDXcam / DXcam, python-mss, D3DShot``` benchmarked as follow:
| | DXcam | python-mss | D3DShot |
|-------------|--------|------------|---------|
| Average FPS | 238.79 :checkered_flag: | 75.87 | 118.36 |
| Std Dev | 1.25 | 0.5447 | 0.3224 |
The benchmark is across 5 runs, with a light-moderate usage on my PC (5900X + 3090; Chrome ~30tabs, VS Code opened, etc.), I used the [Blur Buster UFO test](https://www.testufo.com/framerates#count=5&background=stars&pps=960) to constantly render 240 fps on my monitor (Zowie 2546K). DXcam captured almost every frame rendered.
### For Targeting FPS:
```python
camera = betterdxcam.create(output_idx=0)
camera.start(target_fps=60)
for i in range(1000):
image = camera.get_latest_frame()
camera.stop()
```
| (Target)\\(mean,std) | betterDXcam / DXcam | python-mss | D3DShot |
|------------- |-------- |------------|---------|
| 60fps | 61.71, 0.26 :checkered_flag: | N/A | 47.11, 1.33 |
| 30fps | 30.08, 0.02 :checkered_flag: | N/A | 21.24, 0.17 |
## Work Referenced
[D3DShot](https://github.com/SerpentAI/D3DShot/) : DXcam (and by extension betterDXcam) borrows the ctypes header directly from the no-longer maintained D3DShot.
[OBS Studio](https://github.com/obsproject/obs-studio) : Learned a lot from it.
[^1]: <https://en.wikipedia.org/wiki/Preemption_(computing)> Preemption (computing)
[^2]: <https://github.com/python/cpython/issues/65501> bpo-21302: time.sleep() uses waitable timer on Windows
Raw data
{
"_id": null,
"home_page": "https://github.com/E1Bos/betterDXcam",
"name": "betterDXcam",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": null,
"keywords": "screen, screenshot, screencapture, screengrab, windows",
"author": "E1Bos",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/ac/4a/e9d56795e66bb88bb03ac21fb5675d82d2d275726fdc7b3ea94362f6026b/betterdxcam-0.0.9.tar.gz",
"platform": null,
"description": "# **Better DXcam**\r\n> ***Fastest Python Screenshot for Windows, Forked, and Maintained***\r\n```python\r\nimport betterdxcam\r\ncamera = betterdxcam.create()\r\ncamera.grab()\r\n```\r\n\r\n## Introduction\r\nBetterDXcam is a fork of [DXcam](https://github.com/ra1nty/DXcam), a Python high-performance screenshot library for Windows using Desktop Duplication API. Capable of 240Hz+ capturing. It was originally built as a part of deep learning pipeline for FPS games to perform better than existed python solutions ([python-mss](https://github.com/BoboTiG/python-mss), [D3DShot](https://github.com/SerpentAI/D3DShot/)). \r\n\r\nBetterDXcam provides these improvements over DXcam:\r\n- Fixed crashing when screen changes resolution\r\n\r\nCompared to these existed solutions, DXcam provides:\r\n- Way faster screen capturing speed (> 240Hz)\r\n- Capturing of Direct3D exclusive full-screen application without interrupting, even when alt+tab.\r\n- Automatic handling of scaled / stretched resolution.\r\n- Accurate FPS targeting when in capturing mode, makes it suitable for Video output. \r\n- Seamless integration with NumPy, OpenCV, PyTorch, etc.\r\n\r\n## Installation\r\n### From PyPI:\r\n```bash\r\npip install betterdxcam\r\n```\r\n\r\n**Note:** OpenCV is required by betterDXcam for colorspace conversion. If you don't already have OpenCV, install it easily with command `pip install betterdxcam[cv2]`.\r\n\r\n### From source:\r\n```bash\r\npip install --editable .\r\n\r\n# for installing OpenCV also\r\npip install --editable .[cv2]\r\n```\r\n\r\n## Usage\r\nIn betterDXCam, each output (monitor) is asscociated to a ```betterDXCamera``` instance.\r\nTo create a betterDXCamera instance:\r\n```python\r\nimport betterdxcam\r\ncamera = betterdxcam.create() # returns a betterDXCamera instance on primary monitor\r\n```\r\n### Screenshot\r\nFor screenshot, simply use ```.grab```:\r\n```python\r\nframe = camera.grab()\r\n```\r\nThe returned ```frame``` will be a ```numpy.ndarray``` in the shape of ```(Height, Width, 3[RGB])```. This is the default and the only supported format (**for now**). It is worth noting that ```.grab``` will return ```None``` if there is no new frame since the last time you called ```.grab```. Usually it means there's nothing new to render since last time (E.g. You are idling).\r\n\r\nTo view the captured screenshot:\r\n```python\r\nfrom PIL import Image\r\nImage.fromarray(frame).show()\r\n```\r\nTo screenshot a specific region, use the ```region``` parameter: it takes ```tuple[int, int, int, int]``` as the left, top, right, bottom coordinates of the bounding box. Similar to [PIL.ImageGrab.grab](https://pillow.readthedocs.io/en/stable/reference/ImageGrab.html).\r\n```python\r\nleft, top = (1920 - 640) // 2, (1080 - 640) // 2\r\nright, bottom = left + 640, top + 640\r\nregion = (left, top, right, bottom)\r\nframe = camera.grab(region=region) # numpy.ndarray of size (640x640x3) -> (HXWXC)\r\n```\r\nThe above code will take a screenshot of the center ```640x640``` portion of a ```1920x1080``` monitor.\r\n### Screen Capture\r\nTo start a screen capture, simply use ```.start```: the capture will be started in a separated thread, default at 60Hz. Use ```.stop``` to stop the capture.\r\n```python\r\ncamera.start(region=(left, top, right, bottom)) # Optional argument to capture a region\r\ncamera.is_capturing # True\r\n# ... Do Something\r\ncamera.stop()\r\ncamera.is_capturing # False\r\n```\r\n### Consume the Screen Capture Data\r\nWhile the ```betterDXCamera``` instance is in capture mode, you can use ```.get_latest_frame``` to get the latest frame in the frame buffer:\r\n```python\r\ncamera.start()\r\nfor i in range(1000):\r\n image = camera.get_latest_frame() # Will block until new frame available\r\ncamera.stop()\r\n```\r\nNotice that ```.get_latest_frame``` by default will block until there is a new frame available since the last call to ```.get_latest_frame```. To change this behavior, use ```video_mode=True```.\r\n\r\n## Advanced Usage and Remarks\r\n### Multiple monitors / GPUs\r\n```python\r\ncam1 = betterdxcam.create(device_idx=0, output_idx=0)\r\ncam2 = betterdxcam.create(device_idx=0, output_idx=1)\r\ncam3 = betterdxcam.create(device_idx=1, output_idx=1)\r\nimg1 = cam1.grab()\r\nimg2 = cam2.grab()\r\nimg2 = cam3.grab()\r\n```\r\nThe above code creates three ```betterDXCamera``` instances for: ```[monitor0, GPU0], [monitor1, GPU0], [monitor1, GPU1]```, and subsequently takes three full-screen screenshots. (cross GPU untested, but I hope it works.) To get a complete list of devices and outputs:\r\n```pycon\r\n>>> import betterdxcam\r\n>>> betterdxcam.device_info()\r\n'Device[0]:<Device Name:NVIDIA GeForce RTX 3090 Dedicated VRAM:24348Mb VendorId:4318>\\n'\r\n>>> betterdxcam.output_info()\r\n'Device[0] Output[0]: Res:(1920, 1080) Rot:0 Primary:True\\nDevice[0] Output[1]: Res:(1920, 1080) Rot:0 Primary:False\\n'\r\n```\r\n\r\n### Output Format\r\nYou can specify the output color mode upon creation of the betterDXCamera instance:\r\n```python\r\nbetterdxcam.create(output_idx=0, output_color=\"BGRA\")\r\n```\r\nWe currently support \"RGB\", \"RGBA\", \"BGR\", \"BGRA\", \"GRAY\", with \"GRAY being the gray scale. As for the data format, ```betterDXCamera``` only supports ```numpy.ndarray``` in shape of ```(Height, Width, Channels)``` right now. ***We will soon add support for other output formats.***\r\n\r\n### Video Buffer\r\nThe captured frames will be insert into a fixed-size ring buffer, and when the buffer is full the newest frame will replace the oldest frame. You can specify the max buffer length (defualt to 64) using the argument ```max_buffer_len``` upon creation of the ```betterDXCamera``` instance. \r\n```python\r\ncamera = betterdxcam.create(max_buffer_len=512)\r\n```\r\n***Note: Right now to consume frames during capturing there is only `get_latest_frame` available which assume the user to process frames in a LIFO pattern. This is a read-only action and won't pop the processed frame from the buffer. we will make changes to support various of consuming pattern soon.***\r\n\r\n### Target FPS\r\nTo make ```betterDXCamera``` capture close to the user specified ```target_fps```, we used the undocumented ```CREATE_WAITABLE_TIMER_HIGH_RESOLUTION ``` flag to create a Windows [Waitable Timer Object](https://docs.microsoft.com/en-us/windows/win32/sync/waitable-timer-objects). This is far more accurate (+/- 1ms) than Python (<3.11) ```time.sleep``` (min resolution 16ms). The implementation is done through ```ctypes``` creating a perodic timer. Python 3.11 used a similar approach[^2]. \r\n```python\r\ncamera.start(target_fps=120) # Should not be made greater than 160.\r\n```\r\nHowever, due to Windows itself is a preemptive OS[^1] and the overhead of Python calls, the target FPS can not be guarenteed accurate when greater than 160. (See Benchmarks)\r\n\r\n\r\n### Video Mode\r\nThe default behavior of ```.get_latest_frame``` only put newly rendered frame in the buffer, which suits the usage scenario of a object detection/machine learning pipeline. However, when recording a video that is not ideal since we aim to get the frames at a constant framerate: When the ```video_mode=True``` is specified when calling ```.start``` method of a ```betterDXCamera``` instance, the frame buffer will be feeded at the target fps, using the last frame if there is no new frame available. For example, the following code output a 5-second, 120Hz screen capture:\r\n```python\r\ntarget_fps = 120\r\ncamera = betterdxcam.create(output_idx=0, output_color=\"BGR\")\r\ncamera.start(target_fps=target_fps, video_mode=True)\r\nwriter = cv2.VideoWriter(\r\n \"video.mp4\", cv2.VideoWriter_fourcc(*\"mp4v\"), target_fps, (1920, 1080)\r\n)\r\nfor i in range(600):\r\n writer.write(camera.get_latest_frame())\r\ncamera.stop()\r\nwriter.release()\r\n```\r\n> You can do interesting stuff with libraries like ```pyav``` and ```pynput```: see examples/instant_replay.py for a ghetto implementation of instant replay using hot-keys\r\n\r\n\r\n### Safely Releasing of Resource\r\nUpon calling ```.release``` on a betterDXCamera instance, it will stop any active capturing, free the buffer and release the duplicator and staging resource. Upon calling ```.stop()```, betterDXCamera will stop the active capture and free the frame buffer. If you want to manually recreate a ```betterDXCamera``` instance on the same output with different parameters, you can also manully delete it:\r\n```python\r\ncamera1 = betterdxcam.create(output_idx=0, output_color=\"BGR\")\r\ncamera2 = betterdxcam.create(output_idx=0) # Not allowed, camera1 will be returned\r\ncamera1 is camera2 # True\r\ndel camera1\r\ndel camera2\r\ncamera2 = betterdxcam.create(output_idx=0) # Allowed\r\n```\r\n\r\n## Benchmarks\r\n### For Max FPS Capability:\r\n```python\r\nstart_time, fps = time.perf_counter(), 0\r\ncam = betterdxcam.create()\r\nstart = time.perf_counter()\r\nwhile fps < 1000:\r\n frame = cam.grab()\r\n if frame is not None: # New frame\r\n fps += 1\r\nend_time = time.perf_counter() - start_time\r\nprint(f\"{title}: {fps/end_time}\")\r\n```\r\nWhen using a similar logistic (only captured new frame counts), ```betterDXcam / DXcam, python-mss, D3DShot``` benchmarked as follow:\r\n\r\n| | DXcam | python-mss | D3DShot |\r\n|-------------|--------|------------|---------|\r\n| Average FPS | 238.79 :checkered_flag: | 75.87 | 118.36 |\r\n| Std Dev | 1.25 | 0.5447 | 0.3224 |\r\n\r\nThe benchmark is across 5 runs, with a light-moderate usage on my PC (5900X + 3090; Chrome ~30tabs, VS Code opened, etc.), I used the [Blur Buster UFO test](https://www.testufo.com/framerates#count=5&background=stars&pps=960) to constantly render 240 fps on my monitor (Zowie 2546K). DXcam captured almost every frame rendered.\r\n\r\n### For Targeting FPS:\r\n```python\r\ncamera = betterdxcam.create(output_idx=0)\r\ncamera.start(target_fps=60)\r\nfor i in range(1000):\r\n image = camera.get_latest_frame()\r\ncamera.stop()\r\n```\r\n| (Target)\\\\(mean,std) | betterDXcam / DXcam | python-mss | D3DShot |\r\n|------------- |-------- |------------|---------|\r\n| 60fps | 61.71, 0.26 :checkered_flag: | N/A | 47.11, 1.33 |\r\n| 30fps | 30.08, 0.02 :checkered_flag: | N/A | 21.24, 0.17 |\r\n\r\n## Work Referenced\r\n[D3DShot](https://github.com/SerpentAI/D3DShot/) : DXcam (and by extension betterDXcam) borrows the ctypes header directly from the no-longer maintained D3DShot.\r\n\r\n[OBS Studio](https://github.com/obsproject/obs-studio) : Learned a lot from it.\r\n\r\n\r\n[^1]: <https://en.wikipedia.org/wiki/Preemption_(computing)> Preemption (computing)\r\n\r\n[^2]: <https://github.com/python/cpython/issues/65501> bpo-21302: time.sleep() uses waitable timer on Windows\r\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A Python high-performance screenshot library for Windows use Desktop Duplication API",
"version": "0.0.9",
"project_urls": {
"Homepage": "https://github.com/E1Bos/betterDXcam",
"Source": "https://github.com/E1Bos/betterdxcam",
"Tracker": "https://github.com/E1Bos/betterdxcam/issues"
},
"split_keywords": [
"screen",
" screenshot",
" screencapture",
" screengrab",
" windows"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "c2d219bf81e23c96aa00bea73b72bdb9cb007dfd0c37fdaeca8f6e3942e133a1",
"md5": "9813b7ed09d0e3af66651c8cd7edc3b1",
"sha256": "37c38a75805c9074abe364c561aaa4cbf1a5aa2b48b09cd2c7a70695ed9b6253"
},
"downloads": -1,
"filename": "betterDXcam-0.0.9-py3-none-any.whl",
"has_sig": false,
"md5_digest": "9813b7ed09d0e3af66651c8cd7edc3b1",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 21681,
"upload_time": "2024-06-14T16:53:08",
"upload_time_iso_8601": "2024-06-14T16:53:08.642261Z",
"url": "https://files.pythonhosted.org/packages/c2/d2/19bf81e23c96aa00bea73b72bdb9cb007dfd0c37fdaeca8f6e3942e133a1/betterDXcam-0.0.9-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "ac4ae9d56795e66bb88bb03ac21fb5675d82d2d275726fdc7b3ea94362f6026b",
"md5": "e6d714ce9207a8f3a7b92a1b5a1141e9",
"sha256": "5ee82bc4ed565f0bd5d4ac95f842c751d4a9995d9a03265d3a7af37a24c471e5"
},
"downloads": -1,
"filename": "betterdxcam-0.0.9.tar.gz",
"has_sig": false,
"md5_digest": "e6d714ce9207a8f3a7b92a1b5a1141e9",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 18020,
"upload_time": "2024-06-14T16:53:10",
"upload_time_iso_8601": "2024-06-14T16:53:10.382761Z",
"url": "https://files.pythonhosted.org/packages/ac/4a/e9d56795e66bb88bb03ac21fb5675d82d2d275726fdc7b3ea94362f6026b/betterdxcam-0.0.9.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-06-14 16:53:10",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "E1Bos",
"github_project": "betterDXcam",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "betterdxcam"
}