Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch from Python-Audio-Tools to Pydub and m4a support #13

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ junit-*.xml

# Sphinx documentation
docs/_build/

#VSCode stuff
.vscode/
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ language: python
addons:
apt:
packages:
- lame
- libmp3lame-dev
- libmpg123-dev
- libvorbis-dev
- ffmpeg
- libavcodec-extra
install: pip install tox
script: tox

Expand All @@ -17,6 +15,8 @@ jobs:
env: TOXENV=py37
- python: 3.8
env: TOXENV=py38
- python: 3.9
env: TOXENV=py39
install: pip install tox coveralls
after_success: coveralls
- stage: deploy
Expand Down
24 changes: 7 additions & 17 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,14 @@ paths in addition to the FAT32 filename adaptations.
Dependencies
------------

- Python 3.5
- `Python Audio Tools`_ >= 3.0 (for transcoding to MP3)
- Python >=3.5
- Pydub_ >= 0.25.1 (for transcoding to MP3)
- Mutagen_ >= 1.29 (for tag manipulation)

Installation
------------

The first step is to install `Python Audio Tools`_ which depends on a couple of
native libraries and doesn't offer a PyPI package. On Ubuntu 16.04 or later
there's an official package that can simply be installed using::

# apt install audiotools

As an alternative `Python Audio Tools`_ can be installed from source after the
necessary native libraries are installed::

# apt install python3-dev lame libmp3lame-dev libmpg123-dev libvorbis-dev
# pip3 install https://github.com/tuffy/python-audio-tools/archive/master.zip
The first step is to install FFmpeg_ and libavcodec-extra.

Then *sync_music* can be installed from PyPI with::

Expand Down Expand Up @@ -97,16 +87,15 @@ the destination)::
sync_music --audio-src=<FOLDER> --audio-dest=<FOLDER> --mode=transcode

Transcoding MP3 files can lead to significantly smaller files if the source
contains many 320kbps CBR MP3s as the target rate is 190kbps VBR. The drawback
contains many 320kbps CBR MP3s as the target rate is 192kbps ABR. The drawback
is that transcoding is slower and needs more CPU power.

The *replaygain* and *replaygain-album* modes apply (track or album) based
volume normalization from ReplayGain_ tags when transcoding::

sync_music --audio-src=<FOLDER> --audio-dest=<FOLDER> --mode=replaygain

Transcoding modes require that the MP3 files can be decoded by `Python
Audio Tools`_ without issues. Problematic input files can be analyzed and fixed
Transcoding modes require that the MP3 files can be decoded by FFmpeg_ without issues. Problematic input files can be analyzed and fixed
for example with `MP3 Diags`_.

Hacks
Expand Down Expand Up @@ -149,7 +138,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
`GNU General Public License <http://www.gnu.org/licenses/gpl-2.0.html>`_
for more details.

.. _`Python Audio Tools`: http://audiotools.sourceforge.net
.. _Pydub: https://github.com/jiaaro/pydub/
.. _`MP3 Diags`: http://mp3diags.sourceforge.net
.. _Mutagen: https://mutagen.readthedocs.io
.. _ReplayGain: https://en.wikipedia.org/wiki/ReplayGain
.. _FFmpeg: https://ffmpeg.org/
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@

mutagen>=1.29
pbr
pydub>=0.25.1
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ packages = sync_music
[entry_points]
console_scripts =
sync_music = sync_music.sync_music:main

[flake8]
max-line-length = 99
15 changes: 6 additions & 9 deletions sync_music/hashdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,24 @@ def load(self):
"""Load hash database to disk."""
if os.path.exists(self.path):
logger.info("Loading hash database from {}", self.path)
hash_file = open(self.path, 'rb')
self.database = pickle.load(hash_file, encoding="utf-8")
hash_file.close()
with open(self.path, 'rb') as hash_file:
self.database = pickle.load(hash_file, encoding="utf-8")
else:
logger.info("No hash database file {}", self.path)

def store(self):
"""Store hash database to disk."""
logger.info("Storing hash database to {}", self.path)
try:
hash_file = open(self.path, 'wb')
pickle.dump(self.database, hash_file)
hash_file.close()
with open(self.path, 'wb') as hash_file:
pickle.dump(self.database, hash_file)
except IOError:
logger.error("Error: Failed to write hash database to {}",
self.path)

@classmethod
def get_hash(cls, path):
"""Calculate hash value for the given path."""
hash_file = open(path, 'rb')
hash_buffer = hash_file.read(4096)
hash_file.close()
with open(path, 'rb') as hash_file:
hash_buffer = hash_file.read(4096)
return hashlib.md5(hash_buffer).hexdigest()
42 changes: 20 additions & 22 deletions sync_music/sync_music.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def _process_file(self, current_file):
def _get_file_action(self, in_filename):
"""Determine the action for the given file."""
extension = os.path.splitext(in_filename)[1]
if extension in ['.flac', '.ogg', '.mp3']:
if extension in ['.flac', '.ogg', '.mp3', '.m4a']:
if self._args.mode == 'copy':
return self._action_copy
return self._action_transcode
Expand Down Expand Up @@ -174,8 +174,8 @@ def sync_audio(self):
for current_file in files:
file_hashes.append(self._process_file(current_file))
else:
pool = Pool(processes=self._args.jobs)
file_hashes = pool.map(self._process_file, files)
with Pool(processes=self._args.jobs) as pool:
file_hashes = pool.map(self._process_file, files)
except: # noqa, pylint: disable=bare-except
logger.error(">>> traceback <<<")
logger.exception("Exception")
Expand Down Expand Up @@ -211,25 +211,23 @@ def _sync_playlist(self, filename):
os.remove(destpath)

# Copy file
in_file = codecs.open(srcpath, 'r', encoding='windows-1252')
out_file = codecs.open(destpath, 'w', encoding='windows-1252')
for line in in_file.read().splitlines():
if not line.startswith('#EXT'):
in_filename = line
try:
while True:
if in_filename in self._hashdb.database:
line = self._hashdb.database[in_filename][0]
line = line.replace('/', '\\')
break
in_filename = in_filename.split('/', 1)[1]
except IndexError:
logger.warning("File does not exist: {}", line)
continue
line = line + '\r\n'
out_file.write(line)
in_file.close()
out_file.close()
with codecs.open(srcpath, 'r', encoding='windows-1252') as in_file:
with codecs.open(destpath, 'w', encoding='windows-1252') as out_file:
for line in in_file.read().splitlines():
if not line.startswith('#EXT'):
in_filename = line
try:
while True:
if in_filename in self._hashdb.database:
line = self._hashdb.database[in_filename][0]
line = line.replace('/', '\\')
break
in_filename = in_filename.split('/', 1)[1]
except IndexError:
logger.warning("File does not exist: {}", line)
continue
line = line + '\r\n'
out_file.write(line)


def load_settings(arguments=None): # pylint: disable=too-many-locals
Expand Down
Loading