diff --git a/.assets/lidarr-custom-script.png b/.assets/lidarr-custom-script.png new file mode 100644 index 0000000..6a68132 Binary files /dev/null and b/.assets/lidarr-custom-script.png differ diff --git a/.assets/lidarr-synology.png b/.assets/lidarr-synology.png new file mode 100644 index 0000000..d29fe58 Binary files /dev/null and b/.assets/lidarr-synology.png differ diff --git a/.github/workflows/BuildImage.yml b/.github/workflows/BuildImage.yml index 7758aa8..2d363b4 100644 --- a/.github/workflows/BuildImage.yml +++ b/.github/workflows/BuildImage.yml @@ -6,8 +6,8 @@ jobs: build: env: DOCKERHUB: "linuxserver/mods" #don't modify - BASEIMAGE: "baseimagename" #replace - MODNAME: "modname" #replace + BASEIMAGE: "lidarr" #replace + MODNAME: "flac2mp3" #replace runs-on: ubuntu-latest steps: @@ -17,7 +17,6 @@ jobs: id: build run: | docker build --no-cache -t ${DOCKERHUB}:${BASEIMAGE}-${MODNAME}-${{ github.sha }} . - - name: Push image if: ${{ github.ref == format('refs/heads/{0}-{1}', env.BASEIMAGE, env.MODNAME) }} run: | diff --git a/Dockerfile b/Dockerfile index 4ece5e8..45aa101 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,20 @@ +## Buildstage ## +FROM lsiobase/ubuntu:xenial as buildstage + +# Build arguments +ARG VERSION + +# Add version number for use in container init script +RUN mkdir -p /root-layer/etc && \ + echo "$VERSION" > /root-layer/etc/version.tc989 + +# Stage local files +COPY root/ /root-layer/ + +## Single layer deployed image ## FROM scratch -LABEL maintainer="username" +LABEL maintainer="TheCaptain989" -# copy local files -COPY root/ / +# Copy files from buildstage +COPY --from=buildstage /root-layer/ / diff --git a/Dockerfile.complex b/Dockerfile.complex deleted file mode 100644 index bc97902..0000000 --- a/Dockerfile.complex +++ /dev/null @@ -1,23 +0,0 @@ -## Buildstage ## -FROM lsiobase/alpine:3.12 as buildstage - -RUN \ - echo "**** install packages ****" && \ - apk add --no-cache \ - curl && \ - echo "**** grab rclone ****" && \ - mkdir -p /root-layer && \ - curl -o \ - /root-layer/rclone.deb -L \ - "https://downloads.rclone.org/v1.47.0/rclone-v1.47.0-linux-amd64.deb" - -# copy local files -COPY root/ /root-layer/ - -## Single layer deployed image ## -FROM scratch - -LABEL maintainer="username" - -# Add files from buildstage -COPY --from=buildstage /root-layer/ / diff --git a/README.md b/README.md index 5636dec..fdb191d 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,91 @@ -# Rsync - Docker mod for openssh-server +A [Docker Mod](https://github.com/linuxserver/docker-mods) for the LinuxServer.io Lidarr Docker container that adds a script to automatically convert downloaded FLAC files to MP3s using ffmpeg. Default quality is 320Kbps. -This mod adds rsync to openssh-server, to be installed/updated during container start. +>**NOTE:** This mod support Linux OSes only. -In openssh-server docker arguments, set an environment variable `DOCKER_MODS=linuxserver/mods:openssh-server-rsync` +Container info: +![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/linuxserver/mods/lidarr-flac2mp3)) -If adding multiple mods, enter them in an array separated by `|`, such as `DOCKER_MODS=linuxserver/mods:openssh-server-rsync|linuxserver/mods:openssh-server-mod2` +# Installation +1. Pull the [linuxserver/lidarr](https://hub.docker.com/r/linuxserver/lidarr "LinuxServer.io's Lidarr container") docker image from Docker Hub: + `docker pull linuxserver/lidarr:latest` -# Mod creation instructions +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 the **DOCKER_MODS** environment variable to the `docker create` command, as follows: + `-e DOCKER_MODS=linuxserver/mods:lidarr-flac2mp3` -* Fork the repo, create a new branch based on the branch `template`. -* Edit the `Dockerfile` for the mod. `Dockerfile.complex` is only an example and included for reference; it should be deleted when done. -* Inspect the `root` folder contents. Edit, add and remove as necessary. -* Edit this readme with pertinent info, delete these instructions. -* Finally edit the `.github/workflows/BuildImage.yml`. Customize the build branch, and the vars for `BASEIMAGE` and `MODNAME`. -* Ask the team to create a new branch named `-`. Baseimage should be the name of the image the mod will be applied to. The new branch will be based on the `template` branch. -* Submit PR against the branch created by the team. + *Example Synology Configuration* + ![flac2mp3](.assets/lidarr-synology.png "Synology container settings") + + 2. Start the container. + +3. After all of the above configuration is complete, to use ffmpeg, configure a custom script from the Settings->Connect screen and type the following in the **Path** field: + `/usr/local/bin/flac2mp3.sh` + +## Usage +New file(s) with an MP3 extension will be placed in the same directory as the original FLAC file(s). Existing MP3 files with the same track name will be overwritten. + +If you've configured the Lidarr Recycle Bin path correctly, the original video will be moved there. +![warning24] **NOTE:** If you have *not* configured the Recycle Bin, the original FLAC audio file(s) will be deleted and permanently lost. + +### 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. + +The script accepts two options which may be placed in the **Arguments** field: + +`[-d] [-b ]` + +The `-b bitrate` option, if specified, sets the output quality in bits per second. If no `-b` option is specified, the script will default to 320Kbps. + +The `-d` option enables debug logging. + +### Examples +``` +-b 320k # Output 320 kilobits per second MP3 (same as default behavior) +-d -b 160k # Enable debugging, and output 160 kilobits per second MP3 +``` + +### Included Wrapper Script +For your convenience, a wrapper script to enable debugging is included in the `/usr/local/bin/` directory. +Use this script in place of the `flac2mp3.sh` mentioned in the [Installation](./README.md#installation) section above. + +``` +flac2mp3-debug.sh # Enable debugging +``` + +### Example Wrapper Script +To configure the last entry from the [Examples](./README.md#examples) section above, create and save a file called `wrapper.sh` to `/usr/local/bin` containing the following text: +``` +#!/bin/bash + +. /usr/local/bin/flac2mp3.sh -d -b 160k +``` +Then put `/usr/local/bin/wrapper.sh` in the **Path** field in place of `/usr/local/bin/flac2mp3.sh` mentioned in the [Installation](./README.md#installation) section above. + +### Triggers +The only events/notification triggers that have been tested are **On Release Import** and **On Upgrade** + +![lidarr-flac2mp3](.assets/lidarr-custom-script.png "Lidarr Custom Script dialog") + +### Logs +A log file is created for the script activity called: + +`/config/logs/flac2mp3.txt` + +This log can be downloaded from the Lidarr GUI under System->Log Files + +Log rotation is performed, with 5 log files of 1MB each kept, matching Lidarr's log retention. +>![warning24] **NOTE:** If debug logging is enabled, the log file can grow very large very quickly. *Do not leave debug logging enabled permanently.* + +___ +# Credits +This would not be possible without the following: + +[Lidarr](https://lidarr.audio/ "Lidarr homepage") +[LinuxServer.io Lidarr](https://hub.docker.com/r/linuxserver/lidarr "Lidarr Docker container") container +[LinuxServer.io Docker Mods](https://hub.docker.com/r/linuxserver/mods "Docker Mods containers") project +[ffmpeg](https://ffmpeg.org/ "FFMpeg homepage") + +[warning]: http://files.softicons.com/download/application-icons/32x32-free-design-icons-by-aha-soft/png/32/Warning.png "Warning" +[warning24]: http://files.softicons.com/download/toolbar-icons/24x24-free-pixel-icons-by-aha-soft/png/24x24/Warning.png "Warning" diff --git a/root/etc/cont-init.d/98-flac2mp3 b/root/etc/cont-init.d/98-flac2mp3 new file mode 100644 index 0000000..7943d61 --- /dev/null +++ b/root/etc/cont-init.d/98-flac2mp3 @@ -0,0 +1,46 @@ +#!/usr/bin/with-contenv bash + +cat <>> Flac2MP3 Mod by TheCaptain989 <<< +Repo: https://github.com/linuxserver/docker-mods/tree/lidarr-flac2mp3 + +Version: $(cat /etc/version.tc989) +---------------- +EOF + +# Determine if setup is needed +if [ ! -f /usr/bin/ffmpeg ]; then + echo "Running first time setup." + + if [ -f /usr/bin/apt ]; then + # Ubuntu + echo "Installing ffmpeg using apt-get" + apt-get update && \ + apt-get -y install ffmpeg && \ + rm -rf /var/lib/apt/lists/* + elif [ -f /sbin/apk ]; then + # Alpine + echo "Installing ffmpeg using apk" + apk add --no-cache ffmpeg && \ + rm -rf /var/lib/apt/lists/* + else + # Unknown + echo "Unknown package manager. Attempting to install ffmpeg using apt-get" + apt-get update && \ + apt-get -y install ffmpeg && \ + rm -rf /var/lib/apt/lists/* + fi +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/flac2mp3*.sh +fi + +# Make executable +if [ ! -x /usr/local/bin/flac2mp3.sh ]; then + echo "Making scripts executable." + chmod +x /usr/local/bin/flac2mp3*.sh +fi diff --git a/root/etc/cont-init.d/98-vpn-config b/root/etc/cont-init.d/98-vpn-config deleted file mode 100644 index a5f9127..0000000 --- a/root/etc/cont-init.d/98-vpn-config +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/with-contenv bash - -# Determine if setup is needed -if [ ! -f /usr/local/lib/python***/dist-packages/sshuttle ] && \ -[ -f /usr/bin/apt ]; then - ## Ubuntu - apt-get update - apt-get install --no-install-recommends -y \ - iptables \ - openssh-client \ - python3 \ - python3-pip - pip3 install sshuttle -fi -if [ ! -f /usr/lib/python***/site-packages/sshuttle ] && \ -[ -f /sbin/apk ]; then - # Alpine - apk add --no-cache \ - iptables \ - openssh \ - py3-pip \ - python3 - pip3 install sshuttle -fi - -chown -R root:root /root -chmod -R 600 /root/.ssh diff --git a/root/etc/services.d/sshvpn/run b/root/etc/services.d/sshvpn/run deleted file mode 100644 index 7d49e79..0000000 --- a/root/etc/services.d/sshvpn/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/with-contenv bash - -sshuttle --dns --remote root@${HOST}:${PORT} 0/0 -x 172.17.0.0/16 diff --git a/root/usr/local/bin/flac2mp3-debug.sh b/root/usr/local/bin/flac2mp3-debug.sh new file mode 100644 index 0000000..bb698ee --- /dev/null +++ b/root/usr/local/bin/flac2mp3-debug.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +. /usr/local/bin/flac2mp3.sh -d diff --git a/root/usr/local/bin/flac2mp3.sh b/root/usr/local/bin/flac2mp3.sh new file mode 100644 index 0000000..02b5891 --- /dev/null +++ b/root/usr/local/bin/flac2mp3.sh @@ -0,0 +1,256 @@ +#!/bin/bash + +# Script to convert FLAC files to MP3 using FFMpeg +# https://github.com/linuxserver/docker-mods/tree/lidarr-flac2mp3 +# Can also process MP3s and tag them appropriately +# Resultant MP3s are fully tagged + +# Dependencies: +# ffmpeg +# awk +# stat +# nice + +# Exit codes: +# 0 - success; or test +# 1 - no tracks files specified on command line +# 2 - mkvmerge not found +# 10 - awk script generated an error + +### Variables +export flac2mp3_script=$(basename "$0") +export flac2mp3_pid=$$ +export flac2mp3_config=/config/config.xml +export flac2mp3_log=/config/logs/flac2mp3.txt +export flac2mp3_maxlogsize=1024000 +export flac2mp3_maxlog=4 +export flac2mp3_debug=0 +export flac2mp3_tracks="$lidarr_addedtrackpaths" +[ -z "$flac2mp3_tracks" ] && flac2mp3_tracks="$lidarr_trackfile_path" # For other event type +export flac2mp3_recyclebin=$(sqlite3 /config/lidarr.db 'SELECT Value FROM Config WHERE Key="recyclebin"') +RET=$?; [ "$RET" != 0 ] && >&2 echo "WARNING[$RET]: Unable to read recyclebin information from database \"/config/lidarr.db\"" + +### Functions +function usage { + usage=" +$flac2mp3_script +Audio conversion script designed for use with Bazarr + +Source: https://github.com/TheCaptain989/lidarr-flac2mp3 + +Usage: + $0 [-d] [-b ] + +Arguments: + bitrate # output quality in bits per second (SI units) + +Options: + -d # enable debug logging + -b # set bitrate; default 320K + +Examples: + $flac2mp3_script -b 320k # Output 320 kilobits per second MP3 + (same as default behavior) + $flac2mp3_script -d -b 160k # Enable debugging, and output quality + 160 kilobits per second +" + >&2 echo "$usage" +} +# Can still go over flac2mp3_maxlog if read line is too long +# Must include whole function in subshell for read to work! +function log {( + while read + do + echo $(date +"%y-%-m-%-d %H:%M:%S.%1N")\|"[$flac2mp3_pid]$REPLY" >>"$flac2mp3_log" + local FILESIZE=$(stat -c %s "$flac2mp3_log") + if [ $FILESIZE -gt $flac2mp3_maxlogsize ] + then + for i in `seq $((flac2mp3_maxlog-1)) -1 0` + do + [ -f "${flac2mp3_log::-4}.$i.txt" ] && mv "${flac2mp3_log::-4}."{$i,$((i+1))}".txt" + done + [ -f "${flac2mp3_log::-4}.txt" ] && mv "${flac2mp3_log::-4}.txt" "${flac2mp3_log::-4}.0.txt" + touch "$flac2mp3_log" + fi + done +)} +# Inspired by https://stackoverflow.com/questions/893585/how-to-parse-xml-in-bash +function read_xml { + local IFS=\> + read -d \< ENTITY CONTENT +} +# Initiate API Rescan request +function rescan { + MSG="Info|Calling Lidarr API to rescan artist" + echo "$MSG" | log + [ $flac2mp3_debug -eq 1 ] && echo "Debug|Forcing rescan of artist '$lidarr_artist_id'. Calling Lidarr API 'RefreshArtist' using POST and URL 'http://$flac2mp3_bindaddress:$flac2mp3_port$flac2mp3_urlbase/api/v1/command?apikey=(removed)'" | log + RESULT=$(curl -s -d "{name: 'RefreshArtist', artistId: $lidarr_artist_id}" -H "Content-Type: application/json" \ + -X POST http://$flac2mp3_bindaddress:$flac2mp3_port$flac2mp3_urlbase/api/v1/command?apikey=$flac2mp3_apikey) + [ $flac2mp3_debug -eq 1 ] && echo "API returned: $RESULT" | awk '{print "Debug|"$0}' | log + JOBID="$(echo $RESULT | jq -crM .id)" + if [ "$JOBID" != "null" ]; then + local RET=0 + else + local RET=1 + fi + return $RET +} +# Check result of rescan job +function check_rescan { + local i=0 + for ((i=1; i <= 15; i++)); do + [ $flac2mp3_debug -eq 1 ] && echo "Debug|Checking job $JOBID completion, try #$i. Calling Lidarr API using GET and URL 'http://$flac2mp3_bindaddress:$flac2mp3_port$flac2mp3_urlbase/api/command/$JOBID?apikey=(removed)'" | log + RESULT=$(curl -s -H "Content-Type: application/json" \ + -X GET http://$flac2mp3_bindaddress:$flac2mp3_port$flac2mp3_urlbase/api/v1/command/$JOBID?apikey=$flac2mp3_apikey) + [ $flac2mp3_debug -eq 1 ] && echo "API returned: $RESULT" | awk '{print "Debug|"$0}' | log + if [ "$(echo $RESULT | jq -crM .status)" = "completed" ]; then + local RET=0 + break + else + if [ "$(echo $RESULT | jq -crM .status)" = "failed" ]; then + local RET=2 + break + else + local RET=1 + sleep 1 + fi + fi + done + return $RET +} +# Process options +while getopts ":db:" opt; do + case ${opt} in + d ) # For debug purposes only + MSG="Debug|Enabling debug logging." + echo "$MSG" | log + >&2 echo "$MSG" + flac2mp3_debug=1 + printenv | sort | sed 's/^/Debug|/' | log + ;; + b ) # Set bitrate + flac2mp3_bitrate="$OPTARG" + ;; + : ) + MSG="Error|Invalid option: -$OPTARG requires an argument" + echo "$MSG" | log + >&2 echo "$MSG" + ;; + esac +done +shift $((OPTIND -1)) + +# Set default bitrate +[ -z "$flac2mp3_bitrate" ] && flac2mp3_bitrate="320k" + +if [[ "$lidarr_eventtype" = "Test" ]]; then + echo "Info|Lidarr event: $lidarr_eventtype" | log + echo "Info|Script was test executed successfully." | log + exit 0 +fi + +if [ -z "$flac2mp3_tracks" ]; then + MSG="Error|No track file(s) specified! Not called from Lidarr?" + echo "$MSG" | log + >&2 echo "$MSG" + usage + exit 1 +fi + +if [ ! -f "/usr/bin/ffmpeg" ]; then + MSG="Error|/usr/bin/ffmpeg is required by this script" + echo "$MSG" | log + >&2 echo "$MSG" + exit 2 +fi + +# Legacy one-liner script +#find "$lidarr_artist_path" -name "*.flac" -exec bash -c 'ffmpeg -loglevel warning -i "{}" -y -acodec libmp3lame -b:a 320k "${0/.flac}.mp3" && rm "{}"' {} \; + +#### MAIN +echo "Info|Lidarr event: $lidarr_eventtype, Artist: $lidarr_artist_name ($lidarr_artist_id), Album: $lidarr_album_title ($lidarr_album_id), Export bitrate: $flac2mp3_bitrate, Tracks: $flac2mp3_tracks" | log +echo "$flac2mp3_tracks" | awk -v Debug=$flac2mp3_debug \ +-v Recycle="$flac2mp3_recyclebin" \ +-v Bitrate=$flac2mp3_bitrate ' +BEGIN { + FFMpeg="/usr/bin/ffmpeg" + FS="|" + RS="|" + IGNORECASE=1 +} +/\.flac/ { + Track=$1 + sub(/\n/,"",Track) + NewTrack=substr(Track, 1, length(Track)-5)".mp3" + print "Info|Writing: "NewTrack + if (Debug) print "Debug|Executing: nice "FFMpeg" -loglevel error -i \""Track"\" "CoverCmds1"-map 0 -y -acodec libmp3lame -b:a "Bitrate" -write_id3v1 1 -id3v2_version 3 "CoverCmds2"\""NewTrack"\"" + Result=system("nice "FFMpeg" -loglevel error -i \""Track"\" "CoverCmds1"-map 0 -y -acodec libmp3lame -b:a "Bitrate" -write_id3v1 1 -id3v2_version 3 "CoverCmds2"\""NewTrack"\" 2>&1") + if (Result) { + print "Error|"Result" converting \""Track"\"" + } else { + if (Recycle=="") { + if (Debug) print "Debug|Deleting: \""Track"\"" + system("[ -s \""NewTrack"\" ] && [ -f \""Track"\" ] && rm \""Track"\"") + } else { + match(Track,/^\/?[^\/]+\//) + RecPath=substr(Track,RSTART+RLENGTH) + sub(/[^\/]+$/,"",RecPath) + RecPath=Recycle RecPath + if (Debug) print "Debug|Moving: \""Track"\" to \""RecPath"\"" + system("[ ! -e \""RecPath"\" ] && mkdir -p \""RecPath"\"; [ -s \""NewTrack"\" ] && [ -f \""Track"\" ] && mv -t \""RecPath"\" \""Track"\"") + } + } +} +' | log + +RET="${PIPESTATUS[1]}" # captures awk exit status +if [ $RET != "0" ]; then + # Check for script completion and non-empty file + MSG="Error|Script exited abnormally. File permissions issue?" + echo "$MSG" | log + >&2 echo "$MSG" + exit 10 +fi + +# Call Lidarr API to RescanArtist +if [ ! -z "$lidarr_artist_id" ]; then + if [ -f "$flac2mp3_config" ]; then + # Read Lidarr config.xml + while read_xml; do + [[ $ENTITY = "Port" ]] && flac2mp3_port=$CONTENT + [[ $ENTITY = "UrlBase" ]] && flac2mp3_urlbase=$CONTENT + [[ $ENTITY = "BindAddress" ]] && flac2mp3_bindaddress=$CONTENT + [[ $ENTITY = "ApiKey" ]] && flac2mp3_apikey=$CONTENT + done < $flac2mp3_config + + [[ $flac2mp3_bindaddress = "*" ]] && flac2mp3_bindaddress=localhost + + # Scan the disk for the new audio tracks + if rescan; then + # Check that the rescan completed + if ! check_rescan; then + # Timeout or failure + MSG="Warn|Lidarr job ID $JOBID timed out or failed." + echo "$MSG" | log + >&2 echo "$MSG" + fi + else + # Error from API + MSG="Error|The 'RefreshArtist' API with artist $lidarr_artist_id failed." + echo "$MSG" | log + >&2 echo "$MSG" + fi + else + MSG="Warn|Unable to locate Lidarr config file: '$flac2mp3_config'" + echo "$MSG" | log + >&2 echo "$MSG" + fi +else + MSG="Warn|Missing environment variable lidarr_artist_id" + echo "$MSG" | log + >&2 echo "$MSG" +fi + +# Cool bash feature +MSG="Info|Completed in $(($SECONDS/60))m $(($SECONDS%60))s" +echo "$MSG" | log