# pyhandbrake - Python library to interface with HandBrakeCLI
pyhandbrake is a small wrapper around `HandBrakeCLI` which I wrote when
developing a project. Main features include:
* Fully typed models representing handbrake output
* Ability to load, modify and save presets
* Ability to scan and rip titles
* Live progress callbacks for supported commands
## Installation
You will need a working copy of HandBrakeCLI as it is not included in this
python package. For instructions on multiple OSes, see the [installation
guide](https://handbrake.fr/docs/en/1.2.0/get-handbrake/download-and-install.html).
For ubuntu based distributions, it can be obtained with `sudo apt-get install
handbrake-cli`.
### From PyPI
pyhandbrake is available on PyPI, simply run `pip install pyhandbrake`
### From the git repo
You can install python projects using pip directly from the git repo, in this instance with
`pip install git+https://github.com/dominicprice/pyhandbrake`
### From source
1. Clone the repo with `git clone https://github.com/dominicprice/pyhandbrake`
2. `cd` into the `pyhandbrake` directory
3. Run `pip install .`
## Usage
Although the package is named `pyhandbrake`, the actual name of the package you
should import is `handbrake` (a slightly confusing but common convention with
python packages). The package exposes one main object, `HandBrake`, which
contains methods for interacting the handbrake cli:
* `HandBrake.version(...)`
* `HandBrake.convert_title(...)`
* `HandBrake.scan_title(...)`
* `HandBrake.scan_all_titles(...)`
* `HandBrake.get_preset(...)`
* `HandBrake.list_presets(...)`
* `HandBrake.load_preset_from_file(...)`
Information about these function can be found in the docstrings on the methods,
found in [src/handbrake/__init__.py](src/handbrake/__init__.py) and information about the return types can
be found in the [src/handbrake/models](src/handbrake/models) directory.
### Passing arguments to `convert_title`
Rather than accept all possible command line arguments, `convert_title` only allows
you to set most options through a preset. Therefore you need to find out which
preset setting corresponds to the given command line argument and set that value
in a `Preset` object and add the preset to the search path by using it in the
`presets` parameter.
Note that you should name the preset something unique, and then tell handbrake
to use that preset by also passing the name to the `preset` argument, for
example:
```
from handbrake import HandBrake
h = HandBrake()
# get the default CLI preset, but could also use
# h.get_preset("<builtin preset name>") to get any
# of the inbuilt presets, or h.load_preset("<path to preset>")
# to load a custom preset
preset = h.get_preset("CLI Default")
# a preset contains layers which are held in the `preset_list` field. The default
# presets all contain one layer so we only do our work on `preset_list[0]`
preset.preset_list[0]["PictureWidth"] = 100
preset.preset_list[0]["PictureHeight"] = 50
preset.preset_list[0]["PresetName"] = "my_preset"
h.convert_title("/path/to/input", "/path/to/output", "main", preset="my_preset", presets=[preset])
```
### Handling progress updates
Methods related to reading titles accept a `ProgressHandler` argument. This
should be a function which accepts a `Progress` model, and it is called every
time handbrake emits a progress message allowing you to display information
about the progress of the command, e.g.
```
from handbrake import HandBrake
from handbrake.models import Progress
def progress_handler(p: Progress):
print(p.task_description, f"{int(p.percent)}%")
h = HandBrake()
h.rip_title("/path/to/input", "/path/to/output", "main", progress_handler=progress_handler)
```
## Developing
pyhandbrake uses poetry as a toolchain. You should install poetry (via e.g.
`pipx install poetry`) and then inside the project root run `poetry install` to
create a virtual environment with all the dependencies.
Common project tasks are defined inside the Makefile:
* `make lint`: Run the mypy type checker
* `make format`: Run the code formatter
* `make test`: Run the test suite
* `make sdist`: Build the source distribution
* `make wheel`: Build a python wheel
## Troubleshooting
I do not really intend on actively maintaining this project unless I have to use
it again, so future versions of handbrake may introduce breaking changes. If you
notice this then pull requests would be very welcome! These notes are for future
me as much as they are for any other interested readers.
I anticipate that the most likely thing to cause errors/unexpected output is if
fields are added/removed/renamed from the JSON objects which handbrake outputs.
For added fields, pyhandbrake should silently ignore them - to include them you
will need to modify the relevant model in the [models](src/handbrake/models)
subpackage to include the field. Note that handbrake uses pascal case for field
names, but I have used snake case for the model field names. Pydantic
automatically takes care of converting between these naming conventions, but
will trip over capitalised abbreviations, (e.g. a field named `SequenceID`). In
this case, you need to assign a `Field` value to the field with the full name
handbrake uses as an alias parameter (e.g. `sequence_id: str =
Field(alias="SequenceID")`).
For fields which are removed/renamed, a pydantic validation error will be
emitted as the value would be required to populate the model. In this instance,
you should assign the value to a `Field` instance with a default value and the
deprecated flag (e.g. `old_field_name: str = Field("", deprecated=True)`). Note:
if the field is a mutable type then you will need to use a `default_factory`
instead of a value (e.g. `old_field_name: list[str] =
Field(default_factory=lambda: [], deprecated=True)`). If the field has been
renamed you can then create a new field with the new name as described in the
paragraph above.
A more pernicious problem would be if the output of the handbrake program
changes format. In this case the processing of the command output would need to
be rewritten, you are welcome to do so but the logic should still be able to
handle older versions of handbrake by i.e. checking the version and delegating
to an appropriate function.
Raw data
{
"_id": null,
"home_page": null,
"name": "pyhandbrake",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.10",
"maintainer_email": null,
"keywords": "handbrake, dvd, bluray, multimedia, convert, video, audio",
"author": "Dominic Price",
"author_email": "dominicprice@outlook.com",
"download_url": "https://files.pythonhosted.org/packages/12/94/d54cb87a009861c4823e70863fe7794f9ce52cadd5b7a49b8d09c5f58042/pyhandbrake-1.2.0.tar.gz",
"platform": null,
"description": "# pyhandbrake - Python library to interface with HandBrakeCLI\n\npyhandbrake is a small wrapper around `HandBrakeCLI` which I wrote when\ndeveloping a project. Main features include:\n\n* Fully typed models representing handbrake output\n* Ability to load, modify and save presets\n* Ability to scan and rip titles\n* Live progress callbacks for supported commands\n\n## Installation\n\nYou will need a working copy of HandBrakeCLI as it is not included in this\npython package. For instructions on multiple OSes, see the [installation\nguide](https://handbrake.fr/docs/en/1.2.0/get-handbrake/download-and-install.html).\nFor ubuntu based distributions, it can be obtained with `sudo apt-get install\nhandbrake-cli`.\n\n### From PyPI\n\npyhandbrake is available on PyPI, simply run `pip install pyhandbrake`\n\n### From the git repo\n\nYou can install python projects using pip directly from the git repo, in this instance with\n`pip install git+https://github.com/dominicprice/pyhandbrake`\n\n### From source\n\n1. Clone the repo with `git clone https://github.com/dominicprice/pyhandbrake`\n2. `cd` into the `pyhandbrake` directory\n3. Run `pip install .`\n\n## Usage\n\nAlthough the package is named `pyhandbrake`, the actual name of the package you\nshould import is `handbrake` (a slightly confusing but common convention with\npython packages). The package exposes one main object, `HandBrake`, which\ncontains methods for interacting the handbrake cli:\n\n* `HandBrake.version(...)`\n* `HandBrake.convert_title(...)`\n* `HandBrake.scan_title(...)`\n* `HandBrake.scan_all_titles(...)`\n* `HandBrake.get_preset(...)`\n* `HandBrake.list_presets(...)`\n* `HandBrake.load_preset_from_file(...)`\n\nInformation about these function can be found in the docstrings on the methods,\nfound in [src/handbrake/__init__.py](src/handbrake/__init__.py) and information about the return types can\nbe found in the [src/handbrake/models](src/handbrake/models) directory.\n\n### Passing arguments to `convert_title`\n\nRather than accept all possible command line arguments, `convert_title` only allows\nyou to set most options through a preset. Therefore you need to find out which\npreset setting corresponds to the given command line argument and set that value\nin a `Preset` object and add the preset to the search path by using it in the\n`presets` parameter.\n\nNote that you should name the preset something unique, and then tell handbrake\nto use that preset by also passing the name to the `preset` argument, for\nexample:\n\n```\nfrom handbrake import HandBrake\n\nh = HandBrake()\n# get the default CLI preset, but could also use\n# h.get_preset(\"<builtin preset name>\") to get any\n# of the inbuilt presets, or h.load_preset(\"<path to preset>\")\n# to load a custom preset\npreset = h.get_preset(\"CLI Default\")\n\n# a preset contains layers which are held in the `preset_list` field. The default\n# presets all contain one layer so we only do our work on `preset_list[0]`\npreset.preset_list[0][\"PictureWidth\"] = 100\npreset.preset_list[0][\"PictureHeight\"] = 50\npreset.preset_list[0][\"PresetName\"] = \"my_preset\"\n\nh.convert_title(\"/path/to/input\", \"/path/to/output\", \"main\", preset=\"my_preset\", presets=[preset])\n```\n\n### Handling progress updates\n\nMethods related to reading titles accept a `ProgressHandler` argument. This\nshould be a function which accepts a `Progress` model, and it is called every\ntime handbrake emits a progress message allowing you to display information\nabout the progress of the command, e.g.\n\n```\nfrom handbrake import HandBrake\nfrom handbrake.models import Progress\n\ndef progress_handler(p: Progress):\n print(p.task_description, f\"{int(p.percent)}%\")\n\nh = HandBrake()\nh.rip_title(\"/path/to/input\", \"/path/to/output\", \"main\", progress_handler=progress_handler)\n```\n\n\n## Developing\n\npyhandbrake uses poetry as a toolchain. You should install poetry (via e.g.\n`pipx install poetry`) and then inside the project root run `poetry install` to\ncreate a virtual environment with all the dependencies.\n\nCommon project tasks are defined inside the Makefile:\n\n* `make lint`: Run the mypy type checker\n* `make format`: Run the code formatter\n* `make test`: Run the test suite\n* `make sdist`: Build the source distribution\n* `make wheel`: Build a python wheel\n\n## Troubleshooting\n\nI do not really intend on actively maintaining this project unless I have to use\nit again, so future versions of handbrake may introduce breaking changes. If you\nnotice this then pull requests would be very welcome! These notes are for future\nme as much as they are for any other interested readers.\n\nI anticipate that the most likely thing to cause errors/unexpected output is if\nfields are added/removed/renamed from the JSON objects which handbrake outputs.\n\nFor added fields, pyhandbrake should silently ignore them - to include them you\nwill need to modify the relevant model in the [models](src/handbrake/models)\nsubpackage to include the field. Note that handbrake uses pascal case for field\nnames, but I have used snake case for the model field names. Pydantic\nautomatically takes care of converting between these naming conventions, but\nwill trip over capitalised abbreviations, (e.g. a field named `SequenceID`). In\nthis case, you need to assign a `Field` value to the field with the full name\nhandbrake uses as an alias parameter (e.g. `sequence_id: str =\nField(alias=\"SequenceID\")`).\n\nFor fields which are removed/renamed, a pydantic validation error will be\nemitted as the value would be required to populate the model. In this instance,\nyou should assign the value to a `Field` instance with a default value and the\ndeprecated flag (e.g. `old_field_name: str = Field(\"\", deprecated=True)`). Note:\nif the field is a mutable type then you will need to use a `default_factory`\ninstead of a value (e.g. `old_field_name: list[str] =\nField(default_factory=lambda: [], deprecated=True)`). If the field has been\nrenamed you can then create a new field with the new name as described in the\nparagraph above.\n\nA more pernicious problem would be if the output of the handbrake program\nchanges format. In this case the processing of the command output would need to\nbe rewritten, you are welcome to do so but the logic should still be able to\nhandle older versions of handbrake by i.e. checking the version and delegating\nto an appropriate function.\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python library to interface with HandBrakeCLI",
"version": "1.2.0",
"project_urls": {
"Bug Tracker": "https://github.com/dominicprice/pyhandbrake/issues",
"Documentation": "https://github.com/dominicprice/pyhandbrake",
"Homepage": "https://github.com/dominicprice/pyhandbrake"
},
"split_keywords": [
"handbrake",
" dvd",
" bluray",
" multimedia",
" convert",
" video",
" audio"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f8ac87853b59addb0d0301d17dbf4533087f6c7ff64c434688fbb1906c2145c3",
"md5": "a08a7701ee2798e8ab2aa7e481a5b17f",
"sha256": "966fd399907d8ed4c7d3bd08a22f25e6a79b7cd15b3b6d7e36aa7d0817c5e61a"
},
"downloads": -1,
"filename": "pyhandbrake-1.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a08a7701ee2798e8ab2aa7e481a5b17f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.10",
"size": 12057,
"upload_time": "2024-11-08T00:09:13",
"upload_time_iso_8601": "2024-11-08T00:09:13.186180Z",
"url": "https://files.pythonhosted.org/packages/f8/ac/87853b59addb0d0301d17dbf4533087f6c7ff64c434688fbb1906c2145c3/pyhandbrake-1.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "1294d54cb87a009861c4823e70863fe7794f9ce52cadd5b7a49b8d09c5f58042",
"md5": "fd9221c7a341a335b8ee22b3a48927bc",
"sha256": "fbd157bdb5dadee207faf2ab93175532edf0c35632fc2283c380c9d19242e539"
},
"downloads": -1,
"filename": "pyhandbrake-1.2.0.tar.gz",
"has_sig": false,
"md5_digest": "fd9221c7a341a335b8ee22b3a48927bc",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.10",
"size": 12073,
"upload_time": "2024-11-08T00:09:14",
"upload_time_iso_8601": "2024-11-08T00:09:14.992660Z",
"url": "https://files.pythonhosted.org/packages/12/94/d54cb87a009861c4823e70863fe7794f9ce52cadd5b7a49b8d09c5f58042/pyhandbrake-1.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-08 00:09:14",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "dominicprice",
"github_project": "pyhandbrake",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "pyhandbrake"
}