Compare commits

...
87 Commits
Author SHA1 Message Date
James R. Barlow 173ce2f215 v10.3.3 release notes 2020-08-07 02:23:21 -07:00
James R. Barlow 9b641055e1 Fix KeyError: 'dpi' when using --threshold on image to PDF
Fixes #607
2020-08-07 02:21:02 -07:00
James R. Barlow 4fa28d7e74 v10.3.2 release notes 2020-08-05 01:36:56 -07:00
James R. Barlow bed74501fc Fix test breakage in validation
Broken in commit 4cc0dc
2020-08-05 01:35:26 -07:00
James R. Barlow e821ca46d5 Approve pdfminer.six 20200726 2020-08-03 16:04:16 -07:00
James R. Barlow a29e4952fb Document use of mmap 2020-08-03 16:03:54 -07:00
James R. Barlow 4cc0dc6b4a Additional size increase reasons 2020-08-03 16:03:29 -07:00
James R. Barlow 7263702de9 Remove gs.py (spoofers entirely removed) and update copyright 2020-07-29 16:31:47 -07:00
James R. Barlow 0287d91874 v10.3.1 release notes 2020-07-26 21:53:08 -07:00
James R. Barlow 436af55050 Approve pdfminer.six 20200720 2020-07-26 21:51:49 -07:00
James R. Barlow d6128e6937 Fix support for older versions of pdfminer.six (boxes_flow error) 2020-07-26 21:51:25 -07:00
James R. Barlow 642437e804 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2020-07-22 00:34:33 -07:00
James R. Barlow 4ce802fdb2 v10.3.0 release notes 2020-07-22 00:34:27 -07:00
James R. Barlow a672422b0b Enable pikepdf mmap in other contexts 2020-07-22 00:20:07 -07:00
James R. Barlow addc2cbad0 Enable pikepdf mmap and set up signal handlers 2020-07-22 00:19:50 -07:00
James R. Barlow 5f45f77b4e docs: plugins update 2020-07-21 23:53:30 -07:00
James R. Barlow 93f9bffb37 Merge branch 'feature/leptonica-179' 2020-07-20 21:23:53 -07:00
James R. Barlow 44149ad319 Disable test_error_trap for Leptonica < 1.79
Old error trap seems unreliable in the first place so difficult to set up
a test.
2020-07-20 21:12:00 -07:00
fcatusandGitHub d80d963cea pdfinfo: Replace list comp with gen expr'n 2020-07-20 02:21:58 -07:00
James R. Barlow 4da33b8050 Update debian/copyright from Debian, with fixes 2020-07-19 03:50:43 -07:00
James R. Barlow 5cbbff8472 For Leptonica 1.79+ use leptSetStderrHandler
Lock free and considerably less dangerous to stderr messages.
2020-07-19 03:40:33 -07:00
James R. Barlow fa6e47c277 Merge branch 'feature/optimize-cleanup' 2020-07-19 01:53:11 -07:00
James R. Barlow 4ea9cffebd Add locking to Leptonica error trap
To protect another thread from interfering with our redirection of
stderr.
2020-07-19 01:51:58 -07:00
James R. Barlow ae68edefc5 pipelines: fix Python 3.7/3.8 on macOS 2020-07-19 01:51:58 -07:00
James R. Barlow 1558e068f1 docs: explain firstresult hook behavior 2020-07-16 00:01:59 -07:00
James R. Barlow d2a9c413f8 docs: install notes for ARM64 2020-07-14 01:25:16 -07:00
James R. Barlow a510b21b20 optimize: add typing for Xref, remove fspath()'s 2020-07-09 14:06:41 -07:00
James R. Barlow e33ba07aa4 Update pre-commit settings 2020-07-08 23:45:53 -07:00
James R. Barlow 373f27832b optimize: improve typing of xref_exts 2020-07-07 22:41:29 -07:00
James R. Barlow b20a6e4c5d optimize: add type hints 2020-07-07 22:18:50 -07:00
James R. Barlow 49734d5456 optimize: fix incorrect to prevent re-optimizing JBIG2s 2020-07-07 21:52:11 -07:00
James R. Barlow 26a415c5dd docs: Note usage of OCR_JSON_SETTINGS for watcher 2020-07-07 21:26:57 -07:00
James R. Barlow 60be64a5f1 Fix debug.log missing pageno handler 2020-07-04 03:59:38 -07:00
James R. Barlow 190294634c docs: edit plugins 2020-07-03 16:16:01 -07:00
James R. Barlow 1722cb579d v10.2.1 release notes 2020-07-01 03:26:57 -07:00
James R. Barlow dc42beb6a8 More typing improvements
Typing fixes bugs.
2020-06-30 15:02:30 -07:00
James R. Barlow 378f543619 TextPositionTracker: set boxes_flow=None
We don't care about the order of lines in our analysis, and this is an
expensive calculation in pdfminer.
2020-06-30 04:20:58 -07:00
James R. Barlow 62924ee280 Improve API documentation 2020-06-30 04:20:14 -07:00
James R. Barlow 86a73191b0 Plugin manager: accept Path(plugin) 2020-06-30 04:17:30 -07:00
James R. Barlow 86875997b8 Fix more mypy errors 2020-06-29 02:17:14 -07:00
James R. Barlow b939584c7a quality: fixing typing issues 2020-06-29 01:45:45 -07:00
James R. Barlow bbd174071d readme: markdown cleanup 2020-06-29 01:45:27 -07:00
James R. Barlow e5b6fe1317 pyproject.toml: weird line wrapping? 2020-06-29 01:45:12 -07:00
James R. Barlow f15d9049eb install: add Mageia
Closes #586. Thanks to @yannick56
2020-06-26 23:28:26 -07:00
James R. Barlow 7630c93e5b install: drop Ubuntu 14.04 steps
Bit rot must have set in.
2020-06-26 23:27:42 -07:00
James R. Barlow 638d68aa8a docs: move Windows ahead of FreeBSD 2020-06-26 22:49:34 -07:00
James R. Barlow a92dde058a docs: promote one liner installs, reorg Windows 2020-06-26 22:47:44 -07:00
James R. Barlow 580f2ebb4b Python 3.9beta is now known to work (Fedora) 2020-06-26 00:06:58 -07:00
James R. Barlow 01cae7a584 docs: Update Fedora versions 2020-06-23 02:08:24 -07:00
James R. Barlow 66337813e6 Spell runslow correctly 2020-06-22 23:32:09 -07:00
James R. Barlow eb5a211e72 New hocrtransform test isn't platform stable - mark runslow 2020-06-22 16:59:59 -07:00
James R. Barlow 5142933120 v10.2.0 release notes
Closes #582, #584, #545
2020-06-22 16:37:51 -07:00
James R. Barlow 06ab114aa8 Update test cache 2020-06-22 16:31:34 -07:00
James R. Barlow 1257419465 test_hocrtransform: this test is worth not caching 2020-06-22 16:31:06 -07:00
James R. Barlow 30404f53f0 Add test to sanity check our pdf renderers 2020-06-22 16:18:38 -07:00
James R. Barlow 1ce8edbdfe hocrtransform: some text not included in output after Tesseract changes 2020-06-22 15:48:23 -07:00
James R. Barlow d4b704a0ae hocrtransform: refactor colors 2020-06-22 15:22:48 -07:00
James R. Barlow 2d64e1536d hocrtransform: refactor xpath manipulations 2020-06-22 14:44:34 -07:00
James R. Barlow c8b581ac31 hoctransform: remove deprecated element.getchildren()
Breaks Python 3.9.
2020-06-22 14:28:18 -07:00
James R. Barlow ad8dead7df Document that API accepts streams now 2020-06-22 14:27:27 -07:00
James R. Barlow c9bd87254e A few minor typing issues 2020-06-22 02:31:53 -07:00
James R. BarlowandJames R. Barlow f4cb424451 Support input/output streams at API level 2020-06-22 02:02:18 -07:00
James R. Barlow fef14778d5 Fix missing f-string in log message 2020-06-22 01:17:16 -07:00
James R. Barlow 86ec63f215 Decouple plugin manager forking from PdfContext/Pagecontext 2020-06-22 01:16:59 -07:00
James R. Barlow 5b10ec9d39 jobcontext.PdfContext: remove dead code, add annotations 2020-06-22 00:34:58 -07:00
James R. Barlow 800c75c4e5 Bump requirements (mainly for Docker's benefit) 2020-06-21 01:58:53 -07:00
James R. Barlow 24d64b04c3 Update Docker to Ubuntu 20.04 and jbig2-latest 2020-06-21 01:48:31 -07:00
James R. Barlow 48e2750551 Fix some tests that were failing in Docker 2020-06-21 01:48:13 -07:00
James R. Barlow e182c5f63e Update and sync .dockerignore, .gitignore
Also blacklist .* and whitelist the ones we want.
2020-06-21 01:25:59 -07:00
James R. Barlow 06d52326db Fix deleted path in .coveragerc 2020-06-21 01:24:23 -07:00
James R. Barlow ebfe4f0d29 Fix issue #582 - PDF/A acquires title "Untitled" after conversion 2020-06-20 02:01:16 -07:00
James R. Barlow ad22977c84 v10.1.1 release notes 2020-06-17 14:45:32 -07:00
James R. Barlow 6ac50646f0 Fix OMP_THREAD_LIMIT rounded down to 0 in some cases 2020-06-17 14:43:19 -07:00
James R. Barlow 24b6a4ad50 v10.1.0 notes 2020-06-16 00:55:28 -07:00
James R. Barlow e802896d4d unpaper: use PNG input where possible
Unpaper accepts PNG as input now, so avoid generating a huge
temporary PPM file if we can. If we
must create a PNG, compress it lightly to keep our temp usage down.
2020-06-16 00:50:18 -07:00
James R. Barlow 0b5a20e593 coverage: ignore type checking 2020-06-15 15:55:39 -07:00
James R. Barlow 642998ead6 sync: refactor preprocess image filtering 2020-06-15 15:26:41 -07:00
James R. Barlow 698aab4f75 Add a lot of type annotations 2020-06-15 15:20:50 -07:00
James R. Barlow 34231ac667 sync: refactor intermediate image production 2020-06-15 15:02:28 -07:00
James R. Barlow ddedf7cd2e For --clean-final, use same image as --clean if possible 2020-06-15 13:48:49 -07:00
James R. Barlow 9d127d354c docs: improve description of plugins 2020-06-15 12:51:49 -07:00
James R. Barlow 2d2a4894ab Some corrections to release notes 2020-06-15 12:51:28 -07:00
James R. Barlow 862861e3ca Fix error message in logging from repeated filtering
If logging somehow triggers PageNumberFilter multiple times, it would fail on the second occurrence.
2020-06-13 14:50:58 -07:00
James R. Barlow 892db88f0e test_two_languages: use narrower test 2020-06-12 14:33:02 -07:00
James R. Barlow eeb44f78cc Fix tests that failed on other platforms from previous fix 2020-06-12 12:59:46 -07:00
James R. Barlow 863835f660 v10.0.1 release notes 2020-06-12 12:11:21 -07:00
James R. Barlow 393c5a9ea4 Fix error on -l lang1+lang2 2020-06-12 12:10:29 -07:00
178 changed files with 7061 additions and 5690 deletions
+1 -2
View File
@@ -11,8 +11,6 @@ concurrency =
multiprocessing
source =
src/ocrmypdf
omit =
tests/spoof/*
[report]
exclude_lines =
@@ -23,3 +21,4 @@ exclude_lines =
if 0:
if False:
if __name__ == .__main__.:
if TYPE_CHECKING:
+2 -2
View File
@@ -1,6 +1,6 @@
# OCRmyPDF
#
FROM ubuntu:19.10 as base
FROM ubuntu:20.04 as base
FROM base as builder
@@ -24,7 +24,7 @@ RUN \
# Needs libleptonica-dev, zlib1g-dev
RUN \
mkdir jbig2 \
&& curl -L https://github.com/agl/jbig2enc/archive/0.29.tar.gz | \
&& curl -L https://github.com/agl/jbig2enc/archive/ea6a40a.tar.gz | \
tar xz -C jbig2 --strip-components=1 \
&& cd jbig2 \
&& ./autogen.sh && ./configure && make && make install \
+38 -19
View File
@@ -1,25 +1,44 @@
# dotfiles
.*
!.coveragerc
!.dockerignore
!.git_archival.txt
!.gitattributes
!.gitignore
!.pre-commit-config.yaml
!.readthedocs.yml
# Dev scratch
*.ipynb
*.pdf
*.pyc
*.rst
*.sublime*
**/*.pyc
.*/
!.git/
.ruffus_history.sqlite
bin/
build/
docs/
dist/
htmlcov/
include/
lib/
MANIFEST.in
ocrmypdf.egg-info/
staging/
tests/cache/
tests/output/
/*.pdf
/*.qdf
/*.png
/scratch.py
IDEAS
log/
tests/resources/private/
tmp/
venv*/
/debug_tests.py
*.traineddata
/private
# Package building
*.egg-info/
build/
dist/
wheelhouse/
pip-wheel-metadata/
# Code coverage
htmlcov/
# Docker specific
bin/
docs/
include/
lib/
# Docker include .git/
!.git/
+28 -33
View File
@@ -1,47 +1,42 @@
# Development environment
.bash_history
.pylintrc
.pytest_cache/
.ruffus_history.sqlite
.venv*/
*.pyc
*.sublime-*
*.DS_Store
.mypy_cache/
# dotfiles
.*
!.coveragerc
!.dockerignore
!.git_archival.txt
!.gitattributes
!.gitignore
!.pre-commit-config.yaml
!.readthedocs.yml
# Dev scratch
*.ipynb
**/*.pyc
/*.pdf
/*.qdf
/*.png
/scratch.py
IDEAS
log/
tests/resources/private/
tmp/
venv*/
/debug_tests.py
*.traineddata
/private
# Package building
.eggs/
*.egg-info/
build/
dist/
wheelhouse/
pip-wheel-metadata/
# Code coverage
htmlcov/
# Automatically generated files
docs/_build/
docs/_static/
docs/_templates/
docs/Makefile
ocrmypdf/lib/_*.py
# Code coverage
.coverage*
htmlcov/
# Testing
.ipynb_checkpoints/
.vscode/
*.ipynb
*.profile
/*.pdf
/*.qdf
/*.png
/scratch.py
IDEAS
log/
tests/output/
tests/resources/private/
tmp/
/debug_tests.py
*.traineddata
/private
+5 -5
View File
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
rev: v3.1.0
hooks:
- id: check-case-conflict
- id: check-merge-conflict
@@ -8,16 +8,16 @@ repos:
- id: check-yaml
- id: debug-statements
- repo: https://github.com/asottile/seed-isort-config
rev: v1.9.3
rev: v2.2.0
hooks:
- id: seed-isort-config
- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.21 # pick the isort version you'd like to use from https://github.com/pre-commit/mirrors-isort/releases
rev: v5.0.5 # pick the isort version you'd like to use from https://github.com/pre-commit/mirrors-isort/releases
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: stable
rev: 19.10b0
hooks:
- id: black
language_version: python3.7
language_version: python3.8
exclude: ^src/ocrmypdf/lib/_leptonica.py
+11 -25
View File
@@ -3,15 +3,10 @@
[![Build Status][azure]](https://dev.azure.com/jim0585/ocrmypdf/_build/latest?definitionId=2&branchName=master) [![PyPI version][pypi]](https://pypi.org/project/ocrmypdf/) ![Homebrew version][homebrew] ![ReadTheDocs][docs] ![Python versions][pyversions]
[azure]: https://dev.azure.com/jim0585/ocrmypdf/_apis/build/status/jbarlow83.OCRmyPDF?branchName=master
[travis]: https://travis-ci.org/jbarlow83/OCRmyPDF.svg?branch=master "Travis build status"
[pypi]: https://img.shields.io/pypi/v/ocrmypdf.svg "PyPI version"
[homebrew]: https://img.shields.io/homebrew/v/ocrmypdf.svg "Homebrew version"
[docs]: https://readthedocs.org/projects/ocrmypdf/badge/?version=latest "RTD"
[pyversions]: https://img.shields.io/pypi/pyversions/ocrmypdf "Supported Python versions"
OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched or copy-pasted.
@@ -30,8 +25,7 @@ ocrmypdf # it's a scriptable command line program
[See the release notes for details on the latest changes](https://ocrmypdf.readthedocs.io/en/latest/release_notes.html).
Main features
-------------
## Main features
- Generates a searchable [PDF/A](https://en.wikipedia.org/?title=PDF/A) file from a regular PDF
- Places OCR text accurately below the image to ease copy / paste
@@ -47,8 +41,7 @@ Main features
For details: please consult the [documentation](https://ocrmypdf.readthedocs.io/en/latest/).
Motivation
----------
## Motivation
I searched the web for a free command line tool to OCR PDF files: I found many, but none of them were really satisfying:
@@ -62,8 +55,7 @@ I searched the web for a free command line tool to OCR PDF files: I found many,
...so I decided to develop my own tool.
Installation
------------
## Installation
Linux, Windows, macOS and FreeBSD are supported. Docker images are also available.
@@ -87,8 +79,7 @@ brew install ocrmypdf
For everyone else, [see our documentation](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for installation steps.
Languages
---------
## Languages
OCRmyPDF uses Tesseract for OCR, and relies on its language packs. For Linux users, you can often find packages that provide language packs:
@@ -105,8 +96,7 @@ pacman -S tesseract-data-eng tesseract-data-deu # Example: Install the English a
You can then pass the `-l LANG` argument to OCRmyPDF to give a hint as to what languages it should search for. Multiple languages can be requested.
Documentation and support
-------------------------
## Documentation and support
Once OCRmyPDF is installed, the built-in help which explains the command syntax and options can be accessed via:
@@ -118,27 +108,24 @@ Our [documentation is served on Read the Docs](https://ocrmypdf.readthedocs.io/e
Please report issues on our [GitHub issues](https://github.com/jbarlow83/OCRmyPDF/issues) page, and follow the issue template for quick response.
Requirements
------------
## Requirements
In addition to the required Python version (3.6+), OCRmyPDF requires external program installations of Ghostscript, Tesseract OCR, QPDF, and Leptonica. OCRmyPDF is pure Python, but uses CFFI to portably generate library bindings. OCRmyPDF works on pretty much everything: Linux, macOS, Windows and FreeBSD.
Press & Media
-------------
## Press & Media
- [Going paperless with OCRmyPDF](https://medium.com/@ikirichenko/going-paperless-with-ocrmypdf-e2f36143f46a)
- [Converting a scanned document into a compressed searchable PDF with redactions](https://medium.com/@treyharris/converting-a-scanned-document-into-a-compressed-searchable-pdf-with-redactions-63f61c34fe4c)
- [c't 1-2014, page 59](https://heise.de/-2279695): Detailed presentation of OCRmyPDF v1.0 in the leading German IT magazine c't
- [heise Open Source, 09/2014: Texterkennung mit OCRmyPDF](https://heise.de/-2356670)
- [heise Durchsuchbare PDF-Dokumente mit OCRmyPDF erstellen](https://www.heise.de/ratgeber/Durchsuchbare-PDF-Dokumente-mit-OCRmyPDF-erstellen-4607592.html)
- [Excellent Utilities: OCRmyPDF](https://www.linuxlinks.com/excellent-utilities-ocrmypdf-add-ocr-text-layer-scanned-pdfs/)
Business enquiries
------------------
## Business enquiries
OCRmyPDF would not be the software that it is today without companies and users choosing to provide support for feature development and consulting enquiries. We are happy to discuss all enquiries, whether for extending the existing feature set, or integrating OCRmyPDF into a larger system.
License
-------
## License
The OCRmyPDF software is licensed under the GNU GPLv3. Certain files are covered by other licenses, as noted in their source files.
@@ -146,7 +133,6 @@ The license for each test file varies, and is noted in tests/resources/README.rs
OCRmyPDF versions prior to 6.0 were distributed under the MIT License.
Disclaimer
----------
## Disclaimer
The software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+10 -4
View File
@@ -152,9 +152,9 @@ stages:
strategy:
matrix:
Python37:
python.version: "3.7"
# Python38:
# python.version: "3.8"
python.version: ""
Python38:
python.version: "python@3.8"
steps:
# https://github.com/actions/virtual-environments/issues/664
# - task: UsePythonVersion@0
@@ -163,6 +163,13 @@ stages:
- bash: |
brew update
brew unlink python@2
if [ "$(python.version)" != "" ]; then
brew upgrade $(python.version)
else
echo "Using Python `python3 --version`"
fi
displayName: "Update brew and Python"
- bash: |
brew install \
exempi \
ghostscript \
@@ -170,7 +177,6 @@ stages:
leptonica \
openjpeg \
pngquant \
python \
tesseract \
unpaper
displayName: "Install system packages"
+692 -4
View File
@@ -2,7 +2,6 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: OCRmyPDF
Upstream-Contact: James R. Barlow <barlow.jim@gmail.com>
Source: https://github.com/jbarlow83/OCRmyPDF
Files-Excluded: tests/resources/milk.pdf
Files: *
Copyright:
@@ -10,10 +9,24 @@ Copyright:
(C) 2013-2016, 2015-2017 2016, 2017, 2017-2018, 2018 James R. Barlow
License: GPL-3+
Files: misc/watcher.py
Copyright:
(C) 2019 Ian Alexander: https://github.com/ianalexander
(C) 2020 James R. Barlow
License: GPL-3+
Files: misc/webservice.py
Copyright: (C) 2019 James R. Barlow
License: AGPL-3+
Files: docs tests/resources/*
Copyright: (C) 2013-2018 James R. Barlow
License: CC-BY-SA-4.0
Files: docs/images/bitmap_vs_svg.svg
Copyright: (C) 2006 Yug
License: CC-BY-SA-2.5
Files: src/ocrmypdf/hocrtransform.py
Copyright: (C) 2010 Jonathan Brinley <jonathanbrinley@gmail.com>
(C) 2013-14 Julien Pfefferkorn
@@ -30,7 +43,7 @@ Copyright: (C) 2014 Armin Ronacher
(C) 2017 James R. Barlow
License: BSD-3-clause
Files: tests/spoof/*
Files: tests/plugins/*
Copyright: (C) 2016, 2017, 2016-2018 James R. Barlow
License: Expat
@@ -79,15 +92,20 @@ Copyright: held by the contributors to the Wikipedia article "Optical character
(epson.pdf generated from Wikipedia article as of 2016-09-14)
License: CC-BY-SA-3.0
Files: tests/resources/typewriter.png tests/resources/2400dpi.pdf tests/resources/3small.pdf
Files: tests/resources/typewriter.png tests/resources/2400dpi.pdf
Copyright: (C) 2005 Ellywa
License: GFDL-1.2+ or CC-BY-SA-1.0 or CC-BY-SA-2.0 or CC-BY-SA-2.5 or CC-BY-SA-3.0
Comment:
Obtained from: https://commons.wikimedia.org/wiki/File:Triumph.typewriter_text_Linzensoep.gif
Files: tests/resources/overlay.pdf
Copyright: (C) 2017 Max Anderson
License: Expat
Files: tests/resources/baiona*.png tests/resources/3small.pdf
Files:
tests/resources/baiona*.png
tests/resources/baiona*.jpg
tests/resources/link.pdf
Copyright: (C) 2014 Euskaldunaa
License: CC-BY-SA-4.0
@@ -95,6 +113,13 @@ Files: tests/resources/vector.pdf
Copyright: (C) 2018 Catscratch
License: Expat
Files: tests/resources/3small.pdf
Copyright: (C) 2014 Euskaldunaa
(C) 2017 James R. Barlow
(C) 2005 Ellywa
License: CC-BY-SA-4.0 and (GFDL-1.2+ or CC-BY-SA-1.0 or CC-BY-SA-2.0 or CC-BY-SA-2.5 or CC-BY-SA-3.0)
Comment: concatenation of baiona_gray.png, crom.png and typewriter.png/2400dpi.pdf
Files: src/ocrmypdf/data/sRGB.icc
Copyright: Kai-Uwe Behrmann <www.behrmann.name>
Marti Maria <www.littlecms.com>
@@ -124,6 +149,669 @@ License: GPL-3+
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
License: AGPL-3+
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
.
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
.
Preamble
.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
.
The precise terms and conditions for copying, distribution and
modification follow.
.
TERMS AND CONDITIONS
.
0. Definitions.
.
"This License" refers to version 3 of the GNU Affero General Public License.
.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
.
A "covered work" means either the unmodified Program or a work based
on the Program.
.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
.
1. Source Code.
.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
.
The Corresponding Source for a work in source code form is that
same work.
.
2. Basic Permissions.
.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
.
4. Conveying Verbatim Copies.
.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
.
5. Conveying Modified Source Versions.
.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
.
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
.
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
.
6. Conveying Non-Source Forms.
.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
.
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
.
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
.
7. Additional Terms.
.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
.
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
.
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
.
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
.
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
.
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
.
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
.
8. Termination.
.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
.
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
.
9. Acceptance Not Required for Having Copies.
.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
.
10. Automatic Licensing of Downstream Recipients.
.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
.
11. Patents.
.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
.
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
.
12. No Surrender of Others' Freedom.
.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
.
13. Remote Network Interaction; Use with the GNU General Public License.
.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
.
14. Revised Versions of this License.
.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
.
15. Disclaimer of Warranty.
.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
.
16. Limitation of Liability.
.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
.
17. Interpretation of Sections 15 and 16.
.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
.
END OF TERMS AND CONDITIONS
.
How to Apply These Terms to Your New Programs
.
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
.
Also add information on how to contact you by electronic and paper mail.
.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.
License: Expat
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
+4 -4
View File
@@ -51,6 +51,10 @@ Forking a child process to call ``ocrmypdf.ocr()`` is suggested. That
way your application will survive and remain interactive even if
OCRmyPDF does not.
Programs that call ``ocrmypdf.ocr()`` should also install a SIGBUS signal
handler (except on Windows), to raise an exception if access to a memory
mapped file fails. OCRmyPDF may use memory mapping.
.. warning::
On Windows, the script that calls ``ocrmypdf.ocr()`` must be protected
@@ -106,7 +110,3 @@ Reference
:undoc-members:
.. autofunction:: ocrmypdf.configure_logging
.. automodule:: ocrmypdf.exceptions
:members:
:undoc-members:
+43
View File
@@ -0,0 +1,43 @@
=============
API Reference
=============
This page summarizes the rest of the public API. Generally speaking this
should mainly of interest to plugin developers.
ocrmypdf.exceptions
===================
.. automodule:: ocrmypdf.exceptions
:members:
:undoc-members:
ocrmypdf.helpers
================
.. automodule:: ocrmypdf.helpers
:members:
ocrmypdf.hocrtransform
======================
.. automodule:: ocrmypdf.hocrtransform
:members:
ocrmypdf.pdfa
=============
.. automodule:: ocrmypdf.pdfa
:members:
ocrmypdf.quality
================
.. automodule:: ocrmypdf.quality
:members:
ocrmypdf.subprocess
===================
.. automodule:: ocrmypdf.subprocess
:members:
+1 -1
View File
@@ -127,7 +127,7 @@ Users may need to customize the script to meet their requirements.
"OCR_ON_SUCCESS_DELETE", "This will delete the input file if the exit code is 0 (OK)"
"OCR_OUTPUT_DIRECTORY_YEAR_MONTH", "This will place files in the output in ``{output}/{year}/{month}/{filename}``"
"OCR_DESKEW", "Apply deskew to crooked input PDFs"
"OCR_JSON_SETTINGS", "A JSON string specifying any other arguments for ``ocrmypdf.ocr``"
"OCR_JSON_SETTINGS", "A JSON string specifying any other arguments for ``ocrmypdf.ocr``, e.g. ``'OCR_JSON_SETTINGS={"rotate_pages": true}'``.
"OCR_POLL_NEW_FILE_SECONDS", "Polling interval"
"OCR_LOGLEVEL", "Level of log messages to report"
+7 -3
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# ocrmypdf documentation build configuration file, created by
# sphinx-quickstart on Sun Sep 4 14:29:43 2016.
@@ -21,6 +20,8 @@
# import sys
# sys.path.insert(0, os.path.abspath('.'))
"""isort:skip_file"""
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@@ -32,6 +33,8 @@
# ones.
extensions = ['sphinx.ext.napoleon']
napoleon_use_rtype = False
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -51,7 +54,7 @@ master_doc = 'index'
# General information about the project.
project = 'ocrmypdf'
copyright = (
'2019, James R. Barlow. Licensed under Creative Commons Attribution-ShareAlike 4.0.'
'2020, James R. Barlow. Licensed under Creative Commons Attribution-ShareAlike 4.0.'
)
author = 'James R. Barlow'
@@ -90,6 +93,7 @@ from pkg_resources import get_distribution, DistributionNotFound
release = get_distribution('ocrmypdf').version
version = '.'.join(release.split('.')[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
@@ -174,7 +178,7 @@ html_theme_options = {'display_version': False}
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#
# html_logo = None
# html_logo = "images/logo.svg" # looks bad
# The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+1
View File
@@ -36,6 +36,7 @@ image processing and OCR to existing PDFs.
api
plugins
apiref
contributing
Indices and tables
+110 -122
View File
@@ -8,20 +8,26 @@ Installing OCRmyPDF
|latest|
The easiest way to install OCRmyPDF is to follow the steps for your operating
system/platform, although sometimes this version may be out of date. This
installation guide provides information allowing you to compare the current
version to the one provided by your platform.
system/platform. This version may be out of date, however.
If you want to use the latest version of OCRmyPDF and all of its optional
dependencies, the easiest way to get that is install the Homebrew package. Homebrew
is best known as a macOS package manger, but also works for
`Linux and Windows Subsystem for Linux <https://docs.brew.sh/Homebrew-on-Linux>`__.
After Homebrew is installed, simply run ``brew install ocrmypdf``.
These platforms have one-liner installs:
You can also use the more detailed procedures here to manually install OCRmyPDF
from source or with the ``pip`` package manager for binary wheels. The reason
for these varied steps is that OCRmyPDF requires third-party executables that are
not part of Python.
+-----------------------------+-------------------------------+
| Debian, Ubuntu | ``apt install ocrmypdf`` |
+-----------------------------+-------------------------------+
| Windows Subsystem for Linux | ``apt install ocrmypdf`` |
+-----------------------------+-------------------------------+
| Fedora | ``dnf install ocrmypdf`` |
+-----------------------------+-------------------------------+
| macOS | ``brew install ocrmypdf`` |
+-----------------------------+-------------------------------+
| LinuxBrew | ``brew install ocrmypdf`` |
+-----------------------------+-------------------------------+
| FreeBSD | ``pkg install py37-ocrmypdf`` |
+-----------------------------+-------------------------------+
More detailed procedures are outlined below. If you want to do a manual
install, or install a more recent version than your platform provides, read on.
.. contents:: Platform-specific steps
:depth: 2
@@ -58,7 +64,7 @@ Debian and Ubuntu 18.04 or newer
| |ubu-1804| |ubu-2004| |
+-----------------------------------------------+
Users of Debian 9 ("stretch") or later or Ubuntu 18.04 or later, including users
Users of Debian 9 ("stretch") or later, or Ubuntu 18.04 or later, including users
of Windows Subsystem for Linux, may simply
.. code-block:: bash
@@ -87,11 +93,11 @@ For full details on version availability for your platform, check the
Fedora 29 or newer
------------------
.. |fedora-29| image:: https://repology.org/badge/version-for-repo/fedora_29/ocrmypdf.svg
:alt: Fedora 29
.. |fedora-31| image:: https://repology.org/badge/version-for-repo/fedora_31/ocrmypdf.svg
:alt: Fedora 31
.. |fedora-30| image:: https://repology.org/badge/version-for-repo/fedora_30/ocrmypdf.svg
:alt: Fedora 30
.. |fedora-32| image:: https://repology.org/badge/version-for-repo/fedora_32/ocrmypdf.svg
:alt: Fedora 32
.. |fedora-rawhide| image:: https://repology.org/badge/version-for-repo/fedora_rawhide/ocrmypdf.svg
:alt: Fedore Rawhide
@@ -101,7 +107,7 @@ Fedora 29 or newer
+-----------------------------------------------+
| |latest| |
+-----------------------------------------------+
| |fedora-29| |fedora-30| |fedora-rawhide| |
| |fedora-31| |fedora-32| |fedora-rawhide| |
+-----------------------------------------------+
Users of Fedora 29 or later may simply
@@ -162,7 +168,7 @@ To install for the current user only:
pip3 install --user ocrmypdf
Ubuntu 18.04 LTS
-------------------------------------------------
----------------
Ubuntu 18.04 includes ocrmypdf 6.1.2 - you can install that with ``apt``, but
it is quite old now. To install a more recent version, uninstall the old version
@@ -265,68 +271,6 @@ environment variable contains ``$HOME/.local/bin``.
To add JBIG2 encoding, see :ref:`jbig2`.
Ubuntu 14.04 LTS
----------------
Installing on Ubuntu 14.04 LTS (trusty) is more difficult than some
other options, because of its age. Several backports are required. For
explanations of some steps of this procedure, see the similar steps for
Ubuntu 16.04.
Install system dependencies:
.. code-block:: bash
sudo apt-get update
sudo apt-get install \
software-properties-common python-software-properties \
zlib1g-dev \
libexempi3 \
libjpeg-dev \
libffi-dev \
pngquant \
qpdf
We will need backports of Ghostscript 9.16, libav-11 (for unpaper 6.1),
Tesseract 4.00 (alpha), and Python 3.6. This will replace Ghostscript
and Tesseract 3.x on your system. Python 3.6 will be installed alongside
the system Python 3.4.
If you prefer to not modify your system in this matter, consider using a
Docker container.
.. code-block:: bash
sudo add-apt-repository ppa:vshn/ghostscript -y
sudo add-apt-repository ppa:heyarje/libav-11 -y
sudo add-apt-repository ppa:alex-p/tesseract-ocr -y
sudo add-apt-repository ppa:jonathonf/python-3.6 -y
sudo apt-get update
sudo apt-get install \
python3.6-dev \
ghostscript \
tesseract-ocr \
tesseract-ocr-eng \
libavformat56 libavcodec56 libavutil54 \
wget
Now we need to install ``pip`` and let it install ocrmypdf:
.. code-block:: bash
curl https://bootstrap.pypa.io/ez_setup.py -o - | python3.6 && python3.6 -m easy_install pip
pip3.6 install ocrmypdf
The optional dependency ``unpaper`` is only available at 0.4.2 in Ubuntu 14.04,
and no backports are available. Previously the author maintained a backported
.deb package for unpaper 6.1, but since Ubuntu 14.04 is now end of life, this is
not supported. As such, ``unpaper`` is not available on Ubuntu 14.04 or must by
compiled by hand.
To add JBIG2 encoding, see :ref:`jbig2`.
Arch Linux (AUR)
----------------
@@ -407,6 +351,43 @@ To install OCRmyPDF for Alpine Linux:
apk add ocrmypdf
Mageia 7
--------
Install the following dependencies:
.. code-block:: bash
# As root user
urpmi.update -a
urpmi \
ghostscript \
icc-profiles-openicc \
jbig2dec \
lib64leptonica5 \
pngquant \
python3-pip \
python3-cffi \
python3-distutils-extra \
python3-pkg-resources \
python3-reportlab \
qpdf \
tesseract \
tesseract-osd \
tesseract-eng \
tesseract-fra
To install ocrmypdf for the system:
# As root user
pip3 install ocrmypdf
ldconfig
Or, to install for the current user only:
export PATH=$HOME/.local/bin:$PATH
pip3 install --user ocrmypdf
Other Linux packages
--------------------
@@ -507,40 +488,15 @@ The command line program should now be available:
ocrmypdf --help
Installing on FreeBSD
=====================
.. image:: https://repology.org/badge/version-for-repo/freebsd/python:ocrmypdf.svg
:alt: FreeBSD
:target: https://repology.org/project/python:ocrmypdf/versions
FreeBSD 11.3, 12.0, 12.1-RELEASE and 13.0-CURRENT are supported. Other
versions likely work but have not been tested.
.. code-block:: bash
pkg install py37-ocrmypdf
To install a more recent version, you could attempt to first install the system
version with ``pkg``, then use ``pip install --user ocrmypdf``.
Installing the Docker image
===========================
For some users, installing the Docker image will be easier than
installing all of OCRmyPDF's dependencies.
See `OCRmyPDF Docker Image <docker>`__ for more information.
Installing on Windows
=====================
.. warning::
Native Windows
--------------
Native Windows support is new. Consider it "beta" software. Some
functionality is missing or may be more difficult to enable. If you need a
production-ready solution, use Windows Subsystem for Linux or a Docker
image.
.. note::
It is easier to install OCRmyPDF on Windows Subsystem for Linux.
.. note::
@@ -548,7 +504,7 @@ Installing on Windows
You must install the following for Windows:
* Python 3.7 (64-bit)
* Python 3.7 (64-bit) or later
* Tesseract 4.0 or later
* Ghostscript 9.50 or later
@@ -577,8 +533,8 @@ You may then use pip to install ocrmypdf:
* ``pip install ocrmypdf``
Installing on Windows Subsystem for Linux
=========================================
Windows Subsystem for Linux
---------------------------
#. Install Ubuntu 18.04 for Windows Subsystem for Linux, if not already installed.
#. Follow the procedure to install :ref:`OCRmyPDF on Ubuntu 18.04 <ubuntu-lts-latest>`.
@@ -597,14 +553,8 @@ Then confirm that the expected version from PyPI (|latest|) is installed:
You can then run OCRmyPDF in the Windows command prompt or Powershell, prefixing
``wsl``, and call it from Windows programs or batch files.
Docker
^^^^^^
You can also :ref:`Install the Docker <docker-install>` container on Windows. Ensure that
your command prompt can run the docker "hello world" container.
Installing on Cygwin64 under Windows
====================================
Cygwin64
--------
First install the the following prerequisite Cygwin packages using ``setup-x86_64.exe``::
@@ -650,6 +600,37 @@ The optional dependency "unpaper" that is currently not available under Cygwin.
Without it, certain options such as ``--clean`` will produce an error message.
However, the OCR-to-text-layer functionality is available.
Docker
------
You can also :ref:`Install the Docker <docker-install>` container on Windows. Ensure that
your command prompt can run the docker "hello world" container.
Installing on FreeBSD
=====================
.. image:: https://repology.org/badge/version-for-repo/freebsd/python:ocrmypdf.svg
:alt: FreeBSD
:target: https://repology.org/project/python:ocrmypdf/versions
FreeBSD 11.3, 12.0, 12.1-RELEASE and 13.0-CURRENT are supported. Other
versions likely work but have not been tested.
.. code-block:: bash
pkg install py37-ocrmypdf
To install a more recent version, you could attempt to first install the system
version with ``pkg``, then use ``pip install --user ocrmypdf``.
Installing the Docker image
===========================
For some users, installing the Docker image will be easier than
installing all of OCRmyPDF's dependencies.
See `OCRmyPDF Docker Image <docker>`__ for more information.
Installing with Python pip
==========================
@@ -684,6 +665,13 @@ instead use this for a system wide installation:
pip3 install ocrmypdf
.. note::
AArch64 (ARM64) users: this process will be difficult because most
Python packages are not available as binary wheels for your platform.
You're probably better off using a platform install on Debian, Ubuntu,
or Fedora.
Requirements for pip and HEAD install
-------------------------------------
+103 -18
View File
@@ -2,6 +2,11 @@
Plugins
=======
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and
"OPTIONAL" in this document are to be interpreted as described in
RFC 2119.
You can use plugins to customize the behavior of OCRmyPDF at certain points of
interest.
@@ -11,39 +16,34 @@ Currently, it is possible to:
- override the decision for whether or not to perform OCR on a particular file
- modify the image is about to be sent for OCR
- modify the page image before it is converted to PDF
- replace the Tesseract OCR with another OCR engine that has similar behavior
- replace Ghostscript with another PDF to image converter (rasterizer) or
PDF/A generator
OCRmyPDF plugins are based on the Python ``pluggy`` package and conform to its
conventions. Note that: plugins installed with as setuptools entrypoints are
not checked currently, because OCRmyPDF assumes you may not want to enable
plugins for all files. Also, plugins must be functions, not classes.
How plugins are imported
========================
Plugins are imported on demand, by the OCRmyPDF worker process that needs to use
them. As such, plugins cannot share state with other plugins, cannot rely on
their module's or the interpreter's global state, and should expect asynchronous
copies of themselves to be running. Plugins can write intermediate files to the
folder specified in ``options.work_folder``.
Plugins should work whether executed in threads or processes.
plugins for all files.
Script plugins
==============
Script plugins may be called from the command line, by specifying the name of a file.
Script plugins may be convenient for informal or "one-off" plugins, when a certain
batch of files needs a special processing step for example.
.. code-block:: bash
ocrmypdf --plugin example_plugin.py input.pdf output.pdf
ocrmypdf --plugin ocrmypdf_example_plugin.py input.pdf output.pdf
Multiple plugins may be called by issuing the ``--plugin`` argument multiple times.
Multiple plugins may be installed by issuing the ``--plugin`` argument multiple times.
Packaged plugins
================
Installed plugins may be installed into the same virtual environment as OCRmyPDF
is installed into. They may be invoked using Python standard module naming.
If you are intending to distribute a plugin, please package it.
.. code-block:: bash
@@ -59,10 +59,45 @@ as packaged plugins, and these modules should begin with the name ``ocrmypdf_``
similar to ``pytest`` packages such as ``pytest-cov`` (the package) and
``pytest_cov`` (the module).
.. note::
We strongly recommend plugin authors name their plugins with the prefix
``ocrmypdf-`` (for the package name on PyPI) and ``ocrmypdf_`` (for the
module), just like pytest plugins.
Plugin requirements
===================
OCRmyPDF generally uses multiple worker processes. When a new worker is started,
Python will import all plugins again, including all plugins that were imported earlier.
This means that the global state of a plugin in one worker will not be shared with
other workers. As such, plugin hook implementations should be stateless, relying
only on their inputs. Hook implementations may use their input parameters to
to obtain a reference to shared state prepared by another hook implementation.
Plugins must expect that other instances of the plugin will be running
simultaneously.
The ``context`` object that is passed to many hooks can be used to share information
about a file being worked on. Plugins must write private, plugin-specific data to
a subfolder named ``{options.work_folder}/ocrmypdf-plugin-name``. Plugins MAY
read and write files in ``options.work_folder``, but should be aware that their
semantics are subject to change.
OCRmyPDF will delete ``options.work_folder`` when it has finished OCRing
a file, unless invoked with ``--keep-temporary-files``.
The documentation for some plugin hooks contain a detailed description of the
execution context in which they will be called.
Plugins should be prepared to work whether executed in worker threads or worker
processes. Generally, OCRmyPDF uses processes, but has a semi-hidden threaded
argument that simplifies debugging.
Plugin hooks
============
A plugin may provide the following hooks. Hooks should be decorated with
A plugin may provide the following hooks. Hooks must be decorated with
``ocrmypdf.hookimpl``, for example:
.. code-block:: python
@@ -70,11 +105,61 @@ A plugin may provide the following hooks. Hooks should be decorated with
from ocrmpydf import hookimpl
@hookimpl
def prepare(options):
def add_options(parser):
pass
The following is a complete list of hooks that may be installed and when
The following is a complete list of hooks that are available, and when
they are called.
.. automodule:: ocrmypdf.pluginspec
.. _firstresult:
**Note on firstresult hooks**
If multiple plugins install implementations for this hook, they will be called in
the reverse of the order in which they are installed (i.e., last plugin wins).
When each hook implementation is called in order, the first implementation that
returns a value other than ``None`` will "win" and prevent execution of all other
hooks. As such, you cannot "chain" a series of plugin filters together in this
way. Instead, a single hook implementation should be responsible for any such
chaining operations.
Custom command line arguments
-----------------------------
.. autofunction:: ocrmypdf.pluginspec.add_options
.. autofunction:: ocrmypdf.pluginspec.check_options
Applying special behavior before processing
-------------------------------------------
.. autofunction:: ocrmypdf.pluginspec.validate
PDF page to image
-----------------
.. autofunction:: ocrmypdf.pluginspec.rasterize_pdf_page
Modifying intermediate images
-----------------------------
.. autofunction:: ocrmypdf.pluginspec.filter_ocr_image
.. autofunction:: ocrmypdf.pluginspec.filter_page_image
OCR engine
----------
.. autofunction:: ocrmypdf.pluginspec.get_ocr_engine
.. autoclass:: ocrmypdf.pluginspec.OcrEngine
:members:
.. automethod:: __str__
.. autoclass:: ocrmypdf.pluginspec.OrientationConfidence
PDF/A production
----------------
.. autofunction:: ocrmypdf.pluginspec.generate_pdfa
+93 -5
View File
@@ -5,14 +5,98 @@ Release notes
OCRmyPDF uses `semantic versioning <http://semver.org/>`__ for its
command line interface and its public API.
The ``ocrmypdf`` package may now be imported. The public API may be
useful in scripts that launch OCRmyPDF processes or that wish to use
some of its features for working with PDFs.
OCRmyPDF's output messages are not considered part of the stable interface -
that is, output messages may be improved at any release level, so parsing them
may be unreliable. Use the API to depend on precise behavior.
The public API may be useful in scripts that launch OCRmyPDF processes or that
wish to use some of its features for working with PDFs.
Note that it is licensed under GPLv3, so scripts that
``import ocrmypdf`` and are released publicly should probably also be
licensed under GPLv3.
v10.3.3
=======
- Fixed a "KeyError: 'dpi'" error message when using ``--threshold`` on an image.
(#607)
v10.3.2
=======
- Fixed a case where we reported "no reason" for a file size increase, when we
could determine the reason.
- Enabled support for pdfminer.six 20200726.
v10.3.1
=======
- Fixed a number of test suite failures with pdfminer.six older than veresion 20200420.
- Enabled support for pdfminer.six 20200720.
v10.3.0
=======
- Fixed an issue where we would consider images that were already JBIG2-encoded
for optimization, potentially producing a less optimized image than the original.
We do not believe this issue would ever cause an image to loss fidelity.
- Where available, pikepdf memory mapping is now used. This improves performance.
- When Leptonica 1.79+ is installed, use its new error handling API to avoid
a "messy" redirection of stderr which was necessary to capture its error
messages.
- For older versions of Leptonica, added a new thread level lock. This fixes a
possible race condition in handling error conditions in Leptonica (although
there is no evidence it ever caused issues in practice).
- Documentation improvements and more type hinting.
v10.2.1
=======
- Disabled calculation of text box order with pdfminer. We never needed this result
and it is expensive to calculate on files with complex pre-existing text.
- Fixed plugin manager to accept ``Path(plugin)`` as a path to a plugin.
- Fixed some typing errors.
- Documentation improvements.
v10.2.0
=======
- Update Docker image to use Ubuntu 20.04.
- Fixed issue PDF/A acquires title "Untitled" after conversion. (#582)
- Fixed a problem where, when using ``--pdf-renderer hocr``, some text would
be missing from the output when using a more recent version of Tesseract.
Tesseract began adding more detailed markup about the semantics of text
that our HOCR transform did not recognize, so it ignored them. This option is
not the default. If necessary ``--redo-ocr`` also redoing OCR to fix such issues.
- Fixed an error in Python 3.9 beta, due to removal of deprecated
``Element.getchildren()``. (#584)
- Implemented support using the API with ``BytesIO`` and other file stream objects.
(#545)
v10.1.1
=======
- Fixed ``OMP_THREAD_LIMIT`` set to invalid value error messages on some input
files. (The error was harmless, apart from less than optimal performance in
some cases.)
v10.1.0
=======
- Previously, we ``--clean-final`` would cause an unpaper-cleaned page image to
be produced twice, which was necessary in some cases but not in general. We
now take this optimization opportunity and reuse the image if possible.
- We now provide PNG files as input to unpaper, since it accepts them, instead
of generating PPM files which can be very large. This can improve performance
and temporary disk usage.
- Documentation updated for plugins.
v10.0.1
=======
- Fixed regression when ``-l lang1+lang2`` is used from command line.
v10.0.0
=======
@@ -31,8 +115,12 @@ v10.0.0
**New features and improvements**
- PDF page scanning is now parallelized across CPUs, speeding up this phase
for files with a high page count.
dramatically for files with a high page counts.
- PDF page scanning is optimized, addressing some performance regressions.
- PDF page scanning is no longer run on pages that are not selected when the
``--pages`` argument is used.
- PDF page scanning is now independent of Ghostscript, ending our past reliance
on this occasionally unstable feature in Ghostscript.
- A plugin architecture has been added, currently allowing one to more easily
use a different OCR engine or PDF renderer from Tesseract and Ghostscript,
respectively. A plugin can also override some decisions, such changing
@@ -41,7 +129,7 @@ v10.0.0
**Developer changes**
- The test spoofing mechanism, used to correct handling of failures in
- The test spoofing mechanism, used to test correct handling of failures in
Tesseract and Ghostscript, has been removed in favor of using plugins for
testing. The spoofing mechanism was fairly complex and required many special
hacks for Windows.
+1 -2
View File
@@ -10,8 +10,7 @@ build-backend = "setuptools.build_meta"
[tool.black]
line-length = 88
target-version = ["py36",
"py37", "py38"]
target-version = ["py36", "py37", "py38"]
skip-string-normalization = true
include = '\.pyi?$'
exclude = '''
+5 -5
View File
@@ -3,10 +3,10 @@
# installation
cffi == 1.14.0
coloredlogs == 14.0 # technically optional
img2pdf == 0.3.4
img2pdf == 0.3.6
pdfminer.six == 20200517
pikepdf == 1.14.0
pikepdf == 1.16.1
pluggy == 0.13.1
Pillow == 7.1.1
reportlab == 3.5.34
tqdm == 4.45.0
Pillow == 7.1.2
reportlab == 3.5.42
tqdm == 4.46.1
+1 -1
View File
@@ -1,7 +1,7 @@
pytest >= 5.0.0
pytest-helpers-namespace >= 2019.1.8
pytest-xdist >= 1.31.0
pytest-cov >= 2.9.0
pytest-cov >= 2.10.0
python-xmp-toolkit == 2.0.1 # requires apt-get install libexempi3
# or brew install exempi
#PyMuPDF == 1.13.4 # optional
+1 -1
View File
@@ -1 +1 @@
watchdog >= 0.8.2, < 1.0
watchdog == 0.10.2
+2 -1
View File
@@ -54,6 +54,7 @@ setup(
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: End Users/Desktop",
@@ -82,7 +83,7 @@ setup(
'cffi >= 1.9.1', # must be a setup and install requirement
'coloredlogs >= 14.0', # strictly optional
'img2pdf >= 0.3.0, < 0.4', # pure Python, so track HEAD closely
'pdfminer.six >= 20191110, <= 20200517',
'pdfminer.six >= 20191110, <= 20200726',
'pikepdf >= 1.14.0, < 2',
'Pillow >= 7.0.0',
'pluggy >= 0.13.0',
+14 -1
View File
@@ -18,6 +18,7 @@
import logging
import os
import signal
import sys
from multiprocessing import set_start_method
@@ -26,11 +27,20 @@ from ocrmypdf._plugin_manager import get_parser_options_plugins
from ocrmypdf._sync import run_pipeline
from ocrmypdf._validation import check_closed_streams, check_options
from ocrmypdf.api import Verbosity, configure_logging
from ocrmypdf.exceptions import BadArgsError, ExitCode, MissingDependencyError
from ocrmypdf.exceptions import (
BadArgsError,
ExitCode,
InputFileError,
MissingDependencyError,
)
log = logging.getLogger('ocrmypdf')
def sigbus(*args):
raise InputFileError("Lost access to the input file")
def run(args=None):
_parser, options, plugin_manager = get_parser_options_plugins(args=args)
@@ -62,6 +72,9 @@ def run(args=None):
log.error(e)
return ExitCode.missing_dependency
if hasattr(signal, 'SIGBUS'):
signal.signal(signal.SIGBUS, sigbus)
result = run_pipeline(options=options, plugin_manager=plugin_manager)
return result
+14 -1
View File
@@ -28,6 +28,8 @@ from typing import Callable, Iterable, Optional
from tqdm import tqdm
from ocrmypdf.exceptions import InputFileError
def log_listener(queue):
"""Listen to the worker processes and forward the messages to logging
@@ -53,12 +55,20 @@ def log_listener(queue):
traceback.print_exc(file=sys.stderr)
def process_sigbus(*args):
raise InputFileError("A worker process lost access to an input file")
def process_init(queue, user_init):
"""Initialize a process pool worker"""
# Ignore SIGINT (our parent process will kill us gracefully)
signal.signal(signal.SIGINT, signal.SIG_IGN)
# Install SIGBUS handler (so our parent process can abort somewhat gracefully)
if hasattr(signal, 'SIGBUS'):
signal.signal(signal.SIGBUS, process_sigbus)
# Reconfigure the root logger for this process to send all messages to a queue
h = logging.handlers.QueueHandler(queue)
root = logging.getLogger()
@@ -70,6 +80,9 @@ def process_init(queue, user_init):
def thread_init(_queue, user_init):
# As a thread, block SIGBUS so the main thread deals with it...
if hasattr(signal, 'SIGBUS'):
signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGBUS})
if user_init:
user_init()
@@ -84,7 +97,7 @@ def exec_progress_pool(
task_arguments: Optional[Iterable] = None,
task_finished: Optional[Callable] = None,
):
log_queue = multiprocessing.Queue(-1)
log_queue: multiprocessing.Queue = multiprocessing.Queue(-1)
listener = threading.Thread(target=log_listener, args=(log_queue,))
if use_threads:
+11 -7
View File
@@ -25,6 +25,7 @@ from os import fspath
from pathlib import Path
from shutil import which
from subprocess import PIPE, CalledProcessError
from typing import Optional, cast
from PIL import Image
@@ -34,12 +35,12 @@ from ocrmypdf.subprocess import get_version, run
log = logging.getLogger(__name__)
GS = 'gs'
_gswin = None
if os.name == 'nt':
GS = which('gswin64c')
if not GS:
GS = which('gswin32c')
if not GS:
_gswin = which('gswin64c')
if not _gswin:
_gswin = which('gswin32c')
if not _gswin:
raise MissingDependencyError(
"""
---------------------------------------------------------------------
@@ -52,7 +53,10 @@ if os.name == 'nt':
---------------------------------------------------------------------
"""
)
GS = Path(GS).stem
_gswin = Path(_gswin).stem
GS = _gswin if _gswin else 'gs'
del _gswin
def version():
@@ -77,7 +81,7 @@ def jpeg_passthrough_available() -> bool:
def _gs_error_reported(stream) -> bool:
return re.search(r'error', stream, flags=re.IGNORECASE)
return True if re.search(r'error', stream, flags=re.IGNORECASE) else False
def rasterize_pdf(
+3
View File
@@ -17,6 +17,7 @@
"""Interface to pngquant executable"""
from os import fspath
from tempfile import NamedTemporaryFile
from PIL import Image
@@ -38,6 +39,8 @@ def available():
def quantize(input_file, output_file, quality_min, quality_max):
input_file = fspath(input_file)
output_file = fspath(output_file)
if input_file.endswith('.jpg'):
with Image.open(input_file) as im, NamedTemporaryFile(suffix='.png') as tmp:
im.save(tmp)
+6 -6
View File
@@ -123,7 +123,7 @@ def get_languages():
return set(lang.strip() for lang in rest)
def tess_base_args(langs: List[str], engine_mode) -> List[str]:
def tess_base_args(langs: List[str], engine_mode: int) -> List[str]:
args = ['tesseract']
if langs:
args.extend(['-l', '+'.join(langs)])
@@ -132,7 +132,7 @@ def tess_base_args(langs: List[str], engine_mode) -> List[str]:
return args
def get_orientation(input_file: Path, engine_mode, timeout: float):
def get_orientation(input_file: Path, engine_mode: int, timeout: float):
args_tesseract = tess_base_args(['osd'], engine_mode) + [
'--psm',
'0',
@@ -229,9 +229,9 @@ def generate_hocr(
input_file: Path,
output_hocr: Path,
output_text: Path,
languages: list,
engine_mode,
tessconfig: list,
languages: List[str],
engine_mode: int,
tessconfig: List[str],
timeout: float,
pagesegmode: int,
user_words,
@@ -290,7 +290,7 @@ def generate_pdf(
output_pdf: Path,
output_text: Path,
languages: List[str],
engine_mode,
engine_mode: int,
tessconfig: List[str],
timeout: float,
pagesegmode: int,
+27 -17
View File
@@ -26,6 +26,7 @@ import shlex
from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError
from tempfile import TemporaryDirectory
from typing import Tuple
from PIL import Image
@@ -40,13 +41,11 @@ def version():
return get_version('unpaper')
def run(input_file, output_file, dpi, mode_args):
args_unpaper = ['unpaper', '-v', '--dpi', str(dpi)] + mode_args
def _setup_unpaper_io(tmpdir: Path, input_file: Path) -> Tuple[Path, Path]:
SUFFIXES = {'1': '.pbm', 'L': '.pgm', 'RGB': '.ppm'}
with TemporaryDirectory() as tmpdir, Image.open(input_file) as im:
if im.mode not in SUFFIXES.keys():
with Image.open(input_file) as im:
im_modified = False
if im.mode not in SUFFIXES:
log.info("Converting image to other colorspace")
try:
if im.mode == 'P' and len(im.getcolors()) == 2:
@@ -54,11 +53,11 @@ def run(input_file, output_file, dpi, mode_args):
else:
im = im.convert(mode='RGB')
except IOError as e:
im.close()
raise MissingDependencyError(
"Could not convert image with type " + im.mode
) from e
else:
im_modified = True
try:
suffix = SUFFIXES[im.mode]
except KeyError:
@@ -66,9 +65,21 @@ def run(input_file, output_file, dpi, mode_args):
"Failed to convert image to a supported format."
) from e
input_pnm = Path(tmpdir) / f'input{suffix}'
output_pnm = Path(tmpdir) / f'output{suffix}'
im.save(input_pnm, format='PPM')
if im_modified or input_file.suffix != '.png':
input_png = tmpdir / 'input.png'
im.save(input_png, format='PNG', compress_level=1)
else:
# No changes, PNG input, just use the file we already have
input_png = input_file
output_pnm = tmpdir / f'output{suffix}'
return input_png, output_pnm
def run(input_file, output_file, dpi, mode_args):
args_unpaper = ['unpaper', '-v', '--dpi', str(dpi)] + mode_args
with TemporaryDirectory() as tmpdir:
input_png, output_pnm = _setup_unpaper_io(Path(tmpdir), input_file)
# To prevent any shenanigans from accepting arbitrary parameters in
# --unpaper-args, we:
@@ -77,23 +88,22 @@ def run(input_file, output_file, dpi, mode_args):
# 3) append absolute paths for the input and output file
# This should ensure that a user cannot clobber some other file with
# their unpaper arguments (whether intentionally or otherwise)
args_unpaper.extend([os.fspath(input_pnm), os.fspath(output_pnm)])
args_unpaper.extend([os.fspath(input_png), os.fspath(output_pnm)])
try:
proc = external_run(
args_unpaper,
check=True,
close_fds=True,
universal_newlines=True,
stderr=STDOUT,
cwd=tmpdir,
stderr=STDOUT, # unpaper writes logging output to stdout and stderr
cwd=tmpdir, # and cannot send file output to stdout
stdout=PIPE,
)
except CalledProcessError as e:
log.debug(e.output)
log.debug(e.stderr)
raise e from e
else:
log.debug(proc.stdout)
# unpaper sets dpi to 72; fix this
log.debug(proc.stderr)
try:
with Image.open(output_pnm) as imout:
imout.save(output_file, dpi=(dpi, dpi))
+7 -1
View File
@@ -18,6 +18,7 @@
import logging
from contextlib import suppress
from pathlib import Path
from typing import Optional
import pikepdf
@@ -108,7 +109,12 @@ class OcrGrafter:
self.interim_count = 0
def graft_page(
self, *, pageno: int, image: Path, textpdf: Path, autorotate_correction: int
self,
*,
pageno: int,
image: Optional[Path],
textpdf: Optional[Path],
autorotate_correction: int,
):
if textpdf and not self.font:
self.font, self.font_key = self._find_font(textpdf)
+23 -20
View File
@@ -18,33 +18,36 @@
import os
import shutil
import sys
from argparse import Namespace
from copy import copy
from io import IOBase
from pathlib import Path
from typing import Iterator
from ocrmypdf._plugin_manager import get_plugin_manager
from ocrmypdf.pdfinfo import PdfInfo
class PdfContext:
"""Holds our context for a particular run of the pipeline"""
def __init__(
self, options, work_folder: Path, origin: Path, pdfinfo, plugin_manager
self,
options: Namespace,
work_folder: Path,
origin: Path,
pdfinfo: PdfInfo,
plugin_manager,
):
self.options = options
self.work_folder = Path(work_folder)
self.origin = Path(origin)
self.work_folder = work_folder
self.origin = origin
self.pdfinfo = pdfinfo
self.plugin_manager = plugin_manager
if options:
self.name = os.path.basename(options.input_file)
else:
self.name = 'origin.pdf'
if self.name == '-':
self.name = 'stdin'
def get_path(self, name: str) -> Path:
return self.work_folder / name
def get_page_contexts(self):
def get_page_contexts(self) -> Iterator['PageContext']:
npages = len(self.pdfinfo)
for n in range(npages):
yield PageContext(self, n)
@@ -53,14 +56,14 @@ class PdfContext:
class PageContext:
"""Holds our context for a page
Must be pickable, so only store intrinsic/simple data elements
Must be pickable, so stores only intrinsic/simple data elements or those
capable of their serializing themselves via __getstate__.
"""
def __init__(self, pdf_context: PdfContext, pageno):
self.work_folder = pdf_context.work_folder
self.origin = pdf_context.origin
self.options = pdf_context.options
self.name = pdf_context.name
self.pageno = pageno
self.pageinfo = pdf_context.pdfinfo[pageno]
self.plugin_manager = pdf_context.plugin_manager
@@ -70,16 +73,16 @@ class PageContext:
def __getstate__(self):
state = self.__dict__.copy()
if state['plugin_manager'] is not None:
state['plugin_manager'] = None
state['options'] = copy(self.options)
if not isinstance(state['options'].input_file, (str, bytes, os.PathLike)):
state['options'].input_file = 'stream'
if not isinstance(state['options'].output_file, (str, bytes, os.PathLike)):
state['options'].output_file = 'stream'
return state
def __setstate__(self, state):
self.__dict__.update(state)
self.plugin_manager = get_plugin_manager(self.options.plugins)
def cleanup_working_files(work_folder, options):
def cleanup_working_files(work_folder: Path, options: Namespace):
if options.keep_temporary_files:
print(f"Temporary working files retained at:\n{work_folder}", file=sys.stderr)
else:
+2 -2
View File
@@ -25,9 +25,9 @@ from tqdm import tqdm
class PageNumberFilter(logging.Filter):
def filter(self, record):
pageno = getattr(record, 'pageno', None)
if pageno is not None:
if isinstance(pageno, int):
record.pageno = f'{pageno:5d} '
else:
elif pageno is None:
record.pageno = ''
return True
+47 -24
View File
@@ -19,8 +19,11 @@ import logging
import os
import re
import sys
from contextlib import suppress
from datetime import datetime, timezone
from pathlib import Path
from shutil import copyfileobj
from typing import BinaryIO, Dict, Iterable, Optional, Union, cast
import img2pdf
import pikepdf
@@ -29,6 +32,7 @@ from PIL import Image, ImageColor, ImageDraw
from ocrmypdf import leptonica
from ocrmypdf._exec import unpaper
from ocrmypdf._jobcontext import PageContext, PdfContext
from ocrmypdf._version import PROGRAM_NAME
from ocrmypdf._version import __version__ as VERSION
from ocrmypdf.exceptions import (
@@ -170,7 +174,7 @@ def get_pdfinfo(
raise InputFileError()
def validate_pdfinfo_options(context):
def validate_pdfinfo_options(context: PdfContext):
pdfinfo = context.pdfinfo
options = context.options
@@ -255,7 +259,7 @@ def get_canvas_square_dpi(pageinfo, options) -> Resolution:
return Resolution(units, units)
def is_ocr_required(page_context):
def is_ocr_required(page_context: PageContext):
pageinfo = page_context.pageinfo
options = page_context.options
@@ -329,7 +333,7 @@ def is_ocr_required(page_context):
return ocr_required
def rasterize_preview(input_file, page_context):
def rasterize_preview(input_file: Path, page_context: PageContext):
output_file = page_context.get_path('rasterize_preview.jpg')
canvas_dpi = get_canvas_square_dpi(page_context.pageinfo, page_context.options)
page_dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
@@ -344,7 +348,7 @@ def rasterize_preview(input_file, page_context):
return output_file
def describe_rotation(page_context, orient_conf, correction):
def describe_rotation(page_context: PageContext, orient_conf, correction: int):
"""
Describe the page rotation we are going to perform.
"""
@@ -373,7 +377,7 @@ def describe_rotation(page_context, orient_conf, correction):
return f"{facing}, confidence {orient_conf.confidence:.2f} - {action}"
def get_orientation_correction(preview, page_context):
def get_orientation_correction(preview: Path, page_context: PageContext):
"""Work out orientation correct for each page.
We ask Ghostscript to draw a preview page, which will rasterize with the
@@ -405,7 +409,11 @@ def get_orientation_correction(preview, page_context):
def rasterize(
input_file, page_context, correction=0, output_tag='', remove_vectors=None
input_file: Path,
page_context: PageContext,
correction: int = 0,
output_tag: str = '',
remove_vectors=None,
):
colorspaces = ['pngmono', 'pnggray', 'png256', 'png16m']
device_idx = 0
@@ -455,7 +463,7 @@ def rasterize(
return output_file
def preprocess_remove_background(input_file, page_context):
def preprocess_remove_background(input_file: Path, page_context: PageContext):
if any(image.bpc > 1 for image in page_context.pageinfo.images):
output_file = page_context.get_path('pp_rm_bg.png')
leptonica.remove_background(input_file, output_file)
@@ -465,21 +473,21 @@ def preprocess_remove_background(input_file, page_context):
return input_file
def preprocess_deskew(input_file, page_context):
def preprocess_deskew(input_file: Path, page_context: PageContext):
output_file = page_context.get_path('pp_deskew.png')
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
leptonica.deskew(input_file, output_file, dpi.x)
return output_file
def preprocess_clean(input_file, page_context):
def preprocess_clean(input_file: Path, page_context: PageContext):
output_file = page_context.get_path('pp_clean.png')
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
unpaper.clean(input_file, output_file, dpi.x, page_context.options.unpaper_args)
return output_file
def create_ocr_image(image, page_context):
def create_ocr_image(image: Path, page_context: PageContext):
"""Create the image we send for OCR. May not be the same as the display
image depending on preprocessing. This image will never be shown to the
user."""
@@ -522,7 +530,9 @@ def create_ocr_image(image, page_context):
if options.threshold:
pix = leptonica.Pix.frompil(im)
pix = pix.masked_threshold_on_background_norm()
im = pix.topil()
im_pix = pix.topil()
im_pix.info['dpi'] = im.info['dpi']
im = im_pix
del draw
@@ -538,7 +548,7 @@ def create_ocr_image(image, page_context):
return output_file
def ocr_engine_hocr(input_file, page_context):
def ocr_engine_hocr(input_file: Path, page_context: PageContext):
hocr_out = page_context.get_path('ocr_hocr.hocr')
hocr_text_out = page_context.get_path('ocr_hocr.txt')
options = page_context.options
@@ -558,7 +568,7 @@ def should_visible_page_image_use_jpg(pageinfo):
return pageinfo.images and all(im.enc == Encoding.jpeg for im in pageinfo.images)
def create_visible_page_jpg(image, page_context):
def create_visible_page_jpg(image: Path, page_context: PageContext) -> Path:
output_file = page_context.get_path('visible.jpg')
with Image.open(image) as im:
# At this point the image should be a .png, but deskew, unpaper
@@ -577,7 +587,7 @@ def create_visible_page_jpg(image, page_context):
return output_file
def create_pdf_page_from_image(image, page_context):
def create_pdf_page_from_image(image: Path, page_context: PageContext):
# We rasterize a square DPI version of each page because most image
# processing tools don't support rectangular DPI. Use the square DPI as it
# accurately describes the image. It would be possible to resample the image
@@ -598,7 +608,7 @@ def create_pdf_page_from_image(image, page_context):
return output_file
def render_hocr_page(hocr, page_context):
def render_hocr_page(hocr: Path, page_context: PageContext):
output_file = page_context.get_path('ocr_hocr.pdf')
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
hocrtransform = HocrTransform(hocr, dpi.x) # square
@@ -612,7 +622,7 @@ def render_hocr_page(hocr, page_context):
return output_file
def ocr_engine_textonly_pdf(input_image, page_context):
def ocr_engine_textonly_pdf(input_image: Path, page_context: PageContext):
output_pdf = page_context.get_path('ocr_tess.pdf')
output_text = page_context.get_path('ocr_tess.txt')
options = page_context.options
@@ -627,7 +637,7 @@ def ocr_engine_textonly_pdf(input_image, page_context):
return (output_pdf, output_text)
def get_docinfo(base_pdf, context):
def get_docinfo(base_pdf: pikepdf.Pdf, context: PdfContext) -> Dict[str, str]:
options = context.options
def from_document_info(key):
@@ -664,13 +674,13 @@ def get_docinfo(base_pdf, context):
return pdfmark
def generate_postscript_stub(context):
def generate_postscript_stub(context: PdfContext):
output_file = context.get_path('pdfa.ps')
generate_pdfa_ps(output_file)
return output_file
def convert_to_pdfa(input_pdf, input_ps_stub, context):
def convert_to_pdfa(input_pdf: Path, input_ps_stub: Path, context: PdfContext):
options = context.options
input_pdfinfo = context.pdfinfo
fix_docinfo_file = context.get_path('fix_docinfo.pdf')
@@ -712,14 +722,14 @@ def convert_to_pdfa(input_pdf, input_ps_stub, context):
return output_file
def should_linearize(working_file, context):
def should_linearize(working_file: Path, context: PdfContext):
filesize = os.stat(working_file).st_size
if filesize > (context.options.fast_web_view * 1_000_000):
return True
return False
def metadata_fixup(working_file, context):
def metadata_fixup(working_file: Path, context: PdfContext):
output_file = context.get_path('metafix.pdf')
options = context.options
@@ -749,6 +759,14 @@ def metadata_fixup(working_file, context):
if 'xmp:CreateDate' not in meta:
meta['xmp:CreateDate'] = meta.get('xmp:ModifyDate', '')
# Ghostscript likes to set title to Untitled if omitted from input.
# Reverse this, because PDF/A TechNote 0003:Metadata in PDF/A-1
# and the XMP Spec do not make this recommendation.
if meta.get('dc:title') == 'Untitled':
with original.open_metadata() as original_meta:
if 'dc:title' not in original_meta:
del meta['dc:title']
meta_original = original.open_metadata()
missing = set(meta_original.keys()) - set(meta.keys())
report_on_metadata(missing)
@@ -768,7 +786,7 @@ def metadata_fixup(working_file, context):
return output_file
def optimize_pdf(input_file, context):
def optimize_pdf(input_file: Path, context: PdfContext):
output_file = context.get_path('optimize.pdf')
save_settings = dict(
compress_streams=True,
@@ -780,7 +798,7 @@ def optimize_pdf(input_file, context):
return output_file
def merge_sidecars(txt_files, context):
def merge_sidecars(txt_files: Iterable[Optional[Path]], context: PdfContext):
output_file = context.get_path('sidecar.txt')
with open(output_file, 'w', encoding="utf-8") as stream:
for page_num, txt_file in enumerate(txt_files):
@@ -801,12 +819,17 @@ def merge_sidecars(txt_files, context):
return output_file
def copy_final(input_file, output_file, _context):
def copy_final(input_file, output_file, _context: PdfContext):
log.debug('%s -> %s', input_file, output_file)
with open(input_file, 'rb') as input_stream:
if output_file == '-':
copyfileobj(input_stream, sys.stdout.buffer)
sys.stdout.flush()
elif hasattr(output_file, 'writable'):
output_stream = output_file
copyfileobj(input_stream, output_stream)
with suppress(AttributeError):
output_stream.flush()
else:
# At this point we overwrite the output_file specified by the user
# use copyfileobj because then we use open() to create the file and
+57 -11
View File
@@ -19,8 +19,9 @@ import argparse
import importlib
import importlib.util
import sys
from functools import partial
from pathlib import Path
from typing import List
from typing import Callable, List, Tuple, Union
import pluggy
@@ -28,19 +29,57 @@ from ocrmypdf import pluginspec
from ocrmypdf.cli import get_parser, plugins_only_parser
def get_plugin_manager(plugins: List[str], builtins=True):
pm = pluggy.PluginManager('ocrmypdf')
class OcrmypdfPluginManager(pluggy.PluginManager):
"""pluggy.PluginManager that can fork.
Capable of reconstructing itself in child workers.
Arguments:
setup_func: callback that initializes the plugin manager with all
standard plugins
"""
def __init__(
self, *args, setup_func: Callable[[pluggy.PluginManager], None], **kwargs
):
self._init_args = args
self._setup_func = setup_func
self._init_kwargs = kwargs
super().__init__(*args, **kwargs)
setup_func(self)
def __getstate__(self):
state = dict(
_init_args=self._init_args,
_setup_func=self._setup_func,
_init_kwargs=self._init_kwargs,
)
return state
def __setstate__(self, state):
self.__init__(
*state['_init_args'],
setup_func=state['_setup_func'],
**state['_init_kwargs'],
)
def _setup_plugins(
pm: pluggy.PluginManager, plugins: List[Union[str, Path]], builtins: bool = True
):
pm.add_hookspecs(pluginspec)
all_plugins: List[Union[str, Path]] = []
if builtins:
all_plugins = [
'ocrmypdf.builtin_plugins.ghostscript',
'ocrmypdf.builtin_plugins.tesseract_ocr',
] + plugins
else:
all_plugins = plugins
all_plugins.extend(
[
'ocrmypdf.builtin_plugins.ghostscript',
'ocrmypdf.builtin_plugins.tesseract_ocr',
]
)
all_plugins.extend(plugins)
for name in all_plugins:
if name.endswith('.py'):
if isinstance(name, Path) or name.endswith('.py'):
# Import by filename
module_name = Path(name).stem
spec = importlib.util.spec_from_file_location(module_name, name)
@@ -51,12 +90,19 @@ def get_plugin_manager(plugins: List[str], builtins=True):
# Import by dotted module name
module = importlib.import_module(name)
pm.register(module)
def get_plugin_manager(plugins: List[str], builtins=True):
pm = OcrmypdfPluginManager(
project_name='ocrmypdf',
setup_func=partial(_setup_plugins, plugins=plugins, builtins=builtins),
)
return pm
def get_parser_options_plugins(
args,
) -> (argparse.ArgumentParser, argparse.Namespace, pluggy.PluginManager):
) -> Tuple[argparse.ArgumentParser, argparse.Namespace, pluggy.PluginManager]:
pre_options, _unused = plugins_only_parser.parse_known_args(args=args)
plugin_manager = get_plugin_manager(pre_options.plugins)
+131 -82
View File
@@ -20,16 +20,18 @@ import logging.handlers
import os
import sys
import threading
from collections import namedtuple
from functools import partial
from pathlib import Path
from tempfile import mkdtemp
from typing import List, NamedTuple, Optional, Tuple
import pikepdf
import PIL
from ocrmypdf._concurrent import exec_progress_pool
from ocrmypdf._graft import OcrGrafter
from ocrmypdf._jobcontext import PdfContext, cleanup_working_files
from ocrmypdf._jobcontext import PageContext, PdfContext, cleanup_working_files
from ocrmypdf._logging import PageNumberFilter
from ocrmypdf._pipeline import (
convert_to_pdfa,
copy_final,
@@ -62,29 +64,29 @@ from ocrmypdf._validation import (
report_output_file_size,
)
from ocrmypdf.exceptions import ExitCode, ExitCodeException
from ocrmypdf.helpers import available_cpu_count, check_pdf, samefile
from ocrmypdf.helpers import (
available_cpu_count,
check_pdf,
pikepdf_enable_mmap,
samefile,
)
from ocrmypdf.pdfa import file_claims_pdfa
log = logging.getLogger(__name__)
PageResult = namedtuple(
'PageResult', 'pageno, pdf_page_from_image, ocr, text, orientation_correction'
)
class PageResult(NamedTuple):
pageno: int
pdf_page_from_image: Optional[Path]
ocr: Optional[Path]
text: Optional[Path]
orientation_correction: int
tls = threading.local()
tls.pageno = None
def preprocess(page_context, image, remove_background, deskew, clean):
if remove_background:
image = preprocess_remove_background(image, page_context)
if deskew:
image = preprocess_deskew(image, page_context)
if clean:
image = preprocess_clean(image, page_context)
return image
old_factory = logging.getLogRecordFactory()
@@ -98,56 +100,71 @@ def record_factory(*args, **kwargs):
logging.setLogRecordFactory(record_factory)
def exec_page_sync(page_context):
def preprocess(
page_context: PageContext,
image: Path,
remove_background: bool,
deskew: bool,
clean: bool,
) -> Path:
if remove_background:
image = preprocess_remove_background(image, page_context)
if deskew:
image = preprocess_deskew(image, page_context)
if clean:
image = preprocess_clean(image, page_context)
return image
def make_intermediate_images(
page_context: PageContext, orientation_correction: int
) -> Tuple[Path, Optional[Path]]:
options = page_context.options
tls.pageno = page_context.pageno + 1
orientation_correction = 0
pdf_page_from_image_out = None
ocr_out = None
text_out = None
if is_ocr_required(page_context):
if options.rotate_pages:
# Rasterize
rasterize_preview_out = rasterize_preview(page_context.origin, page_context)
orientation_correction = get_orientation_correction(
rasterize_preview_out, page_context
)
ocr_image = preprocess_out = None
rasterize_out = rasterize(
page_context.origin,
page_context,
correction=orientation_correction,
remove_vectors=False,
)
rasterize_out = rasterize(
page_context.origin,
if not any([options.clean, options.clean_final, options.remove_vectors]):
ocr_image = preprocess_out = preprocess(
page_context,
correction=orientation_correction,
remove_vectors=False,
rasterize_out,
options.remove_background,
options.deskew,
clean=False,
)
if not any([options.clean, options.clean_final, options.remove_vectors]):
ocr_image = preprocess_out = preprocess(
else:
if not options.lossless_reconstruction:
preprocess_out = preprocess(
page_context,
rasterize_out,
options.remove_background,
options.deskew,
clean=False,
clean=options.clean_final,
)
if options.remove_vectors:
rasterize_ocr_out = rasterize(
page_context.origin,
page_context,
correction=orientation_correction,
remove_vectors=True,
output_tag='_ocr',
)
else:
if not options.lossless_reconstruction:
preprocess_out = preprocess(
page_context,
rasterize_out,
options.remove_background,
options.deskew,
clean=options.clean_final,
)
if options.remove_vectors:
rasterize_ocr_out = rasterize(
page_context.origin,
page_context,
correction=orientation_correction,
remove_vectors=True,
output_tag='_ocr',
)
else:
rasterize_ocr_out = rasterize_out
rasterize_ocr_out = rasterize_out
if (
preprocess_out
and rasterize_ocr_out == rasterize_out
and options.clean == options.clean_final
):
# Optimization: image for OCR is identical to presentation image
ocr_image = preprocess_out
else:
ocr_image = preprocess(
page_context,
rasterize_ocr_out,
@@ -155,32 +172,56 @@ def exec_page_sync(page_context):
options.deskew,
clean=options.clean,
)
return ocr_image, preprocess_out
ocr_image_out = create_ocr_image(ocr_image, page_context)
pdf_page_from_image_out = None
if not options.lossless_reconstruction:
visible_image_out = preprocess_out
if should_visible_page_image_use_jpg(page_context.pageinfo):
visible_image_out = create_visible_page_jpg(
visible_image_out, page_context
)
visible_image_out = (
page_context.plugin_manager.hook.filter_page_image(
page=page_context, image_filename=Path(visible_image_out)
)
or visible_image_out
)
pdf_page_from_image_out = create_pdf_page_from_image(
visible_image_out, page_context
)
def exec_page_sync(page_context: PageContext):
options = page_context.options
tls.pageno = page_context.pageno + 1
if options.pdf_renderer == 'hocr':
(hocr_out, text_out) = ocr_engine_hocr(ocr_image_out, page_context)
ocr_out = render_hocr_page(hocr_out, page_context)
if not is_ocr_required(page_context):
return PageResult(
pageno=page_context.pageno,
pdf_page_from_image=None,
ocr=None,
text=None,
orientation_correction=0,
)
if options.pdf_renderer == 'sandwich':
(ocr_out, text_out) = ocr_engine_textonly_pdf(ocr_image_out, page_context)
orientation_correction = 0
if options.rotate_pages:
# Rasterize
rasterize_preview_out = rasterize_preview(page_context.origin, page_context)
orientation_correction = get_orientation_correction(
rasterize_preview_out, page_context
)
ocr_image, preprocess_out = make_intermediate_images(
page_context, orientation_correction
)
ocr_image_out = create_ocr_image(ocr_image, page_context)
pdf_page_from_image_out = None
if not options.lossless_reconstruction:
assert preprocess_out
visible_image_out = preprocess_out
if should_visible_page_image_use_jpg(page_context.pageinfo):
visible_image_out = create_visible_page_jpg(visible_image_out, page_context)
filtered_image = page_context.plugin_manager.hook.filter_page_image(
page=page_context, image_filename=visible_image_out
)
if filtered_image:
visible_image_out = filtered_image
pdf_page_from_image_out = create_pdf_page_from_image(
visible_image_out, page_context
)
if options.pdf_renderer == 'hocr':
(hocr_out, text_out) = ocr_engine_hocr(ocr_image_out, page_context)
ocr_out = render_hocr_page(hocr_out, page_context)
if options.pdf_renderer == 'sandwich':
(ocr_out, text_out) = ocr_engine_textonly_pdf(ocr_image_out, page_context)
return PageResult(
pageno=page_context.pageno,
@@ -191,7 +232,7 @@ def exec_page_sync(page_context):
)
def post_process(pdf_file, context):
def post_process(pdf_file, context: PdfContext):
pdf_out = pdf_file
if context.options.output_type.startswith('pdfa'):
ps_stub_out = generate_postscript_stub(context)
@@ -201,14 +242,15 @@ def post_process(pdf_file, context):
return optimize_pdf(pdf_out, context)
def worker_init(max_pixels):
def worker_init(max_pixels: int):
# In Windows, child process will not inherit our change to this value in
# the parent process, so ensure workers get it set. Not needed when running
# threaded, but harmless to set again.
PIL.Image.MAX_IMAGE_PIXELS = max_pixels
pikepdf_enable_mmap()
def exec_concurrent(context):
def exec_concurrent(context: PdfContext):
"""Execute the pipeline concurrently"""
# Run exec_page_sync on every page context
@@ -216,10 +258,10 @@ def exec_concurrent(context):
if max_workers > 1:
log.info("Start processing %d pages concurrently", max_workers)
sidecars = [None] * len(context.pdfinfo)
sidecars: List[Optional[Path]] = [None] * len(context.pdfinfo)
ocrgraft = OcrGrafter(context)
def update_page(result, pbar):
def update_page(result: PageResult, pbar):
sidecars[result.pageno] = result.text
pbar.update()
ocrgraft.graft_page(
@@ -275,6 +317,7 @@ def configure_debug_logging(log_filename, prefix=''):
'[%(asctime)s] - %(name)s - %(levelname)7s -%(pageno)s %(message)s'
)
log_file_handler.setFormatter(formatter)
log_file_handler.addFilter(PageNumberFilter())
logging.getLogger(prefix).addHandler(log_file_handler)
return log_file_handler
@@ -295,6 +338,8 @@ def run_pipeline(options, *, plugin_manager, api=False):
):
debug_log_handler = configure_debug_logging(Path(work_folder) / "debug.log")
pikepdf_enable_mmap()
try:
check_requested_output_file(options)
start_input_file, original_filename = create_input_file(options, work_folder)
@@ -323,6 +368,10 @@ def run_pipeline(options, *, plugin_manager, api=False):
if options.output_file == '-':
log.info("Output sent to stdout")
elif (
hasattr(options.output_file, 'writable') and options.output_file.writable()
):
log.info("Output written to stream")
elif samefile(options.output_file, os.devnull):
pass # Say nothing when sending to dev null
else:
+31 -10
View File
@@ -24,6 +24,7 @@ import sys
import unicodedata
from pathlib import Path
from shutil import copyfileobj
from typing import Tuple
import pikepdf
import PIL
@@ -67,20 +68,20 @@ def check_platform():
)
def check_options_languages(options, plugin_manager):
def check_options_languages(options, ocr_engine_languages):
if not options.languages:
options.languages = {DEFAULT_LANGUAGE}
system_lang = locale.getlocale()[0]
if system_lang and not system_lang.startswith('en'):
log.debug("No language specified; assuming --language %s", DEFAULT_LANGUAGE)
ocr_engine = plugin_manager.hook.get_ocr_engine()
if not options.languages.issubset(ocr_engine.languages(options)):
if not ocr_engine_languages:
return
if not options.languages.issubset(ocr_engine_languages):
msg = (
f"{ocr_engine} does not have language data for the following "
f"OCR engine does not have language data for the following "
"requested languages: \n"
)
for lang in options.languages - ocr_engine.languages(options):
for lang in options.languages - ocr_engine_languages:
msg += lang + '\n'
raise MissingDependencyError(msg)
@@ -251,9 +252,9 @@ def check_options_pillow(options):
PIL.Image.MAX_IMAGE_PIXELS = None
def check_options(options, plugin_manager):
def _check_options(options, plugin_manager, ocr_engine_languages):
check_platform()
check_options_languages(options, plugin_manager)
check_options_languages(options, ocr_engine_languages)
check_options_metadata(options)
check_options_output(options)
check_options_sidecar(options)
@@ -265,6 +266,11 @@ def check_options(options, plugin_manager):
plugin_manager.hook.check_options(options=options)
def check_options(options, plugin_manager):
ocr_engine_languages = plugin_manager.hook.get_ocr_engine().languages(options)
_check_options(options, plugin_manager, ocr_engine_languages)
def check_closed_streams(options): # pragma: no cover
"""Work around Python issue with multiprocessing forking on closed streams
@@ -324,14 +330,22 @@ def log_page_orientations(pdfinfo):
log.info('Page orientations detected: %s', ' '.join(orientations))
def create_input_file(options, work_folder: Path) -> (Path, str):
def create_input_file(options, work_folder: Path) -> Tuple[Path, str]:
if options.input_file == '-':
# stdin
log.info('reading file from standard input')
target = work_folder / 'stdin'
with open(target, 'wb') as stream_buffer:
copyfileobj(sys.stdin.buffer, stream_buffer)
return target, "<stdin>"
return target, "stdin"
elif hasattr(options.input_file, 'readable'):
if not options.input_file.readable():
raise InputFileError("Input file stream is not readable")
log.info('reading file from input stream')
target = work_folder / 'stream'
with open(target, 'wb') as stream_buffer:
copyfileobj(options.input_file, stream_buffer)
return target, "stream"
else:
try:
target = work_folder / 'origin'
@@ -349,6 +363,9 @@ def check_requested_output_file(options):
"is connected to a terminal. Please redirect stdout to a "
"file."
)
elif hasattr(options.output_file, 'writable'):
if not options.output_file.writable():
raise OutputFileAccessError("Output stream is not writable")
elif not is_file_writable(options.output_file):
raise OutputFileAccessError(
f"Output file location ({options.output_file}) is not a writable file."
@@ -399,6 +416,10 @@ def report_output_file_size(options, input_file, output_file):
f"The optional dependency '{name}' was not found, so some image "
f"optimizations could not be attempted."
)
if options.output_type.startswith('pdfa'):
reasons.append("PDF/A conversion was enabled. (Try `--output-type pdf`.)")
if options.plugins:
reasons.append("Plugins were used.")
if reasons:
explanation = "Possible reasons for this include:\n" + '\n'.join(reasons) + "\n"
+42 -14
View File
@@ -18,16 +18,16 @@
import logging
import os
import sys
from argparse import ArgumentParser
from enum import IntEnum
from pathlib import Path
from typing import Iterable
from typing import BinaryIO, Iterable, Union
from ocrmypdf._logging import PageNumberFilter, TqdmConsole
from ocrmypdf._plugin_manager import get_plugin_manager
from ocrmypdf._sync import run_pipeline
from ocrmypdf._validation import check_options
from ocrmypdf.cli import get_parser
from ocrmypdf.cli import ArgumentParser, get_parser
from ocrmypdf.helpers import is_iterable_notstr
try:
import coloredlogs
@@ -35,6 +35,9 @@ except ModuleNotFoundError:
coloredlogs = None
PathOrIO = Union[BinaryIO, os.PathLike, str, bytes]
class Verbosity(IntEnum):
"""Verbosity level for configure_logging."""
@@ -127,11 +130,7 @@ def configure_logging(
def create_options(
*,
input_file: os.PathLike,
output_file: os.PathLike,
parser: ArgumentParser,
**kwargs,
*, input_file: PathOrIO, output_file: PathOrIO, parser: ArgumentParser, **kwargs
):
cmdline = []
deferred = []
@@ -154,7 +153,7 @@ def create_options(
cmdline.append(f"--{cmd_style_arg}")
continue
if isinstance(val, Iterable) and not isinstance(val, str):
if is_iterable_notstr(val):
for elem in val:
cmdline.append(f"--{cmd_style_arg}")
cmdline.append(elem)
@@ -171,19 +170,31 @@ def create_options(
else:
raise TypeError(f"{arg}: {val} ({type(val)})")
cmdline.append(str(input_file))
cmdline.append(str(output_file))
try:
cmdline.append(os.fspath(input_file))
except TypeError:
cmdline.append('stream://input_file')
try:
cmdline.append(os.fspath(output_file))
except TypeError:
cmdline.append('stream://output_file')
parser._api_mode = True
options = parser.parse_args(cmdline)
for keyword, val in deferred:
setattr(options, keyword, val)
if options.input_file == 'stream://input_file':
options.input_file = input_file
if options.output_file == 'stream://output_file':
options.output_file = output_file
return options
def ocr( # pylint: disable=unused-argument
input_file: os.PathLike,
output_file: os.PathLike,
input_file: PathOrIO,
output_file: PathOrIO,
*,
language: Iterable[str] = None,
image_dpi: int = None,
@@ -236,9 +247,24 @@ def ocr( # pylint: disable=unused-argument
A few specific arguments are discussed here:
Args:
use_threads (bool): Use worker threads instead of processes. This reduces
use_threads: Use worker threads instead of processes. This reduces
performance but may make debugging easier since it is easier to set
breakpoints.
input_file: If a :class:`pathlib.Path`, ``str`` or ``bytes``, this is
interpreted as file system path to the input file. If the object
appears to be a readable stream (with methods such as ``.read()``
and ``.seek()``), the object will be read in its entirety and saved to
a temporary file. If ``input_file`` is ``"-"``, standard input will be
read.
output_file: If a :class:`pathlib.Path`, ``str`` or ``bytes``, this is
interpreted as file system path to the output file. If the object
appears to be a writable stream (with methods such as ``.read()`` and
``.seek()``), the output will be written to this stream. If
``output_file`` is ``"-"``, the output will be written to ``sys.stdout``
(provided that standard output does not seem to be a terminal device).
When a stream is used as output, whether via a writable object or
``"-"``, some final validation steps are not performed (we do not read
back the stream after it is written).
Raises:
ocrmypdf.PdfMergeFailedError: If the input PDF is malformed, preventing merging
with the OCR layer.
@@ -264,6 +290,8 @@ def ocr( # pylint: disable=unused-argument
"""
if not plugins:
plugins = []
else:
plugins = list(plugins)
parser = get_parser()
_plugin_manager = get_plugin_manager(plugins)
@@ -22,6 +22,7 @@ from ocrmypdf import hookimpl
from ocrmypdf._exec import tesseract
from ocrmypdf.cli import numeric
from ocrmypdf.exceptions import MissingDependencyError
from ocrmypdf.helpers import clamp
from ocrmypdf.pluginspec import OcrEngine
from ocrmypdf.subprocess import check_external_program
@@ -126,7 +127,7 @@ def validate(pdfinfo, options):
# constraint: (ocrmypdf workers) * (tesseract threads) <= max_workers.
# As of Tesseract 4.1, 3 threads is the most effective on a 4 core/8 thread system.
if not os.environ.get('OMP_THREAD_LIMIT', '').isnumeric():
tess_threads = min(3, options.jobs // len(pdfinfo), len(pdfinfo))
tess_threads = clamp(options.jobs // len(pdfinfo), 1, 3)
os.environ['OMP_THREAD_LIMIT'] = str(tess_threads)
else:
tess_threads = int(os.environ['OMP_THREAD_LIMIT'])
+12 -9
View File
@@ -63,7 +63,7 @@ class LanguageSetAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
dest = getattr(namespace, self.dest)
if '+' in values:
dest.add(lang for lang in values.split('+'))
dest.update(lang for lang in values.split('+'))
else:
dest.add(values)
@@ -83,8 +83,8 @@ image, and then creates a PDF from the OCR information.
""",
epilog="""\
OCRmyPDF attempts to keep the output file at about the same size. If a file
contains losslessly compressed images, and output file will be losslessly
compressed as well.
contains losslessly compressed images, and images in the output file will be
losslessly compressed as well.
PDF is a page description file that attempts to preserve a layout exactly.
A PDF can contain vector objects (such as text or lines) and raster objects
@@ -103,9 +103,8 @@ all objects on the page and produce an image-only PDF as output.
If you are concerned about long-term archiving of PDFs, use the default option
--output-type pdfa which converts the PDF to a standardized PDF/A-2b. This
converts images to sRGB colorspace, removes some features from the PDF such
as Javascript or forms. If you want to minimize the number of changes made to
your PDF, use --output-type pdf.
removes some features from the PDF such as Javascript or forms. If you want to
minimize the number of changes made to your PDF, use --output-type pdf.
If OCRmyPDF is given an image file as input, it will attempt to convert the
image to a PDF before processing. For more control over the conversion of
@@ -113,8 +112,7 @@ images to PDF, use the Python package img2pdf or other image to PDF software.
For example, this command uses img2pdf to convert all .png files beginning
with the 'page' prefix to a PDF, fitting each image on A4-sized paper, and
sending the result to OCRmyPDF through a pipe. img2pdf is a dependency of
ocrmypdf so it is already installed.
sending the result to OCRmyPDF through a pipe.
img2pdf --pagesize A4 page*.png | ocrmypdf - myfile.pdf
@@ -462,7 +460,12 @@ Online documentation is located at:
dest='plugins',
action='append',
default=[],
help="Name of plugin to import.",
help="Name of plugin to import. Argument may be issued multiple times to "
"import multiple plugins. Plugins may be specified as module names in "
"Python syntax, provided they are installed in the same Python (virtual) "
"environment as ocrmypdf; or you may give the path to the Python file that "
"contains the plugin. Plugins must conform to the specification in the "
"OCRmyPDF documentation.",
)
debugging = parser.add_argument_group(
+34 -9
View File
@@ -27,6 +27,7 @@ from functools import wraps
from io import StringIO
from math import isclose
from pathlib import Path
from typing import Any, Sequence, TypeVar
import pikepdf
@@ -34,16 +35,18 @@ log = logging.getLogger(__name__)
class Resolution(namedtuple('Resolution', ('x', 'y'))):
"""The number of pixels per inch in each 2D direction."""
__slots__ = ()
def round(self, ndigits):
def round(self, ndigits: int):
return Resolution(round(self.x, ndigits), round(self.y, ndigits))
def to_int(self):
return Resolution(int(round(self.x)), int(round(self.y)))
@property
def is_square(self):
def is_square(self) -> bool:
return isclose(self.x, self.y, rel_tol=1e-3)
def take_max(self, vals, yvals=None):
@@ -104,28 +107,30 @@ def safe_symlink(input_file: os.PathLike, soft_link_name: os.PathLike):
os.symlink(os.path.abspath(input_file), soft_link_name)
def samefile(f1, f2):
def samefile(f1: os.PathLike, f2: os.PathLike):
if os.name == 'nt':
return f1 == f2
else:
return os.path.samefile(f1, f2)
def is_iterable_notstr(thing):
def is_iterable_notstr(thing: Any) -> bool:
"""Is this is an iterable type, other than a string?"""
return isinstance(thing, Iterable) and not isinstance(thing, str)
def monotonic(L: Iterable):
def monotonic(L: Sequence) -> bool:
"""Does list increase monotonically?"""
return all(b > a for a, b in zip(L, L[1:]))
def page_number(input_file: os.PathLike):
def page_number(input_file: os.PathLike) -> int:
"""Get one-based page number implied by filename (000002.pdf -> 2)"""
return int(os.path.basename(os.fspath(input_file))[0:6])
def available_cpu_count():
def available_cpu_count() -> int:
"""Returns number of CPUs in the system."""
try:
return multiprocessing.cpu_count()
except NotImplementedError:
@@ -136,7 +141,7 @@ def available_cpu_count():
return 1
def is_file_writable(test_file: os.PathLike):
def is_file_writable(test_file: os.PathLike) -> bool:
"""Intentionally racy test if target is writable.
We intend to write to the output file if and only if we succeed and
@@ -171,7 +176,11 @@ def is_file_writable(test_file: os.PathLike):
return False
def check_pdf(input_file):
def check_pdf(input_file: Path) -> bool:
"""Check if a PDF complies with the PDF specification.
Checks for proper formatting and proper linearization.
"""
pdf = None
try:
pdf = pikepdf.open(input_file)
@@ -205,6 +214,22 @@ def check_pdf(input_file):
pdf.close()
T = TypeVar('T')
def clamp(n: T, smallest: T, largest: T) -> T:
"""Clamps the value of n to between smallest and largest."""
return max(smallest, min(n, largest))
def pikepdf_enable_mmap():
try:
if pikepdf._qpdf.set_access_default_mmap(True):
log.debug("pikepdf mmap enabled")
except AttributeError:
log.debug("pikepdf mmap not available")
def deprecated(func):
"""Warn that function is deprecated"""
+55 -23
View File
@@ -32,10 +32,13 @@ import argparse
import os
import re
from collections import namedtuple
from itertools import chain
from math import atan, cos, sin
from pathlib import Path
from typing import Optional, Tuple, Union
from xml.etree import ElementTree
from reportlab.lib.colors import black, cyan, magenta, red
from reportlab.lib.units import inch
from reportlab.pdfgen.canvas import Canvas
@@ -66,9 +69,9 @@ class HocrTransform:
{'': 'ff', '': 'ffi', '': 'ffl', '': 'fi', '': 'fl'}
)
def __init__(self, hocr_filename: str, dpi: float):
def __init__(self, hocr_filename: Union[str, Path], dpi: float):
self.dpi = dpi
self.hocr = ElementTree.parse(hocr_filename)
self.hocr = ElementTree.parse(os.fspath(hocr_filename))
# if the hOCR file has a namespace, ElementTree requires its use to
# find elements
@@ -79,7 +82,7 @@ class HocrTransform:
# get dimension in pt (not pixel!!!!) of the OCRed image
self.width, self.height = None, None
for div in self.hocr.findall(".//%sdiv[@class='ocr_page']" % (self.xmlns)):
for div in self.hocr.findall(self._child_xpath('div', 'ocr_page')):
coords = self.element_coordinates(div)
pt_coords = self.pt_from_pixel(coords)
self.width = pt_coords.x2 - pt_coords.x1
@@ -96,7 +99,7 @@ class HocrTransform:
"""
if self.hocr is None:
return ''
body = self.hocr.find(".//%sbody" % (self.xmlns))
body = self.hocr.find(self._child_xpath('body'))
if body:
return self._get_element_text(body)
else:
@@ -109,7 +112,7 @@ class HocrTransform:
text = ''
if element.text is not None:
text += element.text
for child in element.getchildren():
for child in element:
text += self._get_element_text(child)
if element.tail is not None:
text += element.tail
@@ -130,7 +133,7 @@ class HocrTransform:
return out
@classmethod
def baseline(cls, element):
def baseline(cls, element) -> Tuple[float, float]:
"""
Returns a tuple containing the baseline slope and intercept.
"""
@@ -140,30 +143,41 @@ class HocrTransform:
return float(matches.group(1)), int(matches.group(2))
return (0.0, 0.0)
def pt_from_pixel(self, pxl):
def pt_from_pixel(self, pxl) -> Rect:
"""
Returns the quantity in PDF units (pt) given quantity in pixels
"""
return Rect._make((c / self.dpi * inch) for c in pxl)
def _child_xpath(self, html_tag, html_class=None):
xpath = f".//{self.xmlns}{html_tag}"
if html_class:
xpath += f"[@class='{html_class}']"
return xpath
@classmethod
def replace_unsupported_chars(cls, s: str):
def replace_unsupported_chars(cls, s: str) -> str:
"""
Given an input string, returns the corresponding string that:
- is available in the helvetica facetype
- does not contain any ligature (to allow easy search in the PDF file)
* is available in the Helvetica facetype
* does not contain any ligature (to allow easy search in the PDF file)
"""
return s.translate(cls.ligatures)
def topdown_position(self, element):
pxl_line_coords = self.element_coordinates(element)
line_box = self.pt_from_pixel(pxl_line_coords)
return -line_box.y2
def to_pdf(
self,
out_filename: Path,
image_filename: Path = None,
image_filename: Optional[Path] = None,
show_bounding_boxes: bool = False,
fontname: str = "Helvetica",
invisible_text: bool = False,
interword_spaces: bool = False,
):
) -> None:
"""
Creates a PDF file with an image superimposed on top of the text.
Text is positioned according to the bounding box of the lines in
@@ -171,6 +185,20 @@ class HocrTransform:
The image need not be identical to the image used to create the hOCR
file.
It can have a lower resolution, different color mode, etc.
Arguments:
out_filename: Path of PDF to write.
image_filename: Image to use for this file. If omitted, the OCR text
is shown.
show_bounding_boxes: Show bounding boxes around various text regions,
for debugging.
fontname: Name of font to use.
invisible_text: If True, text is rendered invisible so that is
selectable but never drawn. If False, text is visible and may
be seen if the image is skipped or deleted in Acrobat.
interword_spaces: If True, insert spaces between words rather than
drawing each word without spaces. Generally this improves text
extraction.
"""
# create the PDF file
# page size in points (1/72 in.)
@@ -182,12 +210,11 @@ class HocrTransform:
# draw bounding box for each paragraph
# light blue for bounding box of paragraph
pdf.setStrokeColorRGB(0, 1, 1)
pdf.setStrokeColor(cyan)
# light blue for bounding box of paragraph
pdf.setFillColorRGB(0, 1, 1)
pdf.setFillColor(cyan)
pdf.setLineWidth(0) # no line for bounding box
for elem in self.hocr.findall(".//%sp[@class='%s']" % (self.xmlns, "ocr_par")):
for elem in self.hocr.iterfind(self._child_xpath('p', 'ocr_par')):
elemtxt = self._get_element_text(elem).rstrip()
if len(elemtxt) == 0:
continue
@@ -202,8 +229,13 @@ class HocrTransform:
)
found_lines = False
for line in self.hocr.findall(
".//%sspan[@class='%s']" % (self.xmlns, "ocr_line")
for line in sorted(
chain(
self.hocr.iterfind(self._child_xpath('span', 'ocr_header')),
self.hocr.iterfind(self._child_xpath('span', 'ocr_line')),
self.hocr.iterfind(self._child_xpath('span', 'ocr_textfloat')),
),
key=self.topdown_position,
):
found_lines = True
self._do_line(
@@ -218,7 +250,7 @@ class HocrTransform:
if not found_lines:
# Tesseract did not report any lines (just words)
root = self.hocr.find(".//%sdiv[@class='%s']" % (self.xmlns, "ocr_page"))
root = self.hocr.find(self._child_xpath('div', 'ocr_page'))
self._do_line(
pdf,
root,
@@ -280,7 +312,7 @@ class HocrTransform:
if show_bounding_boxes: # pragma: no cover
# draw the baseline in magenta, dashed
pdf.setDash()
pdf.setStrokeColorRGB(0.95, 0.65, 0.95)
pdf.setStrokeColor(magenta)
pdf.setLineWidth(0.5)
# negate slope because it is defined as a rise/run in pixel
# coordinates and page coordinates have the y axis flipped
@@ -292,12 +324,12 @@ class HocrTransform:
)
# light green for bounding box of word/line
pdf.setDash(6, 3)
pdf.setStrokeColorRGB(1, 0, 0)
pdf.setStrokeColor(red)
text.setTextTransform(cos_a, -sin_a, sin_a, cos_a, line_box.x1, baseline_y2)
pdf.setFillColorRGB(0, 0, 0) # text in black
pdf.setFillColor(black) # text in black
elements = line.findall(".//%sspan[@class='%s']" % (self.xmlns, elemclass))
elements = line.findall(self._child_xpath('span', elemclass))
for elem in elements:
elemtxt = self._get_element_text(elem).strip()
elemtxt = self.replace_unsupported_chars(elemtxt)
+72 -5
View File
@@ -24,7 +24,9 @@ import argparse
import logging
import os
import sys
import threading
import warnings
from collections import deque
from collections.abc import Sequence
from contextlib import suppress
from ctypes.util import find_library
@@ -75,7 +77,7 @@ except ffi.error as e:
) from e
class _LeptonicaErrorTrap:
class _LeptonicaErrorTrap_Redirect:
"""
Context manager to trap errors reported by Leptonica.
@@ -90,19 +92,21 @@ class _LeptonicaErrorTrap:
"""
leptonica_lock = threading.Lock()
def __init__(self):
self.tmpfile = None
self.copy_of_stderr = -1
self.no_stderr = False
def __enter__(self):
self.tmpfile = TemporaryFile()
# Save the old stderr, and redirect stderr to temporary file
with suppress(AttributeError):
sys.stderr.flush()
self.leptonica_lock.acquire()
try:
with suppress(AttributeError):
sys.stderr.flush()
self.copy_of_stderr = os.dup(sys.stderr.fileno())
os.dup2(self.tmpfile.fileno(), sys.stderr.fileno(), inheritable=False)
except AttributeError:
@@ -114,7 +118,10 @@ class _LeptonicaErrorTrap:
os.dup2(self.tmpfile.fileno(), 2, inheritable=False)
except UnsupportedOperation:
self.copy_of_stderr = None
return
except Exception:
self.leptonica_lock.release()
raise
return self
def __exit__(self, exc_type, exc_value, traceback):
# Restore old stderr
@@ -131,6 +138,8 @@ class _LeptonicaErrorTrap:
self.tmpfile.seek(0) # Cursor will be at end, so move back to beginning
leptonica_output = self.tmpfile.read().decode(errors='replace')
self.tmpfile.close()
self.leptonica_lock.release()
# If there are Python errors, record them
if exc_type:
logger.warning(leptonica_output)
@@ -148,6 +157,62 @@ class _LeptonicaErrorTrap:
return False
tls = threading.local()
tls.trap = None
@ffi.callback("void(char *)")
def _stderr_handler(cstr):
msg = ffi.string(cstr).decode(errors='replace')
if msg.startswith("Error"):
logger.error(msg)
elif msg.startswith("Warning"):
logger.warning(msg)
else:
logger.debug(msg)
if tls.trap is not None:
tls.trap.append(msg)
return
class _LeptonicaErrorTrap_Queue:
def __init__(self):
self.queue = deque()
def __enter__(self):
self.queue.clear()
tls.trap = self.queue
def __exit__(self, exc_type, exc_value, traceback):
tls.trap = None
output = ''.join(self.queue)
self.queue.clear()
# If there are Python errors, record them
if exc_type:
logger.warning(output)
if 'Error' in output:
if 'image file not found' in output:
raise FileNotFoundError()
if 'pixWrite: stream not opened' in output:
raise LeptonicaIOError()
if 'index not valid' in output:
raise IndexError()
raise LeptonicaError(output)
return False
try:
lept.leptSetStderrHandler(_stderr_handler)
except ffi.error:
# Pre-1.79 Leptonica does not have leptSetStderrHandler
_LeptonicaErrorTrap = _LeptonicaErrorTrap_Redirect
else:
# 1.79 have this new symbol
_LeptonicaErrorTrap = _LeptonicaErrorTrap_Queue
class LeptonicaError(Exception):
pass
@@ -856,6 +921,8 @@ def get_leptonica_version():
Caveat: Leptonica expects the caller to free this memory. We don't,
since that would involve binding to libc to access libc.free(),
a pointless effort to reclaim 100 bytes of memory.
Reminder that this returns "leptonica-1.xx" or "leptonica-1.yy.0".
"""
return ffi.string(lept.getLeptonicaVersion()).decode()
File diff suppressed because one or more lines are too long
+2
View File
@@ -509,6 +509,8 @@ void selDestroy ( SEL **psel );
l_int32
setMsgSeverity(l_int32 newsev);
void
leptSetStderrHandler(void (*handler)(const char *));
"""
)
+95 -52
View File
@@ -22,9 +22,23 @@ from collections import defaultdict
from functools import partial
from os import fspath
from pathlib import Path
from typing import (
Any,
Callable,
Dict,
Iterator,
List,
MutableSet,
NamedTuple,
NewType,
Optional,
Sequence,
Tuple,
Union,
)
import pikepdf
from pikepdf import Dictionary, Name
from pikepdf import Dictionary, Name, Object, Pdf, PdfImage
from PIL import Image
from tqdm import tqdm
@@ -41,30 +55,40 @@ DEFAULT_JPEG_QUALITY = 75
DEFAULT_PNG_QUALITY = 70
def img_name(root, xref, ext):
return fspath(root / f'{xref:08d}{ext}')
Xref = NewType('Xref', int)
def png_name(root, xref):
class XrefExt(NamedTuple):
xref: Xref
ext: str
def img_name(root: Path, xref: Xref, ext: str) -> Path:
return root / f'{xref:08d}{ext}'
def png_name(root: Path, xref: Xref) -> Path:
return img_name(root, xref, '.png')
def jpg_name(root, xref):
def jpg_name(root: Path, xref: Xref) -> Path:
return img_name(root, xref, '.jpg')
def tif_name(root, xref):
def tif_name(root: Path, xref: Xref) -> Path:
return img_name(root, xref, '.tif')
def extract_image_filter(pike, root, image, xref):
def extract_image_filter(
pike: Pdf, root: Path, image: Object, xref: Xref
) -> Optional[Tuple[PdfImage, Tuple[Name, Object]]]:
if image.Subtype != Name.Image:
return None
if image.Length < 100:
log.debug("Skipping small image, xref %s", xref)
return None
pim = pikepdf.PdfImage(image)
pim = PdfImage(image)
if len(pim.filter_decodeparms) > 1:
log.debug("Skipping multiply filtered, xref %s", xref)
@@ -83,7 +107,9 @@ def extract_image_filter(pike, root, image, xref):
return pim, filtdp
def extract_image_jbig2(*, pike, root, image, xref, options):
def extract_image_jbig2(
*, pike: pikepdf.Pdf, root: Path, image: Object, xref: Xref, options
) -> Optional[XrefExt]:
result = extract_image_filter(pike, root, image, xref)
if result is None:
return None
@@ -91,21 +117,23 @@ def extract_image_jbig2(*, pike, root, image, xref, options):
if (
pim.bits_per_component == 1
and filtdp != Name.JBIG2Decode
and filtdp[0] != Name.JBIG2Decode
and jbig2enc.available()
):
try:
imgname = Path(root / f'{xref:08d}')
imgname = root / f'{xref:08d}'
with imgname.open('wb') as f:
ext = pim.extract_to(stream=f)
imgname.rename(imgname.with_suffix(ext))
except pikepdf.UnsupportedImageTypeError:
return None
return xref, ext
return XrefExt(xref, ext)
return None
def extract_image_generic(*, pike, root, image, xref, options):
def extract_image_generic(
*, pike: Pdf, root: Path, image: PdfImage, xref: Xref, options
) -> Optional[XrefExt]:
result = extract_image_filter(pike, root, image, xref)
if result is None:
return None
@@ -138,13 +166,13 @@ def extract_image_generic(*, pike, root, image, xref, options):
# with Image.open(stream) as im:
# im.save(jpg_name(root, xref), icc_profile=iccbytes)
try:
imgname = Path(root / f'{xref:08d}')
imgname = root / f'{xref:08d}'
with imgname.open('wb') as f:
ext = pim.extract_to(stream=f)
imgname.rename(imgname.with_suffix(ext))
except pikepdf.UnsupportedImageTypeError:
return None
return xref, ext
return XrefExt(xref, ext)
elif (
pim.indexed
and pim.colorspace in pim.SIMPLE_COLORSPACES
@@ -153,12 +181,12 @@ def extract_image_generic(*, pike, root, image, xref, options):
# Try to improve on indexed images - these are far from low hanging
# fruit in most cases
pim.as_pil_image().save(png_name(root, xref))
return xref, '.png'
return XrefExt(xref, '.png')
elif not pim.indexed and pim.colorspace in pim.SIMPLE_COLORSPACES:
# An optimization opportunity here, not currently taken, is directly
# generating a PNG from compressed data
pim.as_pil_image().save(png_name(root, xref))
return xref, '.png'
return XrefExt(xref, '.png')
elif (
not pim.indexed
and pim.colorspace == Name.ICCBased
@@ -169,12 +197,14 @@ def extract_image_generic(*, pike, root, image, xref, options):
# paying any attention to the ICC profile, provided we're not doing
# lossy JBIG2
pim.as_pil_image().save(png_name(root, xref))
return xref, '.png'
return XrefExt(xref, '.png')
return None
def extract_images(pike, root, options, extract_fn):
def extract_images(
pike: Pdf, root: Path, options, extract_fn: Callable[..., Optional[XrefExt]],
) -> Iterator[Tuple[int, XrefExt]]:
"""Extract image using extract_fn
Enumerate images on each page, lookup their xref/ID number in the PDF.
@@ -190,8 +220,8 @@ def extract_images(pike, root, options, extract_fn):
extension. extract_fn must also extract the file it finds interesting.
"""
include_xrefs = set()
exclude_xrefs = set()
include_xrefs: MutableSet[Xref] = set()
exclude_xrefs: MutableSet[Xref] = set()
pageno_for_xref = {}
errors = 0
for pageno, page in enumerate(pike.pages):
@@ -202,10 +232,10 @@ def extract_images(pike, root, options, extract_fn):
for _imname, image in dict(xobjs).items():
if image.objgen[1] != 0:
continue # Ignore images in an incremental PDF
xref = image.objgen[0]
xref = Xref(image.objgen[0])
if hasattr(image, 'SMask'):
# Ignore soft masks
smask_xref = image.SMask.objgen[0]
smask_xref = Xref(image.SMask.objgen[0])
exclude_xrefs.add(smask_xref)
include_xrefs.add(xref)
if xref not in pageno_for_xref:
@@ -224,31 +254,33 @@ def extract_images(pike, root, options, extract_fn):
else:
if result:
_, ext = result
yield pageno_for_xref[xref], xref, ext
yield pageno_for_xref[xref], XrefExt(xref, ext)
def extract_images_generic(pike, root, options):
def extract_images_generic(
pike: Pdf, root: Path, options
) -> Tuple[List[Xref], List[Xref]]:
"""Extract any >=2bpp image we think we can improve"""
jpegs = []
pngs = []
for _, xref, ext in extract_images(pike, root, options, extract_image_generic):
log.debug('xref = %s ext = %s', xref, ext)
if ext == '.png':
pngs.append(xref)
elif ext == '.jpg':
jpegs.append(xref)
for _, xref_ext in extract_images(pike, root, options, extract_image_generic):
log.debug('%s', xref_ext)
if xref_ext.ext == '.png':
pngs.append(xref_ext.xref)
elif xref_ext.ext == '.jpg':
jpegs.append(xref_ext.xref)
log.debug("Optimizable images: JPEGs: %s PNGs: %s", len(jpegs), len(pngs))
return jpegs, pngs
def extract_images_jbig2(pike, root, options):
def extract_images_jbig2(pike: Pdf, root: Path, options) -> Dict[int, List[XrefExt]]:
"""Extract any bitonal image that we think we can improve as JBIG2"""
jbig2_groups = defaultdict(list)
for pageno, xref, ext in extract_images(pike, root, options, extract_image_jbig2):
for pageno, xref_ext in extract_images(pike, root, options, extract_image_jbig2):
group = pageno // options.jbig2_page_group_size
jbig2_groups[group].append((xref, ext))
jbig2_groups[group].append(xref_ext)
# Elide empty groups
jbig2_groups = {
@@ -258,10 +290,12 @@ def extract_images_jbig2(pike, root, options):
return jbig2_groups
def _produce_jbig2_images(jbig2_groups, root, options):
def _produce_jbig2_images(
jbig2_groups: Dict[int, List[XrefExt]], root: Path, options
) -> None:
"""Produce JBIG2 images from their groups"""
def jbig2_group_args(root, groups):
def jbig2_group_args(root: Path, groups: Dict[int, List[XrefExt]]):
for group, xref_exts in groups.items():
prefix = f'group{group:08d}'
yield dict(
@@ -270,7 +304,7 @@ def _produce_jbig2_images(jbig2_groups, root, options):
out_prefix=prefix,
)
def jbig2_single_args(root, groups):
def jbig2_single_args(root, groups: Dict[int, List[XrefExt]]):
for group, xref_exts in groups.items():
prefix = f'group{group:08d}'
# Second loop is to ensure multiple images per page are unpacked
@@ -306,7 +340,9 @@ def _produce_jbig2_images(jbig2_groups, root, options):
)
def convert_to_jbig2(pike, jbig2_groups, root, options):
def convert_to_jbig2(
pike: Pdf, jbig2_groups: Dict[int, List[XrefExt]], root: Path, options
) -> None:
"""Convert images to JBIG2 and insert into PDF.
When the JBIG2 page group size is > 1 we do several JBIG2 images at once
@@ -344,19 +380,19 @@ def convert_to_jbig2(pike, jbig2_groups, root, options):
)
def transcode_jpegs(pike, jpegs, root, options):
def transcode_jpegs(pike: Pdf, jpegs: Sequence[Xref], root: Path, options) -> None:
for xref in tqdm(
jpegs, desc="JPEGs", unit='image', disable=not options.progress_bar
):
in_jpg = Path(jpg_name(root, xref))
in_jpg = jpg_name(root, xref)
opt_jpg = in_jpg.with_suffix('.opt.jpg')
# This produces a debug warning from PIL
# DEBUG:PIL.Image:Error closing: 'NoneType' object has no attribute
# 'close'. Seems to be mostly harmless
# https://github.com/python-pillow/Pillow/issues/1144
with Image.open(fspath(in_jpg)) as im:
im.save(fspath(opt_jpg), optimize=True, quality=options.jpeg_quality)
with Image.open(in_jpg) as im:
im.save(opt_jpg, optimize=True, quality=options.jpeg_quality)
if opt_jpg.stat().st_size > in_jpg.stat().st_size:
log.debug("xref %s, jpeg, made larger - skip", xref)
@@ -367,8 +403,14 @@ def transcode_jpegs(pike, jpegs, root, options):
im_obj.write(compdata.read(), filter=Name.DCTDecode)
def transcode_pngs(pike, images, image_name_fn, root, options):
modified = set()
def transcode_pngs(
pike: Pdf,
images: Sequence[Xref],
image_name_fn: Callable[[Path, Xref], Path],
root: Path,
options,
) -> None:
modified: MutableSet[Xref] = set()
if options.optimize >= 2:
png_quality = (
max(10, options.png_quality - 10),
@@ -431,7 +473,7 @@ def transcode_pngs(pike, images, image_name_fn, root, options):
rewrite_png_as_g4(pike, im_obj, compdata)
def rewrite_png_as_g4(pike, im_obj, compdata):
def rewrite_png_as_g4(pike: Pdf, im_obj: Object, compdata) -> None:
im_obj.BitsPerComponent = 1
im_obj.Width = compdata.w
im_obj.Height = compdata.h
@@ -451,7 +493,7 @@ def rewrite_png_as_g4(pike, im_obj, compdata):
return
def rewrite_png(pike, im_obj, compdata):
def rewrite_png(pike: Pdf, im_obj: Object, compdata) -> None:
# When a PNG is inserted into a PDF, we more or less copy the IDAT section from
# the PDF and transfer the rest of the PNG headers to PDF image metadata.
# One thing we have to do is tell the PDF reader whether a predictor was used
@@ -504,7 +546,7 @@ def rewrite_png(pike, im_obj, compdata):
im_obj.write(compdata.read(), filter=Name.FlateDecode, decode_parms=dparms)
def optimize(input_file, output_file, context, save_settings):
def optimize(input_file: Path, output_file: Path, context, save_settings) -> None:
options = context.options
if options.optimize == 0:
safe_symlink(input_file, output_file)
@@ -518,7 +560,7 @@ def optimize(input_file, output_file, context, save_settings):
options.jbig2_page_group_size = 10 if options.jbig2_lossy else 1
with pikepdf.Pdf.open(input_file) as pike:
root = Path(output_file).parent / 'images'
root = output_file.parent / 'images'
root.mkdir(exist_ok=True)
jpegs, pngs = extract_images_generic(pike, root, options)
@@ -531,12 +573,12 @@ def optimize(input_file, output_file, context, save_settings):
jbig2_groups = extract_images_jbig2(pike, root, options)
convert_to_jbig2(pike, jbig2_groups, root, options)
target_file = Path(output_file).with_suffix('.opt.pdf')
target_file = output_file.with_suffix('.opt.pdf')
pike.remove_unreferenced_resources()
pike.save(target_file, **save_settings)
input_size = Path(input_file).stat().st_size
output_size = Path(target_file).stat().st_size
input_size = input_file.stat().st_size
output_size = target_file.stat().st_size
if output_size == 0:
raise OutputFileAccessError(
f"Output file not created after optimizing. We probably ran "
@@ -557,8 +599,8 @@ def optimize(input_file, output_file, context, save_settings):
def main(infile, outfile, level, jobs=1):
from tempfile import TemporaryDirectory # pylint: disable=import-outside-toplevel
from shutil import copy # pylint: disable=import-outside-toplevel
from tempfile import TemporaryDirectory # pylint: disable=import-outside-toplevel
class OptimizeOptions:
"""Emulate ocrmypdf's options"""
@@ -576,6 +618,7 @@ def main(infile, outfile, level, jobs=1):
self.quiet = True
self.progress_bar = False
infile = Path(infile)
options = OptimizeOptions(
input_file=infile,
jobs=jobs,
+23 -26
View File
@@ -16,24 +16,13 @@
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
"""
Generate a PDFMARK file for Ghostscript >= 9.14, for PDF/A conversion
pdfmark is an extension to the Postscript language that describes some PDF
features like bookmarks and annotations. It was originally specified Adobe
Distiller, for Postscript to PDF conversion:
https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdfmark_reference.pdf
Ghostscript uses pdfmark for PDF to PDF/A conversion as well. To use Ghostscript
to create a PDF/A, we need to create a pdfmark file with the necessary metadata.
This takes care of the many version-specific bugs and pecularities in
Ghostscript's handling of pdfmark.
Utilities for PDF/A production and confirmation with Ghostspcript.
"""
import base64
from pathlib import Path
from string import Template
from typing import Dict, Union
import pikepdf
import pkg_resources
@@ -68,20 +57,28 @@ def
"""
def generate_pdfa_ps(target_filename, icc='sRGB'):
"""Create a Postscript pdfmark file for Ghostscript PDF/A conversion
def generate_pdfa_ps(target_filename: Path, icc: str = 'sRGB'):
"""Create a Postscript PDFMARK file for Ghostscript PDF/A conversion
A pdfmark file is a small Postscript program that provides some information
Ghostscript needs to perform PDF/A conversion. The only information we put
in specifies that we want the file to be a PDF/A, and we want to Ghostscript
to convert objects to the sRGB colorspace if it runs into any object that
it decides must be converted.
pdfmark is an extension to the Postscript language that describes some PDF
features like bookmarks and annotations. It was originally specified Adobe
Distiller, for Postscript to PDF conversion.
See the Adobe pdfmark Reference for details:
https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdfmark_reference.pdf
Ghostscript uses pdfmark for PDF to PDF/A conversion as well. To use Ghostscript
to create a PDF/A, we need to create a pdfmark file with the necessary metadata.
:param target_filename: filename to save
:param icc: ICC identifier such as 'sRGB'
This function takes care of the many version-specific bugs and pecularities in
Ghostscript's handling of pdfmark.
The only information we put in specifies that we want the file to be a
PDF/A, and we want to Ghostscript to convert objects to the sRGB colorspace
if it runs into any object that it decides must be converted.
Arguments:
target_filename: filename to save
icc: ICC identifier such as 'sRGB'
References:
Adobe PDFMARK Reference: https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdfmark_reference.pdf
"""
if icc == 'sRGB':
icc_profile = SRGB_ICC_PROFILE
@@ -102,7 +99,7 @@ def generate_pdfa_ps(target_filename, icc='sRGB'):
return target_filename
def file_claims_pdfa(filename):
def file_claims_pdfa(filename: Path):
"""Determines if the file claims to be PDF/A compliant
This only checks if the XMP metadata contains a PDF/A marker. It does not
@@ -119,7 +116,7 @@ def file_claims_pdfa(filename):
}
valid_part_conforms = {'1A', '1B', '2A', '2B', '2U', '3A', '3B', '3U'}
conformance = f'PDF/A-{pdfmeta.pdfa_status}'
pdfa_dict = {}
pdfa_dict: Dict[str, Union[str, bool]] = {}
if pdfmeta.pdfa_status in valid_part_conforms:
pdfa_dict['pass'] = True
pdfa_dict['output'] = 'pdfa'
+32 -28
View File
@@ -25,6 +25,7 @@ from functools import partial
from math import hypot, isclose
from os import PathLike
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
from warnings import warn
import pikepdf
@@ -32,7 +33,7 @@ from pikepdf import PdfMatrix
from ocrmypdf._concurrent import exec_progress_pool
from ocrmypdf.exceptions import EncryptedPdfError
from ocrmypdf.helpers import Resolution, available_cpu_count
from ocrmypdf.helpers import Resolution, available_cpu_count, pikepdf_enable_mmap
from ocrmypdf.pdfinfo.layout import get_page_analysis, get_text_boxes
logger = logging.getLogger()
@@ -90,7 +91,7 @@ UNIT_SQUARE = (1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
def _is_unit_square(shorthand):
values = map(float, shorthand)
pairwise = zip(values, UNIT_SQUARE)
return all([isclose(a, b, rel_tol=1e-3) for a, b in pairwise])
return all(isclose(a, b, rel_tol=1e-3) for a, b in pairwise)
XobjectSettings = namedtuple('XobjectSettings', ['name', 'shorthand', 'stack_depth'])
@@ -236,7 +237,7 @@ def _get_dpi(ctm_shorthand, image_size):
width-axis vector v0 (1, 0), height-axis vector vh (0, 1) with the matrix,
which gives the dimensions of the image in PDF units. From there we can
compare to actual image dimensions. PDF uses
row vector * matrix_tranposed unlike the traditional
row vector * matrix_transposed unlike the traditional
matrix * column vector.
The offset, width and height vectors can be combined in a matrix and
@@ -522,7 +523,7 @@ def _process_content_streams(*, pdf, container, shorthand=None):
yield from _find_form_xobject_images(pdf, container, contentsinfo)
def _page_has_text(text_blocks, page_width, page_height):
def _page_has_text(text_blocks, page_width, page_height) -> bool:
"""Smarter text detection that ignores text in margins"""
pw, ph = float(page_width), float(page_height)
@@ -566,9 +567,9 @@ def simplify_textboxes(miner, textbox_getter):
def _pdf_get_pageinfo(
pdf, pageno: int, infile: PathLike, check_pages, detailed_analysis
pdf, pageno: int, infile: PathLike, check_pages, detailed_analysis: bool
):
pageinfo = {}
pageinfo: Dict[str, Any] = {}
pageinfo['pageno'] = pageno
pageinfo['images'] = []
@@ -638,6 +639,7 @@ worker_pdf = None
def _pdf_pageinfo_sync_init(infile):
global worker_pdf # pylint: disable=global-statement
pikepdf_enable_mmap()
worker_pdf = pikepdf.open(infile)
@@ -695,41 +697,41 @@ class PageInfo:
)
@property
def pageno(self):
def pageno(self) -> int:
return self._pageno
@property
def has_text(self):
def has_text(self) -> bool:
return self._pageinfo['has_text']
@property
def has_corrupt_text(self):
def has_corrupt_text(self) -> bool:
if not self._detailed_analysis:
raise NotImplementedError('Did not do detailed analysis')
return any(tbox.is_corrupt for tbox in self._pageinfo['textboxes'])
@property
def has_vector(self):
def has_vector(self) -> bool:
return self._pageinfo['has_vector']
@property
def width_inches(self):
def width_inches(self) -> Decimal:
return self._pageinfo['width_inches']
@property
def height_inches(self):
def height_inches(self) -> Decimal:
return self._pageinfo['height_inches']
@property
def width_pixels(self):
def width_pixels(self) -> int:
return int(round(float(self.width_inches) * self.dpi.x))
@property
def height_pixels(self):
def height_pixels(self) -> int:
return int(round(float(self.height_inches) * self.dpi.y))
@property
def rotation(self):
def rotation(self) -> int:
return self._pageinfo.get('rotate', None)
@rotation.setter
@@ -743,7 +745,9 @@ class PageInfo:
def images(self):
return self._pageinfo['images']
def get_textareas(self, visible=None, corrupt=None):
def get_textareas(
self, visible: Optional[bool] = None, corrupt: Optional[bool] = None
):
def predicate(obj, want_visible, want_corrupt):
result = True
if want_visible is not None:
@@ -766,15 +770,15 @@ class PageInfo:
)
@property
def dpi(self):
def dpi(self) -> Resolution:
return self._pageinfo.get('dpi', Resolution(0.0, 0.0))
@property
def userunit(self):
def userunit(self) -> Decimal:
return self._pageinfo.get('userunit', None)
@property
def min_version(self):
def min_version(self) -> str:
if self.userunit is not None:
return '1.6'
else:
@@ -794,9 +798,9 @@ class PdfInfo:
def __init__(
self,
infile,
detailed_analysis=False,
progbar=False,
max_workers=None,
detailed_analysis: bool = False,
progbar: bool = False,
max_workers: int = None,
check_pages=None,
):
self._infile = infile
@@ -827,29 +831,29 @@ class PdfInfo:
return self._pages
@property
def min_version(self):
def min_version(self) -> str:
# The minimum PDF is the maximum version that any particular page needs
return max(page.min_version for page in self.pages)
@property
def has_userunit(self):
def has_userunit(self) -> bool:
return any(page.userunit != 1.0 for page in self.pages)
@property
def has_acroform(self):
def has_acroform(self) -> bool:
return self._has_acroform
@property
def filename(self):
def filename(self) -> Union[str, Path]:
if not isinstance(self._infile, (str, Path)):
raise NotImplementedError("can't get filename from stream")
return self._infile
@property
def needs_rendering(self):
def needs_rendering(self) -> bool:
return self._needs_rendering
def __getitem__(self, item):
def __getitem__(self, item) -> PageInfo:
return self._pages[item]
def __len__(self):
+9 -1
View File
@@ -220,8 +220,16 @@ class TextPositionTracker(PDFLayoutAnalyzer):
def get_page_analysis(infile, pageno, pscript5_mode):
rman = pdfminer.pdfinterp.PDFResourceManager(caching=True)
if pdfminer.__version__ < '20200402':
# Workaround for https://github.com/pdfminer/pdfminer.six/issues/395
disable_boxes_flow = 2
else:
disable_boxes_flow = None
dev = TextPositionTracker(
rman, laparams=LAParams(all_texts=True, detect_vertical=True)
rman,
laparams=LAParams(
all_texts=True, detect_vertical=True, boxes_flow=disable_boxes_flow
),
)
interp = pdfminer.pdfinterp.PDFPageInterpreter(rman, dev)
+151 -43
View File
@@ -19,13 +19,18 @@ from abc import ABC, abstractmethod, abstractstaticmethod
from argparse import ArgumentParser, Namespace
from collections import namedtuple
from pathlib import Path
from typing import AbstractSet, List, Optional
from typing import TYPE_CHECKING, AbstractSet, List, Optional
import pluggy
from PIL import Image
from ocrmypdf.helpers import Resolution
if TYPE_CHECKING:
from PIL import Image
from ocrmypdf._jobcontext import PageContext
from ocrmypdf.pdfinfo import PdfInfo
hookspec = pluggy.HookspecMarker('ocrmypdf')
# pylint: disable=unused-argument
@@ -33,19 +38,37 @@ hookspec = pluggy.HookspecMarker('ocrmypdf')
@hookspec
def add_options(parser: ArgumentParser) -> None:
"""Allows the plugin to add its own command line arguments.
"""Allows the plugin to add its own command line and API arguments.
Even if you do not intend to use plugins in a command line context, you
should use this function to create your options.
OCRmyPDF converts command line arguments to API arguments, so adding
arguments here will cause new arguments to be processed for API calls
to ``ocrmypdf.ocr``, or when invoked on the command line.
Note:
This hook will be called from the main process, and may modify global state
before child worker processes are forked.
"""
@hookspec
def check_options(options: Namespace) -> None:
"""Called to ask the plugin to check all of its options.
"""Called to ask the plugin to check all of the options.
The plugin may modify the *options*. All objects that are in options must
be picklable so they can be marshalled to child worker processes.
The plugin may check if options that it added are valid.
Warnings or other messages may be passed to the user by creating a logger
object using ``log = logging.getLogger(__name__)`` and logging to this.
The plugin may also modify the *options*. All objects that are in options
must be picklable so they can be marshalled to child worker processes.
Raises:
ocrmypdf.exceptions.ExitCodeException: If options are not acceptable
and the application should terminate gracefully with an informative
message and error code.
Note:
This hook will be called from the main process, and may modify global state
before child worker processes are forked.
"""
@@ -59,12 +82,13 @@ def validate(pdfinfo: 'PdfInfo', options: Namespace) -> None:
that a certain type of file should be treated with ``options.force_ocr = True``
based on information in its *pdfinfo*.
The plugin may raise :class:`ocrmypdf.exceptions.InputFileError` or any
:class:`ocrmypdf.exceptions.ExitCodeException` to request
normal termination. ocrmypdf will hold the plugin responsible for raising
exceptions of any other type.
The return value is ignored. To abort processing, raise an ``ExitCodeException``.
Raises:
ocrmypdf.exceptions.ExitCodeException: If options or pdfinfo are not acceptable
and the application should terminate gracefully with an informative
message and error code.
Note:
This hook will be called from the main process, and may modify global state
before child worker processes are forked.
"""
@@ -86,23 +110,48 @@ def rasterize_pdf_page(
be overridden with the values in page_dpi.
Args:
raster_device: type of image to produce at output_file
raster_dpi: resolution at which to rasterize page
pageno: page number to rasterize (beginning at page 1)
page_dpi: resolution, overriding output image DPI
rotation: cardinal angle, clockwise, to rotate page
filter_vector: if True, remove vector graphics objects
input_file: The PDF to rasterize.
output_file: The desired name of the rasterized image.
raster_device: Type of image to produce at output_file
raster_dpi: Resolution at which to rasterize page
pageno: Page number to rasterize (beginning at page 1)
page_dpi: Resolution, overriding output image DPI
rotation: Cardinal angle, clockwise, to rotate page
filter_vector: If True, remove vector graphics objects
Returns:
output_file
Path: output_file if successful
Note:
This hook will be called from child processes. Modifying global state
will not affect the main process or other child processes.
Note:
This is a :ref:`firstresult hook<firstresult>`.
"""
@hookspec(firstresult=True)
def filter_ocr_image(page: 'PageContext', image: Image) -> Image:
def filter_ocr_image(page: 'PageContext', image: 'Image') -> 'Image':
"""Called to filter the image before it is sent to OCR.
This is the image that OCR sees, not what the user sees when they view the
PDF.
PDF. If ``redo_ocr`` is enabled, portions of the image will be masked so
they are not shown to OCR. The main use of this hook is expected to be hiding
content from OCR.
The input image may be color, grayscale, or monochrome, and the
output image may differ. The pixel width and height of the
output image must be identical to the input image, or misalignment between
the OCR text layer and visual position of the text will occur. Likewise,
the output must be a faithful representation of the input, or alignment
errors may occurs.
Tesseract OCR only deals with monochrome images, and internally converts
non-monochrome images to OCR.
Note:
This hook will be called from child processes. Modifying global state
will not affect the main process or other child processes.
Note:
This is a :ref:`firstresult hook<firstresult>`.
"""
@@ -119,28 +168,63 @@ def filter_page_image(page: 'PageContext', image_filename: Path) -> Path:
convert the image to a JPEG, the output page will be created as a JPEG, etc.
Note that the ocrmypdf image optimization stage may ultimately chose a
different format.
Note:
This hook will be called from child processes. Modifying global state
will not affect the main process or other child processes.
Note:
This is a :ref:`firstresult hook<firstresult>`.
"""
OrientationConfidence = namedtuple('OrientationConfidence', ('angle', 'confidence'))
"""Expresses an OCR engine's confidence in page rotation.
Attributes:
angle (int): The clockwise angle (0, 90, 180, 270) that the page should be
rotated. 0 means no rotation.
confidence (float): How confident the OCR engine is that this the correct
rotation. 0 is not confident, 15 is very confident. Arbitrary units.
"""
class OcrEngine(ABC):
"""A class representing an OCR engine with capabilities similar to Tesseract OCR.
This could be used to create a plugin for another OCR engine instead of
Tesseract OCR.
"""
@abstractstaticmethod
def version() -> str:
"""Returns the version of the OCR engine."""
@abstractstaticmethod
def creator_tag(options: Namespace) -> str:
"""Returns the creator tag to identify this software's role in creating the PDF."""
"""Returns the creator tag to identify this software's role in creating the PDF.
This tag will be inserted in the XMP metadata and DocumentInfo dictionary
as appropriate. Ideally you should include the name of the OCR engine and its
version. The text should not contain line breaks. This is to help developers
like yourself identify the software that produced this file.
OCRmyPDF will always prepend its name to this value.
"""
@abstractmethod
def __str__(self):
"""Returns name of OCR engine and version."""
"""Returns name of OCR engine and version.
This is used when OCRmyPDF wants to mention the name of the OCR engine
to the user, usually in an error message.
"""
@abstractstaticmethod
def languages(options: Namespace) -> AbstractSet[str]:
"""Returns set of languages that are supported."""
"""Returns the set of all languages that are supported by the engine.
Languages are typically given in 3-letter ISO 3166-1 codes, but actually
can be any value understood by the OCR engine."""
@abstractstaticmethod
def get_orientation(input_file: Path, options: Namespace) -> OrientationConfidence:
@@ -150,18 +234,34 @@ class OcrEngine(ABC):
def generate_hocr(
input_file: Path, output_hocr: Path, output_text: Path, options: Namespace
) -> None:
"""Called to produce a hOCR file."""
"""Called to produce a hOCR file and sidecar text file."""
@abstractstaticmethod
def generate_pdf(
input_file: Path, output_pdf: Path, output_text: Path, options: Namespace
) -> None:
"""Called to produce a text only PDF (no image, invisible text)."""
"""Called to produce a text only PDF.
Args:
input_file: A page image on which to perform OCR.
output_pdf: The expected name of the output PDF, which must be
a single page PDF with no visible content of any kind, sized
to the dimensions implied by the input_file's width, height
and DPI. The image will be grafted onto the input PDF page.
"""
@hookspec(firstresult=True)
def get_ocr_engine() -> OcrEngine:
pass
"""Returns an OcrEngine to use for processing this file.
The OcrEngine may be instantiated multiple times, by both the main process
and child process. As such, it must be obtain store any state in ``options``
or some common location.
Note:
This is a :ref:`firstresult hook<firstresult>`.
"""
@hookspec(firstresult=True)
@@ -175,21 +275,29 @@ def generate_pdfa(
) -> Path:
"""Generate a PDF/A.
The pdf_pages, a list of files, will be merged into output_file. One or more
PDF files may be merged. The pdfmark file is a PostScript.ps file that
provides Ghostscript with details on how to perform the PDF/A
conversion. By default with we pick PDF/A-2b, but this works for 1 or 3.
This API strongly assumes a PDF/A generator with Ghostscript's semantics.
compression can be 'jpeg', 'lossless', or an empty string. In 'jpeg',
Ghostscript is instructed to convert color and grayscale images to DCT
(JPEG encoding). In 'lossless' Ghostscript is told to convert images to
Flate (lossless/PNG). If the parameter is omitted Ghostscript is left to
make its own decisions about how to encode images; it appears to use a
heuristic to decide how to encode images. As of Ghostscript 9.25, we
support passthrough JPEG which allows Ghostscript to avoid transcoding
images entirely. (The feature was added in 9.23 but broken, and the 9.24
release of Ghostscript had regressions, so we don't support it until 9.25.)
OCRmyPDF will modify the metadata and possibly linearize the PDF/A after it
is generated.
Arguments:
pdf_pages: A list of one or more filenames, will be merged into output_file.
pdfmark: A PostScript file intended for Ghostscript with details on
how to perform the PDF/A conversion.
output_file: The name of the desired output file.
compression: One of ``'jpeg'``, ``'lossless'``, ``''``. For ``'jpeg'``,
the PDF/A generator should convert all images to JPEG encoding where
possible. For lossless, all images should be converted to FlateEncode
(lossless PNG). If an empty string, the PDF generator should make its
own decisions about how to encode images.
pdf_version: The minimum PDF version that the output file should be.
At its own discretion, the PDF/A generator may raise the version,
but should not lower it.
pdfa_part: The desired PDF/A compliance level, such as ``'2B'``.
Returns:
output_file
Path: If successful, the hook should return ``output_file``.
Note:
This is a :ref:`firstresult hook<firstresult>`.
"""
+5 -5
View File
@@ -15,23 +15,23 @@
# You should have received a copy of the GNU General Public License
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
"""Utilities to measure OCR quality"""
import re
from typing import Iterable
"""Utilities to measure OCR quality"""
class OcrQualityDictionary:
"""Manages a dictionary for simple OCR quality checks."""
def __init__(self, *, wordlist: Iterable[str] = []):
def __init__(self, *, wordlist: Iterable[str]):
"""Construct a dictionary from a list of words.
Words for which capitalization is important should be capitalized in the
dictionary. Words that contain spaces or other punctuation will never match.
"""
self.dictionary = set()
self.dictionary.update(w for w in wordlist)
self.dictionary = set(wordlist)
def measure_words_matched(self, ocr_text: str) -> float:
"""Check how many unique words in the OCR text match a dictionary.
+17 -13
View File
@@ -36,18 +36,12 @@ log = logging.getLogger(__name__)
def run(args, *, env=None, **kwargs):
"""Wrapper around subprocess.run()
The main purpose of this wrapper is to log subprocess output.
Secondly we have to account for behavioral differences in Windows in particular.
Creating symbolic links in Windows requires administrator privileges and
may not work if for some reason we're using a FAT file system or the temporary
folder is on a different drive from the working folder. The test suite
works around this by creating shim Python scripts that perform the same function
as a symbolic link, but those shims require support on this side, to ensure
we call them with Python.
"""Wrapper around :py:func:`subprocess.run`
The main purpose of this wrapper is to log subprocess output in an orderly
fashion that indentifies the responsible subprocess. An additional
task is that this function goes to greater lengths to find possible Windows
locations of our dependencies when they are not on the system PATH.
"""
if not env:
env = os.environ
@@ -106,8 +100,18 @@ def _fix_windows_args(program, args, env):
@lru_cache(maxsize=None)
def get_version(program, *, version_arg='--version', regex=r'(\d+(\.\d+)*)', env=None):
"""Get the version of the specified program"""
def get_version(
program: str, *, version_arg: str = '--version', regex=r'(\d+(\.\d+)*)', env=None
):
"""Get the version of the specified program
Arguments:
program: The program to version check.
version_arg: The argument needed to ask for its version, e.g. ``--version``.
regex: A regular expression to parse the program's output and obtain the
version.
env: Custom ``os.environ`` in which to run program.
"""
args_prog = [program, version_arg]
try:
proc = run(
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.1.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.1.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.1.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -4,12 +4,12 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name='ocr-system' content='tesseract 4.0.0' />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<meta name='ocr-system' content='tesseract 4.1.1' />
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word ocrp_wconf'/>
</head>
<body>
<div class='ocr_page' id='page_1' title='image "/var/folders/2s/7t022mgj0h5cprbq0dtb1ksm0000gn/T/com.github.ocrmypdf.xam82ph5/000001_ocr.png"; bbox 0 0 1000 800; ppageno 0'>
</head>
<body>
<div class='ocr_page' id='page_1' title='image "/var/folders/2s/7t022mgj0h5cprbq0dtb1ksm0000gn/T/com.github.ocrmypdf.mrqsewbu/000001_ocr.png"; bbox 0 0 1000 800; ppageno 0'>
<div class='ocr_carea' id='block_1_1' title="bbox 296 96 704 504">
<p class='ocr_par' id='par_1_1' lang='eng' title="bbox 296 96 704 504">
<span class='ocr_line' id='line_1_1' title="bbox 296 96 704 504; baseline 0 296; x_size 169.33333; x_descenders 42.333332; x_ascenders 42.333336">
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -4,19 +4,19 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name='ocr-system' content='tesseract 4.0.0' />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<meta name='ocr-system' content='tesseract 4.1.1' />
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word ocrp_wconf'/>
</head>
<body>
<div class='ocr_page' id='page_1' title='image "/var/folders/2s/7t022mgj0h5cprbq0dtb1ksm0000gn/T/com.github.ocrmypdf.6a60f5yy/000001_ocr.png"; bbox 0 0 2550 3300; ppageno 0'>
</head>
<body>
<div class='ocr_page' id='page_1' title='image "/var/folders/2s/7t022mgj0h5cprbq0dtb1ksm0000gn/T/com.github.ocrmypdf.ces85e5u/000001_ocr.png"; bbox 0 0 2550 3300; ppageno 0'>
<div class='ocr_carea' id='block_1_1' title="bbox 582 131 1968 303">
<p class='ocr_par' id='par_1_1' lang='eng' title="bbox 582 131 1968 303">
<span class='ocr_line' id='line_1_1' title="bbox 882 131 1657 217; baseline 0.001 -17; x_size 85; x_descenders 16; x_ascenders 19">
<span class='ocr_header' id='line_1_1' title="bbox 882 131 1657 217; baseline 0.001 -17; x_size 85; x_descenders 16; x_ascenders 19">
<span class='ocrx_word' id='word_1_1' title='bbox 882 132 1036 202; x_wconf 93'>The</span>
<span class='ocrx_word' id='word_1_2' title='bbox 1061 131 1657 217; x_wconf 91'>LinnSequencer</span>
</span>
<span class='ocr_line' id='line_1_2' title="bbox 582 215 1968 303; baseline 0 -17; x_size 87; x_descenders 16; x_ascenders 21">
<span class='ocr_header' id='line_1_2' title="bbox 582 215 1968 303; baseline 0 -17; x_size 87; x_descenders 16; x_ascenders 21">
<span class='ocrx_word' id='word_1_3' title='bbox 582 215 674 286; x_wconf 96'>32</span>
<span class='ocrx_word' id='word_1_4' title='bbox 697 218 923 288; x_wconf 95'>Track</span>
<span class='ocrx_word' id='word_1_5' title='bbox 948 218 1181 287; x_wconf 96'>MIDI</span>
@@ -27,27 +27,27 @@
</div>
<div class='ocr_carea' id='block_1_2' title="bbox 347 380 2188 574">
<p class='ocr_par' id='par_1_2' lang='eng' title="bbox 347 380 2188 423">
<span class='ocr_line' id='line_1_3' title="bbox 347 380 2188 423; baseline -0.001 -12; x_size 38; x_descenders 8; x_ascenders 10">
<span class='ocr_header' id='line_1_3' title="bbox 347 380 2188 423; baseline -0.001 -12; x_size 38; x_descenders 8; x_ascenders 10">
<span class='ocrx_word' id='word_1_8' title='bbox 347 380 412 410; x_wconf 93'>The</span>
<span class='ocrx_word' id='word_1_9' title='bbox 424 380 661 417; x_wconf 92'>LinnSequencer</span>
<span class='ocrx_word' id='word_1_10' title='bbox 663 380 712 411; x_wconf 96'>is</span>
<span class='ocrx_word' id='word_1_9' title='bbox 424 380 676 417; x_wconf 92'>LinnSequencer</span>
<span class='ocrx_word' id='word_1_10' title='bbox 688 380 712 411; x_wconf 96'>is</span>
<span class='ocrx_word' id='word_1_11' title='bbox 724 390 743 411; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_12' title='bbox 754 381 1005 423; x_wconf 96'>state-of-the-art</span>
<span class='ocrx_word' id='word_1_13' title='bbox 1017 380 1226 418; x_wconf 96'>composition</span>
<span class='ocrx_word' id='word_1_14' title='bbox 1238 381 1299 411; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_15' title='bbox 1311 380 1507 418; x_wconf 96'>performance</span>
<span class='ocrx_word' id='word_1_16' title='bbox 1509 385 1591 411; x_wconf 96'>tool</span>
<span class='ocrx_word' id='word_1_17' title='bbox 1593 380 1663 411; x_wconf 97'>for</span>
<span class='ocrx_word' id='word_1_15' title='bbox 1311 380 1525 418; x_wconf 96'>performance</span>
<span class='ocrx_word' id='word_1_16' title='bbox 1536 380 1602 411; x_wconf 96'>tool</span>
<span class='ocrx_word' id='word_1_17' title='bbox 1615 380 1663 411; x_wconf 97'>for</span>
<span class='ocrx_word' id='word_1_18' title='bbox 1674 381 1725 410; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_19' title='bbox 1737 380 1940 417; x_wconf 95'>professional</span>
<span class='ocrx_word' id='word_1_20' title='bbox 1952 380 2103 411; x_wconf 96'>musician.</span>
<span class='ocrx_word' id='word_1_21' title='bbox 2106 381 2152 410; x_wconf 96'>It</span>
<span class='ocrx_word' id='word_1_20' title='bbox 1952 380 2112 411; x_wconf 96'>musician.</span>
<span class='ocrx_word' id='word_1_21' title='bbox 2127 381 2152 410; x_wconf 96'>It</span>
<span class='ocrx_word' id='word_1_22' title='bbox 2164 380 2188 410; x_wconf 96'>is</span>
</span>
</p>
<p class='ocr_par' id='par_1_3' lang='eng' title="bbox 347 430 1988 468">
<span class='ocr_line' id='line_1_4' title="bbox 347 430 1988 468; baseline 0 -8; x_size 37; x_descenders 7; x_ascenders 9">
<span class='ocr_header' id='line_1_4' title="bbox 347 430 1988 468; baseline 0 -8; x_size 37; x_descenders 7; x_ascenders 9">
<span class='ocrx_word' id='word_1_23' title='bbox 347 430 507 467; x_wconf 96'>extremely</span>
<span class='ocrx_word' id='word_1_24' title='bbox 518 430 677 467; x_wconf 96'>powerful,</span>
<span class='ocrx_word' id='word_1_25' title='bbox 691 435 739 467; x_wconf 96'>yet</span>
@@ -66,7 +66,7 @@
</p>
<p class='ocr_par' id='par_1_4' lang='eng' title="bbox 350 482 2093 574">
<span class='ocr_line' id='line_1_5' title="bbox 350 482 2093 527; baseline 0 -9; x_size 43; x_descenders 7; x_ascenders 11">
<span class='ocr_header' id='line_1_5' title="bbox 350 482 2093 527; baseline 0 -9; x_size 43; x_descenders 7; x_ascenders 11">
<span class='ocrx_word' id='word_1_37' title='bbox 350 490 368 508; x_wconf 73'>¢</span>
<span class='ocrx_word' id='word_1_38' title='bbox 383 482 585 526; x_wconf 95'>Operation</span>
<span class='ocrx_word' id='word_1_39' title='bbox 598 482 627 518; x_wconf 96'>is</span>
@@ -81,18 +81,18 @@
<span class='ocrx_word' id='word_1_48' title='bbox 1741 483 1957 525; x_wconf 96'>RECORD,</span>
<span class='ocrx_word' id='word_1_49' title='bbox 1974 483 2093 518; x_wconf 96'>FAST</span>
</span>
<span class='ocr_line' id='line_1_6' title="bbox 383 532 1345 574; baseline 0.001 -7; x_size 43; x_descenders 7; x_ascenders 11">
<span class='ocr_header' id='line_1_6' title="bbox 383 532 1345 574; baseline 0.001 -7; x_size 43; x_descenders 7; x_ascenders 11">
<span class='ocrx_word' id='word_1_50' title='bbox 383 532 635 574; x_wconf 96'>FORWARD,</span>
<span class='ocrx_word' id='word_1_51' title='bbox 652 532 865 574; x_wconf 95'>REWIND,</span>
<span class='ocrx_word' id='word_1_52' title='bbox 882 532 956 568; x_wconf 95'>and</span>
<span class='ocrx_word' id='word_1_53' title='bbox 971 532 1131 568; x_wconf 95'>LOCATE</span>
<span class='ocrx_word' id='word_1_54' title='bbox 1132 532 1345 568; x_wconf 95'>controls.</span>
<span class='ocrx_word' id='word_1_53' title='bbox 971 532 1163 568; x_wconf 95'>LOCATE</span>
<span class='ocrx_word' id='word_1_54' title='bbox 1177 532 1345 568; x_wconf 95'>controls.</span>
</span>
</p>
</div>
<div class='ocr_carea' id='block_1_3' title="bbox 349 589 2136 685">
<p class='ocr_par' id='par_1_5' lang='eng' title="bbox 349 589 2136 685">
<span class='ocr_line' id='line_1_7' title="bbox 349 589 2136 634; baseline 0.001 -9; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocr_header' id='line_1_7' title="bbox 349 589 2136 634; baseline 0.001 -9; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocrx_word' id='word_1_55' title='bbox 349 597 368 615; x_wconf 59'>e</span>
<span class='ocrx_word' id='word_1_56' title='bbox 383 590 482 625; x_wconf 96'>Each</span>
<span class='ocrx_word' id='word_1_57' title='bbox 496 589 539 625; x_wconf 96'>of</span>
@@ -108,7 +108,7 @@
<span class='ocrx_word' id='word_1_67' title='bbox 1934 590 2035 626; x_wconf 96'>track</span>
<span class='ocrx_word' id='word_1_68' title='bbox 2050 600 2136 634; x_wconf 96'>may</span>
</span>
<span class='ocr_line' id='line_1_8' title="bbox 383 639 2022 685; baseline 0.001 -10; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocr_header' id='line_1_8' title="bbox 383 639 2022 685; baseline 0.001 -10; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocrx_word' id='word_1_69' title='bbox 383 639 428 675; x_wconf 95'>be</span>
<span class='ocrx_word' id='word_1_70' title='bbox 442 639 607 684; x_wconf 95'>assigned</span>
<span class='ocrx_word' id='word_1_71' title='bbox 621 645 659 676; x_wconf 96'>to</span>
@@ -135,7 +135,7 @@
</div>
<div class='ocr_carea' id='block_1_5' title="bbox 349 748 2117 793">
<p class='ocr_par' id='par_1_7' lang='eng' title="bbox 349 748 2117 793">
<span class='ocr_line' id='line_1_10' title="bbox 349 748 2117 793; baseline 0 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocr_header' id='line_1_10' title="bbox 349 748 2117 793; baseline 0 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocrx_word' id='word_1_84' title='bbox 349 755 367 774; x_wconf 58'>¢</span>
<span class='ocrx_word' id='word_1_85' title='bbox 383 748 573 784; x_wconf 91'>Ultra-fast</span>
<span class='ocrx_word' id='word_1_86' title='bbox 588 749 677 784; x_wconf 22'>3%”</span>
@@ -147,8 +147,8 @@
<span class='ocrx_word' id='word_1_92' title='bbox 1330 748 1368 784; x_wconf 96'>in</span>
<span class='ocrx_word' id='word_1_93' title='bbox 1382 748 1535 785; x_wconf 96'>seconds</span>
<span class='ocrx_word' id='word_1_94' title='bbox 1550 748 1624 784; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_95' title='bbox 1638 748 1728 784; x_wconf 96'>holds</span>
<span class='ocrx_word' id='word_1_96' title='bbox 1730 759 1844 784; x_wconf 96'>over</span>
<span class='ocrx_word' id='word_1_95' title='bbox 1638 748 1746 784; x_wconf 96'>holds</span>
<span class='ocrx_word' id='word_1_96' title='bbox 1761 759 1844 784; x_wconf 96'>over</span>
<span class='ocrx_word' id='word_1_97' title='bbox 1859 749 2000 791; x_wconf 96'>110,000</span>
<span class='ocrx_word' id='word_1_98' title='bbox 2013 753 2117 784; x_wconf 96'>notes</span>
</span>
@@ -164,23 +164,23 @@
</div>
<div class='ocr_carea' id='block_1_7' title="bbox 349 855 2030 1016">
<p class='ocr_par' id='par_1_9' lang='eng' title="bbox 349 855 2030 1016">
<span class='ocr_line' id='line_1_12' title="bbox 350 855 1638 900; baseline 0.001 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocr_header' id='line_1_12' title="bbox 350 855 1638 900; baseline 0.001 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocrx_word' id='word_1_101' title='bbox 350 863 367 881; x_wconf 45'>¢</span>
<span class='ocrx_word' id='word_1_102' title='bbox 383 856 444 891; x_wconf 95'>One</span>
<span class='ocrx_word' id='word_1_103' title='bbox 445 866 502 891; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_104' title='bbox 503 855 580 891; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_102' title='bbox 383 856 464 891; x_wconf 95'>One</span>
<span class='ocrx_word' id='word_1_103' title='bbox 478 866 520 891; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_104' title='bbox 534 855 580 891; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_105' title='bbox 594 856 712 892; x_wconf 95'>tracks</span>
<span class='ocrx_word' id='word_1_106' title='bbox 726 867 811 900; x_wconf 95'>may</span>
<span class='ocrx_word' id='word_1_107' title='bbox 823 856 869 892; x_wconf 81'>be</span>
<span class='ocrx_word' id='word_1_108' title='bbox 882 856 1175 892; x_wconf 96'>TRANSPOSED</span>
<span class='ocrx_word' id='word_1_109' title='bbox 1178 857 1264 892; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_108' title='bbox 882 856 1212 892; x_wconf 96'>TRANSPOSED</span>
<span class='ocrx_word' id='word_1_109' title='bbox 1227 861 1264 892; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_110' title='bbox 1277 856 1338 892; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_111' title='bbox 1351 856 1463 892; x_wconf 96'>touch</span>
<span class='ocrx_word' id='word_1_112' title='bbox 1477 867 1501 892; x_wconf 96'>of</span>
<span class='ocrx_word' id='word_1_113' title='bbox 1502 856 1554 892; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_112' title='bbox 1477 856 1520 892; x_wconf 96'>of</span>
<span class='ocrx_word' id='word_1_113' title='bbox 1531 867 1554 892; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_114' title='bbox 1568 856 1638 900; x_wconf 96'>key.</span>
</span>
<span class='ocr_line' id='line_1_13' title="bbox 350 913 1535 958; baseline 0.001 -9; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocr_header' id='line_1_13' title="bbox 350 913 1535 958; baseline 0.001 -9; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocrx_word' id='word_1_115' title='bbox 350 921 367 939; x_wconf 45'>e</span>
<span class='ocrx_word' id='word_1_116' title='bbox 383 913 568 950; x_wconf 96'>Exclusive</span>
<span class='ocrx_word' id='word_1_117' title='bbox 581 913 756 950; x_wconf 96'>real-time</span>
@@ -190,7 +190,7 @@
<span class='ocrx_word' id='word_1_121' title='bbox 1266 914 1400 958; x_wconf 96'>editing</span>
<span class='ocrx_word' id='word_1_122' title='bbox 1414 915 1535 950; x_wconf 95'>FAST.</span>
</span>
<span class='ocr_line' id='line_1_14' title="bbox 349 971 2030 1016; baseline 0.001 -10; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocr_header' id='line_1_14' title="bbox 349 971 2030 1016; baseline 0.001 -10; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocrx_word' id='word_1_123' title='bbox 349 979 367 997; x_wconf 0'>*</span>
<span class='ocrx_word' id='word_1_124' title='bbox 382 971 568 1007; x_wconf 95'>Exclusive</span>
<span class='ocrx_word' id='word_1_125' title='bbox 582 972 773 1007; x_wconf 96'>REPEAT</span>
@@ -216,11 +216,11 @@
</div>
<div class='ocr_carea' id='block_1_9' title="bbox 349 1080 2174 1125">
<p class='ocr_par' id='par_1_11' lang='eng' title="bbox 349 1080 2174 1125">
<span class='ocr_line' id='line_1_16' title="bbox 349 1080 2174 1125; baseline 0.001 -11; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocr_header' id='line_1_16' title="bbox 349 1080 2174 1125; baseline 0.001 -11; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocrx_word' id='word_1_137' title='bbox 349 1087 367 1105; x_wconf 80'>¢</span>
<span class='ocrx_word' id='word_1_138' title='bbox 382 1080 567 1115; x_wconf 95'>TIMING</span>
<span class='ocrx_word' id='word_1_139' title='bbox 582 1080 870 1116; x_wconf 95'>CORRECTION</span>
<span class='ocrx_word' id='word_1_140' title='bbox 872 1080 1041 1116; x_wconf 96'>works</span>
<span class='ocrx_word' id='word_1_139' title='bbox 582 1080 908 1116; x_wconf 95'>CORRECTION</span>
<span class='ocrx_word' id='word_1_140' title='bbox 921 1080 1041 1116; x_wconf 96'>works</span>
<span class='ocrx_word' id='word_1_141' title='bbox 1056 1080 1186 1124; x_wconf 96'>during</span>
<span class='ocrx_word' id='word_1_142' title='bbox 1199 1080 1378 1124; x_wconf 96'>playback</span>
<span class='ocrx_word' id='word_1_143' title='bbox 1392 1080 1466 1116; x_wconf 96'>and</span>
@@ -233,11 +233,11 @@
</div>
<div class='ocr_carea' id='block_1_10' title="bbox 349 1137 1287 1182">
<p class='ocr_par' id='par_1_12' lang='eng' title="bbox 349 1137 1287 1182">
<span class='ocr_line' id='line_1_17' title="bbox 349 1137 1287 1182; baseline 0.001 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocr_textfloat' id='line_1_17' title="bbox 349 1137 1287 1182; baseline 0.001 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocrx_word' id='word_1_148' title='bbox 349 1145 367 1163; x_wconf 80'>¢</span>
<span class='ocrx_word' id='word_1_149' title='bbox 382 1137 560 1182; x_wconf 95'>Optional</span>
<span class='ocrx_word' id='word_1_150' title='bbox 575 1138 708 1174; x_wconf 96'>SMPTE</span>
<span class='ocrx_word' id='word_1_151' title='bbox 709 1138 839 1174; x_wconf 96'>time</span>
<span class='ocrx_word' id='word_1_150' title='bbox 575 1138 739 1174; x_wconf 96'>SMPTE</span>
<span class='ocrx_word' id='word_1_151' title='bbox 752 1138 839 1174; x_wconf 96'>time</span>
<span class='ocrx_word' id='word_1_152' title='bbox 853 1138 945 1174; x_wconf 95'>code</span>
<span class='ocrx_word' id='word_1_153' title='bbox 959 1138 1287 1182; x_wconf 96'>synchronization.</span>
</span>
@@ -278,9 +278,9 @@
<span class='ocrx_word' id='word_1_170' title='bbox 346 1379 411 1406; x_wconf 96'>then</span>
<span class='ocrx_word' id='word_1_171' title='bbox 422 1378 483 1412; x_wconf 96'>play</span>
<span class='ocrx_word' id='word_1_172' title='bbox 493 1387 562 1412; x_wconf 96'>your</span>
<span class='ocrx_word' id='word_1_173' title='bbox 572 1379 646 1405; x_wconf 96'>MIDI</span>
<span class='ocrx_word' id='word_1_174' title='bbox 649 1379 792 1412; x_wconf 96'>keyboard</span>
<span class='ocrx_word' id='word_1_175' title='bbox 792 1379 848 1406; x_wconf 95'>in</span>
<span class='ocrx_word' id='word_1_173' title='bbox 572 1379 659 1405; x_wconf 96'>MIDI</span>
<span class='ocrx_word' id='word_1_174' title='bbox 671 1379 810 1412; x_wconf 96'>keyboard</span>
<span class='ocrx_word' id='word_1_175' title='bbox 821 1379 848 1406; x_wconf 95'>in</span>
<span class='ocrx_word' id='word_1_176' title='bbox 858 1379 923 1406; x_wconf 95'>time</span>
<span class='ocrx_word' id='word_1_177' title='bbox 934 1384 963 1406; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_178' title='bbox 974 1379 1019 1406; x_wconf 93'>the</span>
@@ -294,14 +294,14 @@
<span class='ocrx_word' id='word_1_184' title='bbox 676 1426 810 1452; x_wconf 96'>sequence</span>
<span class='ocrx_word' id='word_1_185' title='bbox 821 1419 901 1452; x_wconf 96'>loops</span>
<span class='ocrx_word' id='word_1_186' title='bbox 912 1419 983 1446; x_wconf 96'>back</span>
<span class='ocrx_word' id='word_1_187' title='bbox 995 1427 1082 1446; x_wconf 96'>around</span>
<span class='ocrx_word' id='word_1_188' title='bbox 1084 1419 1141 1446; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_189' title='bbox 1152 1419 1189 1446; x_wconf 96'>bar</span>
<span class='ocrx_word' id='word_1_190' title='bbox 1189 1419 1232 1450; x_wconf 74'>1,</span>
<span class='ocrx_word' id='word_1_187' title='bbox 995 1419 1101 1446; x_wconf 96'>around</span>
<span class='ocrx_word' id='word_1_188' title='bbox 1112 1423 1141 1446; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_189' title='bbox 1152 1419 1201 1446; x_wconf 96'>bar</span>
<span class='ocrx_word' id='word_1_190' title='bbox 1213 1419 1232 1450; x_wconf 74'>1,</span>
</span>
<span class='ocr_line' id='line_1_23' title="bbox 346 1457 1223 1491; baseline 0.001 -7; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_191' title='bbox 346 1465 400 1490; x_wconf 14'>you</span>
<span class='ocrx_word' id='word_1_192' title='bbox 404 1457 430 1484; x_wconf 14'>ll</span>
<span class='ocrx_word' id='word_1_191' title='bbox 346 1457 430 1490; x_wconf 14'>you</span>
<span class='ocrx_word' id='word_1_192' title='bbox 406 1453 436 1496; x_wconf 14'>ll</span>
<span class='ocrx_word' id='word_1_193' title='bbox 441 1457 506 1485; x_wconf 96'>hear</span>
<span class='ocrx_word' id='word_1_194' title='bbox 517 1458 590 1485; x_wconf 96'>what</span>
<span class='ocrx_word' id='word_1_195' title='bbox 600 1466 654 1491; x_wconf 93'>you</span>
@@ -316,7 +316,7 @@
</div>
<div class='ocr_carea' id='block_1_13' title="bbox 346 1497 1245 1531">
<p class='ocr_par' id='par_1_16' lang='eng' title="bbox 346 1497 1245 1531">
<span class='ocr_line' id='line_1_24' title="bbox 346 1497 1245 1531; baseline 0.001 -7; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocr_textfloat' id='line_1_24' title="bbox 346 1497 1245 1531; baseline 0.001 -7; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocrx_word' id='word_1_202' title='bbox 346 1497 494 1524; x_wconf 96'>corrected!</span>
<span class='ocrx_word' id='word_1_203' title='bbox 508 1497 628 1530; x_wconf 96'>(Timing</span>
<span class='ocrx_word' id='word_1_204' title='bbox 638 1497 791 1525; x_wconf 95'>correction</span>
@@ -343,8 +343,8 @@
<span class='ocrx_word' id='word_1_219' title='bbox 1111 1537 1186 1564; x_wconf 96'>track</span>
</span>
<span class='ocr_line' id='line_1_26' title="bbox 347 1575 1052 1610; baseline 0.001 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_220' title='bbox 347 1591 381 1594; x_wconf 0'>—</span>
<span class='ocrx_word' id='word_1_221' title='bbox 382 1575 495 1609; x_wconf 0'>existing</span>
<span class='ocrx_word' id='word_1_220' title='bbox 347 1591 372 1594; x_wconf 0'>—</span>
<span class='ocrx_word' id='word_1_221' title='bbox 371 1575 495 1609; x_wconf 0'>existing</span>
<span class='ocrx_word' id='word_1_222' title='bbox 505 1580 582 1603; x_wconf 96'>notes</span>
<span class='ocrx_word' id='word_1_223' title='bbox 593 1584 637 1603; x_wconf 96'>are</span>
<span class='ocrx_word' id='word_1_224' title='bbox 648 1580 696 1603; x_wconf 97'>not</span>
@@ -358,10 +358,10 @@
<span class='ocr_line' id='line_1_27' title="bbox 384 1616 1199 1648; baseline 0.001 -6; x_size 32; x_descenders 5; x_ascenders 8">
<span class='ocrx_word' id='word_1_228' title='bbox 384 1616 471 1642; x_wconf 96'>FAST</span>
<span class='ocrx_word' id='word_1_229' title='bbox 481 1616 671 1648; x_wconf 96'>FORWARD,</span>
<span class='ocrx_word' id='word_1_230' title='bbox 684 1617 838 1643; x_wconf 95'>REWIND,</span>
<span class='ocrx_word' id='word_1_231' title='bbox 839 1616 912 1648; x_wconf 95'>and</span>
<span class='ocrx_word' id='word_1_232' title='bbox 924 1616 1045 1643; x_wconf 96'>LOCATE</span>
<span class='ocrx_word' id='word_1_233' title='bbox 1046 1616 1199 1643; x_wconf 95'>controls</span>
<span class='ocrx_word' id='word_1_230' title='bbox 684 1617 844 1648; x_wconf 95'>REWIND,</span>
<span class='ocrx_word' id='word_1_231' title='bbox 857 1616 912 1643; x_wconf 95'>and</span>
<span class='ocrx_word' id='word_1_232' title='bbox 924 1616 1068 1643; x_wconf 96'>LOCATE</span>
<span class='ocrx_word' id='word_1_233' title='bbox 1079 1616 1199 1643; x_wconf 95'>controls</span>
</span>
<span class='ocr_line' id='line_1_28' title="bbox 346 1655 1202 1689; baseline 0 -7; x_size 34; x_descenders 6; x_ascenders 9">
<span class='ocrx_word' id='word_1_234' title='bbox 346 1663 409 1688; x_wconf 92'>may</span>
@@ -385,8 +385,8 @@
<span class='ocrx_word' id='word_1_250' title='bbox 860 1696 897 1722; x_wconf 93'>To</span>
<span class='ocrx_word' id='word_1_251' title='bbox 908 1695 1028 1722; x_wconf 93'>overdub</span>
<span class='ocrx_word' id='word_1_252' title='bbox 1039 1703 1056 1722; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_253' title='bbox 1066 1703 1099 1722; x_wconf 96'>new</span>
<span class='ocrx_word' id='word_1_254' title='bbox 1100 1699 1204 1728; x_wconf 96'>part,</span>
<span class='ocrx_word' id='word_1_253' title='bbox 1066 1703 1125 1722; x_wconf 96'>new</span>
<span class='ocrx_word' id='word_1_254' title='bbox 1135 1699 1204 1728; x_wconf 96'>part,</span>
</span>
<span class='ocr_line' id='line_1_30' title="bbox 347 1733 1150 1768; baseline 0.001 -8; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocrx_word' id='word_1_255' title='bbox 347 1733 426 1761; x_wconf 97'>select</span>
@@ -401,8 +401,8 @@
<span class='ocr_line' id='line_1_31' title="bbox 346 1773 1203 1808; baseline 0.001 -8; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_263' title='bbox 346 1774 448 1806; x_wconf 96'>record,</span>
<span class='ocrx_word' id='word_1_264' title='bbox 460 1774 506 1801; x_wconf 97'>the</span>
<span class='ocrx_word' id='word_1_265' title='bbox 517 1773 577 1801; x_wconf 96'>first</span>
<span class='ocrx_word' id='word_1_266' title='bbox 581 1774 662 1801; x_wconf 96'>track</span>
<span class='ocrx_word' id='word_1_265' title='bbox 503 1769 577 1812; x_wconf 96'>first</span>
<span class='ocrx_word' id='word_1_266' title='bbox 581 1774 658 1801; x_wconf 96'>track</span>
<span class='ocrx_word' id='word_1_267' title='bbox 673 1774 726 1801; x_wconf 96'>will</span>
<span class='ocrx_word' id='word_1_268' title='bbox 736 1774 799 1807; x_wconf 96'>play</span>
<span class='ocrx_word' id='word_1_269' title='bbox 809 1774 836 1801; x_wconf 96'>in</span>
@@ -412,12 +412,12 @@
<span class='ocrx_word' id='word_1_273' title='bbox 1148 1782 1203 1807; x_wconf 96'>you</span>
</span>
<span class='ocr_line' id='line_1_32' title="bbox 346 1813 1191 1847; baseline 0.002 -8; x_size 33; x_descenders 5; x_ascenders 9">
<span class='ocrx_word' id='word_1_274' title='bbox 346 1813 431 1840; x_wconf 95'>MUTE</span>
<span class='ocrx_word' id='word_1_275' title='bbox 431 1813 492 1845; x_wconf 95'>it,</span>
<span class='ocrx_word' id='word_1_274' title='bbox 346 1813 454 1840; x_wconf 95'>MUTE</span>
<span class='ocrx_word' id='word_1_275' title='bbox 464 1813 492 1845; x_wconf 95'>it,</span>
<span class='ocrx_word' id='word_1_276' title='bbox 505 1821 537 1840; x_wconf 95'>or</span>
<span class='ocrx_word' id='word_1_277' title='bbox 547 1813 642 1840; x_wconf 95'>SOLO</span>
<span class='ocrx_word' id='word_1_278' title='bbox 653 1814 756 1841; x_wconf 95'>another</span>
<span class='ocrx_word' id='word_1_279' title='bbox 757 1814 875 1847; x_wconf 94'>track).</span>
<span class='ocrx_word' id='word_1_278' title='bbox 653 1814 769 1841; x_wconf 95'>another</span>
<span class='ocrx_word' id='word_1_279' title='bbox 779 1814 875 1847; x_wconf 94'>track).</span>
<span class='ocrx_word' id='word_1_280' title='bbox 889 1814 920 1840; x_wconf 96'>In</span>
<span class='ocrx_word' id='word_1_281' title='bbox 930 1813 984 1841; x_wconf 96'>this</span>
<span class='ocrx_word' id='word_1_282' title='bbox 995 1822 1057 1847; x_wconf 96'>way,</span>
@@ -431,8 +431,8 @@
<span class='ocrx_word' id='word_1_288' title='bbox 518 1853 552 1879; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_289' title='bbox 562 1853 748 1880; x_wconf 96'>overdubbed!</span>
<span class='ocrx_word' id='word_1_290' title='bbox 761 1853 808 1880; x_wconf 94'>All</span>
<span class='ocrx_word' id='word_1_291' title='bbox 819 1854 892 1880; x_wconf 96'>MIDI</span>
<span class='ocrx_word' id='word_1_292' title='bbox 895 1853 1011 1880; x_wconf 96'>effects</span>
<span class='ocrx_word' id='word_1_291' title='bbox 819 1854 905 1880; x_wconf 96'>MIDI</span>
<span class='ocrx_word' id='word_1_292' title='bbox 917 1853 1011 1880; x_wconf 96'>effects</span>
<span class='ocrx_word' id='word_1_293' title='bbox 1022 1861 1067 1880; x_wconf 96'>are</span>
<span class='ocrx_word' id='word_1_294' title='bbox 1076 1853 1205 1880; x_wconf 96'>recorded</span>
</span>
@@ -440,8 +440,8 @@
<span class='ocrx_word' id='word_1_295' title='bbox 346 1891 485 1925; x_wconf 96'>including</span>
<span class='ocrx_word' id='word_1_296' title='bbox 495 1891 570 1925; x_wconf 96'>pitch</span>
<span class='ocrx_word' id='word_1_297' title='bbox 580 1892 663 1924; x_wconf 96'>bend,</span>
<span class='ocrx_word' id='word_1_298' title='bbox 675 1892 850 1920; x_wconf 96'>modulation,</span>
<span class='ocrx_word' id='word_1_299' title='bbox 854 1892 991 1926; x_wconf 93'>velocity,</span>
<span class='ocrx_word' id='word_1_298' title='bbox 675 1892 859 1924; x_wconf 96'>modulation,</span>
<span class='ocrx_word' id='word_1_299' title='bbox 872 1892 991 1926; x_wconf 93'>velocity,</span>
<span class='ocrx_word' id='word_1_300' title='bbox 1004 1892 1168 1924; x_wconf 92'>aftertouch,</span>
</span>
<span class='ocr_line' id='line_1_35' title="bbox 346 1931 895 1965; baseline 0.002 -7; x_size 33; x_descenders 6; x_ascenders 8">
@@ -465,13 +465,13 @@
<span class='ocrx_word' id='word_1_307' title='bbox 383 2050 419 2076; x_wconf 96'>To</span>
<span class='ocrx_word' id='word_1_308' title='bbox 430 2057 503 2076; x_wconf 95'>erase</span>
<span class='ocrx_word' id='word_1_309' title='bbox 514 2058 530 2077; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_310' title='bbox 540 2058 617 2077; x_wconf 96'>wrong</span>
<span class='ocrx_word' id='word_1_311' title='bbox 618 2054 717 2083; x_wconf 96'>note,</span>
<span class='ocrx_word' id='word_1_310' title='bbox 540 2058 634 2083; x_wconf 96'>wrong</span>
<span class='ocrx_word' id='word_1_311' title='bbox 644 2054 717 2082; x_wconf 96'>note,</span>
<span class='ocrx_word' id='word_1_312' title='bbox 729 2050 829 2083; x_wconf 96'>simply</span>
<span class='ocrx_word' id='word_1_313' title='bbox 839 2050 905 2077; x_wconf 96'>hold</span>
<span class='ocrx_word' id='word_1_314' title='bbox 916 2051 1013 2077; x_wconf 96'>ERASE</span>
<span class='ocrx_word' id='word_1_315' title='bbox 1015 2051 1083 2077; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_316' title='bbox 1085 2050 1186 2084; x_wconf 96'>press</span>
<span class='ocrx_word' id='word_1_314' title='bbox 916 2051 1037 2077; x_wconf 96'>ERASE</span>
<span class='ocrx_word' id='word_1_315' title='bbox 1048 2050 1103 2077; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_316' title='bbox 1113 2059 1186 2084; x_wconf 96'>press</span>
</span>
<span class='ocr_line' id='line_1_38' title="bbox 346 2089 1212 2124; baseline 0.002 -8; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_317' title='bbox 346 2089 391 2116; x_wconf 96'>the</span>
@@ -480,16 +480,16 @@
<span class='ocrx_word' id='word_1_320' title='bbox 515 2090 549 2117; x_wconf 97'>be</span>
<span class='ocrx_word' id='word_1_321' title='bbox 559 2090 652 2117; x_wconf 96'>erased</span>
<span class='ocrx_word' id='word_1_322' title='bbox 661 2090 718 2123; x_wconf 96'>just</span>
<span class='ocrx_word' id='word_1_323' title='bbox 729 2090 808 2117; x_wconf 96'>before</span>
<span class='ocrx_word' id='word_1_324' title='bbox 808 2090 852 2117; x_wconf 96'>it</span>
<span class='ocrx_word' id='word_1_323' title='bbox 729 2090 822 2117; x_wconf 96'>before</span>
<span class='ocrx_word' id='word_1_324' title='bbox 833 2090 852 2117; x_wconf 96'>it</span>
<span class='ocrx_word' id='word_1_325' title='bbox 862 2090 937 2124; x_wconf 96'>plays</span>
<span class='ocrx_word' id='word_1_326' title='bbox 947 2090 975 2117; x_wconf 96'>in</span>
<span class='ocrx_word' id='word_1_327' title='bbox 986 2090 1032 2118; x_wconf 93'>the</span>
<span class='ocrx_word' id='word_1_328' title='bbox 1043 2098 1212 2124; x_wconf 88'>sequence—</span>
</span>
<span class='ocr_line' id='line_1_39' title="bbox 346 2129 1134 2163; baseline 0.003 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_329' title='bbox 346 2129 406 2156; x_wconf 96'>when</span>
<span class='ocrx_word' id='word_1_330' title='bbox 407 2129 531 2162; x_wconf 96'>played</span>
<span class='ocrx_word' id='word_1_329' title='bbox 346 2129 425 2156; x_wconf 96'>when</span>
<span class='ocrx_word' id='word_1_330' title='bbox 435 2129 531 2162; x_wconf 96'>played</span>
<span class='ocrx_word' id='word_1_331' title='bbox 542 2129 621 2161; x_wconf 96'>back,</span>
<span class='ocrx_word' id='word_1_332' title='bbox 633 2129 652 2156; x_wconf 96'>it</span>
<span class='ocrx_word' id='word_1_333' title='bbox 663 2129 716 2156; x_wconf 96'>will</span>
@@ -512,15 +512,15 @@
<span class='ocrx_word' id='word_1_344' title='bbox 749 2169 829 2203; x_wconf 96'>using</span>
<span class='ocrx_word' id='word_1_345' title='bbox 839 2169 885 2196; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_346' title='bbox 896 2170 1031 2196; x_wconf 96'>SINGLE</span>
<span class='ocrx_word' id='word_1_347' title='bbox 1042 2170 1107 2196; x_wconf 91'>STEP</span>
<span class='ocrx_word' id='word_1_348' title='bbox 1109 2169 1220 2196; x_wconf 91'>func-</span>
<span class='ocrx_word' id='word_1_347' title='bbox 1042 2170 1131 2196; x_wconf 91'>STEP</span>
<span class='ocrx_word' id='word_1_348' title='bbox 1143 2169 1220 2196; x_wconf 91'>func-</span>
</span>
<span class='ocr_line' id='line_1_41' title="bbox 345 2207 1228 2242; baseline 0.002 -8; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocrx_word' id='word_1_349' title='bbox 345 2207 412 2234; x_wconf 96'>tion.</span>
<span class='ocrx_word' id='word_1_350' title='bbox 424 2208 461 2235; x_wconf 93'>To</span>
<span class='ocrx_word' id='word_1_351' title='bbox 472 2208 592 2235; x_wconf 91'>overdub</span>
<span class='ocrx_word' id='word_1_352' title='bbox 603 2212 667 2235; x_wconf 96'>notes</span>
<span class='ocrx_word' id='word_1_353' title='bbox 668 2212 718 2235; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_352' title='bbox 603 2212 680 2235; x_wconf 96'>notes</span>
<span class='ocrx_word' id='word_1_353' title='bbox 691 2212 718 2235; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_354' title='bbox 729 2208 841 2242; x_wconf 96'>specific</span>
<span class='ocrx_word' id='word_1_355' title='bbox 851 2209 943 2242; x_wconf 97'>points</span>
<span class='ocrx_word' id='word_1_356' title='bbox 955 2208 1049 2236; x_wconf 96'>within</span>
@@ -544,10 +544,10 @@
<span class='ocrx_word' id='word_1_362' title='bbox 1404 1297 1452 1316; x_wconf 96'>use</span>
<span class='ocrx_word' id='word_1_363' title='bbox 1463 1290 1615 1321; x_wconf 96'>LOCATE,</span>
<span class='ocrx_word' id='word_1_364' title='bbox 1628 1290 1716 1316; x_wconf 96'>FAST</span>
<span class='ocrx_word' id='word_1_365' title='bbox 1726 1289 1910 1316; x_wconf 95'>FORWARD,</span>
<span class='ocrx_word' id='word_1_366' title='bbox 1911 1297 1960 1321; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_367' title='bbox 1972 1290 2098 1316; x_wconf 95'>REWIND</span>
<span class='ocrx_word' id='word_1_368' title='bbox 2100 1290 2165 1317; x_wconf 95'>to</span>
<span class='ocrx_word' id='word_1_365' title='bbox 1726 1289 1917 1321; x_wconf 95'>FORWARD,</span>
<span class='ocrx_word' id='word_1_366' title='bbox 1929 1297 1960 1317; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_367' title='bbox 1972 1290 2126 1316; x_wconf 95'>REWIND</span>
<span class='ocrx_word' id='word_1_368' title='bbox 2136 1294 2165 1317; x_wconf 95'>to</span>
</span>
<span class='ocr_line' id='line_1_44' title="bbox 1297 1328 2033 1362; baseline 0.001 -7; x_size 32; x_descenders 5; x_ascenders 8">
<span class='ocrx_word' id='word_1_369' title='bbox 1297 1328 1356 1355; x_wconf 96'>find</span>
@@ -564,8 +564,8 @@
<p class='ocr_par' id='par_1_24' lang='eng' title="bbox 1295 1368 2160 1593">
<span class='ocr_line' id='line_1_45' title="bbox 1332 1368 2160 1402; baseline 0.002 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_377' title='bbox 1332 1368 1391 1395; x_wconf 93'>The</span>
<span class='ocrx_word' id='word_1_378' title='bbox 1402 1368 1626 1395; x_wconf 91'>INSERT/COPY</span>
<span class='ocrx_word' id='word_1_379' title='bbox 1626 1368 1789 1395; x_wconf 96'>function</span>
<span class='ocrx_word' id='word_1_378' title='bbox 1402 1368 1650 1395; x_wconf 91'>INSERT/COPY</span>
<span class='ocrx_word' id='word_1_379' title='bbox 1662 1368 1789 1395; x_wconf 96'>function</span>
<span class='ocrx_word' id='word_1_380' title='bbox 1800 1368 1893 1395; x_wconf 96'>allows</span>
<span class='ocrx_word' id='word_1_381' title='bbox 1904 1376 1958 1402; x_wconf 96'>you</span>
<span class='ocrx_word' id='word_1_382' title='bbox 1968 1373 1997 1395; x_wconf 96'>to</span>
@@ -580,8 +580,8 @@
<span class='ocrx_word' id='word_1_389' title='bbox 1616 1407 1796 1435; x_wconf 91'>another—in</span>
<span class='ocrx_word' id='word_1_390' title='bbox 1806 1408 1852 1435; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_391' title='bbox 1863 1416 1937 1435; x_wconf 96'>same</span>
<span class='ocrx_word' id='word_1_392' title='bbox 1948 1416 2067 1441; x_wconf 96'>sequence</span>
<span class='ocrx_word' id='word_1_393' title='bbox 2068 1416 2125 1435; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_392' title='bbox 1948 1416 2083 1441; x_wconf 96'>sequence</span>
<span class='ocrx_word' id='word_1_393' title='bbox 2093 1416 2125 1435; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_394' title='bbox 2135 1416 2151 1435; x_wconf 96'>a</span>
</span>
<span class='ocr_line' id='line_1_47' title="bbox 1296 1447 2160 1481; baseline 0.001 -7; x_size 34; x_descenders 7; x_ascenders 8">
@@ -603,15 +603,15 @@
<span class='ocrx_word' id='word_1_408' title='bbox 1449 1487 1571 1514; x_wconf 96'>between</span>
<span class='ocrx_word' id='word_1_409' title='bbox 1582 1487 1628 1514; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_410' title='bbox 1638 1487 1740 1514; x_wconf 96'>second</span>
<span class='ocrx_word' id='word_1_411' title='bbox 1751 1487 1838 1514; x_wconf 96'>chorus</span>
<span class='ocrx_word' id='word_1_412' title='bbox 1841 1495 1899 1514; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_413' title='bbox 1901 1487 1975 1514; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_411' title='bbox 1751 1487 1852 1514; x_wconf 96'>chorus</span>
<span class='ocrx_word' id='word_1_412' title='bbox 1863 1487 1919 1514; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_413' title='bbox 1929 1487 1975 1514; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_414' title='bbox 1985 1487 2087 1521; x_wconf 96'>bridge.</span>
</span>
<span class='ocr_line' id='line_1_49' title="bbox 1296 1527 2047 1560; baseline 0.003 -8; x_size 32; x_descenders 6; x_ascenders 7">
<span class='ocrx_word' id='word_1_415' title='bbox 1296 1527 1441 1553; x_wconf 95'>DELETE</span>
<span class='ocrx_word' id='word_1_416' title='bbox 1453 1527 1527 1553; x_wconf 96'>BARS</span>
<span class='ocrx_word' id='word_1_417' title='bbox 1529 1527 1681 1559; x_wconf 96'>operates</span>
<span class='ocrx_word' id='word_1_416' title='bbox 1453 1527 1546 1553; x_wconf 96'>BARS</span>
<span class='ocrx_word' id='word_1_417' title='bbox 1557 1531 1681 1559; x_wconf 96'>operates</span>
<span class='ocrx_word' id='word_1_418' title='bbox 1691 1527 1737 1553; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_419' title='bbox 1748 1535 1823 1554; x_wconf 96'>same</span>
<span class='ocrx_word' id='word_1_420' title='bbox 1833 1535 1891 1560; x_wconf 96'>way</span>
@@ -619,8 +619,8 @@
<span class='ocrx_word' id='word_1_422' title='bbox 1940 1535 2047 1554; x_wconf 96'>remove</span>
</span>
<span class='ocr_line' id='line_1_50' title="bbox 1295 1565 1577 1593; baseline 0.004 -1; x_size 34.748871; x_descenders 6.7488689; x_ascenders 9">
<span class='ocrx_word' id='word_1_423' title='bbox 1295 1569 1422 1592; x_wconf 96'>unwanted</span>
<span class='ocrx_word' id='word_1_424' title='bbox 1423 1565 1577 1593; x_wconf 95'>sections,</span>
<span class='ocrx_word' id='word_1_423' title='bbox 1295 1565 1441 1592; x_wconf 96'>unwanted</span>
<span class='ocrx_word' id='word_1_424' title='bbox 1452 1565 1577 1593; x_wconf 95'>sections,</span>
</span>
</p>
</div>
@@ -636,12 +636,12 @@
<p class='ocr_par' id='par_1_26' lang='eng' title="bbox 1295 1690 2215 1960">
<span class='ocr_line' id='line_1_52' title="bbox 1333 1690 2146 1723; baseline 0 -6; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_428' title='bbox 1333 1690 1394 1717; x_wconf 96'>One</span>
<span class='ocrx_word' id='word_1_429' title='bbox 1404 1698 1446 1717; x_wconf 96'>way</span>
<span class='ocrx_word' id='word_1_430' title='bbox 1445 1694 1500 1723; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_429' title='bbox 1404 1698 1462 1723; x_wconf 96'>way</span>
<span class='ocrx_word' id='word_1_430' title='bbox 1472 1694 1500 1717; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_431' title='bbox 1511 1694 1598 1717; x_wconf 96'>create</span>
<span class='ocrx_word' id='word_1_432' title='bbox 1608 1698 1625 1717; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_433' title='bbox 1635 1698 1687 1717; x_wconf 95'>song</span>
<span class='ocrx_word' id='word_1_434' title='bbox 1688 1690 1736 1723; x_wconf 95'>is</span>
<span class='ocrx_word' id='word_1_433' title='bbox 1635 1698 1704 1723; x_wconf 95'>song</span>
<span class='ocrx_word' id='word_1_434' title='bbox 1715 1690 1736 1717; x_wconf 95'>is</span>
<span class='ocrx_word' id='word_1_435' title='bbox 1747 1694 1776 1717; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_436' title='bbox 1787 1690 1880 1717; x_wconf 96'>record</span>
<span class='ocrx_word' id='word_1_437' title='bbox 1891 1690 1958 1717; x_wconf 96'>each</span>
@@ -657,8 +657,8 @@
<span class='ocrx_word' id='word_1_445' title='bbox 1592 1730 1644 1756; x_wconf 96'>999</span>
<span class='ocrx_word' id='word_1_446' title='bbox 1654 1729 1738 1762; x_wconf 96'>bars).</span>
<span class='ocrx_word' id='word_1_447' title='bbox 1751 1729 1878 1757; x_wconf 96'>Another</span>
<span class='ocrx_word' id='word_1_448' title='bbox 1888 1737 1930 1757; x_wconf 96'>way</span>
<span class='ocrx_word' id='word_1_449' title='bbox 1929 1729 1977 1762; x_wconf 96'>is</span>
<span class='ocrx_word' id='word_1_448' title='bbox 1888 1737 1945 1762; x_wconf 96'>way</span>
<span class='ocrx_word' id='word_1_449' title='bbox 1956 1729 1977 1757; x_wconf 96'>is</span>
<span class='ocrx_word' id='word_1_450' title='bbox 1987 1733 2016 1757; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_451' title='bbox 2027 1729 2121 1757; x_wconf 96'>record</span>
</span>
@@ -667,8 +667,8 @@
<span class='ocrx_word' id='word_1_453' title='bbox 1373 1768 1448 1796; x_wconf 96'>basic</span>
<span class='ocrx_word' id='word_1_454' title='bbox 1458 1768 1562 1796; x_wconf 96'>section</span>
<span class='ocrx_word' id='word_1_455' title='bbox 1574 1769 1666 1802; x_wconf 96'>(verse,</span>
<span class='ocrx_word' id='word_1_456' title='bbox 1679 1769 1779 1796; x_wconf 96'>chorus,</span>
<span class='ocrx_word' id='word_1_457' title='bbox 1782 1769 1865 1802; x_wconf 96'>etc.)</span>
<span class='ocrx_word' id='word_1_456' title='bbox 1679 1769 1788 1801; x_wconf 96'>chorus,</span>
<span class='ocrx_word' id='word_1_457' title='bbox 1800 1769 1865 1802; x_wconf 96'>etc.)</span>
<span class='ocrx_word' id='word_1_458' title='bbox 1876 1768 1904 1795; x_wconf 96'>in</span>
<span class='ocrx_word' id='word_1_459' title='bbox 1914 1768 2066 1796; x_wconf 96'>individual</span>
</span>
@@ -678,8 +678,8 @@
<span class='ocrx_word' id='word_1_462' title='bbox 1538 1816 1587 1835; x_wconf 96'>use</span>
<span class='ocrx_word' id='word_1_463' title='bbox 1597 1808 1643 1835; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_464' title='bbox 1653 1809 1799 1835; x_wconf 96'>CREATE</span>
<span class='ocrx_word' id='word_1_465' title='bbox 1810 1808 1883 1835; x_wconf 96'>SONG</span>
<span class='ocrx_word' id='word_1_466' title='bbox 1885 1808 2050 1836; x_wconf 96'>function</span>
<span class='ocrx_word' id='word_1_465' title='bbox 1810 1808 1911 1835; x_wconf 96'>SONG</span>
<span class='ocrx_word' id='word_1_466' title='bbox 1923 1808 2050 1836; x_wconf 96'>function</span>
<span class='ocrx_word' id='word_1_467' title='bbox 2060 1812 2089 1835; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_468' title='bbox 2103 1808 2215 1836; x_wconf 93'>“chain”</span>
</span>
@@ -687,14 +687,14 @@
<span class='ocrx_word' id='word_1_469' title='bbox 1295 1848 1370 1874; x_wconf 96'>them</span>
<span class='ocrx_word' id='word_1_470' title='bbox 1381 1848 1508 1881; x_wconf 95'>together.</span>
<span class='ocrx_word' id='word_1_471' title='bbox 1521 1848 1667 1875; x_wconf 96'>CREATE</span>
<span class='ocrx_word' id='word_1_472' title='bbox 1678 1848 1751 1875; x_wconf 96'>SONG</span>
<span class='ocrx_word' id='word_1_473' title='bbox 1753 1847 1842 1874; x_wconf 96'>will</span>
<span class='ocrx_word' id='word_1_472' title='bbox 1678 1848 1779 1875; x_wconf 96'>SONG</span>
<span class='ocrx_word' id='word_1_473' title='bbox 1789 1847 1842 1874; x_wconf 96'>will</span>
<span class='ocrx_word' id='word_1_474' title='bbox 1853 1848 1918 1875; x_wconf 96'>then</span>
<span class='ocrx_word' id='word_1_475' title='bbox 1929 1848 2135 1881; x_wconf 96'>automatically</span>
</span>
<span class='ocr_line' id='line_1_57' title="bbox 1296 1887 2162 1920; baseline 0 -6; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_476' title='bbox 1296 1895 1349 1920; x_wconf 96'>copy</span>
<span class='ocrx_word' id='word_1_477' title='bbox 1349 1887 1412 1920; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_476' title='bbox 1296 1895 1366 1920; x_wconf 96'>copy</span>
<span class='ocrx_word' id='word_1_477' title='bbox 1377 1887 1412 1914; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_478' title='bbox 1422 1887 1468 1914; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_479' title='bbox 1478 1891 1552 1920; x_wconf 96'>parts</span>
<span class='ocrx_word' id='word_1_480' title='bbox 1563 1887 1621 1914; x_wconf 95'>into</span>
@@ -714,8 +714,8 @@
<span class='ocrx_word' id='word_1_492' title='bbox 1540 1926 1590 1953; x_wconf 96'>few</span>
<span class='ocrx_word' id='word_1_493' title='bbox 1601 1927 1664 1953; x_wconf 96'>bars</span>
<span class='ocrx_word' id='word_1_494' title='bbox 1675 1931 1704 1954; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_495' title='bbox 1715 1935 1795 1960; x_wconf 96'>repeat</span>
<span class='ocrx_word' id='word_1_496' title='bbox 1795 1926 1955 1960; x_wconf 96'>infinitely,</span>
<span class='ocrx_word' id='word_1_495' title='bbox 1715 1931 1806 1960; x_wconf 96'>repeat</span>
<span class='ocrx_word' id='word_1_496' title='bbox 1816 1926 1955 1960; x_wconf 96'>infinitely,</span>
<span class='ocrx_word' id='word_1_497' title='bbox 1968 1926 2011 1954; x_wconf 95'>for</span>
<span class='ocrx_word' id='word_1_498' title='bbox 2022 1935 2038 1954; x_wconf 93'>a</span>
<span class='ocrx_word' id='word_1_499' title='bbox 2049 1927 2169 1954; x_wconf 92'>fadeout.</span>
@@ -757,8 +757,8 @@
</span>
<span class='ocr_line' id='line_1_62' title="bbox 1293 2130 2156 2164; baseline 0 -7; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_522' title='bbox 1293 2130 1339 2157; x_wconf 93'>the</span>
<span class='ocrx_word' id='word_1_523' title='bbox 1350 2130 1562 2164; x_wconf 90'>LinnSequencer</span>
<span class='ocrx_word' id='word_1_524' title='bbox 1564 2130 1607 2157; x_wconf 97'>is</span>
<span class='ocrx_word' id='word_1_523' title='bbox 1350 2130 1576 2164; x_wconf 90'>LinnSequencer</span>
<span class='ocrx_word' id='word_1_524' title='bbox 1586 2130 1607 2157; x_wconf 97'>is</span>
<span class='ocrx_word' id='word_1_525' title='bbox 1619 2130 1747 2164; x_wconf 96'>designed</span>
<span class='ocrx_word' id='word_1_526' title='bbox 1758 2134 1787 2157; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_527' title='bbox 1798 2130 1834 2157; x_wconf 96'>let</span>
@@ -767,8 +767,8 @@
<span class='ocrx_word' id='word_1_530' title='bbox 2063 2130 2156 2157; x_wconf 96'>record</span>
</span>
<span class='ocr_line' id='line_1_63' title="bbox 1294 2169 2145 2203; baseline 0 -6; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_531' title='bbox 1294 2178 1330 2197; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_532' title='bbox 1331 2169 1414 2197; x_wconf 96'>edit</span>
<span class='ocrx_word' id='word_1_531' title='bbox 1294 2170 1349 2197; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_532' title='bbox 1360 2169 1414 2197; x_wconf 96'>edit</span>
<span class='ocrx_word' id='word_1_533' title='bbox 1425 2170 1503 2197; x_wconf 96'>while</span>
<span class='ocrx_word' id='word_1_534' title='bbox 1514 2170 1643 2203; x_wconf 96'>devoting</span>
<span class='ocrx_word' id='word_1_535' title='bbox 1653 2178 1721 2203; x_wconf 96'>your</span>
@@ -792,7 +792,7 @@
</div>
<div class='ocr_carea' id='block_1_21' title="bbox 347 2343 2171 2378">
<p class='ocr_par' id='par_1_29' lang='eng' title="bbox 347 2343 2171 2378">
<span class='ocr_line' id='line_1_65' title="bbox 347 2343 2171 2378; baseline 0.001 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocr_header' id='line_1_65' title="bbox 347 2343 2171 2378; baseline 0.001 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_549' title='bbox 347 2350 361 2363; x_wconf 58'>*</span>
<span class='ocrx_word' id='word_1_550' title='bbox 373 2343 483 2377; x_wconf 96'>Simple,</span>
<span class='ocrx_word' id='word_1_551' title='bbox 495 2352 559 2377; x_wconf 96'>easy</span>
@@ -806,8 +806,8 @@
<span class='ocrx_word' id='word_1_559' title='bbox 1326 2345 1424 2378; x_wconf 97'>clearly</span>
<span class='ocrx_word' id='word_1_560' title='bbox 1434 2345 1528 2378; x_wconf 96'>guides</span>
<span class='ocrx_word' id='word_1_561' title='bbox 1539 2353 1594 2378; x_wconf 97'>you</span>
<span class='ocrx_word' id='word_1_562' title='bbox 1604 2345 1705 2378; x_wconf 96'>through</span>
<span class='ocrx_word' id='word_1_563' title='bbox 1706 2344 1770 2371; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_562' title='bbox 1604 2345 1724 2378; x_wconf 96'>through</span>
<span class='ocrx_word' id='word_1_563' title='bbox 1735 2344 1770 2371; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_564' title='bbox 1781 2344 1947 2377; x_wconf 96'>operations.</span>
<span class='ocrx_word' id='word_1_565' title='bbox 1961 2344 1989 2371; x_wconf 96'>If</span>
<span class='ocrx_word' id='word_1_566' title='bbox 1997 2344 2112 2376; x_wconf 96'>needed,</span>
@@ -818,8 +818,8 @@
<div class='ocr_carea' id='block_1_22' title="bbox 373 2381 1083 2415">
<p class='ocr_par' id='par_1_30' lang='eng' title="bbox 373 2381 1083 2415">
<span class='ocr_line' id='line_1_66' title="bbox 373 2381 1083 2415; baseline 0.003 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_568' title='bbox 373 2381 448 2407; x_wconf 96'>HELP</span>
<span class='ocrx_word' id='word_1_569' title='bbox 450 2381 583 2408; x_wconf 96'>button</span>
<span class='ocrx_word' id='word_1_568' title='bbox 373 2381 472 2407; x_wconf 96'>HELP</span>
<span class='ocrx_word' id='word_1_569' title='bbox 483 2381 583 2408; x_wconf 96'>button</span>
<span class='ocrx_word' id='word_1_570' title='bbox 594 2381 711 2415; x_wconf 96'>displays</span>
<span class='ocrx_word' id='word_1_571' title='bbox 722 2382 875 2409; x_wconf 96'>additional</span>
<span class='ocrx_word' id='word_1_572' title='bbox 886 2382 1083 2415; x_wconf 96'>explanations.</span>
@@ -828,7 +828,7 @@
</div>
<div class='ocr_carea' id='block_1_23' title="bbox 347 2427 2145 2507">
<p class='ocr_par' id='par_1_31' lang='eng' title="bbox 347 2427 2145 2507">
<span class='ocr_line' id='line_1_67' title="bbox 347 2427 1468 2461; baseline 0.002 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocr_header' id='line_1_67' title="bbox 347 2427 1468 2461; baseline 0.002 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_573' title='bbox 347 2432 361 2446; x_wconf 70'>*</span>
<span class='ocrx_word' id='word_1_574' title='bbox 373 2427 612 2454; x_wconf 91'>Non-destructive</span>
<span class='ocrx_word' id='word_1_575' title='bbox 622 2427 914 2461; x_wconf 89'>recording—existing</span>
@@ -839,14 +839,14 @@
<span class='ocrx_word' id='word_1_580' title='bbox 1231 2428 1309 2455; x_wconf 96'>while</span>
<span class='ocrx_word' id='word_1_581' title='bbox 1319 2428 1468 2461; x_wconf 92'>recording.</span>
</span>
<span class='ocr_line' id='line_1_68' title="bbox 347 2473 2145 2507; baseline 0.001 -8; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocr_header' id='line_1_68' title="bbox 347 2473 2145 2507; baseline 0.001 -8; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocrx_word' id='word_1_582' title='bbox 347 2478 361 2492; x_wconf 70'>¢</span>
<span class='ocrx_word' id='word_1_583' title='bbox 372 2473 433 2500; x_wconf 93'>Two</span>
<span class='ocrx_word' id='word_1_584' title='bbox 444 2473 689 2500; x_wconf 90'>FOOTSWITCH</span>
<span class='ocrx_word' id='word_1_585' title='bbox 701 2474 818 2500; x_wconf 95'>INPUTS</span>
<span class='ocrx_word' id='word_1_586' title='bbox 819 2474 894 2501; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_587' title='bbox 893 2473 939 2507; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_588' title='bbox 941 2473 1091 2507; x_wconf 96'>assigned</span>
<span class='ocrx_word' id='word_1_585' title='bbox 701 2474 837 2501; x_wconf 95'>INPUTS</span>
<span class='ocrx_word' id='word_1_586' title='bbox 848 2481 910 2507; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_587' title='bbox 921 2473 955 2500; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_588' title='bbox 966 2473 1091 2507; x_wconf 96'>assigned</span>
<span class='ocrx_word' id='word_1_589' title='bbox 1101 2478 1130 2501; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_590' title='bbox 1141 2474 1271 2507; x_wconf 96'>remotely</span>
<span class='ocrx_word' id='word_1_591' title='bbox 1281 2473 1387 2501; x_wconf 96'>control</span>
@@ -873,12 +873,12 @@
</div>
<div class='ocr_carea' id='block_1_25' title="bbox 347 2556 1768 2590">
<p class='ocr_par' id='par_1_33' lang='eng' title="bbox 347 2556 1768 2590">
<span class='ocr_line' id='line_1_70' title="bbox 347 2556 1768 2590; baseline 0.001 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocr_header' id='line_1_70' title="bbox 347 2556 1768 2590; baseline 0.001 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_604' title='bbox 347 2561 361 2575; x_wconf 86'>¢</span>
<span class='ocrx_word' id='word_1_605' title='bbox 372 2556 433 2583; x_wconf 85'>Iwo</span>
<span class='ocrx_word' id='word_1_606' title='bbox 443 2556 612 2583; x_wconf 96'>TRIGGER</span>
<span class='ocrx_word' id='word_1_607' title='bbox 623 2556 778 2584; x_wconf 96'>OUTPUTS</span>
<span class='ocrx_word' id='word_1_608' title='bbox 780 2557 871 2590; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_607' title='bbox 623 2556 797 2584; x_wconf 96'>OUTPUTS</span>
<span class='ocrx_word' id='word_1_608' title='bbox 808 2565 871 2590; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_609' title='bbox 881 2557 915 2584; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_610' title='bbox 925 2557 1119 2590; x_wconf 96'>programmed</span>
<span class='ocrx_word' id='word_1_611' title='bbox 1129 2561 1158 2584; x_wconf 96'>to</span>
@@ -904,14 +904,14 @@
<span class='ocrx_word' id='word_1_625' title='bbox 875 2610 907 2629; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_626' title='bbox 918 2602 989 2629; x_wconf 96'>Linn</span>
<span class='ocrx_word' id='word_1_627' title='bbox 1000 2603 1069 2629; x_wconf 95'>9000</span>
<span class='ocrx_word' id='word_1_628' title='bbox 1080 2610 1129 2635; x_wconf 96'>sync</span>
<span class='ocrx_word' id='word_1_629' title='bbox 1131 2607 1226 2630; x_wconf 96'>tone.</span>
<span class='ocrx_word' id='word_1_628' title='bbox 1080 2610 1145 2635; x_wconf 96'>sync</span>
<span class='ocrx_word' id='word_1_629' title='bbox 1155 2607 1226 2630; x_wconf 96'>tone.</span>
</span>
</p>
</div>
<div class='ocr_carea' id='block_1_27' title="bbox 347 2648 2100 2727">
<p class='ocr_par' id='par_1_35' lang='eng' title="bbox 347 2648 2100 2727">
<span class='ocr_line' id='line_1_72' title="bbox 347 2648 1664 2682; baseline 0.001 -7; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocr_header' id='line_1_72' title="bbox 347 2648 1664 2682; baseline 0.001 -7; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_630' title='bbox 347 2654 360 2667; x_wconf 45'>©</span>
<span class='ocrx_word' id='word_1_631' title='bbox 372 2648 483 2675; x_wconf 95'>Utilizes</span>
<span class='ocrx_word' id='word_1_632' title='bbox 493 2648 564 2680; x_wconf 96'>ultra</span>
@@ -927,17 +927,17 @@
<span class='ocrx_word' id='word_1_642' title='bbox 1414 2649 1502 2676; x_wconf 96'>FAST</span>
<span class='ocrx_word' id='word_1_643' title='bbox 1512 2648 1664 2682; x_wconf 96'>operation.</span>
</span>
<span class='ocr_line' id='line_1_73' title="bbox 347 2694 2100 2727; baseline 0.001 -7; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocr_header' id='line_1_73' title="bbox 347 2694 2100 2727; baseline 0.001 -7; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_644' title='bbox 347 2699 361 2713; x_wconf 31'>*</span>
<span class='ocrx_word' id='word_1_645' title='bbox 372 2694 476 2720; x_wconf 96'>TEMPO</span>
<span class='ocrx_word' id='word_1_646' title='bbox 478 2694 562 2721; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_647' title='bbox 561 2694 606 2727; x_wconf 95'>be</span>
<span class='ocrx_word' id='word_1_648' title='bbox 608 2694 764 2727; x_wconf 95'>specified</span>
<span class='ocrx_word' id='word_1_645' title='bbox 372 2694 504 2721; x_wconf 96'>TEMPO</span>
<span class='ocrx_word' id='word_1_646' title='bbox 515 2702 578 2727; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_647' title='bbox 589 2694 623 2721; x_wconf 95'>be</span>
<span class='ocrx_word' id='word_1_648' title='bbox 633 2694 764 2727; x_wconf 95'>specified</span>
<span class='ocrx_word' id='word_1_649' title='bbox 774 2694 802 2721; x_wconf 93'>in</span>
<span class='ocrx_word' id='word_1_650' title='bbox 814 2695 1172 2722; x_wconf 91'>BEATS-PER-MINUTE</span>
<span class='ocrx_word' id='word_1_651' title='bbox 1183 2703 1215 2722; x_wconf 93'>or</span>
<span class='ocrx_word' id='word_1_652' title='bbox 1225 2695 1547 2722; x_wconf 91'>FRAMES-PER-BEAT</span>
<span class='ocrx_word' id='word_1_653' title='bbox 1543 2695 1605 2721; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_652' title='bbox 1225 2695 1567 2722; x_wconf 91'>FRAMES-PER-BEAT</span>
<span class='ocrx_word' id='word_1_653' title='bbox 1577 2698 1605 2721; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_654' title='bbox 1616 2695 1659 2726; x_wconf 96'>24,</span>
<span class='ocrx_word' id='word_1_655' title='bbox 1672 2695 1716 2726; x_wconf 96'>25,</span>
<span class='ocrx_word' id='word_1_656' title='bbox 1728 2702 1760 2721; x_wconf 96'>or</span>
@@ -959,11 +959,11 @@
</div>
<div class='ocr_carea' id='block_1_29' title="bbox 347 2777 2174 2811">
<p class='ocr_par' id='par_1_37' lang='eng' title="bbox 347 2777 2174 2811">
<span class='ocr_line' id='line_1_75' title="bbox 347 2777 2174 2811; baseline 0.001 -8; x_size 33; x_descenders 5; x_ascenders 9">
<span class='ocr_header' id='line_1_75' title="bbox 347 2777 2174 2811; baseline 0.001 -8; x_size 33; x_descenders 5; x_ascenders 9">
<span class='ocrx_word' id='word_1_664' title='bbox 347 2782 360 2796; x_wconf 81'>¢</span>
<span class='ocrx_word' id='word_1_665' title='bbox 372 2777 476 2803; x_wconf 94'>TEMPO</span>
<span class='ocrx_word' id='word_1_666' title='bbox 478 2777 562 2804; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_667' title='bbox 561 2777 622 2810; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_665' title='bbox 372 2777 504 2804; x_wconf 94'>TEMPO</span>
<span class='ocrx_word' id='word_1_666' title='bbox 515 2785 578 2810; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_667' title='bbox 588 2777 622 2804; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_668' title='bbox 633 2778 741 2804; x_wconf 95'>entered</span>
<span class='ocrx_word' id='word_1_669' title='bbox 751 2777 934 2811; x_wconf 96'>numerically,</span>
<span class='ocrx_word' id='word_1_670' title='bbox 946 2777 1101 2811; x_wconf 95'>adjustable</span>
@@ -987,36 +987,36 @@
<span class='ocrx_word' id='word_1_682' title='bbox 372 2822 410 2841; x_wconf 96'>on</span>
<span class='ocrx_word' id='word_1_683' title='bbox 420 2815 466 2842; x_wconf 95'>the</span>
<span class='ocrx_word' id='word_1_684' title='bbox 476 2815 545 2841; x_wconf 95'>TAP</span>
<span class='ocrx_word' id='word_1_685' title='bbox 556 2816 660 2842; x_wconf 95'>TEMPO</span>
<span class='ocrx_word' id='word_1_686' title='bbox 662 2815 808 2842; x_wconf 96'>button.</span>
<span class='ocrx_word' id='word_1_685' title='bbox 556 2815 689 2842; x_wconf 95'>TEMPO</span>
<span class='ocrx_word' id='word_1_686' title='bbox 699 2815 808 2842; x_wconf 96'>button.</span>
</span>
</p>
</div>
<div class='ocr_carea' id='block_1_31' title="bbox 347 2861 1792 2940">
<p class='ocr_par' id='par_1_39' lang='eng' title="bbox 347 2861 1792 2940">
<span class='ocr_line' id='line_1_77' title="bbox 347 2861 1792 2895; baseline 0.001 -8; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocr_header' id='line_1_77' title="bbox 347 2861 1792 2895; baseline 0.001 -8; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_687' title='bbox 347 2866 360 2880; x_wconf 43'>¢</span>
<span class='ocrx_word' id='word_1_688' title='bbox 372 2861 504 2887; x_wconf 96'>TEMPO</span>
<span class='ocrx_word' id='word_1_689' title='bbox 515 2861 677 2888; x_wconf 96'>CHANGES</span>
<span class='ocrx_word' id='word_1_690' title='bbox 679 2861 771 2894; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_689' title='bbox 515 2861 696 2888; x_wconf 96'>CHANGES</span>
<span class='ocrx_word' id='word_1_690' title='bbox 707 2869 771 2894; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_691' title='bbox 781 2861 815 2888; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_692' title='bbox 825 2869 1000 2894; x_wconf 96'>programmed</span>
<span class='ocrx_word' id='word_1_693' title='bbox 1001 2861 1087 2888; x_wconf 96'>into</span>
<span class='ocrx_word' id='word_1_692' title='bbox 825 2861 1019 2894; x_wconf 96'>programmed</span>
<span class='ocrx_word' id='word_1_693' title='bbox 1030 2861 1087 2888; x_wconf 96'>into</span>
<span class='ocrx_word' id='word_1_694' title='bbox 1099 2869 1115 2888; x_wconf 95'>a</span>
<span class='ocrx_word' id='word_1_695' title='bbox 1126 2870 1268 2895; x_wconf 96'>sequence,</span>
<span class='ocrx_word' id='word_1_696' title='bbox 1280 2861 1344 2888; x_wconf 96'>with</span>
<span class='ocrx_word' id='word_1_697' title='bbox 1356 2866 1448 2888; x_wconf 95'>smooth</span>
<span class='ocrx_word' id='word_1_698' title='bbox 1450 2861 1635 2888; x_wconf 96'>transitions</span>
<span class='ocrx_word' id='word_1_697' title='bbox 1356 2862 1467 2888; x_wconf 95'>smooth</span>
<span class='ocrx_word' id='word_1_698' title='bbox 1478 2861 1635 2888; x_wconf 96'>transitions</span>
<span class='ocrx_word' id='word_1_699' title='bbox 1646 2861 1670 2887; x_wconf 96'>if</span>
<span class='ocrx_word' id='word_1_700' title='bbox 1679 2861 1792 2888; x_wconf 87'>desired.</span>
</span>
<span class='ocr_line' id='line_1_78' title="bbox 347 2906 1507 2940; baseline 0.002 -8; x_size 33; x_descenders 7; x_ascenders 8">
<span class='ocr_header' id='line_1_78' title="bbox 347 2906 1507 2940; baseline 0.002 -8; x_size 33; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_701' title='bbox 347 2911 360 2925; x_wconf 69'>¢</span>
<span class='ocrx_word' id='word_1_702' title='bbox 371 2906 434 2938; x_wconf 96'>Any</span>
<span class='ocrx_word' id='word_1_703' title='bbox 444 2906 539 2932; x_wconf 96'>TIME</span>
<span class='ocrx_word' id='word_1_704' title='bbox 550 2906 739 2933; x_wconf 96'>SIGNATURE</span>
<span class='ocrx_word' id='word_1_705' title='bbox 740 2907 820 2933; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_706' title='bbox 819 2907 880 2939; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_704' title='bbox 550 2906 763 2933; x_wconf 96'>SIGNATURE</span>
<span class='ocrx_word' id='word_1_705' title='bbox 773 2915 836 2939; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_706' title='bbox 846 2907 880 2933; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_707' title='bbox 891 2907 968 2938; x_wconf 96'>used,</span>
<span class='ocrx_word' id='word_1_708' title='bbox 980 2907 1036 2934; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_709' title='bbox 1046 2915 1109 2940; x_wconf 96'>may</span>
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
File diff suppressed because it is too large Load Diff
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -103,7 +103,7 @@ ERASE, REPEAT, PLAY/STOP, or LOCATE.
© Will sync to standard LinnDrum or Linn 9000 sync tone.
© Utilizes ultra high-speed, 8 MHz 80186 16 bit computer internally for FAST operation.
® Utilizes ultra high-speed, 8 MHz 80186 16 bit computer internally for FAST operation.
* TEMPO may be specified in BEATS-PER-MINUTE or FRAMES-PER-BEAT at 24, 25, or 30 frames per second,
(even drop frame!)
@@ -115,9 +115,9 @@ on the TAP TEMPO button.
¢ TEMPO CHANGES may be programmed into a sequence, with smooth transitions if desired.
¢ Any TIME SIGNATURE may be used, and may be changed within a song.
linn
Linn Electronics, Inc.
nn
Linn Electronics, Inc.
18720 Oxnard Street, Tarzana, CA 91356
(818) 708-8131 TELEX #298949 LINN UR
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
File diff suppressed because it is too large Load Diff
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -1,123 +1,128 @@
The LinnSequencer
32 Track MIDI Sequence Recorder
2A NNII 6F6867# XATALL IE18-80L (818)
The LinnSequencer is a state-of-the-art composition and performance tool for the professional musician. It is
9SEI6 VO “BUBZIRY, “J0aNS PIPUXO OZLEI
“Uy soTUOMOI,q UUrT
extremely powerful, yet amazingly simple to learn and use. Its many remarkable features include:
uut]
¢ Operation is similar to multi-track tape recorder with PLAY, STOP, RECORD, FAST
FORWARD, REWIND, and LOCATE controls.
“‘SUOS B UIJIM pasueyo oq ABU pue posn oq AWW AYN IVNOIS AWLL AUV
“parlsop Jr SUOTIISUBI} YIOOUIS YIM “BoueNbas eB OJUI pourtueIZOId 9q ABU SFONWHO OdINAL e
e Each of the 100 sequences contains 32 simultaneous, polyphonic tracks. Each track may
be assigned to one of 16 MIDI channels. Simultaneously plays up to 16 polyphonic
uonng OdNAL dV L 9) uO
synthesizers!
sojou Jayienb Suiddy} Aq 10 syUSTIOIOUI oINUTIAI-J8g-Jesg & JO sys} UL ofquisn(pe ATTeouIAUINU paiajus oq ABU OdINALL e
¢ Ultra-fast 3%” disk drive stores complex songs in seconds and holds over 110,000 notes
(jouer doup u3a9)
per disk!
“puooes Jed souely O€ 10 “SZ “pz 18 [LVAG-MAd-SHN VU 10 ALOANIWAAd-SLVAd U! patyoeds aq kewl OAL «
uoTe1odo [SVx JO} Aj[eusoyUT JoyndUIOd 11g 9] 98108 ZHI 8g poeds-ysry Bann soz] e
¢ One or all tracks may be TRANSPOSED at the touch of a key.
e Exclusive real-time ERASE function makes editing FAST.
* Exclusive REPEAT function automatically repeats any held notes at a pre-selected
"9U0} DUAS 0006 UUL] Jo wNIqUUr] prepue}s 0} OUAS [ITAA ©
rhythmic value.
“ONYBA 9}OU poloapes Aue Je sas—nd jndyno 07 pewureigold 3q ACW SL Ad LNO YADONAL OML
¢ TIMING CORRECTION works during playback and operates without chopping notes.
"ALVOOT 10 GOLS/AV 1d LWddad “ASV
¢ Optional SMPTE time code synchronization.
SUIpNpoUr suOTIOUN] posn A[UOUILUOS 94] JO AUBUT [O1]UOD AJ9]OWIAI 0} PousIsse oq ACUI ST AdNI HOLIMSLOO OME «
“SUIPIONAI I[IYM P2sesd JOU Iv $3}OU BUTISIXO—ZUIPIOIA SATON.ASOP-UON
© Optional remote control.
suoneurldxa peuoyippe sdeydsip uowng g1TqH
Recording a Sequence
oy] pepsau JI suoneiodo [ye yYsnosy] NOA sapins ApIespo Avfdsip QO] Joey Z7¢ 9y3—uoeIodo Urea] 0} Ased aus «
To record a sequence, simply press RECORD and PLAY,
then play your MIDI keyboard in time to the Sequencers
click track. When the sequence loops back around to bar 1,
you ll hear what you played—only all timing errors will be
jUorel]suowtap & IO} Aepol Jayeap uur] INOA dag dISHUL
INOA 0} UONUS}]¥ PaplAIPUN INOA SUTJOASp ITY ps pue
p1osai asoduod no Jay 0} pausisap st 1s0uenbesuur] oy)
Aum Aposiooid $,Jeu], SS9d0Id SATTBS1D OY} YIM SOIOJIOIUT
yey) xo]dwWI0d Os dq JOA9U P[NoUsS osn NOA AZopOuYdE} oy
corrected! (Timing correction may be adjusted or defeated).
ISTUMOIAUIO?) NOAA UOHISOdWIO)
Any additional notes played will be added into the track
— existing notes are not erased while recording!
"NOSpr] B Oy AONUTJUT yada 0} seq Maz Se] BY] Jas UdAd
uvd NOA palisap JJ souanbes Mou ¥B OVUT sjied ou] [Te Adoo
ATesrewO Ne WI) [IM ONOS ALVAAO JeyIe80} wey}
,deyd,, 0} UOTOUNJ ONOS ALVA ou] asn usy] saouanbes
JENPIAIpUt UI (“949 snJOYD aS1OA) UOTIDIS JIseq Yes
Pl0da1 OF ST ABM JOuIOUY “(812g 666 01 dn) ysnory) ABM
FAST FORWARD, REWIND, and LOCATE controls
may be used at any time to quickly access any location in
your sequence for spot-recording. To overdub a new part,
select a different track and start recording—while you
record, the first track will play in perfect sync (unless you
MUTE it, or SOLO another track). In this way, up to 32
tracks may be overdubbed! All MIDI effects are recorded
including pitch bend, modulation, velocity, aftertouch,
sustain pedal, and program changes!
dU} [fe YORI] YORs p10991 0} ST SUOS B 9789I9 0} ABM SUG,
Editing
SUOS & SUTVAID
To erase a wrong note, simply hold ERASE and press
the note to be erased just before it plays in the sequence—
when played back, it will be gone. Notes may also be
*suoT}oes poJUBMUN
added, erased, or changed using the SINGLE STEP func-
tion. To overdub notes at specific points within a sequence,
SAOUIOI 0} ABM SWS dU} SoyeIodo SUV ALATAaG
Additional Features
“OBPLIq dy} PUB SNIOY PUOdAS dT]] Ud9MIAQ SIDA ISI
simply use LOCATE, FAST FORWARD, or REWIND to
find the desired bar number, then start recording.
ay) Jo Adoo B JJasuT WYSE NOAA afdwexs 10.f UO JUSIN]JIP
The INSERT/COPY function allows you to move bars
from one location to another—in the same sequence or a
different one. For example, you might insert a copy of the
first verse between the second chorus and the bridge.
DELETE BARS operates the same way to remove
unwanted sections,
B IO aouaNbas sues OY} UI—JOY OUP 0} UOTIEIO] 9UO WOT]
$1Bq JAOUI OF NOA sMOTIe WOTIOUNS AdOO/IMASNI OULL
Creating a Song
SUIPIONAI JIVIS Udy) “OQuINU eq porisop ay} puy
One way to create a song is to record each track all the
way through (up to 999 bars). Another way is to record
each basic section (verse, chorus, etc.) in individual
sequences, then use the CREATE SONG function to “chain”
them together. CREATE SONG will then automatically
copy all the parts into a new sequence. If desired, you can
even set the last few bars to repeat infinitely, for a fadeout.
0} CNIMAY 10 CYVM Od LSWA “AEVOOT esn Apduns
Composition Without Compromise
sainjeay [PUOHIPPY
The technology you use should never be so complex that
it interferes with the creative process. Thats precisely why
the LinnSequencer is designed to let you compose, record
and edit while devoting your undivided attention to your
music. See your Linn dealer today for a demonstration!
gouanbas & UTYIIM s]UTOd a1y1dads 3¥ $9100 QnPIOAO OL "UOT}
-ouns dALLS ATONIS 24) Suisn pasueyo Jo pasesa pappe
aq osye ABUT S9]ON U0 9q ]IIM 1 “yoeq podeyd uayM
—aouanbas oy] ul skeyd 71 a10J9q Isnf posers oq 0} d]0U ayy
ssaid pue ASvwug ploy Aydunis jou Suomm & aseso OL
* Simple, easy to learn operation—the 32 character LCD display clearly guides you through all operations. If needed, the
sunipa
HELP button displays additional explanations.
jsesdueyo ureisoid pue fepod ureysns
yonoplalje AWOOTOA UOTyeTNpow pusg youd Surpnyour
pep10del are $199JJ2 TCTIN [WV iPeqqnpseao aq Aeur syoen
Ze 07 dn Kem sie Uy *(foeI} JOyOUR OJOS 10 ALLAN
NOA ssofum) duAS yOaysod ul Avy [[IM Yow] ISI 93 “prooar
NOA 3[IYM—SUIPIOIA LIBIS PU YORI) TUdIOTJIP B JOaTas
*y1ed MOU B QNPIsA0 OL, “SuIps0daJ-jods 10} aouanbes mno0k
UI UOHBIO] Aue ssad0e ATYOIND 0} owt} Aue ye pasn aq AvUE
SJONUOD FLIVOOT pur ANIMA CYVMaYOd LSVd
{SUIPIOSAI {IY posesa JOU se So]OU SuTsTXO—
yous} 3U} OUT poppe aq JIM poteyd sajou yeuonippe Auy
* Non-destructive recording—existing notes are not erased while recording.
¢ Two FOOTSWITCH INPUTS may be assigned to remotely control many of the commonly used functions, including
*(povesjap 10 poysn{pe oq ABW UOTIIII0D BUTUTT]) j{paqoeLI09
ERASE, REPEAT, PLAY/STOP, or LOCATE.
2q ][IM S1OLIe Sur [fe ATUO—patey]d nod Jey Jedy ]],NOA
¢ Iwo TRIGGER OUTPUTS may be programmed to output pulses at any selected note value.
] req 0] punose yoeq sdoo] sduanbas ay] Udy AA “YOu Yor
© Will sync to standard LinnDrum or Linn 9000 sync tone.
§,sa0uaNbas at} O] SUIT) UI preogday [IW] INO Avy usy3
AV'1d pue (YOON ssoid Ayduus aousnbes & p1o09es OF,
© Utilizes ultra high-speed, 8 MHz 80186 16 bit computer internally for FAST operation.
* TEMPO may be specified in BEATS-PER-MINUTE or FRAMES-PER-BEAT at 24, 25, or 30 frames per second,
g0uaNbas & SUIP10I0y]
(even drop frame!)
JONWOD s}JouNaI TeuONdGO e
¢ TEMPO may be entered numerically, adjustable in tenths of a Beat-Per-Minute increments, or by tapping quarter notes
"UOTJEZIUOIYUAS OPOS UIT} FLAWS [euondo e
on the TAP TEMPO button.
sou .sulddoys, noyyM sayelodo pue yoegdvyd ZuLINp S¥IOM NOLLOANNYOO ONIWILL e
¢ TEMPO CHANGES may be programmed into a sequence, with smooth transitions if desired.
¢ Any TIME SIGNATURE may be used, and may be changed within a song.
onqea ory AY
linn
Linn Electronics, Inc.
pojoojes-oid & ye sajou pyoy Aue syeadas ATTeONewWO Ne UOTOUNS [WAdAY OAISNOX e
LSVJ SUnIpS soyeu UOTOUN ASV UA OUlN-[eal SAISNIOXY e
Koy B JO YONO} 941 12 CASOdSNVALL 0g ABU Syde] [Te 10 9UC e
18720 Oxnard Street, Tarzana, CA 91356
(818) 708-8131 TELEX #298949 LINN UR
i ASIP Jed
S9}0U OOOOTT JOA SpfOy puv SpUOdeS UT SBUOS Xa[AUIOD So10}S DALIP YSIP , 74 € ISCJ-CNIN
jSIOZISOUJUAS
stuoydAjod of 0} dn skeyd A[snoourynuls spouueYd [IW 9T JO duo 0} pousisse oq
ABUL YORI] YOR syous) oruoydAjod snoouelnurs 7¢ SuTeJUOS ssouUaNbas QO] OY} JO YORA e
SJONUOS ATWOOT pur GNIMAY GaVM OA
LSVd GYOOde AOLS AV Td YIM Jopsocas ade} Yowsj-N[NU O} eps st UOTLISdO @
LOPNOUT SaINjeoy s[quyIeUlss AUB S.JJ OSN pue UIes] 0} o[duns A[suIzeUe JOA PnJsomod APOUIOITXO
St 1] “UeIOIsNUL feUOIssajoid oY} 10 JOO} soUBULIOJIJAd pue UOTIsOduIOS 11e-dY1-JO-9}e)s B SI IONUANbDaguUT] ay
JOps1odady soUINbIS [GTI YVAL ZE
Jgouanbaguury oy
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
File diff suppressed because it is too large Load Diff
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -1,123 +1,124 @@
The LinnSequencer
32 Track MIDI Sequence Recorder
2A NNII 6F6867# XATALL IE18-80L (818)
The LinnSequencer is a state-of-the-art composition and performance tool for the professional musician. It is
9SEI6 VO “BUBZIRY, “J0aNS PIPUXO OZLEI
“Uy soTUOMOI,q UUrT
extremely powerful, yet amazingly simple to learn and use. Its many remarkable features include:
uu
¢ Operation is similar to multi-track tape recorder with PLAY, STOP, RECORD, FAST
FORWARD, REWIND, and LOCATE controls.
“‘SUOS B UIJIM pasueyo oq ABU pue posn oq AWW AYN IVNOIS AWLL AUV
“parlsop Jr SUOTIISUBI} YIOOUIS YIM “BoueNbas eB OJUI pourtueIZOId 9q ABU SFONWHO OdINAL e
e Each of the 100 sequences contains 32 simultaneous, polyphonic tracks. Each track may
be assigned to one of 16 MIDI channels. Simultaneously plays up to 16 polyphonic
uonng OdNAL dV L 9) uO
synthesizers!
sojou Jayienb Suiddy} Aq 10 syUSTIOIOUI oINUTIAI-J8g-Jesg & JO sys} UL ofquisn(pe ATTeouIAUINU paiajus oq ABU OdINALL e
¢ Ultra-fast 3%” disk drive stores complex songs in seconds and holds over 110,000 notes
(jouer doup u3a9)
per disk!
“puooes Jed souely O€ 10 “SZ “pz 18 [LVAG-MAd-SHN VU 10 ALOANIWAAd-SLVAd U! patyoeds aq kewl OAL «
uoTe1odo [SVx JO} Aj[eusoyUT JoyndUIOd 11g 9] 98108 ZHI 8g poeds-ysry Bann soz] e
¢ One or all tracks may be TRANSPOSED at the touch of a key.
e Exclusive real-time ERASE function makes editing FAST.
* Exclusive REPEAT function automatically repeats any held notes at a pre-selected
"9U0} DUAS 0006 UUL] Jo wNIqUUr] prepue}s 0} OUAS [ITAA ©
rhythmic value.
“ONYBA 9}OU poloapes Aue Je sas—nd jndyno 07 pewureigold 3q ACW SL Ad LNO YADONAL OML
¢ TIMING CORRECTION works during playback and operates without chopping notes.
"ALVOOT 10 GOLS/AV 1d LWddad “ASV
¢ Optional SMPTE time code synchronization.
SUIpNpoUr suOTIOUN] posn A[UOUILUOS 94] JO AUBUT [O1]UOD AJ9]OWIAI 0} PousIsse oq ACUI ST AdNI HOLIMSLOO OME «
“SUIPIONAI I[IYM P2sesd JOU Iv $3}OU BUTISIXO—ZUIPIOIA SATON.ASOP-UON
© Optional remote control.
suoneurldxa peuoyippe sdeydsip uowng g1TqH
Recording a Sequence
oy] pepsau JI suoneiodo [ye yYsnosy] NOA sapins ApIespo Avfdsip QO] Joey Z7¢ 9y3—uoeIodo Urea] 0} Ased aus «
To record a sequence, simply press RECORD and PLAY,
then play your MIDI keyboard in time to the Sequencers
click track. When the sequence loops back around to bar 1,
you ll hear what you played—only all timing errors will be
jUorel]suowtap & IO} Aepol Jayeap uur] INOA dag dISHUL
INOA 0} UONUS}]¥ PaplAIPUN INOA SUTJOASp ITY ps pue
p1osai asoduod no Jay 0} pausisap st 1s0uenbesuur] oy)
Aum Aposiooid $,Jeu], SS9d0Id SATTBS1D OY} YIM SOIOJIOIUT
yey) xo]dwWI0d Os dq JOA9U P[NoUsS osn NOA AZopOuYdE} oy
corrected! (Timing correction may be adjusted or defeated).
ISTUMOIAUIO?) NOAA UOHISOdWIO)
Any additional notes played will be added into the track
— existing notes are not erased while recording!
"NOSpr] B Oy AONUTJUT yada 0} seq Maz Se] BY] Jas UdAd
uvd NOA palisap JJ souanbes Mou ¥B OVUT sjied ou] [Te Adoo
ATesrewO Ne WI) [IM ONOS ALVAAO JeyIe80} wey}
,deyd,, 0} UOTOUNJ ONOS ALVA ou] asn usy] saouanbes
JENPIAIpUt UI (“949 snJOYD aS1OA) UOTIDIS JIseq Yes
Pl0da1 OF ST ABM JOuIOUY “(812g 666 01 dn) ysnory) ABM
FAST FORWARD, REWIND, and LOCATE controls
may be used at any time to quickly access any location in
your sequence for spot-recording. To overdub a new part,
select a different track and start recording—while you
record, the first track will play in perfect sync (unless you
MUTE it, or SOLO another track). In this way, up to 32
tracks may be overdubbed! All MIDI effects are recorded
including pitch bend, modulation, velocity, aftertouch,
sustain pedal, and program changes!
dU} [fe YORI] YORs p10991 0} ST SUOS B 9789I9 0} ABM SUG,
Editing
SUOS & SUTVAID
To erase a wrong note, simply hold ERASE and press
the note to be erased just before it plays in the sequence—
when played back, it will be gone. Notes may also be
*suoT}oes poJUBMUN
added, erased, or changed using the SINGLE STEP func-
tion. To overdub notes at specific points within a sequence,
SAOUIOI 0} ABM SWS dU} SoyeIodo SUV ALATAaG
Additional Features
“OBPLIq dy} PUB SNIOY PUOdAS dT]] Ud9MIAQ SIDA ISI
simply use LOCATE, FAST FORWARD, or REWIND to
find the desired bar number, then start recording.
ay) Jo Adoo B JJasuT WYSE NOAA afdwexs 10.f UO JUSIN]JIP
The INSERT/COPY function allows you to move bars
from one location to another—in the same sequence or a
different one. For example, you might insert a copy of the
first verse between the second chorus and the bridge.
DELETE BARS operates the same way to remove
unwanted sections,
B IO aouaNbas sues OY} UI—JOY OUP 0} UOTIEIO] 9UO WOT]
$1Bq JAOUI OF NOA sMOTIe WOTIOUNS AdOO/IMASNI OULL
Creating a Song
SUIPIONAI JIVIS Udy) “OQuINU eq porisop ay} puy
One way to create a song is to record each track all the
way through (up to 999 bars). Another way is to record
each basic section (verse, chorus, etc.) in individual
sequences, then use the CREATE SONG function to “chain”
them together. CREATE SONG will then automatically
copy all the parts into a new sequence. If desired, you can
even set the last few bars to repeat infinitely, for a fadeout.
0} CNIMAY 10 CYVM Od LSWA “AEVOOT esn Apduns
Composition Without Compromise
sainjeay [PUOHIPPY
The technology you use should never be so complex that
it interferes with the creative process. Thats precisely why
the LinnSequencer is designed to let you compose, record
and edit while devoting your undivided attention to your
music. See your Linn dealer today for a demonstration!
gouanbas & UTYIIM s]UTOd a1y1dads 3¥ $9100 QnPIOAO OL "UOT}
-ouns dALLS ATONIS 24) Suisn pasueyo Jo pasesa pappe
aq osye ABUT S9]ON U0 9q ]IIM 1 “yoeq podeyd uayM
—aouanbas oy] ul skeyd 71 a10J9q Isnf posers oq 0} d]0U ayy
ssaid pue ASvwug ploy Aydunis jou Suomm & aseso OL
* Simple, easy to learn operation—the 32 character LCD display clearly guides you through all operations. If needed, the
sunipa
HELP button displays additional explanations.
jsesdueyo ureisoid pue fepod ureysns
yonoplalje AWOOTOA UOTyeTNpow pusg youd Surpnyour
pep10del are $199JJ2 TCTIN [WV iPeqqnpseao aq Aeur syoen
Ze 07 dn Kem sie Uy *(foeI} JOyOUR OJOS 10 ALLAN
NOA ssofum) duAS yOaysod ul Avy [[IM Yow] ISI 93 “prooar
NOA 3[IYM—SUIPIOIA LIBIS PU YORI) TUdIOTJIP B JOaTas
*y1ed MOU B QNPIsA0 OL, “SuIps0daJ-jods 10} aouanbes mno0k
UI UOHBIO] Aue ssad0e ATYOIND 0} owt} Aue ye pasn aq AvUE
SJONUOD FLIVOOT pur ANIMA CYVMaYOd LSVd
{SUIPIOSAI {IY posesa JOU se So]OU SuTsTXO—
yous} 3U} OUT poppe aq JIM poteyd sajou yeuonippe Auy
*(povesjap 10 poysn{pe oq ABW UOTIIII0D BUTUTT]) j{paqoeLI09
2q ][IM S1OLIe Sur [fe ATUO—patey]d nod Jey Jedy ]],NOA
] req 0] punose yoeq sdoo] sduanbas ay] Udy AA “YOu Yor
§,sa0uaNbas at} O] SUIT) UI preogday [IW] INO Avy usy3
AV'1d pue (YOON ssoid Ayduus aousnbes & p1o09es OF,
* Non-destructive recording—existing notes are not erased while recording.
¢ Two FOOTSWITCH INPUTS may be assigned to remotely control many of the commonly used functions, including
g0uaNbas & SUIP10I0y]
ERASE, REPEAT, PLAY/STOP, or LOCATE.
JONWOD s}JouNaI TeuONdGO e
¢ Iwo TRIGGER OUTPUTS may be programmed to output pulses at any selected note value.
"UOTJEZIUOIYUAS OPOS UIT} FLAWS [euondo e
© Will sync to standard LinnDrum or Linn 9000 sync tone.
sou .sulddoys, noyyM sayelodo pue yoegdvyd ZuLINp S¥IOM NOLLOANNYOO ONIWILL e
© Utilizes ultra high-speed, 8 MHz 80186 16 bit computer internally for FAST operation.
* TEMPO may be specified in BEATS-PER-MINUTE or FRAMES-PER-BEAT at 24, 25, or 30 frames per second,
onqea ory AY
(even drop frame!)
pojoojes-oid & ye sajou pyoy Aue syeadas ATTeONewWO Ne UOTOUNS [WAdAY OAISNOX e
LSVJ SUnIpS soyeu UOTOUN ASV UA OUlN-[eal SAISNIOXY e
Koy B JO YONO} 941 12 CASOdSNVALL 0g ABU Syde] [Te 10 9UC e
¢ TEMPO may be entered numerically, adjustable in tenths of a Beat-Per-Minute increments, or by tapping quarter notes
i ASIP Jed
on the TAP TEMPO button.
S9}0U OOOOTT JOA SpfOy puv SpUOdeS UT SBUOS Xa[AUIOD So10}S DALIP YSIP , 74 € ISCJ-CNIN
¢ TEMPO CHANGES may be programmed into a sequence, with smooth transitions if desired.
¢ Any TIME SIGNATURE may be used, and may be changed within a song.
jSIOZISOUJUAS
linn
Linn Electronics, Inc.
stuoydAjod of 0} dn skeyd A[snoourynuls spouueYd [IW 9T JO duo 0} pousisse oq
ABUL YORI] YOR syous) oruoydAjod snoouelnurs 7¢ SuTeJUOS ssouUaNbas QO] OY} JO YORA e
18720 Oxnard Street, Tarzana, CA 91356
(818) 708-8131 TELEX #298949 LINN UR
SJONUOS ATWOOT pur GNIMAY GaVM OA
LSVd GYOOde AOLS AV Td YIM Jopsocas ade} Yowsj-N[NU O} eps st UOTLISdO @
LOPNOUT SaINjeoy s[quyIeUlss AUB S.JJ OSN pue UIes] 0} o[duns A[suIzeUe JOA PnJsomod APOUIOITXO
St 1] “UeIOIsNUL feUOIssajoid oY} 10 JOO} soUBULIOJIJAd pue UOTIsOduIOS 11e-dY1-JO-9}e)s B SI IONUANbDaguUT] ay
JOps1odady soUINbIS [GTI YVAL ZE
Jgouanbaguury oy
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -1 +0,0 @@
Warning. Invalid resolution 0 dpi. Using 70 instead.
@@ -1 +0,0 @@
Warning. Invalid resolution 0 dpi. Using 70 instead.
@@ -1 +0,0 @@
Warning. Invalid resolution 0 dpi. Using 70 instead.
@@ -1 +0,0 @@
Warning. Invalid resolution 0 dpi. Using 70 instead.
@@ -4,19 +4,19 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name='ocr-system' content='tesseract 4.0.0' />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<meta name='ocr-system' content='tesseract 4.1.1' />
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word ocrp_wconf'/>
</head>
<body>
<div class='ocr_page' id='page_1' title='image "/var/folders/2s/7t022mgj0h5cprbq0dtb1ksm0000gn/T/com.github.ocrmypdf.y8xsq1f7/000001_ocr.png"; bbox 0 0 2550 3300; ppageno 0'>
</head>
<body>
<div class='ocr_page' id='page_1' title='image "/var/folders/2s/7t022mgj0h5cprbq0dtb1ksm0000gn/T/com.github.ocrmypdf.ucmqv4tt/000001_ocr.png"; bbox 0 0 2550 3300; ppageno 0'>
<div class='ocr_carea' id='block_1_1' title="bbox 582 131 1968 303">
<p class='ocr_par' id='par_1_1' lang='eng' title="bbox 582 131 1968 303">
<span class='ocr_line' id='line_1_1' title="bbox 882 131 1657 217; baseline 0.001 -17; x_size 85; x_descenders 16; x_ascenders 19">
<span class='ocr_header' id='line_1_1' title="bbox 882 131 1657 217; baseline 0.001 -17; x_size 85; x_descenders 16; x_ascenders 19">
<span class='ocrx_word' id='word_1_1' title='bbox 882 132 1036 202; x_wconf 93'>The</span>
<span class='ocrx_word' id='word_1_2' title='bbox 1061 131 1657 217; x_wconf 91'>LinnSequencer</span>
</span>
<span class='ocr_line' id='line_1_2' title="bbox 582 215 1968 303; baseline 0 -17; x_size 87; x_descenders 16; x_ascenders 21">
<span class='ocr_header' id='line_1_2' title="bbox 582 215 1968 303; baseline 0 -17; x_size 87; x_descenders 16; x_ascenders 21">
<span class='ocrx_word' id='word_1_3' title='bbox 582 215 674 286; x_wconf 96'>32</span>
<span class='ocrx_word' id='word_1_4' title='bbox 697 218 923 288; x_wconf 95'>Track</span>
<span class='ocrx_word' id='word_1_5' title='bbox 948 218 1181 287; x_wconf 96'>MIDI</span>
@@ -27,27 +27,27 @@
</div>
<div class='ocr_carea' id='block_1_2' title="bbox 347 380 2188 574">
<p class='ocr_par' id='par_1_2' lang='eng' title="bbox 347 380 2188 423">
<span class='ocr_line' id='line_1_3' title="bbox 347 380 2188 423; baseline -0.001 -12; x_size 38; x_descenders 8; x_ascenders 10">
<span class='ocr_header' id='line_1_3' title="bbox 347 380 2188 423; baseline -0.001 -12; x_size 38; x_descenders 8; x_ascenders 10">
<span class='ocrx_word' id='word_1_8' title='bbox 347 380 412 410; x_wconf 93'>The</span>
<span class='ocrx_word' id='word_1_9' title='bbox 424 380 661 417; x_wconf 91'>LinnSequencer</span>
<span class='ocrx_word' id='word_1_10' title='bbox 663 380 712 411; x_wconf 96'>is</span>
<span class='ocrx_word' id='word_1_9' title='bbox 424 380 676 417; x_wconf 92'>LinnSequencer</span>
<span class='ocrx_word' id='word_1_10' title='bbox 688 380 712 411; x_wconf 96'>is</span>
<span class='ocrx_word' id='word_1_11' title='bbox 724 390 743 411; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_12' title='bbox 754 381 1005 423; x_wconf 96'>state-of-the-art</span>
<span class='ocrx_word' id='word_1_13' title='bbox 1017 380 1226 418; x_wconf 96'>composition</span>
<span class='ocrx_word' id='word_1_14' title='bbox 1238 381 1299 411; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_15' title='bbox 1311 380 1507 418; x_wconf 96'>performance</span>
<span class='ocrx_word' id='word_1_16' title='bbox 1509 385 1591 411; x_wconf 96'>tool</span>
<span class='ocrx_word' id='word_1_17' title='bbox 1593 380 1663 411; x_wconf 97'>for</span>
<span class='ocrx_word' id='word_1_15' title='bbox 1311 380 1525 418; x_wconf 96'>performance</span>
<span class='ocrx_word' id='word_1_16' title='bbox 1536 380 1602 411; x_wconf 96'>tool</span>
<span class='ocrx_word' id='word_1_17' title='bbox 1615 380 1663 411; x_wconf 97'>for</span>
<span class='ocrx_word' id='word_1_18' title='bbox 1674 381 1725 410; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_19' title='bbox 1737 380 1940 417; x_wconf 96'>professional</span>
<span class='ocrx_word' id='word_1_20' title='bbox 1952 380 2103 411; x_wconf 96'>musician.</span>
<span class='ocrx_word' id='word_1_21' title='bbox 2106 381 2152 410; x_wconf 96'>It</span>
<span class='ocrx_word' id='word_1_19' title='bbox 1737 380 1940 417; x_wconf 95'>professional</span>
<span class='ocrx_word' id='word_1_20' title='bbox 1952 380 2112 411; x_wconf 96'>musician.</span>
<span class='ocrx_word' id='word_1_21' title='bbox 2127 381 2152 410; x_wconf 96'>It</span>
<span class='ocrx_word' id='word_1_22' title='bbox 2164 380 2188 410; x_wconf 96'>is</span>
</span>
</p>
<p class='ocr_par' id='par_1_3' lang='eng' title="bbox 347 430 1988 468">
<span class='ocr_line' id='line_1_4' title="bbox 347 430 1988 468; baseline 0 -8; x_size 37; x_descenders 7; x_ascenders 9">
<span class='ocr_header' id='line_1_4' title="bbox 347 430 1988 468; baseline 0 -8; x_size 37; x_descenders 7; x_ascenders 9">
<span class='ocrx_word' id='word_1_23' title='bbox 347 430 507 467; x_wconf 96'>extremely</span>
<span class='ocrx_word' id='word_1_24' title='bbox 518 430 677 467; x_wconf 96'>powerful,</span>
<span class='ocrx_word' id='word_1_25' title='bbox 691 435 739 467; x_wconf 96'>yet</span>
@@ -66,7 +66,7 @@
</p>
<p class='ocr_par' id='par_1_4' lang='eng' title="bbox 350 482 2093 574">
<span class='ocr_line' id='line_1_5' title="bbox 350 482 2093 527; baseline 0 -9; x_size 43; x_descenders 7; x_ascenders 11">
<span class='ocr_header' id='line_1_5' title="bbox 350 482 2093 527; baseline 0 -9; x_size 43; x_descenders 7; x_ascenders 11">
<span class='ocrx_word' id='word_1_37' title='bbox 350 490 368 508; x_wconf 73'>¢</span>
<span class='ocrx_word' id='word_1_38' title='bbox 383 482 585 526; x_wconf 95'>Operation</span>
<span class='ocrx_word' id='word_1_39' title='bbox 598 482 627 518; x_wconf 96'>is</span>
@@ -81,18 +81,18 @@
<span class='ocrx_word' id='word_1_48' title='bbox 1741 483 1957 525; x_wconf 96'>RECORD,</span>
<span class='ocrx_word' id='word_1_49' title='bbox 1974 483 2093 518; x_wconf 96'>FAST</span>
</span>
<span class='ocr_line' id='line_1_6' title="bbox 383 532 1345 574; baseline 0.001 -7; x_size 43; x_descenders 7; x_ascenders 11">
<span class='ocr_header' id='line_1_6' title="bbox 383 532 1345 574; baseline 0.001 -7; x_size 43; x_descenders 7; x_ascenders 11">
<span class='ocrx_word' id='word_1_50' title='bbox 383 532 635 574; x_wconf 96'>FORWARD,</span>
<span class='ocrx_word' id='word_1_51' title='bbox 652 532 865 574; x_wconf 95'>REWIND,</span>
<span class='ocrx_word' id='word_1_52' title='bbox 882 532 956 568; x_wconf 95'>and</span>
<span class='ocrx_word' id='word_1_53' title='bbox 971 532 1131 568; x_wconf 95'>LOCATE</span>
<span class='ocrx_word' id='word_1_54' title='bbox 1132 532 1345 568; x_wconf 95'>controls.</span>
<span class='ocrx_word' id='word_1_53' title='bbox 971 532 1163 568; x_wconf 95'>LOCATE</span>
<span class='ocrx_word' id='word_1_54' title='bbox 1177 532 1345 568; x_wconf 95'>controls.</span>
</span>
</p>
</div>
<div class='ocr_carea' id='block_1_3' title="bbox 349 589 2136 685">
<p class='ocr_par' id='par_1_5' lang='eng' title="bbox 349 589 2136 685">
<span class='ocr_line' id='line_1_7' title="bbox 349 589 2136 634; baseline 0.001 -9; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocr_header' id='line_1_7' title="bbox 349 589 2136 634; baseline 0.001 -9; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocrx_word' id='word_1_55' title='bbox 349 597 368 615; x_wconf 59'>e</span>
<span class='ocrx_word' id='word_1_56' title='bbox 383 590 482 625; x_wconf 96'>Each</span>
<span class='ocrx_word' id='word_1_57' title='bbox 496 589 539 625; x_wconf 96'>of</span>
@@ -108,7 +108,7 @@
<span class='ocrx_word' id='word_1_67' title='bbox 1934 590 2035 626; x_wconf 96'>track</span>
<span class='ocrx_word' id='word_1_68' title='bbox 2050 600 2136 634; x_wconf 96'>may</span>
</span>
<span class='ocr_line' id='line_1_8' title="bbox 383 639 2022 685; baseline 0.001 -10; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocr_header' id='line_1_8' title="bbox 383 639 2022 685; baseline 0.001 -10; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocrx_word' id='word_1_69' title='bbox 383 639 428 675; x_wconf 95'>be</span>
<span class='ocrx_word' id='word_1_70' title='bbox 442 639 607 684; x_wconf 95'>assigned</span>
<span class='ocrx_word' id='word_1_71' title='bbox 621 645 659 676; x_wconf 96'>to</span>
@@ -135,7 +135,7 @@
</div>
<div class='ocr_carea' id='block_1_5' title="bbox 349 748 2117 793">
<p class='ocr_par' id='par_1_7' lang='eng' title="bbox 349 748 2117 793">
<span class='ocr_line' id='line_1_10' title="bbox 349 748 2117 793; baseline 0 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocr_header' id='line_1_10' title="bbox 349 748 2117 793; baseline 0 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocrx_word' id='word_1_84' title='bbox 349 755 367 774; x_wconf 58'>¢</span>
<span class='ocrx_word' id='word_1_85' title='bbox 383 748 573 784; x_wconf 91'>Ultra-fast</span>
<span class='ocrx_word' id='word_1_86' title='bbox 588 749 677 784; x_wconf 22'>3%”</span>
@@ -147,8 +147,8 @@
<span class='ocrx_word' id='word_1_92' title='bbox 1330 748 1368 784; x_wconf 96'>in</span>
<span class='ocrx_word' id='word_1_93' title='bbox 1382 748 1535 785; x_wconf 96'>seconds</span>
<span class='ocrx_word' id='word_1_94' title='bbox 1550 748 1624 784; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_95' title='bbox 1638 748 1728 784; x_wconf 96'>holds</span>
<span class='ocrx_word' id='word_1_96' title='bbox 1730 759 1844 784; x_wconf 96'>over</span>
<span class='ocrx_word' id='word_1_95' title='bbox 1638 748 1746 784; x_wconf 96'>holds</span>
<span class='ocrx_word' id='word_1_96' title='bbox 1761 759 1844 784; x_wconf 96'>over</span>
<span class='ocrx_word' id='word_1_97' title='bbox 1859 749 2000 791; x_wconf 96'>110,000</span>
<span class='ocrx_word' id='word_1_98' title='bbox 2013 753 2117 784; x_wconf 96'>notes</span>
</span>
@@ -164,23 +164,23 @@
</div>
<div class='ocr_carea' id='block_1_7' title="bbox 349 855 2030 1016">
<p class='ocr_par' id='par_1_9' lang='eng' title="bbox 349 855 2030 1016">
<span class='ocr_line' id='line_1_12' title="bbox 350 855 1638 900; baseline 0.001 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocr_header' id='line_1_12' title="bbox 350 855 1638 900; baseline 0.001 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocrx_word' id='word_1_101' title='bbox 350 863 367 881; x_wconf 45'>¢</span>
<span class='ocrx_word' id='word_1_102' title='bbox 383 856 444 891; x_wconf 95'>One</span>
<span class='ocrx_word' id='word_1_103' title='bbox 445 866 502 891; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_104' title='bbox 503 855 580 891; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_102' title='bbox 383 856 464 891; x_wconf 95'>One</span>
<span class='ocrx_word' id='word_1_103' title='bbox 478 866 520 891; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_104' title='bbox 534 855 580 891; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_105' title='bbox 594 856 712 892; x_wconf 95'>tracks</span>
<span class='ocrx_word' id='word_1_106' title='bbox 726 867 811 900; x_wconf 95'>may</span>
<span class='ocrx_word' id='word_1_107' title='bbox 823 856 869 892; x_wconf 81'>be</span>
<span class='ocrx_word' id='word_1_108' title='bbox 882 856 1175 892; x_wconf 96'>TRANSPOSED</span>
<span class='ocrx_word' id='word_1_109' title='bbox 1178 857 1264 892; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_108' title='bbox 882 856 1212 892; x_wconf 96'>TRANSPOSED</span>
<span class='ocrx_word' id='word_1_109' title='bbox 1227 861 1264 892; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_110' title='bbox 1277 856 1338 892; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_111' title='bbox 1351 856 1463 892; x_wconf 96'>touch</span>
<span class='ocrx_word' id='word_1_112' title='bbox 1477 867 1501 892; x_wconf 96'>of</span>
<span class='ocrx_word' id='word_1_113' title='bbox 1502 856 1554 892; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_112' title='bbox 1477 856 1520 892; x_wconf 96'>of</span>
<span class='ocrx_word' id='word_1_113' title='bbox 1531 867 1554 892; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_114' title='bbox 1568 856 1638 900; x_wconf 96'>key.</span>
</span>
<span class='ocr_line' id='line_1_13' title="bbox 350 913 1535 958; baseline 0.001 -9; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocr_header' id='line_1_13' title="bbox 350 913 1535 958; baseline 0.001 -9; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocrx_word' id='word_1_115' title='bbox 350 921 367 939; x_wconf 45'>e</span>
<span class='ocrx_word' id='word_1_116' title='bbox 383 913 568 950; x_wconf 96'>Exclusive</span>
<span class='ocrx_word' id='word_1_117' title='bbox 581 913 756 950; x_wconf 96'>real-time</span>
@@ -190,7 +190,7 @@
<span class='ocrx_word' id='word_1_121' title='bbox 1266 914 1400 958; x_wconf 96'>editing</span>
<span class='ocrx_word' id='word_1_122' title='bbox 1414 915 1535 950; x_wconf 95'>FAST.</span>
</span>
<span class='ocr_line' id='line_1_14' title="bbox 349 971 2030 1016; baseline 0.001 -10; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocr_header' id='line_1_14' title="bbox 349 971 2030 1016; baseline 0.001 -10; x_size 44; x_descenders 8; x_ascenders 11">
<span class='ocrx_word' id='word_1_123' title='bbox 349 979 367 997; x_wconf 0'>*</span>
<span class='ocrx_word' id='word_1_124' title='bbox 382 971 568 1007; x_wconf 95'>Exclusive</span>
<span class='ocrx_word' id='word_1_125' title='bbox 582 972 773 1007; x_wconf 96'>REPEAT</span>
@@ -209,18 +209,18 @@
<div class='ocr_carea' id='block_1_8' title="bbox 382 1021 689 1065">
<p class='ocr_par' id='par_1_10' lang='eng' title="bbox 382 1021 689 1065">
<span class='ocr_line' id='line_1_15' title="bbox 382 1021 689 1065; baseline 0.003 -8; x_size 45; x_descenders 8; x_ascenders 12">
<span class='ocrx_word' id='word_1_135' title='bbox 382 1021 564 1065; x_wconf 95'>rhythmic</span>
<span class='ocrx_word' id='word_1_135' title='bbox 382 1021 564 1065; x_wconf 96'>rhythmic</span>
<span class='ocrx_word' id='word_1_136' title='bbox 577 1021 689 1058; x_wconf 96'>value.</span>
</span>
</p>
</div>
<div class='ocr_carea' id='block_1_9' title="bbox 349 1080 2174 1125">
<p class='ocr_par' id='par_1_11' lang='eng' title="bbox 349 1080 2174 1125">
<span class='ocr_line' id='line_1_16' title="bbox 349 1080 2174 1125; baseline 0.001 -11; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocr_header' id='line_1_16' title="bbox 349 1080 2174 1125; baseline 0.001 -11; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocrx_word' id='word_1_137' title='bbox 349 1087 367 1105; x_wconf 80'>¢</span>
<span class='ocrx_word' id='word_1_138' title='bbox 382 1080 567 1115; x_wconf 95'>TIMING</span>
<span class='ocrx_word' id='word_1_139' title='bbox 582 1080 870 1116; x_wconf 95'>CORRECTION</span>
<span class='ocrx_word' id='word_1_140' title='bbox 872 1080 1041 1116; x_wconf 96'>works</span>
<span class='ocrx_word' id='word_1_139' title='bbox 582 1080 908 1116; x_wconf 95'>CORRECTION</span>
<span class='ocrx_word' id='word_1_140' title='bbox 921 1080 1041 1116; x_wconf 96'>works</span>
<span class='ocrx_word' id='word_1_141' title='bbox 1056 1080 1186 1124; x_wconf 96'>during</span>
<span class='ocrx_word' id='word_1_142' title='bbox 1199 1080 1378 1124; x_wconf 96'>playback</span>
<span class='ocrx_word' id='word_1_143' title='bbox 1392 1080 1466 1116; x_wconf 96'>and</span>
@@ -233,11 +233,11 @@
</div>
<div class='ocr_carea' id='block_1_10' title="bbox 349 1137 1287 1182">
<p class='ocr_par' id='par_1_12' lang='eng' title="bbox 349 1137 1287 1182">
<span class='ocr_line' id='line_1_17' title="bbox 349 1137 1287 1182; baseline 0.001 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocr_textfloat' id='line_1_17' title="bbox 349 1137 1287 1182; baseline 0.001 -9; x_size 45; x_descenders 9; x_ascenders 11">
<span class='ocrx_word' id='word_1_148' title='bbox 349 1145 367 1163; x_wconf 80'>¢</span>
<span class='ocrx_word' id='word_1_149' title='bbox 382 1137 560 1182; x_wconf 95'>Optional</span>
<span class='ocrx_word' id='word_1_150' title='bbox 575 1138 708 1174; x_wconf 96'>SMPTE</span>
<span class='ocrx_word' id='word_1_151' title='bbox 709 1138 839 1174; x_wconf 96'>time</span>
<span class='ocrx_word' id='word_1_150' title='bbox 575 1138 739 1174; x_wconf 96'>SMPTE</span>
<span class='ocrx_word' id='word_1_151' title='bbox 752 1138 839 1174; x_wconf 96'>time</span>
<span class='ocrx_word' id='word_1_152' title='bbox 853 1138 945 1174; x_wconf 95'>code</span>
<span class='ocrx_word' id='word_1_153' title='bbox 959 1138 1287 1182; x_wconf 96'>synchronization.</span>
</span>
@@ -246,7 +246,7 @@
<div class='ocr_carea' id='block_1_11' title="bbox 349 1195 874 1240">
<p class='ocr_par' id='par_1_13' lang='eng' title="bbox 349 1195 874 1240">
<span class='ocr_line' id='line_1_18' title="bbox 349 1195 874 1240; baseline 0 -8; x_size 45; x_descenders 8; x_ascenders 12">
<span class='ocrx_word' id='word_1_154' title='bbox 349 1203 367 1222; x_wconf 70'>©</span>
<span class='ocrx_word' id='word_1_154' title='bbox 349 1203 367 1222; x_wconf 74'>©</span>
<span class='ocrx_word' id='word_1_155' title='bbox 382 1195 560 1240; x_wconf 96'>Optional</span>
<span class='ocrx_word' id='word_1_156' title='bbox 573 1201 709 1233; x_wconf 96'>remote</span>
<span class='ocrx_word' id='word_1_157' title='bbox 723 1196 874 1233; x_wconf 95'>control.</span>
@@ -278,9 +278,9 @@
<span class='ocrx_word' id='word_1_170' title='bbox 346 1379 411 1406; x_wconf 96'>then</span>
<span class='ocrx_word' id='word_1_171' title='bbox 422 1378 483 1412; x_wconf 96'>play</span>
<span class='ocrx_word' id='word_1_172' title='bbox 493 1387 562 1412; x_wconf 96'>your</span>
<span class='ocrx_word' id='word_1_173' title='bbox 572 1379 646 1405; x_wconf 96'>MIDI</span>
<span class='ocrx_word' id='word_1_174' title='bbox 649 1379 792 1412; x_wconf 96'>keyboard</span>
<span class='ocrx_word' id='word_1_175' title='bbox 792 1379 848 1406; x_wconf 95'>in</span>
<span class='ocrx_word' id='word_1_173' title='bbox 572 1379 659 1405; x_wconf 96'>MIDI</span>
<span class='ocrx_word' id='word_1_174' title='bbox 671 1379 810 1412; x_wconf 96'>keyboard</span>
<span class='ocrx_word' id='word_1_175' title='bbox 821 1379 848 1406; x_wconf 95'>in</span>
<span class='ocrx_word' id='word_1_176' title='bbox 858 1379 923 1406; x_wconf 95'>time</span>
<span class='ocrx_word' id='word_1_177' title='bbox 934 1384 963 1406; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_178' title='bbox 974 1379 1019 1406; x_wconf 93'>the</span>
@@ -294,14 +294,14 @@
<span class='ocrx_word' id='word_1_184' title='bbox 676 1426 810 1452; x_wconf 96'>sequence</span>
<span class='ocrx_word' id='word_1_185' title='bbox 821 1419 901 1452; x_wconf 96'>loops</span>
<span class='ocrx_word' id='word_1_186' title='bbox 912 1419 983 1446; x_wconf 96'>back</span>
<span class='ocrx_word' id='word_1_187' title='bbox 995 1427 1082 1446; x_wconf 96'>around</span>
<span class='ocrx_word' id='word_1_188' title='bbox 1084 1419 1141 1446; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_189' title='bbox 1152 1419 1189 1446; x_wconf 96'>bar</span>
<span class='ocrx_word' id='word_1_190' title='bbox 1189 1419 1232 1450; x_wconf 74'>1,</span>
<span class='ocrx_word' id='word_1_187' title='bbox 995 1419 1101 1446; x_wconf 96'>around</span>
<span class='ocrx_word' id='word_1_188' title='bbox 1112 1423 1141 1446; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_189' title='bbox 1152 1419 1201 1446; x_wconf 96'>bar</span>
<span class='ocrx_word' id='word_1_190' title='bbox 1213 1419 1232 1450; x_wconf 74'>1,</span>
</span>
<span class='ocr_line' id='line_1_23' title="bbox 346 1457 1223 1491; baseline 0.001 -7; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_191' title='bbox 346 1465 400 1490; x_wconf 15'>you</span>
<span class='ocrx_word' id='word_1_192' title='bbox 404 1457 430 1484; x_wconf 15'>ll</span>
<span class='ocrx_word' id='word_1_191' title='bbox 346 1457 430 1490; x_wconf 14'>you</span>
<span class='ocrx_word' id='word_1_192' title='bbox 406 1453 436 1496; x_wconf 14'>ll</span>
<span class='ocrx_word' id='word_1_193' title='bbox 441 1457 506 1485; x_wconf 96'>hear</span>
<span class='ocrx_word' id='word_1_194' title='bbox 517 1458 590 1485; x_wconf 96'>what</span>
<span class='ocrx_word' id='word_1_195' title='bbox 600 1466 654 1491; x_wconf 93'>you</span>
@@ -316,7 +316,7 @@
</div>
<div class='ocr_carea' id='block_1_13' title="bbox 346 1497 1245 1531">
<p class='ocr_par' id='par_1_16' lang='eng' title="bbox 346 1497 1245 1531">
<span class='ocr_line' id='line_1_24' title="bbox 346 1497 1245 1531; baseline 0.001 -7; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocr_textfloat' id='line_1_24' title="bbox 346 1497 1245 1531; baseline 0.001 -7; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocrx_word' id='word_1_202' title='bbox 346 1497 494 1524; x_wconf 96'>corrected!</span>
<span class='ocrx_word' id='word_1_203' title='bbox 508 1497 628 1530; x_wconf 96'>(Timing</span>
<span class='ocrx_word' id='word_1_204' title='bbox 638 1497 791 1525; x_wconf 95'>correction</span>
@@ -343,8 +343,8 @@
<span class='ocrx_word' id='word_1_219' title='bbox 1111 1537 1186 1564; x_wconf 96'>track</span>
</span>
<span class='ocr_line' id='line_1_26' title="bbox 347 1575 1052 1610; baseline 0.001 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_220' title='bbox 347 1591 381 1594; x_wconf 0'>—</span>
<span class='ocrx_word' id='word_1_221' title='bbox 382 1575 495 1609; x_wconf 0'>existing</span>
<span class='ocrx_word' id='word_1_220' title='bbox 347 1591 372 1594; x_wconf 0'>—</span>
<span class='ocrx_word' id='word_1_221' title='bbox 371 1575 495 1609; x_wconf 0'>existing</span>
<span class='ocrx_word' id='word_1_222' title='bbox 505 1580 582 1603; x_wconf 96'>notes</span>
<span class='ocrx_word' id='word_1_223' title='bbox 593 1584 637 1603; x_wconf 96'>are</span>
<span class='ocrx_word' id='word_1_224' title='bbox 648 1580 696 1603; x_wconf 97'>not</span>
@@ -358,10 +358,10 @@
<span class='ocr_line' id='line_1_27' title="bbox 384 1616 1199 1648; baseline 0.001 -6; x_size 32; x_descenders 5; x_ascenders 8">
<span class='ocrx_word' id='word_1_228' title='bbox 384 1616 471 1642; x_wconf 96'>FAST</span>
<span class='ocrx_word' id='word_1_229' title='bbox 481 1616 671 1648; x_wconf 96'>FORWARD,</span>
<span class='ocrx_word' id='word_1_230' title='bbox 684 1617 838 1643; x_wconf 95'>REWIND,</span>
<span class='ocrx_word' id='word_1_231' title='bbox 839 1616 912 1648; x_wconf 95'>and</span>
<span class='ocrx_word' id='word_1_232' title='bbox 924 1616 1045 1643; x_wconf 96'>LOCATE</span>
<span class='ocrx_word' id='word_1_233' title='bbox 1046 1616 1199 1643; x_wconf 96'>controls</span>
<span class='ocrx_word' id='word_1_230' title='bbox 684 1617 844 1648; x_wconf 95'>REWIND,</span>
<span class='ocrx_word' id='word_1_231' title='bbox 857 1616 912 1643; x_wconf 95'>and</span>
<span class='ocrx_word' id='word_1_232' title='bbox 924 1616 1068 1643; x_wconf 96'>LOCATE</span>
<span class='ocrx_word' id='word_1_233' title='bbox 1079 1616 1199 1643; x_wconf 95'>controls</span>
</span>
<span class='ocr_line' id='line_1_28' title="bbox 346 1655 1202 1689; baseline 0 -7; x_size 34; x_descenders 6; x_ascenders 9">
<span class='ocrx_word' id='word_1_234' title='bbox 346 1663 409 1688; x_wconf 92'>may</span>
@@ -385,8 +385,8 @@
<span class='ocrx_word' id='word_1_250' title='bbox 860 1696 897 1722; x_wconf 93'>To</span>
<span class='ocrx_word' id='word_1_251' title='bbox 908 1695 1028 1722; x_wconf 93'>overdub</span>
<span class='ocrx_word' id='word_1_252' title='bbox 1039 1703 1056 1722; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_253' title='bbox 1066 1703 1099 1722; x_wconf 96'>new</span>
<span class='ocrx_word' id='word_1_254' title='bbox 1100 1699 1204 1728; x_wconf 96'>part,</span>
<span class='ocrx_word' id='word_1_253' title='bbox 1066 1703 1125 1722; x_wconf 96'>new</span>
<span class='ocrx_word' id='word_1_254' title='bbox 1135 1699 1204 1728; x_wconf 96'>part,</span>
</span>
<span class='ocr_line' id='line_1_30' title="bbox 347 1733 1150 1768; baseline 0.001 -8; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocrx_word' id='word_1_255' title='bbox 347 1733 426 1761; x_wconf 97'>select</span>
@@ -400,9 +400,9 @@
</span>
<span class='ocr_line' id='line_1_31' title="bbox 346 1773 1203 1808; baseline 0.001 -8; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_263' title='bbox 346 1774 448 1806; x_wconf 96'>record,</span>
<span class='ocrx_word' id='word_1_264' title='bbox 460 1774 506 1801; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_265' title='bbox 517 1773 577 1801; x_wconf 96'>first</span>
<span class='ocrx_word' id='word_1_266' title='bbox 587 1774 662 1801; x_wconf 96'>track</span>
<span class='ocrx_word' id='word_1_264' title='bbox 460 1774 506 1801; x_wconf 97'>the</span>
<span class='ocrx_word' id='word_1_265' title='bbox 503 1769 577 1812; x_wconf 96'>first</span>
<span class='ocrx_word' id='word_1_266' title='bbox 581 1774 658 1801; x_wconf 96'>track</span>
<span class='ocrx_word' id='word_1_267' title='bbox 673 1774 726 1801; x_wconf 96'>will</span>
<span class='ocrx_word' id='word_1_268' title='bbox 736 1774 799 1807; x_wconf 96'>play</span>
<span class='ocrx_word' id='word_1_269' title='bbox 809 1774 836 1801; x_wconf 96'>in</span>
@@ -412,12 +412,12 @@
<span class='ocrx_word' id='word_1_273' title='bbox 1148 1782 1203 1807; x_wconf 96'>you</span>
</span>
<span class='ocr_line' id='line_1_32' title="bbox 346 1813 1191 1847; baseline 0.002 -8; x_size 33; x_descenders 5; x_ascenders 9">
<span class='ocrx_word' id='word_1_274' title='bbox 346 1813 431 1840; x_wconf 94'>MUTE</span>
<span class='ocrx_word' id='word_1_275' title='bbox 431 1813 492 1845; x_wconf 94'>it,</span>
<span class='ocrx_word' id='word_1_274' title='bbox 346 1813 454 1840; x_wconf 95'>MUTE</span>
<span class='ocrx_word' id='word_1_275' title='bbox 464 1813 492 1845; x_wconf 95'>it,</span>
<span class='ocrx_word' id='word_1_276' title='bbox 505 1821 537 1840; x_wconf 95'>or</span>
<span class='ocrx_word' id='word_1_277' title='bbox 547 1813 642 1840; x_wconf 95'>SOLO</span>
<span class='ocrx_word' id='word_1_278' title='bbox 653 1814 756 1841; x_wconf 96'>another</span>
<span class='ocrx_word' id='word_1_279' title='bbox 757 1814 875 1847; x_wconf 94'>track).</span>
<span class='ocrx_word' id='word_1_278' title='bbox 653 1814 769 1841; x_wconf 95'>another</span>
<span class='ocrx_word' id='word_1_279' title='bbox 779 1814 875 1847; x_wconf 94'>track).</span>
<span class='ocrx_word' id='word_1_280' title='bbox 889 1814 920 1840; x_wconf 96'>In</span>
<span class='ocrx_word' id='word_1_281' title='bbox 930 1813 984 1841; x_wconf 96'>this</span>
<span class='ocrx_word' id='word_1_282' title='bbox 995 1822 1057 1847; x_wconf 96'>way,</span>
@@ -431,8 +431,8 @@
<span class='ocrx_word' id='word_1_288' title='bbox 518 1853 552 1879; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_289' title='bbox 562 1853 748 1880; x_wconf 96'>overdubbed!</span>
<span class='ocrx_word' id='word_1_290' title='bbox 761 1853 808 1880; x_wconf 94'>All</span>
<span class='ocrx_word' id='word_1_291' title='bbox 819 1854 892 1880; x_wconf 96'>MIDI</span>
<span class='ocrx_word' id='word_1_292' title='bbox 895 1853 1011 1880; x_wconf 96'>effects</span>
<span class='ocrx_word' id='word_1_291' title='bbox 819 1854 905 1880; x_wconf 96'>MIDI</span>
<span class='ocrx_word' id='word_1_292' title='bbox 917 1853 1011 1880; x_wconf 96'>effects</span>
<span class='ocrx_word' id='word_1_293' title='bbox 1022 1861 1067 1880; x_wconf 96'>are</span>
<span class='ocrx_word' id='word_1_294' title='bbox 1076 1853 1205 1880; x_wconf 96'>recorded</span>
</span>
@@ -440,8 +440,8 @@
<span class='ocrx_word' id='word_1_295' title='bbox 346 1891 485 1925; x_wconf 96'>including</span>
<span class='ocrx_word' id='word_1_296' title='bbox 495 1891 570 1925; x_wconf 96'>pitch</span>
<span class='ocrx_word' id='word_1_297' title='bbox 580 1892 663 1924; x_wconf 96'>bend,</span>
<span class='ocrx_word' id='word_1_298' title='bbox 675 1892 850 1920; x_wconf 96'>modulation,</span>
<span class='ocrx_word' id='word_1_299' title='bbox 854 1892 991 1926; x_wconf 93'>velocity,</span>
<span class='ocrx_word' id='word_1_298' title='bbox 675 1892 859 1924; x_wconf 96'>modulation,</span>
<span class='ocrx_word' id='word_1_299' title='bbox 872 1892 991 1926; x_wconf 93'>velocity,</span>
<span class='ocrx_word' id='word_1_300' title='bbox 1004 1892 1168 1924; x_wconf 92'>aftertouch,</span>
</span>
<span class='ocr_line' id='line_1_35' title="bbox 346 1931 895 1965; baseline 0.002 -7; x_size 33; x_descenders 6; x_ascenders 8">
@@ -465,13 +465,13 @@
<span class='ocrx_word' id='word_1_307' title='bbox 383 2050 419 2076; x_wconf 96'>To</span>
<span class='ocrx_word' id='word_1_308' title='bbox 430 2057 503 2076; x_wconf 95'>erase</span>
<span class='ocrx_word' id='word_1_309' title='bbox 514 2058 530 2077; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_310' title='bbox 540 2058 617 2077; x_wconf 96'>wrong</span>
<span class='ocrx_word' id='word_1_311' title='bbox 618 2054 717 2083; x_wconf 96'>note,</span>
<span class='ocrx_word' id='word_1_310' title='bbox 540 2058 634 2083; x_wconf 96'>wrong</span>
<span class='ocrx_word' id='word_1_311' title='bbox 644 2054 717 2082; x_wconf 96'>note,</span>
<span class='ocrx_word' id='word_1_312' title='bbox 729 2050 829 2083; x_wconf 96'>simply</span>
<span class='ocrx_word' id='word_1_313' title='bbox 839 2050 905 2077; x_wconf 96'>hold</span>
<span class='ocrx_word' id='word_1_314' title='bbox 916 2051 1013 2077; x_wconf 96'>ERASE</span>
<span class='ocrx_word' id='word_1_315' title='bbox 1015 2051 1083 2077; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_316' title='bbox 1085 2050 1186 2084; x_wconf 96'>press</span>
<span class='ocrx_word' id='word_1_314' title='bbox 916 2051 1037 2077; x_wconf 96'>ERASE</span>
<span class='ocrx_word' id='word_1_315' title='bbox 1048 2050 1103 2077; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_316' title='bbox 1113 2059 1186 2084; x_wconf 96'>press</span>
</span>
<span class='ocr_line' id='line_1_38' title="bbox 346 2089 1212 2124; baseline 0.002 -8; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_317' title='bbox 346 2089 391 2116; x_wconf 96'>the</span>
@@ -480,16 +480,16 @@
<span class='ocrx_word' id='word_1_320' title='bbox 515 2090 549 2117; x_wconf 97'>be</span>
<span class='ocrx_word' id='word_1_321' title='bbox 559 2090 652 2117; x_wconf 96'>erased</span>
<span class='ocrx_word' id='word_1_322' title='bbox 661 2090 718 2123; x_wconf 96'>just</span>
<span class='ocrx_word' id='word_1_323' title='bbox 729 2090 808 2117; x_wconf 96'>before</span>
<span class='ocrx_word' id='word_1_324' title='bbox 808 2090 852 2117; x_wconf 96'>it</span>
<span class='ocrx_word' id='word_1_323' title='bbox 729 2090 822 2117; x_wconf 96'>before</span>
<span class='ocrx_word' id='word_1_324' title='bbox 833 2090 852 2117; x_wconf 96'>it</span>
<span class='ocrx_word' id='word_1_325' title='bbox 862 2090 937 2124; x_wconf 96'>plays</span>
<span class='ocrx_word' id='word_1_326' title='bbox 947 2090 975 2117; x_wconf 96'>in</span>
<span class='ocrx_word' id='word_1_327' title='bbox 986 2090 1032 2118; x_wconf 93'>the</span>
<span class='ocrx_word' id='word_1_328' title='bbox 1043 2098 1212 2124; x_wconf 88'>sequence—</span>
</span>
<span class='ocr_line' id='line_1_39' title="bbox 346 2129 1134 2163; baseline 0.003 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_329' title='bbox 346 2129 406 2156; x_wconf 96'>when</span>
<span class='ocrx_word' id='word_1_330' title='bbox 407 2129 531 2162; x_wconf 96'>played</span>
<span class='ocrx_word' id='word_1_329' title='bbox 346 2129 425 2156; x_wconf 96'>when</span>
<span class='ocrx_word' id='word_1_330' title='bbox 435 2129 531 2162; x_wconf 96'>played</span>
<span class='ocrx_word' id='word_1_331' title='bbox 542 2129 621 2161; x_wconf 96'>back,</span>
<span class='ocrx_word' id='word_1_332' title='bbox 633 2129 652 2156; x_wconf 96'>it</span>
<span class='ocrx_word' id='word_1_333' title='bbox 663 2129 716 2156; x_wconf 96'>will</span>
@@ -512,19 +512,19 @@
<span class='ocrx_word' id='word_1_344' title='bbox 749 2169 829 2203; x_wconf 96'>using</span>
<span class='ocrx_word' id='word_1_345' title='bbox 839 2169 885 2196; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_346' title='bbox 896 2170 1031 2196; x_wconf 96'>SINGLE</span>
<span class='ocrx_word' id='word_1_347' title='bbox 1042 2170 1107 2196; x_wconf 91'>STEP</span>
<span class='ocrx_word' id='word_1_348' title='bbox 1109 2169 1220 2196; x_wconf 91'>func-</span>
<span class='ocrx_word' id='word_1_347' title='bbox 1042 2170 1131 2196; x_wconf 91'>STEP</span>
<span class='ocrx_word' id='word_1_348' title='bbox 1143 2169 1220 2196; x_wconf 91'>func-</span>
</span>
<span class='ocr_line' id='line_1_41' title="bbox 345 2207 1228 2242; baseline 0.002 -8; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocrx_word' id='word_1_349' title='bbox 345 2207 412 2234; x_wconf 96'>tion.</span>
<span class='ocrx_word' id='word_1_350' title='bbox 424 2208 461 2235; x_wconf 93'>To</span>
<span class='ocrx_word' id='word_1_351' title='bbox 472 2208 592 2235; x_wconf 92'>overdub</span>
<span class='ocrx_word' id='word_1_352' title='bbox 603 2212 667 2235; x_wconf 96'>notes</span>
<span class='ocrx_word' id='word_1_353' title='bbox 668 2212 718 2235; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_351' title='bbox 472 2208 592 2235; x_wconf 91'>overdub</span>
<span class='ocrx_word' id='word_1_352' title='bbox 603 2212 680 2235; x_wconf 96'>notes</span>
<span class='ocrx_word' id='word_1_353' title='bbox 691 2212 718 2235; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_354' title='bbox 729 2208 841 2242; x_wconf 96'>specific</span>
<span class='ocrx_word' id='word_1_355' title='bbox 851 2209 943 2242; x_wconf 97'>points</span>
<span class='ocrx_word' id='word_1_356' title='bbox 955 2208 1049 2236; x_wconf 97'>within</span>
<span class='ocrx_word' id='word_1_357' title='bbox 1060 2217 1076 2236; x_wconf 97'>a</span>
<span class='ocrx_word' id='word_1_356' title='bbox 955 2208 1049 2236; x_wconf 96'>within</span>
<span class='ocrx_word' id='word_1_357' title='bbox 1060 2217 1076 2236; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_358' title='bbox 1086 2216 1228 2242; x_wconf 96'>sequence,</span>
</span>
</p>
@@ -544,10 +544,10 @@
<span class='ocrx_word' id='word_1_362' title='bbox 1404 1297 1452 1316; x_wconf 96'>use</span>
<span class='ocrx_word' id='word_1_363' title='bbox 1463 1290 1615 1321; x_wconf 96'>LOCATE,</span>
<span class='ocrx_word' id='word_1_364' title='bbox 1628 1290 1716 1316; x_wconf 96'>FAST</span>
<span class='ocrx_word' id='word_1_365' title='bbox 1726 1289 1910 1316; x_wconf 95'>FORWARD,</span>
<span class='ocrx_word' id='word_1_366' title='bbox 1911 1297 1960 1321; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_367' title='bbox 1972 1290 2098 1316; x_wconf 95'>REWIND</span>
<span class='ocrx_word' id='word_1_368' title='bbox 2100 1290 2165 1317; x_wconf 95'>to</span>
<span class='ocrx_word' id='word_1_365' title='bbox 1726 1289 1917 1321; x_wconf 95'>FORWARD,</span>
<span class='ocrx_word' id='word_1_366' title='bbox 1929 1297 1960 1317; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_367' title='bbox 1972 1290 2126 1316; x_wconf 95'>REWIND</span>
<span class='ocrx_word' id='word_1_368' title='bbox 2136 1294 2165 1317; x_wconf 95'>to</span>
</span>
<span class='ocr_line' id='line_1_44' title="bbox 1297 1328 2033 1362; baseline 0.001 -7; x_size 32; x_descenders 5; x_ascenders 8">
<span class='ocrx_word' id='word_1_369' title='bbox 1297 1328 1356 1355; x_wconf 96'>find</span>
@@ -564,8 +564,8 @@
<p class='ocr_par' id='par_1_24' lang='eng' title="bbox 1295 1368 2160 1593">
<span class='ocr_line' id='line_1_45' title="bbox 1332 1368 2160 1402; baseline 0.002 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_377' title='bbox 1332 1368 1391 1395; x_wconf 93'>The</span>
<span class='ocrx_word' id='word_1_378' title='bbox 1402 1368 1626 1395; x_wconf 91'>INSERT/COPY</span>
<span class='ocrx_word' id='word_1_379' title='bbox 1626 1368 1789 1395; x_wconf 96'>function</span>
<span class='ocrx_word' id='word_1_378' title='bbox 1402 1368 1650 1395; x_wconf 91'>INSERT/COPY</span>
<span class='ocrx_word' id='word_1_379' title='bbox 1662 1368 1789 1395; x_wconf 96'>function</span>
<span class='ocrx_word' id='word_1_380' title='bbox 1800 1368 1893 1395; x_wconf 96'>allows</span>
<span class='ocrx_word' id='word_1_381' title='bbox 1904 1376 1958 1402; x_wconf 96'>you</span>
<span class='ocrx_word' id='word_1_382' title='bbox 1968 1373 1997 1395; x_wconf 96'>to</span>
@@ -580,8 +580,8 @@
<span class='ocrx_word' id='word_1_389' title='bbox 1616 1407 1796 1435; x_wconf 91'>another—in</span>
<span class='ocrx_word' id='word_1_390' title='bbox 1806 1408 1852 1435; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_391' title='bbox 1863 1416 1937 1435; x_wconf 96'>same</span>
<span class='ocrx_word' id='word_1_392' title='bbox 1948 1416 2067 1441; x_wconf 96'>sequence</span>
<span class='ocrx_word' id='word_1_393' title='bbox 2068 1416 2125 1435; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_392' title='bbox 1948 1416 2083 1441; x_wconf 96'>sequence</span>
<span class='ocrx_word' id='word_1_393' title='bbox 2093 1416 2125 1435; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_394' title='bbox 2135 1416 2151 1435; x_wconf 96'>a</span>
</span>
<span class='ocr_line' id='line_1_47' title="bbox 1296 1447 2160 1481; baseline 0.001 -7; x_size 34; x_descenders 7; x_ascenders 8">
@@ -603,15 +603,15 @@
<span class='ocrx_word' id='word_1_408' title='bbox 1449 1487 1571 1514; x_wconf 96'>between</span>
<span class='ocrx_word' id='word_1_409' title='bbox 1582 1487 1628 1514; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_410' title='bbox 1638 1487 1740 1514; x_wconf 96'>second</span>
<span class='ocrx_word' id='word_1_411' title='bbox 1751 1487 1838 1514; x_wconf 96'>chorus</span>
<span class='ocrx_word' id='word_1_412' title='bbox 1841 1495 1899 1514; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_413' title='bbox 1901 1487 1975 1514; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_411' title='bbox 1751 1487 1852 1514; x_wconf 96'>chorus</span>
<span class='ocrx_word' id='word_1_412' title='bbox 1863 1487 1919 1514; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_413' title='bbox 1929 1487 1975 1514; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_414' title='bbox 1985 1487 2087 1521; x_wconf 96'>bridge.</span>
</span>
<span class='ocr_line' id='line_1_49' title="bbox 1296 1527 2047 1560; baseline 0.003 -8; x_size 32; x_descenders 6; x_ascenders 7">
<span class='ocrx_word' id='word_1_415' title='bbox 1296 1527 1441 1553; x_wconf 95'>DELETE</span>
<span class='ocrx_word' id='word_1_416' title='bbox 1453 1527 1527 1553; x_wconf 96'>BARS</span>
<span class='ocrx_word' id='word_1_417' title='bbox 1529 1527 1681 1559; x_wconf 96'>operates</span>
<span class='ocrx_word' id='word_1_416' title='bbox 1453 1527 1546 1553; x_wconf 96'>BARS</span>
<span class='ocrx_word' id='word_1_417' title='bbox 1557 1531 1681 1559; x_wconf 96'>operates</span>
<span class='ocrx_word' id='word_1_418' title='bbox 1691 1527 1737 1553; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_419' title='bbox 1748 1535 1823 1554; x_wconf 96'>same</span>
<span class='ocrx_word' id='word_1_420' title='bbox 1833 1535 1891 1560; x_wconf 96'>way</span>
@@ -619,8 +619,8 @@
<span class='ocrx_word' id='word_1_422' title='bbox 1940 1535 2047 1554; x_wconf 96'>remove</span>
</span>
<span class='ocr_line' id='line_1_50' title="bbox 1295 1565 1577 1593; baseline 0.004 -1; x_size 34.748871; x_descenders 6.7488689; x_ascenders 9">
<span class='ocrx_word' id='word_1_423' title='bbox 1295 1569 1422 1592; x_wconf 96'>unwanted</span>
<span class='ocrx_word' id='word_1_424' title='bbox 1423 1565 1577 1593; x_wconf 95'>sections,</span>
<span class='ocrx_word' id='word_1_423' title='bbox 1295 1565 1441 1592; x_wconf 96'>unwanted</span>
<span class='ocrx_word' id='word_1_424' title='bbox 1452 1565 1577 1593; x_wconf 95'>sections,</span>
</span>
</p>
</div>
@@ -636,12 +636,12 @@
<p class='ocr_par' id='par_1_26' lang='eng' title="bbox 1295 1690 2215 1960">
<span class='ocr_line' id='line_1_52' title="bbox 1333 1690 2146 1723; baseline 0 -6; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_428' title='bbox 1333 1690 1394 1717; x_wconf 96'>One</span>
<span class='ocrx_word' id='word_1_429' title='bbox 1404 1698 1446 1717; x_wconf 96'>way</span>
<span class='ocrx_word' id='word_1_430' title='bbox 1445 1694 1500 1723; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_429' title='bbox 1404 1698 1462 1723; x_wconf 96'>way</span>
<span class='ocrx_word' id='word_1_430' title='bbox 1472 1694 1500 1717; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_431' title='bbox 1511 1694 1598 1717; x_wconf 96'>create</span>
<span class='ocrx_word' id='word_1_432' title='bbox 1608 1698 1625 1717; x_wconf 96'>a</span>
<span class='ocrx_word' id='word_1_433' title='bbox 1635 1698 1687 1717; x_wconf 95'>song</span>
<span class='ocrx_word' id='word_1_434' title='bbox 1688 1690 1736 1723; x_wconf 95'>is</span>
<span class='ocrx_word' id='word_1_433' title='bbox 1635 1698 1704 1723; x_wconf 95'>song</span>
<span class='ocrx_word' id='word_1_434' title='bbox 1715 1690 1736 1717; x_wconf 95'>is</span>
<span class='ocrx_word' id='word_1_435' title='bbox 1747 1694 1776 1717; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_436' title='bbox 1787 1690 1880 1717; x_wconf 96'>record</span>
<span class='ocrx_word' id='word_1_437' title='bbox 1891 1690 1958 1717; x_wconf 96'>each</span>
@@ -657,8 +657,8 @@
<span class='ocrx_word' id='word_1_445' title='bbox 1592 1730 1644 1756; x_wconf 96'>999</span>
<span class='ocrx_word' id='word_1_446' title='bbox 1654 1729 1738 1762; x_wconf 96'>bars).</span>
<span class='ocrx_word' id='word_1_447' title='bbox 1751 1729 1878 1757; x_wconf 96'>Another</span>
<span class='ocrx_word' id='word_1_448' title='bbox 1888 1737 1930 1757; x_wconf 96'>way</span>
<span class='ocrx_word' id='word_1_449' title='bbox 1929 1729 1977 1762; x_wconf 96'>is</span>
<span class='ocrx_word' id='word_1_448' title='bbox 1888 1737 1945 1762; x_wconf 96'>way</span>
<span class='ocrx_word' id='word_1_449' title='bbox 1956 1729 1977 1757; x_wconf 96'>is</span>
<span class='ocrx_word' id='word_1_450' title='bbox 1987 1733 2016 1757; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_451' title='bbox 2027 1729 2121 1757; x_wconf 96'>record</span>
</span>
@@ -667,8 +667,8 @@
<span class='ocrx_word' id='word_1_453' title='bbox 1373 1768 1448 1796; x_wconf 96'>basic</span>
<span class='ocrx_word' id='word_1_454' title='bbox 1458 1768 1562 1796; x_wconf 96'>section</span>
<span class='ocrx_word' id='word_1_455' title='bbox 1574 1769 1666 1802; x_wconf 96'>(verse,</span>
<span class='ocrx_word' id='word_1_456' title='bbox 1679 1769 1779 1796; x_wconf 96'>chorus,</span>
<span class='ocrx_word' id='word_1_457' title='bbox 1782 1769 1865 1802; x_wconf 96'>etc.)</span>
<span class='ocrx_word' id='word_1_456' title='bbox 1679 1769 1788 1801; x_wconf 96'>chorus,</span>
<span class='ocrx_word' id='word_1_457' title='bbox 1800 1769 1865 1802; x_wconf 96'>etc.)</span>
<span class='ocrx_word' id='word_1_458' title='bbox 1876 1768 1904 1795; x_wconf 96'>in</span>
<span class='ocrx_word' id='word_1_459' title='bbox 1914 1768 2066 1796; x_wconf 96'>individual</span>
</span>
@@ -678,23 +678,23 @@
<span class='ocrx_word' id='word_1_462' title='bbox 1538 1816 1587 1835; x_wconf 96'>use</span>
<span class='ocrx_word' id='word_1_463' title='bbox 1597 1808 1643 1835; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_464' title='bbox 1653 1809 1799 1835; x_wconf 96'>CREATE</span>
<span class='ocrx_word' id='word_1_465' title='bbox 1810 1808 1883 1835; x_wconf 96'>SONG</span>
<span class='ocrx_word' id='word_1_466' title='bbox 1885 1808 2050 1836; x_wconf 96'>function</span>
<span class='ocrx_word' id='word_1_465' title='bbox 1810 1808 1911 1835; x_wconf 96'>SONG</span>
<span class='ocrx_word' id='word_1_466' title='bbox 1923 1808 2050 1836; x_wconf 96'>function</span>
<span class='ocrx_word' id='word_1_467' title='bbox 2060 1812 2089 1835; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_468' title='bbox 2103 1808 2215 1836; x_wconf 91'>“chain”</span>
<span class='ocrx_word' id='word_1_468' title='bbox 2103 1808 2215 1836; x_wconf 93'>“chain”</span>
</span>
<span class='ocr_line' id='line_1_56' title="bbox 1295 1847 2135 1881; baseline 0.001 -7; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_469' title='bbox 1295 1848 1370 1874; x_wconf 96'>them</span>
<span class='ocrx_word' id='word_1_470' title='bbox 1381 1848 1508 1881; x_wconf 95'>together.</span>
<span class='ocrx_word' id='word_1_471' title='bbox 1521 1848 1667 1875; x_wconf 96'>CREATE</span>
<span class='ocrx_word' id='word_1_472' title='bbox 1678 1848 1751 1875; x_wconf 96'>SONG</span>
<span class='ocrx_word' id='word_1_473' title='bbox 1753 1847 1842 1874; x_wconf 96'>will</span>
<span class='ocrx_word' id='word_1_472' title='bbox 1678 1848 1779 1875; x_wconf 96'>SONG</span>
<span class='ocrx_word' id='word_1_473' title='bbox 1789 1847 1842 1874; x_wconf 96'>will</span>
<span class='ocrx_word' id='word_1_474' title='bbox 1853 1848 1918 1875; x_wconf 96'>then</span>
<span class='ocrx_word' id='word_1_475' title='bbox 1929 1848 2135 1881; x_wconf 96'>automatically</span>
</span>
<span class='ocr_line' id='line_1_57' title="bbox 1296 1887 2162 1920; baseline 0 -6; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_476' title='bbox 1296 1895 1349 1920; x_wconf 96'>copy</span>
<span class='ocrx_word' id='word_1_477' title='bbox 1349 1887 1412 1920; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_476' title='bbox 1296 1895 1366 1920; x_wconf 96'>copy</span>
<span class='ocrx_word' id='word_1_477' title='bbox 1377 1887 1412 1914; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_478' title='bbox 1422 1887 1468 1914; x_wconf 96'>the</span>
<span class='ocrx_word' id='word_1_479' title='bbox 1478 1891 1552 1920; x_wconf 96'>parts</span>
<span class='ocrx_word' id='word_1_480' title='bbox 1563 1887 1621 1914; x_wconf 95'>into</span>
@@ -714,8 +714,8 @@
<span class='ocrx_word' id='word_1_492' title='bbox 1540 1926 1590 1953; x_wconf 96'>few</span>
<span class='ocrx_word' id='word_1_493' title='bbox 1601 1927 1664 1953; x_wconf 96'>bars</span>
<span class='ocrx_word' id='word_1_494' title='bbox 1675 1931 1704 1954; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_495' title='bbox 1715 1935 1795 1960; x_wconf 96'>repeat</span>
<span class='ocrx_word' id='word_1_496' title='bbox 1795 1926 1955 1960; x_wconf 96'>infinitely,</span>
<span class='ocrx_word' id='word_1_495' title='bbox 1715 1931 1806 1960; x_wconf 96'>repeat</span>
<span class='ocrx_word' id='word_1_496' title='bbox 1816 1926 1955 1960; x_wconf 96'>infinitely,</span>
<span class='ocrx_word' id='word_1_497' title='bbox 1968 1926 2011 1954; x_wconf 95'>for</span>
<span class='ocrx_word' id='word_1_498' title='bbox 2022 1935 2038 1954; x_wconf 93'>a</span>
<span class='ocrx_word' id='word_1_499' title='bbox 2049 1927 2169 1954; x_wconf 92'>fadeout.</span>
@@ -757,8 +757,8 @@
</span>
<span class='ocr_line' id='line_1_62' title="bbox 1293 2130 2156 2164; baseline 0 -7; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_522' title='bbox 1293 2130 1339 2157; x_wconf 93'>the</span>
<span class='ocrx_word' id='word_1_523' title='bbox 1350 2130 1562 2164; x_wconf 90'>LinnSequencer</span>
<span class='ocrx_word' id='word_1_524' title='bbox 1564 2130 1607 2157; x_wconf 97'>is</span>
<span class='ocrx_word' id='word_1_523' title='bbox 1350 2130 1576 2164; x_wconf 90'>LinnSequencer</span>
<span class='ocrx_word' id='word_1_524' title='bbox 1586 2130 1607 2157; x_wconf 97'>is</span>
<span class='ocrx_word' id='word_1_525' title='bbox 1619 2130 1747 2164; x_wconf 96'>designed</span>
<span class='ocrx_word' id='word_1_526' title='bbox 1758 2134 1787 2157; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_527' title='bbox 1798 2130 1834 2157; x_wconf 96'>let</span>
@@ -767,8 +767,8 @@
<span class='ocrx_word' id='word_1_530' title='bbox 2063 2130 2156 2157; x_wconf 96'>record</span>
</span>
<span class='ocr_line' id='line_1_63' title="bbox 1294 2169 2145 2203; baseline 0 -6; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_531' title='bbox 1294 2178 1330 2197; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_532' title='bbox 1331 2169 1414 2197; x_wconf 96'>edit</span>
<span class='ocrx_word' id='word_1_531' title='bbox 1294 2170 1349 2197; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_532' title='bbox 1360 2169 1414 2197; x_wconf 96'>edit</span>
<span class='ocrx_word' id='word_1_533' title='bbox 1425 2170 1503 2197; x_wconf 96'>while</span>
<span class='ocrx_word' id='word_1_534' title='bbox 1514 2170 1643 2203; x_wconf 96'>devoting</span>
<span class='ocrx_word' id='word_1_535' title='bbox 1653 2178 1721 2203; x_wconf 96'>your</span>
@@ -792,7 +792,7 @@
</div>
<div class='ocr_carea' id='block_1_21' title="bbox 347 2343 2171 2378">
<p class='ocr_par' id='par_1_29' lang='eng' title="bbox 347 2343 2171 2378">
<span class='ocr_line' id='line_1_65' title="bbox 347 2343 2171 2378; baseline 0.001 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocr_header' id='line_1_65' title="bbox 347 2343 2171 2378; baseline 0.001 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_549' title='bbox 347 2350 361 2363; x_wconf 58'>*</span>
<span class='ocrx_word' id='word_1_550' title='bbox 373 2343 483 2377; x_wconf 96'>Simple,</span>
<span class='ocrx_word' id='word_1_551' title='bbox 495 2352 559 2377; x_wconf 96'>easy</span>
@@ -806,8 +806,8 @@
<span class='ocrx_word' id='word_1_559' title='bbox 1326 2345 1424 2378; x_wconf 97'>clearly</span>
<span class='ocrx_word' id='word_1_560' title='bbox 1434 2345 1528 2378; x_wconf 96'>guides</span>
<span class='ocrx_word' id='word_1_561' title='bbox 1539 2353 1594 2378; x_wconf 97'>you</span>
<span class='ocrx_word' id='word_1_562' title='bbox 1604 2345 1705 2378; x_wconf 96'>through</span>
<span class='ocrx_word' id='word_1_563' title='bbox 1706 2344 1770 2371; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_562' title='bbox 1604 2345 1724 2378; x_wconf 96'>through</span>
<span class='ocrx_word' id='word_1_563' title='bbox 1735 2344 1770 2371; x_wconf 96'>all</span>
<span class='ocrx_word' id='word_1_564' title='bbox 1781 2344 1947 2377; x_wconf 96'>operations.</span>
<span class='ocrx_word' id='word_1_565' title='bbox 1961 2344 1989 2371; x_wconf 96'>If</span>
<span class='ocrx_word' id='word_1_566' title='bbox 1997 2344 2112 2376; x_wconf 96'>needed,</span>
@@ -818,8 +818,8 @@
<div class='ocr_carea' id='block_1_22' title="bbox 373 2381 1083 2415">
<p class='ocr_par' id='par_1_30' lang='eng' title="bbox 373 2381 1083 2415">
<span class='ocr_line' id='line_1_66' title="bbox 373 2381 1083 2415; baseline 0.003 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_568' title='bbox 373 2381 448 2407; x_wconf 96'>HELP</span>
<span class='ocrx_word' id='word_1_569' title='bbox 450 2381 583 2408; x_wconf 96'>button</span>
<span class='ocrx_word' id='word_1_568' title='bbox 373 2381 472 2407; x_wconf 96'>HELP</span>
<span class='ocrx_word' id='word_1_569' title='bbox 483 2381 583 2408; x_wconf 96'>button</span>
<span class='ocrx_word' id='word_1_570' title='bbox 594 2381 711 2415; x_wconf 96'>displays</span>
<span class='ocrx_word' id='word_1_571' title='bbox 722 2382 875 2409; x_wconf 96'>additional</span>
<span class='ocrx_word' id='word_1_572' title='bbox 886 2382 1083 2415; x_wconf 96'>explanations.</span>
@@ -828,7 +828,7 @@
</div>
<div class='ocr_carea' id='block_1_23' title="bbox 347 2427 2145 2507">
<p class='ocr_par' id='par_1_31' lang='eng' title="bbox 347 2427 2145 2507">
<span class='ocr_line' id='line_1_67' title="bbox 347 2427 1468 2461; baseline 0.002 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocr_header' id='line_1_67' title="bbox 347 2427 1468 2461; baseline 0.002 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_573' title='bbox 347 2432 361 2446; x_wconf 70'>*</span>
<span class='ocrx_word' id='word_1_574' title='bbox 373 2427 612 2454; x_wconf 91'>Non-destructive</span>
<span class='ocrx_word' id='word_1_575' title='bbox 622 2427 914 2461; x_wconf 89'>recording—existing</span>
@@ -839,14 +839,14 @@
<span class='ocrx_word' id='word_1_580' title='bbox 1231 2428 1309 2455; x_wconf 96'>while</span>
<span class='ocrx_word' id='word_1_581' title='bbox 1319 2428 1468 2461; x_wconf 92'>recording.</span>
</span>
<span class='ocr_line' id='line_1_68' title="bbox 347 2473 2145 2507; baseline 0.001 -8; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocr_header' id='line_1_68' title="bbox 347 2473 2145 2507; baseline 0.001 -8; x_size 35; x_descenders 7; x_ascenders 9">
<span class='ocrx_word' id='word_1_582' title='bbox 347 2478 361 2492; x_wconf 70'>¢</span>
<span class='ocrx_word' id='word_1_583' title='bbox 372 2473 433 2500; x_wconf 93'>Two</span>
<span class='ocrx_word' id='word_1_584' title='bbox 444 2473 689 2500; x_wconf 90'>FOOTSWITCH</span>
<span class='ocrx_word' id='word_1_585' title='bbox 701 2474 818 2500; x_wconf 95'>INPUTS</span>
<span class='ocrx_word' id='word_1_586' title='bbox 819 2474 894 2501; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_587' title='bbox 893 2473 939 2507; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_588' title='bbox 941 2473 1091 2507; x_wconf 96'>assigned</span>
<span class='ocrx_word' id='word_1_585' title='bbox 701 2474 837 2501; x_wconf 95'>INPUTS</span>
<span class='ocrx_word' id='word_1_586' title='bbox 848 2481 910 2507; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_587' title='bbox 921 2473 955 2500; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_588' title='bbox 966 2473 1091 2507; x_wconf 96'>assigned</span>
<span class='ocrx_word' id='word_1_589' title='bbox 1101 2478 1130 2501; x_wconf 96'>to</span>
<span class='ocrx_word' id='word_1_590' title='bbox 1141 2474 1271 2507; x_wconf 96'>remotely</span>
<span class='ocrx_word' id='word_1_591' title='bbox 1281 2473 1387 2501; x_wconf 96'>control</span>
@@ -873,12 +873,12 @@
</div>
<div class='ocr_carea' id='block_1_25' title="bbox 347 2556 1768 2590">
<p class='ocr_par' id='par_1_33' lang='eng' title="bbox 347 2556 1768 2590">
<span class='ocr_line' id='line_1_70' title="bbox 347 2556 1768 2590; baseline 0.001 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_604' title='bbox 347 2561 361 2575; x_wconf 85'>¢</span>
<span class='ocrx_word' id='word_1_605' title='bbox 372 2556 433 2583; x_wconf 84'>Iwo</span>
<span class='ocr_header' id='line_1_70' title="bbox 347 2556 1768 2590; baseline 0.001 -8; x_size 34; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_604' title='bbox 347 2561 361 2575; x_wconf 86'>¢</span>
<span class='ocrx_word' id='word_1_605' title='bbox 372 2556 433 2583; x_wconf 85'>Iwo</span>
<span class='ocrx_word' id='word_1_606' title='bbox 443 2556 612 2583; x_wconf 96'>TRIGGER</span>
<span class='ocrx_word' id='word_1_607' title='bbox 623 2556 778 2584; x_wconf 95'>OUTPUTS</span>
<span class='ocrx_word' id='word_1_608' title='bbox 780 2557 871 2590; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_607' title='bbox 623 2556 797 2584; x_wconf 96'>OUTPUTS</span>
<span class='ocrx_word' id='word_1_608' title='bbox 808 2565 871 2590; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_609' title='bbox 881 2557 915 2584; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_610' title='bbox 925 2557 1119 2590; x_wconf 96'>programmed</span>
<span class='ocrx_word' id='word_1_611' title='bbox 1129 2561 1158 2584; x_wconf 96'>to</span>
@@ -887,7 +887,7 @@
<span class='ocrx_word' id='word_1_614' title='bbox 1381 2561 1409 2584; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_615' title='bbox 1419 2565 1472 2590; x_wconf 96'>any</span>
<span class='ocrx_word' id='word_1_616' title='bbox 1483 2557 1598 2584; x_wconf 96'>selected</span>
<span class='ocrx_word' id='word_1_617' title='bbox 1608 2561 1673 2584; x_wconf 96'>note</span>
<span class='ocrx_word' id='word_1_617' title='bbox 1608 2561 1673 2584; x_wconf 97'>note</span>
<span class='ocrx_word' id='word_1_618' title='bbox 1683 2556 1768 2583; x_wconf 96'>value.</span>
</span>
</p>
@@ -904,22 +904,22 @@
<span class='ocrx_word' id='word_1_625' title='bbox 875 2610 907 2629; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_626' title='bbox 918 2602 989 2629; x_wconf 96'>Linn</span>
<span class='ocrx_word' id='word_1_627' title='bbox 1000 2603 1069 2629; x_wconf 95'>9000</span>
<span class='ocrx_word' id='word_1_628' title='bbox 1080 2610 1129 2635; x_wconf 96'>sync</span>
<span class='ocrx_word' id='word_1_629' title='bbox 1131 2607 1226 2630; x_wconf 96'>tone.</span>
<span class='ocrx_word' id='word_1_628' title='bbox 1080 2610 1145 2635; x_wconf 96'>sync</span>
<span class='ocrx_word' id='word_1_629' title='bbox 1155 2607 1226 2630; x_wconf 96'>tone.</span>
</span>
</p>
</div>
<div class='ocr_carea' id='block_1_27' title="bbox 347 2648 2100 2727">
<p class='ocr_par' id='par_1_35' lang='eng' title="bbox 347 2648 2100 2727">
<span class='ocr_line' id='line_1_72' title="bbox 347 2648 1664 2682; baseline 0 -7; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_630' title='bbox 347 2654 360 2667; x_wconf 51'>©</span>
<span class='ocrx_word' id='word_1_631' title='bbox 372 2648 483 2675; x_wconf 96'>Utilizes</span>
<span class='ocrx_word' id='word_1_632' title='bbox 493 2648 564 2677; x_wconf 96'>ultra</span>
<span class='ocr_header' id='line_1_72' title="bbox 347 2648 1664 2682; baseline 0.001 -7; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_630' title='bbox 347 2654 360 2667; x_wconf 45'>©</span>
<span class='ocrx_word' id='word_1_631' title='bbox 372 2648 483 2675; x_wconf 95'>Utilizes</span>
<span class='ocrx_word' id='word_1_632' title='bbox 493 2648 564 2680; x_wconf 96'>ultra</span>
<span class='ocrx_word' id='word_1_633' title='bbox 573 2648 744 2682; x_wconf 96'>high-speed,</span>
<span class='ocrx_word' id='word_1_634' title='bbox 757 2649 772 2676; x_wconf 95'>8</span>
<span class='ocrx_word' id='word_1_635' title='bbox 783 2649 862 2675; x_wconf 94'>MHz</span>
<span class='ocrx_word' id='word_1_636' title='bbox 873 2649 954 2676; x_wconf 95'>80186</span>
<span class='ocrx_word' id='word_1_637' title='bbox 965 2649 994 2676; x_wconf 95'>16</span>
<span class='ocrx_word' id='word_1_636' title='bbox 873 2649 954 2676; x_wconf 96'>80186</span>
<span class='ocrx_word' id='word_1_637' title='bbox 965 2649 994 2676; x_wconf 96'>16</span>
<span class='ocrx_word' id='word_1_638' title='bbox 1004 2648 1043 2676; x_wconf 96'>bit</span>
<span class='ocrx_word' id='word_1_639' title='bbox 1054 2653 1197 2682; x_wconf 96'>computer</span>
<span class='ocrx_word' id='word_1_640' title='bbox 1208 2649 1350 2682; x_wconf 96'>internally</span>
@@ -927,17 +927,17 @@
<span class='ocrx_word' id='word_1_642' title='bbox 1414 2649 1502 2676; x_wconf 96'>FAST</span>
<span class='ocrx_word' id='word_1_643' title='bbox 1512 2648 1664 2682; x_wconf 96'>operation.</span>
</span>
<span class='ocr_line' id='line_1_73' title="bbox 347 2694 2100 2727; baseline 0.001 -7; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocr_header' id='line_1_73' title="bbox 347 2694 2100 2727; baseline 0.001 -7; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_644' title='bbox 347 2699 361 2713; x_wconf 31'>*</span>
<span class='ocrx_word' id='word_1_645' title='bbox 372 2694 476 2720; x_wconf 96'>TEMPO</span>
<span class='ocrx_word' id='word_1_646' title='bbox 478 2694 562 2721; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_647' title='bbox 561 2694 606 2727; x_wconf 95'>be</span>
<span class='ocrx_word' id='word_1_648' title='bbox 608 2694 764 2727; x_wconf 95'>specified</span>
<span class='ocrx_word' id='word_1_645' title='bbox 372 2694 504 2721; x_wconf 96'>TEMPO</span>
<span class='ocrx_word' id='word_1_646' title='bbox 515 2702 578 2727; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_647' title='bbox 589 2694 623 2721; x_wconf 95'>be</span>
<span class='ocrx_word' id='word_1_648' title='bbox 633 2694 764 2727; x_wconf 95'>specified</span>
<span class='ocrx_word' id='word_1_649' title='bbox 774 2694 802 2721; x_wconf 93'>in</span>
<span class='ocrx_word' id='word_1_650' title='bbox 814 2695 1172 2722; x_wconf 91'>BEATS-PER-MINUTE</span>
<span class='ocrx_word' id='word_1_651' title='bbox 1183 2703 1215 2722; x_wconf 93'>or</span>
<span class='ocrx_word' id='word_1_652' title='bbox 1225 2695 1547 2722; x_wconf 91'>FRAMES-PER-BEAT</span>
<span class='ocrx_word' id='word_1_653' title='bbox 1543 2695 1605 2721; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_652' title='bbox 1225 2695 1567 2722; x_wconf 91'>FRAMES-PER-BEAT</span>
<span class='ocrx_word' id='word_1_653' title='bbox 1577 2698 1605 2721; x_wconf 96'>at</span>
<span class='ocrx_word' id='word_1_654' title='bbox 1616 2695 1659 2726; x_wconf 96'>24,</span>
<span class='ocrx_word' id='word_1_655' title='bbox 1672 2695 1716 2726; x_wconf 96'>25,</span>
<span class='ocrx_word' id='word_1_656' title='bbox 1728 2702 1760 2721; x_wconf 96'>or</span>
@@ -959,19 +959,19 @@
</div>
<div class='ocr_carea' id='block_1_29' title="bbox 347 2777 2174 2811">
<p class='ocr_par' id='par_1_37' lang='eng' title="bbox 347 2777 2174 2811">
<span class='ocr_line' id='line_1_75' title="bbox 347 2777 2174 2811; baseline 0.001 -8; x_size 33; x_descenders 5; x_ascenders 9">
<span class='ocrx_word' id='word_1_664' title='bbox 347 2782 360 2796; x_wconf 79'>¢</span>
<span class='ocrx_word' id='word_1_665' title='bbox 372 2777 476 2803; x_wconf 94'>TEMPO</span>
<span class='ocrx_word' id='word_1_666' title='bbox 478 2777 562 2804; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_667' title='bbox 561 2777 622 2810; x_wconf 95'>be</span>
<span class='ocr_header' id='line_1_75' title="bbox 347 2777 2174 2811; baseline 0.001 -8; x_size 33; x_descenders 5; x_ascenders 9">
<span class='ocrx_word' id='word_1_664' title='bbox 347 2782 360 2796; x_wconf 81'>¢</span>
<span class='ocrx_word' id='word_1_665' title='bbox 372 2777 504 2804; x_wconf 94'>TEMPO</span>
<span class='ocrx_word' id='word_1_666' title='bbox 515 2785 578 2810; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_667' title='bbox 588 2777 622 2804; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_668' title='bbox 633 2778 741 2804; x_wconf 95'>entered</span>
<span class='ocrx_word' id='word_1_669' title='bbox 751 2777 934 2811; x_wconf 96'>numerically,</span>
<span class='ocrx_word' id='word_1_670' title='bbox 946 2777 1101 2811; x_wconf 96'>adjustable</span>
<span class='ocrx_word' id='word_1_671' title='bbox 1111 2777 1139 2804; x_wconf 96'>in</span>
<span class='ocrx_word' id='word_1_670' title='bbox 946 2777 1101 2811; x_wconf 95'>adjustable</span>
<span class='ocrx_word' id='word_1_671' title='bbox 1111 2777 1139 2804; x_wconf 95'>in</span>
<span class='ocrx_word' id='word_1_672' title='bbox 1149 2778 1239 2805; x_wconf 96'>tenths</span>
<span class='ocrx_word' id='word_1_673' title='bbox 1250 2778 1282 2805; x_wconf 96'>of</span>
<span class='ocrx_word' id='word_1_674' title='bbox 1290 2786 1307 2805; x_wconf 93'>a</span>
<span class='ocrx_word' id='word_1_675' title='bbox 1317 2777 1567 2805; x_wconf 92'>Beat-Per-Minute</span>
<span class='ocrx_word' id='word_1_674' title='bbox 1290 2786 1307 2805; x_wconf 91'>a</span>
<span class='ocrx_word' id='word_1_675' title='bbox 1317 2777 1567 2805; x_wconf 91'>Beat-Per-Minute</span>
<span class='ocrx_word' id='word_1_676' title='bbox 1577 2777 1748 2809; x_wconf 96'>increments,</span>
<span class='ocrx_word' id='word_1_677' title='bbox 1760 2785 1792 2804; x_wconf 96'>or</span>
<span class='ocrx_word' id='word_1_678' title='bbox 1803 2777 1839 2810; x_wconf 96'>by</span>
@@ -987,36 +987,36 @@
<span class='ocrx_word' id='word_1_682' title='bbox 372 2822 410 2841; x_wconf 96'>on</span>
<span class='ocrx_word' id='word_1_683' title='bbox 420 2815 466 2842; x_wconf 95'>the</span>
<span class='ocrx_word' id='word_1_684' title='bbox 476 2815 545 2841; x_wconf 95'>TAP</span>
<span class='ocrx_word' id='word_1_685' title='bbox 556 2816 660 2842; x_wconf 95'>TEMPO</span>
<span class='ocrx_word' id='word_1_686' title='bbox 662 2815 808 2842; x_wconf 96'>button.</span>
<span class='ocrx_word' id='word_1_685' title='bbox 556 2815 689 2842; x_wconf 95'>TEMPO</span>
<span class='ocrx_word' id='word_1_686' title='bbox 699 2815 808 2842; x_wconf 96'>button.</span>
</span>
</p>
</div>
<div class='ocr_carea' id='block_1_31' title="bbox 347 2861 1792 2940">
<p class='ocr_par' id='par_1_39' lang='eng' title="bbox 347 2861 1792 2940">
<span class='ocr_line' id='line_1_77' title="bbox 347 2861 1792 2895; baseline 0.001 -8; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocr_header' id='line_1_77' title="bbox 347 2861 1792 2895; baseline 0.001 -8; x_size 33; x_descenders 6; x_ascenders 8">
<span class='ocrx_word' id='word_1_687' title='bbox 347 2866 360 2880; x_wconf 43'>¢</span>
<span class='ocrx_word' id='word_1_688' title='bbox 372 2861 504 2887; x_wconf 96'>TEMPO</span>
<span class='ocrx_word' id='word_1_689' title='bbox 515 2861 677 2888; x_wconf 96'>CHANGES</span>
<span class='ocrx_word' id='word_1_690' title='bbox 679 2861 771 2894; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_689' title='bbox 515 2861 696 2888; x_wconf 96'>CHANGES</span>
<span class='ocrx_word' id='word_1_690' title='bbox 707 2869 771 2894; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_691' title='bbox 781 2861 815 2888; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_692' title='bbox 825 2869 1000 2894; x_wconf 96'>programmed</span>
<span class='ocrx_word' id='word_1_693' title='bbox 1001 2861 1087 2888; x_wconf 96'>into</span>
<span class='ocrx_word' id='word_1_692' title='bbox 825 2861 1019 2894; x_wconf 96'>programmed</span>
<span class='ocrx_word' id='word_1_693' title='bbox 1030 2861 1087 2888; x_wconf 96'>into</span>
<span class='ocrx_word' id='word_1_694' title='bbox 1099 2869 1115 2888; x_wconf 95'>a</span>
<span class='ocrx_word' id='word_1_695' title='bbox 1126 2870 1268 2895; x_wconf 96'>sequence,</span>
<span class='ocrx_word' id='word_1_696' title='bbox 1280 2861 1344 2888; x_wconf 96'>with</span>
<span class='ocrx_word' id='word_1_697' title='bbox 1356 2866 1448 2888; x_wconf 95'>smooth</span>
<span class='ocrx_word' id='word_1_698' title='bbox 1450 2861 1635 2888; x_wconf 96'>transitions</span>
<span class='ocrx_word' id='word_1_697' title='bbox 1356 2862 1467 2888; x_wconf 95'>smooth</span>
<span class='ocrx_word' id='word_1_698' title='bbox 1478 2861 1635 2888; x_wconf 96'>transitions</span>
<span class='ocrx_word' id='word_1_699' title='bbox 1646 2861 1670 2887; x_wconf 96'>if</span>
<span class='ocrx_word' id='word_1_700' title='bbox 1679 2861 1792 2888; x_wconf 87'>desired.</span>
</span>
<span class='ocr_line' id='line_1_78' title="bbox 347 2906 1507 2940; baseline 0.002 -8; x_size 33; x_descenders 7; x_ascenders 8">
<span class='ocr_header' id='line_1_78' title="bbox 347 2906 1507 2940; baseline 0.002 -8; x_size 33; x_descenders 7; x_ascenders 8">
<span class='ocrx_word' id='word_1_701' title='bbox 347 2911 360 2925; x_wconf 69'>¢</span>
<span class='ocrx_word' id='word_1_702' title='bbox 371 2906 434 2938; x_wconf 96'>Any</span>
<span class='ocrx_word' id='word_1_703' title='bbox 444 2906 539 2932; x_wconf 96'>TIME</span>
<span class='ocrx_word' id='word_1_704' title='bbox 550 2906 739 2933; x_wconf 96'>SIGNATURE</span>
<span class='ocrx_word' id='word_1_705' title='bbox 740 2907 820 2933; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_706' title='bbox 819 2907 880 2939; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_704' title='bbox 550 2906 763 2933; x_wconf 96'>SIGNATURE</span>
<span class='ocrx_word' id='word_1_705' title='bbox 773 2915 836 2939; x_wconf 96'>may</span>
<span class='ocrx_word' id='word_1_706' title='bbox 846 2907 880 2933; x_wconf 96'>be</span>
<span class='ocrx_word' id='word_1_707' title='bbox 891 2907 968 2938; x_wconf 96'>used,</span>
<span class='ocrx_word' id='word_1_708' title='bbox 980 2907 1036 2934; x_wconf 96'>and</span>
<span class='ocrx_word' id='word_1_709' title='bbox 1046 2915 1109 2940; x_wconf 96'>may</span>
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -1 +1 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
@@ -1 +0,0 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
@@ -1,13 +0,0 @@
Portez ce vieux whisky au juge
blond qui fume sur son Ile
interieure, a cöte de l'alcöve
ovoide, oU les büches se
consument dans l'ätre, ce qui
lui permet de penser & la
caenogenese de |'etre dont il
est question dans la cause
ambigu& entendue a MoY, dans
un capharnaüm qui, pense-t-il,
diminue ca et la la qualite de son
ceuvre.
@@ -1,54 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name='ocr-system' content='tesseract 4.0.0' />
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word ocrp_wconf'/>
</head>
<body>
<div class='ocr_page' id='page_1' title='image "/var/folders/2s/7t022mgj0h5cprbq0dtb1ksm0000gn/T/com.github.ocrmypdf.iw9uvooj/000001_ocr.png"; bbox 0 0 900 900; ppageno 0'>
<div class='ocr_carea' id='block_1_1' title="bbox 97 87 502 204">
<p class='ocr_par' id='par_1_1' lang='eng' title="bbox 97 87 502 204">
<span class='ocr_line' id='line_1_1' title="bbox 97 87 399 122; baseline 0 0; x_size 40.714287; x_descenders 5.7142859; x_ascenders 15">
<span class='ocrx_word' id='word_1_1' title='bbox 97 87 285 122; x_wconf 95'>Multicolor</span>
<span class='ocrx_word' id='word_1_2' title='bbox 302 87 399 122; x_wconf 96'>Black</span>
</span>
<span class='ocr_line' id='line_1_2' title="bbox 114 160 502 204; baseline 0 -9; x_size 44; x_descenders 9; x_ascenders 15">
<span class='ocrx_word' id='word_1_3' title='bbox 114 163 191 195; x_wconf 96'>Pure</span>
<span class='ocrx_word' id='word_1_4' title='bbox 210 160 308 195; x_wconf 93'>Black</span>
<span class='ocrx_word' id='word_1_5' title='bbox 325 160 363 204; x_wconf 93'>(K</span>
<span class='ocrx_word' id='word_1_6' title='bbox 378 174 400 183; x_wconf 96'>=</span>
<span class='ocrx_word' id='word_1_7' title='bbox 421 160 502 204; x_wconf 70'>100}</span>
</span>
</p>
</div>
<div class='ocr_carea' id='block_1_2' title="bbox 121 282 383 326">
<p class='ocr_par' id='par_1_2' lang='eng' title="bbox 121 282 383 326">
<span class='ocr_line' id='line_1_3' title="bbox 121 282 383 326; baseline 0 -11; x_size 44; x_descenders 11; x_ascenders 13">
<span class='ocrx_word' id='word_1_8' title='bbox 121 283 199 315; x_wconf 96'>Pure</span>
<span class='ocrx_word' id='word_1_9' title='bbox 216 282 383 326; x_wconf 96'>Magenta</span>
</span>
</p>
</div>
<div class='ocr_carea' id='block_1_3' title="bbox 140 375 329 418">
<p class='ocr_par' id='par_1_3' lang='eng' title="bbox 140 375 329 418">
<span class='ocr_line' id='line_1_4' title="bbox 140 375 329 418; baseline 0 -11; x_size 43; x_descenders 11; x_ascenders 12">
<span class='ocrx_word' id='word_1_10' title='bbox 140 375 218 407; x_wconf 96'>Pure</span>
<span class='ocrx_word' id='word_1_11' title='bbox 234 375 329 418; x_wconf 96'>Cyan</span>
</span>
</p>
</div>
<div class='ocr_carea' id='block_1_4' title="bbox 207 570 422 605">
<p class='ocr_par' id='par_1_4' lang='eng' title="bbox 207 570 422 605">
<span class='ocr_line' id='line_1_5' title="bbox 207 570 422 605; baseline 0 0; x_size 40.365852; x_descenders 5.3658538; x_ascenders 15">
<span class='ocrx_word' id='word_1_12' title='bbox 207 573 285 605; x_wconf 96'>Pure</span>
<span class='ocrx_word' id='word_1_13' title='bbox 299 570 422 605; x_wconf 95'>Yellow</span>
</span>
</p>
</div>
</div>
</body>
</html>
@@ -1 +0,0 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
@@ -1,9 +0,0 @@
Multicolor Black
Pure Black (K = 100}
Pure Magenta
Pure Cyan
Pure Yellow
@@ -1 +0,0 @@
Tesseract Open Source OCR Engine v4.0.0 with Leptonica
@@ -1,9 +0,0 @@
Multicolor Black
Pure Black (K = 100}
Pure Magenta
Pure Cyan
Pure Yellow

Some files were not shown because too many files have changed in this diff Show More