docs(global): add documentation
This commit is contained in:
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
58
docs/conf.py
Normal file
58
docs/conf.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tomllib
|
||||
|
||||
sys.path.insert(0, os.path.abspath("."))
|
||||
sys.path.insert(0, os.path.abspath("../."))
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = "logic-plugin-manager"
|
||||
copyright = "2025, h"
|
||||
author = "h"
|
||||
release = tomllib.load(open("../pyproject.toml", "rb"))["project"]["version"]
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinx_autodoc_typehints",
|
||||
]
|
||||
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
autodoc_default_options = {
|
||||
"members": True,
|
||||
"member-order": "bysource",
|
||||
"special-members": "__init__",
|
||||
"undoc-members": True,
|
||||
"exclude-members": "__weakref__",
|
||||
"inherited-members": False,
|
||||
}
|
||||
|
||||
autodoc_typehints = "description"
|
||||
typehints_use_signature = True
|
||||
typehints_use_signature_return = True
|
||||
|
||||
autodoc_member_order = "bysource"
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = "furo"
|
||||
html_static_path = ["_static"]
|
||||
html_title = "Logic Plugin Manager"
|
||||
html_theme_options = {
|
||||
"sidebar_hide_name": False,
|
||||
"navigation_with_keys": True,
|
||||
}
|
||||
467
docs/core_concepts.rst
Normal file
467
docs/core_concepts.rst
Normal file
@@ -0,0 +1,467 @@
|
||||
Core Concepts
|
||||
=============
|
||||
|
||||
This page explains the key concepts and architecture of Logic Plugin Manager.
|
||||
|
||||
Architecture Overview
|
||||
---------------------
|
||||
|
||||
Logic Plugin Manager is structured around three main areas:
|
||||
|
||||
1. **Components**: Audio Unit plugin bundles and their metadata
|
||||
2. **Logic Management**: High-level interface for plugin discovery and organization
|
||||
3. **Tags & Categories**: Logic Pro's categorization system
|
||||
|
||||
The Library's Structure
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
logic_plugin_manager/
|
||||
├── components/ # Audio Unit components
|
||||
│ ├── AudioComponent # Individual plugin representation
|
||||
│ ├── Component # .component bundle parser
|
||||
│ └── AudioUnitType # Audio Unit type enumeration
|
||||
├── logic/ # Main management interface
|
||||
│ ├── Logic # Primary entry point
|
||||
│ ├── Plugins # Plugin collection with search
|
||||
│ └── SearchResult # Search result with scoring
|
||||
└── tags/ # Category and tag management
|
||||
├── Category # Plugin category management
|
||||
├── MusicApps # Database interface
|
||||
├── Tagpool # Plugin count tracking
|
||||
├── Properties # Category sorting
|
||||
└── Tagset # Plugin tag files
|
||||
|
||||
Understanding Audio Components
|
||||
------------------------------
|
||||
|
||||
Audio Component Bundle Structure
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
macOS Audio Unit plugins are distributed as ``.component`` bundles:
|
||||
|
||||
::
|
||||
|
||||
PluginName.component/
|
||||
└── Contents/
|
||||
├── Info.plist # Metadata and component definitions
|
||||
├── MacOS/
|
||||
│ └── PluginName # Executable binary
|
||||
└── Resources/ # UI resources, presets, etc.
|
||||
|
||||
The ``Info.plist`` file contains an ``AudioComponents`` array defining one or more Audio Units within the bundle.
|
||||
|
||||
AudioComponent Class
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Each Audio Unit is represented by an ``AudioComponent`` instance with:
|
||||
|
||||
- **Identification**: Type, subtype, and manufacturer codes
|
||||
- **Metadata**: Name, description, version
|
||||
- **Categories**: Tags assigned in Logic Pro
|
||||
- **Tags ID**: Unique identifier derived from codes
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
plugin = logic.plugins.get_by_name("Pro-Q 3")
|
||||
|
||||
# Access component information
|
||||
print(f"Full Name: {plugin.full_name}")
|
||||
print(f"Manufacturer: {plugin.manufacturer}")
|
||||
print(f"Type Code: {plugin.type_code}")
|
||||
print(f"Subtype Code: {plugin.subtype_code}")
|
||||
print(f"Manufacturer Code: {plugin.manufacturer_code}")
|
||||
print(f"Tags ID: {plugin.tags_id}")
|
||||
|
||||
Audio Unit Types
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Logic Plugin Manager recognizes five Audio Unit types:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:widths: 15 25 20 40
|
||||
|
||||
* - Code
|
||||
- Display Name
|
||||
- Alternative Name
|
||||
- Description
|
||||
* - ``aufx``
|
||||
- Audio FX
|
||||
- Effect
|
||||
- Audio effect processors
|
||||
* - ``aumu``
|
||||
- Instrument
|
||||
- Music Device
|
||||
- Software instruments and synthesizers
|
||||
* - ``aumf``
|
||||
- MIDI-controlled Effects
|
||||
- Music Effect
|
||||
- Effects controlled by MIDI input
|
||||
* - ``aumi``
|
||||
- MIDI FX
|
||||
- MIDI Generator
|
||||
- MIDI processors and generators
|
||||
* - ``augn``
|
||||
- Generator
|
||||
- Generator
|
||||
- Audio generators
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import AudioUnitType
|
||||
|
||||
# Get type by code
|
||||
instrument_type = AudioUnitType.from_code("aumu")
|
||||
print(instrument_type.display_name) # "Instrument"
|
||||
|
||||
# Search for types
|
||||
effect_types = AudioUnitType.search("effect")
|
||||
|
||||
The Logic Class
|
||||
---------------
|
||||
|
||||
The ``Logic`` class is the main entry point for plugin management.
|
||||
|
||||
Initialization Process
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When you create a ``Logic`` instance (with ``lazy=False``, the default):
|
||||
|
||||
1. Loads MusicApps database (tagpool and properties files)
|
||||
2. Scans ``/Library/Audio/Plug-Ins/Components`` directory
|
||||
3. Parses each ``.component`` bundle's ``Info.plist``
|
||||
4. Creates ``AudioComponent`` instances
|
||||
5. Loads tagsets and categories for each plugin
|
||||
6. Indexes plugins for fast lookups
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
# Full initialization
|
||||
logic = Logic() # Discovers everything
|
||||
|
||||
# Lazy initialization (manual control)
|
||||
logic = Logic(lazy=True)
|
||||
logic.discover_plugins() # When ready to load plugins
|
||||
logic.discover_categories() # When ready to load categories
|
||||
|
||||
Logic Instance Attributes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Access discovered data
|
||||
logic.plugins # Plugins collection
|
||||
logic.components # Set of Component bundles
|
||||
logic.categories # Dict of category_name -> Category
|
||||
logic.musicapps # MusicApps database interface
|
||||
|
||||
# Configuration paths
|
||||
logic.components_path # Path to Components directory
|
||||
logic.tags_path # Path to tags database
|
||||
|
||||
Plugin Collection & Search
|
||||
---------------------------
|
||||
|
||||
The Plugins Class
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``Plugins`` class provides a searchable collection with multiple indexes:
|
||||
|
||||
- **By full name**: Exact manufacturer + plugin name match
|
||||
- **By manufacturer**: All plugins from a vendor
|
||||
- **By name**: Plugin name only
|
||||
- **By codes**: Type, subtype, manufacturer codes
|
||||
- **By category**: All plugins in a category
|
||||
- **By tags_id**: Unique component identifier
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Exact lookups (fast)
|
||||
plugin = logic.plugins.get_by_full_name("fabfilter: pro-q 3")
|
||||
plugin = logic.plugins.get_by_tags_id("61756678-65517033-46466")
|
||||
|
||||
# Set lookups
|
||||
fabfilter = logic.plugins.get_by_manufacturer("fabfilter")
|
||||
effects = logic.plugins.get_by_type_code("aufx")
|
||||
eq_plugins = logic.plugins.get_by_category("Effects:EQ")
|
||||
|
||||
Search Algorithm
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``search()`` method implements a sophisticated scoring system:
|
||||
|
||||
**Priority Levels** (highest to lowest):
|
||||
|
||||
1. **Tags ID exact match** (score: 1000)
|
||||
2. **Name substring match** (850-900)
|
||||
3. **Full name substring match** (750-800)
|
||||
4. **Fuzzy name match** (650-700, requires rapidfuzz)
|
||||
5. **Manufacturer match** (580-650)
|
||||
6. **Category match** (480-550)
|
||||
7. **Type code match** (380-450)
|
||||
8. **Subtype code match** (290-350)
|
||||
9. **Manufacturer code match** (190-250)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Fuzzy search with scoring
|
||||
results = logic.plugins.search(
|
||||
"serum",
|
||||
use_fuzzy=True,
|
||||
fuzzy_threshold=80,
|
||||
max_results=10
|
||||
)
|
||||
|
||||
for result in results:
|
||||
print(f"{result.plugin.full_name}")
|
||||
print(f" Score: {result.score}")
|
||||
print(f" Matched: {result.match_field}")
|
||||
|
||||
Categories & Tags
|
||||
-----------------
|
||||
|
||||
Hierarchical Structure
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Categories in Logic Pro use colon-separated hierarchies:
|
||||
|
||||
::
|
||||
|
||||
Effects
|
||||
Effects:Dynamics
|
||||
Effects:Dynamics:Compressor
|
||||
Effects:EQ
|
||||
Instruments
|
||||
Instruments:Synth
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Navigate hierarchy
|
||||
dynamics = logic.categories["Effects:Dynamics"]
|
||||
parent = dynamics.parent # "Effects" category
|
||||
child = parent.child("EQ") # "Effects:EQ" category
|
||||
|
||||
# Check properties
|
||||
print(dynamics.plugin_amount)
|
||||
print(dynamics.is_root)
|
||||
|
||||
Category Operations
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Create new category
|
||||
my_cat = logic.introduce_category("Studio Essentials")
|
||||
|
||||
# Manage plugins
|
||||
plugin = logic.plugins.get_by_name("Pro-Q 3")
|
||||
plugin.add_to_category(my_cat)
|
||||
plugin.remove_from_category(my_cat)
|
||||
plugin.move_to_category(my_cat) # Removes from all others
|
||||
|
||||
# Bulk operations
|
||||
plugins_set = {plugin1, plugin2, plugin3}
|
||||
logic.add_plugins_to_category(my_cat, plugins_set)
|
||||
|
||||
# Update plugin count
|
||||
logic.sync_category_plugin_amount(my_cat)
|
||||
|
||||
Category Sorting
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Categories have a sort order managed by the ``Properties`` database:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
category = logic.categories["Effects:EQ"]
|
||||
|
||||
# Check position
|
||||
print(category.index)
|
||||
print(category.is_first)
|
||||
print(category.is_last)
|
||||
prev, next = category.neighbors
|
||||
|
||||
# Reorder
|
||||
category.move_up(steps=2)
|
||||
category.move_down()
|
||||
category.move_to_top()
|
||||
category.move_to_bottom()
|
||||
category.move_before(other_category)
|
||||
category.move_after(other_category)
|
||||
category.swap(other_category)
|
||||
|
||||
Tagsets
|
||||
-------
|
||||
|
||||
What are Tagsets?
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Each ``AudioComponent`` has an associated ``.tagset`` file stored at:
|
||||
|
||||
``~/Music/Audio Music Apps/Databases/Tags/<tags_id>.tagset``
|
||||
|
||||
These XML plist files store:
|
||||
|
||||
- **nickname**: Custom display name
|
||||
- **shortname**: Abbreviated name for UI
|
||||
- **tags**: Dictionary of category assignments
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
logic = Logic()
|
||||
plugin = logic.plugins.get_by_name("Pro-Q 3")
|
||||
|
||||
# Access tagset
|
||||
print(plugin.tagset.nickname)
|
||||
print(plugin.tagset.shortname)
|
||||
print(plugin.tagset.tags) # {"Effects:EQ": "user"}
|
||||
|
||||
# Modify tagset
|
||||
plugin.set_nickname("My Favorite EQ")
|
||||
plugin.set_shortname("PQ3")
|
||||
|
||||
Tag Values
|
||||
~~~~~~~~~~
|
||||
|
||||
Category tags typically have the value ``"user"`` indicating user assignment, but can have other values from Logic Pro's internal management.
|
||||
|
||||
MusicApps Database
|
||||
------------------
|
||||
|
||||
Database Files
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Logic Pro stores category information in two files:
|
||||
|
||||
**MusicApps.tagpool**
|
||||
Maps category names to plugin counts:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{
|
||||
"Effects": 245,
|
||||
"Effects:EQ": 18,
|
||||
"Instruments": 89,
|
||||
...
|
||||
}
|
||||
|
||||
**MusicApps.properties**
|
||||
Stores category sort order and preferences:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{
|
||||
"sorting": ["Effects", "Effects:Dynamics", ...],
|
||||
"user_sorted": "property" # or absent for alphabetical
|
||||
}
|
||||
|
||||
Accessing the Database
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
logic = Logic()
|
||||
musicapps = logic.musicapps
|
||||
|
||||
# Access tagpool
|
||||
print(musicapps.tagpool.categories)
|
||||
|
||||
# Access properties
|
||||
print(musicapps.properties.sorting)
|
||||
print(musicapps.properties.user_sorted)
|
||||
|
||||
# Modify database
|
||||
musicapps.introduce_category("New Category")
|
||||
musicapps.remove_category("Old Category")
|
||||
|
||||
Thread Safety
|
||||
-------------
|
||||
|
||||
Logic Plugin Manager is **not thread-safe**. The library performs file I/O operations on Logic Pro's database files without locking mechanisms.
|
||||
|
||||
**Recommendations:**
|
||||
|
||||
- Use a single ``Logic`` instance per process
|
||||
- Avoid concurrent writes to categories or tagsets
|
||||
- Reload data after external changes (e.g., Logic Pro modifying categories)
|
||||
|
||||
Performance Considerations
|
||||
--------------------------
|
||||
|
||||
Initial Discovery
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Loading all plugins can take several seconds depending on the number of installed components. Use ``lazy=True`` for faster startup when you don't need immediate access.
|
||||
|
||||
Indexing
|
||||
~~~~~~~~
|
||||
|
||||
The ``Plugins`` collection maintains multiple indexes. Reindexing is required if you:
|
||||
|
||||
- Modify plugin properties externally
|
||||
- Add plugins after initialization
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
logic = Logic(lazy=True)
|
||||
logic.discover_plugins()
|
||||
# ... modify plugins externally ...
|
||||
logic.plugins.reindex_all()
|
||||
|
||||
Search Performance
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Exact lookups by indexes are O(1)
|
||||
- Fuzzy search with ``rapidfuzz`` is O(n) but optimized
|
||||
- Use ``max_results`` to limit computation for large result sets
|
||||
|
||||
Best Practices
|
||||
--------------
|
||||
|
||||
1. **Single Instance**: Create one ``Logic`` instance and reuse it
|
||||
2. **Error Handling**: Wrap operations in try-except blocks for specific exceptions
|
||||
3. **Lazy Loading**: Use for CLI tools or services that don't need immediate access
|
||||
4. **Reloading**: Call ``load()`` methods after external modifications to databases
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import (
|
||||
Logic,
|
||||
CategoryValidationError,
|
||||
PluginLoadError
|
||||
)
|
||||
|
||||
try:
|
||||
logic = Logic()
|
||||
except PluginLoadError as e:
|
||||
print(f"Failed to load plugins: {e}")
|
||||
# Handle gracefully
|
||||
|
||||
# Batch operation example
|
||||
favorites = {
|
||||
logic.plugins.get_by_name(name)
|
||||
for name in ["Pro-Q 3", "Serum", "Valhalla VintageVerb"]
|
||||
if logic.plugins.get_by_name(name)
|
||||
}
|
||||
|
||||
if favorites:
|
||||
fav_category = logic.introduce_category("Favorites")
|
||||
logic.add_plugins_to_category(fav_category, favorites)
|
||||
logic.sync_category_plugin_amount(fav_category)
|
||||
712
docs/examples.rst
Normal file
712
docs/examples.rst
Normal file
@@ -0,0 +1,712 @@
|
||||
Usage Examples
|
||||
==============
|
||||
|
||||
This page provides practical examples for common use cases.
|
||||
|
||||
Plugin Discovery & Inspection
|
||||
------------------------------
|
||||
|
||||
List All Plugins
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
print(f"Total plugins: {len(logic.plugins.all())}")
|
||||
|
||||
for plugin in logic.plugins.all():
|
||||
print(f"{plugin.full_name}")
|
||||
print(f" Type: {plugin.type_name.display_name}")
|
||||
print(f" Version: {plugin.version}")
|
||||
print(f" Categories: {', '.join(c.name for c in plugin.categories)}")
|
||||
print()
|
||||
|
||||
Filter by Type
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Get all instruments
|
||||
instruments = logic.plugins.get_by_type_code("aumu")
|
||||
print(f"Found {len(instruments)} instruments")
|
||||
|
||||
# Get all effects
|
||||
effects = logic.plugins.get_by_type_code("aufx")
|
||||
print(f"Found {len(effects)} effects")
|
||||
|
||||
Find Plugins by Manufacturer
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# List all manufacturers
|
||||
manufacturers = set()
|
||||
for plugin in logic.plugins.all():
|
||||
manufacturers.add(plugin.manufacturer)
|
||||
|
||||
for mfr in sorted(manufacturers):
|
||||
plugins = logic.plugins.get_by_manufacturer(mfr)
|
||||
print(f"{mfr}: {len(plugins)} plugins")
|
||||
|
||||
# Get specific manufacturer's plugins
|
||||
fabfilter = logic.plugins.get_by_manufacturer("fabfilter")
|
||||
for plugin in fabfilter:
|
||||
print(f" - {plugin.name}")
|
||||
|
||||
Inspect Plugin Details
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
plugin = logic.plugins.get_by_full_name("fabfilter: pro-q 3")
|
||||
|
||||
if plugin:
|
||||
print(f"Full Name: {plugin.full_name}")
|
||||
print(f"Manufacturer: {plugin.manufacturer}")
|
||||
print(f"Name: {plugin.name}")
|
||||
print(f"Description: {plugin.description}")
|
||||
print(f"Type: {plugin.type_name.display_name} ({plugin.type_code})")
|
||||
print(f"Subtype: {plugin.subtype_code}")
|
||||
print(f"Manufacturer Code: {plugin.manufacturer_code}")
|
||||
print(f"Version: {plugin.version}")
|
||||
print(f"Factory Function: {plugin.factory_function}")
|
||||
print(f"Tags ID: {plugin.tags_id}")
|
||||
print(f"Tagset Path: {plugin.tagset.path}")
|
||||
|
||||
if plugin.tagset.nickname:
|
||||
print(f"Nickname: {plugin.tagset.nickname}")
|
||||
if plugin.tagset.shortname:
|
||||
print(f"Short Name: {plugin.tagset.shortname}")
|
||||
|
||||
print(f"Categories: {', '.join(c.name for c in plugin.categories)}")
|
||||
|
||||
Search & Discovery
|
||||
------------------
|
||||
|
||||
Simple Text Search
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Simple substring search
|
||||
reverb_plugins = logic.plugins.search_simple("reverb")
|
||||
|
||||
print(f"Found {len(reverb_plugins)} plugins with 'reverb' in name")
|
||||
for plugin in reverb_plugins:
|
||||
print(f" - {plugin.full_name}")
|
||||
|
||||
Advanced Fuzzy Search
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Fuzzy search with scoring
|
||||
results = logic.plugins.search(
|
||||
query="compressor",
|
||||
use_fuzzy=True,
|
||||
fuzzy_threshold=80,
|
||||
max_results=10
|
||||
)
|
||||
|
||||
for i, result in enumerate(results, 1):
|
||||
print(f"{i}. {result.plugin.full_name}")
|
||||
print(f" Score: {result.score:.1f}")
|
||||
print(f" Matched field: {result.match_field}")
|
||||
print()
|
||||
|
||||
Search by Multiple Criteria
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
def find_plugins(search_term, plugin_type=None, category=None):
|
||||
"""Search plugins with optional filters."""
|
||||
results = logic.plugins.search(search_term, use_fuzzy=True)
|
||||
|
||||
plugins = [r.plugin for r in results]
|
||||
|
||||
# Filter by type if specified
|
||||
if plugin_type:
|
||||
plugins = [p for p in plugins if p.type_code == plugin_type]
|
||||
|
||||
# Filter by category if specified
|
||||
if category:
|
||||
plugins = [
|
||||
p for p in plugins
|
||||
if any(c.name == category for c in p.categories)
|
||||
]
|
||||
|
||||
return plugins
|
||||
|
||||
# Find reverb effects (not instruments)
|
||||
reverbs = find_plugins("reverb", plugin_type="aufx")
|
||||
|
||||
# Find synthesizers in Instruments category
|
||||
synths = find_plugins("synth", plugin_type="aumu", category="Instruments")
|
||||
|
||||
Category Management
|
||||
-------------------
|
||||
|
||||
List All Categories
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
print("Categories:")
|
||||
for name, category in sorted(logic.categories.items()):
|
||||
print(f" {name} ({category.plugin_amount} plugins)")
|
||||
|
||||
Create Category Hierarchy
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Create nested categories
|
||||
categories_to_create = [
|
||||
"My Plugins",
|
||||
"My Plugins:Favorites",
|
||||
"My Plugins:Favorites:Mixing",
|
||||
"My Plugins:Favorites:Mastering",
|
||||
]
|
||||
|
||||
for cat_name in categories_to_create:
|
||||
if cat_name not in logic.categories:
|
||||
category = logic.introduce_category(cat_name)
|
||||
print(f"Created: {cat_name}")
|
||||
else:
|
||||
print(f"Already exists: {cat_name}")
|
||||
|
||||
Add Plugins to Category
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Create or get category
|
||||
favorites = logic.categories.get("Favorites")
|
||||
if not favorites:
|
||||
favorites = logic.introduce_category("Favorites")
|
||||
|
||||
# Add single plugin
|
||||
plugin = logic.plugins.get_by_full_name("fabfilter: pro-q 3")
|
||||
if plugin:
|
||||
plugin.add_to_category(favorites)
|
||||
print(f"Added {plugin.full_name} to {favorites.name}")
|
||||
|
||||
# Update category count
|
||||
logic.sync_category_plugin_amount(favorites)
|
||||
|
||||
Bulk Category Assignment
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Find all FabFilter plugins
|
||||
fabfilter_plugins = logic.plugins.get_by_manufacturer("fabfilter")
|
||||
|
||||
# Create category
|
||||
fabfilter_cat = logic.categories.get("FabFilter")
|
||||
if not fabfilter_cat:
|
||||
fabfilter_cat = logic.introduce_category("FabFilter")
|
||||
|
||||
# Bulk add
|
||||
logic.add_plugins_to_category(fabfilter_cat, fabfilter_plugins)
|
||||
print(f"Added {len(fabfilter_plugins)} FabFilter plugins")
|
||||
|
||||
Move Plugins Between Categories
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Get plugins from one category
|
||||
dynamics_plugins = logic.plugins.get_by_category("Effects:Dynamics")
|
||||
|
||||
# Filter for compressors
|
||||
compressors = {
|
||||
p for p in dynamics_plugins
|
||||
if "compress" in p.name.lower()
|
||||
}
|
||||
|
||||
# Move to specific category
|
||||
comp_category = logic.categories.get("Effects:Dynamics:Compressor")
|
||||
if comp_category:
|
||||
logic.move_plugins_to_category(comp_category, compressors)
|
||||
logic.sync_category_plugin_amount(comp_category)
|
||||
|
||||
Organize Uncategorized Plugins
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Get plugins without categories
|
||||
uncategorized = logic.plugins.get_by_category(None)
|
||||
|
||||
print(f"Found {len(uncategorized)} uncategorized plugins")
|
||||
|
||||
# Auto-categorize by manufacturer
|
||||
for plugin in uncategorized:
|
||||
manufacturer = plugin.manufacturer.strip()
|
||||
category_name = f"By Manufacturer:{manufacturer}"
|
||||
|
||||
# Create category if needed
|
||||
if category_name not in logic.categories:
|
||||
logic.introduce_category(category_name)
|
||||
|
||||
category = logic.categories[category_name]
|
||||
plugin.add_to_category(category)
|
||||
|
||||
# Update all category counts
|
||||
logic.sync_all_categories_plugin_amount()
|
||||
|
||||
Category Sorting
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Get category
|
||||
category = logic.categories["Effects:EQ"]
|
||||
|
||||
# Check current position
|
||||
print(f"Current index: {category.index}")
|
||||
prev, next_cat = category.neighbors
|
||||
if prev:
|
||||
print(f"Previous: {prev.name}")
|
||||
if next_cat:
|
||||
print(f"Next: {next_cat.name}")
|
||||
|
||||
# Move category
|
||||
category.move_to_top()
|
||||
category.move_up(steps=5)
|
||||
category.move_down()
|
||||
|
||||
# Move relative to another category
|
||||
target = logic.categories["Effects:Delay"]
|
||||
category.move_before(target)
|
||||
|
||||
Custom Plugin Metadata
|
||||
----------------------
|
||||
|
||||
Set Nicknames
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Set custom nicknames for easier identification
|
||||
plugins_to_rename = {
|
||||
"fabfilter: pro-q 3": "PQ3 - Main EQ",
|
||||
"fabfilter: pro-c 2": "PC2 - Main Compressor",
|
||||
"valhalla shimmer": "Shimmer - Ambient Verb",
|
||||
}
|
||||
|
||||
for full_name, nickname in plugins_to_rename.items():
|
||||
plugin = logic.plugins.get_by_full_name(full_name)
|
||||
if plugin:
|
||||
plugin.set_nickname(nickname)
|
||||
print(f"Renamed: {full_name} -> {nickname}")
|
||||
|
||||
Set Short Names
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Set short names for UI display
|
||||
shortnames = {
|
||||
"fabfilter: pro-q 3": "PQ3",
|
||||
"fabfilter: pro-c 2": "PC2",
|
||||
"serum": "SRM",
|
||||
}
|
||||
|
||||
for full_name, shortname in shortnames.items():
|
||||
plugin = logic.plugins.get_by_full_name(full_name)
|
||||
if plugin:
|
||||
plugin.set_shortname(shortname)
|
||||
|
||||
Batch Metadata Updates
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Add manufacturer prefix to all plugin nicknames
|
||||
for plugin in logic.plugins.all():
|
||||
if not plugin.tagset.nickname:
|
||||
manufacturer = plugin.manufacturer.upper()
|
||||
nickname = f"[{manufacturer}] {plugin.name}"
|
||||
plugin.set_nickname(nickname)
|
||||
print(f"Set nickname for {plugin.full_name}")
|
||||
|
||||
Advanced Operations
|
||||
-------------------
|
||||
|
||||
Export Plugin Inventory
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import csv
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Export to CSV
|
||||
with open("plugin_inventory.csv", "w", newline="") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"Full Name", "Manufacturer", "Type", "Version",
|
||||
"Categories", "Subtype Code", "Tags ID"
|
||||
])
|
||||
|
||||
for plugin in sorted(logic.plugins.all(), key=lambda p: p.full_name):
|
||||
writer.writerow([
|
||||
plugin.full_name,
|
||||
plugin.manufacturer,
|
||||
plugin.type_name.display_name,
|
||||
plugin.version,
|
||||
"; ".join(c.name for c in plugin.categories),
|
||||
plugin.subtype_code,
|
||||
plugin.tags_id,
|
||||
])
|
||||
|
||||
print("Exported plugin inventory to plugin_inventory.csv")
|
||||
|
||||
Clone Category Structure
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
def clone_category(source: str, target: str, logic: Logic):
|
||||
"""Clone plugins from source category to target category."""
|
||||
# Get plugins in source
|
||||
source_plugins = logic.plugins.get_by_category(source)
|
||||
|
||||
# Create target if needed
|
||||
if target not in logic.categories:
|
||||
logic.introduce_category(target)
|
||||
|
||||
target_category = logic.categories[target]
|
||||
|
||||
# Add plugins to target
|
||||
logic.add_plugins_to_category(target_category, source_plugins)
|
||||
|
||||
print(f"Cloned {len(source_plugins)} plugins from {source} to {target}")
|
||||
|
||||
logic = Logic()
|
||||
clone_category("Effects:EQ", "My Plugins:EQ", logic)
|
||||
|
||||
Find Duplicate Plugins
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from collections import defaultdict
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Group by name (ignoring manufacturer)
|
||||
by_name = defaultdict(list)
|
||||
for plugin in logic.plugins.all():
|
||||
by_name[plugin.name.lower()].append(plugin)
|
||||
|
||||
# Find duplicates
|
||||
duplicates = {
|
||||
name: plugins
|
||||
for name, plugins in by_name.items()
|
||||
if len(plugins) > 1
|
||||
}
|
||||
|
||||
print(f"Found {len(duplicates)} plugin names with multiple versions:\n")
|
||||
for name, plugins in sorted(duplicates.items()):
|
||||
print(f"{name}:")
|
||||
for plugin in plugins:
|
||||
print(f" - {plugin.full_name} (v{plugin.version})")
|
||||
print()
|
||||
|
||||
Backup and Restore Categories
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
def backup_categories(output_file: Path):
|
||||
"""Backup all plugin category assignments."""
|
||||
logic = Logic()
|
||||
|
||||
backup_data = {}
|
||||
for plugin in logic.plugins.all():
|
||||
backup_data[plugin.tags_id] = {
|
||||
"full_name": plugin.full_name,
|
||||
"categories": [c.name for c in plugin.categories],
|
||||
}
|
||||
|
||||
with open(output_file, "w") as f:
|
||||
json.dump(backup_data, f, indent=2)
|
||||
|
||||
print(f"Backed up {len(backup_data)} plugin assignments")
|
||||
|
||||
def restore_categories(backup_file: Path):
|
||||
"""Restore plugin category assignments from backup."""
|
||||
logic = Logic()
|
||||
|
||||
with open(backup_file) as f:
|
||||
backup_data = json.load(f)
|
||||
|
||||
restored = 0
|
||||
for tags_id, data in backup_data.items():
|
||||
plugin = logic.plugins.get_by_tags_id(tags_id)
|
||||
if plugin:
|
||||
# Restore categories
|
||||
categories = [
|
||||
logic.categories[name]
|
||||
for name in data["categories"]
|
||||
if name in logic.categories
|
||||
]
|
||||
if categories:
|
||||
plugin.set_categories(categories)
|
||||
restored += 1
|
||||
|
||||
print(f"Restored {restored} plugin assignments")
|
||||
|
||||
# Usage
|
||||
backup_categories(Path("categories_backup.json"))
|
||||
# restore_categories(Path("categories_backup.json"))
|
||||
|
||||
Generate Category Report
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
print("=" * 60)
|
||||
print("CATEGORY REPORT")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Sort categories by hierarchy
|
||||
sorted_categories = sorted(logic.categories.items())
|
||||
|
||||
for name, category in sorted_categories:
|
||||
# Calculate depth based on colons
|
||||
depth = name.count(":")
|
||||
indent = " " * depth
|
||||
|
||||
# Get plugins in this exact category
|
||||
plugins = logic.plugins.get_by_category(name)
|
||||
|
||||
print(f"{indent}├─ {name.split(':')[-1]}")
|
||||
print(f"{indent}│ Count: {category.plugin_amount}")
|
||||
print(f"{indent}│ Index: {category.index}")
|
||||
|
||||
if plugins:
|
||||
print(f"{indent}│ Plugins:")
|
||||
for plugin in sorted(plugins, key=lambda p: p.full_name)[:5]:
|
||||
print(f"{indent}│ - {plugin.full_name}")
|
||||
if len(plugins) > 5:
|
||||
print(f"{indent}│ ... and {len(plugins) - 5} more")
|
||||
print()
|
||||
|
||||
Working with Component Bundles
|
||||
-------------------------------
|
||||
|
||||
Inspect Component Bundles
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
print(f"Total component bundles: {len(logic.components)}\n")
|
||||
|
||||
for component in logic.components:
|
||||
print(f"Bundle: {component.name}")
|
||||
print(f" ID: {component.bundle_id}")
|
||||
print(f" Version: {component.version} ({component.short_version})")
|
||||
print(f" Audio Components: {len(component.audio_components)}")
|
||||
|
||||
for audio_comp in component.audio_components:
|
||||
print(f" - {audio_comp.full_name}")
|
||||
print()
|
||||
|
||||
Find Multi-Plugin Bundles
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Find bundles with multiple plugins
|
||||
multi_plugin_bundles = [
|
||||
comp for comp in logic.components
|
||||
if len(comp.audio_components) > 1
|
||||
]
|
||||
|
||||
print(f"Found {len(multi_plugin_bundles)} bundles with multiple plugins:\n")
|
||||
|
||||
for component in multi_plugin_bundles:
|
||||
print(f"{component.name} - {len(component.audio_components)} plugins:")
|
||||
for plugin in component.audio_components:
|
||||
print(f" - {plugin.name} ({plugin.type_name.display_name})")
|
||||
print()
|
||||
|
||||
Error Handling Examples
|
||||
-----------------------
|
||||
|
||||
Robust Plugin Search
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic, PluginLoadError
|
||||
|
||||
def safe_find_plugin(plugin_name: str) -> None:
|
||||
"""Safely search for a plugin with error handling."""
|
||||
try:
|
||||
logic = Logic()
|
||||
except PluginLoadError as e:
|
||||
print(f"Error loading plugins: {e}")
|
||||
return
|
||||
|
||||
plugin = logic.plugins.get_by_full_name(plugin_name)
|
||||
|
||||
if plugin:
|
||||
print(f"Found: {plugin.full_name}")
|
||||
print(f"Type: {plugin.type_name.display_name}")
|
||||
|
||||
# Try to load tagset
|
||||
try:
|
||||
print(f"Categories: {', '.join(c.name for c in plugin.categories)}")
|
||||
except Exception as e:
|
||||
print(f"Could not load categories: {e}")
|
||||
else:
|
||||
print(f"Plugin '{plugin_name}' not found")
|
||||
|
||||
# Try fuzzy search
|
||||
results = logic.plugins.search(plugin_name)
|
||||
if results:
|
||||
print(f"\nDid you mean:")
|
||||
for result in results[:3]:
|
||||
print(f" - {result.plugin.full_name}")
|
||||
|
||||
safe_find_plugin("fabfilter: pro-q 3")
|
||||
|
||||
Graceful Category Operations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import (
|
||||
Logic,
|
||||
CategoryValidationError,
|
||||
MusicAppsLoadError
|
||||
)
|
||||
|
||||
def safe_add_to_category(plugin_name: str, category_name: str):
|
||||
"""Add plugin to category with error handling."""
|
||||
try:
|
||||
logic = Logic()
|
||||
except MusicAppsLoadError as e:
|
||||
print(f"Database error: {e}")
|
||||
return
|
||||
|
||||
# Find plugin
|
||||
plugin = logic.plugins.get_by_full_name(plugin_name)
|
||||
if not plugin:
|
||||
print(f"Plugin '{plugin_name}' not found")
|
||||
return
|
||||
|
||||
# Get or create category
|
||||
try:
|
||||
category = logic.categories[category_name]
|
||||
except KeyError:
|
||||
try:
|
||||
category = logic.introduce_category(category_name)
|
||||
print(f"Created new category: {category_name}")
|
||||
except Exception as e:
|
||||
print(f"Could not create category: {e}")
|
||||
return
|
||||
|
||||
# Add to category
|
||||
try:
|
||||
plugin.add_to_category(category)
|
||||
logic.sync_category_plugin_amount(category)
|
||||
print(f"Added {plugin.full_name} to {category_name}")
|
||||
except Exception as e:
|
||||
print(f"Error adding to category: {e}")
|
||||
|
||||
safe_add_to_category("fabfilter: pro-q 3", "My Favorites")
|
||||
240
docs/getting_started.rst
Normal file
240
docs/getting_started.rst
Normal file
@@ -0,0 +1,240 @@
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
This guide will help you get started with Logic Plugin Manager, a Python library for programmatically managing Logic Pro's audio plugins.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Basic Installation
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Install the package using pip or uv:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install logic-plugin-manager
|
||||
|
||||
Or with uv:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
uv add logic-plugin-manager
|
||||
|
||||
With Search Functionality
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For fuzzy search capabilities, install with the ``search`` extra:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install logic-plugin-manager[search]
|
||||
# or
|
||||
uv add logic-plugin-manager[search]
|
||||
|
||||
This includes the ``rapidfuzz`` dependency for advanced plugin searching.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
- **Python**: 3.13 or higher
|
||||
- **Operating System**: macOS only (Logic Pro specific)
|
||||
- **Logic Pro**: Installed with audio plugins
|
||||
|
||||
The library accesses:
|
||||
|
||||
- Audio Components directory: ``/Library/Audio/Plug-Ins/Components``
|
||||
- Tags database: ``~/Music/Audio Music Apps/Databases/Tags``
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
Basic Usage
|
||||
~~~~~~~~~~~
|
||||
|
||||
The simplest way to start is by creating a ``Logic`` instance:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
# Initialize and discover all plugins
|
||||
logic = Logic()
|
||||
|
||||
# Access all plugins
|
||||
for plugin in logic.plugins.all():
|
||||
print(f"{plugin.full_name} - {plugin.type_name.display_name}")
|
||||
|
||||
# Access categories
|
||||
for category_name, category in logic.categories.items():
|
||||
print(f"{category_name}: {category.plugin_amount} plugins")
|
||||
|
||||
Lazy Loading
|
||||
~~~~~~~~~~~~
|
||||
|
||||
For faster initialization when you don't need immediate access to all plugins:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
# Initialize without loading plugins
|
||||
logic = Logic(lazy=True)
|
||||
|
||||
# Manually discover plugins when needed
|
||||
logic.discover_plugins()
|
||||
logic.discover_categories()
|
||||
|
||||
Searching Plugins
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Search for plugins by name, manufacturer, or category:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Simple substring search
|
||||
results = logic.plugins.search_simple("reverb")
|
||||
|
||||
# Advanced fuzzy search with scoring
|
||||
results = logic.plugins.search("serum", use_fuzzy=True)
|
||||
for result in results[:5]: # Top 5 results
|
||||
print(f"{result.plugin.full_name} (score: {result.score})")
|
||||
|
||||
Working with Categories
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Organize plugins into categories:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Get plugins in a specific category
|
||||
effects = logic.plugins.get_by_category("Effects")
|
||||
|
||||
# Get or create a category
|
||||
my_category = logic.categories.get("My Favorites")
|
||||
if not my_category:
|
||||
my_category = logic.introduce_category("My Favorites")
|
||||
|
||||
# Add plugin to category
|
||||
plugin = logic.plugins.get_by_full_name("fabfilter: pro-q 3")
|
||||
if plugin:
|
||||
plugin.add_to_category(my_category)
|
||||
|
||||
Custom Paths
|
||||
~~~~~~~~~~~~
|
||||
|
||||
If your Logic Pro or components are in non-standard locations:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pathlib import Path
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic(
|
||||
components_path=Path("/custom/path/to/Components"),
|
||||
tags_path=Path("~/custom/path/to/Tags").expanduser()
|
||||
)
|
||||
|
||||
Next Steps
|
||||
----------
|
||||
|
||||
- Learn about :doc:`core_concepts` to understand the library's architecture
|
||||
- Explore :doc:`examples` for common use cases
|
||||
- Check the :doc:`logic_plugin_manager` for detailed API reference
|
||||
|
||||
Common Patterns
|
||||
---------------
|
||||
|
||||
Finding a Specific Plugin
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# By full name (exact match)
|
||||
plugin = logic.plugins.get_by_full_name("apple: logic eq")
|
||||
|
||||
# By manufacturer
|
||||
fabfilter_plugins = logic.plugins.get_by_manufacturer("fabfilter")
|
||||
|
||||
# By audio unit type
|
||||
instruments = logic.plugins.get_by_type_code("aumu")
|
||||
|
||||
Batch Category Operations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
|
||||
# Get all synthesizer plugins
|
||||
synths = logic.plugins.search("synth", use_fuzzy=True)
|
||||
synth_plugins = {result.plugin for result in synths[:20]}
|
||||
|
||||
# Move them to a custom category
|
||||
synth_category = logic.introduce_category("Synthesizers")
|
||||
logic.move_plugins_to_category(synth_category, synth_plugins)
|
||||
|
||||
Working with Plugin Metadata
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
logic = Logic()
|
||||
plugin = logic.plugins.get_by_name("Pro-Q 3")
|
||||
|
||||
if plugin:
|
||||
# Access metadata
|
||||
print(f"Manufacturer: {plugin.manufacturer}")
|
||||
print(f"Type: {plugin.type_name.display_name}")
|
||||
print(f"Version: {plugin.version}")
|
||||
print(f"Categories: {[c.name for c in plugin.categories]}")
|
||||
|
||||
# Set custom nickname
|
||||
plugin.set_nickname("My Favorite EQ")
|
||||
|
||||
# Set short name for UI display
|
||||
plugin.set_shortname("PQ3")
|
||||
|
||||
Error Handling
|
||||
--------------
|
||||
|
||||
The library raises specific exceptions for different error conditions:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import (
|
||||
Logic,
|
||||
PluginLoadError,
|
||||
MusicAppsLoadError,
|
||||
CategoryValidationError
|
||||
)
|
||||
|
||||
try:
|
||||
logic = Logic()
|
||||
except MusicAppsLoadError as e:
|
||||
print(f"Could not load Logic's database: {e}")
|
||||
except PluginLoadError as e:
|
||||
print(f"Error loading plugins: {e}")
|
||||
|
||||
try:
|
||||
category = logic.categories["Nonexistent"]
|
||||
except KeyError:
|
||||
print("Category not found")
|
||||
|
||||
See :doc:`logic_plugin_manager` for all exception types.
|
||||
91
docs/index.rst
Normal file
91
docs/index.rst
Normal file
@@ -0,0 +1,91 @@
|
||||
Logic Plugin Manager
|
||||
====================
|
||||
|
||||
**Programmatic management of Logic Pro audio plugins**
|
||||
|
||||
Logic Plugin Manager is a Python library for discovering, organizing, and managing macOS Audio Unit plugins used by Logic Pro. It provides programmatic access to Logic's internal tag database, enabling automated plugin organization, bulk categorization, and advanced search capabilities.
|
||||
|
||||
----
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- **Plugin Discovery**: Automatically scan and index all installed Audio Unit plugins
|
||||
- **Category Management**: Create, modify, and organize plugin categories programmatically
|
||||
- **Advanced Search**: Fuzzy search with scoring across multiple attributes
|
||||
- **Bulk Operations**: Efficiently manage large plugin collections
|
||||
- **Metadata Control**: Set custom nicknames, short names, and categories
|
||||
- **Type-Safe**: Fully typed Python API with comprehensive documentation
|
||||
|
||||
Quick Example
|
||||
-------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from logic_plugin_manager import Logic
|
||||
|
||||
# Initialize and discover all plugins
|
||||
logic = Logic()
|
||||
|
||||
# Search for plugins
|
||||
results = logic.plugins.search("reverb", use_fuzzy=True)
|
||||
for result in results[:5]:
|
||||
print(f"{result.plugin.full_name} (score: {result.score})")
|
||||
|
||||
# Organize into categories
|
||||
favorites = logic.introduce_category("Favorites")
|
||||
plugin = logic.plugins.get_by_full_name("fabfilter: pro-q 3")
|
||||
plugin.add_to_category(favorites)
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install logic-plugin-manager
|
||||
|
||||
# With search functionality
|
||||
pip install logic-plugin-manager[search]
|
||||
|
||||
**Requirements**: Python 3.13+, macOS, Logic Pro
|
||||
|
||||
----
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: User Guide
|
||||
|
||||
getting_started
|
||||
core_concepts
|
||||
examples
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: API Reference
|
||||
|
||||
logic_plugin_manager
|
||||
|
||||
----
|
||||
|
||||
Indices
|
||||
=======
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
----
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
This project is dual-licensed:
|
||||
|
||||
- **Open Source (AGPL-3.0)**: Free for open source projects
|
||||
- **Commercial License**: Available for closed-source/commercial use
|
||||
|
||||
Contact: h@kotikot.com
|
||||
|
||||
20
docs/logic_plugin_manager.components.rst
Normal file
20
docs/logic_plugin_manager.components.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
Components
|
||||
==========
|
||||
|
||||
The components package provides classes for working with macOS Audio Unit components and bundles.
|
||||
|
||||
AudioComponent
|
||||
--------------
|
||||
|
||||
.. automodule:: logic_plugin_manager.components.audiocomponent
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
Component
|
||||
---------
|
||||
|
||||
.. automodule:: logic_plugin_manager.components.component
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
20
docs/logic_plugin_manager.logic.rst
Normal file
20
docs/logic_plugin_manager.logic.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
Logic Management
|
||||
================
|
||||
|
||||
The logic package provides the main interface for plugin management and search functionality.
|
||||
|
||||
Logic Class
|
||||
-----------
|
||||
|
||||
.. automodule:: logic_plugin_manager.logic.logic
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
Plugins Collection
|
||||
------------------
|
||||
|
||||
.. automodule:: logic_plugin_manager.logic.plugins
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
28
docs/logic_plugin_manager.rst
Normal file
28
docs/logic_plugin_manager.rst
Normal file
@@ -0,0 +1,28 @@
|
||||
API Reference
|
||||
=============
|
||||
|
||||
Core Modules
|
||||
------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
logic_plugin_manager.components
|
||||
logic_plugin_manager.logic
|
||||
logic_plugin_manager.tags
|
||||
|
||||
Defaults
|
||||
--------
|
||||
|
||||
.. automodule:: logic_plugin_manager.defaults
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
.. automodule:: logic_plugin_manager.exceptions
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
28
docs/logic_plugin_manager.tags.rst
Normal file
28
docs/logic_plugin_manager.tags.rst
Normal file
@@ -0,0 +1,28 @@
|
||||
Tags & Categories
|
||||
=================
|
||||
|
||||
The tags package manages Logic Pro's category system and tag database.
|
||||
|
||||
Category Management
|
||||
-------------------
|
||||
|
||||
.. automodule:: logic_plugin_manager.tags.category
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
MusicApps Database
|
||||
------------------
|
||||
|
||||
.. automodule:: logic_plugin_manager.tags.musicapps
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
Tagset Files
|
||||
------------
|
||||
|
||||
.. automodule:: logic_plugin_manager.tags.tagset
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
3
docs/requirements.txt
Normal file
3
docs/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
sphinx
|
||||
furo
|
||||
sphinx-autodoc-typehints
|
||||
Reference in New Issue
Block a user