diff --git a/.assets/lidarr-synology-2.png b/.assets/lidarr-synology-2.png new file mode 100644 index 0000000..eb9f221 Binary files /dev/null and b/.assets/lidarr-synology-2.png differ diff --git a/.assets/lidarr-synology.png b/.assets/lidarr-synology.png index d29fe58..e0f58bd 100644 Binary files a/.assets/lidarr-synology.png and b/.assets/lidarr-synology.png differ diff --git a/README.md b/README.md index c61f41d..d5bb8f3 100644 --- a/README.md +++ b/README.md @@ -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} | {-v|--quality} | {-a|--advanced} "" {-e|--extension} ]` -OR -`flac2mp3 [OPTIONS] {-f|--file} ` +`flac2mp3 [{-d|--debug} []] [{-b|--bitrate} | {-v|--quality} | {-a|--advanced} "" {-e|--extension} ] [{-f|--file} ] [{-k|--keepfile}] [{-o|--output} ] [{-r|--regex} ''] [{-t|--tags} ]` Where: @@ -76,11 +75,13 @@ Option|Argument|Description -d, --debug|\[\\]|Enables debug logging. Level is optional.
Default of 1 (low).
2 includes API and FFmpeg output. -b, --bitrate|\|Sets the output quality in constant bits per second (CBR).
Examples: 160k, 240k, 300000
**Note:** May not be specified with `-v`, `-a`, or `-e`. -v, --quality|\|Sets the output variable bit rate (VBR).
Specify a value between 0 and 9, with 0 being the highest quality.
See the [FFmpeg MP3 Encoding Guide](https://trac.ffmpeg.org/wiki/Encode/MP3) for more details.
**Note:** May not be specified with `-b`, `-a`, or `-e`. --a, --advanced|\"\\"|Advanced ffmpeg options.
The specified `options` replace all script defaults and are sent directly to ffmpeg.
The `options` value must be enclosed in quotes.
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.
**Note:** Requires the `-e` option to also be specified. May not be specified with `-v` or `-b`.
![danger] **WARNING:** You must specify an audio codec (by including a `-c:a ` ffmpeg option) or the resulting file will contain no audio!
![danger] **WARNING:** Invalid `options` could result in script failure! --e, --extension|\|Sets the output file extension
The extension may be prefixed by a dot (".") or not.
**Note:** Requires the `-a` option to also be specified. May not be specified with `-v` or `-b`. --f, --file||If included, the script enters **[Batch Mode](./README.md#batch-mode)** and converts the specified audio file.
![danger] **WARNING:** Do not use this argument when called from Lidarr! +-a, --advanced|\"\\"|Advanced ffmpeg options.
The specified `options` replace all script defaults and are sent directly to ffmpeg.
The `options` value must be enclosed in quotes.
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.
**Note:** Requires the `-e` option to also be specified. May not be specified with `-v` or `-b`.
![warning] **WARNING:** You must specify an audio codec (by including a `-c:a ` ffmpeg option) or the resulting file will contain no audio!
![warning] **WARNING:** Invalid `options` could result in script failure! +-e, --extension|\|Sets the output file extension.
The extension may be prefixed by a dot (".") or not.
Example: .ogg
**Note:** Requires the `-a` option to also be specified. May not be specified with `-v` or `-b`. +-f, --file||If included, the script enters **[Batch Mode](./README.md#batch-mode)** and converts the specified audio file.
![warning] **WARNING:** Do not use this argument when called from Lidarr! -o, --output|\|Converted audio file(s) are saved to `directory` instead of being located in the same directory as the source audio file.
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.
**Note:** This also disables triggering a Lidarr rescan after conversion. +-r, --regex|'\'|Sets the regular expression used to select input files.
The `regex` value should be enclosed in single quotes and escaped properly.
Defaults to `"\.flac$"`. +-t, --tags|\|Comma separated list of metadata tags to apply automated corrections to.
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: diff --git a/SECURITY.md b/SECURITY.md index b1e653c..cbb6d71 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -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 diff --git a/root/etc/cont-init.d/98-flac2mp3 b/root/etc/cont-init.d/98-flac2mp3 index 9b5de82..9de3545 100644 --- a/root/etc/cont-init.d/98-flac2mp3 +++ b/root/etc/cont-init.d/98-flac2mp3 @@ -44,4 +44,3 @@ if [ ! -x /usr/local/bin/flac2mp3.sh ]; then echo "Making scripts executable." chmod +x /usr/local/bin/flac2*.sh fi - diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-lidarr-flac2mp3-add-package/run b/root/etc/s6-overlay/s6-rc.d/init-mod-lidarr-flac2mp3-add-package/run index 75e655e..ab2abb4 100755 --- a/root/etc/s6-overlay/s6-rc.d/init-mod-lidarr-flac2mp3-add-package/run +++ b/root/etc/s6-overlay/s6-rc.d/init-mod-lidarr-flac2mp3-add-package/run @@ -4,8 +4,8 @@ cat <>> Flac2MP3 Mod by TheCaptain989 <<< Repos: -Dev/test: https://github.com/TheCaptain989/lidarr-flac2mp3 -Prod: https://github.com/linuxserver/docker-mods/tree/lidarr-flac2mp3 + Dev/test: https://github.com/TheCaptain989/lidarr-flac2mp3 + Prod: https://github.com/linuxserver/docker-mods/tree/lidarr-flac2mp3 Version: {{VERSION}} ---------------- @@ -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 diff --git a/root/usr/local/bin/flac2mp3-tags.sh b/root/usr/local/bin/flac2mp3-tags.sh new file mode 100644 index 0000000..6bf4d4f --- /dev/null +++ b/root/usr/local/bin/flac2mp3-tags.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +. /usr/local/bin/flac2mp3.sh --tags disc,genre diff --git a/root/usr/local/bin/flac2mp3.sh b/root/usr/local/bin/flac2mp3.sh index f95fef8..95a66fd 100755 --- a/root/usr/local/bin/flac2mp3.sh +++ b/root/usr/local/bin/flac2mp3.sh @@ -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 | -v | -a \"\" -e ] - $0 [OPTIONS] {-f|--file} + $0 [{-d|--debug} []] [{-b|--bitrate} | {-v|--quality} | {-a|--advanced} \"\" {-e|--extension} ] [{-f|--file} ] [{-k|--keepfile}] [{-o|--output} ] [{-r|--regex} ''] [{-t|--tags} ] Options: - -d, --debug [] enable debug logging - Level is optional, default of 1 (low) - -b, --bitrate set output quality in constant bits per second + -d, --debug [] Enable debug logging + level is optional, between 1-3 + 1 is lowest, 3 is highest + [default: 1] + -b, --bitrate Set output quality in constant bits per second [default: 320k] Ex: 160k, 240k, 300000 - -v, --quality set variable bitrate; quality between 0-9 + -v, --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 \"\" advanced ffmpeg options enclosed in quotes + -a, --advanced \"\" 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 file extension for output file, with or without + -e, --extension File extension for output file, with or without dot Required when -a is specified! - -f, --file the script enters batch mode, using the + -f, --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 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 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 '' Regular expression used to select input files + [default: \.flac$] + -t, --tags 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 " + echo -e "Error|Unknown or missing 'lidarr_eventtype' environment variable: ${flac2mp3_type}\nNot called from Lidarr.\nTry using Batch Mode option: -f " exit 7 fi @@ -323,13 +364,14 @@ 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 done return $flac2mp3_return } +### End Functions # Check for required binaries if [ ! -f "/usr/bin/ffmpeg" ]; then @@ -338,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 @@ -392,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 @@ -411,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 @@ -423,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 @@ -439,6 +519,7 @@ fi #find "$lidarr_artist_path" -name "*.flac" -exec bash -c 'ffmpeg -loglevel warning -i "{}" -y -acodec libmp3lame -b:a 320k "${0/.flac}.mp3" && rm "{}"' {} \; #### BEGIN MAIN +# Build dynamic log message flac2mp3_message="Info|Lidarr event: ${lidarr_eventtype}" if [ "$flac2mp3_type" != "batch" ]; then flac2mp3_message+=", Artist: ${lidarr_artist_name} (${lidarr_artist_id}), Album: ${lidarr_album_title} (${lidarr_album_id})" @@ -454,86 +535,163 @@ 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 +# Process tracks echo -n "$flac2mp3_tracks" | awk -v Debug=$flac2mp3_debug \ -v Recycle="$flac2mp3_recyclebin" \ -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 - #### END MAIN # Check for awk script completion 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