mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-05 04:53:01 +00:00
merge refactors pr #364 from @michilyy
* Save to DB only unique trackhashes * Add check if track already exists in playlist * replace all paths with `pathlib.Path` * `architecture.md`: * add config folder layout `config.py`: * fix bug where `pathlib.Path` cannot be serialized `files.py`: * remove unused imports * update path concatenation to `pathlib.Path` * add config-folder creation `imgserver.py`: * fix serialisation bug `playlistlib.py`: * update path concatenation to `pathlib.Path` * update all `settings.Paths` usages to new singleton `Paths` class. * update all usages of `settings.Paths` * `files.py`: * rework assets copy function. * remove unused loop and unused `shutil.copy2` function `settings.py` * fix recursion exception in `Paths` * `settings.py`: * remove Singleton and `@property` todos from `Paths` * `__init__.py`: * remove now unused function `create_config_dir()` `setup.files`: * remove because merged into `settings.Paths()` for more central and clear flow how the base path gets decided `settings.py`: * add `copy_assets` function `start_swingmusic.py`: * add configurable settings.Paths class `__main__.py`: * update click to used correct default path * remove wrong commited egg files * remove change in the wrong branch * add forgotten `property` decorator update `get_files_and_dirs` to use pathlib where possible `config.py`: * update type annotation `folders.py`: * convert `pathlib` to posix path where needed for sub-functions `folderlib.py`: * rework `get_files_and_dirs` to use `pathlib` where possible `settings.py`: * add forgotten `@property` `start_swingmusic.py`: * remove second `log_startup_info()` * `artistlib.py`: * fix calling property `tagger.py`: * fix comparing elements in `pathlib.Path` * add support for repeating lyrics. * rework lyrics api and lib * update most path functions. add type-hint pathlib where needed * for serialization paths are converted to posix path * use `open` instead of `os.open` update `metaclass` with constant * fix initial config exception if empty file existed * update `userConfig` with `InitVar` to be excluded from `asdict` * remove `is_windows_slash()` rework path function to use pathlib * convert `pathlib.Path` to `str` for serialization * fixing bug with str + pathlib * `__main__.py`: * update click to use package version * remove now unused function `print_version` `filesystem.py`: * rework `CWD` to use importlib `pyproject.toml`: * disable namespace for `importlib.resources` to work correctly * update `lyrics.py`: * remove unused functions * simplify functions * fix bug where assets get created on root * remove unused code * update lyrics for clearer structure. * add support for unsynced lyrics * fix wrong return type in unsynced lyrics * update `/check` to use `send_lyrics` * prefer tags to duplicates * `lyrics.py`: * add docs to a function group * `logger.py`: * add logging config dict. * combine Logging into one file * add socket logger * add debug mode to logger * add JSONL formater * `logger.py`: * update config to directly use the formater. resolves circular import exception `__main__.py`: * add logger setup to main `start_swingmusic.py`: * add debug option to cli * `lyrics.py`: * add offset support * add `setuptools-scm` to get version from git * add support for docker build with scm * add support for docker build with scm need someone who can test the changes workflow * update all usage of `version.txt` to `metadata.version()` * 2x update all usage of `version.txt` to `metadata.version()` * update to no local_scheme version * provide fix for #331. convert `sql.Row` and `TrackTable` to dict before converting to dataclass. * fix `__main__.py`: * wrong import and uncommited changes * add debug and base_path parameter * fix logger pathlib * add client build workflow * set name * split client from build * try fixing builds * try another fix * try also another fix * try again something new * try again something new * change runner * fix failed run because of malformed runner * add wheel builds * remove systems from pure python build * add isolated pyinstaller build * artifacts with names * wrong wheel path * try fetch-depth for tag fetch * disable fail-fast. add wheel installation * add install system packages * add debug * fix wheel install fix pyinstaller spec file * try fix for pyinstaller * try another fix * build on release * add concrete release types * only run on released or pre-released * try release upload * reformat upload * fix needs tag * identifiable pyinstaller builds * compress client folder before uploading * update to src build * remove no more needed aarch64 build script rename pyinstaller assets to lowercase * remove unneeded code * fix: save to DB only unique track hashes * replace click with argparse * set concrete types in argparse * replace manuall path usages with pathlib * remove unused `configs.py` file * reformat `start_swingmusic.py` * fix empty set startup exception * optimizing static files serve function * fixing bug in optimisation of static files serve function * fix folder view bug * colorlib.py: * fix wrong type exception * remove singe use Index_everything class * update logging of populate.py * cleanup files * fix settings.py Paths copy function. Created folder on file. * add exist check to folder * remove unused `INFO` class * fix multiprocessing bug on windows * potential icon fix for pyinstaller fix multiple logging bug * fix argparse config path bug add jobs file * cleanup code fragments fix logging issue add notes to function * note that concurrent creates own sys.modules * refactor some lyrics plugin condition remove unused import from hashing * refactor taglib.py * update import statements to be static * playlistlib.py: * refactoring and more doc strings populate.py: * add poc bugfix settings.py: * add typehint * possible bugfix for multitreading globals * folder.py: * add check if provided path is absolute populate.py: * add bug note settings.py: * add possible error from Singleton implementation start_swingmusic.py: * correct spelling * pass resolved path to Paths tagger.py: * add logging * trying out fixes for multithreading * only upload results not metadata * fix build action again * folder.py: * strictly use pathlib where possible folderlib.py: * add missing docstring to function, who really need it. track.py: * refactor some code folder.py: * refactor some more code * Merge DBPath class and Paths class. Update all usages of DBPath folderslib.py: * fix bug with logging taglib.py: * add missing docstring settings.py: * merge classes * refactor * network.py: * add more docstring config.py: * update pathlib usage tools.py: * refactor * add docstrings * colorlib.py: * add docstring Refactor App builder into grouped config settings. * update assets access for migration * Update FUNDING.yml * Update FUNDING.yml * upgrade tinytag in requirements.txt * update readme * update license * update readme * Update README.md * Update README.md * cleanup requirements.txt remove unused import in audio_segment.py add entrypoint.sh for appimage support update pyproject.toml for optional dependencies add appimage to github workflow * fix invalid workflow file * AppImage build needs more research. Commenting for now * testing a new build workflow * add libev installation * update workflow to new optional dependencies * trying again another fix * finally fix all optional deps installation correctly * remove AppImage poc * albumslib.py: * add docstring folder.py: * add unix path fix update logger name to `__name__` * update build with docker update Dockerfile with git fix typo in lyrics.py add dynamic deps back * add log for static folder * add missing import * add some more todos * add support for AppImages even when it's not perfect. * quick bugfix for wrong appimage config path * fix uploading not finding AppImages builds aka wrong pattern * optimise docker build by using artifacts. Add client path option. change docstring to sphinx format * add todos * Now support AppImages for real: manually build AppImage as we are building a complex project. * fix missing dep in AppImage build * add full AppImage metadata * add missing image file. * only update swingmusic appimage not tool * add todo and fix AppImage build again. * Try fixing some path mixup in AppImage build * add debug tag to action * correct path to appimage folder * do not download tool before checkout * Another fix for path in appimage build * extend config files with more information * default client dir is now inside the config dir. TODOs updated. * default client dir is now inside the config dir. TODOs updated. Add priority todos. * Auto download client when client not found. Respects user provided dir. * rename `requests` submodule to `request` * poc for arm AppImage builds * try out another fix * fix typo in build.yml * add missing arch tag * fix uploading double names * unique naming * enable fallback version for project. * do not download client into readonly dir. * fix relative client download path. Client was resolved into parent of config. * remove client backup path as client is now downloadable * `Paths` checks if config folder exists and creates it if necessary. logger no more creates the config folder. `app_builder.py`: static route no more with '/client' * path are only created in MainProcess. fix gz file not found. * move assets into src and update usages accordingly * remove solved todos * Only upload artefacts if not draft/master aka only on tag * wrong type in assets copy * update log with correct priority * add debug statements and logging to Paths * remove debugging statement * remove double version tag from docker build * fork save release protection * fix typo * add fallback client dir for static builds. * update argparse to new param * add missing import pathlib * add sparse checkout as we do not need everything downloaded * add assets copy check * init logger bevor Paths * remove unused import * check if logdir exists and create if not * only add exec info to file * remove exception log from cli * move logging into main. Allows tools support again. * UserConfig now correctly uses _finished key. Bug where _finished was never written * double save serverId. update root_dir to trow no exception on init. remove debug param * clean up TODOs --------- Co-authored-by: skilletfun <skilletfun.laptew.sergey@yandex.ru> Co-authored-by: Mungai Njoroge <geoffreymungai45@gmail.com>
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
# Pydub [](https://travis-ci.org/jiaaro/pydub) [](https://ci.appveyor.com/project/jiaaro/pydub/branch/master)
|
||||
|
||||
Pydub lets you do stuff to audio in a way that isn't stupid.
|
||||
|
||||
**Stuff you might be looking for**:
|
||||
- [Installing Pydub](https://github.com/jiaaro/pydub#installation)
|
||||
- [API Documentation](https://github.com/jiaaro/pydub/blob/master/API.markdown)
|
||||
- [Dependencies](https://github.com/jiaaro/pydub#dependencies)
|
||||
- [Playback](https://github.com/jiaaro/pydub#playback)
|
||||
- [Setting up ffmpeg](https://github.com/jiaaro/pydub#getting-ffmpeg-set-up)
|
||||
- [Questions/Bugs](https://github.com/jiaaro/pydub#bugs--questions)
|
||||
|
||||
|
||||
## Quickstart
|
||||
|
||||
Open a WAV file
|
||||
|
||||
```python
|
||||
from pydub import AudioSegment
|
||||
|
||||
song = AudioSegment.from_wav("never_gonna_give_you_up.wav")
|
||||
```
|
||||
|
||||
...or a mp3
|
||||
|
||||
```python
|
||||
song = AudioSegment.from_mp3("never_gonna_give_you_up.mp3")
|
||||
```
|
||||
|
||||
... or an ogg, or flv, or [anything else ffmpeg supports](http://www.ffmpeg.org/general.html#File-Formats)
|
||||
|
||||
```python
|
||||
ogg_version = AudioSegment.from_ogg("never_gonna_give_you_up.ogg")
|
||||
flv_version = AudioSegment.from_flv("never_gonna_give_you_up.flv")
|
||||
|
||||
mp4_version = AudioSegment.from_file("never_gonna_give_you_up.mp4", "mp4")
|
||||
wma_version = AudioSegment.from_file("never_gonna_give_you_up.wma", "wma")
|
||||
aac_version = AudioSegment.from_file("never_gonna_give_you_up.aiff", "aac")
|
||||
```
|
||||
|
||||
Slice audio:
|
||||
|
||||
```python
|
||||
# pydub does things in milliseconds
|
||||
ten_seconds = 10 * 1000
|
||||
|
||||
first_10_seconds = song[:ten_seconds]
|
||||
|
||||
last_5_seconds = song[-5000:]
|
||||
```
|
||||
|
||||
Make the beginning louder and the end quieter
|
||||
|
||||
```python
|
||||
# boost volume by 6dB
|
||||
beginning = first_10_seconds + 6
|
||||
|
||||
# reduce volume by 3dB
|
||||
end = last_5_seconds - 3
|
||||
```
|
||||
|
||||
Concatenate audio (add one file to the end of another)
|
||||
|
||||
```python
|
||||
without_the_middle = beginning + end
|
||||
```
|
||||
|
||||
How long is it?
|
||||
|
||||
```python
|
||||
without_the_middle.duration_seconds == 15.0
|
||||
```
|
||||
|
||||
AudioSegments are immutable
|
||||
|
||||
```python
|
||||
# song is not modified
|
||||
backwards = song.reverse()
|
||||
```
|
||||
|
||||
Crossfade (again, beginning and end are not modified)
|
||||
|
||||
```python
|
||||
# 1.5 second crossfade
|
||||
with_style = beginning.append(end, crossfade=1500)
|
||||
```
|
||||
|
||||
Repeat
|
||||
|
||||
```python
|
||||
# repeat the clip twice
|
||||
do_it_over = with_style * 2
|
||||
```
|
||||
|
||||
Fade (note that you can chain operations because everything returns
|
||||
an AudioSegment)
|
||||
|
||||
```python
|
||||
# 2 sec fade in, 3 sec fade out
|
||||
awesome = do_it_over.fade_in(2000).fade_out(3000)
|
||||
```
|
||||
|
||||
Save the results (again whatever ffmpeg supports)
|
||||
|
||||
```python
|
||||
awesome.export("mashup.mp3", format="mp3")
|
||||
```
|
||||
|
||||
Save the results with tags (metadata)
|
||||
|
||||
```python
|
||||
awesome.export("mashup.mp3", format="mp3", tags={'artist': 'Various artists', 'album': 'Best of 2011', 'comments': 'This album is awesome!'})
|
||||
```
|
||||
|
||||
You can pass an optional bitrate argument to export using any syntax ffmpeg
|
||||
supports.
|
||||
|
||||
```python
|
||||
awesome.export("mashup.mp3", format="mp3", bitrate="192k")
|
||||
```
|
||||
|
||||
Any further arguments supported by ffmpeg can be passed as a list in a
|
||||
'parameters' argument, with switch first, argument second. Note that no
|
||||
validation takes place on these parameters, and you may be limited by what
|
||||
your particular build of ffmpeg/avlib supports.
|
||||
|
||||
```python
|
||||
# Use preset mp3 quality 0 (equivalent to lame V0)
|
||||
awesome.export("mashup.mp3", format="mp3", parameters=["-q:a", "0"])
|
||||
|
||||
# Mix down to two channels and set hard output volume
|
||||
awesome.export("mashup.mp3", format="mp3", parameters=["-ac", "2", "-vol", "150"])
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
Most issues people run into are related to converting between formats using
|
||||
ffmpeg/avlib. Pydub provides a logger that outputs the subprocess calls to
|
||||
help you track down issues:
|
||||
|
||||
```python
|
||||
>>> import logging
|
||||
|
||||
>>> l = logging.getLogger("pydub.converter")
|
||||
>>> l.setLevel(logging.DEBUG)
|
||||
>>> l.addHandler(logging.StreamHandler())
|
||||
|
||||
>>> AudioSegment.from_file("./test/data/test1.mp3")
|
||||
subprocess.call(['ffmpeg', '-y', '-i', '/var/folders/71/42k8g72x4pq09tfp920d033r0000gn/T/tmpeZTgMy', '-vn', '-f', 'wav', '/var/folders/71/42k8g72x4pq09tfp920d033r0000gn/T/tmpK5aLcZ'])
|
||||
<pydub.audio_segment.AudioSegment object at 0x101b43e10>
|
||||
```
|
||||
|
||||
Don't worry about the temporary files used in the conversion. They're cleaned up
|
||||
automatically.
|
||||
|
||||
## Bugs & Questions
|
||||
|
||||
You can file bugs in our [github issues tracker](https://github.com/jiaaro/pydub/issues),
|
||||
and ask any technical questions on
|
||||
[Stack Overflow using the pydub tag](http://stackoverflow.com/questions/ask?tags=pydub).
|
||||
We keep an eye on both.
|
||||
|
||||
## Installation
|
||||
|
||||
Installing pydub is easy, but don't forget to install ffmpeg/avlib (the next section in this doc)
|
||||
|
||||
pip install pydub
|
||||
|
||||
Or install the latest dev version from github (or replace `@master` with a [release version like `@v0.12.0`](https://github.com/jiaaro/pydub/releases))…
|
||||
|
||||
pip install git+https://github.com/jiaaro/pydub.git@master
|
||||
|
||||
-OR-
|
||||
|
||||
git clone https://github.com/jiaaro/pydub.git
|
||||
|
||||
-OR-
|
||||
|
||||
Copy the pydub directory into your python path. Zip
|
||||
[here](https://github.com/jiaaro/pydub/zipball/master)
|
||||
|
||||
## Dependencies
|
||||
|
||||
You can open and save WAV files with pure python. For opening and saving non-wav
|
||||
files – like mp3 – you'll need [ffmpeg](http://www.ffmpeg.org/) or
|
||||
[libav](http://libav.org/).
|
||||
|
||||
### Playback
|
||||
|
||||
You can play audio if you have one of these installed (simpleaudio _strongly_ recommended, even if you are installing ffmpeg/libav):
|
||||
|
||||
- [simpleaudio](https://simpleaudio.readthedocs.io/en/latest/)
|
||||
- [pyaudio](https://people.csail.mit.edu/hubert/pyaudio/docs/#)
|
||||
- ffplay (usually bundled with ffmpeg, see the next section)
|
||||
- avplay (usually bundled with libav, see the next section)
|
||||
|
||||
```python
|
||||
from pydub import AudioSegment
|
||||
from pydub.playback import play
|
||||
|
||||
sound = AudioSegment.from_file("mysound.wav", format="wav")
|
||||
play(sound)
|
||||
```
|
||||
|
||||
## Getting ffmpeg set up
|
||||
|
||||
You may use **libav or ffmpeg**.
|
||||
|
||||
Mac (using [homebrew](http://brew.sh)):
|
||||
|
||||
```bash
|
||||
# libav
|
||||
brew install libav
|
||||
|
||||
#### OR #####
|
||||
|
||||
# ffmpeg
|
||||
brew install ffmpeg
|
||||
```
|
||||
|
||||
Linux (using aptitude):
|
||||
|
||||
```bash
|
||||
# libav
|
||||
apt-get install libav-tools libavcodec-extra
|
||||
|
||||
#### OR #####
|
||||
|
||||
# ffmpeg
|
||||
apt-get install ffmpeg libavcodec-extra
|
||||
```
|
||||
|
||||
Windows:
|
||||
|
||||
1. Download and extract libav from [Windows binaries provided here](http://builds.libav.org/windows/).
|
||||
2. Add the libav `/bin` folder to your PATH envvar
|
||||
3. `pip install pydub`
|
||||
|
||||
## Important Notes
|
||||
|
||||
`AudioSegment` objects are [immutable](http://www.devshed.com/c/a/Python/String-and-List-Python-Object-Types/1/)
|
||||
|
||||
|
||||
### Ogg exporting and default codecs
|
||||
|
||||
The Ogg specification ([http://tools.ietf.org/html/rfc5334](rfc5334)) does not specify
|
||||
the codec to use, this choice is left up to the user. Vorbis and Theora are just
|
||||
some of a number of potential codecs (see page 3 of the rfc) that can be used for the
|
||||
encapsulated data.
|
||||
|
||||
When no codec is specified exporting to `ogg` will _default_ to using `vorbis`
|
||||
as a convenience. That is:
|
||||
|
||||
```python
|
||||
from pydub import AudioSegment
|
||||
song = AudioSegment.from_mp3("test/data/test1.mp3")
|
||||
song.export("out.ogg", format="ogg") # Is the same as:
|
||||
song.export("out.ogg", format="ogg", codec="libvorbis")
|
||||
```
|
||||
|
||||
## Example Use
|
||||
|
||||
Suppose you have a directory filled with *mp4* and *flv* videos and you want to convert all of them to *mp3* so you can listen to them on your mp3 player.
|
||||
|
||||
```python
|
||||
import os
|
||||
import glob
|
||||
from pydub import AudioSegment
|
||||
|
||||
video_dir = '/home/johndoe/downloaded_videos/' # Path where the videos are located
|
||||
extension_list = ('*.mp4', '*.flv')
|
||||
|
||||
os.chdir(video_dir)
|
||||
for extension in extension_list:
|
||||
for video in glob.glob(extension):
|
||||
mp3_filename = os.path.splitext(os.path.basename(video))[0] + '.mp3'
|
||||
AudioSegment.from_file(video).export(mp3_filename, format='mp3')
|
||||
```
|
||||
|
||||
### How about another example?
|
||||
|
||||
```python
|
||||
from glob import glob
|
||||
from pydub import AudioSegment
|
||||
|
||||
playlist_songs = [AudioSegment.from_mp3(mp3_file) for mp3_file in glob("*.mp3")]
|
||||
|
||||
first_song = playlist_songs.pop(0)
|
||||
|
||||
# let's just include the first 30 seconds of the first song (slicing
|
||||
# is done by milliseconds)
|
||||
beginning_of_song = first_song[:30*1000]
|
||||
|
||||
playlist = beginning_of_song
|
||||
for song in playlist_songs:
|
||||
|
||||
# We don't want an abrupt stop at the end, so let's do a 10 second crossfades
|
||||
playlist = playlist.append(song, crossfade=(10 * 1000))
|
||||
|
||||
# let's fade out the end of the last song
|
||||
playlist = playlist.fade_out(30)
|
||||
|
||||
# hmm I wonder how long it is... ( len(audio_segment) returns milliseconds )
|
||||
playlist_length = len(playlist) / (1000*60)
|
||||
|
||||
# lets save it!
|
||||
with open("%s_minute_playlist.mp3" % playlist_length, 'wb') as out_f:
|
||||
playlist.export(out_f, format='mp3')
|
||||
```
|
||||
|
||||
## License ([MIT License](http://opensource.org/licenses/mit-license.php))
|
||||
|
||||
Copyright © 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.
|
||||
|
||||
Reference in New Issue
Block a user