mirror of
https://github.com/rwinkhart/flac2pod.git
synced 2026-09-01 14:47:18 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c32b16598a | ||
|
|
3865e91517 | ||
|
|
7411af721d | ||
|
|
ee3e1f9fa1 | ||
|
|
91cea901be | ||
|
|
1d59ed62ac | ||
|
|
572b997286 |
@@ -1,27 +1,41 @@
|
||||
# flac2pod
|
||||
|
||||
Converts your existing FLAC library to be played on an iPod Classic.
|
||||
|
||||
This script makes use of ffmpeg to convert your existing FLAC music library to AAC-256 for easy iPod Classic use. It can also bake your existing ReplayGain data into the output files. SoundCheck support was removed due to this feature being very finicky on iPods. If you would like to add SoundCheck data to your output files, omit the --bake option and then use https://github.com/rwinkhart/rg2sc on your output files.
|
||||
|
||||
In order for ReplayGain baking to work, your FLAC library must already be tagged with ReplayGain data. If you haven't already done this, flac2pod comes with a script (flac2pod-flacgain) for quickly applying these ReplayGain tags.
|
||||
|
||||
# WARNING
|
||||
|
||||
This is an older project of mine that is only updated to fix bugs and maintain compatibility. It is written rather poorly, but it does what it says on the tin, so I will not be investing time into rewriting it.
|
||||
|
||||
# Usage
|
||||
|
||||
Simply run...
|
||||
|
||||
```
|
||||
flac2pod <source_dir> <destination_dir>
|
||||
```
|
||||
|
||||
...to convert your library, or run...
|
||||
|
||||
```
|
||||
flac2pod --help
|
||||
```
|
||||
|
||||
...to see additional options, or run...
|
||||
|
||||
```
|
||||
flac2pod-flacgain <source_dir>
|
||||
```
|
||||
|
||||
...to tag your FLAC library with ReplayGain data, or run...
|
||||
|
||||
```
|
||||
flac2pod-artconvert <source_dir>
|
||||
```
|
||||
|
||||
...to convert embedded PNGs (in MP4/M4A files) to iPod-ready JPEGs.
|
||||
|
||||
In order for flac2pod to function, your source_dir must be structured as follows:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
git add -f LICENSE README.md commit.sh package.sh share lib/flac2pod-artconvert.py lib/flac2pod-flacgain.py lib/flac2pod.py
|
||||
git commit -m "$1"
|
||||
git push
|
||||
+21
-21
@@ -2,8 +2,9 @@
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from flac2pod import scan_source
|
||||
from glob import glob
|
||||
from mutagen.flac import FLAC, FLACNoHeaderError
|
||||
from os import listdir, system
|
||||
from os import listdir
|
||||
from subprocess import PIPE, run
|
||||
from sys import exit as s_exit
|
||||
|
||||
@@ -25,33 +26,35 @@ if __name__ == "__main__":
|
||||
|
||||
gain = 1
|
||||
for album in sorted(source_directories[0]):
|
||||
std_album = album.replace(' ', '\\ ').replace("'", "\\'").replace(')', '\\)').replace('(', '\\(')\
|
||||
.replace(']', '\\]').replace('[', '\\[').replace('&', '\\&').replace('`', '\\`').replace('$', '\\$')
|
||||
for song in listdir(album):
|
||||
try:
|
||||
audio = FLAC(f"{album}/{song}")
|
||||
if audio.get('replaygain_track_gain') or audio.get('REPLAYGAIN_TRACK_GAIN'):
|
||||
gain = 1
|
||||
else:
|
||||
gain = 0
|
||||
break
|
||||
except FLACNoHeaderError:
|
||||
gain = 1 # reset for each song
|
||||
if not song.lower().endswith('.flac'):
|
||||
gain = 2 # set non-flac signal
|
||||
else:
|
||||
try:
|
||||
audio = FLAC(f"{album}/{song}")
|
||||
if audio.get('replaygain_track_gain') or audio.get('REPLAYGAIN_TRACK_GAIN'):
|
||||
print(f"[{album}/*] ReplayGain data is already present.")
|
||||
break
|
||||
else:
|
||||
gain = 0
|
||||
except FLACNoHeaderError:
|
||||
gain = 2 # set non-flac signal
|
||||
if gain == 2:
|
||||
print(f"[{album}/*] Contains non-FLAC files, skipping album...")
|
||||
gain = 2
|
||||
break
|
||||
if gain != 2 and (args.force or gain == 0):
|
||||
print(f"[{album}/*] Adding ReplayGain data...")
|
||||
system(f"metaflac --remove-replay-gain {std_album}/*")
|
||||
output = run(f"metaflac --add-replay-gain {std_album}/*", shell=True, stderr=PIPE, text=True)
|
||||
album_files = glob(album + '/*')
|
||||
run(['metaflac', '--remove-replay-gain'] + album_files)
|
||||
output = run(['metaflac', '--add-replay-gain'] + album_files, stderr=PIPE, text=True)
|
||||
if output.returncode == 1:
|
||||
if output.stderr.__contains__('sample') or output.stderr.__contains__('resolution of') or output.\
|
||||
stderr.__contains__('does not match'):
|
||||
print('Resolution/Sample Rate/Channel mismatch, scanning tracks as individuals...')
|
||||
for song in listdir(album):
|
||||
print(f"[{album}/{song}] Adding ReplayGain data...")
|
||||
std_song = song.replace(' ', '\\ ').replace("'", "\\'").replace(')', '\\)')\
|
||||
.replace('(', '\\(').replace('&', '\\&').replace('`', '\\`').replace('$', '\\$')
|
||||
output = run(f"metaflac --add-replay-gain {std_album}/{std_song}", shell=True, stderr=PIPE,
|
||||
text=True)
|
||||
output = run(('metaflac', '--add-replay-gain', f"{album}/{song}"), stderr=PIPE, text=True)
|
||||
if output.returncode == 1:
|
||||
print('There was an error processing your files.')
|
||||
print(f"Return Code: [{str(output.returncode)}] {output.stderr}")
|
||||
@@ -60,6 +63,3 @@ if __name__ == "__main__":
|
||||
print('There was an error processing your files.')
|
||||
print(f"Return Code: [{str(output.returncode)}] {output.stderr}")
|
||||
s_exit(1)
|
||||
|
||||
else:
|
||||
print(f"[{album}/*] ReplayGain data is already present.")
|
||||
|
||||
+42
-36
@@ -1,19 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# external modules
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from mutagen.flac import FLAC
|
||||
from os import cpu_count, path, walk
|
||||
from os import cpu_count, getuid, listdir, path, stat, walk
|
||||
from pathlib import Path
|
||||
from shutil import copy
|
||||
from subprocess import check_output, Popen
|
||||
from subprocess import DEVNULL, Popen
|
||||
from sys import exit as s_exit
|
||||
from time import sleep
|
||||
|
||||
|
||||
# utility functions
|
||||
|
||||
# scans source directory for music files, returns lists of album directories and their full paths
|
||||
def scan_source(_source):
|
||||
_artist_dirs, _album_dirs, _full_paths = [], [], []
|
||||
for _root, _directories, _files in walk(path.expanduser(_source)):
|
||||
@@ -29,18 +26,34 @@ def scan_source(_source):
|
||||
for _album in sorted(_album_dirs):
|
||||
for _root, _directories, _files in walk(path.expanduser(_album)):
|
||||
for _filename in _files:
|
||||
if _filename.endswith('.flac') or _filename.endswith('.mp3') or _filename.endswith('.mp4') \
|
||||
or _filename.endswith('.m4a'):
|
||||
if _filename.lower().endswith('.flac') or _filename.lower().endswith('.mp3') or \
|
||||
_filename.lower().endswith('.mp4') or _filename.lower().endswith('.m4a'):
|
||||
_full_paths.append(f"{_album}/{_filename}")
|
||||
return _album_dirs, _full_paths
|
||||
|
||||
|
||||
# creates destination directory structure
|
||||
def create_destination():
|
||||
for _dir in source_directories[0]:
|
||||
Path(path.expanduser(_dir.replace(path.expanduser(source), path.expanduser(destination))))\
|
||||
.mkdir(0o700, parents=True, exist_ok=True)
|
||||
|
||||
|
||||
# counts how many ffmpeg processes are running under the current user
|
||||
def count_user_ffmpeg_processes():
|
||||
_user = getuid()
|
||||
_proc_directory = '/proc'
|
||||
_running_processes = 0
|
||||
_subdirectories = [_d for _d in listdir(_proc_directory) if path.isdir(path.join(_proc_directory, _d))]
|
||||
for _subdirectory in _subdirectories:
|
||||
if _subdirectory.isnumeric():
|
||||
if stat(_proc_directory + '/' + _subdirectory).st_uid == _user:
|
||||
with open(_proc_directory + '/' + _subdirectory + '/cmdline', 'r') as _f:
|
||||
if _f.read().startswith('ffmpeg'):
|
||||
_running_processes += 1
|
||||
return _running_processes
|
||||
|
||||
|
||||
def scan_destination_convert():
|
||||
_i, _i_total, _total_files = 0, 0, len(source_directories[1])
|
||||
for _file in source_directories[1]:
|
||||
@@ -48,25 +61,19 @@ def scan_destination_convert():
|
||||
_progress = round((_i_total / _total_files) * 100, 2)
|
||||
if not Path(f"{(_file.replace(path.expanduser(source), path.expanduser(destination)))[:-5]}.m4a").is_file()\
|
||||
and not Path(f"{(_file.replace(path.expanduser(source), path.expanduser(destination)))}").is_file():
|
||||
if not _file.endswith('.flac'):
|
||||
if not _file.lower().endswith('.flac'):
|
||||
print(f"[{_file}] Not a FLAC file, copying...")
|
||||
copy(f"{_file}", f"{_file.replace(path.expanduser(source), path.expanduser(destination))}")
|
||||
else:
|
||||
_file_s = _file.replace(' ', '\\ ').replace("'", "\\'").replace(')', '\\)').replace('(', '\\(') \
|
||||
.replace(']', '\\]').replace('[', '\\[').replace('&', '\\&').replace('`', '\\`') \
|
||||
.replace('$', '\\$')
|
||||
_file_d = _file.replace(path.expanduser(source), path.expanduser(destination)).replace(' ', '\\ ') \
|
||||
.replace("'", "\\'").replace(')', '\\)').replace('(', '\\(').replace(']', '\\]') \
|
||||
.replace('[', '\\[').replace('&', '\\&').replace('`', '\\`').replace('$', '\\$')[
|
||||
:-5] + '.m4a'
|
||||
_active_processes = int(check_output('ps -C ffmpeg | wc -l', shell=True)) - 1
|
||||
if _active_processes >= cpu_count():
|
||||
while _active_processes >= cpu_count():
|
||||
print(f"\nThere are already {cpu_count()} processes running...\n")
|
||||
sleep(1)
|
||||
_active_processes = int(check_output('ps -C ffmpeg | wc -l', shell=True)) - 1
|
||||
_file_d = _file.replace(path.expanduser(source), path.expanduser(destination))[:-5] + '.m4a'
|
||||
_active_processes = count_user_ffmpeg_processes()
|
||||
while _active_processes >= cpu_count():
|
||||
print(f"\nThere are already {_active_processes} processes running on your {cpu_count()} "
|
||||
f"threads...\n")
|
||||
sleep(1)
|
||||
_active_processes = count_user_ffmpeg_processes()
|
||||
_i += 1
|
||||
print(f"\n{_progress}% | \u001b[38;5;0;48;5;15mConverting {_file_s} to {_file_d}...\u001b[0m\n")
|
||||
print(f"\n{_progress}% | \u001b[38;5;0;48;5;15mConverting {_file} to {_file_d}...\u001b[0m\n")
|
||||
# get relevant ReplayGain info
|
||||
_audio = FLAC(f"{_file}")
|
||||
if args.albumgain:
|
||||
@@ -97,31 +104,30 @@ def scan_destination_convert():
|
||||
_rgpeak = None
|
||||
# determine ffmpeg arguments
|
||||
if args.bake and _rggain is not None:
|
||||
_bake_args, _flac2pod_rg_tags = '-filter:a "volume=' + str(_rggain) + 'dB"', ''
|
||||
_bake_args, _flac2pod_rg_tags = ['-filter:a', 'volume=' + str(_rggain) + 'dB'], []
|
||||
elif _rggain is not None and _rgpeak is not None:
|
||||
_bake_args, _flac2pod_rg_tags = '', f"-metadata comment='FLAC2PODRG#{_rggain}#{_rgpeak}#'"
|
||||
_bake_args, _flac2pod_rg_tags = [], ['-metadata', f"comment=FLAC2PODRG#{_rggain}#{_rgpeak}#"]
|
||||
else:
|
||||
_bake_args, _flac2pod_rg_tags = '', ''
|
||||
_bake_args, _flac2pod_rg_tags = [], []
|
||||
if args.preserve:
|
||||
_art_args = '-c:v copy -map_metadata 0:g'
|
||||
_art_args = ['-c:v', 'copy', '-map_metadata', '0:g']
|
||||
else:
|
||||
_art_args = '-vn'
|
||||
_art_args = ['-vn']
|
||||
# generate the appropriate ffmpeg command
|
||||
_cmd = f"screen -DmS flac2pod{_i} ffmpeg -i {_file_s} {_art_args} -c:a aac -b:a 256k {_bake_args} " \
|
||||
f"{_flac2pod_rg_tags} -aac_pns 0 -movflags +faststart {_file_d} </dev/null"
|
||||
_cmd = ['ffmpeg', '-i', _file] + _art_args + ['-c:a', 'aac', '-b:a', '256k'] + _bake_args +\
|
||||
_flac2pod_rg_tags + ['-aac_pns', '0', '-movflags', '+faststart', _file_d]
|
||||
if _rggain is not None:
|
||||
print(f"\u001b[38;5;0;48;5;15mGain adjustment: {_rggain}dB\u001b[0m")
|
||||
else:
|
||||
print(f"\u001b[38;5;0;48;5;88mNo ReplayGain data found!\u001b[0m")
|
||||
print('\u001b[38;5;0;48;5;88mNo ReplayGain data found!\u001b[0m')
|
||||
if args.stopifnogain:
|
||||
s_exit(1)
|
||||
Popen(_cmd, shell=True)
|
||||
_active_processes = int(check_output('ps -C ffmpeg | wc -l', shell=True)) - 1
|
||||
Popen(_cmd, stdout=DEVNULL, stderr=DEVNULL)
|
||||
_active_processes = count_user_ffmpeg_processes()
|
||||
while _active_processes != 0:
|
||||
print('\nPlease wait for the remaining conversions to complete...\n')
|
||||
print(f"\nPlease wait for the remaining {_active_processes} conversions to complete...\n")
|
||||
sleep(1)
|
||||
_active_processes = int(check_output('ps -C ffmpeg | wc -l', shell=True)) - 1
|
||||
sleep(1)
|
||||
_active_processes = count_user_ffmpeg_processes()
|
||||
print('\niPod conversion complete!\n')
|
||||
s_exit(0)
|
||||
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ pkgdesc='Converts your FLAC library to be iPod-ready'
|
||||
url='https://github.com/rwinkhart/flac2pod'
|
||||
arch=('any')
|
||||
license=('GPL2')
|
||||
depends=(ffmpeg flac python python-mutagen python-pillow screen)
|
||||
depends=(ffmpeg flac python python-mutagen python-pillow)
|
||||
source=(\""$source"\")
|
||||
sha512sums=('"$sha512"')
|
||||
|
||||
|
||||
Executable → Regular
Reference in New Issue
Block a user