Release 2.2

- Switch to hybrid S6
- **Added --regex option** TheCaptain989/lidarr-flac2mp3#33
- **Added optional use of environment variable** TheCaptain989/lidarr-flac2mp3#33
- **Added new --tags option to resolve TheCaptain989/lidarr-flac2mp3#15**
- Added checks for identical track name
- Recycled files are now moved to subdirectory paths that more closely resemble the original path
- Better error handling in awk script when calling system commands
- Added logging for skipped tracks
- Corrected some logging anomalies
- Modified exit codes
- Updated command line help
- Fixed missing executable attribute on some script files
- Updated README
This commit is contained in:
TheCaptain989 2022-11-27 09:56:09 -06:00
parent a5b136d992
commit 64b36034ee
7 changed files with 313 additions and 100 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,5 +1,5 @@
# About
A [Docker Mod](https://github.com/linuxserver/docker-mods) for the LinuxServer.io Lidarr Docker container that uses ffmpeg and a script to automatically convert downloaded FLAC files to MP3s. Default output quality is 320Kbps constant bit rate.
A [Docker Mod](https://github.com/linuxserver/docker-mods) for the LinuxServer.io Lidarr Docker container that uses ffmpeg and a script to automatically convert downloaded FLAC (or other format) files to MP3s. Default output quality is 320Kbps constant bit rate.
Advanced options act as a light wrapper to ffmpeg, allowing conversion to any supported audio format, including AAC, AC3, Opus, and many others.
A [Batch Mode](./README.md#batch-mode) is also supported that allows usage outside of Lidarr.
@ -17,7 +17,7 @@ Production Container info: ![Docker Image Size](https://img.shields.io/docker/im
2. Configure the Docker container with all the port, volume, and environment settings from the *original container documentation* here:
**[linuxserver/lidarr](https://hub.docker.com/r/linuxserver/lidarr "Docker container")**
1. Add a **DOCKER_MODS** environment variable to the `docker run` command, as follows:
- Dev/test release: `-e DOCKER_MODS=thecaptain989/lidarr-flac2mp3:latest`
- Dev/test release: `-e DOCKER_MODS=thecaptain989/lidarr-flac2mp3:latest`
- Stable release: `-e DOCKER_MODS=linuxserver/mods:lidarr-flac2mp3`
*Example Docker CLI Configuration*
@ -49,25 +49,24 @@ Production Container info: ![Docker Image Size](https://img.shields.io/docker/im
This will use the defaults to create a 320Kbps MP3 file.
*For any other setting, you **must** either use one of the [included wrapper scripts](./README.md#included-wrapper-scripts) or create a custom script with the command line options you desire. See the [Syntax](./README.md#syntax) section below.*
*For any other setting, you **must** use one of the supported methods to pass arguments to the script. See the [Syntax](./README.md#syntax) section below.*
## Usage
New file(s) with will be placed in the same directory as the original FLAC file(s) (unless redirected with the `--output` option below) and have the same owner and permissions. Existing files with the same track name will be overwritten.
New file(s) will be placed in the same directory as the original FLAC file(s) (unless redirected with the `--output` option below) and have the same owner and permissions. Existing files with the same track name will be overwritten.
By default, if you've configured Lidarr's **Recycle Bin** path correctly, the original audio file will be moved there.
![danger] **NOTE:** If you have *not* configured the Recycle Bin, the original FLAC audio file(s) will be deleted and permanently lost. This behavior may be modifed with the `--keep-file` option.
![danger] **NOTE:** If you have *not* configured the Recycle Bin, the original FLAC audio file(s) will be deleted and permanently lost. This behavior may be modified with the `--keep-file` option.
### Syntax
>**Note:** The **Arguments** field for Custom Scripts was removed in Lidarr release [v0.7.0.1347](https://github.com/lidarr/Lidarr/commit/b9d240924f8965ebb2c5e307e36b810ae076101e "Lidarr commit notes") due to security concerns.
To support options with this version and later, a wrapper script can be manually created that will call *flac2mp3.sh* with the required arguments.
>**Note:** The _Arguments_ field for Custom Scripts was removed in Lidarr release [v0.7.0.1347](https://github.com/lidarr/Lidarr/commit/b9d240924f8965ebb2c5e307e36b810ae076101e "Lidarr commit notes") due to security concerns.
To supply arguments to the script, you **must** either use one of the **[included wrapper scripts](./README.md#included-wrapper-scripts)**, create a **[custom wrapper script](./README.md#example-wrapper-script)**, or set the `FLAC2MP3_ARGS` **[environment variable](./README.md#environment-variable)**.
#### Command Line Options and Arguments
The script may be called with optional command line arguments.
The syntax for the command line is:
`flac2mp3 [OPTIONS] [{-b|--bitrate} <bitrate> | {-v|--quality} <quality> | {-a|--advanced} "<options>" {-e|--extension} <extension>]`
OR
`flac2mp3 [OPTIONS] {-f|--file} <audio_file>`
`flac2mp3 [{-d|--debug} [<level>]] [{-b|--bitrate} <bitrate> | {-v|--quality} <quality> | {-a|--advanced} "<options>" {-e|--extension} <extension>] [{-f|--file} <audio_file>] [{-k|--keepfile}] [{-o|--output} <directory>] [{-r|--regex} '<regex>'] [{-t|--tags} <taglist>]`
Where:
@ -76,11 +75,13 @@ Option|Argument|Description
-d, --debug|\[\<level\>\]|Enables debug logging. Level is optional.<br/>Default of 1 (low).<br/>2 includes API and FFmpeg output.
-b, --bitrate|\<bitrate\>|Sets the output quality in constant bits per second (CBR).<br/>Examples: 160k, 240k, 300000<br/>**Note:** May not be specified with `-v`, `-a`, or `-e`.
-v, --quality|\<quality\>|Sets the output variable bit rate (VBR).<br/>Specify a value between 0 and 9, with 0 being the highest quality.<br/>See the [FFmpeg MP3 Encoding Guide](https://trac.ffmpeg.org/wiki/Encode/MP3) for more details.<br/>**Note:** May not be specified with `-b`, `-a`, or `-e`.
-a, --advanced|\"\<options\>\"|Advanced ffmpeg options.<br/>The specified `options` replace all script defaults and are sent directly to ffmpeg.<br/>The `options` value must be enclosed in quotes.<br/>See [FFmpeg Options](https://ffmpeg.org/ffmpeg.html#Options) for details on valid options, and [Guidelines for high quality audio encoding](https://trac.ffmpeg.org/wiki/Encode/HighQualityAudio) for suggested usage.<br/>**Note:** Requires the `-e` option to also be specified. May not be specified with `-v` or `-b`.<br/>![danger] **WARNING:** You must specify an audio codec (by including a `-c:a <codec>` ffmpeg option) or the resulting file will contain no audio!<br/>![danger] **WARNING:** Invalid `options` could result in script failure!
-e, --extension|\<extension\>|Sets the output file extension<br/>The extension may be prefixed by a dot (".") or not.<br/>**Note:** Requires the `-a` option to also be specified. May not be specified with `-v` or `-b`.
-f, --file|<audio_file>|If included, the script enters **[Batch Mode](./README.md#batch-mode)** and converts the specified audio file.<br/>![danger] **WARNING:** Do not use this argument when called from Lidarr!
-a, --advanced|\"\<options\>\"|Advanced ffmpeg options.<br/>The specified `options` replace all script defaults and are sent directly to ffmpeg.<br/>The `options` value must be enclosed in quotes.<br/>See [FFmpeg Options](https://ffmpeg.org/ffmpeg.html#Options) for details on valid options, and [Guidelines for high quality audio encoding](https://trac.ffmpeg.org/wiki/Encode/HighQualityAudio) for suggested usage.<br/>**Note:** Requires the `-e` option to also be specified. May not be specified with `-v` or `-b`.<br/>![warning] **WARNING:** You must specify an audio codec (by including a `-c:a <codec>` ffmpeg option) or the resulting file will contain no audio!<br/>![warning] **WARNING:** Invalid `options` could result in script failure!
-e, --extension|\<extension\>|Sets the output file extension.<br/>The extension may be prefixed by a dot (".") or not.<br/>Example: .ogg<br/>**Note:** Requires the `-a` option to also be specified. May not be specified with `-v` or `-b`.
-f, --file|<audio_file>|If included, the script enters **[Batch Mode](./README.md#batch-mode)** and converts the specified audio file.<br/>![warning] **WARNING:** Do not use this argument when called from Lidarr!
-o, --output|\<directory\>|Converted audio file(s) are saved to `directory` instead of being located in the same directory as the source audio file.<br/>The path will be created if it does not exist.
-k, --keep-file| |Do not delete the source file or move it to the Lidarr Recycle bin.<br/>**Note:** This also disables triggering a Lidarr rescan after conversion.
-r, --regex|'\<regex\>'|Sets the regular expression used to select input files.<br/>The `regex` value should be enclosed in single quotes and escaped properly.<br/>Defaults to `"\.flac$"`.
-t, --tags|\'<taglist\'>|Comma separated list of metadata tags to apply automated corrections to.<br/>See [Metadata Corrections](./README.md#metadata-corrections) section.
--help| |Display help and exit.
--version| |Display version and exit.
@ -91,28 +92,42 @@ The `-a` option effectively makes the script a generic wrapper for ffmpeg. FFmp
The exact format of the executed ffmpeg command is:
```
ffmpeg -loglevel error -i "input.flac" ${options} "output.${extension}"
ffmpeg -loglevel error -nostdin -i "input.flac" ${options} "output.${extension}"
```
#### Technical notes on regex
By default, the script only matches and interacts with FLAC files (specifically, files ending in ".flac"). The `-r` option allows the script to match on a user specified regular expression (i.e. "regex") pattern.
Files are passed to the script with the full Linux path intact. (Ex: `/path/to/audio/a-ha/Hunting High and Low/01 Take on Me.mp3`). Craft your regex with this in mind.
![warning] **NOTE:** Escaping special regex characters (like a dot `.`) requires a double backslash, _even when single quoted!_ This is because **awk** (the program that processes audio files in the script) in most cases [strips a single backslash](https://www.gnu.org/software/gawk/manual/html_node/Escape-Sequences.html "GNU awk reference") from strings. Double quoted or unquoted strings require _four_ backslashes to preserve a regex escape because the bash shell will process the escapes first.
For example, to convert all audio files to AAC audio files, use the following options:
```
-a "-y -map 0 -c:a aac -b:a 240k -c:v copy" -e m4a --regex '\\.[^.]*$'
```
Regular expression syntax is beyond the scope of this document. See this [tutorial](https://www.regular-expressions.info/tutorial.html "Regular Expressions Tutorial") for more information. Regex patterns may be tested [here](http://regexstorm.net/tester "regex tester").
### Examples
```
-b 320k # Output 320 kbit/s MP3 (non-VBR; same as default behavior)
-v 0 # Output variable bitrate MP3, VBR 220-260 kbit/s
-d -b 160k # Enable debugging level 1, and output a 160 kbit/s MP3
-r '\\.[^.]*$' # Convert any file to MP3 (not just FLAC)
-a "-c:v libtheora -map 0 -q:v 10 -c:a libopus -b:a 192k" -e .opus
# Convert to Opus format, VBR 192 kbit/s, cover art, no overwright
# Convert to Opus format, 192 kbit/s, cover art
-a "-vn -c:a libopus -b:a 192K" -e .opus -r '\.mp3$'
# Convert .mp3 files to Opus format, 192 kbit/s, no cover art
-a "-y -map 0 -c:a aac -b:a 240k -c:v copy" -e mp4
# Convert to MP4 format, using AAC 240 kbit/s audio, cover art, overwrite file
--file "/path/to/audio/a-ha/Hunting High and Low/01 Take on Me.flac"
# Batch Mode
# Output 320kbit/s MP3
-o "/path/to/audio" -k
# Place the converted file(s) in the specified directory and do not delete the original audio file(s).
# Place the converted file(s) in the specified directory and do not delete the original audio file(s)
```
### Wrapper Scripts
To supply arguments to the script, one of the included wrapper scripts may be used or a custom wrapper script must be created.
#### Included Wrapper Scripts
For your convenience, several wrapper scripts are included in the `/usr/local/bin/` directory.
You may use any of these scripts in place of the `flac2mp3.sh` mentioned in the [Installation](./README.md#installation) section above.
@ -141,6 +156,26 @@ Then put `/config/flac2mp3-custom.sh` in the **Path** field in place of `/usr/lo
>**Note:** If you followed the Linuxserver.io recommendations when configuring your container, the `/config` directory will be mapped to an external storage location. It is therefore recommended to place custom scripts in the `/config` directory so they will survive container updates, but they may be placed anywhere that is accessible by Lidarr.
### Environment Variable
The `flac2mp3.sh` script also allows the use of arguments provided by the `FLAC2MP3_ARGS` environment variable. This allows advanced use cases without having to provide a custom script.
For example, the following value would convert any .mp3 to Opus:
```
-e FLAC2MP3_ARGS='-a "-vn -c:a libopus -b:a 192k" -e .opus -r "\\.mp3$"'
```
Make sure to correctly use quotes and/or escape special characters when using this method. (See [regex notes](./README.md#technical-notes-on-regex) above.)
In Docker Compose, the previous command would need an extra `$` to match the end-of-line:
```yaml
environment:
- FLAC2MP3_ARGS=-a "-vn -c:a libopus -b:a 192k" -e .opus -r '\\.mp3$$'
```
*Example Synology Configuration*
![flac2mp3](.assets/lidarr-synology-2.png "Synology container settings")
>**NOTE:** The environment variable settings are _only_ used when **no** command line arguments are present. **Any** command line argument will disable the use of the environment variable.
### Triggers
The only events/notification triggers that are supported are **On Release Import** and **On Upgrade**
@ -171,6 +206,21 @@ This log can be downloaded from Lidarr under *System* > *Log Files*
Log rotation is performed, with 5 log files of 1MB each kept, matching Lidarr's log retention.
>![danger] **NOTE:** If debug logging is enabled with a level above 1, the log file can grow very large very quickly and is much more likely to be rotated. *Do not leave high-level debug logging enabled permanently.*
#### Metadata Corrections
This feature is not meant for general purpose use. It is only documented for completeness.
List of supported tags and metadata corrections that are applied:
|Tag|Original|Correction
|---|---|---
|disc|1|1/1
|genre|/Pop/|"Pop"
| |/Indie/|"Alternative & Indie"
| |/Industrial/|"Industrial Rock"
| |/Electronic/|"Electronica & Dance"
| |/Punk\|Alternative/|"Alternative & Punk"
| |/Rock/|"Rock"
## Credits
This would not be possible without the following:

View File

@ -6,8 +6,8 @@ Only the latest major and minor version are supported.
| Version | Supported |
| ------- | ------------------ |
| 2.0.x | :heavy_check_mark: |
| < 2.0 | :x: |
| 2.2.x | :heavy_check_mark: |
| < 2.2 | :x: |
## Reporting a Vulnerability

View File

@ -44,4 +44,3 @@ if [ ! -x /usr/local/bin/flac2mp3.sh ]; then
echo "Making scripts executable."
chmod +x /usr/local/bin/flac2*.sh
fi

View File

@ -13,19 +13,24 @@ EOF
# Determine if setup is needed
if [ ! -f /usr/bin/ffmpeg ]; then
echo "**** Adding ffmpeg to package install list ****"
echo "ffmpeg" >> /mod-repo-packages-to-install.list
echo "**** Adding ffmpeg to package install list ****"
echo "ffmpeg" >> /mod-repo-packages-to-install.list
else
echo "**** flac2mp3 deps already installed, skipping ****"
fi
# Change ownership
if [ $(stat -c '%G' /usr/local/bin/flac2mp3.sh) != "abc" ]; then
echo "Changing ownership on scripts."
chown abc:abc /usr/local/bin/flac2*.sh
fi
# Make executable
if [ ! -x /usr/local/bin/flac2mp3.sh ]; then
echo "Making scripts executable."
chmod +x /usr/local/bin/flac2*.sh
fi
# Check ownership and attributes on each script file
for file in /usr/local/bin/flac2mp3*.sh
do
# Change ownership
if [ $(stat -c '%G' $file) != "abc" ]; then
echo "Changing ownership on $file script."
chown abc:abc $file
fi
# Make executable
if [ ! -x $file ]; then
echo "Making $file script executable."
chmod +x $file
fi
done

View File

@ -0,0 +1,3 @@
#!/bin/bash
. /usr/local/bin/flac2mp3.sh --tags disc,genre

View File

@ -7,6 +7,7 @@
# Dependencies:
# ffmpeg
# ffprobe
# awk
# curl
# jq
@ -18,7 +19,7 @@
# Exit codes:
# 0 - success; or test
# 1 - no audio file specified on command line
# 1 - no audio tracks detected
# 2 - ffmpeg not found
# 3 - invalid command line arguments
# 5 - specified audio file not found
@ -48,20 +49,21 @@ Audio conversion script designed for use with Lidarr
Source: https://github.com/TheCaptain989/lidarr-flac2mp3
Usage:
$0 [OPTIONS] [-b <bitrate> | -v <quality> | -a \"<options>\" -e <extension>]
$0 [OPTIONS] {-f|--file} <audio_file>
$0 [{-d|--debug} [<level>]] [{-b|--bitrate} <bitrate> | {-v|--quality} <quality> | {-a|--advanced} \"<options>\" {-e|--extension} <extension>] [{-f|--file} <audio_file>] [{-k|--keepfile}] [{-o|--output} <directory>] [{-r|--regex} '<regex>'] [{-t|--tags} <taglist>]
Options:
-d, --debug [<level>] enable debug logging
Level is optional, default of 1 (low)
-b, --bitrate <bitrate> set output quality in constant bits per second
-d, --debug [<level>] Enable debug logging
level is optional, between 1-3
1 is lowest, 3 is highest
[default: 1]
-b, --bitrate <bitrate> Set output quality in constant bits per second
[default: 320k]
Ex: 160k, 240k, 300000
-v, --quality <quality> set variable bitrate; quality between 0-9
-v, --quality <quality> Set variable bitrate; quality between 0-9
0 is highest quality, 9 is lowest
For more details, see:
https://trac.ffmpeg.org/wiki/Encode/MP3
-a, --advanced \"<options>\" advanced ffmpeg options enclosed in quotes
-a, --advanced \"<options>\" Advanced ffmpeg options enclosed in quotes
Specified options replace all script defaults
and are sent as entered to ffmpeg for
processing.
@ -73,32 +75,40 @@ Options:
Requires -e option to also be specified
For more details, see:
https://github.com/TheCaptain989/lidarr-flac2mp3
-e, --extension <extension> file extension for output file, with or without
-e, --extension <extension> File extension for output file, with or without
dot
Required when -a is specified!
-f, --file <audio_file> the script enters batch mode, using the
-f, --file <audio_file> The script enters batch mode, using the
specified audio file as input
WARNING: Do not use this argument when called
from Lidarr!
-o, --output <directory> specify a directory for the converted audio
file(s)
This will be created if it does not exist.
-k, --keep-file do not delete the source file or move it to the
-o, --output <directory> Specify a destination directory for the
converted audio file(s)
It will be created if it does not exist.
-k, --keep-file Do not delete the source file or move it to the
Lidarr Recycle bin
This also disables the Lidarr rescan.
--help display this help and exit
--version display script version and exit
-r, --regex '<regex>' Regular expression used to select input files
[default: \.flac$]
-t, --tags <taglist> Comma separated list of metadata tags to apply
automated corrections to.
Supports: disc, genre
--help Display this help and exit
--version Display script version and exit
Examples:
$flac2mp3_script -b 320k # Output 320 kbit/s MP3 (non-VBR; same as
default behavior)
$flac2mp3_script -v 0 # Output variable bitrate MP3, VBR 220-260
kbit/s
$flac2mp3_script -d -b 160k # Enable debugging level 1 and set output a
$flac2mp3_script -d -b 160k # Enable debugging level 1 and output a
160 kbit/s MP3
$flac2mp3_script -a \"-vn -c:a libopus -b:a 192K\" -e .opus
# Convert to Opus format, VBR 192 kbit/s, no
cover art
$flac2mp3_script -r '\\\\.[^.]*$' # Convert any file to MP3 (not just FLAC)
$flac2mp3_script -a \"-c:v libtheora -map 0 -q:v 10 -c:a libopus -b:a 192k\" -e .opus
# Convert to Opus format, 192 kbit/s, cover art
$flac2mp3_script -a \"-vn -c:a libopus -b:a 192K\" -e .opus -r '\.mp3$'
# Convert .mp3 files to Opus format, 192 kbit/s
no cover art
$flac2mp3_script -a \"-y -map 0 -c:a aac -b:a 240K -c:v copy\" -e mp4
# Convert to MP4 format, using AAC 240 kbit/s
audio, cover art, overwrite file
@ -107,12 +117,23 @@ Examples:
Output 320 kbit/s MP3
$flac2mp3_script -o \"/path/to/audio\" -k
# Place the converted file(s) in specified
directory and do not delete the original audio
file(s).
directory and do not delete the original
audio file(s)
"
echo "$usage" >&2
}
# Check for environment variable arguments
if [ -n "$FLAC2MP3_ARGS" ]; then
if [ $# -ne 0 ]; then
flac2mp3_prelogmessage="Warning|FLAC2MP3_ARGS environment variable set but will be ignored because command line arguments were also specified."
else
# Move the environment variable arguments to the command line for processing
flac2mp3_prelogmessage="Info|Using settings from environment variable."
eval set -- "$FLAC2MP3_ARGS"
fi
fi
# Process arguments
while (( "$#" )); do
case "$1" in
@ -142,7 +163,7 @@ while (( "$#" )); do
else
echo "Error|Invalid option: $1 requires an argument." >&2
usage
exit 1
exit 3
fi
;;
-b|--bitrate ) # Set constant bit rate
@ -220,13 +241,33 @@ while (( "$#" )); do
usage
exit 3
fi
# Test for trailing slash
# Test for trailing backslash
[ "${flac2mp3_output: -1:1}" != "/" ] && flac2mp3_output="${flac2mp3_output}/"
;;
-k|--keep-file ) # Do not delete source file(s)
export flac2mp3_keep=1
shift
;;
-r|--regex ) # Sets the regex used to match input files
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
export flac2mp3_regex="$2"
shift 2
else
echo "Error|Invalid option: $1 requires an argument." >&2
usage
exit 3
fi
;;
-t|--tags ) # Metadata tags to correct
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
export flac2mp3_tags="$2"
shift 2
else
echo "Error|Invalid option: $1 requires an argument." >&2
usage
exit 3
fi
;;
-*|--*=) # Unknown option
echo "Error|Unknown option: $1" >&2
usage
@ -258,7 +299,7 @@ elif [[ "${flac2mp3_type,,}" = "lidarr" ]]; then
[ -z "$flac2mp3_tracks" ] && flac2mp3_tracks="$lidarr_trackfile_path"
else
# Called in an unexpected way
echo -e "Error|Unknown or missing 'lidarr_eventtype' environment variable: ${flac2mp3_type}\nNot called within Lidarr.\nTry using Batch Mode option: -f <file>"
echo -e "Error|Unknown or missing 'lidarr_eventtype' environment variable: ${flac2mp3_type}\nNot called from Lidarr.\nTry using Batch Mode option: -f <file>"
exit 7
fi
@ -323,7 +364,7 @@ function check_rescan {
else
# It may have timed out, so let's wait a second
local flac2mp3_return=1
[ $flac2mp3_debug -ge 1 ] && echo "Debug|Job not done. Waiting 1 second." | log
[ $flac2mp3_debug -ge 1 ] && echo "Debug|Job not done. Waiting 1 second." | log
sleep 1
fi
fi
@ -339,12 +380,25 @@ if [ ! -f "/usr/bin/ffmpeg" ]; then
echo "$flac2mp3_message" >&2
exit 2
fi
if [ ! -f "/usr/bin/ffprobe" ]; then
flac2mp3_message="Error|/usr/bin/ffprobe is required by this script"
echo "$flac2mp3_message" | log
echo "$flac2mp3_message" >&2
exit 2
fi
# Log Debug state
if [ $flac2mp3_debug -ge 1 ]; then
flac2mp3_message="Debug|Enabling debug logging level ${flac2mp3_debug}. Starting ${lidarr_eventtype^} run."
echo "$flac2mp3_message" | log
echo "$flac2mp3_message" >&2
echo "$flac2mp3_message"
fi
# Log FLAC2MP3_ARGS usage
if [ -n "$flac2mp3_prelogmessage" ]; then
# flac2mp3_prelogmessage is set above, before argument processing
echo "$flac2mp3_prelogmessage" | log
[ $flac2mp3_debug -ge 1 ] && echo "Debug|FLAC2MP3_ARGS: ${FLAC2MP3_ARGS}" | log
fi
# Log environment
@ -393,15 +447,32 @@ elif [ -f "$flac2mp3_config" ]; then
-H "Content-Type: application/json" \
-X GET "$flac2mp3_api_url/config/mediamanagement")
flac2mp3_return=$?; [ "$flac2mp3_return" != 0 ] && {
flac2mp3_message="Error|[$flac2mp3_return] curl error when parsing: \"$flac2mp3_api_url/v3/config/mediamanagement\""
flac2mp3_message="Error|[$flac2mp3_return] curl error when parsing: \"$flac2mp3_api_url/config/mediamanagement\""
echo "$flac2mp3_message" | log
echo "$flac2mp3_message" >&2
}
[ $flac2mp3_debug -ge 2 ] && echo "API returned: $flac2mp3_result" | awk '{print "Debug|"$0}' | log
flac2mp3_recyclebin="$(echo $flac2mp3_result | jq -crM .recycleBin)"
# Test for trailing backslash
[ "${flac2mp3_recyclebin: -1:1}" != "/" ] && flac2mp3_recyclebin="${flac2mp3_recyclebin}/"
[ $flac2mp3_debug -ge 1 ] && echo "Debug|Detected Lidarr RecycleBin '$flac2mp3_recyclebin'" | log
# Get root folder path from Artist info
if [ "$lidarr_artist_id" != "" ]; then
flac2mp3_result=$(curl -s -H "X-Api-Key: $flac2mp3_apikey" \
-H "Content-Type: application/json" \
-X GET $flac2mp3_api_url/artist/$lidarr_artist_id)
flac2mp3_return=$?; [ "$flac2mp3_return" != 0 ] && {
flac2mp3_message="Error|[$flac2mp3_return] curl error when parsing: \"$flac2mp3_api_url/artist/$lidarr_artist_id\""
echo "$flac2mp3_message" | log
echo "$flac2mp3_message" >&2
}
[ $flac2mp3_debug -ge 2 ] && echo "API returned: $flac2mp3_result" | awk '{print "Debug|"$0}' | log
flac2mp3_root="$(echo $flac2mp3_result | jq -crM .rootFolderPath)"
[ $flac2mp3_debug -ge 1 ] && echo "Debug|Detected Lidarr Root Folder '$flac2mp3_root'" | log
fi
else
# No config file means we can't call the API. Best effort at this point.
# No config file means we can't call the API. Best effort at this point.
flac2mp3_message="Warn|Unable to locate Lidarr config file: '$flac2mp3_config'"
echo "$flac2mp3_message" | log
echo "$flac2mp3_message" >&2
@ -412,7 +483,7 @@ if [[ "$lidarr_eventtype" = "Test" ]]; then
echo "Info|Lidarr event: $lidarr_eventtype" | log
flac2mp3_message="Info|Script was test executed successfully."
echo "$flac2mp3_message" | log
echo "$flac2mp3_message" >&2
echo "$flac2mp3_message"
exit 0
fi
@ -424,12 +495,20 @@ if [ "$flac2mp3_type" = "batch" -a ! -f "$flac2mp3_tracks" ]; then
exit 5
fi
# Check for empty track variable
if [ -z "$flac2mp3_tracks" ]; then
flac2mp3_message="Error|No audio tracks were detected or specified!"
echo "$flac2mp3_message" | log
echo "$flac2mp3_message" >&2
exit 1
fi
# If specified, check if destination folder exists and create if necessary
if [ "$flac2mp3_output" -a ! -d "$flac2mp3_output" ]; then
[ $flac2mp3_debug -ge 1 ] && echo "Debug|Destination directory does not exist. Creating: $flac2mp3_output" | log
[ $flac2mp3_debug -ge 1 ] && echo "Debug|Destination directory does not exist. Creating: $flac2mp3_output" | log
mkdir -p "$flac2mp3_output"
flac2mp3_return=$?; [ "$flac2mp3_return" != 0 ] && {
flac2mp3_message="Error|[$flac2mp3_return] mkdir returned an error. Unable to create output directory."
flac2mp3_message="Error|[$flac2mp3_return] mkdir returned an error. Unable to create output directory."
echo "$flac2mp3_message" | log
echo "$flac2mp3_message" >&2
exit 6
@ -456,6 +535,9 @@ fi
if [ $flac2mp3_keep = 1 ]; then
flac2mp3_message+=", Keep source"
fi
if [ -n "$flac2mp3_regex" ]; then
flac2mp3_message+=", Matching regex: '${flac2mp3_regex}'"
fi
flac2mp3_message+=", Track(s): ${flac2mp3_tracks}"
echo "${flac2mp3_message}" | log
@ -465,67 +547,141 @@ echo -n "$flac2mp3_tracks" | awk -v Debug=$flac2mp3_debug \
-v Bitrate="$flac2mp3_bitrate" \
-v VBR="$flac2mp3_vbrquality" \
-v FFmpegADV="$flac2mp3_ffmpegadv" \
-v EXT="$flac2mp3_extension" \
-v Ext="$flac2mp3_extension" \
-v Output="$flac2mp3_output" \
-v Keep="$flac2mp3_keep" '
-v Keep=$flac2mp3_keep \
-v Pat="$flac2mp3_regex" \
-v Root="$flac2mp3_root" \
-v Taglist="$flac2mp3_tags" '
BEGIN {
FFmpeg="/usr/bin/ffmpeg"
FS="|"
RS="|"
IGNORECASE=1
if (EXT == "") EXT=".mp3"
if (Debug == 0) FFmpegLOG="error"
else if (Debug == 1) FFmpegLOG="warning"
else if (Debug >= 2) FFmpegLOG="info"
FFmpeg = "/usr/bin/ffmpeg"
FS = "|"
RS = "|"
IGNORECASE = 1
# Set default extension, pattern, and logging values
if (Ext == "") Ext = ".mp3"
if (Pat == "") Pat = "\\.flac$"
if (Debug == 0) FFmpegLOG = "error"
else if (Debug == 1) FFmpegLOG = "warning"
else if (Debug >= 2) FFmpegLOG = "info"
if (Bitrate) {
if (Debug >= 1) print "Debug|Using constant bitrate of "Bitrate
BrCommand="-b:a "Bitrate
BrCommand = "-b:a "Bitrate
} else if (VBR >= 0) {
if (Debug >= 1) print "Debug|Using variable quality of "VBR
BrCommand="-q:a "VBR
BrCommand = "-q:a "VBR
} else if (FFmpegADV) {
if (Debug >= 1) print "Debug|Using advanced ffmpeg options: \""FFmpegADV"\""
if (Debug >= 1) print "Debug|Exporting with file extension "EXT
if (Debug >= 1) print "Debug|Using advanced ffmpeg options \""FFmpegADV"\""
if (Debug >= 1) print "Debug|Exporting with file extension \""Ext"\""
FFmpegOPTS = FFmpegADV
}
if (Debug >= 1) print "Debug|Matching tracks against regex \""Pat"\""
# Set default ffmpeg options
if (FFmpegOPTS == "") FFmpegOPTS = "-c:v copy -map 0 -y -acodec libmp3lame "BrCommand" -write_id3v1 1 -id3v2_version 3"
}
/\.flac$/ {
# Get each FLAC file name and create a new MP3 (or other) name
Track=$1
NewTrack=substr(Track, 1, length(Track)-5) EXT
$0 !~ Pat {
if (Debug >= 1) print "Debug|Skipping track that did not match regex: "$0
}
$0 ~ Pat {
# Get each audio file name and create a new track name with the given extension
Track = $0
Last = split(Track, Parts, ".")
NewTrack = substr(Track, 1, length(Track) - length(Parts[Last]) - 1) Ext
# Redirect output if asked
if (Output) sub(/^.*\//,Output ,NewTrack)
if (Output) sub(/^.*\//, Output, NewTrack)
# Check for same track name
if (Track == NewTrack) {
print "Error|The original track name and new name are the same! Skipping track: "Track
next
}
# Set metadata options to fix tags if asked
if (Taglist) {
Metadata = ""; JSON = ""
NumTags = split(Taglist, Tag, ",")
if (Debug >= 1) print "Debug|Detecting and fixing common problems with the following metadata tags: "Taglist
Command = "/usr/bin/ffprobe -hide_banner -loglevel fatal -print_format json=compact=1 -show_format -show_entries \"format=tags : format_tags=disc,genre\" -i \""Track"\" 2>&1"
if (Debug >= 2) print "Debug|Executing: "Command
# Concatenate FFprobe output into one line
while (Command | getline Line) JSON = JSON Line
for (i = 1; i <= NumTags; i++) {
if (Tag[i] == "disc") {
# Fix one disc by itself (\47 is single quote in octal)
Command = "echo \47"JSON"\47 | jq -crM \47.format.tags.disc\47 2>&1"
if (Debug >= 2) print "Debug|Executing: "Command
Command | getline Disc
sub(/\n/, "", Disc) # I do not yet know why this is needed
if (Debug >= 1) print "Debug|Discovered disc: "Disc
if (Disc ~ /^1$/) Metadata = "-metadata disc=\"1/1\" "Metadata
}
if (Tag[i] == "genre") {
# Fix multiple genres
Command = "echo \47"JSON"\47 | jq -crM \47.format.tags | to_entries[] | select(.key | match(\"genre\";\"i\")).value\47 2>&1"
if (Debug >= 2) print "Debug|Executing: "Command
Command | getline Genre
sub(/\n/, "", Genre) # I do not yet know why this is needed
if (Debug >= 1) print "Debug|Discovered genre: "Genre
if (Genre ~ /;/) {
if (Genre ~ /Pop/) Metadata = "-metadata genre=\"Pop\" "Metadata
else if (Genre ~ /Indie/) Metadata = "-metadata genre=\"Alternative & Indie\" "Metadata
else if (Genre ~ /Industrial/) Metadata = "-metadata genre=\"Industrial Rock\" "Metadata
else if (Genre ~ /Electronic/) Metadata = "-metadata genre=\"Electronica & Dance\" "Metadata
else if (Genre ~ /Punk|Alternative/) Metadata = "-metadata genre=\"Alternative & Punk\" "Metadata
else if (Genre ~ /Rock/) Metadata = "-metadata genre=\"Rock\" "Metadata
}
}
if (Debug >= 2) print "Debug|Metadata on pass "i"/"NumTags": "Metadata
}
}
print "Info|Writing: "NewTrack
# Check for advanced options
if (FFmpegADV) FFmpegOPTS=FFmpegADV
else FFmpegOPTS="-c:v copy -map 0 -y -acodec libmp3lame "BrCommand" -write_id3v1 1 -id3v2_version 3"
# Convert the track
if (Debug >= 1) print "Debug|Executing: nice "FFmpeg" -loglevel "FFmpegLOG" -nostdin -i \""Track"\" "FFmpegOPTS" \""NewTrack"\""
Result=system("nice "FFmpeg" -loglevel "FFmpegLOG" -nostdin -i \""Track"\" "FFmpegOPTS" \""NewTrack"\" 2>&1")
Command = "nice "FFmpeg" -loglevel "FFmpegLOG" -nostdin -i \""Track"\" "FFmpegOPTS" "Metadata"\""NewTrack"\" 2>&1"
if (Debug >= 1) print "Debug|Executing: "Command
Result = system(Command)
if (Debug >= 2) print "Debug|ffmpeg exited"
if (Result) {
print "Error|Exit code "Result" converting \""Track"\""
} else {
# Build system command to set owner and permissions, etc.
if (Keep == 1) {
# Do not delete the source file
if (Debug >= 1) print "Debug|Keeping original: \""Track"\" and setting permissions on \""NewTrack"\""
Command="if [ -s \""NewTrack"\" ]; then if [ -f \""Track"\" ]; then chown --reference=\""Track"\" \""NewTrack"\"; chmod --reference=\""Track"\" \""NewTrack"\"; fi; fi"
Command = "if [ -s \""NewTrack"\" ]; then if [ -f \""Track"\" ]; then chown --reference=\""Track"\" \""NewTrack"\"; chmod --reference=\""Track"\" \""NewTrack"\"; fi; fi"
} else {
if (Recycle == "") {
# No Recycle Bin, so check for non-zero size new file and delete the old one
if (Debug >= 1) print "Debug|Deleting: \""Track"\" and setting permissions on \""NewTrack"\""
Command="if [ -s \""NewTrack"\" ]; then if [ -f \""Track"\" ]; then chown --reference=\""Track"\" \""NewTrack"\"; chmod --reference=\""Track"\" \""NewTrack"\"; rm \""Track"\"; fi; fi"
Command = "if [ -s \""NewTrack"\" ]; then if [ -f \""Track"\" ]; then chown --reference=\""Track"\" \""NewTrack"\"; chmod --reference=\""Track"\" \""NewTrack"\"; rm \""Track"\"; fi; fi"
} else {
# Recycle Bin is configured, so check if it exists, append a relative path to it from the track, check for non-zero size new file, and move the old one to the Recycle Bin
match(Track,/^\/?[^\/]+\//)
RecPath=substr(Track,RSTART+RLENGTH)
sub(/[^\/]+$/,"",RecPath)
RecPath=Recycle RecPath
if (Root == "") {
# Legacy way in case the Root music folder cannot be determined
print "Warning|Root music folder is blank. Falling back to legacy split method."
match(Track, /^\/?[^\/]+\//)
} else {
# The following logic tests that the Root folder is a substring of the Track folder, though based on the observed Lidarr behavior this should be a safe assumption. A warning is displayed just in case.
RSTART = index(Track, Root)
if (RSTART) {
RLENGTH = length(Root)
} else {
print "Warning|The root music folder \""Root"\" is not part of \""Track"\". Recycled tracks may not appear as expected."
RLENGTH = 0
}
}
if (Debug >= 2) print "Debug|Splitting track name on RSTART: "RSTART", RLENGTH: "RLENGTH" to prepend recycle path."
RecPath = substr(Track, RSTART + RLENGTH)
sub(/^\/+/, "", RecPath) # remove trailing track name
sub(/[^\/]+$/, "", RecPath) # remove leading backslash
RecPath = Recycle RecPath
if (Debug >= 1) print "Debug|Recycling: \""Track"\" to \""RecPath"\" and setting permissions on \""NewTrack"\""
Command="if [ ! -e \""RecPath"\" ]; then mkdir -p \""RecPath"\"; fi; if [ -s \""NewTrack"\" ]; then if [ -f \""Track"\" ]; then chown --reference=\""Track"\" \""NewTrack"\"; chmod --reference=\""Track"\" \""NewTrack"\"; mv -t \""RecPath"\" \""Track"\"; fi; fi"
Command = "if [ ! -e \""RecPath"\" ]; then mkdir -p \""RecPath"\"; fi; if [ -s \""NewTrack"\" ]; then if [ -f \""Track"\" ]; then chown --reference=\""Track"\" \""NewTrack"\"; chmod --reference=\""Track"\" \""NewTrack"\"; mv -t \""RecPath"\" \""Track"\"; fi; fi"
}
}
# Set owner, permissions, etc.
if (Debug >= 2) print "Debug|Executing: "Command
system(Command)
Result = system(Command)
if (Result) {
print "Error|Exit code "Result" setting permissions and/or recycling on \""NewTrack"\""
}
}
}
' | log
@ -535,7 +691,7 @@ BEGIN {
flac2mp3_return="${PIPESTATUS[1]}" # captures awk exit status
[ $flac2mp3_debug -ge 2 ] && echo "Debug|awk exited with code: $flac2mp3_return" | log
if [ "$flac2mp3_return" != 0 ]; then
flac2mp3_message="Error|[$flac2mp3_return] Script exited abnormally. File permissions issue?"
flac2mp3_message="Error|[$flac2mp3_return] Script exited abnormally. File permissions issue?"
echo "$flac2mp3_message" | log
echo "$flac2mp3_message" >&2
exit 10