mirror of
https://github.com/onyx-and-iris/voicemeeter-api-python.git
synced 2024-11-21 10:30:47 +00:00
version 2.0.0 section added to changelog
readme updated to reflect latest changes test badges updated. fixes #5
This commit is contained in:
parent
f57475daa0
commit
2f9864cf60
2
.gitignore
vendored
2
.gitignore
vendored
@ -131,5 +131,7 @@ dmypy.json
|
|||||||
# test/config
|
# test/config
|
||||||
quick.py
|
quick.py
|
||||||
config.toml
|
config.toml
|
||||||
|
vm-api.log
|
||||||
|
logging.json
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
64
CHANGELOG.md
64
CHANGELOG.md
@ -11,6 +11,68 @@ Before any major/minor/patch bump all unit tests will be run to verify they pass
|
|||||||
|
|
||||||
- [x]
|
- [x]
|
||||||
|
|
||||||
|
## [2.0.0] - 2023-06-19
|
||||||
|
|
||||||
|
Where possible I've attempted to make the changes backwards compatible. The breaking changes affect two higher classes, Strip and Bus, as well as the behaviour of events. All other changes are additive or QOL aimed at giving more options to the developer. For example, every low-level CAPI call is now logged and error raised on Exception, you can now register callback functions as well as observer classes, extra examples to demonstrate different use cases etc.
|
||||||
|
|
||||||
|
This breaking changes are as follows:
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `strip[i].comp` now references StripComp class
|
||||||
|
- To change the comp knob you should now use the property `strip[i].comp.knob`
|
||||||
|
- `strip[i].gate` now references StripGate class
|
||||||
|
|
||||||
|
- To change the gate knob you should now use the property `strip[i].gate.knob`
|
||||||
|
|
||||||
|
- `bus[i].eq` now references BusEQ class
|
||||||
|
|
||||||
|
- To set bus[i].{eq,eq_ab} as before you should now use bus[i].eq.on and bus[i].eq.ab
|
||||||
|
|
||||||
|
- by default, <strong>NO</strong> events are checked for. This is reflected in factory.FactoryBase defaultkwargs.
|
||||||
|
- This is a fundamental behaviour change from version 1.0 of the wrapper. It means the following:
|
||||||
|
- Unless any events are explicitly requested with an event kwarg the event emitter thread will not run automatically.
|
||||||
|
- Whether using a context manager or not, you can still initiate the event thread manually and request events with the event object.<br>
|
||||||
|
see `events` example.
|
||||||
|
|
||||||
|
There are other non-breaking changes:
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `strip[i].eq` added to PhysicalStrip
|
||||||
|
- `strip[i].denoiser` added to PhysicalStrip
|
||||||
|
- `Strip.Comp`, `Strip.Gate`, `Strip.Denoiser` sections added to README.
|
||||||
|
- `Events` section in readme updated to reflect changes to events kwargs.
|
||||||
|
- new comp, gate, denoiser and eq tests added to higher tests.
|
||||||
|
- `levels` example to demonstrate use of the interface without a context manager.
|
||||||
|
- `events` example to demonstrate how to interact with event thread/event object.
|
||||||
|
- `{Remote}.observer` can be used in place of `{Remote}.subject` although subject will still work. Check examples.
|
||||||
|
- Subject class extended to allow registering/de-registering callback functions (as well as observer classes). See `events` example.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `comp.knob`, `gate.knob`, `denoiser.knob`, `eq.on` added to phys_strip_params in config.TOMLStrBuilder
|
||||||
|
|
||||||
|
- The `example.toml` config files have been updated to demonstrate setting new comp, gate and eq settings.
|
||||||
|
|
||||||
|
- event kwargs can now be set directly. no need for `subs`. example: `voicemeeterlib.api('banana', midi=True})`
|
||||||
|
|
||||||
|
- factorybuilder steps now logged in DEBUG mode.
|
||||||
|
|
||||||
|
- now using a producer thread to send events to the updater thread.
|
||||||
|
|
||||||
|
- module level loggers implemented (with class loggers as child loggers)
|
||||||
|
|
||||||
|
- config.loader now checks `Path.home() / ".config" / "voicemeeter" / kind.name` for configs.
|
||||||
|
- note. `Path(__file__).parent / "configs" / kind.name,` was removed as a path to check.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- All low level CAPI calls are now wrapped by CBindings.call() which logs any errors raised.
|
||||||
|
- Dynamic binding of Macrobutton functions from the CAPI.
|
||||||
|
Should add backwards compatibility with very old versions of the api. See [Issue #4][issue 4].
|
||||||
|
- factory.request_remote_obj now raises a `VMError` if passed an incorrect kind.
|
||||||
|
|
||||||
## [1.0.0] - 2023-06-19
|
## [1.0.0] - 2023-06-19
|
||||||
|
|
||||||
No changes to the codebase but it has been stable for several months and should already have been bumped to major version 1.0
|
No changes to the codebase but it has been stable for several months and should already have been bumped to major version 1.0
|
||||||
@ -298,3 +360,5 @@ I will move this commit to a separate branch in preparation for version 2.0.
|
|||||||
- inst module implemented (fetch vm path from registry)
|
- inst module implemented (fetch vm path from registry)
|
||||||
- kind maps implemented as dataclasses
|
- kind maps implemented as dataclasses
|
||||||
- project packaged with poetry and added to pypi.
|
- project packaged with poetry and added to pypi.
|
||||||
|
|
||||||
|
[issue 4]: https://github.com/onyx-and-iris/voicemeeter-api-python/issues/4
|
||||||
|
131
README.md
131
README.md
@ -52,16 +52,18 @@ class ManyThings:
|
|||||||
|
|
||||||
def other_things(self):
|
def other_things(self):
|
||||||
self.vm.bus[3].gain = -6.3
|
self.vm.bus[3].gain = -6.3
|
||||||
self.vm.bus[4].eq = True
|
self.vm.bus[4].eq.on = True
|
||||||
info = (
|
info = (
|
||||||
f"bus 3 gain has been set to {self.vm.bus[3].gain}",
|
f"bus 3 gain has been set to {self.vm.bus[3].gain}",
|
||||||
f"bus 4 eq has been set to {self.vm.bus[4].eq}",
|
f"bus 4 eq has been set to {self.vm.bus[4].eq.on}",
|
||||||
)
|
)
|
||||||
print("\n".join(info))
|
print("\n".join(info))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
with voicemeeterlib.api(kind_id) as vm:
|
KIND_ID = "banana"
|
||||||
|
|
||||||
|
with voicemeeterlib.api(KIND_ID) as vm:
|
||||||
do = ManyThings(vm)
|
do = ManyThings(vm)
|
||||||
do.things()
|
do.things()
|
||||||
do.other_things()
|
do.other_things()
|
||||||
@ -79,16 +81,14 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
kind_id = "banana"
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
```
|
```
|
||||||
|
|
||||||
Otherwise you must remember to call `vm.login()`, `vm.logout()` at the start/end of your code.
|
Otherwise you must remember to call `vm.login()`, `vm.logout()` at the start/end of your code.
|
||||||
|
|
||||||
## `kind_id`
|
## `KIND_ID`
|
||||||
|
|
||||||
Pass the kind of Voicemeeter as an argument. kind_id may be:
|
Pass the kind of Voicemeeter as an argument. KIND_ID may be:
|
||||||
|
|
||||||
- `basic`
|
- `basic`
|
||||||
- `banana`
|
- `banana`
|
||||||
@ -104,8 +104,6 @@ The following properties are available.
|
|||||||
- `solo`: boolean
|
- `solo`: boolean
|
||||||
- `mute`: boolean
|
- `mute`: boolean
|
||||||
- `gain`: float, from -60.0 to 12.0
|
- `gain`: float, from -60.0 to 12.0
|
||||||
- `comp`: float, from 0.0 to 10.0
|
|
||||||
- `gate`: float, from 0.0 to 10.0
|
|
||||||
- `audibility`: float, from 0.0 to 10.0
|
- `audibility`: float, from 0.0 to 10.0
|
||||||
- `limit`: int, from -40 to 12
|
- `limit`: int, from -40 to 12
|
||||||
- `A1 - A5`, `B1 - B3`: boolean
|
- `A1 - A5`, `B1 - B3`: boolean
|
||||||
@ -154,7 +152,72 @@ vm.strip[5].appmute("Spotify", True)
|
|||||||
vm.strip[5].appgain("Spotify", 0.5)
|
vm.strip[5].appgain("Spotify", 0.5)
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Gainlayers
|
#### Strip.Comp
|
||||||
|
|
||||||
|
- `knob`: float, from 0.0 to 10.0
|
||||||
|
- `gainin`: float, from -24.0 to 24.0
|
||||||
|
- `ratio`: float, from 1.0 to 8.0
|
||||||
|
- `threshold`: float, from -40.0 to -3.0
|
||||||
|
- `attack`: float, from 0.0 to 200.0
|
||||||
|
- `release`: float, from 0.0 to 5000.0
|
||||||
|
- `knee`: float, from 0.0 to 1.0
|
||||||
|
- `gainout`: float, from -24.0 to 24.0
|
||||||
|
- `makeup`: boolean
|
||||||
|
|
||||||
|
example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print(vm.strip[4].comp.knob)
|
||||||
|
```
|
||||||
|
|
||||||
|
Strip Comp parameters are defined for PhysicalStrips, potato version only.
|
||||||
|
|
||||||
|
#### Strip.Gate
|
||||||
|
|
||||||
|
- `knob`: float, from 0.0 to 10.0
|
||||||
|
- `threshold`: float, from -60.0 to -10.0
|
||||||
|
- `damping`: float, from -60.0 to -10.0
|
||||||
|
- `bpsidechain`: int, from 100 to 4000
|
||||||
|
- `attack`: float, from 0.0 to 1000.0
|
||||||
|
- `hold`: float, from 0.0 to 5000.0
|
||||||
|
- `release`: float, from 0.0 to 5000.0
|
||||||
|
|
||||||
|
example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
vm.strip[2].gate.attack = 300.8
|
||||||
|
```
|
||||||
|
|
||||||
|
Strip Gate parameters are defined for PhysicalStrips, potato version only.
|
||||||
|
|
||||||
|
#### Strip.Denoiser
|
||||||
|
|
||||||
|
- `knob`: float, from 0.0 to 10.0
|
||||||
|
|
||||||
|
example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
vm.strip[0].denoiser.knob = 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Strip Denoiser parameters are defined for PhysicalStrips, potato version only.
|
||||||
|
|
||||||
|
#### Strip.EQ
|
||||||
|
|
||||||
|
The following properties are available.
|
||||||
|
|
||||||
|
- `on`: boolean
|
||||||
|
- `ab`: boolean
|
||||||
|
|
||||||
|
example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
vm.strip[0].eq.ab = True
|
||||||
|
```
|
||||||
|
|
||||||
|
Strip EQ parameters are defined for PhysicalStrips, potato version only.
|
||||||
|
|
||||||
|
##### Strip.Gainlayers
|
||||||
|
|
||||||
- `gain`: float, from -60.0 to 12.0
|
- `gain`: float, from -60.0 to 12.0
|
||||||
|
|
||||||
@ -166,7 +229,7 @@ vm.strip[3].gainlayer[3].gain = 3.7
|
|||||||
|
|
||||||
Gainlayers are defined for potato version only.
|
Gainlayers are defined for potato version only.
|
||||||
|
|
||||||
##### Levels
|
##### Strip.Levels
|
||||||
|
|
||||||
The following properties are available.
|
The following properties are available.
|
||||||
|
|
||||||
@ -187,8 +250,6 @@ Level properties will return -200.0 if no audio detected.
|
|||||||
The following properties are available.
|
The following properties are available.
|
||||||
|
|
||||||
- `mono`: boolean
|
- `mono`: boolean
|
||||||
- `eq`: boolean
|
|
||||||
- `eq_ab`: boolean
|
|
||||||
- `mute`: boolean
|
- `mute`: boolean
|
||||||
- `sel`: boolean
|
- `sel`: boolean
|
||||||
- `gain`: float, from -60.0 to 12.0
|
- `gain`: float, from -60.0 to 12.0
|
||||||
@ -208,7 +269,20 @@ print(vm.bus[0].label)
|
|||||||
vm.bus[4].mono = True
|
vm.bus[4].mono = True
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Modes
|
##### Bus.EQ
|
||||||
|
|
||||||
|
The following properties are available.
|
||||||
|
|
||||||
|
- `on`: boolean
|
||||||
|
- `ab`: boolean
|
||||||
|
|
||||||
|
example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
vm.bus[3].eq.on = True
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Bus.Modes
|
||||||
|
|
||||||
The following properties are available.
|
The following properties are available.
|
||||||
|
|
||||||
@ -236,7 +310,7 @@ vm.bus[4].mode.amix = True
|
|||||||
print(vm.bus[2].mode.get())
|
print(vm.bus[2].mode.get())
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Levels
|
##### Bus.Levels
|
||||||
|
|
||||||
The following properties are available.
|
The following properties are available.
|
||||||
|
|
||||||
@ -409,7 +483,7 @@ example:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
import voicemeeterlib
|
import voicemeeterlib
|
||||||
with voicemeeterlib.api(kind_id) as vm:
|
with voicemeeterlib.api(KIND_ID) as vm:
|
||||||
for i in range(vm.device.ins):
|
for i in range(vm.device.ins):
|
||||||
print(vm.device.input(i))
|
print(vm.device.input(i))
|
||||||
```
|
```
|
||||||
@ -595,19 +669,19 @@ will load a user config file at configs/banana/example.toml for Voicemeeter Bana
|
|||||||
|
|
||||||
## Events
|
## Events
|
||||||
|
|
||||||
Level updates are considered high volume, by default they are NOT listened for. Use subs keyword arg to initialize event updates.
|
By default, NO events are listened for. Use events kwargs to enable specific event types.
|
||||||
|
|
||||||
example:
|
example:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import voicemeeterlib
|
import voicemeeterlib
|
||||||
# Set updates to occur every 50ms
|
# Set event updates to occur every 50ms
|
||||||
# Listen for level updates but disable midi updates
|
# Listen for level updates only
|
||||||
with voicemeeterlib.api('banana', ratelimit=0.05, subs={"ldirty": True, "midi": False}) as vm:
|
with voicemeeterlib.api('banana', ratelimit=0.05, ldirty=True}) as vm:
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `vm.subject`
|
#### `vm.observer`
|
||||||
|
|
||||||
Use the Subject class to register an app as event observer.
|
Use the Subject class to register an app as event observer.
|
||||||
|
|
||||||
@ -622,7 +696,7 @@ example:
|
|||||||
# register an app to receive updates
|
# register an app to receive updates
|
||||||
class App():
|
class App():
|
||||||
def __init__(self, vm):
|
def __init__(self, vm):
|
||||||
vm.subject.add(self)
|
vm.observer.add(self)
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -664,17 +738,16 @@ print(vm.event.get())
|
|||||||
|
|
||||||
## Remote class
|
## Remote class
|
||||||
|
|
||||||
`voicemeeterlib.api(kind_id: str)`
|
`voicemeeterlib.api(KIND_ID: str)`
|
||||||
|
|
||||||
You may pass the following optional keyword arguments:
|
You may pass the following optional keyword arguments:
|
||||||
|
|
||||||
- `sync`: boolean=False, force the getters to wait for dirty parameters to clear. For most cases leave this as False.
|
- `sync`: boolean=False, force the getters to wait for dirty parameters to clear. For most cases leave this as False.
|
||||||
- `ratelimit`: float=0.033, how often to check for updates in ms.
|
- `ratelimit`: float=0.033, how often to check for updates in ms.
|
||||||
- `subs`: dict={"pdirty": True, "mdirty": True, "midi": True, "ldirty": False}, initialize which event updates to listen for.
|
- `pdirty`: boolean=False, parameter updates
|
||||||
- `pdirty`: parameter updates
|
- `mdirty`: boolean=False, macrobutton updates
|
||||||
- `mdirty`: macrobutton updates
|
- `midi`: boolean=False, midi updates
|
||||||
- `midi`: midi updates
|
- `ldirty`: boolean=False, level updates
|
||||||
- `ldirty`: level updates
|
|
||||||
|
|
||||||
Access to lower level Getters and Setters are provided with these functions:
|
Access to lower level Getters and Setters are provided with these functions:
|
||||||
|
|
||||||
@ -705,4 +778,4 @@ pytest -v
|
|||||||
|
|
||||||
### Official Documentation
|
### Official Documentation
|
||||||
|
|
||||||
- [Voicemeeter Remote C API](https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/main/VoicemeeterRemoteAPI.pdf)
|
- [Voicemeeter Remote C API](https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/update-docs/VoicemeeterRemoteAPI.pdf)
|
||||||
|
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 140"><title>tests: 140</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">140</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">140</text></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 139"><title>tests: 139</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">139</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">139</text></g></svg>
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 116"><title>tests: 116</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">116</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">116</text></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 112"><title>tests: 112</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">112</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">112</text></g></svg>
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 158"><title>tests: 158</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">158</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">158</text></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="68" height="20" role="img" aria-label="tests: 164"><title>tests: 164</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">164</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">164</text></g></svg>
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Loading…
Reference in New Issue
Block a user