diff --git a/.assets/striptracks-synology.png b/.assets/striptracks-synology.png new file mode 100644 index 0000000..84c9af7 Binary files /dev/null and b/.assets/striptracks-synology.png differ diff --git a/.assets/striptracks-v2-custom-script.png b/.assets/striptracks-v2-custom-script.png new file mode 100644 index 0000000..7a2b2fe Binary files /dev/null and b/.assets/striptracks-v2-custom-script.png differ diff --git a/.assets/striptracks-v3-custom-script.png b/.assets/striptracks-v3-custom-script.png new file mode 100644 index 0000000..d9e8564 Binary files /dev/null and b/.assets/striptracks-v3-custom-script.png differ diff --git a/.travis.yml b/.travis.yml index e6e5b1f..7c89849 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ language: shell branches: only: - - - #replace variables, omit brackets + - radarr-striptracks #replace variables, omit brackets services: - docker @@ -12,21 +12,29 @@ services: env: global: - DOCKERHUB="linuxserver/mods" #don't modify - - BASEIMAGE="baseimagename" #replace - - MODNAME="modname" #replace + - BASEIMAGE="radarr" #replace + - MODNAME="striptracks" #replace jobs: include: - stage: PR-BuildImage if: (type IN (pull_request)) script: + # Build variables + - VERSION=$(git describe --tags --always) # Build image - - docker build --no-cache -t ${DOCKERHUB}:${BASEIMAGE}-${MODNAME}-${TRAVIS_COMMIT} . + - docker build --no-cache + --build-arg VERSION=${VERSION} + -t ${DOCKERHUB}:${BASEIMAGE}-${MODNAME}-${TRAVIS_COMMIT} . - stage: BuildImage if: (NOT (type IN (pull_request))) script: + # Build variables + - VERSION=$(git describe --tags --always) # Build image - - docker build --no-cache -t ${DOCKERHUB}:${BASEIMAGE}-${MODNAME}-${TRAVIS_COMMIT} . + - docker build --no-cache + --build-arg VERSION=${VERSION} + -t ${DOCKERHUB}:${BASEIMAGE}-${MODNAME}-${TRAVIS_COMMIT} . - docker tag ${DOCKERHUB}:${BASEIMAGE}-${MODNAME}-${TRAVIS_COMMIT} ${DOCKERHUB}:${BASEIMAGE}-${MODNAME} # Login to DockerHub - echo $DOCKERPASS | docker login -u $DOCKERUSER --password-stdin 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 4463d83..0000000 --- a/Dockerfile.complex +++ /dev/null @@ -1,21 +0,0 @@ -## Buildstage ## -FROM lsiobase/alpine:3.9 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 - -# Add files from buildstage -COPY --from=buildstage /root-layer/ / diff --git a/README.md b/README.md index 62f203f..85e0b11 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,176 @@ -# Rsync - Docker mod for openssh-server +A [Docker Mod](https://github.com/linuxserver/docker-mods) for the LinuxServer.io Radarr/Sonarr Docker container that adds a script to automatically strip out unwanted audio and subtitle streams, keeping only the desired languages. +Chapters, if they exist, are preserved. The Title attribute in the MKV is set to the movie title plus year (ex: `The Sting (1973)`) or the series title plus episode information (ex: `Happy! 01x01 - What Smiles Are For`). -This mod adds rsync to openssh-server, to be installed/updated during container start. +**One unified script works in both Radarr and Sonarr. Use this mod in either container!** +>**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/radarr-striptracks) -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 +>**NOTE:** See the [Sonarr/Radarr v3 Builds](./README.md#sonarrradarr-v3-builds) section below for important differences to these instructions for v3 builds. -# Mod creation instructions +1. Pull your selected container ([linuxserver/radarr](https://hub.docker.com/r/linuxserver/radarr "LinuxServer.io's Radarr container") or [linuxserver/sonarr](https://hub.docker.com/r/linuxserver/sonarr "LinuxServer.io's Sonarr container")) from Docker Hub: + `docker pull linuxserver/radarr:latest` OR + `docker pull linuxserver/sonarr:latest` -* 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 `travis.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. +2. Configure the Docker container with all the port, volume, and environment settings from the *original container documentation* here: + **[linuxserver/radarr](https://hub.docker.com/r/linuxserver/radarr "Radarr Docker container")** + **[linuxserver/sonarr](https://hub.docker.com/r/linuxserver/sonarr "Sonarr Docker container")** + 1. Add the **DOCKER_MODS** environment variable to the `docker create` command, as follows: + `-e DOCKER_MODS=linuxserver/mods:radarr-striptracks` + + *Example Synology Configuration* + ![striptracks](.assets/striptracks-synology.png "Synology container settings") + + 2. Start the container. + +3. After all of the above configuration is complete, to use mkvmerge: + 1. Configure a custom script from the Radarr/Sonnar Settings->Connect screen and type the following in the **Path** field: + `/usr/local/bin/striptracks.sh` + + 2. Add the codes for the audio and subtitle languages you want to keep as **Arguments** (details in the [Syntax](./README.md#syntax) section below): + Suggested Example + **`:eng:und :eng`** + +## Usage +>**NOTE:** See the [Sonarr/Radarr v3 Builds](./README.md#sonarrradarr-v3-builds) section below for important differences to these instructions for v3 builds. + +The source video can be any mkvtoolnix supported video format. The output is an MKV file with the same name. + +If you've configured the Radarr/Sonarr Recycle Bin path correctly, the original video will be moved there. +![warning24] **NOTE:** If you have *not* configured the Recycle Bin, the original video file will be deleted/overwritten and permanently lost. + +### Syntax +The script accepts two arguments and one option in the **Arguments** field: + +`[-d] ` + +The arguments are language codes in [ISO639-2](https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes "List of ISO 639-2 codes") format. +These are three letter abbreviations prefixed with a colon ':', such as: + +* :eng +* :fre +* :spa + +...etc. + +Multiple codes may be concatenated, such as `:eng:spa` for both English and Spanish. + +It is suggested to use `:eng:und :eng` if you are unsure of what to choose. This will keep English and Undetermined audio and English subtitles, if they exist. +>**NOTE:** The script is smart enough to not remove the last audio track. This way you don't have to specify every possible language if you are importing a +foreign film, for example. + +The `-d` option enables debug logging. + +### Examples +``` +:eng:und :eng # keep English and Undetermined audio and English subtitles +-d :eng "" # Enable debugging, keeping English audio and no subtitles +:eng:kor:jpn :eng:spa # Keep English, Korean, and Japanese audio, and English and + Spanish subtitles +``` + +## Triggers +The only events/notification triggers that have been tested are **On Download** and **On Upgrade** + +![striptracks](.assets/striptracks-v2-custom-script.png "Radarr/Sonarr custom script settings") + +## Logs +A log file is created for the script activity called: + +`/config/logs/striptracks.txt` + +This log can be inspected or downloaded from the Radarr/Sonarr GUI under System->Logs->Files + +Script errors will show up in both the script log and the native Radarr/Sonarr log. + +Log rotation is performed with 5 log files of 512KB each being kept. +>![warning24] **NOTE:** If debug logging is enabled, the log file can grow very large very quickly. *Do not leave debug logging enabled permanently.* + +___ + +## Sonarr/Radarr v3 Builds +>![warning] **Warning: Unstable Releases** ![warning] +>The Sonarr/Radarr v3 Builds are the unstable releases (Aphrodite and Phantom) of Radarr and Sonarr. Though the mod works with all versions of the container, I cannot guarantee these releases are stable. + +Important differences for Sonarr/Radarr v3 Builds +### Mod installation +Substitute the following steps for those noted in the [Installation](./README.md#installation) section above. +1. Pull your selected container ([linuxserver/radarr](https://hub.docker.com/r/linuxserver/radarr "LinuxServer.io's Radarr container") or [linuxserver/sonarr](https://hub.docker.com/r/linuxserver/sonarr "LinuxServer.io's Sonarr container")) from Docker Hub: + `docker pull linuxserver/radarr:preview` OR + `docker pull linuxserver/sonarr:preview` + +2. Configure the Docker container with all the port, volume, and environment settings from the *original container documentation* here: + **[linuxserver/radarr](https://hub.docker.com/r/linuxserver/radarr "Radarr Docker container")** + **[linuxserver/sonarr](https://hub.docker.com/r/linuxserver/sonarr "Sonarr Docker container")** + 1. Add the **DOCKER_MODS** environment variable to the `docker create` command, as follows: + `-e DOCKER_MODS=linuxserver/mods:radarr-striptracks` + + *Example Synology Configuration* + ![striptracks](.assets/striptracks-synology.png "Synology container settings") + + 2. Start the container. + +3. After the above configuration is complete, to use mkvmerge, configure a custom script from the Settings->Connect screen and type the following in the **Path** field: + + `/usr/local/bin/striptracks-eng.sh` + + This is a wrapper script that calls striptracks.sh with the following arguments, which keep English audio and subtitles only! + `:eng:und :eng` + + *For any other combinations of audio and subtitles you **must** either use one of the [included wrapper scripts](./README.md#included-wrapper-scripts) or + create a custom script with the codes for the languages you want to keep. See the [Syntax](./README.md#syntax) section above. + Do not put `striptracks.sh` in the **Path** field!* + +### Included Wrapper Scripts +>**NOTE:** The **Arguments** field for Custom Scripts was removed in Radarr and Sonarr v3 due to security concerns. To support options with this version and later, +a wrapper script can be manually created that will call *striptracks.sh* with the required arguments. + +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 `striptracks-eng.sh` mentioned in the [Mod installation](./README.md#mod-installation) section above. + +``` +striptracks-dut.sh # Keep Dutch audio and subtitles +striptracks-eng.sh # Keep English and Undetermined audio and English subtitles +striptracks-eng-debug.sh # Keep English and Undetermined audio and English subtitles, and enable debug logging +striptracks-eng-jpn.sh # Keep English, Japanese, and Undetermined audio and English subtitles +striptracks-fre.sh # Keep French audio and subtitles +striptracks-fre-debug.sh # Keep French audio and subtitles, and enable debug logging +striptracks-ger.sh # Keep German audio and subtitles +striptracks-spa.sh # Keep Spanish audio and subtitles +``` + +### 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/striptracks.sh :eng:kor:jpn :eng:spa +``` +Then put `/usr/local/bin/wrapper.sh` in the **Path** field in place of `/usr/local/bin/striptracks-eng.sh` mentioned in the [Mod installation](./README.md#mod-installation) section above. + +### Preview Triggers +The only events/notification triggers that have been tested are **On Import** and **On Upgrade** + +![striptracks](.assets/striptracks-v3-custom-script.png "Radarr/Sonarr custom script settings") + +### Preview Logs +The log can be inspected or downloaded from the Radarr/Sonarr GUI under System->Log Files + +___ + +# Credits + +This would not be possible without the following: + +[Radarr](http://radarr.video/ "Radarr homepage") +[Sonarr](http://sonarr.tv/ "Sonarr homepage") +[LinuxServer.io Radarr](https://hub.docker.com/r/linuxserver/radarr "Radarr Docker container") container +[LinuxServer.io Sonarr](https://hub.docker.com/r/linuxserver/sonarr "Sonarr Docker container") container +[LinuxServer.io Docker Mods](https://hub.docker.com/r/linuxserver/mods "Docker Mods containers") project +[MKVToolNix](https://mkvtoolnix.download/ "MKVToolNix homepage") by Moritz Bunkus +The AWK script parsing mkvmerge output is adapted from Endoro's post on [VideoHelp](https://forum.videohelp.com/threads/343271-BULK-remove-non-English-tracks-from-MKV-container#post2292889). + +[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-striptracks b/root/etc/cont-init.d/98-striptracks new file mode 100644 index 0000000..8aa9ba1 --- /dev/null +++ b/root/etc/cont-init.d/98-striptracks @@ -0,0 +1,47 @@ +#!/usr/bin/with-contenv bash + +cat <>> Striptracks Mod by TheCaptain989 <<< +Repo: https://github.com/linuxserver/docker-mods/tree/radarr-striptracks + +Version: $(cat /etc/version.tc989) +---------------- +EOF + +# Determine if setup is needed +if [ ! -f /usr/bin/mkvmerge ]; then + echo "Running first time setup." + + if [ -f /usr/bin/apt ]; then + # Ubuntu + echo "Installing MKVToolNix using apt-get" + apt-get update && \ + apt-get -y install mkvtoolnix && \ + rm -rf /var/lib/apt/lists/* + elif [ -f /sbin/apk ]; then + # Alpine + echo "Installing MKVToolNix using apk" + apk add --no-cache mkvtoolnix && \ + rm -rf /var/lib/apt/lists/* + else + # Unknown + echo "Unknown package manager. Attempting to install MKVToolNix using apt-get" + apt-get update && \ + apt-get -y install mkvtoolnix && \ + rm -rf /var/lib/apt/lists/* + fi +fi + +# Change ownership +if [ $(stat -c '%G' /usr/local/bin/striptracks.sh) != "abc" ]; then + echo "Changing ownership on scripts." + chown abc:abc /usr/local/bin/striptracks*.sh +fi + +# Make executable +if [ ! -x /usr/local/bin/striptracks.sh ]; then + echo "Making scripts executable." + chmod +x /usr/local/bin/striptracks*.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/striptracks-dut.sh b/root/usr/local/bin/striptracks-dut.sh new file mode 100644 index 0000000..e2c19af --- /dev/null +++ b/root/usr/local/bin/striptracks-dut.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +. /usr/local/bin/striptracks.sh :dut :dut diff --git a/root/usr/local/bin/striptracks-eng-debug.sh b/root/usr/local/bin/striptracks-eng-debug.sh new file mode 100644 index 0000000..87ed2bb --- /dev/null +++ b/root/usr/local/bin/striptracks-eng-debug.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +. /usr/local/bin/striptracks.sh -d :eng:und :eng diff --git a/root/usr/local/bin/striptracks-eng-jpn.sh b/root/usr/local/bin/striptracks-eng-jpn.sh new file mode 100644 index 0000000..ea37f5d --- /dev/null +++ b/root/usr/local/bin/striptracks-eng-jpn.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +. /usr/local/bin/striptracks.sh :eng:jpn:und :eng diff --git a/root/usr/local/bin/striptracks-eng.sh b/root/usr/local/bin/striptracks-eng.sh new file mode 100644 index 0000000..a0eb344 --- /dev/null +++ b/root/usr/local/bin/striptracks-eng.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +. /usr/local/bin/striptracks.sh :eng:und :eng diff --git a/root/usr/local/bin/striptracks-fre-debug.sh b/root/usr/local/bin/striptracks-fre-debug.sh new file mode 100644 index 0000000..918a198 --- /dev/null +++ b/root/usr/local/bin/striptracks-fre-debug.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +. /usr/local/bin/striptracks.sh -d :fre:fra :fre:fra diff --git a/root/usr/local/bin/striptracks-fre.sh b/root/usr/local/bin/striptracks-fre.sh new file mode 100644 index 0000000..998ce43 --- /dev/null +++ b/root/usr/local/bin/striptracks-fre.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +. /usr/local/bin/striptracks.sh :fre:fra :fre:fra diff --git a/root/usr/local/bin/striptracks-ger.sh b/root/usr/local/bin/striptracks-ger.sh new file mode 100644 index 0000000..62047ab --- /dev/null +++ b/root/usr/local/bin/striptracks-ger.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +. /usr/local/bin/striptracks.sh :ger:deu :ger:deu diff --git a/root/usr/local/bin/striptracks-spa.sh b/root/usr/local/bin/striptracks-spa.sh new file mode 100644 index 0000000..92f6c4e --- /dev/null +++ b/root/usr/local/bin/striptracks-spa.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +. /usr/local/bin/striptracks.sh :spa :spa diff --git a/root/usr/local/bin/striptracks.sh b/root/usr/local/bin/striptracks.sh new file mode 100644 index 0000000..9d9034b --- /dev/null +++ b/root/usr/local/bin/striptracks.sh @@ -0,0 +1,489 @@ +#!/bin/bash + +# Video remuxing script designed for use with Radarr and Sonarr +# Automatically strips out unwanted audio and subtitle streams, keeping only the desired languages. +# https://github.com/linuxserver/docker-mods/tree/radarr-striptracks + +# Adapted and corrected from Endoro's post 1/5/2014: +# https://forum.videohelp.com/threads/343271-BULK-remove-non-English-tracks-from-MKV-container#post2292889 +# +# Put a colon `:` in front of every language code. Expects ISO639-2 codes + +# Dependencies: +# mkvmerge +# awk +# curl +# jq +# numfmt +# stat +# nice + +# Exit codes: +# 0 - success; or test +# 1 - no video file specified on command line +# 2 - no audio language specified on command line +# 3 - no subtitle language specified on command line +# 4 - mkvmerge not found +# 5 - specified video file not found +# 6 - unable to rename video to temp video +# 10 - remuxing completed, but no output file found +# 20 - general error + +### Variables +export striptracks_script=$(basename "$0") +export striptracks_pid=$$ +export striptracks_arr_config=/config/config.xml +export striptracks_log=/config/logs/striptracks.txt +export striptracks_maxlogsize=512000 +export striptracks_maxlog=4 +export striptracks_debug=0 +export striptracks_type=$(printenv | sed -n 's/_eventtype *=.*$//p') +if [[ "${striptracks_type,,}" = "radarr" ]]; then + export striptracks_video="$radarr_moviefile_path" + export striptracks_api_endpoint="movie" + export striptracks_json_quality_root=".movieFile" + export striptracks_video_type="movie" + export striptracks_title="$radarr_movie_title ($radarr_movie_year)" +else + if [[ "${striptracks_type,,}" = "sonarr" ]]; then + export striptracks_video="$sonarr_episodefile_path" + export striptracks_api_endpoint="episodefile" + export striptracks_json_quality_root="" + export striptracks_video_type="series" + export striptracks_title="$sonarr_series_title $(numfmt --format "%02f" $sonarr_episodefile_seasonnumber)x$(numfmt --format "%02f" $sonarr_episodefile_episodenumbers) - $sonarr_episodefile_episodetitles" + fi +fi +export striptracks_api="Rescan${striptracks_video_type^}" +export striptracks_json_key="${striptracks_video_type}Id" +export striptracks_api_endpoint_idname="${striptracks_type,,}_${striptracks_api_endpoint}_id" +export striptracks_api_endpoint_id="${!striptracks_api_endpoint_idname}" +export striptracks_video_idname="${striptracks_type,,}_${striptracks_video_type}_id" +export striptracks_video_id="${!striptracks_video_idname}" +export striptracks_eventtype="${striptracks_type,,}_eventtype" +export striptracks_tempvideo="${striptracks_video}.tmp" +export striptracks_newvideo="${striptracks_video%.*}.mkv" +export striptracks_db="/config/${striptracks_type,,}.db" +if [ ! -f "$striptracks_db" ]; then + striptracks_db=/config/nzbdrone.db +fi +export striptracks_recyclebin=$(sqlite3 $striptracks_db 'SELECT Value FROM Config WHERE Key="recyclebin"') +RET=$?; [ "$RET" != 0 ] && >&2 echo "WARNING[$RET]: Unable to read recyclebin information from database \"$striptracks_db\"" + +### Functions +function usage { + usage=" +$striptracks_script +Video remuxing script designed for use with Radarr and Sonarr + +Source: https://github.com/TheCaptain989/radarr-striptracks + +Usage: + $0 [-d] + +Arguments: + audio_languages # ISO639-2 code(s) prefixed with a colon \`:\` + Multiple codes may be concatenated. + subtitle_languages # ISO639-2 code(s) prefixed with a colon \`:\` + Multiple codes may be concatenated. + +Options: + -d # enable debug logging + +Examples: + $striptracks_script :eng:und :eng # keep English and Undetermined audio and + English subtitles + $striptracks_script :eng \"\" # keep English audio and no subtitles + $striptracks_script -d :eng:kor:jpn :eng:spa # Enable debugging, keeping English, Korean, + and Japanese audio, and English and + Spanish subtitles +" + >&2 echo "$usage" +} +# Can still go over striptracks_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")\|"[$striptracks_pid]$REPLY" >>"$striptracks_log" + local FILESIZE=$(stat -c %s "$striptracks_log") + if [ $FILESIZE -gt $striptracks_maxlogsize ] + then + for i in $(seq $((striptracks_maxlog-1)) -1 0); do + [ -f "${striptracks_log::-4}.$i.txt" ] && mv "${striptracks_log::-4}."{$i,$((i+1))}".txt" + done + [ -f "${striptracks_log::-4}.txt" ] && mv "${striptracks_log::-4}.txt" "${striptracks_log::-4}.0.txt" + touch "$striptracks_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 +} +# Get video information +function get_video_info { + [ $striptracks_debug -eq 1 ] && echo "Debug|Getting video information for $striptracks_api_endpoint '$striptracks_api_endpoint_id'. Calling ${striptracks_type^} API using GET and URL 'http://$striptracks_bindaddress:$striptracks_port$striptracks_urlbase/api/$striptracks_api_endpoint/$striptracks_api_endpoint_id?apikey=(removed)'" | log + RESULT=$(curl -s -H "Content-Type: application/json" \ + -X GET http://$striptracks_bindaddress:$striptracks_port$striptracks_urlbase/api/$striptracks_api_endpoint/$striptracks_api_endpoint_id?apikey=$striptracks_apikey) + [ $striptracks_debug -eq 1 ] && echo "API returned: $RESULT" | awk '{print "Debug|"$0}' | log + if [ "$(echo $RESULT | jq -crM .path)" != "null" ]; then + local RET=0 + else + local RET=1 + fi + return $RET +} +# Initiate API Rescan request +function rescan { + MSG="Info|Calling ${striptracks_type^} API to rescan ${striptracks_video_type}, try #$i" + echo "$MSG" | log + [ $striptracks_debug -eq 1 ] && echo "Debug|Forcing rescan of $striptracks_json_key '$striptracks_video_id', try #$i. Calling ${striptracks_type^} API '$striptracks_api' using POST and URL 'http://$striptracks_bindaddress:$striptracks_port$striptracks_urlbase/api/command?apikey=(removed)'" | log + RESULT=$(curl -s -d "{name: '$striptracks_api', $striptracks_json_key: $striptracks_video_id}" -H "Content-Type: application/json" \ + -X POST http://$striptracks_bindaddress:$striptracks_port$striptracks_urlbase/api/command?apikey=$striptracks_apikey) + [ $striptracks_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 + [ $striptracks_debug -eq 1 ] && echo "Debug|Checking job $JOBID completion, try #$i. Calling ${striptracks_type^} API using GET and URL 'http://$striptracks_bindaddress:$striptracks_port$striptracks_urlbase/api/command/$JOBID?apikey=(removed)'" | log + RESULT=$(curl -s -H "Content-Type: application/json" \ + -X GET http://$striptracks_bindaddress:$striptracks_port$striptracks_urlbase/api/command/$JOBID?apikey=$striptracks_apikey) + [ $striptracks_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 ":d" opt; do + case ${opt} in + d ) # For debug purposes only + MSG="Debug|Enabling debug logging." + echo "$MSG" | log + >&2 echo "$MSG" + striptracks_debug=1 + printenv | sort | sed 's/^/Debug|/' | log + ;; + esac +done +shift $((OPTIND -1)) + +if [ -z "$1" ]; then + MSG="Error|No audio languages specified!" + echo "$MSG" | log + >&2 echo "$MSG" + usage + exit 2 +fi + +if [ -z "$2" ]; then + MSG="Error|No subtitles languages specified!" + echo "$MSG" | log + >&2 echo "$MSG" + usage + exit 3 +fi + +if [ ! -f "/usr/bin/mkvmerge" ]; then + MSG="Error|/usr/bin/mkvmerge is required by this script" + echo "$MSG" | log + >&2 echo "$MSG" + exit 4 +fi + +if [[ "${!striptracks_eventtype}" = "Test" ]]; then + echo "Info|${striptracks_type^} event: ${!striptracks_eventtype}" | log + echo "Info|Script was test executed successfully." | log + exit 0 +fi + +if [ -z "$striptracks_video" ]; then + MSG="Error|No video file specified! Not called from Radarr/Sonarr?" + echo "$MSG" | log + >&2 echo "$MSG" + usage + exit 1 +fi + +if [ ! -f "$striptracks_video" ]; then + MSG="Error|Input file not found: \"$striptracks_video\"" + echo "$MSG" | log + >&2 echo "$MSG" + exit 5 +fi + +#### BEGIN MAIN +FILESIZE=$(numfmt --to iec --format "%.3f" $(stat -c %s "$striptracks_video")) +MSG="Info|${striptracks_type^} event: ${!striptracks_eventtype}, Video: $striptracks_video, Size: $FILESIZE, AudioKeep: $1, SubsKeep: $2" +echo "$MSG" | log + +# Rename the original video file to a temporary name +[ $striptracks_debug -eq 1 ] && echo "Debug|Renaming: \"$striptracks_video\" to \"$striptracks_tempvideo\"" | log +mv -f "$striptracks_video" "$striptracks_tempvideo" | log +RET=$?; [ "$RET" != 0 ] && { + MSG="ERROR[$RET]: Unable to rename video: \"$striptracks_video\" to temp video: \"$striptracks_tempvideo\". Halting." + echo "$MSG" | log + >&2 echo "$MSG" + exit 6 +} + +# Read in the output of mkvmerge info extraction +[ $striptracks_debug -eq 1 ] && echo "Debug|Executing: /usr/bin/mkvmerge -J \"$striptracks_tempvideo\"" | log +JSON=$(/usr/bin/mkvmerge -J "$striptracks_tempvideo") +RET=$?; [ "$RET" != 0 ] && { + MSG="ERROR[$RET]: Error executing mkvmerge." + echo "$MSG" | log + >&2 echo "$MSG" +} + +# This and the modified AWK script are a hack, and I know it. JQ is crazy hard to learn, BTW. +# Mimic the mkvmerge --identify-verbose option that has been deprecated +JSON_PROCESSED=$(echo $JSON | jq -jcrM ' +( if (.chapters | .[] | .num_entries) then + "Chapters: \(.chapters | .[] | .num_entries) entries\n" + else + "" + end +), +( .tracks | + .[] | + ( "Track ID \(.id): \(.type) (\(.codec)) [", + ( [.properties | to_entries |.[] | "\(.key):\(.value | tostring | gsub(" "; "\\s"))"] | join(" ")), + "]\n" ) +) +') +[ $striptracks_debug -eq 1 ] && echo "$JSON_PROCESSED" | awk '{print "Debug|"$0}' | log + +echo "$JSON_PROCESSED" | awk -v Debug=$striptracks_debug \ +-v OrgVideo="$striptracks_video" \ +-v TempVideo="$striptracks_tempvideo" \ +-v MKVVideo="$striptracks_newvideo" \ +-v Title="$striptracks_title" \ +-v AudioKeep="$1" \ +-v SubsKeep="$2" ' +BEGIN { + MKVMerge="/usr/bin/mkvmerge" + FS="[\t\n: ]" + IGNORECASE=1 +} +/^Track ID/ { + FieldCount=split($0, Fields) + if (Fields[1]=="Track") { + NoTr++ + Track[NoTr, "id"]=Fields[3] + Track[NoTr, "typ"]=Fields[5] + if (Fields[6]~/^\(/) { + Track[NoTr, "code"]=substr(Line,1,match(Line,/\)/)) + sub(/^[^\(]+/,"",Track[NoTr, "code"]) + } + if (Track[NoTr, "typ"]=="video") VidCnt++ + if (Track[NoTr, "typ"]=="audio") AudCnt++ + if (Track[NoTr, "typ"]=="subtitles") SubsCnt++ + for (i=6; i<=FieldCount; i++) { + if (Fields[i]=="language") Track[NoTr, "lang"]=Fields[++i] + } + } +} +/^Chapters/ { + Chapters=$3 +} +END { + if (!NoTr) { print "Error|No tracks found in \""TempVideo"\"" > "/dev/stderr"; exit } + if (!AudCnt) AudCnt=0; if (!SubsCnt) SubsCnt=0 + print "Info|Original tracks: "NoTr" (audio: "AudCnt", subtitles: "SubsCnt")" + if (Chapters) print "Info|Chapters: "Chapters + for (i=1; i<=NoTr; i++) { + if (Debug) print "Debug|i:"i,"Track ID:"Track[i,"id"],"Type:"Track[i,"typ"],"Lang:"Track[i, "lang"],"Code:"Track[i, "code"] + if (Track[i, "typ"]=="audio") { + if (AudioKeep~Track[i, "lang"]) { + AudKpCnt++ + print "Info|Keeping audio track "Track[i, "id"]": "Track[i, "lang"]" "Track[i, "code"] + if (AudioCommand=="") { + AudioCommand=Track[i, "id"] + } else { + AudioCommand=AudioCommand","Track[i, "id"] + } + # Special case if there is only one audio track, even if it was not specified + } else if (AudCnt==1) { + AudKpCnt++ + print "Info|Keeping only audio track "Track[i, "id"]": "Track[i, "lang"]" "Track[i, "code"] + AudioCommand=Track[i, "id"] + # Special case if there were multiple tracks, none were selected, and this is the last one. + } else if (AudioCommand=="" && Track[i, "id"]==AudCnt) { + AudKpCnt++ + print "Info|Keeping last audio track "Track[i, "id"]": "Track[i, "lang"]" "Track[i, "code"] + AudioCommand=Track[i, "id"] + } else { + if (Debug) print "Debug|\tRemove:", Track[i, "typ"], "track", Track[i, "id"], Track[i, "lang"] + } + } else { + if (Track[i, "typ"]=="subtitles") { + if (SubsKeep~Track[i, "lang"]) { + SubsKpCnt++ + print "Info|Keeping subtitle track "Track[i, "id"]": "Track[i, "lang"]" "Track[i, "code"] + if (SubsCommand=="") { + SubsCommand=Track[i, "id"] + } else { + SubsCommand=SubsCommand","Track[i, "id"] + } + } else { + if (Debug) print "Debug|\tRemove:", Track[i, "typ"], "track", Track[i, "id"], Track[i, "lang"] + } + } + } + } + if (!AudKpCnt) AudKpCnt=0; if (!SubsKpCnt) SubsKpCnt=0 + print "Info|Kept tracks: "AudKpCnt+SubsKpCnt+VidCnt" (audio: "AudKpCnt", subtitles: "SubsKpCnt")" + if (AudioCommand=="") { + # This should never happen, but belt and suspenders + CommandLine="-A" + } else { + CommandLine="-a "AudioCommand + } + if (SubsCommand=="") { + CommandLine=CommandLine" -S" + } else { + CommandLine=CommandLine" -s "SubsCommand + } + if (Debug) print "Debug|Executing: nice "MKVMerge" --title \""Title"\" -q -o \""MKVVideo"\" "CommandLine" \""TempVideo"\"" + Result=system("nice "MKVMerge" --title \""Title"\" -q -o \""MKVVideo"\" "CommandLine" \""TempVideo"\"") + if (Result>1) print "Error|"Result" remuxing \""TempVideo"\"" > "/dev/stderr" +}' | log + +#### END MAIN + +# Check for script completion and non-empty file +if [ -s "$striptracks_newvideo" ]; then + # Use Recycle Bin if configured + if [ "$striptracks_recyclebin" ]; then + [ $striptracks_debug -eq 1 ] && echo "Debug|Moving: \"$striptracks_tempvideo\" to \"${striptracks_recyclebin%/}/$(basename "$striptracks_video")"\" | log + mv "$striptracks_tempvideo" "${striptracks_recyclebin%/}/$(basename "$striptracks_video")" | log + else + [ $striptracks_debug -eq 1 ] && echo "Debug|Deleting: \"$striptracks_tempvideo\"" | log + rm "$striptracks_tempvideo" | log + fi +else + MSG="Error|Unable to locate or invalid remuxed file: \"$striptracks_newvideo\". Undoing rename." + echo "$MSG" | log + >&2 echo "$MSG" + [ $striptracks_debug -eq 1 ] && echo "Debug|Renaming: \"$striptracks_tempvideo\" to \"$striptracks_video\"" | log + mv -f "$striptracks_tempvideo" "$striptracks_video" | log + exit 10 +fi + +FILESIZE=$(numfmt --to iec --format "%.3f" $(stat -c %s "$striptracks_newvideo")) +MSG="Info|New size: $FILESIZE" +echo "$MSG" | log + +# Call *arr API to RescanMovie/RescanSeries +if [ -f "$striptracks_arr_config" ]; then + # Read *arr config.xml + while read_xml; do + [[ $ENTITY = "Port" ]] && striptracks_port=$CONTENT + [[ $ENTITY = "UrlBase" ]] && striptracks_urlbase=$CONTENT + [[ $ENTITY = "BindAddress" ]] && striptracks_bindaddress=$CONTENT + [[ $ENTITY = "ApiKey" ]] && striptracks_apikey=$CONTENT + done < $striptracks_arr_config + + [[ $striptracks_bindaddress = "*" ]] && striptracks_bindaddress=localhost + + # Check for video ID + if [ "$striptracks_video_id" ]; then + # Call API + if [ "${striptracks_type,,}" = "radarr" ] && get_video_info; then + # Save original quality + ORGQUALITY=$(echo $RESULT | jq -crM ${striptracks_json_quality_root}.quality) + fi + # Loop a maximum of twice + for ((i=1; $i <= 2; i++)); do + # Scan the disk for the new movie file + if rescan; then + # Check that the Rescan completed + if check_rescan; then + # This whole section doesn't work under Sonarr because the episodefile_id changes after the RescanSeries if the filename changes + # Should look into just using a PUT to change everything at once instead of a Rescan. + if [ "${striptracks_type,,}" = "radarr" ]; then + if get_video_info; then + # Check that the file didn't get lost in the Rescan. + # Radarr sometimes needs to Rescan twice when the file extension changes + # (.avi -> .mkv for example) + if [ "$(echo $RESULT | jq -crM .hasFile)" = "true" ]; then + # If we lost the quality information, put it back + # NOTE: This "works" with Radarr in that the change shows up in the GUI, but only until the page changes. + # It doesn't seem to write the info permanently. Maybe an API bug? + if [ "$(echo $RESULT | jq -crM ${striptracks_json_quality_root}.quality.quality.name)" = "Unknown" ]; then + [ $striptracks_debug -eq 1 ] && echo "Debug|Updating quality to '$(echo $ORGQUALITY | jq -crM .quality.name)'. Calling ${striptracks_type^} API using PUT and URL 'http://$striptracks_bindaddress:$striptracks_port$striptracks_urlbase/api/$striptracks_api_endpoint/$striptracks_video_id?apikey=(removed)'" | log + RESULT=$(curl -s -d "$(echo $RESULT | jq -crM "${striptracks_json_quality_root}.quality=$ORGQUALITY")" -H "Content-Type: application/json" \ + -X PUT http://$striptracks_bindaddress:$striptracks_port$striptracks_urlbase/api/$striptracks_api_endpoint/$striptracks_video_id?apikey=$striptracks_apikey) + [ $striptracks_debug -eq 1 ] && echo "API returned: $RESULT" | awk '{print "Debug|"$0}' | log + if [ "$(echo $RESULT | jq -crM ${striptracks_json_quality_root}.quality.quality.name)" = "Unknown" ]; then + MSG="Warn|Unable to update ${striptracks_type^} $striptracks_api_endpoint '$striptracks_title' to quality '$(echo $ORGQUALITY | jq -crM .quality.name)'" + echo "$MSG" | log + >&2 echo "$MSG" + fi + fi + # The video record is [now] good + break + else + # Loop again because there was no file + continue + fi + else + # No 'path' in returned JSON. + MSG="Warn|The '$striptracks_api' API with $striptracks_api_endpoint $striptracks_api_endpoint_id returned no path." + echo "$MSG" | log + >&2 echo "$MSG" + fi + else + # Didn't do anything because we're in Sonarr + break + fi + else + # Timeout or failure + MSG="Warn|${striptracks_type^} job ID $JOBID timed out or failed." + echo "$MSG" | log + >&2 echo "$MSG" + fi + else + # Error from API + MSG="Error|The '$striptracks_api' API with $striptracks_json_key $striptracks_video_id failed." + echo "$MSG" | log + >&2 echo "$MSG" + fi + done + else + # No video ID means we can't call the API + MSG="Warn|Missing environment variable: $striptracks_video_idname" + echo "$MSG" | log + >&2 echo "$MSG" + fi +else + # No config file means we can't call the API + MSG="Warn|Unable to locate ${striptracks_type^} config file: '$striptracks_arr_config'" + echo "$MSG" | log + >&2 echo "$MSG" +fi + +# Cool bash feature +MSG="Info|Completed in $(($SECONDS/60))m $(($SECONDS%60))s" +echo "$MSG" | log