14 Commits
6 changed files with 201 additions and 199 deletions
+2 -4
View File
@@ -26,8 +26,6 @@ source_dir -> artist dir -> album dir -> FLAC files
# Installation
flac2pod is available in the AUR as "flac2pod". If installing on a non-Arch-based distribution, follow the manual installation instructions:
flac2pod is available in the AUR as "flac2pod".
- copy the contents of https://github.com/rwinkhart/flac2pod/blob/master/bin/ to your /usr/bin/
- install flac, python-mutagen, ffmpeg, and screen
- start converting
If installing on a non-Arch-based distribution, a generic package is available on the releases page.
-146
View File
@@ -1,146 +0,0 @@
#!/bin/python3
# external modules
from argparse import ArgumentParser
from mutagen.flac import FLAC
from os import cpu_count, path, walk
from pathlib import Path
from subprocess import check_output, Popen
from sys import exit as s_exit
from time import sleep
# utility functions
def scan_source(_source):
_artist_dirs, _album_dirs, _full_paths = [], [], []
for _root, _directories, _files in walk(path.expanduser(_source)):
for _dir in sorted(_directories):
if not _root.endswith(path.expanduser(_source)):
_artist_dirs.append(_root)
_artist_dirs = list(set(_artist_dirs))
for _artist in sorted(_artist_dirs):
for _root, _directories, _files in walk(path.expanduser(_artist)):
if not _root.endswith(path.expanduser(_artist)):
_album_dirs.append(_root)
_album_dirs = list(set(_album_dirs))
for _album in sorted(_album_dirs):
for _root, _directories, _files in walk(path.expanduser(_album)):
for _filename in _files:
if _filename.endswith('.flac'):
_full_paths.append(f"{_album}/{_filename[:-5]}")
return _album_dirs, _full_paths
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)
def scan_destination_convert():
_i, _i_total, _total_files = 0, 0, len(source_directories[1])
for _file in source_directories[1]:
_i_total += 1
_progress = round((_i_total / _total_files) * 100, 2)
if not Path(f"{_file.replace(path.expanduser(source), path.expanduser(destination))}.mp3").is_file() and not \
Path(f"{_file.replace(path.expanduser(source), path.expanduser(destination))}.m4a").is_file():
_file_s = _file.replace(' ', '\\ ').replace("'", "\\'").replace(')', '\\)').replace('(', '\\(')\
.replace('&', '\\&').replace('`', '\\`').replace('$', '\\$') + '.flac'
_file_d = _file.replace(path.expanduser(source), path.expanduser(destination)).replace(' ', '\\ ')\
.replace("'", "\\'").replace(')', '\\)').replace('(', '\\(').replace('&', '\\&')\
.replace('`', '\\`').replace('$', '\\$') + '.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
_i += 1
print(f"\n{_progress}% | \u001b[38;5;0;48;5;15mConverting {_file_s} to {_file_d}...\u001b[0m\n")
# get relevant ReplayGain info
_audio = FLAC(f"{_file}.flac")
if args.albumgain:
if _audio.get('replaygain_album_gain'):
_rggain = float(_audio.get('replaygain_album_gain')[0][:-3])
elif _audio.get('REPLAYGAIN_ALBUM_GAIN'):
_rggain = float(_audio.get('REPLAYGAIN_ALBUM_GAIN')[0][:-3])
else:
_rggain = None
if _audio.get('replaygain_album_peak'):
_rgpeak = float(_audio.get('replaygain_album_peak')[0])
elif _audio.get('REPLAYGAIN_ALBUM_PEAK'):
_rgpeak = float(_audio.get('REPLAYGAIN_ALBUM_PEAK')[0])
else:
_rgpeak = None
else:
if _audio.get('replaygain_track_gain'):
_rggain = float(_audio.get('replaygain_track_gain')[0][:-3])
elif _audio.get('REPLAYGAIN_TRACK_GAIN'):
_rggain = float(_audio.get('REPLAYGAIN_TRACK_GAIN')[0][:-3])
else:
_rggain = None
if _audio.get('replaygain_track_peak'):
_rgpeak = float(_audio.get('replaygain_track_peak')[0])
elif _audio.get('REPLAYGAIN_TRACK_PEAK'):
_rgpeak = float(_audio.get('REPLAYGAIN_TRACK_PEAK')[0])
else:
_rgpeak = None
# determine ffmpeg arguments
if args.bake and _rggain is not None:
_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}#'"
else:
_bake_args, _flac2pod_rg_tags = '', ''
# generate the appropriate ffmpeg command
_cmd = f"screen -DmS flac2pod{_i} ffmpeg -i {_file_s} -c:a aac -ab 256k {_bake_args} -map_metadata 0 " \
f"{_flac2pod_rg_tags} -aac_pns 0 -movflags +faststart -vn {_file_d} </dev/null"
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")
if args.stopifnogain:
s_exit(1)
Popen(_cmd, shell=True)
_active_processes = int(check_output('ps -C ffmpeg | wc -l', shell=True)) - 1
while _active_processes != 0:
print('\nPlease wait for the remaining conversions to complete...\n')
sleep(1)
_active_processes = int(check_output('ps -C ffmpeg | wc -l', shell=True)) - 1
sleep(1)
print('\niPod conversion complete!\n')
s_exit(0)
# argument parsing
if __name__ == "__main__":
parser = ArgumentParser(description='Convert your existing FLAC library to be played on an iPod Classic.')
parser.add_argument('source_dir', nargs='+',
help='directory tree containing music files - folder must contain library sorted into artists '
'and albums')
parser.add_argument('destination_dir', nargs='+',
help='destination parent directory for output - will be created if it does not already exist')
parser.add_argument('-b', '--bake', action='store_true',
help='bake (encode) ReplayGain values into the file')
parser.add_argument('-a', '--albumgain', action='store_true',
help='read ReplayGain album gain instead of ReplayGain track gain')
parser.add_argument('-x', '--stopifnogain', action='store_true',
help='stop flac2pod if ReplayGain data cannot be found in a file')
args = parser.parse_args()
if args.source_dir[0].endswith('/'):
source = args.source_dir[0][:-1]
else:
source = args.source_dir[0]
if args.destination_dir[0].endswith('/'):
destination = args.destination_dir[0][:-1]
else:
destination = args.destination_dir[0]
source_directories = scan_source(source)
create_destination()
scan_destination_convert()
s_exit(0)
-1
View File
@@ -1 +0,0 @@
/usr/bin/flac2pod
@@ -1,10 +1,10 @@
#!/bin/python3
#!/usr/bin/env python3
from argparse import ArgumentParser
from flac2pod import scan_source
from mutagen.flac import FLAC
from mutagen.flac import FLAC, FLACNoHeaderError
from os import listdir, system
from subprocess import PIPE, Popen, STDOUT
from subprocess import PIPE, run
from sys import exit as s_exit
# argument parsing
@@ -26,38 +26,40 @@ if __name__ == "__main__":
gain = 1
for album in sorted(source_directories[0]):
std_album = album.replace(' ', '\\ ').replace("'", "\\'").replace(')', '\\)') \
.replace('(', '\\(').replace('&', '\\&').replace('`', '\\`').replace('$', '\\$')
std_album = album.replace(' ', '\\ ').replace("'", "\\'").replace(')', '\\)').replace('(', '\\(')\
.replace(']', '\\]').replace('[', '\\[').replace('&', '\\&').replace('`', '\\`').replace('$', '\\$')
for song in listdir(album):
audio = FLAC(f"{album}/{song}")
if audio.get('replaygain_track_gain') or audio.get('REPLAYGAIN_TRACK_GAIN'):
gain = 1
else:
gain = 0
break
if args.force or gain == 0:
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:
print(f"[{album}/*] Contains non-FLAC files, skipping album...")
gain = 2
if gain != 2 and (args.force or gain == 0):
print(f"[{album}/*] Adding ReplayGain data...")
system(f"metaflac --remove-replay-gain {std_album}/*")
command = f"metaflac --add-replay-gain {std_album}/*"
output = Popen(command, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
block = output.communicate()[0].strip() # blocks the program from proceeding until stdout is given
output = run(f"metaflac --add-replay-gain {std_album}/*", shell=True, stderr=PIPE, text=True)
if output.returncode == 1:
if block.decode('utf-8').__contains__('sample') or block.decode('utf-8').__contains__('resolution of'):
print('Resolution/Sample Rate mismatch, scanning tracks as individuals...')
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('$', '\\$')
command = f"metaflac --add-replay-gain {std_album}/{std_song}"
output = Popen(command, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
block = output.communicate()[0].strip()
output = run(f"metaflac --add-replay-gain {std_album}/{std_song}", shell=True, stderr=PIPE,
text=True)
if output.returncode == 1:
print('There was an error processing your files.')
print(f"Return Code: [{str(output.returncode)}] {block.decode('utf-8')}")
print(f"Return Code: [{str(output.returncode)}] {output.stderr}")
s_exit(1)
else:
print('There was an error processing your files.')
print(f"Return Code: [{str(output.returncode)}] {block.decode('utf-8')}")
print(f"Return Code: [{str(output.returncode)}] {output.stderr}")
s_exit(1)
else:
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
# external modules
from argparse import ArgumentParser
from mutagen.flac import FLAC
from os import cpu_count, path, walk
from pathlib import Path
from shutil import copy
from subprocess import check_output, Popen
from sys import exit as s_exit
from time import sleep
# utility functions
def scan_source(_source):
_artist_dirs, _album_dirs, _full_paths = [], [], []
for _root, _directories, _files in walk(path.expanduser(_source)):
for _dir in sorted(_directories):
if not _root.endswith(path.expanduser(_source)):
_artist_dirs.append(_root)
_artist_dirs = list(set(_artist_dirs))
for _artist in sorted(_artist_dirs):
for _root, _directories, _files in walk(path.expanduser(_artist)):
if not _root.endswith(path.expanduser(_artist)):
_album_dirs.append(_root)
_album_dirs = list(set(_album_dirs))
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'):
_full_paths.append(f"{_album}/{_filename}")
return _album_dirs, _full_paths
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)
def scan_destination_convert():
_i, _i_total, _total_files = 0, 0, len(source_directories[1])
for _file in source_directories[1]:
_i_total += 1
_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'):
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
_i += 1
print(f"\n{_progress}% | \u001b[38;5;0;48;5;15mConverting {_file_s} to {_file_d}...\u001b[0m\n")
# get relevant ReplayGain info
_audio = FLAC(f"{_file}")
if args.albumgain:
if _audio.get('REPLAYGAIN_ALBUM_GAIN'):
_rggain = float(_audio.get('REPLAYGAIN_ALBUM_GAIN')[0][:-3])
elif _audio.get('replaygain_album_gain'):
_rggain = float(_audio.get('replaygain_album_gain')[0][:-3])
else:
_rggain = None
if _audio.get('REPLAYGAIN_ALBUM_PEAK'):
_rgpeak = float(_audio.get('REPLAYGAIN_ALBUM_PEAK')[0])
elif _audio.get('replaygain_album_peak'):
_rgpeak = float(_audio.get('replaygain_album_peak')[0])
else:
_rgpeak = None
else:
if _audio.get('REPLAYGAIN_TRACK_GAIN'):
_rggain = float(_audio.get('REPLAYGAIN_TRACK_GAIN')[0][:-3])
elif _audio.get('replaygain_track_gain'):
_rggain = float(_audio.get('replaygain_track_gain')[0][:-3])
else:
_rggain = None
if _audio.get('REPLAYGAIN_TRACK_PEAK'):
_rgpeak = float(_audio.get('REPLAYGAIN_TRACK_PEAK')[0])
elif _audio.get('replaygain_track_peak'):
_rgpeak = float(_audio.get('replaygain_track_peak')[0])
else:
_rgpeak = None
# determine ffmpeg arguments
if args.bake and _rggain is not None:
_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}#'"
else:
_bake_args, _flac2pod_rg_tags = '', ''
# generate the appropriate ffmpeg command
_cmd = f"screen -DmS flac2pod{_i} ffmpeg -i {_file_s} -c:a aac -ab 256k {_bake_args} -map_metadata 0 " \
f"{_flac2pod_rg_tags} -aac_pns 0 -movflags +faststart -vn {_file_d} </dev/null"
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")
if args.stopifnogain:
s_exit(1)
Popen(_cmd, shell=True)
_active_processes = int(check_output('ps -C ffmpeg | wc -l', shell=True)) - 1
while _active_processes != 0:
print('\nPlease wait for the remaining conversions to complete...\n')
sleep(1)
_active_processes = int(check_output('ps -C ffmpeg | wc -l', shell=True)) - 1
sleep(1)
print('\niPod conversion complete!\n')
s_exit(0)
# argument parsing
if __name__ == "__main__":
parser = ArgumentParser(description='Convert your existing FLAC library to be played on an iPod Classic.')
parser.add_argument('source_dir', nargs='+',
help='directory tree containing music files - folder must contain library sorted into artists '
'and albums')
parser.add_argument('destination_dir', nargs='+',
help='destination parent directory for output - will be created if it does not already exist')
parser.add_argument('-b', '--bake', action='store_true',
help='bake (encode) ReplayGain values into the file')
parser.add_argument('-a', '--albumgain', action='store_true',
help='read ReplayGain album gain instead of ReplayGain track gain')
parser.add_argument('-x', '--stopifnogain', action='store_true',
help='stop flac2pod if ReplayGain data cannot be found in a file')
args = parser.parse_args()
if args.source_dir[0].endswith('/'):
source = args.source_dir[0][:-1]
else:
source = args.source_dir[0]
if args.destination_dir[0].endswith('/'):
destination = args.destination_dir[0][:-1]
else:
destination = args.destination_dir[0]
source_directories = scan_source(source)
create_destination()
scan_destination_convert()
s_exit(0)
+21 -26
View File
@@ -1,14 +1,12 @@
#!/bin/bash
#!/usr/bin/env bash
# This script packages flac2pod (from source) for Arch Linux.
echo -e '\n\nthe value entered in this field will only affect the version reported to the package manager - the latest source is used regardless\n'
read -r -p "version number: " version
read -r -p "revision number: " revision
echo -e '\n\nThe value entered in this field will only affect the version reported to the package manager. The latest source is used regardless.\n'
read -r -p "Version number: " version
read -r -p "Revision number: " revision
echo -e '\nOptions (please enter the number only):'
echo -e '\n1. GitHub Release Tag\n2. Local\n'
read -r -p "Source (for build scripts): " source
echo -e '\noptions (please enter the number only):'
echo -e '\n1. gitHub release tag\n2. local\n'
read -r -p "source (for build scripts): " source
if [ "$source" == "1" ]; then
source='https://github.com/rwinkhart/flac2pod/releases/download/v$pkgver/flac2pod-$pkgver.tar.xz'
@@ -16,22 +14,22 @@ else
source=local://flac2pod-"$version".tar.xz
fi
# Archive creation
echo -e '\nPackaging as generic...\n'
mkdir -p packages/archtemp/usr
cp -r bin packages/archtemp/usr/
cp -r share packages/archtemp/usr/
tar -C packages/archtemp -cvf packages/flac2pod-"$version".tar.xz usr/
rm -rf packages/archtemp
sha512="$(sha512sum packages/flac2pod-"$version".tar.xz | awk '{print $1;}')"
# generic packaging
echo -e '\npackaging as generic...\n'
mkdir -p output/generictemp/usr/{bin,lib/flac2pod}
cp -r lib/. output/generictemp/usr/lib/flac2pod/
ln -s /usr/lib/flac2pod/flac2pod.py output/generictemp/usr/bin/flac2pod
ln -s /usr/lib/flac2pod/flac2pod-flacgain.py output/generictemp/usr/bin/flac2pod-flacgain
cp -r share output/generictemp/usr/
tar -C output/generictemp -cvJf output/flac2pod-"$version".tar.xz usr/
rm -rf output/generictemp
sha512="$(sha512sum output/flac2pod-"$version".tar.xz | awk '{print $1;}')"
echo -e "\nsha512 sum:\n$sha512"
echo -e "\nGeneric packaging complete.\n"
echo -e "\ngeneric packaging complete\n"
# PKGBUILD creation
echo -e '\nGenerating PKGBUILD...'
echo -e '\ngenerating PKGBUILD...'
echo "# Maintainer: Randall Winkhart <idgr at tutanota dot com>
pkgname=flac2pod
pkgver="$version"
pkgrel="$revision"
@@ -40,14 +38,11 @@ url='https://github.com/rwinkhart/flac2pod'
arch=('any')
license=('GPL2')
depends=(ffmpeg flac python python-mutagen screen)
source=(\""$source"\")
sha512sums=('"$sha512"')
package() {
tar xf flac2pod-"\"\$pkgver\"".tar.xz -C "\"\${pkgdir}\""
}
" > packages/PKGBUILD
echo -e "\nPKGBUILD generated.\n"
" > output/PKGBUILD
echo -e "\nPKGBUILD generated\n"