feat(voice): transcribe Telegram voice notes with SpeechRecognition
Add a voice message handler that downloads OGG audio, converts it to WAV via pydub/ffmpeg, and transcribes it with Google Speech Recognition (pt-BR) before feeding the text into the normal message pipeline. Also extract shared text processing into process_text_message, guard against updates without a text attribute, switch the default AI provider to openrouter with deepseek-v4-flash, and document the ffmpeg system requirement in DEPLOY.md.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
pip
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
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
|
||||
this service 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.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
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
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the 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 a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE 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.
|
||||
|
||||
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
|
||||
convey 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 General Public License as published by
|
||||
the Free Software Foundation; either version 2 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,12 @@
|
||||
Copyright (c) 2014-2017, Anthony Zhang <azhang9@gmail.com>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,416 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: SpeechRecognition
|
||||
Version: 3.10.0
|
||||
Summary: Library for performing speech recognition, with support for several engines and APIs, online and offline.
|
||||
Home-page: https://github.com/Uberi/speech_recognition#readme
|
||||
Author: Anthony Zhang (Uberi)
|
||||
Author-email: azhang9@gmail.com
|
||||
License: BSD
|
||||
Keywords: speech recognition voice sphinx google wit bing api houndify ibm snowboy
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Natural Language :: English
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: Microsoft :: Windows
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
Classifier: Operating System :: MacOS :: MacOS X
|
||||
Classifier: Operating System :: Other OS
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
||||
Requires-Python: >=3.8
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE-FLAC.txt
|
||||
License-File: LICENSE.txt
|
||||
Requires-Dist: requests (>=2.26.0)
|
||||
Provides-Extra: whisper-api
|
||||
Requires-Dist: openai ; extra == 'whisper-api'
|
||||
|
||||
SpeechRecognition
|
||||
=================
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/SpeechRecognition.svg
|
||||
:target: https://pypi.python.org/pypi/SpeechRecognition/
|
||||
:alt: Latest Version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/status/SpeechRecognition.svg
|
||||
:target: https://pypi.python.org/pypi/SpeechRecognition/
|
||||
:alt: Development Status
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/SpeechRecognition.svg
|
||||
:target: https://pypi.python.org/pypi/SpeechRecognition/
|
||||
:alt: Supported Python Versions
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/SpeechRecognition.svg
|
||||
:target: https://pypi.python.org/pypi/SpeechRecognition/
|
||||
:alt: License
|
||||
|
||||
.. image:: https://api.travis-ci.org/Uberi/speech_recognition.svg?branch=master
|
||||
:target: https://travis-ci.org/Uberi/speech_recognition
|
||||
:alt: Continuous Integration Test Results
|
||||
|
||||
Library for performing speech recognition, with support for several engines and APIs, online and offline.
|
||||
|
||||
**UPDATE 2022-02-09**: Hey everyone! This project started as a tech demo, but these days it needs more time than I have to keep up with all the PRs and issues. Therefore, I'd like to put out an **open invite for collaborators** - just reach out at me@anthonyz.ca if you're interested!
|
||||
|
||||
Speech recognition engine/API support:
|
||||
|
||||
* `CMU Sphinx <http://cmusphinx.sourceforge.net/wiki/>`__ (works offline)
|
||||
* Google Speech Recognition
|
||||
* `Google Cloud Speech API <https://cloud.google.com/speech/>`__
|
||||
* `Wit.ai <https://wit.ai/>`__
|
||||
* `Microsoft Azure Speech <https://azure.microsoft.com/en-us/services/cognitive-services/speech/>`__
|
||||
* `Microsoft Bing Voice Recognition (Deprecated) <https://www.microsoft.com/cognitive-services/en-us/speech-api>`__
|
||||
* `Houndify API <https://houndify.com/>`__
|
||||
* `IBM Speech to Text <http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/speech-to-text.html>`__
|
||||
* `Snowboy Hotword Detection <https://snowboy.kitt.ai/>`__ (works offline)
|
||||
* `Tensorflow <https://www.tensorflow.org/>`__
|
||||
* `Vosk API <https://github.com/alphacep/vosk-api/>`__ (works offline)
|
||||
* `OpenAI whisper <https://github.com/openai/whisper>`__ (works offline)
|
||||
* `Whisper API <https://platform.openai.com/docs/guides/speech-to-text>`__
|
||||
|
||||
**Quickstart:** ``pip install SpeechRecognition``. See the "Installing" section for more details.
|
||||
|
||||
To quickly try it out, run ``python -m speech_recognition`` after installing.
|
||||
|
||||
Project links:
|
||||
|
||||
- `PyPI <https://pypi.python.org/pypi/SpeechRecognition/>`__
|
||||
- `Source code <https://github.com/Uberi/speech_recognition>`__
|
||||
- `Issue tracker <https://github.com/Uberi/speech_recognition/issues>`__
|
||||
|
||||
Library Reference
|
||||
-----------------
|
||||
|
||||
The `library reference <https://github.com/Uberi/speech_recognition/blob/master/reference/library-reference.rst>`__ documents every publicly accessible object in the library. This document is also included under ``reference/library-reference.rst``.
|
||||
|
||||
See `Notes on using PocketSphinx <https://github.com/Uberi/speech_recognition/blob/master/reference/pocketsphinx.rst>`__ for information about installing languages, compiling PocketSphinx, and building language packs from online resources. This document is also included under ``reference/pocketsphinx.rst``.
|
||||
|
||||
You have to install Vosk models for using Vosk. `Here <https://alphacephei.com/vosk/models>`__ are models avaiable. You have to place them in models folder of your project, like "your-project-folder/models/your-vosk-model"
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
See the ``examples/`` `directory <https://github.com/Uberi/speech_recognition/tree/master/examples>`__ in the repository root for usage examples:
|
||||
|
||||
- `Recognize speech input from the microphone <https://github.com/Uberi/speech_recognition/blob/master/examples/microphone_recognition.py>`__
|
||||
- `Transcribe an audio file <https://github.com/Uberi/speech_recognition/blob/master/examples/audio_transcribe.py>`__
|
||||
- `Save audio data to an audio file <https://github.com/Uberi/speech_recognition/blob/master/examples/write_audio.py>`__
|
||||
- `Show extended recognition results <https://github.com/Uberi/speech_recognition/blob/master/examples/extended_results.py>`__
|
||||
- `Calibrate the recognizer energy threshold for ambient noise levels <https://github.com/Uberi/speech_recognition/blob/master/examples/calibrate_energy_threshold.py>`__ (see ``recognizer_instance.energy_threshold`` for details)
|
||||
- `Listening to a microphone in the background <https://github.com/Uberi/speech_recognition/blob/master/examples/background_listening.py>`__
|
||||
- `Various other useful recognizer features <https://github.com/Uberi/speech_recognition/blob/master/examples/special_recognizer_features.py>`__
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
First, make sure you have all the requirements listed in the "Requirements" section.
|
||||
|
||||
The easiest way to install this is using ``pip install SpeechRecognition``.
|
||||
|
||||
Otherwise, download the source distribution from `PyPI <https://pypi.python.org/pypi/SpeechRecognition/>`__, and extract the archive.
|
||||
|
||||
In the folder, run ``python setup.py install``.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
To use all of the functionality of the library, you should have:
|
||||
|
||||
* **Python** 3.8+ (required)
|
||||
* **PyAudio** 0.2.11+ (required only if you need to use microphone input, ``Microphone``)
|
||||
* **PocketSphinx** (required only if you need to use the Sphinx recognizer, ``recognizer_instance.recognize_sphinx``)
|
||||
* **Google API Client Library for Python** (required only if you need to use the Google Cloud Speech API, ``recognizer_instance.recognize_google_cloud``)
|
||||
* **FLAC encoder** (required only if the system is not x86-based Windows/Linux/OS X)
|
||||
* **Vosk** (required only if you need to use Vosk API speech recognition ``recognizer_instance.recognize_vosk``)
|
||||
* **Whisper** (required only if you need to use Whisper ``recognizer_instance.recognize_whisper``)
|
||||
* **openai** (required only if you need to use Whisper API speech recognition ``recognizer_instance.recognize_whisper_api``)
|
||||
|
||||
The following requirements are optional, but can improve or extend functionality in some situations:
|
||||
|
||||
* If using CMU Sphinx, you may want to `install additional language packs <https://github.com/Uberi/speech_recognition/blob/master/reference/pocketsphinx.rst#installing-other-languages>`__ to support languages like International French or Mandarin Chinese.
|
||||
|
||||
The following sections go over the details of each requirement.
|
||||
|
||||
Python
|
||||
~~~~~~
|
||||
|
||||
The first software requirement is `Python 3.8+ <https://www.python.org/downloads/>`__. This is required to use the library.
|
||||
|
||||
PyAudio (for microphone users)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
`PyAudio <http://people.csail.mit.edu/hubert/pyaudio/#downloads>`__ is required if and only if you want to use microphone input (``Microphone``). PyAudio version 0.2.11+ is required, as earlier versions have known memory management bugs when recording from microphones in certain situations.
|
||||
|
||||
If not installed, everything in the library will still work, except attempting to instantiate a ``Microphone`` object will raise an ``AttributeError``.
|
||||
|
||||
The installation instructions on the PyAudio website are quite good - for convenience, they are summarized below:
|
||||
|
||||
* On Windows, install PyAudio using `Pip <https://pip.readthedocs.org/>`__: execute ``pip install pyaudio`` in a terminal.
|
||||
* On Debian-derived Linux distributions (like Ubuntu and Mint), install PyAudio using `APT <https://wiki.debian.org/Apt>`__: execute ``sudo apt-get install python-pyaudio python3-pyaudio`` in a terminal.
|
||||
* If the version in the repositories is too old, install the latest release using Pip: execute ``sudo apt-get install portaudio19-dev python-all-dev python3-all-dev && sudo pip install pyaudio`` (replace ``pip`` with ``pip3`` if using Python 3).
|
||||
* On OS X, install PortAudio using `Homebrew <http://brew.sh/>`__: ``brew install portaudio``. Then, install PyAudio using `Pip <https://pip.readthedocs.org/>`__: ``pip install pyaudio``.
|
||||
* On other POSIX-based systems, install the ``portaudio19-dev`` and ``python-all-dev`` (or ``python3-all-dev`` if using Python 3) packages (or their closest equivalents) using a package manager of your choice, and then install PyAudio using `Pip <https://pip.readthedocs.org/>`__: ``pip install pyaudio`` (replace ``pip`` with ``pip3`` if using Python 3).
|
||||
|
||||
PyAudio `wheel packages <https://pypi.python.org/pypi/wheel>`__ for common 64-bit Python versions on Windows and Linux are included for convenience, under the ``third-party/`` `directory <https://github.com/Uberi/speech_recognition/tree/master/third-party>`__ in the repository root. To install, simply run ``pip install wheel`` followed by ``pip install ./third-party/WHEEL_FILENAME`` (replace ``pip`` with ``pip3`` if using Python 3) in the repository `root directory <https://github.com/Uberi/speech_recognition>`__.
|
||||
|
||||
PocketSphinx-Python (for Sphinx users)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
`PocketSphinx-Python <https://github.com/bambocher/pocketsphinx-python>`__ is **required if and only if you want to use the Sphinx recognizer** (``recognizer_instance.recognize_sphinx``).
|
||||
|
||||
PocketSphinx-Python `wheel packages <https://pypi.python.org/pypi/wheel>`__ for 64-bit Python 3.4, and 3.5 on Windows are included for convenience, under the ``third-party/`` `directory <https://github.com/Uberi/speech_recognition/tree/master/third-party>`__. To install, simply run ``pip install wheel`` followed by ``pip install ./third-party/WHEEL_FILENAME`` (replace ``pip`` with ``pip3`` if using Python 3) in the SpeechRecognition folder.
|
||||
|
||||
On Linux and other POSIX systems (such as OS X), follow the instructions under "Building PocketSphinx-Python from source" in `Notes on using PocketSphinx <https://github.com/Uberi/speech_recognition/blob/master/reference/pocketsphinx.rst>`__ for installation instructions.
|
||||
|
||||
Note that the versions available in most package repositories are outdated and will not work with the bundled language data. Using the bundled wheel packages or building from source is recommended.
|
||||
|
||||
See `Notes on using PocketSphinx <https://github.com/Uberi/speech_recognition/blob/master/reference/pocketsphinx.rst>`__ for information about installing languages, compiling PocketSphinx, and building language packs from online resources. This document is also included under ``reference/pocketsphinx.rst``.
|
||||
|
||||
Vosk (for Vosk users)
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
Vosk API is **required if and only if you want to use Vosk recognizer** (``recognizer_instance.recognize_vosk``).
|
||||
|
||||
You can install it with ``python3 -m pip install vosk``.
|
||||
|
||||
You also have to install Vosk Models:
|
||||
|
||||
`Here <https://alphacephei.com/vosk/models>`__ are models avaiable for download. You have to place them in models folder of your project, like "your-project-folder/models/your-vosk-model"
|
||||
|
||||
Google Cloud Speech Library for Python (for Google Cloud Speech API users)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
`Google Cloud Speech library for Python <https://cloud.google.com/speech-to-text/docs/quickstart>`__ is required if and only if you want to use the Google Cloud Speech API (``recognizer_instance.recognize_google_cloud``).
|
||||
|
||||
If not installed, everything in the library will still work, except calling ``recognizer_instance.recognize_google_cloud`` will raise an ``RequestError``.
|
||||
|
||||
According to the `official installation instructions <https://cloud.google.com/speech-to-text/docs/quickstart>`__, the recommended way to install this is using `Pip <https://pip.readthedocs.org/>`__: execute ``pip install google-cloud-speech`` (replace ``pip`` with ``pip3`` if using Python 3).
|
||||
|
||||
FLAC (for some systems)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A `FLAC encoder <https://xiph.org/flac/>`__ is required to encode the audio data to send to the API. If using Windows (x86 or x86-64), OS X (Intel Macs only, OS X 10.6 or higher), or Linux (x86 or x86-64), this is **already bundled with this library - you do not need to install anything**.
|
||||
|
||||
Otherwise, ensure that you have the ``flac`` command line tool, which is often available through the system package manager. For example, this would usually be ``sudo apt-get install flac`` on Debian-derivatives, or ``brew install flac`` on OS X with Homebrew.
|
||||
|
||||
Whisper (for Whisper users)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Whisper is **required if and only if you want to use whisper** (``recognizer_instance.recognize_whisper``).
|
||||
|
||||
You can install it with ``python3 -m pip install git+https://github.com/openai/whisper.git soundfile``.
|
||||
|
||||
Whisper API (for Whisper API users)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The library `openai <https://pypi.org/project/openai/>`__ is **required if and only if you want to use Whisper API** (``recognizer_instance.recognize_whisper_api``).
|
||||
|
||||
If not installed, everything in the library will still work, except calling ``recognizer_instance.recognize_whisper_api`` will raise an ``RequestError``.
|
||||
|
||||
You can install it with ``python3 -m pip install openai``.
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
The recognizer tries to recognize speech even when I'm not speaking, or after I'm done speaking.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Try increasing the ``recognizer_instance.energy_threshold`` property. This is basically how sensitive the recognizer is to when recognition should start. Higher values mean that it will be less sensitive, which is useful if you are in a loud room.
|
||||
|
||||
This value depends entirely on your microphone or audio data. There is no one-size-fits-all value, but good values typically range from 50 to 4000.
|
||||
|
||||
Also, check on your microphone volume settings. If it is too sensitive, the microphone may be picking up a lot of ambient noise. If it is too insensitive, the microphone may be rejecting speech as just noise.
|
||||
|
||||
The recognizer can't recognize speech right after it starts listening for the first time.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``recognizer_instance.energy_threshold`` property is probably set to a value that is too high to start off with, and then being adjusted lower automatically by dynamic energy threshold adjustment. Before it is at a good level, the energy threshold is so high that speech is just considered ambient noise.
|
||||
|
||||
The solution is to decrease this threshold, or call ``recognizer_instance.adjust_for_ambient_noise`` beforehand, which will set the threshold to a good value automatically.
|
||||
|
||||
The recognizer doesn't understand my particular language/dialect.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Try setting the recognition language to your language/dialect. To do this, see the documentation for ``recognizer_instance.recognize_sphinx``, ``recognizer_instance.recognize_google``, ``recognizer_instance.recognize_wit``, ``recognizer_instance.recognize_bing``, ``recognizer_instance.recognize_api``, ``recognizer_instance.recognize_houndify``, and ``recognizer_instance.recognize_ibm``.
|
||||
|
||||
For example, if your language/dialect is British English, it is better to use ``"en-GB"`` as the language rather than ``"en-US"``.
|
||||
|
||||
The recognizer hangs on ``recognizer_instance.listen``; specifically, when it's calling ``Microphone.MicrophoneStream.read``.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This usually happens when you're using a Raspberry Pi board, which doesn't have audio input capabilities by itself. This causes the default microphone used by PyAudio to simply block when we try to read it. If you happen to be using a Raspberry Pi, you'll need a USB sound card (or USB microphone).
|
||||
|
||||
Once you do this, change all instances of ``Microphone()`` to ``Microphone(device_index=MICROPHONE_INDEX)``, where ``MICROPHONE_INDEX`` is the hardware-specific index of the microphone.
|
||||
|
||||
To figure out what the value of ``MICROPHONE_INDEX`` should be, run the following code:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import speech_recognition as sr
|
||||
for index, name in enumerate(sr.Microphone.list_microphone_names()):
|
||||
print("Microphone with name \"{1}\" found for `Microphone(device_index={0})`".format(index, name))
|
||||
|
||||
This will print out something like the following:
|
||||
|
||||
::
|
||||
|
||||
Microphone with name "HDA Intel HDMI: 0 (hw:0,3)" found for `Microphone(device_index=0)`
|
||||
Microphone with name "HDA Intel HDMI: 1 (hw:0,7)" found for `Microphone(device_index=1)`
|
||||
Microphone with name "HDA Intel HDMI: 2 (hw:0,8)" found for `Microphone(device_index=2)`
|
||||
Microphone with name "Blue Snowball: USB Audio (hw:1,0)" found for `Microphone(device_index=3)`
|
||||
Microphone with name "hdmi" found for `Microphone(device_index=4)`
|
||||
Microphone with name "pulse" found for `Microphone(device_index=5)`
|
||||
Microphone with name "default" found for `Microphone(device_index=6)`
|
||||
|
||||
Now, to use the Snowball microphone, you would change ``Microphone()`` to ``Microphone(device_index=3)``.
|
||||
|
||||
Calling ``Microphone()`` gives the error ``IOError: No Default Input Device Available``.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As the error says, the program doesn't know which microphone to use.
|
||||
|
||||
To proceed, either use ``Microphone(device_index=MICROPHONE_INDEX, ...)`` instead of ``Microphone(...)``, or set a default microphone in your OS. You can obtain possible values of ``MICROPHONE_INDEX`` using the code in the troubleshooting entry right above this one.
|
||||
|
||||
The program doesn't run when compiled with `PyInstaller <https://github.com/pyinstaller/pyinstaller/wiki>`__.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of PyInstaller version 3.0, SpeechRecognition is supported out of the box. If you're getting weird issues when compiling your program using PyInstaller, simply update PyInstaller.
|
||||
|
||||
You can easily do this by running ``pip install --upgrade pyinstaller``.
|
||||
|
||||
On Ubuntu/Debian, I get annoying output in the terminal saying things like "bt_audio_service_open: [...] Connection refused" and various others.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The "bt_audio_service_open" error means that you have a Bluetooth audio device, but as a physical device is not currently connected, we can't actually use it - if you're not using a Bluetooth microphone, then this can be safely ignored. If you are, and audio isn't working, then double check to make sure your microphone is actually connected. There does not seem to be a simple way to disable these messages.
|
||||
|
||||
For errors of the form "ALSA lib [...] Unknown PCM", see `this StackOverflow answer <http://stackoverflow.com/questions/7088672/pyaudio-working-but-spits-out-error-messages-each-time>`__. Basically, to get rid of an error of the form "Unknown PCM cards.pcm.rear", simply comment out ``pcm.rear cards.pcm.rear`` in ``/usr/share/alsa/alsa.conf``, ``~/.asoundrc``, and ``/etc/asound.conf``.
|
||||
|
||||
For "jack server is not running or cannot be started" or "connect(2) call to /dev/shm/jack-1000/default/jack_0 failed (err=No such file or directory)" or "attempt to connect to server failed", these are caused by ALSA trying to connect to JACK, and can be safely ignored. I'm not aware of any simple way to turn those messages off at this time, besides `entirely disabling printing while starting the microphone <https://github.com/Uberi/speech_recognition/issues/182#issuecomment-266256337>`__.
|
||||
|
||||
On OS X, I get a ``ChildProcessError`` saying that it couldn't find the system FLAC converter, even though it's installed.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Installing `FLAC for OS X <https://xiph.org/flac/download.html>`__ directly from the source code will not work, since it doesn't correctly add the executables to the search path.
|
||||
|
||||
Installing FLAC using `Homebrew <http://brew.sh/>`__ ensures that the search path is correctly updated. First, ensure you have Homebrew, then run ``brew install flac`` to install the necessary files.
|
||||
|
||||
Developing
|
||||
----------
|
||||
|
||||
To hack on this library, first make sure you have all the requirements listed in the "Requirements" section.
|
||||
|
||||
- Most of the library code lives in ``speech_recognition/__init__.py``.
|
||||
- Examples live under the ``examples/`` `directory <https://github.com/Uberi/speech_recognition/tree/master/examples>`__, and the demo script lives in ``speech_recognition/__main__.py``.
|
||||
- The FLAC encoder binaries are in the ``speech_recognition/`` `directory <https://github.com/Uberi/speech_recognition/tree/master/speech_recognition>`__.
|
||||
- Documentation can be found in the ``reference/`` `directory <https://github.com/Uberi/speech_recognition/tree/master/reference>`__.
|
||||
- Third-party libraries, utilities, and reference material are in the ``third-party/`` `directory <https://github.com/Uberi/speech_recognition/tree/master/third-party>`__.
|
||||
|
||||
To install/reinstall the library locally, run ``python setup.py install`` in the project `root directory <https://github.com/Uberi/speech_recognition>`__.
|
||||
|
||||
Before a release, the version number is bumped in ``README.rst`` and ``speech_recognition/__init__.py``. Version tags are then created using ``git config gpg.program gpg2 && git config user.signingkey DB45F6C431DE7C2DCD99FF7904882258A4063489 && git tag -s VERSION_GOES_HERE -m "Version VERSION_GOES_HERE"``.
|
||||
|
||||
Releases are done by running ``make-release.sh VERSION_GOES_HERE`` to build the Python source packages, sign them, and upload them to PyPI.
|
||||
|
||||
Testing
|
||||
~~~~~~~
|
||||
|
||||
To run all the tests:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
python -m unittest discover --verbose
|
||||
|
||||
Testing is also done automatically by TravisCI, upon every push. To set up the environment for offline/local Travis-like testing on a Debian-like system:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
sudo docker run --volume "$(pwd):/speech_recognition" --interactive --tty quay.io/travisci/travis-python:latest /bin/bash
|
||||
su - travis && cd /speech_recognition
|
||||
sudo apt-get update && sudo apt-get install swig libpulse-dev
|
||||
pip install --user pocketsphinx && pip install --user flake8 rstcheck && pip install --user -e .
|
||||
python -m unittest discover --verbose # run unit tests
|
||||
python -m flake8 --ignore=E501,E701 speech_recognition tests examples setup.py # ignore errors for long lines and multi-statement lines
|
||||
python -m rstcheck README.rst reference/*.rst # ensure RST is well-formed
|
||||
|
||||
FLAC Executables
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The included ``flac-win32`` executable is the `official FLAC 1.3.2 32-bit Windows binary <http://downloads.xiph.org/releases/flac/flac-1.3.2-win.zip>`__.
|
||||
|
||||
The included ``flac-linux-x86`` and ``flac-linux-x86_64`` executables are built from the `FLAC 1.3.2 source code <http://downloads.xiph.org/releases/flac/flac-1.3.2.tar.xz>`__ with `Manylinux <https://github.com/pypa/manylinux>`__ to ensure that it's compatible with a wide variety of distributions.
|
||||
|
||||
The built FLAC executables should be bit-for-bit reproducible. To rebuild them, run the following inside the project directory on a Debian-like system:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
# download and extract the FLAC source code
|
||||
cd third-party
|
||||
sudo apt-get install --yes docker.io
|
||||
|
||||
# build FLAC inside the Manylinux i686 Docker image
|
||||
tar xf flac-1.3.2.tar.xz
|
||||
sudo docker run --tty --interactive --rm --volume "$(pwd):/root" quay.io/pypa/manylinux1_i686:latest bash
|
||||
cd /root/flac-1.3.2
|
||||
./configure LDFLAGS=-static # compiler flags to make a static build
|
||||
make
|
||||
exit
|
||||
cp flac-1.3.2/src/flac/flac ../speech_recognition/flac-linux-x86 && sudo rm -rf flac-1.3.2/
|
||||
|
||||
# build FLAC inside the Manylinux x86_64 Docker image
|
||||
tar xf flac-1.3.2.tar.xz
|
||||
sudo docker run --tty --interactive --rm --volume "$(pwd):/root" quay.io/pypa/manylinux1_x86_64:latest bash
|
||||
cd /root/flac-1.3.2
|
||||
./configure LDFLAGS=-static # compiler flags to make a static build
|
||||
make
|
||||
exit
|
||||
cp flac-1.3.2/src/flac/flac ../speech_recognition/flac-linux-x86_64 && sudo rm -r flac-1.3.2/
|
||||
|
||||
The included ``flac-mac`` executable is extracted from `xACT 2.39 <http://xact.scottcbrown.org/>`__, which is a frontend for FLAC 1.3.2 that conveniently includes binaries for all of its encoders. Specifically, it is a copy of ``xACT 2.39/xACT.app/Contents/Resources/flac`` in ``xACT2.39.zip``.
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
::
|
||||
|
||||
Uberi <me@anthonyz.ca> (Anthony Zhang)
|
||||
bobsayshilol
|
||||
arvindch <achembarpu@gmail.com> (Arvind Chembarpu)
|
||||
kevinismith <kevin_i_smith@yahoo.com> (Kevin Smith)
|
||||
haas85
|
||||
DelightRun <changxu.mail@gmail.com>
|
||||
maverickagm
|
||||
kamushadenes <kamushadenes@hyadesinc.com> (Kamus Hadenes)
|
||||
sbraden <braden.sarah@gmail.com> (Sarah Braden)
|
||||
tb0hdan (Bohdan Turkynewych)
|
||||
Thynix <steve@asksteved.com> (Steve Dougherty)
|
||||
beeedy <broderick.carlin@gmail.com> (Broderick Carlin)
|
||||
|
||||
Please report bugs and suggestions at the `issue tracker <https://github.com/Uberi/speech_recognition/issues>`__!
|
||||
|
||||
How to cite this library (APA style):
|
||||
|
||||
Zhang, A. (2017). Speech Recognition (Version 3.8) [Software]. Available from https://github.com/Uberi/speech_recognition#readme.
|
||||
|
||||
How to cite this library (Chicago style):
|
||||
|
||||
Zhang, Anthony. 2017. *Speech Recognition* (version 3.8).
|
||||
|
||||
Also check out the `Python Baidu Yuyin API <https://github.com/DelightRun/PyBaiduYuyin>`__, which is based on an older version of this project, and adds support for `Baidu Yuyin <http://yuyin.baidu.com/>`__. Note that Baidu Yuyin is only available inside China.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright 2014-2017 `Anthony Zhang (Uberi) <http://anthonyz.ca/>`__. The source code for this library is available online at `GitHub <https://github.com/Uberi/speech_recognition>`__.
|
||||
|
||||
SpeechRecognition is made available under the 3-clause BSD license. See ``LICENSE.txt`` in the project's `root directory <https://github.com/Uberi/speech_recognition>`__ for more information.
|
||||
|
||||
For convenience, all the official distributions of SpeechRecognition already include a copy of the necessary copyright notices and licenses. In your project, you can simply **say that licensing information for SpeechRecognition can be found within the SpeechRecognition README, and make sure SpeechRecognition is visible to users if they wish to see it**.
|
||||
|
||||
SpeechRecognition distributes source code, binaries, and language files from `CMU Sphinx <http://cmusphinx.sourceforge.net/>`__. These files are BSD-licensed and redistributable as long as copyright notices are correctly retained. See ``speech_recognition/pocketsphinx-data/*/LICENSE*.txt`` and ``third-party/LICENSE-Sphinx.txt`` for license details for individual parts.
|
||||
|
||||
SpeechRecognition distributes source code and binaries from `PyAudio <http://people.csail.mit.edu/hubert/pyaudio/>`__. These files are MIT-licensed and redistributable as long as copyright notices are correctly retained. See ``third-party/LICENSE-PyAudio.txt`` for license details.
|
||||
|
||||
SpeechRecognition distributes binaries from `FLAC <https://xiph.org/flac/>`__ - ``speech_recognition/flac-win32.exe``, ``speech_recognition/flac-linux-x86``, and ``speech_recognition/flac-mac``. These files are GPLv2-licensed and redistributable, as long as the terms of the GPL are satisfied. The FLAC binaries are an `aggregate <https://www.gnu.org/licenses/gpl-faq.html#MereAggregation>`__ of `separate programs <https://www.gnu.org/licenses/gpl-faq.html#NFUseGPLPlugins>`__, so these GPL restrictions do not apply to the library or your programs that use the library, only to FLAC itself. See ``LICENSE-FLAC.txt`` for license details.
|
||||
@@ -0,0 +1,35 @@
|
||||
SpeechRecognition-3.10.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
SpeechRecognition-3.10.0.dist-info/LICENSE-FLAC.txt,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
|
||||
SpeechRecognition-3.10.0.dist-info/LICENSE.txt,sha256=XUbseO51bVM11Ho-UDWeVMpWwK2qdqLWbpIZX96X10U,1519
|
||||
SpeechRecognition-3.10.0.dist-info/METADATA,sha256=kWq32D5V38A8oTNuZGk5BSJO7QOTfcw6XghWktjhcRQ,28857
|
||||
SpeechRecognition-3.10.0.dist-info/RECORD,,
|
||||
SpeechRecognition-3.10.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
SpeechRecognition-3.10.0.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
|
||||
SpeechRecognition-3.10.0.dist-info/top_level.txt,sha256=iJfbFMOCcDL7Mdhysq4LsbniwTv6EtQR8BbgEnKlPIw,19
|
||||
speech_recognition/__init__.py,sha256=B8T8R4TIm_hwycpZyd3jB-SS6QHvvlQFRG0umnZBh9o,94078
|
||||
speech_recognition/__main__.py,sha256=Afbp3l1yq2AW2Bsm7YBz-O0wm6gHNudgPZh69Ijjubc,833
|
||||
speech_recognition/__pycache__/__init__.cpython-311.pyc,,
|
||||
speech_recognition/__pycache__/__main__.cpython-311.pyc,,
|
||||
speech_recognition/__pycache__/audio.cpython-311.pyc,,
|
||||
speech_recognition/__pycache__/exceptions.cpython-311.pyc,,
|
||||
speech_recognition/audio.py,sha256=2IHCx9EoQRAJSNFRZsnui3Ags3gBWtAmKmG8wIB3MDY,14807
|
||||
speech_recognition/exceptions.py,sha256=LnKmutOVQ6RSMcSO1Ji5Af-UZN-Aqrq4petMgBFXkKs,273
|
||||
speech_recognition/flac-linux-x86,sha256=FOUk-MAmqO11z7GmT4TyHIJnRZaDXZaBVsh5yfP1k8g,1899154
|
||||
speech_recognition/flac-linux-x86_64,sha256=0k6-i9XM2vxk9SKmKuL-0_t0ARrf7ts9Peqmu0YR34Q,2396644
|
||||
speech_recognition/flac-mac,sha256=2LyQYpHz0QJGWm_dnNfJoIOYsDsPI8b-JwZAVns4o6E,451168
|
||||
speech_recognition/flac-win32.exe,sha256=D8yWtMDceZCrDDnb-8gvrtNmxYiPD0oz9GTqO3m38IU,738816
|
||||
speech_recognition/pocketsphinx-data/en-US/LICENSE.txt,sha256=_PXE5BroH3BO1w8VERRicu8PDKe5cIjRHXKJa1ahifU,1537
|
||||
speech_recognition/pocketsphinx-data/en-US/acoustic-model/README,sha256=i4jemAVoUJxkbQUnuEFL7vE2lkORkDtAmW0y9ze_dS4,1617
|
||||
speech_recognition/pocketsphinx-data/en-US/acoustic-model/feat.params,sha256=ioWtKGlsfyNDRE52M84w8WX-2HQPghlUVExvvUb0lR8,165
|
||||
speech_recognition/pocketsphinx-data/en-US/acoustic-model/mdef,sha256=I2D5qGiJwc_ui9YYoCaTh5EeX7KSCllPUGsYuMeWg7A,2959176
|
||||
speech_recognition/pocketsphinx-data/en-US/acoustic-model/means,sha256=gyAZ4yysEusxiWT5b0aQNKyxLQNI7t3DgxgxoQDLTdQ,838732
|
||||
speech_recognition/pocketsphinx-data/en-US/acoustic-model/noisedict,sha256=cpWwffLCBMT4fGeCtr4aOFnXAG1OOGQYHJVdbasQWjM,56
|
||||
speech_recognition/pocketsphinx-data/en-US/acoustic-model/sendump,sha256=jJVkwNW-9pyp2b8QFKvhYvBxZEzwLPH6ikg8PcFlp6g,1969024
|
||||
speech_recognition/pocketsphinx-data/en-US/acoustic-model/transition_matrices,sha256=wffyjqQxd75zS-H4i9fxuahT0OZg-FmcZ8bq7Ki7U5o,2080
|
||||
speech_recognition/pocketsphinx-data/en-US/acoustic-model/variances,sha256=sA1pb4XpaDT8EPjl8GQo2MTba__b5YRbb2m_bvvEj6U,838732
|
||||
speech_recognition/pocketsphinx-data/en-US/language-model.lm.bin,sha256=Ttj1LtBBMEXw6cZaRwmVOJa2_5qZ68EPfvkm9mLF5QY,29208442
|
||||
speech_recognition/pocketsphinx-data/en-US/pronounciation-dictionary.dict,sha256=zjv6Nk45C35Ip0g9NQpI2fFhEn9oQoucb5ZygR_BE08,3240807
|
||||
speech_recognition/recognizers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
speech_recognition/recognizers/__pycache__/__init__.cpython-311.pyc,,
|
||||
speech_recognition/recognizers/__pycache__/whisper.cpython-311.pyc,,
|
||||
speech_recognition/recognizers/whisper.py,sha256=ZSwBPcet4Lnyu6Qwa42hJZuNRhgSBN37R9tv6tnH3EA,1492
|
||||
@@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.38.4)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
speech_recognition
|
||||
@@ -0,0 +1,98 @@
|
||||
James Robert
|
||||
github: jiaaro
|
||||
twitter: @jiaaro
|
||||
web: jiaaro.com
|
||||
email: pydub@jiaaro.com
|
||||
|
||||
Marc Webbie
|
||||
github: marcwebbie
|
||||
|
||||
Jean-philippe Serafin
|
||||
github: jeanphix
|
||||
|
||||
Anurag Ramdasan
|
||||
github: AnuragRamdasan
|
||||
|
||||
Choongmin Lee
|
||||
github: clee704
|
||||
|
||||
Patrick Pittman
|
||||
github: ptpittman
|
||||
|
||||
Hunter Lang
|
||||
github: hunterlang
|
||||
|
||||
Alexey
|
||||
github: nihisil
|
||||
|
||||
Jaymz Campbell
|
||||
github: jaymzcd
|
||||
|
||||
Ross McFarland
|
||||
github: ross
|
||||
|
||||
John McMellen
|
||||
github: jmcmellen
|
||||
|
||||
Johan Lövgren
|
||||
github: dashj
|
||||
|
||||
Joachim Krüger
|
||||
github: jkrgr
|
||||
|
||||
Shichao An
|
||||
github: shichao-an
|
||||
|
||||
Michael Bortnyck
|
||||
github: mbortnyck
|
||||
|
||||
André Cloete
|
||||
github: aj-cloete
|
||||
|
||||
David Acacio
|
||||
github: dacacioa
|
||||
|
||||
Thiago Abdnur
|
||||
github: bolaum
|
||||
|
||||
Aurélien Ooms
|
||||
github: aureooms
|
||||
|
||||
Mike Mattozzi
|
||||
github: mmattozzi
|
||||
|
||||
Marcio Mazza
|
||||
github: marciomazza
|
||||
|
||||
Sungsu Lim
|
||||
github: proflim
|
||||
|
||||
Evandro Myller
|
||||
github: emyller
|
||||
|
||||
Sérgio Agostinho
|
||||
github: SergioRAgostinho
|
||||
|
||||
Antonio Larrosa
|
||||
github: antlarr
|
||||
|
||||
Aaron Craig
|
||||
github: craigthelinguist
|
||||
|
||||
Carlos del Castillo
|
||||
github: greyalien502
|
||||
|
||||
Yudong Sun
|
||||
github: sunjerry019
|
||||
|
||||
Jorge Perianez
|
||||
github: JPery
|
||||
|
||||
Chendi Luo
|
||||
github: Creonalia
|
||||
|
||||
Daniel Lefevre
|
||||
gitHub: dplefevre
|
||||
|
||||
Grzegorz Kotfis
|
||||
github: gkotfis
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2011 James Robert, http://jiaaro.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,37 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: pydub
|
||||
Version: 0.25.1
|
||||
Summary: Manipulate audio with an simple and easy high level interface
|
||||
Home-page: http://pydub.com
|
||||
Author: James Robert
|
||||
Author-email: jiaaro@gmail.com
|
||||
License: MIT
|
||||
Keywords: audio sound high-level
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Topic :: Multimedia :: Sound/Audio
|
||||
Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
|
||||
Classifier: Topic :: Multimedia :: Sound/Audio :: Conversion
|
||||
Classifier: Topic :: Multimedia :: Sound/Audio :: Editors
|
||||
Classifier: Topic :: Multimedia :: Sound/Audio :: Mixers
|
||||
Classifier: Topic :: Software Development :: Libraries
|
||||
Classifier: Topic :: Utilities
|
||||
|
||||
|
||||
Manipulate audio with an simple and easy high level interface.
|
||||
|
||||
See the README file for details, usage info, and a list of gotchas.
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
pydub-0.25.1.dist-info/AUTHORS,sha256=AyY2PS9I2enOyBnUnxcpeAX-NnMNWLQT4yDtg8IIy78,1250
|
||||
pydub-0.25.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
pydub-0.25.1.dist-info/LICENSE,sha256=roVlNiJMx6OJ6Wh3H8XyWYFL3Q2mNTnPcigq2672iXo,1074
|
||||
pydub-0.25.1.dist-info/METADATA,sha256=f0M8_ZVtbiYoUI9ejXIeJ03Jo9A5Nbi-0V1bVqs5iYk,1406
|
||||
pydub-0.25.1.dist-info/RECORD,,
|
||||
pydub-0.25.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
pydub-0.25.1.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110
|
||||
pydub-0.25.1.dist-info/top_level.txt,sha256=PHhiDCQVZdycZxfKL2lQozruBT6ZhvyZAwqjRrw3t0w,6
|
||||
pydub/__init__.py,sha256=w1Xv1awbaR3fMhTNE1-grnfswgARTNQrKpBzfZ--VBA,39
|
||||
pydub/__pycache__/__init__.cpython-311.pyc,,
|
||||
pydub/__pycache__/audio_segment.cpython-311.pyc,,
|
||||
pydub/__pycache__/effects.cpython-311.pyc,,
|
||||
pydub/__pycache__/exceptions.cpython-311.pyc,,
|
||||
pydub/__pycache__/generators.cpython-311.pyc,,
|
||||
pydub/__pycache__/logging_utils.cpython-311.pyc,,
|
||||
pydub/__pycache__/playback.cpython-311.pyc,,
|
||||
pydub/__pycache__/pyaudioop.cpython-311.pyc,,
|
||||
pydub/__pycache__/scipy_effects.cpython-311.pyc,,
|
||||
pydub/__pycache__/silence.cpython-311.pyc,,
|
||||
pydub/__pycache__/utils.cpython-311.pyc,,
|
||||
pydub/audio_segment.py,sha256=Nf5VkHGY1v9Jqb7NtEYfwRpLrfqusfBdPGOZsi7R5Cg,49185
|
||||
pydub/effects.py,sha256=1HUMzhefrwG_E1rTnzvbl-P0-KNuwHklCnu8QCGS7jA,11507
|
||||
pydub/exceptions.py,sha256=osgXoUujwpH8K6hr80iYpW30CMBDFwqyaRD-5d7ZpKs,455
|
||||
pydub/generators.py,sha256=u6q7J8JLOY-uEZqMPUTzakxyua3XNQcPiDsuiK2-lLA,4045
|
||||
pydub/logging_utils.py,sha256=WuSqfzn4zyT7PxXHGV-PXMDynufeM6sC6eSmVlGX2RU,374
|
||||
pydub/playback.py,sha256=zFngVclUL_7oDipjzKC8b7jToPNV11DV28rGyH8pio0,1987
|
||||
pydub/pyaudioop.py,sha256=Dp_cQgAyYjD4OV2ZHuxtKI2KABuPi9YYNRUF8giR80Q,13094
|
||||
pydub/scipy_effects.py,sha256=U2p8AQuVreTp5MrtUAzRbWgOHUc6Dwq0TAG_RtEg-7g,6637
|
||||
pydub/silence.py,sha256=F6MV0VlaO6mtuisjLGks_UR-GVmzO1v87_NKvzwRc30,6457
|
||||
pydub/utils.py,sha256=W71pgJFbbNP3adH63yn0Eo0CLLVgzXG7WHYSXpWvdyc,12368
|
||||
@@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.36.2)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pydub
|
||||
@@ -0,0 +1 @@
|
||||
from .audio_segment import AudioSegment
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,341 @@
|
||||
import sys
|
||||
import math
|
||||
import array
|
||||
from .utils import (
|
||||
db_to_float,
|
||||
ratio_to_db,
|
||||
register_pydub_effect,
|
||||
make_chunks,
|
||||
audioop,
|
||||
get_min_max_value
|
||||
)
|
||||
from .silence import split_on_silence
|
||||
from .exceptions import TooManyMissingFrames, InvalidDuration
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
xrange = range
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def apply_mono_filter_to_each_channel(seg, filter_fn):
|
||||
n_channels = seg.channels
|
||||
|
||||
channel_segs = seg.split_to_mono()
|
||||
channel_segs = [filter_fn(channel_seg) for channel_seg in channel_segs]
|
||||
|
||||
out_data = seg.get_array_of_samples()
|
||||
for channel_i, channel_seg in enumerate(channel_segs):
|
||||
for sample_i, sample in enumerate(channel_seg.get_array_of_samples()):
|
||||
index = (sample_i * n_channels) + channel_i
|
||||
out_data[index] = sample
|
||||
|
||||
return seg._spawn(out_data)
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def normalize(seg, headroom=0.1):
|
||||
"""
|
||||
headroom is how close to the maximum volume to boost the signal up to (specified in dB)
|
||||
"""
|
||||
peak_sample_val = seg.max
|
||||
|
||||
# if the max is 0, this audio segment is silent, and can't be normalized
|
||||
if peak_sample_val == 0:
|
||||
return seg
|
||||
|
||||
target_peak = seg.max_possible_amplitude * db_to_float(-headroom)
|
||||
|
||||
needed_boost = ratio_to_db(target_peak / peak_sample_val)
|
||||
return seg.apply_gain(needed_boost)
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def speedup(seg, playback_speed=1.5, chunk_size=150, crossfade=25):
|
||||
# we will keep audio in 150ms chunks since one waveform at 20Hz is 50ms long
|
||||
# (20 Hz is the lowest frequency audible to humans)
|
||||
|
||||
# portion of AUDIO TO KEEP. if playback speed is 1.25 we keep 80% (0.8) and
|
||||
# discard 20% (0.2)
|
||||
atk = 1.0 / playback_speed
|
||||
|
||||
if playback_speed < 2.0:
|
||||
# throwing out more than half the audio - keep 50ms chunks
|
||||
ms_to_remove_per_chunk = int(chunk_size * (1 - atk) / atk)
|
||||
else:
|
||||
# throwing out less than half the audio - throw out 50ms chunks
|
||||
ms_to_remove_per_chunk = int(chunk_size)
|
||||
chunk_size = int(atk * chunk_size / (1 - atk))
|
||||
|
||||
# the crossfade cannot be longer than the amount of audio we're removing
|
||||
crossfade = min(crossfade, ms_to_remove_per_chunk - 1)
|
||||
|
||||
# DEBUG
|
||||
#print("chunk: {0}, rm: {1}".format(chunk_size, ms_to_remove_per_chunk))
|
||||
|
||||
chunks = make_chunks(seg, chunk_size + ms_to_remove_per_chunk)
|
||||
if len(chunks) < 2:
|
||||
raise Exception("Could not speed up AudioSegment, it was too short {2:0.2f}s for the current settings:\n{0}ms chunks at {1:0.1f}x speedup".format(
|
||||
chunk_size, playback_speed, seg.duration_seconds))
|
||||
|
||||
# we'll actually truncate a bit less than we calculated to make up for the
|
||||
# crossfade between chunks
|
||||
ms_to_remove_per_chunk -= crossfade
|
||||
|
||||
# we don't want to truncate the last chunk since it is not guaranteed to be
|
||||
# the full chunk length
|
||||
last_chunk = chunks[-1]
|
||||
chunks = [chunk[:-ms_to_remove_per_chunk] for chunk in chunks[:-1]]
|
||||
|
||||
out = chunks[0]
|
||||
for chunk in chunks[1:]:
|
||||
out = out.append(chunk, crossfade=crossfade)
|
||||
|
||||
out += last_chunk
|
||||
return out
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def strip_silence(seg, silence_len=1000, silence_thresh=-16, padding=100):
|
||||
if padding > silence_len:
|
||||
raise InvalidDuration("padding cannot be longer than silence_len")
|
||||
|
||||
chunks = split_on_silence(seg, silence_len, silence_thresh, padding)
|
||||
crossfade = padding / 2
|
||||
|
||||
if not len(chunks):
|
||||
return seg[0:0]
|
||||
|
||||
seg = chunks[0]
|
||||
for chunk in chunks[1:]:
|
||||
seg = seg.append(chunk, crossfade=crossfade)
|
||||
|
||||
return seg
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def compress_dynamic_range(seg, threshold=-20.0, ratio=4.0, attack=5.0, release=50.0):
|
||||
"""
|
||||
Keyword Arguments:
|
||||
|
||||
threshold - default: -20.0
|
||||
Threshold in dBFS. default of -20.0 means -20dB relative to the
|
||||
maximum possible volume. 0dBFS is the maximum possible value so
|
||||
all values for this argument sould be negative.
|
||||
|
||||
ratio - default: 4.0
|
||||
Compression ratio. Audio louder than the threshold will be
|
||||
reduced to 1/ratio the volume. A ratio of 4.0 is equivalent to
|
||||
a setting of 4:1 in a pro-audio compressor like the Waves C1.
|
||||
|
||||
attack - default: 5.0
|
||||
Attack in milliseconds. How long it should take for the compressor
|
||||
to kick in once the audio has exceeded the threshold.
|
||||
|
||||
release - default: 50.0
|
||||
Release in milliseconds. How long it should take for the compressor
|
||||
to stop compressing after the audio has falled below the threshold.
|
||||
|
||||
|
||||
For an overview of Dynamic Range Compression, and more detailed explanation
|
||||
of the related terminology, see:
|
||||
|
||||
http://en.wikipedia.org/wiki/Dynamic_range_compression
|
||||
"""
|
||||
|
||||
thresh_rms = seg.max_possible_amplitude * db_to_float(threshold)
|
||||
|
||||
look_frames = int(seg.frame_count(ms=attack))
|
||||
def rms_at(frame_i):
|
||||
return seg.get_sample_slice(frame_i - look_frames, frame_i).rms
|
||||
def db_over_threshold(rms):
|
||||
if rms == 0: return 0.0
|
||||
db = ratio_to_db(rms / thresh_rms)
|
||||
return max(db, 0)
|
||||
|
||||
output = []
|
||||
|
||||
# amount to reduce the volume of the audio by (in dB)
|
||||
attenuation = 0.0
|
||||
|
||||
attack_frames = seg.frame_count(ms=attack)
|
||||
release_frames = seg.frame_count(ms=release)
|
||||
for i in xrange(int(seg.frame_count())):
|
||||
rms_now = rms_at(i)
|
||||
|
||||
# with a ratio of 4.0 this means the volume will exceed the threshold by
|
||||
# 1/4 the amount (of dB) that it would otherwise
|
||||
max_attenuation = (1 - (1.0 / ratio)) * db_over_threshold(rms_now)
|
||||
|
||||
attenuation_inc = max_attenuation / attack_frames
|
||||
attenuation_dec = max_attenuation / release_frames
|
||||
|
||||
if rms_now > thresh_rms and attenuation <= max_attenuation:
|
||||
attenuation += attenuation_inc
|
||||
attenuation = min(attenuation, max_attenuation)
|
||||
else:
|
||||
attenuation -= attenuation_dec
|
||||
attenuation = max(attenuation, 0)
|
||||
|
||||
frame = seg.get_frame(i)
|
||||
if attenuation != 0.0:
|
||||
frame = audioop.mul(frame,
|
||||
seg.sample_width,
|
||||
db_to_float(-attenuation))
|
||||
|
||||
output.append(frame)
|
||||
|
||||
return seg._spawn(data=b''.join(output))
|
||||
|
||||
|
||||
# Invert the phase of the signal.
|
||||
|
||||
@register_pydub_effect
|
||||
|
||||
def invert_phase(seg, channels=(1, 1)):
|
||||
"""
|
||||
channels- specifies which channel (left or right) to reverse the phase of.
|
||||
Note that mono AudioSegments will become stereo.
|
||||
"""
|
||||
if channels == (1, 1):
|
||||
inverted = audioop.mul(seg._data, seg.sample_width, -1.0)
|
||||
return seg._spawn(data=inverted)
|
||||
|
||||
else:
|
||||
if seg.channels == 2:
|
||||
left, right = seg.split_to_mono()
|
||||
else:
|
||||
raise Exception("Can't implicitly convert an AudioSegment with " + str(seg.channels) + " channels to stereo.")
|
||||
|
||||
if channels == (1, 0):
|
||||
left = left.invert_phase()
|
||||
else:
|
||||
right = right.invert_phase()
|
||||
|
||||
return seg.from_mono_audiosegments(left, right)
|
||||
|
||||
|
||||
|
||||
# High and low pass filters based on implementation found on Stack Overflow:
|
||||
# http://stackoverflow.com/questions/13882038/implementing-simple-high-and-low-pass-filters-in-c
|
||||
|
||||
@register_pydub_effect
|
||||
def low_pass_filter(seg, cutoff):
|
||||
"""
|
||||
cutoff - Frequency (in Hz) where higher frequency signal will begin to
|
||||
be reduced by 6dB per octave (doubling in frequency) above this point
|
||||
"""
|
||||
RC = 1.0 / (cutoff * 2 * math.pi)
|
||||
dt = 1.0 / seg.frame_rate
|
||||
|
||||
alpha = dt / (RC + dt)
|
||||
|
||||
original = seg.get_array_of_samples()
|
||||
filteredArray = array.array(seg.array_type, original)
|
||||
|
||||
frame_count = int(seg.frame_count())
|
||||
|
||||
last_val = [0] * seg.channels
|
||||
for i in range(seg.channels):
|
||||
last_val[i] = filteredArray[i] = original[i]
|
||||
|
||||
for i in range(1, frame_count):
|
||||
for j in range(seg.channels):
|
||||
offset = (i * seg.channels) + j
|
||||
last_val[j] = last_val[j] + (alpha * (original[offset] - last_val[j]))
|
||||
filteredArray[offset] = int(last_val[j])
|
||||
|
||||
return seg._spawn(data=filteredArray)
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def high_pass_filter(seg, cutoff):
|
||||
"""
|
||||
cutoff - Frequency (in Hz) where lower frequency signal will begin to
|
||||
be reduced by 6dB per octave (doubling in frequency) below this point
|
||||
"""
|
||||
RC = 1.0 / (cutoff * 2 * math.pi)
|
||||
dt = 1.0 / seg.frame_rate
|
||||
|
||||
alpha = RC / (RC + dt)
|
||||
|
||||
minval, maxval = get_min_max_value(seg.sample_width * 8)
|
||||
|
||||
original = seg.get_array_of_samples()
|
||||
filteredArray = array.array(seg.array_type, original)
|
||||
|
||||
frame_count = int(seg.frame_count())
|
||||
|
||||
last_val = [0] * seg.channels
|
||||
for i in range(seg.channels):
|
||||
last_val[i] = filteredArray[i] = original[i]
|
||||
|
||||
for i in range(1, frame_count):
|
||||
for j in range(seg.channels):
|
||||
offset = (i * seg.channels) + j
|
||||
offset_minus_1 = ((i-1) * seg.channels) + j
|
||||
|
||||
last_val[j] = alpha * (last_val[j] + original[offset] - original[offset_minus_1])
|
||||
filteredArray[offset] = int(min(max(last_val[j], minval), maxval))
|
||||
|
||||
return seg._spawn(data=filteredArray)
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def pan(seg, pan_amount):
|
||||
"""
|
||||
pan_amount should be between -1.0 (100% left) and +1.0 (100% right)
|
||||
|
||||
When pan_amount == 0.0 the left/right balance is not changed.
|
||||
|
||||
Panning does not alter the *perceived* loundness, but since loudness
|
||||
is decreasing on one side, the other side needs to get louder to
|
||||
compensate. When panned hard left, the left channel will be 3dB louder.
|
||||
"""
|
||||
if not -1.0 <= pan_amount <= 1.0:
|
||||
raise ValueError("pan_amount should be between -1.0 (100% left) and +1.0 (100% right)")
|
||||
|
||||
max_boost_db = ratio_to_db(2.0)
|
||||
boost_db = abs(pan_amount) * max_boost_db
|
||||
|
||||
boost_factor = db_to_float(boost_db)
|
||||
reduce_factor = db_to_float(max_boost_db) - boost_factor
|
||||
|
||||
reduce_db = ratio_to_db(reduce_factor)
|
||||
|
||||
# Cut boost in half (max boost== 3dB) - in reality 2 speakers
|
||||
# do not sum to a full 6 dB.
|
||||
boost_db = boost_db / 2.0
|
||||
|
||||
if pan_amount < 0:
|
||||
return seg.apply_gain_stereo(boost_db, reduce_db)
|
||||
else:
|
||||
return seg.apply_gain_stereo(reduce_db, boost_db)
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def apply_gain_stereo(seg, left_gain=0.0, right_gain=0.0):
|
||||
"""
|
||||
left_gain - amount of gain to apply to the left channel (in dB)
|
||||
right_gain - amount of gain to apply to the right channel (in dB)
|
||||
|
||||
note: mono audio segments will be converted to stereo
|
||||
"""
|
||||
if seg.channels == 1:
|
||||
left = right = seg
|
||||
elif seg.channels == 2:
|
||||
left, right = seg.split_to_mono()
|
||||
|
||||
l_mult_factor = db_to_float(left_gain)
|
||||
r_mult_factor = db_to_float(right_gain)
|
||||
|
||||
left_data = audioop.mul(left._data, left.sample_width, l_mult_factor)
|
||||
left_data = audioop.tostereo(left_data, left.sample_width, 1, 0)
|
||||
|
||||
right_data = audioop.mul(right._data, right.sample_width, r_mult_factor)
|
||||
right_data = audioop.tostereo(right_data, right.sample_width, 0, 1)
|
||||
|
||||
output = audioop.add(left_data, right_data, seg.sample_width)
|
||||
|
||||
return seg._spawn(data=output,
|
||||
overrides={'channels': 2,
|
||||
'frame_width': 2 * seg.sample_width})
|
||||
@@ -0,0 +1,32 @@
|
||||
class PydubException(Exception):
|
||||
"""
|
||||
Base class for any Pydub exception
|
||||
"""
|
||||
|
||||
|
||||
class TooManyMissingFrames(PydubException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidDuration(PydubException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidTag(PydubException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidID3TagVersion(PydubException):
|
||||
pass
|
||||
|
||||
|
||||
class CouldntDecodeError(PydubException):
|
||||
pass
|
||||
|
||||
|
||||
class CouldntEncodeError(PydubException):
|
||||
pass
|
||||
|
||||
|
||||
class MissingAudioParameter(PydubException):
|
||||
pass
|
||||
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
Each generator will return float samples from -1.0 to 1.0, which can be
|
||||
converted to actual audio with 8, 16, 24, or 32 bit depth using the
|
||||
SiganlGenerator.to_audio_segment() method (on any of it's subclasses).
|
||||
|
||||
See Wikipedia's "waveform" page for info on some of the generators included
|
||||
here: http://en.wikipedia.org/wiki/Waveform
|
||||
"""
|
||||
|
||||
import math
|
||||
import array
|
||||
import itertools
|
||||
import random
|
||||
from .audio_segment import AudioSegment
|
||||
from .utils import (
|
||||
db_to_float,
|
||||
get_frame_width,
|
||||
get_array_type,
|
||||
get_min_max_value
|
||||
)
|
||||
|
||||
|
||||
|
||||
class SignalGenerator(object):
|
||||
def __init__(self, sample_rate=44100, bit_depth=16):
|
||||
self.sample_rate = sample_rate
|
||||
self.bit_depth = bit_depth
|
||||
|
||||
def to_audio_segment(self, duration=1000.0, volume=0.0):
|
||||
"""
|
||||
Duration in milliseconds
|
||||
(default: 1 second)
|
||||
Volume in DB relative to maximum amplitude
|
||||
(default 0.0 dBFS, which is the maximum value)
|
||||
"""
|
||||
minval, maxval = get_min_max_value(self.bit_depth)
|
||||
sample_width = get_frame_width(self.bit_depth)
|
||||
array_type = get_array_type(self.bit_depth)
|
||||
|
||||
gain = db_to_float(volume)
|
||||
sample_count = int(self.sample_rate * (duration / 1000.0))
|
||||
|
||||
sample_data = (int(val * maxval * gain) for val in self.generate())
|
||||
sample_data = itertools.islice(sample_data, 0, sample_count)
|
||||
|
||||
data = array.array(array_type, sample_data)
|
||||
|
||||
try:
|
||||
data = data.tobytes()
|
||||
except:
|
||||
data = data.tostring()
|
||||
|
||||
return AudioSegment(data=data, metadata={
|
||||
"channels": 1,
|
||||
"sample_width": sample_width,
|
||||
"frame_rate": self.sample_rate,
|
||||
"frame_width": sample_width,
|
||||
})
|
||||
|
||||
def generate(self):
|
||||
raise NotImplementedError("SignalGenerator subclasses must implement the generate() method, and *should not* call the superclass implementation.")
|
||||
|
||||
|
||||
|
||||
class Sine(SignalGenerator):
|
||||
def __init__(self, freq, **kwargs):
|
||||
super(Sine, self).__init__(**kwargs)
|
||||
self.freq = freq
|
||||
|
||||
def generate(self):
|
||||
sine_of = (self.freq * 2 * math.pi) / self.sample_rate
|
||||
sample_n = 0
|
||||
while True:
|
||||
yield math.sin(sine_of * sample_n)
|
||||
sample_n += 1
|
||||
|
||||
|
||||
|
||||
class Pulse(SignalGenerator):
|
||||
def __init__(self, freq, duty_cycle=0.5, **kwargs):
|
||||
super(Pulse, self).__init__(**kwargs)
|
||||
self.freq = freq
|
||||
self.duty_cycle = duty_cycle
|
||||
|
||||
def generate(self):
|
||||
sample_n = 0
|
||||
|
||||
# in samples
|
||||
cycle_length = self.sample_rate / float(self.freq)
|
||||
pulse_length = cycle_length * self.duty_cycle
|
||||
|
||||
while True:
|
||||
if (sample_n % cycle_length) < pulse_length:
|
||||
yield 1.0
|
||||
else:
|
||||
yield -1.0
|
||||
sample_n += 1
|
||||
|
||||
|
||||
|
||||
class Square(Pulse):
|
||||
def __init__(self, freq, **kwargs):
|
||||
kwargs['duty_cycle'] = 0.5
|
||||
super(Square, self).__init__(freq, **kwargs)
|
||||
|
||||
|
||||
|
||||
class Sawtooth(SignalGenerator):
|
||||
def __init__(self, freq, duty_cycle=1.0, **kwargs):
|
||||
super(Sawtooth, self).__init__(**kwargs)
|
||||
self.freq = freq
|
||||
self.duty_cycle = duty_cycle
|
||||
|
||||
def generate(self):
|
||||
sample_n = 0
|
||||
|
||||
# in samples
|
||||
cycle_length = self.sample_rate / float(self.freq)
|
||||
midpoint = cycle_length * self.duty_cycle
|
||||
ascend_length = midpoint
|
||||
descend_length = cycle_length - ascend_length
|
||||
|
||||
while True:
|
||||
cycle_position = sample_n % cycle_length
|
||||
if cycle_position < midpoint:
|
||||
yield (2 * cycle_position / ascend_length) - 1.0
|
||||
else:
|
||||
yield 1.0 - (2 * (cycle_position - midpoint) / descend_length)
|
||||
sample_n += 1
|
||||
|
||||
|
||||
|
||||
class Triangle(Sawtooth):
|
||||
def __init__(self, freq, **kwargs):
|
||||
kwargs['duty_cycle'] = 0.5
|
||||
super(Triangle, self).__init__(freq, **kwargs)
|
||||
|
||||
|
||||
class WhiteNoise(SignalGenerator):
|
||||
def generate(self):
|
||||
while True:
|
||||
yield (random.random() * 2) - 1.0
|
||||
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
|
||||
"""
|
||||
import logging
|
||||
|
||||
converter_logger = logging.getLogger("pydub.converter")
|
||||
|
||||
def log_conversion(conversion_command):
|
||||
converter_logger.debug("subprocess.call(%s)", repr(conversion_command))
|
||||
|
||||
def log_subprocess_output(output):
|
||||
if output:
|
||||
for line in output.rstrip().splitlines():
|
||||
converter_logger.debug('subprocess output: %s', line.rstrip())
|
||||
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Support for playing AudioSegments. Pyaudio will be used if it's installed,
|
||||
otherwise will fallback to ffplay. Pyaudio is a *much* nicer solution, but
|
||||
is tricky to install. See my notes on installing pyaudio in a virtualenv (on
|
||||
OSX 10.10): https://gist.github.com/jiaaro/9767512210a1d80a8a0d
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
from tempfile import NamedTemporaryFile
|
||||
from .utils import get_player_name, make_chunks
|
||||
|
||||
def _play_with_ffplay(seg):
|
||||
PLAYER = get_player_name()
|
||||
with NamedTemporaryFile("w+b", suffix=".wav") as f:
|
||||
seg.export(f.name, "wav")
|
||||
subprocess.call([PLAYER, "-nodisp", "-autoexit", "-hide_banner", f.name])
|
||||
|
||||
|
||||
def _play_with_pyaudio(seg):
|
||||
import pyaudio
|
||||
|
||||
p = pyaudio.PyAudio()
|
||||
stream = p.open(format=p.get_format_from_width(seg.sample_width),
|
||||
channels=seg.channels,
|
||||
rate=seg.frame_rate,
|
||||
output=True)
|
||||
|
||||
# Just in case there were any exceptions/interrupts, we release the resource
|
||||
# So as not to raise OSError: Device Unavailable should play() be used again
|
||||
try:
|
||||
# break audio into half-second chunks (to allows keyboard interrupts)
|
||||
for chunk in make_chunks(seg, 500):
|
||||
stream.write(chunk._data)
|
||||
finally:
|
||||
stream.stop_stream()
|
||||
stream.close()
|
||||
|
||||
p.terminate()
|
||||
|
||||
|
||||
def _play_with_simpleaudio(seg):
|
||||
import simpleaudio
|
||||
return simpleaudio.play_buffer(
|
||||
seg.raw_data,
|
||||
num_channels=seg.channels,
|
||||
bytes_per_sample=seg.sample_width,
|
||||
sample_rate=seg.frame_rate
|
||||
)
|
||||
|
||||
|
||||
def play(audio_segment):
|
||||
try:
|
||||
playback = _play_with_simpleaudio(audio_segment)
|
||||
try:
|
||||
playback.wait_done()
|
||||
except KeyboardInterrupt:
|
||||
playback.stop()
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
||||
try:
|
||||
_play_with_pyaudio(audio_segment)
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
|
||||
_play_with_ffplay(audio_segment)
|
||||
@@ -0,0 +1,553 @@
|
||||
try:
|
||||
from __builtin__ import max as builtin_max
|
||||
from __builtin__ import min as builtin_min
|
||||
except ImportError:
|
||||
from builtins import max as builtin_max
|
||||
from builtins import min as builtin_min
|
||||
import math
|
||||
import struct
|
||||
try:
|
||||
from fractions import gcd
|
||||
except ImportError: # Python 3.9+
|
||||
from math import gcd
|
||||
from ctypes import create_string_buffer
|
||||
|
||||
|
||||
class error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _check_size(size):
|
||||
if size != 1 and size != 2 and size != 4:
|
||||
raise error("Size should be 1, 2 or 4")
|
||||
|
||||
|
||||
def _check_params(length, size):
|
||||
_check_size(size)
|
||||
if length % size != 0:
|
||||
raise error("not a whole number of frames")
|
||||
|
||||
|
||||
def _sample_count(cp, size):
|
||||
return len(cp) / size
|
||||
|
||||
|
||||
def _get_samples(cp, size, signed=True):
|
||||
for i in range(_sample_count(cp, size)):
|
||||
yield _get_sample(cp, size, i, signed)
|
||||
|
||||
|
||||
def _struct_format(size, signed):
|
||||
if size == 1:
|
||||
return "b" if signed else "B"
|
||||
elif size == 2:
|
||||
return "h" if signed else "H"
|
||||
elif size == 4:
|
||||
return "i" if signed else "I"
|
||||
|
||||
|
||||
def _get_sample(cp, size, i, signed=True):
|
||||
fmt = _struct_format(size, signed)
|
||||
start = i * size
|
||||
end = start + size
|
||||
return struct.unpack_from(fmt, buffer(cp)[start:end])[0]
|
||||
|
||||
|
||||
def _put_sample(cp, size, i, val, signed=True):
|
||||
fmt = _struct_format(size, signed)
|
||||
struct.pack_into(fmt, cp, i * size, val)
|
||||
|
||||
|
||||
def _get_maxval(size, signed=True):
|
||||
if signed and size == 1:
|
||||
return 0x7f
|
||||
elif size == 1:
|
||||
return 0xff
|
||||
elif signed and size == 2:
|
||||
return 0x7fff
|
||||
elif size == 2:
|
||||
return 0xffff
|
||||
elif signed and size == 4:
|
||||
return 0x7fffffff
|
||||
elif size == 4:
|
||||
return 0xffffffff
|
||||
|
||||
|
||||
def _get_minval(size, signed=True):
|
||||
if not signed:
|
||||
return 0
|
||||
elif size == 1:
|
||||
return -0x80
|
||||
elif size == 2:
|
||||
return -0x8000
|
||||
elif size == 4:
|
||||
return -0x80000000
|
||||
|
||||
|
||||
def _get_clipfn(size, signed=True):
|
||||
maxval = _get_maxval(size, signed)
|
||||
minval = _get_minval(size, signed)
|
||||
return lambda val: builtin_max(min(val, maxval), minval)
|
||||
|
||||
|
||||
def _overflow(val, size, signed=True):
|
||||
minval = _get_minval(size, signed)
|
||||
maxval = _get_maxval(size, signed)
|
||||
if minval <= val <= maxval:
|
||||
return val
|
||||
|
||||
bits = size * 8
|
||||
if signed:
|
||||
offset = 2**(bits-1)
|
||||
return ((val + offset) % (2**bits)) - offset
|
||||
else:
|
||||
return val % (2**bits)
|
||||
|
||||
|
||||
def getsample(cp, size, i):
|
||||
_check_params(len(cp), size)
|
||||
if not (0 <= i < len(cp) / size):
|
||||
raise error("Index out of range")
|
||||
return _get_sample(cp, size, i)
|
||||
|
||||
|
||||
def max(cp, size):
|
||||
_check_params(len(cp), size)
|
||||
|
||||
if len(cp) == 0:
|
||||
return 0
|
||||
|
||||
return builtin_max(abs(sample) for sample in _get_samples(cp, size))
|
||||
|
||||
|
||||
def minmax(cp, size):
|
||||
_check_params(len(cp), size)
|
||||
|
||||
max_sample, min_sample = 0, 0
|
||||
for sample in _get_samples(cp, size):
|
||||
max_sample = builtin_max(sample, max_sample)
|
||||
min_sample = builtin_min(sample, min_sample)
|
||||
|
||||
return min_sample, max_sample
|
||||
|
||||
|
||||
def avg(cp, size):
|
||||
_check_params(len(cp), size)
|
||||
sample_count = _sample_count(cp, size)
|
||||
if sample_count == 0:
|
||||
return 0
|
||||
return sum(_get_samples(cp, size)) / sample_count
|
||||
|
||||
|
||||
def rms(cp, size):
|
||||
_check_params(len(cp), size)
|
||||
|
||||
sample_count = _sample_count(cp, size)
|
||||
if sample_count == 0:
|
||||
return 0
|
||||
|
||||
sum_squares = sum(sample**2 for sample in _get_samples(cp, size))
|
||||
return int(math.sqrt(sum_squares / sample_count))
|
||||
|
||||
|
||||
def _sum2(cp1, cp2, length):
|
||||
size = 2
|
||||
total = 0
|
||||
for i in range(length):
|
||||
total += getsample(cp1, size, i) * getsample(cp2, size, i)
|
||||
return total
|
||||
|
||||
|
||||
def findfit(cp1, cp2):
|
||||
size = 2
|
||||
|
||||
if len(cp1) % 2 != 0 or len(cp2) % 2 != 0:
|
||||
raise error("Strings should be even-sized")
|
||||
|
||||
if len(cp1) < len(cp2):
|
||||
raise error("First sample should be longer")
|
||||
|
||||
len1 = _sample_count(cp1, size)
|
||||
len2 = _sample_count(cp2, size)
|
||||
|
||||
sum_ri_2 = _sum2(cp2, cp2, len2)
|
||||
sum_aij_2 = _sum2(cp1, cp1, len2)
|
||||
sum_aij_ri = _sum2(cp1, cp2, len2)
|
||||
|
||||
result = (sum_ri_2 * sum_aij_2 - sum_aij_ri * sum_aij_ri) / sum_aij_2
|
||||
|
||||
best_result = result
|
||||
best_i = 0
|
||||
|
||||
for i in range(1, len1 - len2 + 1):
|
||||
aj_m1 = _get_sample(cp1, size, i - 1)
|
||||
aj_lm1 = _get_sample(cp1, size, i + len2 - 1)
|
||||
|
||||
sum_aij_2 += aj_lm1**2 - aj_m1**2
|
||||
sum_aij_ri = _sum2(buffer(cp1)[i*size:], cp2, len2)
|
||||
|
||||
result = (sum_ri_2 * sum_aij_2 - sum_aij_ri * sum_aij_ri) / sum_aij_2
|
||||
|
||||
if result < best_result:
|
||||
best_result = result
|
||||
best_i = i
|
||||
|
||||
factor = _sum2(buffer(cp1)[best_i*size:], cp2, len2) / sum_ri_2
|
||||
|
||||
return best_i, factor
|
||||
|
||||
|
||||
def findfactor(cp1, cp2):
|
||||
size = 2
|
||||
|
||||
if len(cp1) % 2 != 0:
|
||||
raise error("Strings should be even-sized")
|
||||
|
||||
if len(cp1) != len(cp2):
|
||||
raise error("Samples should be same size")
|
||||
|
||||
sample_count = _sample_count(cp1, size)
|
||||
|
||||
sum_ri_2 = _sum2(cp2, cp2, sample_count)
|
||||
sum_aij_ri = _sum2(cp1, cp2, sample_count)
|
||||
|
||||
return sum_aij_ri / sum_ri_2
|
||||
|
||||
|
||||
def findmax(cp, len2):
|
||||
size = 2
|
||||
sample_count = _sample_count(cp, size)
|
||||
|
||||
if len(cp) % 2 != 0:
|
||||
raise error("Strings should be even-sized")
|
||||
|
||||
if len2 < 0 or sample_count < len2:
|
||||
raise error("Input sample should be longer")
|
||||
|
||||
if sample_count == 0:
|
||||
return 0
|
||||
|
||||
result = _sum2(cp, cp, len2)
|
||||
best_result = result
|
||||
best_i = 0
|
||||
|
||||
for i in range(1, sample_count - len2 + 1):
|
||||
sample_leaving_window = getsample(cp, size, i - 1)
|
||||
sample_entering_window = getsample(cp, size, i + len2 - 1)
|
||||
|
||||
result -= sample_leaving_window**2
|
||||
result += sample_entering_window**2
|
||||
|
||||
if result > best_result:
|
||||
best_result = result
|
||||
best_i = i
|
||||
|
||||
return best_i
|
||||
|
||||
|
||||
def avgpp(cp, size):
|
||||
_check_params(len(cp), size)
|
||||
sample_count = _sample_count(cp, size)
|
||||
|
||||
prevextremevalid = False
|
||||
prevextreme = None
|
||||
avg = 0
|
||||
nextreme = 0
|
||||
|
||||
prevval = getsample(cp, size, 0)
|
||||
val = getsample(cp, size, 1)
|
||||
|
||||
prevdiff = val - prevval
|
||||
|
||||
for i in range(1, sample_count):
|
||||
val = getsample(cp, size, i)
|
||||
diff = val - prevval
|
||||
|
||||
if diff * prevdiff < 0:
|
||||
if prevextremevalid:
|
||||
avg += abs(prevval - prevextreme)
|
||||
nextreme += 1
|
||||
|
||||
prevextremevalid = True
|
||||
prevextreme = prevval
|
||||
|
||||
prevval = val
|
||||
if diff != 0:
|
||||
prevdiff = diff
|
||||
|
||||
if nextreme == 0:
|
||||
return 0
|
||||
|
||||
return avg / nextreme
|
||||
|
||||
|
||||
def maxpp(cp, size):
|
||||
_check_params(len(cp), size)
|
||||
sample_count = _sample_count(cp, size)
|
||||
|
||||
prevextremevalid = False
|
||||
prevextreme = None
|
||||
max = 0
|
||||
|
||||
prevval = getsample(cp, size, 0)
|
||||
val = getsample(cp, size, 1)
|
||||
|
||||
prevdiff = val - prevval
|
||||
|
||||
for i in range(1, sample_count):
|
||||
val = getsample(cp, size, i)
|
||||
diff = val - prevval
|
||||
|
||||
if diff * prevdiff < 0:
|
||||
if prevextremevalid:
|
||||
extremediff = abs(prevval - prevextreme)
|
||||
if extremediff > max:
|
||||
max = extremediff
|
||||
prevextremevalid = True
|
||||
prevextreme = prevval
|
||||
|
||||
prevval = val
|
||||
if diff != 0:
|
||||
prevdiff = diff
|
||||
|
||||
return max
|
||||
|
||||
|
||||
def cross(cp, size):
|
||||
_check_params(len(cp), size)
|
||||
|
||||
crossings = 0
|
||||
last_sample = 0
|
||||
for sample in _get_samples(cp, size):
|
||||
if sample <= 0 < last_sample or sample >= 0 > last_sample:
|
||||
crossings += 1
|
||||
last_sample = sample
|
||||
|
||||
return crossings
|
||||
|
||||
|
||||
def mul(cp, size, factor):
|
||||
_check_params(len(cp), size)
|
||||
clip = _get_clipfn(size)
|
||||
|
||||
result = create_string_buffer(len(cp))
|
||||
|
||||
for i, sample in enumerate(_get_samples(cp, size)):
|
||||
sample = clip(int(sample * factor))
|
||||
_put_sample(result, size, i, sample)
|
||||
|
||||
return result.raw
|
||||
|
||||
|
||||
def tomono(cp, size, fac1, fac2):
|
||||
_check_params(len(cp), size)
|
||||
clip = _get_clipfn(size)
|
||||
|
||||
sample_count = _sample_count(cp, size)
|
||||
|
||||
result = create_string_buffer(len(cp) / 2)
|
||||
|
||||
for i in range(0, sample_count, 2):
|
||||
l_sample = getsample(cp, size, i)
|
||||
r_sample = getsample(cp, size, i + 1)
|
||||
|
||||
sample = (l_sample * fac1) + (r_sample * fac2)
|
||||
sample = clip(sample)
|
||||
|
||||
_put_sample(result, size, i / 2, sample)
|
||||
|
||||
return result.raw
|
||||
|
||||
|
||||
def tostereo(cp, size, fac1, fac2):
|
||||
_check_params(len(cp), size)
|
||||
|
||||
sample_count = _sample_count(cp, size)
|
||||
|
||||
result = create_string_buffer(len(cp) * 2)
|
||||
clip = _get_clipfn(size)
|
||||
|
||||
for i in range(sample_count):
|
||||
sample = _get_sample(cp, size, i)
|
||||
|
||||
l_sample = clip(sample * fac1)
|
||||
r_sample = clip(sample * fac2)
|
||||
|
||||
_put_sample(result, size, i * 2, l_sample)
|
||||
_put_sample(result, size, i * 2 + 1, r_sample)
|
||||
|
||||
return result.raw
|
||||
|
||||
|
||||
def add(cp1, cp2, size):
|
||||
_check_params(len(cp1), size)
|
||||
|
||||
if len(cp1) != len(cp2):
|
||||
raise error("Lengths should be the same")
|
||||
|
||||
clip = _get_clipfn(size)
|
||||
sample_count = _sample_count(cp1, size)
|
||||
result = create_string_buffer(len(cp1))
|
||||
|
||||
for i in range(sample_count):
|
||||
sample1 = getsample(cp1, size, i)
|
||||
sample2 = getsample(cp2, size, i)
|
||||
|
||||
sample = clip(sample1 + sample2)
|
||||
|
||||
_put_sample(result, size, i, sample)
|
||||
|
||||
return result.raw
|
||||
|
||||
|
||||
def bias(cp, size, bias):
|
||||
_check_params(len(cp), size)
|
||||
|
||||
result = create_string_buffer(len(cp))
|
||||
|
||||
for i, sample in enumerate(_get_samples(cp, size)):
|
||||
sample = _overflow(sample + bias, size)
|
||||
_put_sample(result, size, i, sample)
|
||||
|
||||
return result.raw
|
||||
|
||||
|
||||
def reverse(cp, size):
|
||||
_check_params(len(cp), size)
|
||||
sample_count = _sample_count(cp, size)
|
||||
|
||||
result = create_string_buffer(len(cp))
|
||||
for i, sample in enumerate(_get_samples(cp, size)):
|
||||
_put_sample(result, size, sample_count - i - 1, sample)
|
||||
|
||||
return result.raw
|
||||
|
||||
|
||||
def lin2lin(cp, size, size2):
|
||||
_check_params(len(cp), size)
|
||||
_check_size(size2)
|
||||
|
||||
if size == size2:
|
||||
return cp
|
||||
|
||||
new_len = (len(cp) / size) * size2
|
||||
|
||||
result = create_string_buffer(new_len)
|
||||
|
||||
for i in range(_sample_count(cp, size)):
|
||||
sample = _get_sample(cp, size, i)
|
||||
if size < size2:
|
||||
sample = sample << (4 * size2 / size)
|
||||
elif size > size2:
|
||||
sample = sample >> (4 * size / size2)
|
||||
|
||||
sample = _overflow(sample, size2)
|
||||
|
||||
_put_sample(result, size2, i, sample)
|
||||
|
||||
return result.raw
|
||||
|
||||
|
||||
def ratecv(cp, size, nchannels, inrate, outrate, state, weightA=1, weightB=0):
|
||||
_check_params(len(cp), size)
|
||||
if nchannels < 1:
|
||||
raise error("# of channels should be >= 1")
|
||||
|
||||
bytes_per_frame = size * nchannels
|
||||
frame_count = len(cp) / bytes_per_frame
|
||||
|
||||
if bytes_per_frame / nchannels != size:
|
||||
raise OverflowError("width * nchannels too big for a C int")
|
||||
|
||||
if weightA < 1 or weightB < 0:
|
||||
raise error("weightA should be >= 1, weightB should be >= 0")
|
||||
|
||||
if len(cp) % bytes_per_frame != 0:
|
||||
raise error("not a whole number of frames")
|
||||
|
||||
if inrate <= 0 or outrate <= 0:
|
||||
raise error("sampling rate not > 0")
|
||||
|
||||
d = gcd(inrate, outrate)
|
||||
inrate /= d
|
||||
outrate /= d
|
||||
|
||||
prev_i = [0] * nchannels
|
||||
cur_i = [0] * nchannels
|
||||
|
||||
if state is None:
|
||||
d = -outrate
|
||||
else:
|
||||
d, samps = state
|
||||
|
||||
if len(samps) != nchannels:
|
||||
raise error("illegal state argument")
|
||||
|
||||
prev_i, cur_i = zip(*samps)
|
||||
prev_i, cur_i = list(prev_i), list(cur_i)
|
||||
|
||||
q = frame_count / inrate
|
||||
ceiling = (q + 1) * outrate
|
||||
nbytes = ceiling * bytes_per_frame
|
||||
|
||||
result = create_string_buffer(nbytes)
|
||||
|
||||
samples = _get_samples(cp, size)
|
||||
out_i = 0
|
||||
while True:
|
||||
while d < 0:
|
||||
if frame_count == 0:
|
||||
samps = zip(prev_i, cur_i)
|
||||
retval = result.raw
|
||||
|
||||
# slice off extra bytes
|
||||
trim_index = (out_i * bytes_per_frame) - len(retval)
|
||||
retval = buffer(retval)[:trim_index]
|
||||
|
||||
return (retval, (d, tuple(samps)))
|
||||
|
||||
for chan in range(nchannels):
|
||||
prev_i[chan] = cur_i[chan]
|
||||
cur_i[chan] = samples.next()
|
||||
|
||||
cur_i[chan] = (
|
||||
(weightA * cur_i[chan] + weightB * prev_i[chan])
|
||||
/ (weightA + weightB)
|
||||
)
|
||||
|
||||
frame_count -= 1
|
||||
d += outrate
|
||||
|
||||
while d >= 0:
|
||||
for chan in range(nchannels):
|
||||
cur_o = (
|
||||
(prev_i[chan] * d + cur_i[chan] * (outrate - d))
|
||||
/ outrate
|
||||
)
|
||||
_put_sample(result, size, out_i, _overflow(cur_o, size))
|
||||
out_i += 1
|
||||
d -= inrate
|
||||
|
||||
|
||||
def lin2ulaw(cp, size):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def ulaw2lin(cp, size):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def lin2alaw(cp, size):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def alaw2lin(cp, size):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def lin2adpcm(cp, size, state):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def adpcm2lin(cp, size, state):
|
||||
raise NotImplementedError()
|
||||
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
This module provides scipy versions of high_pass_filter, and low_pass_filter
|
||||
as well as an additional band_pass_filter.
|
||||
|
||||
Of course, you will need to install scipy for these to work.
|
||||
|
||||
When this module is imported the high and low pass filters from this module
|
||||
will be used when calling audio_segment.high_pass_filter() and
|
||||
audio_segment.high_pass_filter() instead of the slower, less powerful versions
|
||||
provided by pydub.effects.
|
||||
"""
|
||||
from scipy.signal import butter, sosfilt
|
||||
from .utils import (register_pydub_effect,stereo_to_ms,ms_to_stereo)
|
||||
|
||||
|
||||
def _mk_butter_filter(freq, type, order):
|
||||
"""
|
||||
Args:
|
||||
freq: The cutoff frequency for highpass and lowpass filters. For
|
||||
band filters, a list of [low_cutoff, high_cutoff]
|
||||
type: "lowpass", "highpass", or "band"
|
||||
order: nth order butterworth filter (default: 5th order). The
|
||||
attenuation is -6dB/octave beyond the cutoff frequency (for 1st
|
||||
order). A Higher order filter will have more attenuation, each level
|
||||
adding an additional -6dB (so a 3rd order butterworth filter would
|
||||
be -18dB/octave).
|
||||
|
||||
Returns:
|
||||
function which can filter a mono audio segment
|
||||
|
||||
"""
|
||||
def filter_fn(seg):
|
||||
assert seg.channels == 1
|
||||
|
||||
nyq = 0.5 * seg.frame_rate
|
||||
try:
|
||||
freqs = [f / nyq for f in freq]
|
||||
except TypeError:
|
||||
freqs = freq / nyq
|
||||
|
||||
sos = butter(order, freqs, btype=type, output='sos')
|
||||
y = sosfilt(sos, seg.get_array_of_samples())
|
||||
|
||||
return seg._spawn(y.astype(seg.array_type))
|
||||
|
||||
return filter_fn
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def band_pass_filter(seg, low_cutoff_freq, high_cutoff_freq, order=5):
|
||||
filter_fn = _mk_butter_filter([low_cutoff_freq, high_cutoff_freq], 'band', order=order)
|
||||
return seg.apply_mono_filter_to_each_channel(filter_fn)
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def high_pass_filter(seg, cutoff_freq, order=5):
|
||||
filter_fn = _mk_butter_filter(cutoff_freq, 'highpass', order=order)
|
||||
return seg.apply_mono_filter_to_each_channel(filter_fn)
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def low_pass_filter(seg, cutoff_freq, order=5):
|
||||
filter_fn = _mk_butter_filter(cutoff_freq, 'lowpass', order=order)
|
||||
return seg.apply_mono_filter_to_each_channel(filter_fn)
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def _eq(seg, focus_freq, bandwidth=100, mode="peak", gain_dB=0, order=2):
|
||||
"""
|
||||
Args:
|
||||
focus_freq - middle frequency or known frequency of band (in Hz)
|
||||
bandwidth - range of the equalizer band
|
||||
mode - Mode of Equalization(Peak/Notch(Bell Curve),High Shelf, Low Shelf)
|
||||
order - Rolloff factor(1 - 6dB/Octave 2 - 12dB/Octave)
|
||||
|
||||
Returns:
|
||||
Equalized/Filtered AudioSegment
|
||||
"""
|
||||
filt_mode = ["peak", "low_shelf", "high_shelf"]
|
||||
if mode not in filt_mode:
|
||||
raise ValueError("Incorrect Mode Selection")
|
||||
|
||||
if gain_dB >= 0:
|
||||
if mode == "peak":
|
||||
sec = band_pass_filter(seg, focus_freq - bandwidth/2, focus_freq + bandwidth/2, order = order)
|
||||
seg = seg.overlay(sec - (3 - gain_dB))
|
||||
return seg
|
||||
|
||||
if mode == "low_shelf":
|
||||
sec = low_pass_filter(seg, focus_freq, order=order)
|
||||
seg = seg.overlay(sec - (3 - gain_dB))
|
||||
return seg
|
||||
|
||||
if mode == "high_shelf":
|
||||
sec = high_pass_filter(seg, focus_freq, order=order)
|
||||
seg = seg.overlay(sec - (3 - gain_dB))
|
||||
return seg
|
||||
|
||||
if gain_dB < 0:
|
||||
if mode == "peak":
|
||||
sec = high_pass_filter(seg, focus_freq - bandwidth/2, order=order)
|
||||
seg = seg.overlay(sec - (3 + gain_dB)) + gain_dB
|
||||
sec = low_pass_filter(seg, focus_freq + bandwidth/2, order=order)
|
||||
seg = seg.overlay(sec - (3 + gain_dB)) + gain_dB
|
||||
return seg
|
||||
|
||||
if mode == "low_shelf":
|
||||
sec = high_pass_filter(seg, focus_freq, order=order)
|
||||
seg = seg.overlay(sec - (3 + gain_dB)) + gain_dB
|
||||
return seg
|
||||
|
||||
if mode=="high_shelf":
|
||||
sec=low_pass_filter(seg, focus_freq, order=order)
|
||||
seg=seg.overlay(sec - (3 + gain_dB)) +gain_dB
|
||||
return seg
|
||||
|
||||
|
||||
@register_pydub_effect
|
||||
def eq(seg, focus_freq, bandwidth=100, channel_mode="L+R", filter_mode="peak", gain_dB=0, order=2):
|
||||
"""
|
||||
Args:
|
||||
focus_freq - middle frequency or known frequency of band (in Hz)
|
||||
bandwidth - range of the equalizer band
|
||||
channel_mode - Select Channels to be affected by the filter.
|
||||
L+R - Standard Stereo Filter
|
||||
L - Only Left Channel is Filtered
|
||||
R - Only Right Channel is Filtered
|
||||
M+S - Blumlien Stereo Filter(Mid-Side)
|
||||
M - Only Mid Channel is Filtered
|
||||
S - Only Side Channel is Filtered
|
||||
Mono Audio Segments are completely filtered.
|
||||
filter_mode - Mode of Equalization(Peak/Notch(Bell Curve),High Shelf, Low Shelf)
|
||||
order - Rolloff factor(1 - 6dB/Octave 2 - 12dB/Octave)
|
||||
|
||||
Returns:
|
||||
Equalized/Filtered AudioSegment
|
||||
"""
|
||||
channel_modes = ["L+R", "M+S", "L", "R", "M", "S"]
|
||||
if channel_mode not in channel_modes:
|
||||
raise ValueError("Incorrect Channel Mode Selection")
|
||||
|
||||
if seg.channels == 1:
|
||||
return _eq(seg, focus_freq, bandwidth, filter_mode, gain_dB, order)
|
||||
|
||||
if channel_mode == "L+R":
|
||||
return _eq(seg, focus_freq, bandwidth, filter_mode, gain_dB, order)
|
||||
|
||||
if channel_mode == "L":
|
||||
seg = seg.split_to_mono()
|
||||
seg = [_eq(seg[0], focus_freq, bandwidth, filter_mode, gain_dB, order), seg[1]]
|
||||
return AudioSegment.from_mono_audio_segements(seg[0], seg[1])
|
||||
|
||||
if channel_mode == "R":
|
||||
seg = seg.split_to_mono()
|
||||
seg = [seg[0], _eq(seg[1], focus_freq, bandwidth, filter_mode, gain_dB, order)]
|
||||
return AudioSegment.from_mono_audio_segements(seg[0], seg[1])
|
||||
|
||||
if channel_mode == "M+S":
|
||||
seg = stereo_to_ms(seg)
|
||||
seg = _eq(seg, focus_freq, bandwidth, filter_mode, gain_dB, order)
|
||||
return ms_to_stereo(seg)
|
||||
|
||||
if channel_mode == "M":
|
||||
seg = stereo_to_ms(seg).split_to_mono()
|
||||
seg = [_eq(seg[0], focus_freq, bandwidth, filter_mode, gain_dB, order), seg[1]]
|
||||
seg = AudioSegment.from_mono_audio_segements(seg[0], seg[1])
|
||||
return ms_to_stereo(seg)
|
||||
|
||||
if channel_mode == "S":
|
||||
seg = stereo_to_ms(seg).split_to_mono()
|
||||
seg = [seg[0], _eq(seg[1], focus_freq, bandwidth, filter_mode, gain_dB, order)]
|
||||
seg = AudioSegment.from_mono_audio_segements(seg[0], seg[1])
|
||||
return ms_to_stereo(seg)
|
||||
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
Various functions for finding/manipulating silence in AudioSegments
|
||||
"""
|
||||
import itertools
|
||||
|
||||
from .utils import db_to_float
|
||||
|
||||
|
||||
def detect_silence(audio_segment, min_silence_len=1000, silence_thresh=-16, seek_step=1):
|
||||
"""
|
||||
Returns a list of all silent sections [start, end] in milliseconds of audio_segment.
|
||||
Inverse of detect_nonsilent()
|
||||
|
||||
audio_segment - the segment to find silence in
|
||||
min_silence_len - the minimum length for any silent section
|
||||
silence_thresh - the upper bound for how quiet is silent in dFBS
|
||||
seek_step - step size for interating over the segment in ms
|
||||
"""
|
||||
seg_len = len(audio_segment)
|
||||
|
||||
# you can't have a silent portion of a sound that is longer than the sound
|
||||
if seg_len < min_silence_len:
|
||||
return []
|
||||
|
||||
# convert silence threshold to a float value (so we can compare it to rms)
|
||||
silence_thresh = db_to_float(silence_thresh) * audio_segment.max_possible_amplitude
|
||||
|
||||
# find silence and add start and end indicies to the to_cut list
|
||||
silence_starts = []
|
||||
|
||||
# check successive (1 sec by default) chunk of sound for silence
|
||||
# try a chunk at every "seek step" (or every chunk for a seek step == 1)
|
||||
last_slice_start = seg_len - min_silence_len
|
||||
slice_starts = range(0, last_slice_start + 1, seek_step)
|
||||
|
||||
# guarantee last_slice_start is included in the range
|
||||
# to make sure the last portion of the audio is searched
|
||||
if last_slice_start % seek_step:
|
||||
slice_starts = itertools.chain(slice_starts, [last_slice_start])
|
||||
|
||||
for i in slice_starts:
|
||||
audio_slice = audio_segment[i:i + min_silence_len]
|
||||
if audio_slice.rms <= silence_thresh:
|
||||
silence_starts.append(i)
|
||||
|
||||
# short circuit when there is no silence
|
||||
if not silence_starts:
|
||||
return []
|
||||
|
||||
# combine the silence we detected into ranges (start ms - end ms)
|
||||
silent_ranges = []
|
||||
|
||||
prev_i = silence_starts.pop(0)
|
||||
current_range_start = prev_i
|
||||
|
||||
for silence_start_i in silence_starts:
|
||||
continuous = (silence_start_i == prev_i + seek_step)
|
||||
|
||||
# sometimes two small blips are enough for one particular slice to be
|
||||
# non-silent, despite the silence all running together. Just combine
|
||||
# the two overlapping silent ranges.
|
||||
silence_has_gap = silence_start_i > (prev_i + min_silence_len)
|
||||
|
||||
if not continuous and silence_has_gap:
|
||||
silent_ranges.append([current_range_start,
|
||||
prev_i + min_silence_len])
|
||||
current_range_start = silence_start_i
|
||||
prev_i = silence_start_i
|
||||
|
||||
silent_ranges.append([current_range_start,
|
||||
prev_i + min_silence_len])
|
||||
|
||||
return silent_ranges
|
||||
|
||||
|
||||
def detect_nonsilent(audio_segment, min_silence_len=1000, silence_thresh=-16, seek_step=1):
|
||||
"""
|
||||
Returns a list of all nonsilent sections [start, end] in milliseconds of audio_segment.
|
||||
Inverse of detect_silent()
|
||||
|
||||
audio_segment - the segment to find silence in
|
||||
min_silence_len - the minimum length for any silent section
|
||||
silence_thresh - the upper bound for how quiet is silent in dFBS
|
||||
seek_step - step size for interating over the segment in ms
|
||||
"""
|
||||
silent_ranges = detect_silence(audio_segment, min_silence_len, silence_thresh, seek_step)
|
||||
len_seg = len(audio_segment)
|
||||
|
||||
# if there is no silence, the whole thing is nonsilent
|
||||
if not silent_ranges:
|
||||
return [[0, len_seg]]
|
||||
|
||||
# short circuit when the whole audio segment is silent
|
||||
if silent_ranges[0][0] == 0 and silent_ranges[0][1] == len_seg:
|
||||
return []
|
||||
|
||||
prev_end_i = 0
|
||||
nonsilent_ranges = []
|
||||
for start_i, end_i in silent_ranges:
|
||||
nonsilent_ranges.append([prev_end_i, start_i])
|
||||
prev_end_i = end_i
|
||||
|
||||
if end_i != len_seg:
|
||||
nonsilent_ranges.append([prev_end_i, len_seg])
|
||||
|
||||
if nonsilent_ranges[0] == [0, 0]:
|
||||
nonsilent_ranges.pop(0)
|
||||
|
||||
return nonsilent_ranges
|
||||
|
||||
|
||||
def split_on_silence(audio_segment, min_silence_len=1000, silence_thresh=-16, keep_silence=100,
|
||||
seek_step=1):
|
||||
"""
|
||||
Returns list of audio segments from splitting audio_segment on silent sections
|
||||
|
||||
audio_segment - original pydub.AudioSegment() object
|
||||
|
||||
min_silence_len - (in ms) minimum length of a silence to be used for
|
||||
a split. default: 1000ms
|
||||
|
||||
silence_thresh - (in dBFS) anything quieter than this will be
|
||||
considered silence. default: -16dBFS
|
||||
|
||||
keep_silence - (in ms or True/False) leave some silence at the beginning
|
||||
and end of the chunks. Keeps the sound from sounding like it
|
||||
is abruptly cut off.
|
||||
When the length of the silence is less than the keep_silence duration
|
||||
it is split evenly between the preceding and following non-silent
|
||||
segments.
|
||||
If True is specified, all the silence is kept, if False none is kept.
|
||||
default: 100ms
|
||||
|
||||
seek_step - step size for interating over the segment in ms
|
||||
"""
|
||||
|
||||
# from the itertools documentation
|
||||
def pairwise(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||
a, b = itertools.tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
if isinstance(keep_silence, bool):
|
||||
keep_silence = len(audio_segment) if keep_silence else 0
|
||||
|
||||
output_ranges = [
|
||||
[ start - keep_silence, end + keep_silence ]
|
||||
for (start,end)
|
||||
in detect_nonsilent(audio_segment, min_silence_len, silence_thresh, seek_step)
|
||||
]
|
||||
|
||||
for range_i, range_ii in pairwise(output_ranges):
|
||||
last_end = range_i[1]
|
||||
next_start = range_ii[0]
|
||||
if next_start < last_end:
|
||||
range_i[1] = (last_end+next_start)//2
|
||||
range_ii[0] = range_i[1]
|
||||
|
||||
return [
|
||||
audio_segment[ max(start,0) : min(end,len(audio_segment)) ]
|
||||
for start,end in output_ranges
|
||||
]
|
||||
|
||||
|
||||
def detect_leading_silence(sound, silence_threshold=-50.0, chunk_size=10):
|
||||
"""
|
||||
Returns the millisecond/index that the leading silence ends.
|
||||
|
||||
audio_segment - the segment to find silence in
|
||||
silence_threshold - the upper bound for how quiet is silent in dFBS
|
||||
chunk_size - chunk size for interating over the segment in ms
|
||||
"""
|
||||
trim_ms = 0 # ms
|
||||
assert chunk_size > 0 # to avoid infinite loop
|
||||
while sound[trim_ms:trim_ms+chunk_size].dBFS < silence_threshold and trim_ms < len(sound):
|
||||
trim_ms += chunk_size
|
||||
|
||||
# if there is no end it should return the length of the segment
|
||||
return min(trim_ms, len(sound))
|
||||
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
from __future__ import division
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from subprocess import Popen, PIPE
|
||||
from math import log, ceil
|
||||
from tempfile import TemporaryFile
|
||||
from warnings import warn
|
||||
from functools import wraps
|
||||
|
||||
try:
|
||||
import audioop
|
||||
except ImportError:
|
||||
import pyaudioop as audioop
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
basestring = str
|
||||
|
||||
FRAME_WIDTHS = {
|
||||
8: 1,
|
||||
16: 2,
|
||||
32: 4,
|
||||
}
|
||||
ARRAY_TYPES = {
|
||||
8: "b",
|
||||
16: "h",
|
||||
32: "i",
|
||||
}
|
||||
ARRAY_RANGES = {
|
||||
8: (-0x80, 0x7f),
|
||||
16: (-0x8000, 0x7fff),
|
||||
32: (-0x80000000, 0x7fffffff),
|
||||
}
|
||||
|
||||
|
||||
def get_frame_width(bit_depth):
|
||||
return FRAME_WIDTHS[bit_depth]
|
||||
|
||||
|
||||
def get_array_type(bit_depth, signed=True):
|
||||
t = ARRAY_TYPES[bit_depth]
|
||||
if not signed:
|
||||
t = t.upper()
|
||||
return t
|
||||
|
||||
|
||||
def get_min_max_value(bit_depth):
|
||||
return ARRAY_RANGES[bit_depth]
|
||||
|
||||
|
||||
def _fd_or_path_or_tempfile(fd, mode='w+b', tempfile=True):
|
||||
close_fd = False
|
||||
if fd is None and tempfile:
|
||||
fd = TemporaryFile(mode=mode)
|
||||
close_fd = True
|
||||
|
||||
if isinstance(fd, basestring):
|
||||
fd = open(fd, mode=mode)
|
||||
close_fd = True
|
||||
|
||||
try:
|
||||
if isinstance(fd, os.PathLike):
|
||||
fd = open(fd, mode=mode)
|
||||
close_fd = True
|
||||
except AttributeError:
|
||||
# module os has no attribute PathLike, so we're on python < 3.6.
|
||||
# The protocol we're trying to support doesn't exist, so just pass.
|
||||
pass
|
||||
|
||||
return fd, close_fd
|
||||
|
||||
|
||||
def db_to_float(db, using_amplitude=True):
|
||||
"""
|
||||
Converts the input db to a float, which represents the equivalent
|
||||
ratio in power.
|
||||
"""
|
||||
db = float(db)
|
||||
if using_amplitude:
|
||||
return 10 ** (db / 20)
|
||||
else: # using power
|
||||
return 10 ** (db / 10)
|
||||
|
||||
|
||||
def ratio_to_db(ratio, val2=None, using_amplitude=True):
|
||||
"""
|
||||
Converts the input float to db, which represents the equivalent
|
||||
to the ratio in power represented by the multiplier passed in.
|
||||
"""
|
||||
ratio = float(ratio)
|
||||
|
||||
# accept 2 values and use the ratio of val1 to val2
|
||||
if val2 is not None:
|
||||
ratio = ratio / val2
|
||||
|
||||
# special case for multiply-by-zero (convert to silence)
|
||||
if ratio == 0:
|
||||
return -float('inf')
|
||||
|
||||
if using_amplitude:
|
||||
return 20 * log(ratio, 10)
|
||||
else: # using power
|
||||
return 10 * log(ratio, 10)
|
||||
|
||||
|
||||
def register_pydub_effect(fn, name=None):
|
||||
"""
|
||||
decorator for adding pydub effects to the AudioSegment objects.
|
||||
example use:
|
||||
@register_pydub_effect
|
||||
def normalize(audio_segment):
|
||||
...
|
||||
or you can specify a name:
|
||||
@register_pydub_effect("normalize")
|
||||
def normalize_audio_segment(audio_segment):
|
||||
...
|
||||
"""
|
||||
if isinstance(fn, basestring):
|
||||
name = fn
|
||||
return lambda fn: register_pydub_effect(fn, name)
|
||||
|
||||
if name is None:
|
||||
name = fn.__name__
|
||||
|
||||
from .audio_segment import AudioSegment
|
||||
setattr(AudioSegment, name, fn)
|
||||
return fn
|
||||
|
||||
|
||||
def make_chunks(audio_segment, chunk_length):
|
||||
"""
|
||||
Breaks an AudioSegment into chunks that are <chunk_length> milliseconds
|
||||
long.
|
||||
if chunk_length is 50 then you'll get a list of 50 millisecond long audio
|
||||
segments back (except the last one, which can be shorter)
|
||||
"""
|
||||
number_of_chunks = ceil(len(audio_segment) / float(chunk_length))
|
||||
return [audio_segment[i * chunk_length:(i + 1) * chunk_length]
|
||||
for i in range(int(number_of_chunks))]
|
||||
|
||||
|
||||
def which(program):
|
||||
"""
|
||||
Mimics behavior of UNIX which command.
|
||||
"""
|
||||
# Add .exe program extension for windows support
|
||||
if os.name == "nt" and not program.endswith(".exe"):
|
||||
program += ".exe"
|
||||
|
||||
envdir_list = [os.curdir] + os.environ["PATH"].split(os.pathsep)
|
||||
|
||||
for envdir in envdir_list:
|
||||
program_path = os.path.join(envdir, program)
|
||||
if os.path.isfile(program_path) and os.access(program_path, os.X_OK):
|
||||
return program_path
|
||||
|
||||
|
||||
def get_encoder_name():
|
||||
"""
|
||||
Return enconder default application for system, either avconv or ffmpeg
|
||||
"""
|
||||
if which("avconv"):
|
||||
return "avconv"
|
||||
elif which("ffmpeg"):
|
||||
return "ffmpeg"
|
||||
else:
|
||||
# should raise exception
|
||||
warn("Couldn't find ffmpeg or avconv - defaulting to ffmpeg, but may not work", RuntimeWarning)
|
||||
return "ffmpeg"
|
||||
|
||||
|
||||
def get_player_name():
|
||||
"""
|
||||
Return enconder default application for system, either avconv or ffmpeg
|
||||
"""
|
||||
if which("avplay"):
|
||||
return "avplay"
|
||||
elif which("ffplay"):
|
||||
return "ffplay"
|
||||
else:
|
||||
# should raise exception
|
||||
warn("Couldn't find ffplay or avplay - defaulting to ffplay, but may not work", RuntimeWarning)
|
||||
return "ffplay"
|
||||
|
||||
|
||||
def get_prober_name():
|
||||
"""
|
||||
Return probe application, either avconv or ffmpeg
|
||||
"""
|
||||
if which("avprobe"):
|
||||
return "avprobe"
|
||||
elif which("ffprobe"):
|
||||
return "ffprobe"
|
||||
else:
|
||||
# should raise exception
|
||||
warn("Couldn't find ffprobe or avprobe - defaulting to ffprobe, but may not work", RuntimeWarning)
|
||||
return "ffprobe"
|
||||
|
||||
|
||||
def fsdecode(filename):
|
||||
"""Wrapper for os.fsdecode which was introduced in python 3.2 ."""
|
||||
|
||||
if sys.version_info >= (3, 2):
|
||||
PathLikeTypes = (basestring, bytes)
|
||||
if sys.version_info >= (3, 6):
|
||||
PathLikeTypes += (os.PathLike,)
|
||||
if isinstance(filename, PathLikeTypes):
|
||||
return os.fsdecode(filename)
|
||||
else:
|
||||
if isinstance(filename, bytes):
|
||||
return filename.decode(sys.getfilesystemencoding())
|
||||
if isinstance(filename, basestring):
|
||||
return filename
|
||||
|
||||
raise TypeError("type {0} not accepted by fsdecode".format(type(filename)))
|
||||
|
||||
|
||||
def get_extra_info(stderr):
|
||||
"""
|
||||
avprobe sometimes gives more information on stderr than
|
||||
on the json output. The information has to be extracted
|
||||
from stderr of the format of:
|
||||
' Stream #0:0: Audio: flac, 88200 Hz, stereo, s32 (24 bit)'
|
||||
or (macOS version):
|
||||
' Stream #0:0: Audio: vorbis'
|
||||
' 44100 Hz, stereo, fltp, 320 kb/s'
|
||||
|
||||
:type stderr: str
|
||||
:rtype: list of dict
|
||||
"""
|
||||
extra_info = {}
|
||||
|
||||
re_stream = r'(?P<space_start> +)Stream #0[:\.](?P<stream_id>([0-9]+))(?P<content_0>.+)\n?(?! *Stream)((?P<space_end> +)(?P<content_1>.+))?'
|
||||
for i in re.finditer(re_stream, stderr):
|
||||
if i.group('space_end') is not None and len(i.group('space_start')) <= len(
|
||||
i.group('space_end')):
|
||||
content_line = ','.join([i.group('content_0'), i.group('content_1')])
|
||||
else:
|
||||
content_line = i.group('content_0')
|
||||
tokens = [x.strip() for x in re.split('[:,]', content_line) if x]
|
||||
extra_info[int(i.group('stream_id'))] = tokens
|
||||
return extra_info
|
||||
|
||||
|
||||
def mediainfo_json(filepath, read_ahead_limit=-1):
|
||||
"""Return json dictionary with media info(codec, duration, size, bitrate...) from filepath
|
||||
"""
|
||||
prober = get_prober_name()
|
||||
command_args = [
|
||||
"-v", "info",
|
||||
"-show_format",
|
||||
"-show_streams",
|
||||
]
|
||||
try:
|
||||
command_args += [fsdecode(filepath)]
|
||||
stdin_parameter = None
|
||||
stdin_data = None
|
||||
except TypeError:
|
||||
if prober == 'ffprobe':
|
||||
command_args += ["-read_ahead_limit", str(read_ahead_limit),
|
||||
"cache:pipe:0"]
|
||||
else:
|
||||
command_args += ["-"]
|
||||
stdin_parameter = PIPE
|
||||
file, close_file = _fd_or_path_or_tempfile(filepath, 'rb', tempfile=False)
|
||||
file.seek(0)
|
||||
stdin_data = file.read()
|
||||
if close_file:
|
||||
file.close()
|
||||
|
||||
command = [prober, '-of', 'json'] + command_args
|
||||
res = Popen(command, stdin=stdin_parameter, stdout=PIPE, stderr=PIPE)
|
||||
output, stderr = res.communicate(input=stdin_data)
|
||||
output = output.decode("utf-8", 'ignore')
|
||||
stderr = stderr.decode("utf-8", 'ignore')
|
||||
|
||||
info = json.loads(output)
|
||||
|
||||
if not info:
|
||||
# If ffprobe didn't give any information, just return it
|
||||
# (for example, because the file doesn't exist)
|
||||
return info
|
||||
|
||||
extra_info = get_extra_info(stderr)
|
||||
|
||||
audio_streams = [x for x in info['streams'] if x['codec_type'] == 'audio']
|
||||
if len(audio_streams) == 0:
|
||||
return info
|
||||
|
||||
# We just operate on the first audio stream in case there are more
|
||||
stream = audio_streams[0]
|
||||
|
||||
def set_property(stream, prop, value):
|
||||
if prop not in stream or stream[prop] == 0:
|
||||
stream[prop] = value
|
||||
|
||||
for token in extra_info[stream['index']]:
|
||||
m = re.match('([su]([0-9]{1,2})p?) \(([0-9]{1,2}) bit\)$', token)
|
||||
m2 = re.match('([su]([0-9]{1,2})p?)( \(default\))?$', token)
|
||||
if m:
|
||||
set_property(stream, 'sample_fmt', m.group(1))
|
||||
set_property(stream, 'bits_per_sample', int(m.group(2)))
|
||||
set_property(stream, 'bits_per_raw_sample', int(m.group(3)))
|
||||
elif m2:
|
||||
set_property(stream, 'sample_fmt', m2.group(1))
|
||||
set_property(stream, 'bits_per_sample', int(m2.group(2)))
|
||||
set_property(stream, 'bits_per_raw_sample', int(m2.group(2)))
|
||||
elif re.match('(flt)p?( \(default\))?$', token):
|
||||
set_property(stream, 'sample_fmt', token)
|
||||
set_property(stream, 'bits_per_sample', 32)
|
||||
set_property(stream, 'bits_per_raw_sample', 32)
|
||||
elif re.match('(dbl)p?( \(default\))?$', token):
|
||||
set_property(stream, 'sample_fmt', token)
|
||||
set_property(stream, 'bits_per_sample', 64)
|
||||
set_property(stream, 'bits_per_raw_sample', 64)
|
||||
return info
|
||||
|
||||
|
||||
def mediainfo(filepath):
|
||||
"""Return dictionary with media info(codec, duration, size, bitrate...) from filepath
|
||||
"""
|
||||
|
||||
prober = get_prober_name()
|
||||
command_args = [
|
||||
"-v", "quiet",
|
||||
"-show_format",
|
||||
"-show_streams",
|
||||
filepath
|
||||
]
|
||||
|
||||
command = [prober, '-of', 'old'] + command_args
|
||||
res = Popen(command, stdout=PIPE)
|
||||
output = res.communicate()[0].decode("utf-8")
|
||||
|
||||
if res.returncode != 0:
|
||||
command = [prober] + command_args
|
||||
output = Popen(command, stdout=PIPE).communicate()[0].decode("utf-8")
|
||||
|
||||
rgx = re.compile(r"(?:(?P<inner_dict>.*?):)?(?P<key>.*?)\=(?P<value>.*?)$")
|
||||
info = {}
|
||||
|
||||
if sys.platform == 'win32':
|
||||
output = output.replace("\r", "")
|
||||
|
||||
for line in output.split("\n"):
|
||||
# print(line)
|
||||
mobj = rgx.match(line)
|
||||
|
||||
if mobj:
|
||||
# print(mobj.groups())
|
||||
inner_dict, key, value = mobj.groups()
|
||||
|
||||
if inner_dict:
|
||||
try:
|
||||
info[inner_dict]
|
||||
except KeyError:
|
||||
info[inner_dict] = {}
|
||||
info[inner_dict][key] = value
|
||||
else:
|
||||
info[key] = value
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def cache_codecs(function):
|
||||
cache = {}
|
||||
|
||||
@wraps(function)
|
||||
def wrapper():
|
||||
try:
|
||||
return cache[0]
|
||||
except:
|
||||
cache[0] = function()
|
||||
return cache[0]
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@cache_codecs
|
||||
def get_supported_codecs():
|
||||
encoder = get_encoder_name()
|
||||
command = [encoder, "-codecs"]
|
||||
res = Popen(command, stdout=PIPE, stderr=PIPE)
|
||||
output = res.communicate()[0].decode("utf-8")
|
||||
if res.returncode != 0:
|
||||
return []
|
||||
|
||||
if sys.platform == 'win32':
|
||||
output = output.replace("\r", "")
|
||||
|
||||
|
||||
rgx = re.compile(r"^([D.][E.][AVS.][I.][L.][S.]) (\w*) +(.*)")
|
||||
decoders = set()
|
||||
encoders = set()
|
||||
for line in output.split('\n'):
|
||||
match = rgx.match(line.strip())
|
||||
if not match:
|
||||
continue
|
||||
flags, codec, name = match.groups()
|
||||
|
||||
if flags[0] == 'D':
|
||||
decoders.add(codec)
|
||||
|
||||
if flags[1] == 'E':
|
||||
encoders.add(codec)
|
||||
|
||||
return (decoders, encoders)
|
||||
|
||||
|
||||
def get_supported_decoders():
|
||||
return get_supported_codecs()[0]
|
||||
|
||||
|
||||
def get_supported_encoders():
|
||||
return get_supported_codecs()[1]
|
||||
|
||||
def stereo_to_ms(audio_segment):
|
||||
'''
|
||||
Left-Right -> Mid-Side
|
||||
'''
|
||||
channel = audio_segment.split_to_mono()
|
||||
channel = [channel[0].overlay(channel[1]), channel[0].overlay(channel[1].invert_phase())]
|
||||
return AudioSegment.from_mono_audiosegments(channel[0], channel[1])
|
||||
|
||||
def ms_to_stereo(audio_segment):
|
||||
'''
|
||||
Mid-Side -> Left-Right
|
||||
'''
|
||||
channel = audio_segment.split_to_mono()
|
||||
channel = [channel[0].overlay(channel[1]) - 3, channel[0].overlay(channel[1].invert_phase()) - 3]
|
||||
return AudioSegment.from_mono_audiosegments(channel[0], channel[1])
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
import speech_recognition as sr
|
||||
|
||||
r = sr.Recognizer()
|
||||
m = sr.Microphone()
|
||||
|
||||
try:
|
||||
print("A moment of silence, please...")
|
||||
with m as source: r.adjust_for_ambient_noise(source)
|
||||
print("Set minimum energy threshold to {}".format(r.energy_threshold))
|
||||
while True:
|
||||
print("Say something!")
|
||||
with m as source: audio = r.listen(source)
|
||||
print("Got it! Now to recognize it...")
|
||||
try:
|
||||
# recognize speech using Google Speech Recognition
|
||||
value = r.recognize_google(audio)
|
||||
|
||||
print("You said {}".format(value))
|
||||
except sr.UnknownValueError:
|
||||
print("Oops! Didn't catch that")
|
||||
except sr.RequestError as e:
|
||||
print("Uh oh! Couldn't request results from Google Speech Recognition service; {0}".format(e))
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,317 @@
|
||||
import aifc
|
||||
import audioop
|
||||
import io
|
||||
import os
|
||||
import platform
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import wave
|
||||
|
||||
|
||||
class AudioData(object):
|
||||
"""
|
||||
Creates a new ``AudioData`` instance, which represents mono audio data.
|
||||
|
||||
The raw audio data is specified by ``frame_data``, which is a sequence of bytes representing audio samples. This is the frame data structure used by the PCM WAV format.
|
||||
|
||||
The width of each sample, in bytes, is specified by ``sample_width``. Each group of ``sample_width`` bytes represents a single audio sample.
|
||||
|
||||
The audio data is assumed to have a sample rate of ``sample_rate`` samples per second (Hertz).
|
||||
|
||||
Usually, instances of this class are obtained from ``recognizer_instance.record`` or ``recognizer_instance.listen``, or in the callback for ``recognizer_instance.listen_in_background``, rather than instantiating them directly.
|
||||
"""
|
||||
|
||||
def __init__(self, frame_data, sample_rate, sample_width):
|
||||
assert sample_rate > 0, "Sample rate must be a positive integer"
|
||||
assert (
|
||||
sample_width % 1 == 0 and 1 <= sample_width <= 4
|
||||
), "Sample width must be between 1 and 4 inclusive"
|
||||
self.frame_data = frame_data
|
||||
self.sample_rate = sample_rate
|
||||
self.sample_width = int(sample_width)
|
||||
|
||||
def get_segment(self, start_ms=None, end_ms=None):
|
||||
"""
|
||||
Returns a new ``AudioData`` instance, trimmed to a given time interval. In other words, an ``AudioData`` instance with the same audio data except starting at ``start_ms`` milliseconds in and ending ``end_ms`` milliseconds in.
|
||||
|
||||
If not specified, ``start_ms`` defaults to the beginning of the audio, and ``end_ms`` defaults to the end.
|
||||
"""
|
||||
assert (
|
||||
start_ms is None or start_ms >= 0
|
||||
), "``start_ms`` must be a non-negative number"
|
||||
assert end_ms is None or end_ms >= (
|
||||
0 if start_ms is None else start_ms
|
||||
), "``end_ms`` must be a non-negative number greater or equal to ``start_ms``"
|
||||
if start_ms is None:
|
||||
start_byte = 0
|
||||
else:
|
||||
start_byte = int(
|
||||
(start_ms * self.sample_rate * self.sample_width) // 1000
|
||||
)
|
||||
if end_ms is None:
|
||||
end_byte = len(self.frame_data)
|
||||
else:
|
||||
end_byte = int(
|
||||
(end_ms * self.sample_rate * self.sample_width) // 1000
|
||||
)
|
||||
return AudioData(
|
||||
self.frame_data[start_byte:end_byte],
|
||||
self.sample_rate,
|
||||
self.sample_width,
|
||||
)
|
||||
|
||||
def get_raw_data(self, convert_rate=None, convert_width=None):
|
||||
"""
|
||||
Returns a byte string representing the raw frame data for the audio represented by the ``AudioData`` instance.
|
||||
|
||||
If ``convert_rate`` is specified and the audio sample rate is not ``convert_rate`` Hz, the resulting audio is resampled to match.
|
||||
|
||||
If ``convert_width`` is specified and the audio samples are not ``convert_width`` bytes each, the resulting audio is converted to match.
|
||||
|
||||
Writing these bytes directly to a file results in a valid `RAW/PCM audio file <https://en.wikipedia.org/wiki/Raw_audio_format>`__.
|
||||
"""
|
||||
assert (
|
||||
convert_rate is None or convert_rate > 0
|
||||
), "Sample rate to convert to must be a positive integer"
|
||||
assert convert_width is None or (
|
||||
convert_width % 1 == 0 and 1 <= convert_width <= 4
|
||||
), "Sample width to convert to must be between 1 and 4 inclusive"
|
||||
|
||||
raw_data = self.frame_data
|
||||
|
||||
# make sure unsigned 8-bit audio (which uses unsigned samples) is handled like higher sample width audio (which uses signed samples)
|
||||
if self.sample_width == 1:
|
||||
raw_data = audioop.bias(
|
||||
raw_data, 1, -128
|
||||
) # subtract 128 from every sample to make them act like signed samples
|
||||
|
||||
# resample audio at the desired rate if specified
|
||||
if convert_rate is not None and self.sample_rate != convert_rate:
|
||||
raw_data, _ = audioop.ratecv(
|
||||
raw_data,
|
||||
self.sample_width,
|
||||
1,
|
||||
self.sample_rate,
|
||||
convert_rate,
|
||||
None,
|
||||
)
|
||||
|
||||
# convert samples to desired sample width if specified
|
||||
if convert_width is not None and self.sample_width != convert_width:
|
||||
if (
|
||||
convert_width == 3
|
||||
): # we're converting the audio into 24-bit (workaround for https://bugs.python.org/issue12866)
|
||||
raw_data = audioop.lin2lin(
|
||||
raw_data, self.sample_width, 4
|
||||
) # convert audio into 32-bit first, which is always supported
|
||||
try:
|
||||
audioop.bias(
|
||||
b"", 3, 0
|
||||
) # test whether 24-bit audio is supported (for example, ``audioop`` in Python 3.3 and below don't support sample width 3, while Python 3.4+ do)
|
||||
except (
|
||||
audioop.error
|
||||
): # this version of audioop doesn't support 24-bit audio (probably Python 3.3 or less)
|
||||
raw_data = b"".join(
|
||||
raw_data[i + 1 : i + 4]
|
||||
for i in range(0, len(raw_data), 4)
|
||||
) # since we're in little endian, we discard the first byte from each 32-bit sample to get a 24-bit sample
|
||||
else: # 24-bit audio fully supported, we don't need to shim anything
|
||||
raw_data = audioop.lin2lin(
|
||||
raw_data, self.sample_width, convert_width
|
||||
)
|
||||
else:
|
||||
raw_data = audioop.lin2lin(
|
||||
raw_data, self.sample_width, convert_width
|
||||
)
|
||||
|
||||
# if the output is 8-bit audio with unsigned samples, convert the samples we've been treating as signed to unsigned again
|
||||
if convert_width == 1:
|
||||
raw_data = audioop.bias(
|
||||
raw_data, 1, 128
|
||||
) # add 128 to every sample to make them act like unsigned samples again
|
||||
|
||||
return raw_data
|
||||
|
||||
def get_wav_data(self, convert_rate=None, convert_width=None):
|
||||
"""
|
||||
Returns a byte string representing the contents of a WAV file containing the audio represented by the ``AudioData`` instance.
|
||||
|
||||
If ``convert_width`` is specified and the audio samples are not ``convert_width`` bytes each, the resulting audio is converted to match.
|
||||
|
||||
If ``convert_rate`` is specified and the audio sample rate is not ``convert_rate`` Hz, the resulting audio is resampled to match.
|
||||
|
||||
Writing these bytes directly to a file results in a valid `WAV file <https://en.wikipedia.org/wiki/WAV>`__.
|
||||
"""
|
||||
raw_data = self.get_raw_data(convert_rate, convert_width)
|
||||
sample_rate = (
|
||||
self.sample_rate if convert_rate is None else convert_rate
|
||||
)
|
||||
sample_width = (
|
||||
self.sample_width if convert_width is None else convert_width
|
||||
)
|
||||
|
||||
# generate the WAV file contents
|
||||
with io.BytesIO() as wav_file:
|
||||
wav_writer = wave.open(wav_file, "wb")
|
||||
try: # note that we can't use context manager, since that was only added in Python 3.4
|
||||
wav_writer.setframerate(sample_rate)
|
||||
wav_writer.setsampwidth(sample_width)
|
||||
wav_writer.setnchannels(1)
|
||||
wav_writer.writeframes(raw_data)
|
||||
wav_data = wav_file.getvalue()
|
||||
finally: # make sure resources are cleaned up
|
||||
wav_writer.close()
|
||||
return wav_data
|
||||
|
||||
def get_aiff_data(self, convert_rate=None, convert_width=None):
|
||||
"""
|
||||
Returns a byte string representing the contents of an AIFF-C file containing the audio represented by the ``AudioData`` instance.
|
||||
|
||||
If ``convert_width`` is specified and the audio samples are not ``convert_width`` bytes each, the resulting audio is converted to match.
|
||||
|
||||
If ``convert_rate`` is specified and the audio sample rate is not ``convert_rate`` Hz, the resulting audio is resampled to match.
|
||||
|
||||
Writing these bytes directly to a file results in a valid `AIFF-C file <https://en.wikipedia.org/wiki/Audio_Interchange_File_Format>`__.
|
||||
"""
|
||||
raw_data = self.get_raw_data(convert_rate, convert_width)
|
||||
sample_rate = (
|
||||
self.sample_rate if convert_rate is None else convert_rate
|
||||
)
|
||||
sample_width = (
|
||||
self.sample_width if convert_width is None else convert_width
|
||||
)
|
||||
|
||||
# the AIFF format is big-endian, so we need to convert the little-endian raw data to big-endian
|
||||
if hasattr(
|
||||
audioop, "byteswap"
|
||||
): # ``audioop.byteswap`` was only added in Python 3.4
|
||||
raw_data = audioop.byteswap(raw_data, sample_width)
|
||||
else: # manually reverse the bytes of each sample, which is slower but works well enough as a fallback
|
||||
raw_data = raw_data[sample_width - 1 :: -1] + b"".join(
|
||||
raw_data[i + sample_width : i : -1]
|
||||
for i in range(sample_width - 1, len(raw_data), sample_width)
|
||||
)
|
||||
|
||||
# generate the AIFF-C file contents
|
||||
with io.BytesIO() as aiff_file:
|
||||
aiff_writer = aifc.open(aiff_file, "wb")
|
||||
try: # note that we can't use context manager, since that was only added in Python 3.4
|
||||
aiff_writer.setframerate(sample_rate)
|
||||
aiff_writer.setsampwidth(sample_width)
|
||||
aiff_writer.setnchannels(1)
|
||||
aiff_writer.writeframes(raw_data)
|
||||
aiff_data = aiff_file.getvalue()
|
||||
finally: # make sure resources are cleaned up
|
||||
aiff_writer.close()
|
||||
return aiff_data
|
||||
|
||||
def get_flac_data(self, convert_rate=None, convert_width=None):
|
||||
"""
|
||||
Returns a byte string representing the contents of a FLAC file containing the audio represented by the ``AudioData`` instance.
|
||||
|
||||
Note that 32-bit FLAC is not supported. If the audio data is 32-bit and ``convert_width`` is not specified, then the resulting FLAC will be a 24-bit FLAC.
|
||||
|
||||
If ``convert_rate`` is specified and the audio sample rate is not ``convert_rate`` Hz, the resulting audio is resampled to match.
|
||||
|
||||
If ``convert_width`` is specified and the audio samples are not ``convert_width`` bytes each, the resulting audio is converted to match.
|
||||
|
||||
Writing these bytes directly to a file results in a valid `FLAC file <https://en.wikipedia.org/wiki/FLAC>`__.
|
||||
"""
|
||||
assert convert_width is None or (
|
||||
convert_width % 1 == 0 and 1 <= convert_width <= 3
|
||||
), "Sample width to convert to must be between 1 and 3 inclusive"
|
||||
|
||||
if (
|
||||
self.sample_width > 3 and convert_width is None
|
||||
): # resulting WAV data would be 32-bit, which is not convertable to FLAC using our encoder
|
||||
convert_width = 3 # the largest supported sample width is 24-bit, so we'll limit the sample width to that
|
||||
|
||||
# run the FLAC converter with the WAV data to get the FLAC data
|
||||
wav_data = self.get_wav_data(convert_rate, convert_width)
|
||||
flac_converter = get_flac_converter()
|
||||
if (
|
||||
os.name == "nt"
|
||||
): # on Windows, specify that the process is to be started without showing a console window
|
||||
startup_info = subprocess.STARTUPINFO()
|
||||
startup_info.dwFlags |= (
|
||||
subprocess.STARTF_USESHOWWINDOW
|
||||
) # specify that the wShowWindow field of `startup_info` contains a value
|
||||
startup_info.wShowWindow = (
|
||||
subprocess.SW_HIDE
|
||||
) # specify that the console window should be hidden
|
||||
else:
|
||||
startup_info = None # default startupinfo
|
||||
process = subprocess.Popen(
|
||||
[
|
||||
flac_converter,
|
||||
"--stdout",
|
||||
"--totally-silent", # put the resulting FLAC file in stdout, and make sure it's not mixed with any program output
|
||||
"--best", # highest level of compression available
|
||||
"-", # the input FLAC file contents will be given in stdin
|
||||
],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
startupinfo=startup_info,
|
||||
)
|
||||
flac_data, stderr = process.communicate(wav_data)
|
||||
return flac_data
|
||||
|
||||
|
||||
def get_flac_converter():
|
||||
"""Returns the absolute path of a FLAC converter executable, or raises an OSError if none can be found."""
|
||||
flac_converter = shutil_which("flac") # check for installed version first
|
||||
if flac_converter is None: # flac utility is not installed
|
||||
base_path = os.path.dirname(
|
||||
os.path.abspath(__file__)
|
||||
) # directory of the current module file, where all the FLAC bundled binaries are stored
|
||||
system, machine = platform.system(), platform.machine()
|
||||
if system == "Windows" and machine in {
|
||||
"i686",
|
||||
"i786",
|
||||
"x86",
|
||||
"x86_64",
|
||||
"AMD64",
|
||||
}:
|
||||
flac_converter = os.path.join(base_path, "flac-win32.exe")
|
||||
elif system == "Darwin" and machine in {
|
||||
"i686",
|
||||
"i786",
|
||||
"x86",
|
||||
"x86_64",
|
||||
"AMD64",
|
||||
}:
|
||||
flac_converter = os.path.join(base_path, "flac-mac")
|
||||
elif system == "Linux" and machine in {"i686", "i786", "x86"}:
|
||||
flac_converter = os.path.join(base_path, "flac-linux-x86")
|
||||
elif system == "Linux" and machine in {"x86_64", "AMD64"}:
|
||||
flac_converter = os.path.join(base_path, "flac-linux-x86_64")
|
||||
else: # no FLAC converter available
|
||||
raise OSError(
|
||||
"FLAC conversion utility not available - consider installing the FLAC command line application by running `apt-get install flac` or your operating system's equivalent"
|
||||
)
|
||||
|
||||
# mark FLAC converter as executable if possible
|
||||
try:
|
||||
# handle known issue when running on docker:
|
||||
# run executable right after chmod() may result in OSError "Text file busy"
|
||||
# fix: flush FS with sync
|
||||
if not os.access(flac_converter, os.X_OK):
|
||||
stat_info = os.stat(flac_converter)
|
||||
os.chmod(flac_converter, stat_info.st_mode | stat.S_IEXEC)
|
||||
if "Linux" in platform.system():
|
||||
os.sync() if sys.version_info >= (3, 3) else os.system("sync")
|
||||
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return flac_converter
|
||||
|
||||
|
||||
def shutil_which(pgm):
|
||||
"""Python 2 compatibility: backport of ``shutil.which()`` from Python 3"""
|
||||
path = os.getenv("PATH")
|
||||
for p in path.split(os.path.pathsep):
|
||||
p = os.path.join(p, pgm)
|
||||
if os.path.exists(p) and os.access(p, os.X_OK):
|
||||
return p
|
||||
@@ -0,0 +1,22 @@
|
||||
class SetupError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class WaitTimeoutError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RequestError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownValueError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TranscriptionNotReady(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TranscriptionFailed(Exception):
|
||||
pass
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+31
@@ -0,0 +1,31 @@
|
||||
Copyright (c) 1999-2015 Carnegie Mellon University. All rights
|
||||
reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
This work was supported in part by funding from the Defense Advanced
|
||||
Research Projects Agency and the National Science Foundation of the
|
||||
United States of America, and the CMU Sphinx Speech Consortium.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND
|
||||
ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY
|
||||
NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/* ====================================================================
|
||||
* Copyright (c) 2015 Alpha Cephei Inc. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY ALPHA CEPHEI INC. ``AS IS'' AND.
|
||||
* ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,.
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ALPHA CEPHEI INC.
|
||||
* NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT.
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,.
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY.
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT.
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE.
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
*/
|
||||
|
||||
This directory contains generic US english acoustic model trained with
|
||||
latest sphinxtrain.
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
-lowerf 130
|
||||
-upperf 6800
|
||||
-nfilt 25
|
||||
-transform dct
|
||||
-lifter 22
|
||||
-feat 1s_c_d_dd
|
||||
-svspec 0-12/13-25/26-38
|
||||
-agc none
|
||||
-cmn current
|
||||
-varnorm no
|
||||
-model ptm
|
||||
-cmninit 40,3,-1
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
BIN
Binary file not shown.
+5
@@ -0,0 +1,5 @@
|
||||
<s> SIL
|
||||
</s> SIL
|
||||
<sil> SIL
|
||||
[NOISE] +NSN+
|
||||
[SPEECH] +SPN+
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+133425
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from speech_recognition.audio import AudioData
|
||||
from speech_recognition.exceptions import SetupError
|
||||
|
||||
|
||||
def recognize_whisper_api(
|
||||
recognizer,
|
||||
audio_data: "AudioData",
|
||||
*,
|
||||
model: str = "whisper-1",
|
||||
api_key: str | None = None,
|
||||
):
|
||||
"""
|
||||
Performs speech recognition on ``audio_data`` (an ``AudioData`` instance), using the OpenAI Whisper API.
|
||||
|
||||
This function requires an OpenAI account; visit https://platform.openai.com/signup, then generate API Key in `User settings <https://platform.openai.com/account/api-keys>`__.
|
||||
|
||||
Detail: https://platform.openai.com/docs/guides/speech-to-text
|
||||
|
||||
Raises a ``speech_recognition.exceptions.SetupError`` exception if there are any issues with the openai installation, or the environment variable is missing.
|
||||
"""
|
||||
if not isinstance(audio_data, AudioData):
|
||||
raise ValueError("``audio_data`` must be an ``AudioData`` instance")
|
||||
if api_key is None and os.environ.get("OPENAI_API_KEY") is None:
|
||||
raise SetupError("Set environment variable ``OPENAI_API_KEY``")
|
||||
|
||||
try:
|
||||
import openai
|
||||
except ImportError:
|
||||
raise SetupError(
|
||||
"missing openai module: ensure that openai is set up correctly."
|
||||
)
|
||||
|
||||
wav_data = BytesIO(audio_data.get_wav_data())
|
||||
wav_data.name = "SpeechRecognition_audio.wav"
|
||||
|
||||
transcript = openai.Audio.transcribe(model, wav_data, api_key=api_key)
|
||||
return transcript["text"]
|
||||
Reference in New Issue
Block a user