From 64b36034eecba9460af7674317d6345580aaba10 Mon Sep 17 00:00:00 2001 From: TheCaptain989 Date: Sun, 27 Nov 2022 09:56:09 -0600 Subject: [PATCH] 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 --- .assets/lidarr-synology-2.png | Bin 0 -> 17591 bytes README.md | 88 ++++-- SECURITY.md | 4 +- root/etc/cont-init.d/98-flac2mp3 | 1 - .../init-mod-lidarr-flac2mp3-add-package/run | 31 +- root/usr/local/bin/flac2mp3-tags.sh | 3 + root/usr/local/bin/flac2mp3.sh | 286 ++++++++++++++---- 7 files changed, 313 insertions(+), 100 deletions(-) create mode 100644 .assets/lidarr-synology-2.png create mode 100644 root/usr/local/bin/flac2mp3-tags.sh diff --git a/.assets/lidarr-synology-2.png b/.assets/lidarr-synology-2.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9f2215a56e06ce9a7af3d41ba517812fe4e738 GIT binary patch literal 17591 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index c61f41d..d8d94d6 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 edb63c7..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 @@ -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 6fc9516..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,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