41 Commits
Author SHA1 Message Date
RandyTheSilly a2e0e3fe74 Create bin directory when packaging (for symlink) 2022-11-16 20:25:18 -05:00
RandyTheSilly d0d34a04ee Scan tracks as individuals if there are mismatched channels 2022-11-16 20:11:37 -05:00
RandyTheSilly e78a0bc2c2 Update packaging, now installs to /usr/lib/flac2pod 2022-11-16 15:02:00 -05:00
RandyTheSilly 2fd4d685e0 Switch from subprocess.popen to subprocess.run where applicable, fix shebangs 2022-11-16 14:36:54 -05:00
RandyTheSilly 6732731c11 Fixed non-FLAC file copy function 2022-05-19 02:11:36 -04:00
RandyTheSilly 5c83969ce2 Copy non-FLAC files instead of moving them 2022-04-24 13:37:14 -04:00
RandyTheSilly 19a7f3b7b2 Fix existing destination file detection 2022-04-24 13:36:34 -04:00
RandyTheSilly be7166e2dd Copy non-FLAC files to destination directory 2022-04-24 12:36:41 -04:00
RandyTheSilly 8ba7815241 Add support for skipping conversions on non-FLAC files 2022-04-22 12:50:27 -04:00
RandyTheSilly d03d73a2f8 Fixed typo from last commit 2022-04-22 12:44:26 -04:00
RandyTheSilly aa9ec97695 Check for metaflac ReplayGain data first 2022-04-22 12:40:52 -04:00
RandyTheSilly fcee6ba3f4 Cleaned up import statements 2022-04-22 12:29:34 -04:00
RandyTheSilly 1daa02f7ac Added '[' and ']' to special character list 2022-04-22 12:27:43 -04:00
RandyTheSilly 6b8553cbee Prevent crashes when a non-FLAC file is found 2022-04-22 12:26:11 -04:00
RandyTheSilly 88e3994f4f Added EOF return codes 2022-04-20 21:02:46 -04:00
RandyTheSilly f45dab8cfb Added return codes 2022-04-20 20:53:57 -04:00
RandyTheSilly 6d8deda002 Prevent errors when attempting to process files without valid ReplayGain data 2022-04-20 20:51:18 -04:00
RandyTheSilly d2598c561a Update README.md 2022-04-20 20:47:00 -04:00
RandyTheSilly 35bc1d9212 Trigger individual scanning on resolution errors 2022-04-20 20:31:40 -04:00
RandyTheSilly 5550bc5b76 Added script for quickly adding ReplayGain data to a FLAC library 2022-04-20 20:23:40 -04:00
RandyTheSilly b91511286c Added option to stop the program if ReplayGain data cannot be found 2022-04-20 00:48:58 -04:00
RandyTheSilly 8704749e7b Update README.md 2022-04-20 00:37:38 -04:00
RandyTheSilly 094725cad0 Remove --strip option 2022-04-20 00:31:30 -04:00
RandyTheSilly f79d6ce025 Add option to use album gain instead of track gain 2022-04-20 00:21:32 -04:00
RandyTheSilly c9a31dcbce Remove rg2sc integration 2022-04-20 00:09:26 -04:00
RandyTheSilly 1be96f0b20 Fix bugs preventing ffmpeg from completing conversions 2022-04-20 00:05:28 -04:00
RandyTheSilly 282c193804 Remove MP3 support 2022-04-19 23:08:55 -04:00
RandyTheSilly e833e4ce60 Add support for baking in ReplayGain values 2022-04-19 23:01:07 -04:00
RandyTheSilly b5a2b9adb3 Compatibility with new commits to rg2sc 2022-03-30 04:28:14 -04:00
RandyTheSilly 3c7ec3e921 Added python-mutagen as a dependency 2022-03-29 01:18:28 -04:00
RandyTheSilly 10973e3ac8 Added tag for transfering FLAC RG data to rg2sc for MP4s 2022-03-28 23:59:44 -04:00
RandyTheSilly c8ef53bc17 Add screen to dependencies 2022-03-28 22:35:28 -04:00
RandyTheSilly 144c3f53e0 Update README.md 2022-03-28 22:33:28 -04:00
RandyTheSilly 998c73c20f Overhauled argument system 2022-03-28 22:19:01 -04:00
RandyTheSilly df1dce38eb Rounded percentage counter 2022-03-28 21:28:47 -04:00
RandyTheSilly ed12cc5d23 AAC support 2022-03-28 21:26:56 -04:00
RandyTheSilly f72d3ac9b7 Basic compatibility with new commits to rg2sc 2022-03-28 21:00:42 -04:00
RandyTheSilly 95a8dacd29 Fix percentage counter not being multiplied properly 2022-03-27 19:05:16 -04:00
RandyTheSilly d64f4c56fa Still strip album art in ffmpeg before rg2sc pass 2022-03-27 17:23:09 -04:00
RandyTheSilly 137e30c80b Move APIC stripping to rg2sc 2022-03-27 16:31:20 -04:00
RandyTheSilly be5c96e182 Fixed inaccurate percentage counter 2022-03-26 21:24:44 -04:00
5 changed files with 263 additions and 129 deletions
+19 -15
View File
@@ -1,27 +1,31 @@
# flac2pod
Seamlessly converts your existing FLAC library to be played on an iPod Classic.
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 MP3-320 for easy iPod Classic use. It also takes advantage of "rg2sc" (https://github.com/rwinkhart/rg2sc) to convert any existing ReplayGain tags to Apple Sound Check tags that are compatible with Apple products.
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.
# Usage
Simply run:
Simply run...
```
flac2pod
flac2pod <source_dir> <destination_dir>
```
...and you will be prompted for your source and destination directories.
...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.
In order for flac2pod to function, your source_dir must be structured as follows:
In order for flac2pod to function, your library must be structured as follows:
library parent dir -> artist dir -> album dir -> FLAC files
When prompted for the source directory, you want to input your library parent directory.
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 flac2pod executable from https://github.com/rwinkhart/flac2pod/blob/master/bin/flac2pod to your /usr/bin/
- copy the rg2sc executable from https://github.com/rwinkhart/rg2sc/blob/master/bin/rg2sc to your /usr/bin
- install python-mutagen, ffmpeg, and lame
If installing on a non-Arch-based distribution, a generic package is available on the releases page.
-87
View File
@@ -1,87 +0,0 @@
#!/bin/python3
# external modules
from os import cpu_count, path, walk
from pathlib import Path
from sys import argv, exit as s_exit
from time import sleep
from subprocess import check_output, Popen
# utility functions
def scan_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_mp3():
_i, _total_files = 0, len(source_directories)
for _file in source_directories[1]:
if not Path(f"{_file.replace(path.expanduser(source), path.expanduser(destination))}.mp3").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('$', '\\$') + '.mp3'
_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
_progress = _i / _total_files
print(f"\n{_progress}% | \u001b[38;5;0;48;5;15mConverting {_file_s} to {_file_d}...\u001b[0m\n")
if '-s' in arguments:
_cmd = f"screen -DmS flac2pod{_i} ffmpeg -i {_file_s} -map 0:a -ab 320k -map_metadata 0" \
f" -id3v2_version 3 {_file_d} </dev/null; rg2sc -f -s {_file_d}"
else:
_cmd = f"screen -DmS flac2pod{_i} ffmpeg -i {_file_s} -c:v copy -ab 320k -map_metadata 0" \
f" -id3v2_version 3 {_file_d} </dev/null; rg2sc -f {_file_d}"
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()
# retrieve typed argument
arguments = argv
source = input('Source parent directory: ')
if source.endswith('/'):
source = source[:-1]
destination = input('Destination parent directory: ')
if destination.endswith('/'):
destination = destination[:-1]
source_directories = scan_source()
create_destination()
scan_destination_convert_mp3()
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
from argparse import ArgumentParser
from flac2pod import scan_source
from mutagen.flac import FLAC, FLACNoHeaderError
from os import listdir, system
from subprocess import PIPE, run
from sys import exit as s_exit
# argument parsing
if __name__ == "__main__":
parser = ArgumentParser(description='Quickly tag your FLAC library with ReplayGain data using metaflac.')
parser.add_argument('source_dir', nargs='+',
help='directory tree containing music files - folder must contain library sorted into artists '
'and albums')
parser.add_argument('-f', '--force', action='store_true',
help='overwrite existing ReplayGain data')
args = parser.parse_args()
if args.source_dir[0].endswith('/'):
source = args.source_dir[0][:-1]
else:
source = args.source_dir[0]
source_directories = scan_source(source)
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:
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}/*")
output = run(f"metaflac --add-replay-gain {std_album}/*", shell=True, 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)
if output.returncode == 1:
print('There was an error processing your files.')
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)}] {output.stderr}")
s_exit(1)
else:
print(f"[{album}/*] ReplayGain data is already present.")
s_exit(0)
+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)
+22 -27
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"
@@ -39,15 +37,12 @@ pkgdesc='Converts your FLAC library to be iPod-ready'
url='https://github.com/rwinkhart/flac2pod'
arch=('any')
license=('GPL2')
depends=(python ffmpeg lame rg2sc)
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"