├── .github └── FUNDING.yml ├── Dockerfile.amd64 ├── Dockerfile.arm32v7 ├── Dockerfile.arm64v8 ├── LICENSE ├── README.md ├── hooks ├── post_push ├── post_push_dockeronly ├── post_push_tool ├── post_push_using_image └── pre_build ├── manifest.yaml └── root ├── etc └── cont-init.d │ └── 30-init.bash └── scripts ├── artist_discograpy.py ├── dlclient.py ├── download.bash ├── start.bash └── tag.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [RandomNinjaAtk] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /Dockerfile.amd64: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/linuxserver/baseimage-alpine:3.12 AS python 2 | 3 | RUN apk add build-base python3 python3-dev py3-pip musl-dev gcc && \ 4 | echo "*********** install python packages ***********" && \ 5 | pip install wheel && \ 6 | pip wheel --wheel-dir=/root/wheels \ 7 | yq \ 8 | mutagen \ 9 | r128gain \ 10 | mediafile \ 11 | confuse \ 12 | requests \ 13 | https://github.com/beetbox/beets/tarball/master \ 14 | deemix 15 | 16 | FROM ghcr.io/linuxserver/baseimage-alpine:3.12 17 | 18 | # Add Python Wheels 19 | COPY --from=python /root/wheels /root/wheels 20 | 21 | ENV TITLE="Automated Music Downloader (AMD)" 22 | ENV TITLESHORT="AMD" 23 | ENV VERSION="1.1.10" 24 | ENV MBRAINZMIRROR="https://musicbrainz.org" 25 | ENV XDG_CONFIG_HOME="/config/deemix/xdg" 26 | ENV DOWNLOADMODE="wanted" 27 | ENV FALLBACKSEARCH="True" 28 | 29 | RUN apk add --no-cache \ 30 | bash \ 31 | ca-certificates \ 32 | curl \ 33 | jq \ 34 | flac \ 35 | eyed3 \ 36 | opus-tools \ 37 | python3 \ 38 | findutils \ 39 | py3-pip \ 40 | musl-dev \ 41 | gcc \ 42 | ffmpeg && \ 43 | echo "************ install python packages ************" && \ 44 | pip install \ 45 | --no-index \ 46 | --find-links=/root/wheels \ 47 | yq \ 48 | mutagen \ 49 | r128gain \ 50 | mediafile \ 51 | confuse \ 52 | requests \ 53 | https://github.com/beetbox/beets/tarball/master \ 54 | deemix && \ 55 | echo "************ setup dl client config directory ************" && \ 56 | echo "************ make directory ************" && \ 57 | mkdir -p "${XDG_CONFIG_HOME}/deemix" 58 | 59 | # copy local files 60 | COPY root/ / 61 | 62 | WORKDIR /config 63 | 64 | # ports and volumes 65 | VOLUME /config 66 | -------------------------------------------------------------------------------- /Dockerfile.arm32v7: -------------------------------------------------------------------------------- 1 | FROM alpine AS builder 2 | 3 | # Download QEMU, see https://github.com/docker/hub-feedback/issues/1261 4 | ENV QEMU_URL https://github.com/balena-io/qemu/releases/download/v3.0.0%2Bresin/qemu-3.0.0+resin-arm.tar.gz 5 | RUN apk add curl && curl -L ${QEMU_URL} | tar zxvf - -C . --strip-components 1 6 | 7 | FROM ghcr.io/linuxserver/baseimage-alpine:arm32v7-3.12 AS python 8 | 9 | # Add QEMU 10 | COPY --from=builder qemu-arm-static /usr/bin 11 | 12 | RUN apk add build-base python3 python3-dev py3-pip musl-dev gcc && \ 13 | echo "*********** install python packages ***********" && \ 14 | pip install wheel && \ 15 | pip wheel --wheel-dir=/root/wheels \ 16 | yq \ 17 | mutagen \ 18 | r128gain \ 19 | mediafile \ 20 | confuse \ 21 | requests \ 22 | https://github.com/beetbox/beets/tarball/master \ 23 | deemix 24 | 25 | FROM ghcr.io/linuxserver/baseimage-alpine:arm32v7-3.12 26 | 27 | # Add QEMU 28 | COPY --from=builder qemu-arm-static /usr/bin 29 | # Add Python Wheels 30 | COPY --from=python /root/wheels /root/wheels 31 | 32 | ENV TITLE="Automated Music Downloader (AMD)" 33 | ENV TITLESHORT="AMD" 34 | ENV VERSION="1.1.10" 35 | ENV MBRAINZMIRROR="https://musicbrainz.org" 36 | ENV XDG_CONFIG_HOME="/config/deemix/xdg" 37 | ENV DOWNLOADMODE="wanted" 38 | ENV FALLBACKSEARCH="True" 39 | 40 | RUN apk add --no-cache \ 41 | bash \ 42 | ca-certificates \ 43 | curl \ 44 | jq \ 45 | flac \ 46 | eyed3 \ 47 | opus-tools \ 48 | python3 \ 49 | findutils \ 50 | py3-pip \ 51 | musl-dev \ 52 | gcc \ 53 | ffmpeg && \ 54 | echo "************ install python packages ************" && \ 55 | pip install \ 56 | --no-index \ 57 | --find-links=/root/wheels \ 58 | yq \ 59 | mutagen \ 60 | r128gain \ 61 | mediafile \ 62 | confuse \ 63 | requests \ 64 | https://github.com/beetbox/beets/tarball/master \ 65 | deemix && \ 66 | echo "************ setup dl client config directory ************" && \ 67 | echo "************ make directory ************" && \ 68 | mkdir -p "${XDG_CONFIG_HOME}/deemix" 69 | 70 | # copy local files 71 | COPY root/ / 72 | 73 | WORKDIR /config 74 | 75 | # ports and volumes 76 | VOLUME /config 77 | -------------------------------------------------------------------------------- /Dockerfile.arm64v8: -------------------------------------------------------------------------------- 1 | FROM alpine AS builder 2 | 3 | # Download QEMU, see https://github.com/docker/hub-feedback/issues/1261 4 | ENV QEMU_URL https://github.com/balena-io/qemu/releases/download/v3.0.0%2Bresin/qemu-3.0.0+resin-aarch64.tar.gz 5 | RUN apk add curl && curl -L ${QEMU_URL} | tar zxvf - -C . --strip-components 1 6 | 7 | FROM ghcr.io/linuxserver/baseimage-alpine:arm64v8-3.12 AS python 8 | 9 | # Add QEMU 10 | COPY --from=builder qemu-aarch64-static /usr/bin 11 | 12 | RUN apk add build-base python3 python3-dev py3-pip musl-dev gcc&& \ 13 | echo "*********** install python packages ***********" && \ 14 | pip install wheel && \ 15 | pip wheel --wheel-dir=/root/wheels \ 16 | yq \ 17 | mutagen \ 18 | r128gain \ 19 | mediafile \ 20 | confuse \ 21 | requests \ 22 | https://github.com/beetbox/beets/tarball/master \ 23 | deemix 24 | 25 | FROM ghcr.io/linuxserver/baseimage-alpine:arm64v8-3.12 26 | 27 | # Add QEMU 28 | COPY --from=builder qemu-aarch64-static /usr/bin 29 | # Add Python Wheels 30 | COPY --from=python /root/wheels /root/wheels 31 | 32 | ENV TITLE="Automated Music Downloader (AMD)" 33 | ENV TITLESHORT="AMD" 34 | ENV VERSION="1.1.10" 35 | ENV MBRAINZMIRROR="https://musicbrainz.org" 36 | ENV XDG_CONFIG_HOME="/config/deemix/xdg" 37 | ENV DOWNLOADMODE="wanted" 38 | ENV FALLBACKSEARCH="True" 39 | 40 | RUN apk add --no-cache \ 41 | bash \ 42 | ca-certificates \ 43 | curl \ 44 | jq \ 45 | findutils \ 46 | flac \ 47 | eyed3 \ 48 | opus-tools \ 49 | python3 \ 50 | py3-pip \ 51 | musl-dev \ 52 | gcc \ 53 | ffmpeg && \ 54 | echo "************ install python packages ************" && \ 55 | pip install \ 56 | --no-index \ 57 | --find-links=/root/wheels \ 58 | yq \ 59 | mutagen \ 60 | r128gain \ 61 | mediafile \ 62 | confuse \ 63 | requests \ 64 | https://github.com/beetbox/beets/tarball/master \ 65 | deemix && \ 66 | echo "************ setup dl client config directory ************" && \ 67 | echo "************ make directory ************" && \ 68 | mkdir -p "${XDG_CONFIG_HOME}/deemix" 69 | 70 | # copy local files 71 | COPY root/ / 72 | 73 | WORKDIR /config 74 | 75 | # ports and volumes 76 | VOLUME /config 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | This repository is now deprecated, will no longer be updated and is being archived. Please visit the new project/replacement: 4 | * [https://github.com/RandomNinjaAtk/docker-lidarr-extended](https://github.com/RandomNinjaAtk/docker-lidarr-extended) 5 | 6 |
7 |
8 | 9 | # AMD - Automated Music Downloader 10 | [RandomNinjaAtk/amd](https://github.com/RandomNinjaAtk/docker-amd) is a Lidarr companion script to automatically download music for Lidarr 11 | 12 | [![RandomNinjaAtk/amd](https://raw.githubusercontent.com/RandomNinjaAtk/unraid-templates/master/randomninjaatk/img/amd.png)](https://github.com/RandomNinjaAtk/docker-amd) 13 | 14 | ### Audio ([AMD](https://github.com/RandomNinjaAtk/docker-amd)) + Video ([AMVD](https://github.com/RandomNinjaAtk/docker-amvd)) (Plex Example) 15 | ![](https://raw.githubusercontent.com/RandomNinjaAtk/Scripts/master/images/plex-musicvideos.png) 16 | 17 | ## Features 18 | * Downloading **Music** using online sources for use in popular applications (Plex/Kodi/Emby/Jellyfin): 19 | * Searches for downloads based on Lidarr's album wanted list 20 | * Downloads using a third party download client automatically 21 | * FLAC / MP3 (320/120) Download Quality 22 | * Notifies Lidarr to automatically import downloaded files 23 | * Music is properly tagged and includes coverart before Lidarr Receives them (Third Party Download Client handles it) 24 | 25 | ## Supported Architectures 26 | 27 | The architectures supported by this image are: 28 | 29 | | Architecture | Tag | 30 | | :----: | --- | 31 | | x86-64 | latest | 32 | 33 | ## Version Tags 34 | 35 | | Tag | Description | 36 | | :----: | --- | 37 | | latest | Newest release code | 38 | 39 | 40 | ## Parameters 41 | 42 | Container images are configured using parameters passed at runtime (such as those above). These parameters are separated by a colon and indicate `:` respectively. For example, `-p 8080:80` would expose port `80` from inside the container to be accessible from the host's IP on port `8080` outside the container. See the [wiki](https://github.com/RandomNinjaAtk/docker-amd/wiki) to understand how it works. 43 | 44 | | Parameter | Function | 45 | | --- | --- | 46 | | `-v /config` | Configuration files for Lidarr. | 47 | | `-v /downloads-amd` | Path to your download folder location. (DO NOT DELETE, this is a required path) :: !!!IMPORTANT!!! Map this exact volume mount to your Lidarr Container for everything to work properly!!! | 48 | | `-e PUID=1000` | for UserID - see below for explanation | 49 | | `-e PGID=1000` | for GroupID - see below for explanation | 50 | | `-e AUTOSTART=true` | true = Enabled :: Runs script automatically on startup | 51 | | `-e SCRIPTINTERVAL=1h` | #s or #m or #h or #d :: s = seconds, m = minutes, h = hours, d = days :: Amount of time between each script run, when AUTOSTART is enabled| 52 | | `-e DOWNLOADMODE=wanted` | wanted or artist :: wanted mode only download missing/cutoff :: artist mode downloads all albums by an artist (requires lidarr volume mapping root media folders for import) | 53 | | `-e FALLBACKSEARCH=True` | True or False :: True = enabled :: Allows DL client to search for missing songs when they are not available | 54 | | `-e LIST=both` | both or missing or cutoff :: both = missing + cutoff :: missng = lidarr missing list :: cutoff = lidarr cutoff list | 55 | | `-e SearchType=both` | both or artist or fuzzy :: both = artist + fuzzy searching :: artist = only artist searching :: fuzzy = only fuzzy searching (Various Artist is always fuzzy searched, regardless of setting) | 56 | | `-e Concurrency=1` | Number of concurrent downloads | 57 | | `-e EMBEDDED_COVER_QUALITY=80` | Controls the quality of the cover image compression in percentage, 100 = no compression | 58 | | `-e FORMAT=FLAC` | FLAC or MP3 or OPUS or AAC or ALAC | 59 | | `-e BITRATE=320` | FLAC -> OPUS/AAC/MP3 will be converted using this bitrate (MP3 320/128 is native, not converted) | 60 | | `-e ENABLEPOSTPROCESSING=true` | true = enabled :: enables or disables post processing processes as much as possible | 61 | | `-e FORCECONVERT=false` | true = enabled :: This will convert lossy MP3 to desired target format (exluding FLAC/ALAC, ALAC will convert to AAC) | 62 | | `-e requirequality=false` | true = enabled :: Requires all downloaded files match target file extension (mp3 or flac) when enabled | 63 | | `-e MatchDistance=10` | Set as an integer, the higher the number, the more lenient it is. Example: A match score of 0 is a perfect match :: For more information, this score is produced using this function: [Algorithm Implementation/Strings/Levenshtein distance](https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance) | 64 | | `-e replaygain=true` | true = enabled :: Scans and analyzes files to add replaygain tags to song metadata | 65 | | `-e FolderPermissions=766` | Based on chmod linux permissions | 66 | | `-e FilePermissions=666` | Based on chmod linux permissions | 67 | | `-e MBRAINZMIRROR=https://musicbrainz.org` | OPTIONAL :: Only change if using a different mirror | 68 | | `-e MBRATELIMIT=1` | OPTIONAL: musicbrainz rate limit, musicbrainz allows only 1 connection per second, max setting is 10 :: Set to 101 to disable limit | 69 | | `-e LidarrUrl=http://x.x.x.x:8686` | Set domain or IP to your Lidarr instance including port. If using reverse proxy, do not use a trailing slash. Ensure you specify http/s. | 70 | | `-e LidarrAPIkey=LIDARRAPI` | Lidarr API key. | 71 | | `-e ARL_TOKEN=ARLTOKEN` | User token for dl client, use google... | 72 | | `-e NOTIFYPLEX=false` | true = enabled :: ONLY APPLIES ARTIST MODE :: Plex must have a music library added and be configured to use the exact same mount point as Lidarr's root folder | 73 | | `-e PLEXLIBRARYNAME=Music` | This must exactly match the name of the Plex Library that contains the Lidarr Media Folder data | 74 | | `-e PLEXURL=http://x.x.x.x:32400` | ONLY used if NOTIFYPLEX is enabled... | 75 | | `-e PLEXTOKEN=plextoken` | ONLY used if NOTIFYPLEX is enabled... | 76 | | `-e ALBUM_TYPE_FILTER=COMPILE` | Filter Types: COMPILE, SINGLE, ALBUM, EP (this is a ", " separated list of Album Types to skip) (Applicable to artist mode only) | 77 | | `-e POSTPROCESSTHREADS=1` | Controls number of threads used for Format conversion and replaygain tagging | 78 | 79 | 80 | ## Usage 81 | 82 | Here are some example snippets to help you get started creating a container. 83 | 84 | ### docker 85 | 86 | ``` 87 | docker create \ 88 | --name=amd \ 89 | -v /path/to/config/files:/config \ 90 | -v /path/to/downloads:/downloads-amd \ 91 | -e PUID=1000 \ 92 | -e PGID=1000 \ 93 | -e AUTOSTART=true \ 94 | -e SCRIPTINTERVAL=1h \ 95 | -e DOWNLOADMODE=wanted \ 96 | -e FALLBACKSEARCH=True \ 97 | -e LIST=both \ 98 | -e SearchType=both \ 99 | -e Concurrency=1 \ 100 | -e EMBEDDED_COVER_QUALITY=80 \ 101 | -e FORMAT=FLAC \ 102 | -e BITRATE=320 \ 103 | -e ENABLEPOSTPROCESSING=true \ 104 | -e FORCECONVERT=false \ 105 | -e requirequality=false \ 106 | -e MatchDistance=10 \ 107 | -e replaygain=true \ 108 | -e FolderPermissions=766 \ 109 | -e FilePermissions=666 \ 110 | -e MBRAINZMIRROR=https://musicbrainz.org \ 111 | -e MBRATELIMIT=1 \ 112 | -e LidarrUrl=http://x.x.x.x:8686 \ 113 | -e LidarrAPIkey=LIDARRAPI \ 114 | -e ARL_TOKEN=ARLTOKEN \ 115 | -e NOTIFYPLEX=false \ 116 | -e PLEXLIBRARYNAME=Music \ 117 | -e PLEXURL=http://x.x.x.x:8686 \ 118 | -e PLEXTOKEN=plextoken \ 119 | -e ALBUM_TYPE_FILTER=COMPILE \ 120 | -e POSTPROCESSTHREADS=1 \ 121 | --restart unless-stopped \ 122 | randomninjaatk/amd 123 | ``` 124 | 125 | 126 | ### docker-compose 127 | 128 | Compatible with docker-compose v2 schemas. 129 | 130 | ``` 131 | version: "2.1" 132 | services: 133 | amd: 134 | image: randomninjaatk/amd 135 | container_name: amd 136 | volumes: 137 | - /path/to/config/files:/config 138 | - /path/to/downloads:/downloads-amd 139 | environment: 140 | - PUID=1000 141 | - PGID=1000 142 | - AUTOSTART=true 143 | - SCRIPTINTERVAL=1h 144 | - DOWNLOADMODE=wanted 145 | - FALLBACKSEARCH=True 146 | - LIST=both 147 | - SearchType=both 148 | - Concurrency=1 149 | - EMBEDDED_COVER_QUALITY=80 150 | - FORMAT=FLAC 151 | - BITRATE=320 152 | - ENABLEPOSTPROCESSING=true 153 | - FORCECONVERT=false 154 | - requirequality=false 155 | - MatchDistance=10 156 | - replaygain=true 157 | - FolderPermissions=766 158 | - FilePermissions=666 159 | - MBRAINZMIRROR=https://musicbrainz.org 160 | - MBRATELIMIT=1 161 | - LidarrUrl=http://x.x.x.x:8686 162 | - LidarrAPIkey=LIDARRAPI 163 | - ARL_TOKEN=ARLTOKEN 164 | - NOTIFYPLEX=false 165 | - PLEXLIBRARYNAME=Music 166 | - PLEXURL=http://x.x.x.x:8686 167 | - PLEXTOKEN=plextoken 168 | - ALBUM_TYPE_FILTER=COMPILE 169 | - POSTPROCESSTHREADS=1 170 | restart: unless-stopped 171 | ``` 172 | 173 | 174 | # Script Information 175 | * Script will automatically run when enabled, if disabled, you will need to manually execute with the following command: 176 | * From Host CLI: `docker exec -it amd /bin/bash -c 'bash /scripts/download.bash'` 177 | * From Docker CLI: `bash /scripts/download.bash` 178 | 179 | ## Directories: 180 | * /config/scripts 181 | * Contains the scripts that are run 182 | * /config/logs 183 | * Contains the log output from the script 184 | * /config/cache 185 | * Contains the artist data cache to speed up processes 186 | * /config/deemix 187 | * Contains deemix app data 188 | 189 |
190 | 191 | # Lidarr Configuration Recommendations 192 | 193 | ## Media Management Settings: 194 | * Disable Track Naming 195 | * Disabling track renaming enables synced lyrics that are imported as extras to be utilized by media players that support using them 196 | 197 | #### Track Naming: 198 | 199 | * Artist Folder: `{Artist Name}{ (Artist Disambiguation)}` 200 | * Album Folder: `{Artist Name}{ - ALBUM TYPE}{ - Release Year} - {Album Title}{ ( Album Disambiguation)}` 201 | 202 | #### Importing: 203 | * Enable Import Extra Files 204 | * `lrc,jpg,png` 205 | 206 | #### File Management 207 | * Change File Date: Album Release Date 208 | 209 | #### Permissions 210 | * Enable Set Permissions 211 |
212 |
213 |
214 |
215 | 216 | 217 | # Credits 218 | - [Original Idea based on lidarr-download-automation by Migz93](https://github.com/Migz93/lidarr-download-automation) 219 | - [Deemix download client](https://deemix.app/) 220 | - [Musicbrainz](https://musicbrainz.org/) 221 | - [Lidarr](https://lidarr.audio/) 222 | - [r128gain](https://github.com/desbma/r128gain) 223 | - [Algorithm Implementation/Strings/Levenshtein distance](https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance) 224 | - Icons made by Freepik from www.flaticon.com 225 | -------------------------------------------------------------------------------- /hooks/post_push: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Use manifest-tool to create the manifest, given the experimental 4 | # "docker manifest" command isn't available yet on Docker Hub. 5 | 6 | #curl -Lo manifest-tool https://github.com/estesp/manifest-tool/releases/download/v1.0.0/manifest-tool-linux-arm64 7 | #chmod +x manifest-tool 8 | #docker manifest annotate jstrader/alpine-int-openvpn:latest jstrader/alpine-int-openvpn:arm32v7 --arch arm --variant v7 9 | #docker manifest annotate jstrader/alpine-int-openvpn:latest jstrader/alpine-int-openvpn:arm64v8 --arch arm --variant v8 10 | #docker manifest create jstrader/alpine-int-openvpn:latest --amend jstrader/alpine-int-openvpn:amd64 --amend jstrader/alpine-int-openvpn:arm32v7 --amend jstrader/alpine-int-openvpn:arm64v8 11 | #docker manifest inspect jstrader/alpine-int-openvpn:latest 12 | #./manifest-tool push from-spec multi-arch-manifest.yaml 13 | 14 | #!/bin/bash 15 | 16 | # Autobuild the Image on Docker Hub with advanced options 17 | # https://docs.docker.com/docker-hub/builds/advanced/ 18 | 19 | # if the host is not equal to the building system architecture, set the image arch with manifest correctly on docker hub. 20 | 21 | set -e 22 | 23 | #IMAGE_OS=$(uname | tr '[:upper:]' '[:lower:]') 24 | IMAGE_OS="linux" 25 | HOST_ARCH=$(uname -m) 26 | HOST_ARCH_ALIAS=$([[ "${HOST_ARCH}" == "x86_64" ]] && echo "amd64" || echo "${HOST_ARCH}") 27 | BUILD_ARCH=$(echo "${DOCKERFILE_PATH}" | cut -d '.' -f 2) 28 | BUILD_ARCH=$([[ "${BUILD_ARCH}" == *\/* ]] && echo "${BUILD_ARCH}" | rev | cut -d '/' -f 1 | rev || echo "${BUILD_ARCH}") 29 | QEMU_USER_STATIC_ARCH=$([[ "${BUILD_ARCH}" == "armhf" ]] && echo "${BUILD_ARCH::-2}" || echo "${BUILD_ARCH}") 30 | PLATFORMS_ARCH=$([[ "${QEMU_USER_STATIC_ARCH}" == "arm" ]] && echo "${IMAGE_OS}/${QEMU_USER_STATIC_ARCH},${IMAGE_OS}/${QEMU_USER_STATIC_ARCH}64,${IMAGE_OS}/${HOST_ARCH_ALIAS}" || echo "${IMAGE_OS}/${QEMU_USER_STATIC_ARCH}") 31 | 32 | echo "PLATFORMS-ARCH: ${PLATFORMS_ARCH}" 33 | 34 | if [[ "${HOST_ARCH}" == "${QEMU_USER_STATIC_ARCH}"* || "${BUILD_ARCH}" == "Dockerfile" ]]; then 35 | echo "Building ${BUILD_ARCH} image natively; No manifest needed for current arch." 36 | exit 0 37 | else 38 | # Manifest 39 | 40 | # docker manifest: https://docs.docker.com/engine/reference/commandline/manifest/ 41 | echo "docker manifest (not working with autobuild on docker hub)" 42 | #docker manifest create "${IMAGE_NAME}" "${IMAGE_NAME}" 43 | #docker manifest annotate "${IMAGE_NAME}" "${IMAGE_NAME}" --os "${IMAGE_OS}" --arch "${QEMU_USER_STATIC_ARCH}" 44 | #docker manifest push "${IMAGE_NAME}" 45 | 46 | # manifest-tool: https://github.com/estesp/manifest-tool 47 | echo "manifest-tool" 48 | # prerelease: 49 | #MANIFEST_TOOL_VERSION=$(curl -s https://api.github.com/repos/estesp/manifest-tool/tags | grep 'name.*v[0-9]' | head -n 1 | cut -d '"' -f 4) 50 | # release: 51 | MANIFEST_TOOL_VERSION=$(curl -s https://api.github.com/repos/estesp/manifest-tool/releases/latest | grep 'tag_name' | cut -d\" -f4) 52 | curl -L \ 53 | --connect-timeout 5 \ 54 | --max-time 10 \ 55 | --retry 5 \ 56 | --retry-delay 0 \ 57 | --retry-max-time 40 \ 58 | "https://github.com/estesp/manifest-tool/releases/download/$MANIFEST_TOOL_VERSION/manifest-tool-$IMAGE_OS-$HOST_ARCH_ALIAS" -o manifest-tool 59 | chmod +x manifest-tool 60 | #./manifest-tool push from-args --platforms ${PLATFORMS_ARCH} --template ${IMAGE_NAME} --target ${IMAGE_NAME} 61 | ./manifest-tool push from-spec manifest.yaml 62 | fi 63 | -------------------------------------------------------------------------------- /hooks/post_push_dockeronly: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Use docker cli v18.09.6 to use experimental manifest feature 4 | curl -SL "https://download.docker.com/linux/static/stable/x86_64/docker-18.09.6.tgz" | tar xzv docker/docker --transform='s/.*/docker-cli/' 5 | mkdir ~/.docker 6 | # Add auths and experimental to docker-cli config 7 | echo '{"auths": '$DOCKERCFG',"experimental":"enabled"}' > ~/.docker/config.json 8 | # Check if all arch images are in dockerhub 9 | VIRTUAL_IMAGE=$(echo "${IMAGE_NAME}" | rev | cut -d- -f2- | rev ) 10 | 11 | AMD64_IMAGE="$VIRTUAL_IMAGE-amd64" 12 | ARM64_IMAGE="$VIRTUAL_IMAGE-arm64v8" 13 | ARM32_IMAGE="$VIRTUAL_IMAGE-arm32v7" 14 | 15 | echo "checking if ${AMD64_IMAGE} Manifest exists" 16 | if ! ./docker-cli manifest inspect ${AMD64_IMAGE}; then AMD64_IMAGE='' ; fi 17 | echo "checking if ${ARM32_IMAGE} Manifest exists" 18 | if ! ./docker-cli manifest inspect ${ARM32_IMAGE}; then ARM32_IMAGE='' ; fi 19 | echo "checking if ${ARM64_IMAGE} Manifest exists" 20 | if ! ./docker-cli manifest inspect ${ARM64_IMAGE}; then ARM64_IMAGE='' ; fi 21 | 22 | echo "Creating multiarch manifest" 23 | ./docker-cli manifest create $VIRTUAL_IMAGE $AMD64_IMAGE $ARM32_IMAGE $ARM64_IMAGE 24 | if [ -n "${ARM32_IMAGE}" ]; then 25 | ./docker-cli manifest annotate $VIRTUAL_IMAGE $ARM32_IMAGE --os linux --arch arm 26 | fi 27 | if [ -n "${ARM64_IMAGE}" ]; then 28 | ./docker-cli manifest annotate $VIRTUAL_IMAGE $ARM64_IMAGE --os linux --arch arm64 29 | fi 30 | ./docker-cli manifest push $VIRTUAL_IMAGE 31 | 32 | rm -r docker-cli ~/.docker -------------------------------------------------------------------------------- /hooks/post_push_tool: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Use manifest-tool to create the manifest, given the experimental 4 | # "docker manifest" command isn't available yet on Docker Hub. 5 | 6 | #curl -Lo manifest-tool https://github.com/estesp/manifest-tool/releases/download/v1.0.0/manifest-tool-linux-arm64 7 | #chmod +x manifest-tool 8 | #docker manifest annotate jstrader/alpine-int-openvpn:latest jstrader/alpine-int-openvpn:arm32v7 --arch arm --variant v7 9 | #docker manifest annotate jstrader/alpine-int-openvpn:latest jstrader/alpine-int-openvpn:arm64v8 --arch arm --variant v8 10 | #docker manifest create jstrader/alpine-int-openvpn:latest --amend jstrader/alpine-int-openvpn:amd64 --amend jstrader/alpine-int-openvpn:arm32v7 --amend jstrader/alpine-int-openvpn:arm64v8 11 | #docker manifest inspect jstrader/alpine-int-openvpn:latest 12 | #./manifest-tool push from-spec multi-arch-manifest.yaml 13 | 14 | #!/bin/bash 15 | 16 | # Autobuild the Image on Docker Hub with advanced options 17 | # https://docs.docker.com/docker-hub/builds/advanced/ 18 | 19 | # if the host is not equal to the building system architecture, set the image arch with manifest correctly on docker hub. 20 | 21 | set -e 22 | 23 | #IMAGE_OS=$(uname | tr '[:upper:]' '[:lower:]') 24 | IMAGE_OS="linux" 25 | HOST_ARCH=$(uname -m) 26 | HOST_ARCH_ALIAS=$([[ "${HOST_ARCH}" == "x86_64" ]] && echo "amd64" || echo "${HOST_ARCH}") 27 | BUILD_ARCH=$(echo "${DOCKERFILE_PATH}" | cut -d '.' -f 2) 28 | BUILD_ARCH=$([[ "${BUILD_ARCH}" == *\/* ]] && echo "${BUILD_ARCH}" | rev | cut -d '/' -f 1 | rev || echo "${BUILD_ARCH}") 29 | QEMU_USER_STATIC_ARCH=$([[ "${BUILD_ARCH}" == "armhf" ]] && echo "${BUILD_ARCH::-2}" || echo "${BUILD_ARCH}") 30 | PLATFORMS_ARCH=$([[ "${QEMU_USER_STATIC_ARCH}" == "arm" ]] && echo "${IMAGE_OS}/${QEMU_USER_STATIC_ARCH},${IMAGE_OS}/${QEMU_USER_STATIC_ARCH}64,${IMAGE_OS}/${HOST_ARCH_ALIAS}" || echo "${IMAGE_OS}/${QEMU_USER_STATIC_ARCH}") 31 | 32 | echo "PLATFORMS-ARCH: ${PLATFORMS_ARCH}" 33 | 34 | if [[ "${HOST_ARCH}" == "${QEMU_USER_STATIC_ARCH}"* || "${BUILD_ARCH}" == "Dockerfile" ]]; then 35 | echo "Building ${BUILD_ARCH} image natively; No manifest needed for current arch." 36 | exit 0 37 | else 38 | # Manifest 39 | 40 | # docker manifest: https://docs.docker.com/engine/reference/commandline/manifest/ 41 | echo "docker manifest (not working with autobuild on docker hub)" 42 | #docker manifest create "${IMAGE_NAME}" "${IMAGE_NAME}" 43 | #docker manifest annotate "${IMAGE_NAME}" "${IMAGE_NAME}" --os "${IMAGE_OS}" --arch "${QEMU_USER_STATIC_ARCH}" 44 | #docker manifest push "${IMAGE_NAME}" 45 | 46 | # manifest-tool: https://github.com/estesp/manifest-tool 47 | echo "manifest-tool" 48 | # prerelease: 49 | #MANIFEST_TOOL_VERSION=$(curl -s https://api.github.com/repos/estesp/manifest-tool/tags | grep 'name.*v[0-9]' | head -n 1 | cut -d '"' -f 4) 50 | # release: 51 | MANIFEST_TOOL_VERSION=$(curl -s https://api.github.com/repos/estesp/manifest-tool/releases/latest | grep 'tag_name' | cut -d\" -f4) 52 | curl -L https://github.com/estesp/manifest-tool/releases/download/$MANIFEST_TOOL_VERSION/manifest-tool-$IMAGE_OS-$HOST_ARCH_ALIAS -o manifest-tool 53 | chmod +x manifest-tool 54 | #./manifest-tool push from-args --platforms ${PLATFORMS_ARCH} --template ${IMAGE_NAME} --target ${IMAGE_NAME} 55 | ./manifest-tool push from-spec manifest.yaml 56 | fi -------------------------------------------------------------------------------- /hooks/post_push_using_image: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Use docker cli v18.09.6 to use experimental manifest feature 4 | curl -SL "https://download.docker.com/linux/static/stable/x86_64/docker-18.09.6.tgz" | tar xzv docker/docker --transform='s/.*/docker-cli/' 5 | mkdir ~/.docker 6 | # Add auths and experimental to docker-cli config 7 | echo '{"auths": '$DOCKERCFG',"experimental":"enabled"}' > ~/.docker/config.json 8 | # Check if all arch images are in dockerhub 9 | VIRTUAL_IMAGE=$(echo "${IMAGE_NAME}" | rev | cut -d- -f2- | rev ) 10 | 11 | AMD64_IMAGE="$VIRTUAL_IMAGE-amd64" 12 | ARM64_IMAGE="$VIRTUAL_IMAGE-arm64v8" 13 | ARM32_IMAGE="$VIRTUAL_IMAGE-arm32v7" 14 | LATEST="$VIRTUAL_IMAGE:latest" 15 | echo $LATEST 16 | echo "checking if ${AMD64_IMAGE} Manifest exists" 17 | if ! ./docker-cli manifest inspect ${AMD64_IMAGE}; then AMD64_IMAGE='' ; fi 18 | echo "checking if ${ARM32_IMAGE} Manifest exists" 19 | if ! ./docker-cli manifest inspect ${ARM32_IMAGE}; then ARM32_IMAGE='' ; fi 20 | echo "checking if ${ARM64_IMAGE} Manifest exists" 21 | if ! ./docker-cli manifest inspect ${ARM64_IMAGE}; then ARM64_IMAGE='' ; fi 22 | 23 | echo "Creating multiarch manifest" 24 | ./docker-cli manifest create $LATEST $AMD64_IMAGE $ARM32_IMAGE $ARM64_IMAGE 25 | if [ -n "${ARM32_IMAGE}" ]; then 26 | ./docker-cli manifest annotate $VIRTUAL_IMAGE $ARM32_IMAGE --os linux --arch arm --variant v7 27 | fi 28 | if [ -n "${ARM64_IMAGE}" ]; then 29 | ./docker-cli manifest annotate $VIRTUAL_IMAGE $ARM64_IMAGE --os linux --arch arm64 --variant v8 30 | fi 31 | ./docker-cli manifest push $VIRTUAL_IMAGE 32 | 33 | rm -r docker-cli ~/.docker -------------------------------------------------------------------------------- /hooks/pre_build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Register qemu-*-static for all supported processors except the 4 | # current one, but also remove all registered binfmt_misc before 5 | docker run --rm --privileged multiarch/qemu-user-static:register --reset -------------------------------------------------------------------------------- /manifest.yaml: -------------------------------------------------------------------------------- 1 | image: randomninjaatk/amd:latest 2 | manifests: 3 | - image: randomninjaatk/amd:amd64 4 | platform: 5 | architecture: amd64 6 | os: linux 7 | - image: randomninjaatk/amd:arm32v7 8 | platform: 9 | architecture: arm 10 | os: linux 11 | variant: v7 12 | - image: randomninjaatk/amd:arm64v8 13 | platform: 14 | architecture: arm64 15 | os: linux 16 | variant: v8 -------------------------------------------------------------------------------- /root/etc/cont-init.d/30-init.bash: -------------------------------------------------------------------------------- 1 | #! /usr/bin/with-contenv bash 2 | 3 | # Create directories 4 | mkdir -p /config/{cache,logs,deemix/xdg/deemix} 5 | 6 | # Set directory ownership 7 | chown -R abc:abc /config 8 | chown -R abc:abc /scripts 9 | chmod 0777 -R /scripts 10 | chmod 0777 -R /config 11 | 12 | if [[ ${AUTOSTART} == true ]]; then 13 | bash /scripts/start.bash 14 | else 15 | echo "Automatic Start disabled, start manually with:" 16 | echo "bash /scripts/start.bash" 17 | fi 18 | -------------------------------------------------------------------------------- /root/scripts/artist_discograpy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from deezer import Deezer 3 | import sys 4 | 5 | if __name__ == '__main__': 6 | if len(sys.argv) > 1: 7 | dz = Deezer() 8 | releases = dz.gw.get_artist_discography_tabs(sys.argv[1], 100) 9 | for type in releases: 10 | for release in releases[type]: 11 | print(release['id']) 12 | -------------------------------------------------------------------------------- /root/scripts/dlclient.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | from deemix.__main__ import download 4 | from deemix.settings import save as saveSettings 5 | 6 | 7 | newsettings = { 8 | "downloadLocation": "/downloads-amd/amd/dlclient", 9 | "tracknameTemplate": "%discnumber%%tracknumber% - %title% %explicit%", 10 | "albumTracknameTemplate": "%discnumber%%tracknumber% - %title% %explicit%", 11 | "artistNameTemplate": "%artist% (%artist_id%)", 12 | "albumNameTemplate": "%artist% - %type% - %year% - %album_id% - %album% %explicit%", 13 | "createCDFolder": False, 14 | "createAlbumFolder": False, 15 | "saveArtworkArtist": True, 16 | "queueConcurrency": CONCURRENT_DOWNLOADS, 17 | "jpegImageQuality": EMBEDDED_COVER_QUALITY, 18 | "embeddedArtworkSize": 1200, 19 | "localArtworkSize": 1200, 20 | "removeAlbumVersion": True, 21 | "syncedLyrics": True, 22 | "coverImageTemplate": "folder", 23 | "tags": { 24 | "trackTotal": True, 25 | "discTotal": True, 26 | "explicit": True, 27 | "length": False, 28 | "lyrics": True, 29 | "syncedLyrics": True, 30 | "involvedPeople": True, 31 | "copyright": True, 32 | "composer": True, 33 | "savePlaylistAsCompilation": True, 34 | "removeDuplicateArtists": True, 35 | "featuredToTitle": "0", 36 | "saveID3v1": False, 37 | "multiArtistSeparator": "andFeat", 38 | "singleAlbumArtist": True 39 | } 40 | } 41 | 42 | if __name__ == '__main__': 43 | sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) 44 | saveSettings(newsettings) 45 | download() 46 | -------------------------------------------------------------------------------- /root/scripts/download.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | export XDG_CONFIG_HOME="/config/deemix/xdg" 3 | export LC_ALL=C.UTF-8 4 | export LANG=C.UTF-8 5 | agent="automated-music-downloader ( https://github.com/RandomNinjaAtk/docker-amd )" 6 | 7 | Configuration () { 8 | processdownloadid="$(pgrep -f 'bash /scripts/download.bash')" 9 | log "To kill the download script, use the following command:" 10 | log "kill -9 $processdownloadid" 11 | log "" 12 | log "" 13 | sleep 2 14 | log "####### $TITLE" 15 | log "####### SCRIPT VERSION 1.5.484" 16 | log "####### DOCKER VERSION $VERSION" 17 | log "####### CONFIGURATION VERIFICATION" 18 | error=0 19 | 20 | if [ "$AUTOSTART" == "true" ]; then 21 | log "$TITLESHORT Script Autostart: ENABLED" 22 | if [ -z "$SCRIPTINTERVAL" ]; then 23 | log "WARNING: $TITLESHORT Script Interval not set! Using default..." 24 | SCRIPTINTERVAL="15m" 25 | fi 26 | log "$TITLESHORT Script Interval: $SCRIPTINTERVAL" 27 | else 28 | log "$TITLESHORT Script Autostart: DISABLED" 29 | fi 30 | 31 | # Verify Lidarr Connectivity 32 | lidarrtest=$(curl -s "$LidarrUrl/api/v1/system/status?apikey=${LidarrAPIkey}" | jq -r ".version") 33 | if [ ! -z "$lidarrtest" ]; then 34 | if [ "$lidarrtest" != "null" ]; then 35 | log "Lidarr Connection Valid, version: $lidarrtest" 36 | else 37 | log "ERROR: Cannot communicate with Lidarr, most likely a...." 38 | log "ERROR: Invalid API Key: $LidarrAPIkey" 39 | error=1 40 | fi 41 | else 42 | log "ERROR: Cannot communicate with Lidarr, no response" 43 | log "ERROR: URL: $LidarrUrl" 44 | log "ERROR: API Key: $LidarrAPIkey" 45 | error=1 46 | fi 47 | 48 | if [ ! -z "$LIDARRREMOTEPATH" ]; then 49 | log "Lidarr Remote Path Mapping: ENABLED ($LIDARRREMOTEPATH)" 50 | remotepath="true" 51 | else 52 | remotepath="false" 53 | fi 54 | 55 | # Verify Musicbrainz DB Connectivity 56 | musicbrainzdbtest=$(curl -s -A "$agent" "${MBRAINZMIRROR}/ws/2/artist/f59c5520-5f46-4d2c-b2c4-822eabf53419?fmt=json") 57 | musicbrainzdbtestname=$(echo "${musicbrainzdbtest}"| jq -r '.name?') 58 | if [ "$musicbrainzdbtestname" != "Linkin Park" ]; then 59 | log "ERROR: Cannot communicate with Musicbrainz" 60 | log "ERROR: Expected Response \"Linkin Park\", received response \"$musicbrainzdbtestname\"" 61 | log "ERROR: URL might be Invalid: $MBRAINZMIRROR" 62 | log "ERROR: Remote Mirror may be throttling connection..." 63 | log "ERROR: Link used for testing: ${MBRAINZMIRROR}/ws/2/artist/f59c5520-5f46-4d2c-b2c4-822eabf53419?fmt=json" 64 | log "ERROR: Please correct error, consider using official Musicbrainz URL: https://musicbrainz.org" 65 | error=1 66 | else 67 | log "Musicbrainz Mirror Valid: $MBRAINZMIRROR" 68 | if echo "$MBRAINZMIRROR" | grep -i "musicbrainz.org" | read; then 69 | if [ "$MBRATELIMIT" != 1 ]; then 70 | MBRATELIMIT="1.5" 71 | fi 72 | log "Musicbrainz Rate Limit: $MBRATELIMIT (Queries Per Second)" 73 | else 74 | if [ "$MBRATELIMIT" == "101" ]; then 75 | log "Musicbrainz Rate Limit: DISABLED" 76 | else 77 | log "Musicbrainz Rate Limit: $MBRATELIMIT (Queries Per Second)" 78 | fi 79 | MBRATELIMIT="0$(echo $(( 100 * 1 / $MBRATELIMIT )) | sed 's/..$/.&/')" 80 | fi 81 | fi 82 | 83 | # verify downloads location 84 | if [ -d "/downloads-amd" ]; then 85 | DOWNLOADS="/downloads-amd" 86 | log "Downloads Location: $DOWNLOADS/amd/dlclient" 87 | log "Import Location: $DOWNLOADS/amd/import" 88 | else 89 | if [ -d "$DOWNLOADS" ]; then 90 | log "DOWNLOADS Location: $DOWNLOADS" 91 | else 92 | log "ERROR: DOWNLOADS setting invalid, currently set to: $DOWNLOADS" 93 | log "ERROR: DOWNLOADS Expected Valid Setting: /your/path/to/music/downloads" 94 | error=1 95 | fi 96 | fi 97 | 98 | if [ ! -z "$ARL_TOKEN" ]; then 99 | log "ARL Token: Configured" 100 | if [ -f "$XDG_CONFIG_HOME/deemix/.arl" ]; then 101 | rm "$XDG_CONFIG_HOME/deemix/.arl" 102 | fi 103 | if [ ! -f "$XDG_CONFIG_HOME/deemix/.arl" ]; then 104 | echo -n "$ARL_TOKEN" > "$XDG_CONFIG_HOME/deemix/.arl" 105 | fi 106 | else 107 | log "ERROR: ARL_TOKEN setting invalid, currently set to: $ARL_TOKEN" 108 | error=1 109 | fi 110 | 111 | if [ ! -z "$Concurrency" ]; then 112 | log "Audio: Concurrency: $Concurrency" 113 | sed -i "s%CONCURRENT_DOWNLOADS%$Concurrency%g" "/scripts/dlclient.py" 114 | else 115 | log "WARNING: Concurrency setting invalid, defaulting to: 1" 116 | Concurrency="1" 117 | sed -i "s%CONCURRENT_DOWNLOADS%$Concurrency%g" "/scripts/dlclient.py" 118 | fi 119 | 120 | if [ ! -z "$FALLBACKSEARCH" ]; then 121 | log "Audio: FALLBACKSEARCH: $FALLBACKSEARCH" 122 | sed -i "s%FALLBACKSEARCHS%$FALLBACKSEARCH%g" "/scripts/dlclient.py" 123 | else 124 | log "WARNING: FALLBACKSEARCH setting invalid, defaulting to: True" 125 | FALLBACKSEARCH="TRUE" 126 | sed -i "s%FALLBACKSEARCHS%$FALLBACKSEARCH%g" "/scripts/dlclient.py" 127 | fi 128 | 129 | if [ ! -z "$FORMAT" ]; then 130 | log "Audio: Download Format: $FORMAT" 131 | if [ "$FORMAT" = "ALAC" ]; then 132 | quality="FLAC" 133 | options="-c:a alac -movflags faststart" 134 | extension="m4a" 135 | log "Audio: Download File Bitrate: lossless" 136 | elif [ "$FORMAT" = "FLAC" ]; then 137 | quality="FLAC" 138 | extension="flac" 139 | log "Audio: Download File Bitrate: lossless" 140 | elif [ "$FORMAT" = "OPUS" ]; then 141 | quality="FLAC" 142 | options="-acodec libopus -ab ${BITRATE}k -application audio -vbr off" 143 | extension="opus" 144 | log "Audio: Download File Bitrate: $BITRATE" 145 | elif [ "$FORMAT" = "AAC" ]; then 146 | quality="FLAC" 147 | options="-c:a libfdk_aac -b:a ${BITRATE}k -movflags faststart" 148 | extension="m4a" 149 | log "Audio: Download File Bitrate: $BITRATE" 150 | elif [ "$FORMAT" = "MP3" ]; then 151 | if [ "$BITRATE" = "320" ]; then 152 | quality="320" 153 | extension="mp3" 154 | log "Audio: Download File Bitrate: $BITRATE" 155 | elif [ "$BITRATE" = "128" ]; then 156 | quality="128" 157 | extension="mp3" 158 | log "Audio: Download File Bitrate: $BITRATE" 159 | else 160 | quality="FLAC" 161 | options="-acodec libmp3lame -ab ${BITRATE}k" 162 | extension="mp3" 163 | log "Audio: Download File Bitrate: $BITRATE" 164 | fi 165 | else 166 | log "ERROR: \"$FORMAT\" Does not match a required setting, check for trailing space..." 167 | error=1 168 | fi 169 | else 170 | if [ "$quality" == "FLAC" ]; then 171 | log "Audio: Download Quality: FLAC" 172 | log "Audio: Download Bitrate: lossless" 173 | elif [ "$quality" == "320" ]; then 174 | log "Audio: Download Quality: MP3" 175 | log "Audio: Download Bitrate: 320k" 176 | elif [ "$quality" == "128" ]; then 177 | log "Audio: Download Quality: MP3" 178 | log "Audio: Download Bitrate: 128k" 179 | else 180 | log "Audio: Download Quality: FLAC" 181 | log "Audio: Download Bitrate: lossless" 182 | quality="FLAC" 183 | fi 184 | fi 185 | 186 | if [ ! -z "$FORCECONVERT" ]; then 187 | if [ $FORCECONVERT == true ]; then 188 | log "Audio: Force Convert: ENABLED" 189 | else 190 | log "Audio: Force Convert: DISABLED" 191 | fi 192 | else 193 | log "Audio: Force Convert: DISABLED" 194 | log "WARNING: FORCECONVERT setting invalid, using default setting" 195 | FORCECONVERT="false" 196 | fi 197 | 198 | if [ ! -z "$ENABLEPOSTPROCESSING" ]; then 199 | if [ $ENABLEPOSTPROCESSING == true ]; then 200 | log "Audio: Audio Post Processing: ENABLED" 201 | else 202 | log "Audio: Audio Post Processing: DISABLED" 203 | fi 204 | else 205 | log "Audio: Audio Post Processing: ENABLED" 206 | log "WARNING: ENABLEPOSTPROCESSING setting invalid, using default setting" 207 | ENABLEPOSTPROCESSING="true" 208 | fi 209 | 210 | if [ ! -z "$POSTPROCESSTHREADS" ]; then 211 | log "Audio: Number of Post Process Threads: $POSTPROCESSTHREADS" 212 | else 213 | POSTPROCESSTHREADS=1 214 | log "WARNING: POSTPROCESSTHREADS setting invalid, defaulting to: 1" 215 | log "Audio: Number of Post Process Threads: $POSTPROCESSTHREADS" 216 | fi 217 | 218 | if [ ! -z "$EMBEDDED_COVER_QUALITY" ]; then 219 | log "Audio: Embedded Cover Quality: $EMBEDDED_COVER_QUALITY (%)" 220 | sed -i "s%EMBEDDED_COVER_QUALITY%$EMBEDDED_COVER_QUALITY%g" "/scripts/dlclient.py" 221 | else 222 | EMBEDDED_COVER_QUALITY=80 223 | log "WARNING: EMBEDDED_COVER_QUALITY setting invalid, defaulting to: 80" 224 | log "Audio: Embedded Cover Quality: $EMBEDDED_COVER_QUALITY (%)" 225 | sed -i "s%EMBEDDED_COVER_QUALITY%$EMBEDDED_COVER_QUALITY%g" "/scripts/dlclient.py" 226 | fi 227 | 228 | if [ "$DOWNLOADMODE" == "artist" ]; then 229 | log "Audio: Download Mode: $DOWNLOADMODE (Archives all albums by artist)" 230 | wantit=$(curl -s --header "X-Api-Key:"${LidarrAPIkey} --request GET "$LidarrUrl/api/v1/rootFolder") 231 | path=($(echo "${wantit}" | jq -r ".[].path")) 232 | for id in ${!path[@]}; do 233 | pathprocess=$(( $id + 1 )) 234 | folder="${path[$id]}" 235 | if [ ! -d "$folder" ]; then 236 | log "ERROR: \"$folder\" Path not found, add missing volume that matches Lidarr" 237 | error=1 238 | break 239 | else 240 | continue 241 | fi 242 | done 243 | 244 | if [ ! -z "$ALBUM_TYPE_FILTER" ]; then 245 | ALBUM_FILTER=true 246 | log "Audio: Album Type Filter: ENABLED" 247 | log "Audio: Filtering: $ALBUM_TYPE_FILTER" 248 | else 249 | ALBUM_FILTER=false 250 | log "Audio: Album Type Filter: DISABLED" 251 | fi 252 | 253 | 254 | if [ "$NOTIFYPLEX" == "true" ]; then 255 | log "Audio: Plex Library Notification: ENABLED" 256 | plexlibraries="$(curl -s "$PLEXURL/library/sections?X-Plex-Token=$PLEXTOKEN" | xq .)" 257 | for id in ${!path[@]}; do 258 | pathprocess=$(( $id + 1 )) 259 | folder="${path[$id]%?}" 260 | if log "$plexlibraries" | grep "$folder" | read; then 261 | plexlibrarykey="$(echo "$plexlibraries" | jq -r ".MediaContainer.Directory[] | select(.\"@title\"==\"$PLEXLIBRARYNAME\") | .\"@key\"" | head -n 1)" 262 | if [ -z "$plexlibrarykey" ]; then 263 | log "ERROR: No Plex Library found named \"$PLEXLIBRARYNAME\"" 264 | error=1 265 | fi 266 | else 267 | log "ERROR: No Plex Library found containing path \"$folder\"" 268 | log "ERROR: Add \"$folder\" as a folder to a Plex Music Library or Disable NOTIFYPLEX" 269 | error=1 270 | fi 271 | done 272 | else 273 | log "Audio : Plex Library Notification: DISABLED" 274 | fi 275 | fi 276 | if [ ! -z "$requirequality" ]; then 277 | if [ "$requirequality" == "true" ]; then 278 | log "Audio: Require Quality: ENABLED" 279 | else 280 | log "Audio: Require Quality: DISABLED" 281 | fi 282 | else 283 | log "WARNING: requirequality setting invalid, defaulting to: false" 284 | requirequality="false" 285 | fi 286 | 287 | if [ "$DOWNLOADMODE" == "wanted" ]; then 288 | log "Audio: Download Mode: $DOWNLOADMODE (Processes monitored albums)" 289 | if [ "$LIST" == "both" ]; then 290 | log "Audio: Wanted List Type: Both (missing & cutoff)" 291 | elif [ "$LIST" == "missing" ]; then 292 | log "Audio: Wanted List Type: Missing" 293 | elif [ "$LIST" == "cutoff" ]; then 294 | log "Audio: Wanted List Type: Cutoff" 295 | else 296 | log "WARNING: LIST type not selected, using default..." 297 | log "Audio: Wanted List Type: Missing" 298 | LIST="missing" 299 | fi 300 | 301 | if [ "$SearchType" == "both" ]; then 302 | log "Audio: Search Type: Artist Searching & Backup Fuzzy Searching" 303 | elif [ "$SearchType" == "artist" ]; then 304 | log "Audio: Search Type: Artist Searching Only (Exception: Fuzzy search only for Various Artists)" 305 | elif [ "$SearchType" == "fuzzy" ]; then 306 | log "Audio: Search Type: Fuzzy Searching Only" 307 | else 308 | log "Audio: Search Type: Artist Searching & Backup Fuzzy Searching" 309 | SearchType="both" 310 | fi 311 | 312 | if [ ! -z "$MatchDistance" ]; then 313 | log "Audio: Match Distance: $MatchDistance" 314 | else 315 | log "WARNING: MatchDistance not set, using default..." 316 | MatchDistance="10" 317 | log "Audio: Match Distance: $MatchDistance" 318 | fi 319 | 320 | fi 321 | 322 | if [ ! -z "$replaygain" ]; then 323 | if [ "$replaygain" == "true" ]; then 324 | log "Audio: Replaygain Tagging: ENABLED" 325 | else 326 | log "Audio: Replaygain Tagging: DISABLED" 327 | fi 328 | else 329 | log "WARNING: replaygain setting invalid, defaulting to: true" 330 | replaygain="true" 331 | fi 332 | 333 | if [ ! -z "$FilePermissions" ]; then 334 | log "Audio: File Permissions: $FilePermissions" 335 | else 336 | log "WARNING: FilePermissions not set, using default..." 337 | FilePermissions="666" 338 | log "Audio: File Permissions: $FilePermissions" 339 | fi 340 | 341 | if [ ! -z "$FolderPermissions" ]; then 342 | log "Audio: Folder Permissions: $FolderPermissions" 343 | else 344 | log "WARNING: FolderPermissions not set, using default..." 345 | FolderPermissions="766" 346 | log "Audio: Folder Permissions: $FolderPermissions" 347 | fi 348 | 349 | if [ $error = 1 ]; then 350 | log "Please correct errors before attempting to run script again..." 351 | log "Exiting..." 352 | fi 353 | amount=1000000000 354 | sleep 2.5 355 | } 356 | 357 | AlbumFilter () { 358 | 359 | IFS=', ' read -r -a filters <<< "$ALBUM_TYPE_FILTER" 360 | for filter in "${filters[@]}" 361 | do 362 | if [ "$filter" == "${deezeralbumtype^^}" ]; then 363 | filtermatch=true 364 | filtertype="$filter" 365 | break 366 | else 367 | filtermatch=false 368 | filtertype="" 369 | continue 370 | fi 371 | done 372 | 373 | } 374 | 375 | FlacConvert () { 376 | 377 | fname="$1" 378 | filename="$(basename "${fname%.flac}")" 379 | if [ "$extension" == "m4a" ]; then 380 | cover="/downloads-amd/amd/dlclient/folder.jpg" 381 | songtitle="null" 382 | songalbum="null" 383 | songartist="null" 384 | songartistalbum="null" 385 | songoriginalbpm="null" 386 | songbpm="null" 387 | songcopyright="null" 388 | songtracknumber="null" 389 | songtracktotal="null" 390 | songdiscnumber="null" 391 | songdisctotal="null" 392 | songlyricrating="null" 393 | songcompilation="null" 394 | songdate="null" 395 | songyear="null" 396 | songgenre="null" 397 | songcomposer="null" 398 | songisrc="null" 399 | fi 400 | if [ "$extension" == "m4a" ]; then 401 | tags="$(ffprobe -v quiet -print_format json -show_format "$fname" | jq -r '.[] | .tags')" 402 | filelrc="${fname%.flac}.lrc" 403 | songtitle="$(echo "$tags" | jq -r ".TITLE")" 404 | songalbum="$(echo "$tags" | jq -r ".ALBUM")" 405 | songartist="$(echo "$tags" | jq -r ".ARTIST")" 406 | songartistalbum="$(echo "$tags" | jq -r ".album_artist")" 407 | songoriginalbpm="$(echo "$tags" | jq -r ".BPM")" 408 | songbpm=${songoriginalbpm%.*} 409 | songcopyright="$(echo "$tags" | jq -r ".COPYRIGHT")" 410 | songpublisher="$(echo "$tags" | jq -r ".PUBLISHER")" 411 | songtracknumber="$(echo "$tags" | jq -r ".track")" 412 | songtracktotal="$(echo "$tags" | jq -r ".TRACKTOTAL")" 413 | songdiscnumber="$(echo "$tags" | jq -r ".disc")" 414 | songdisctotal="$(echo "$tags" | jq -r ".DISCTOTAL")" 415 | songlyricrating="$(echo "$tags" | jq -r ".ITUNESADVISORY")" 416 | songcompilation="$(echo "$tags" | jq -r ".COMPILATION")" 417 | songdate="$(echo "$tags" | jq -r ".DATE")" 418 | songyear="${songdate:0:4}" 419 | songgenre="$(echo "$tags" | jq -r ".GENRE" | cut -f1 -d";")" 420 | songcomposer="$(echo "$tags" | jq -r ".composer")" 421 | songcomment="Source File: FLAC" 422 | songisrc="$(echo "$tags" | jq -r ".ISRC")" 423 | songauthor="$(echo "$tags" | jq -r ".author")" 424 | songartists="$(echo "$tags" | jq -r ".ARTISTS")" 425 | songengineer="$(echo "$tags" | jq -r ".engineer")" 426 | songproducer="$(echo "$tags" | jq -r ".producer")" 427 | songmixer="$(echo "$tags" | jq -r ".mixer")" 428 | songwriter="$(echo "$tags" | jq -r ".writer")" 429 | songbarcode="$(echo "$tags" | jq -r ".BARCODE")" 430 | 431 | if [ -f "$filelrc" ]; then 432 | songsyncedlyrics="$(cat "$filelrc")" 433 | else 434 | songsyncedlyrics="" 435 | fi 436 | 437 | if [ "$songtitle" = "null" ]; then 438 | songtitle="" 439 | fi 440 | 441 | if [ "$songpublisher" = "null" ]; then 442 | songpublisher="" 443 | fi 444 | 445 | if [ "$songalbum" = "null" ]; then 446 | songalbum="" 447 | fi 448 | 449 | if [ "$songartist" = "null" ]; then 450 | songartist="" 451 | fi 452 | 453 | if [ "$songartistalbum" = "null" ]; then 454 | songartistalbum="" 455 | fi 456 | 457 | if [ "$songbpm" = "null" ]; then 458 | songbpm="" 459 | fi 460 | 461 | if [ "$songlyricrating" = "null" ]; then 462 | songlyricrating="0" 463 | fi 464 | 465 | if [ "$songcopyright" = "null" ]; then 466 | songcopyright="" 467 | fi 468 | 469 | if [ "$songtracknumber" = "null" ]; then 470 | songtracknumber="" 471 | fi 472 | 473 | if [ "$songtracktotal" = "null" ]; then 474 | songtracktotal="" 475 | fi 476 | 477 | if [ "$songdiscnumber" = "null" ]; then 478 | songdiscnumber="" 479 | fi 480 | 481 | if [ "$songdisctotal" = "null" ]; then 482 | songdisctotal="" 483 | fi 484 | 485 | if [ "$songcompilation" = "null" ]; then 486 | songcompilation="0" 487 | fi 488 | 489 | if [ "$songdate" = "null" ]; then 490 | songdate="" 491 | fi 492 | 493 | if [ "$songyear" = "null" ]; then 494 | songyear="" 495 | fi 496 | 497 | if [ "$songgenre" = "null" ]; then 498 | songgenre="" 499 | fi 500 | 501 | if [ "$songcomposer" = "null" ]; then 502 | songcomposer="" 503 | else 504 | songcomposer=${songcomposert//\//, } 505 | fi 506 | 507 | if [ "$songwriter" = "null" ]; then 508 | songwriter="" 509 | fi 510 | 511 | if [ "$songauthor" = "null" ]; then 512 | songauthor="$songwriter" 513 | fi 514 | 515 | if [ "$songartists" = "null" ]; then 516 | songartists="" 517 | fi 518 | 519 | if [ "$songengineer" = "null" ]; then 520 | songengineer="" 521 | fi 522 | 523 | if [ "$songproducer" = "null" ]; then 524 | songproducer="" 525 | fi 526 | 527 | if [ "$songmixer" = "null" ]; then 528 | songmixer="" 529 | fi 530 | 531 | if [ "$songbarcode" = "null" ]; then 532 | songbarcode="" 533 | fi 534 | 535 | if [ "$songcomment" = "null" ]; then 536 | songcomment="" 537 | fi 538 | fi 539 | 540 | if [ "${FORMAT}" == "OPUS" ]; then 541 | if opusenc --bitrate $BITRATE --music "$fname" "${fname%.flac}.temp.$extension"; then 542 | converterror=0 543 | else 544 | converterror=1 545 | fi 546 | else 547 | if ffmpeg -loglevel warning -hide_banner -nostats -i "$fname" -n -vn $options "${fname%.flac}.temp.$extension"; then 548 | converterror=0 549 | else 550 | converterror=1 551 | fi 552 | fi 553 | 554 | if [ "$converterror" == "1" ]; then 555 | log "$logheader :: CONVERSION :: ERROR :: Conversion Failed: $filename, performing cleanup..." 556 | rm "${fname%.flac}.temp.$extension" 557 | continue 558 | elif [ -f "${fname%.flac}.temp.$extension" ]; then 559 | mv "${fname%.flac}.temp.$extension" "${fname%.flac}.$extension" 560 | log "$logheader :: CONVERSION :: $filename :: Converted!" 561 | fi 562 | 563 | if [ "$extension" == "m4a" ]; then 564 | log "$logheader :: CONVERSION :: $filename :: Tagging" 565 | python3 /scripts/tag.py \ 566 | --file "${fname%.flac}.$extension" \ 567 | --songtitle "$songtitle" \ 568 | --songalbum "$songalbum" \ 569 | --songartist "$songartist" \ 570 | --songartistalbum "$songartistalbum" \ 571 | --songbpm "$songbpm" \ 572 | --songcopyright "$songcopyright" \ 573 | --songtracknumber "$songtracknumber" \ 574 | --songtracktotal "$songtracktotal" \ 575 | --songdiscnumber "$songdiscnumber" \ 576 | --songdisctotal "$songdisctotal" \ 577 | --songcompilation "$songcompilation" \ 578 | --songlyricrating "$songlyricrating" \ 579 | --songdate "$songdate" \ 580 | --songyear "$songyear" \ 581 | --songgenre "$songgenre" \ 582 | --songcomposer "$songcomposer" \ 583 | --songisrc "$songisrc" \ 584 | --songauthor "$songauthor" \ 585 | --songartists "$songartists" \ 586 | --songengineer "$songengineer" \ 587 | --songproducer "$songproducer" \ 588 | --songmixer "$songmixer" \ 589 | --songpublisher "$songpublisher" \ 590 | --songcomment "$songcomment" \ 591 | --songbarcode "$songbarcode" \ 592 | --mbrainzalbumartistid "$albumartistmbzid" \ 593 | --mbrainzreleasegroupid "$albumreleasegroupmbzid" \ 594 | --mbrainzalbumid "$albummbid" \ 595 | --songartwork "$cover" 596 | log "$logheader :: CONVERSION :: $filename :: Tagged" 597 | 598 | fi 599 | 600 | if [ -f "${fname%.flac}.$extension" ]; then 601 | rm "$fname" 602 | sleep 0.1 603 | fi 604 | } 605 | 606 | MP3Convert () { 607 | fname="$1" 608 | filename="$(basename "${fname%.mp3}")" 609 | if [ "$extension" == "m4a" ]; then 610 | cover="/downloads-amd/amd/dlclient/folder.jpg" 611 | songtitle="null" 612 | songalbum="null" 613 | songartist="null" 614 | songartistalbum="null" 615 | songoriginalbpm="null" 616 | songbpm="null" 617 | songcopyright="null" 618 | songtracknumber="null" 619 | songtracktotal="null" 620 | songdiscnumber="null" 621 | songdisctotal="null" 622 | songlyricrating="null" 623 | songcompilation="null" 624 | songdate="null" 625 | songyear="null" 626 | songgenre="null" 627 | songcomposer="null" 628 | songisrc="null" 629 | fi 630 | if [ "$extension" = "m4a" ]; then 631 | tags="$(ffprobe -v quiet -print_format json -show_format "$fname" | jq -r '.[] | .tags')" 632 | filelrc="${fname%.mp3}.lrc" 633 | songtitle="$(echo "$tags" | jq -r ".title")" 634 | songalbum="$(echo "$tags" | jq -r ".album")" 635 | songartist="$(echo "$tags" | jq -r ".artist")" 636 | songartistalbum="$(echo "$tags" | jq -r ".album_artist")" 637 | songoriginalbpm="$(echo "$tags" | jq -r ".TBPM")" 638 | songbpm=${songoriginalbpm%.*} 639 | songcopyright="$(echo "$tags" | jq -r ".copyright")" 640 | songpublisher="$(echo "$tags" | jq -r ".publisher")" 641 | songtracknumber="$(echo "$tags" | jq -r ".track" | cut -f1 -d "/")" 642 | songtracktotal="$(echo "$tags" | jq -r ".track" | cut -f2 -d "/")" 643 | songdiscnumber="$(echo "$tags" | jq -r ".disc" | cut -f1 -d "/")" 644 | songdisctotal="$(echo "$tags" | jq -r ".disc" | cut -f2 -d "/")" 645 | songlyricrating="$(echo "$tags" | jq -r ".ITUNESADVISORY")" 646 | songcompilation="$(echo "$tags" | jq -r ".compilation")" 647 | songdate="$(echo "$tags" | jq -r ".date")" 648 | songyear="$(echo "$tags" | jq -r ".date")" 649 | songgenre="$(echo "$tags" | jq -r ".genre" | cut -f1 -d";")" 650 | songcomposer="$(echo "$tags" | jq -r ".composer")" 651 | songcomment="Source File: MP3" 652 | songisrc="$(echo "$tags" | jq -r ".TSRC")" 653 | songauthor="" 654 | songartists="$(echo "$tags" | jq -r ".ARTISTS")" 655 | songengineer="" 656 | songproducer="" 657 | songmixer="" 658 | songbarcode="$(echo "$tags" | jq -r ".BARCODE")" 659 | 660 | if [ -f "$filelrc" ]; then 661 | songsyncedlyrics="$(cat "$filelrc")" 662 | else 663 | songsyncedlyrics="" 664 | fi 665 | 666 | if [ "$songtitle" = "null" ]; then 667 | songtitle="" 668 | fi 669 | 670 | if [ "$songpublisher" = "null" ]; then 671 | songpublisher="" 672 | fi 673 | 674 | if [ "$songalbum" = "null" ]; then 675 | songalbum="" 676 | fi 677 | 678 | if [ "$songartist" = "null" ]; then 679 | songartist="" 680 | fi 681 | 682 | if [ "$songartistalbum" = "null" ]; then 683 | songartistalbum="" 684 | fi 685 | 686 | if [ "$songbpm" = "null" ]; then 687 | songbpm="" 688 | fi 689 | 690 | if [ "$songlyricrating" = "null" ]; then 691 | songlyricrating="0" 692 | fi 693 | 694 | if [ "$songcopyright" = "null" ]; then 695 | songcopyright="" 696 | fi 697 | 698 | if [ "$songtracknumber" = "null" ]; then 699 | songtracknumber="" 700 | fi 701 | 702 | if [ "$songtracktotal" = "null" ]; then 703 | songtracktotal="" 704 | fi 705 | 706 | if [ "$songdiscnumber" = "null" ]; then 707 | songdiscnumber="" 708 | fi 709 | 710 | if [ "$songdisctotal" = "null" ]; then 711 | songdisctotal="" 712 | fi 713 | 714 | if [ "$songcompilation" = "null" ]; then 715 | songcompilation="0" 716 | fi 717 | 718 | if [ "$songdate" = "null" ]; then 719 | songdate="" 720 | fi 721 | 722 | if [ "$songyear" = "null" ]; then 723 | songyear="" 724 | fi 725 | 726 | if [ "$songgenre" = "null" ]; then 727 | songgenre="" 728 | fi 729 | 730 | if [ "$songcomposer" = "null" ]; then 731 | songcomposer="" 732 | else 733 | songcomposer=${songcomposer//;/, } 734 | fi 735 | 736 | if [ "$songwriter" = "null" ]; then 737 | songwriter="" 738 | fi 739 | 740 | if [ "$songauthor" = "null" ]; then 741 | songauthor="$songwriter" 742 | fi 743 | 744 | if [ "$songartists" = "null" ]; then 745 | songartists="" 746 | fi 747 | 748 | if [ "$songengineer" = "null" ]; then 749 | songengineer="" 750 | fi 751 | 752 | if [ "$songproducer" = "null" ]; then 753 | songproducer="" 754 | fi 755 | 756 | if [ "$songmixer" = "null" ]; then 757 | songmixer="" 758 | fi 759 | 760 | if [ "$songbarcode" = "null" ]; then 761 | songbarcode="" 762 | fi 763 | 764 | if [ "$songcomment" = "null" ]; then 765 | songcomment="" 766 | fi 767 | fi 768 | 769 | if [ "${FORMAT}" == "OPUS" ]; then 770 | if opusenc --bitrate $BITRATE --music "$fname" "${fname%.mp3}.temp.$extension"; then 771 | converterror=0 772 | else 773 | converterror=1 774 | fi 775 | else 776 | if ffmpeg -loglevel warning -hide_banner -nostats -i "$fname" -n -vn $options "${fname%.mp3}.temp.$extension"; then 777 | converterror=0 778 | else 779 | converterror=1 780 | fi 781 | fi 782 | 783 | if [ "$converterror" == "1" ]; then 784 | log "$logheader :: CONVERSION :: ERROR :: Conversion Failed: $filename, performing cleanup..." 785 | rm "${fname%.mp3}.temp.$extension" 786 | continue 787 | elif [ -f "${fname%.mp3}.temp.$extension" ]; then 788 | mv "${fname%.mp3}.temp.$extension" "${fname%.mp3}.$extension" 789 | log "$logheader :: CONVERSION :: $filename :: Converted!" 790 | fi 791 | 792 | if [ "$extension" == "m4a" ]; then 793 | log "$logheader :: CONVERSION :: $filename :: Tagging" 794 | python3 /scripts/tag.py \ 795 | --file "${fname%.mp3}.$extension" \ 796 | --songtitle "$songtitle" \ 797 | --songalbum "$songalbum" \ 798 | --songartist "$songartist" \ 799 | --songartistalbum "$songartistalbum" \ 800 | --songbpm "$songbpm" \ 801 | --songcopyright "$songcopyright" \ 802 | --songtracknumber "$songtracknumber" \ 803 | --songtracktotal "$songtracktotal" \ 804 | --songdiscnumber "$songdiscnumber" \ 805 | --songdisctotal "$songdisctotal" \ 806 | --songcompilation "$songcompilation" \ 807 | --songlyricrating "$songlyricrating" \ 808 | --songdate "$songdate" \ 809 | --songyear "$songyear" \ 810 | --songgenre "$songgenre" \ 811 | --songcomposer "$songcomposer" \ 812 | --songisrc "$songisrc" \ 813 | --songauthor "$songauthor" \ 814 | --songartists "$songartists" \ 815 | --songengineer "$songengineer" \ 816 | --songproducer "$songproducer" \ 817 | --songmixer "$songmixer" \ 818 | --songpublisher "$songpublisher" \ 819 | --songcomment "$songcomment" \ 820 | --songbarcode "$songbarcode" \ 821 | --mbrainzalbumartistid "$albumartistmbzid" \ 822 | --mbrainzreleasegroupid "$albumreleasegroupmbzid" \ 823 | --mbrainzalbumid "$albummbid" \ 824 | --songartwork "$cover" 825 | log "$logheader :: CONVERSION :: $filename :: Tagged" 826 | fi 827 | 828 | if [ -f "${fname%.mp3}.$extension" ]; then 829 | rm "$fname" 830 | sleep 0.1 831 | fi 832 | } 833 | 834 | Conversion () { 835 | if [ "${FORMAT}" != "FLAC" ]; then 836 | if [ $FORCECONVERT == true ]; then 837 | converttrackcount=$(find /downloads-amd/amd/dlclient/ -regex ".*/.*\.\(flac\|mp3\)" | wc -l) 838 | else 839 | converttrackcount=$(find /downloads-amd/amd/dlclient/ -name "*.flac" | wc -l) 840 | fi 841 | log "$logheader :: CONVERSION :: Converting: $converttrackcount Tracks (Target Format: $FORMAT (${BITRATE}))" 842 | if find /downloads-amd/amd/dlclient/ -name "*.flac" | read; then 843 | for fname in /downloads-amd/amd/dlclient/*.flac; do 844 | FlacConvert "$fname" & 845 | N=$POSTPROCESSTHREADS 846 | (( ++count % N == 0)) && wait 847 | done 848 | check=1 849 | let j=0 850 | while [[ $check -le 1 ]]; do 851 | if find /downloads-amd/amd/dlclient -iname "*.flac" | read; then 852 | check=1 853 | sleep 1 854 | else 855 | check=2 856 | fi 857 | done 858 | fi 859 | 860 | if [ $FORCECONVERT == true ]; then 861 | if [[ "${FORMAT}" != "MP3" && "${FORMAT}" != "FLAC" ]]; then 862 | if find /downloads-amd/amd/dlclient/ -name "*.mp3" | read; then 863 | for fname in /downloads-amd/amd/dlclient/*.mp3; do 864 | MP3Convert "$fname" & 865 | N=$POSTPROCESSTHREADS 866 | (( ++count % N == 0)) && wait 867 | done 868 | fi 869 | fi 870 | check=1 871 | let j=0 872 | while [[ $check -le 1 ]]; do 873 | if find /downloads-amd/amd/dlclient -iname "*.mp3" | read; then 874 | check=1 875 | sleep 1 876 | else 877 | check=2 878 | fi 879 | done 880 | fi 881 | fi 882 | } 883 | 884 | DownloadQualityCheck () { 885 | 886 | if [ "$requirequality" == "true" ]; then 887 | log "$logheader :: DOWNLOAD :: Checking for unwanted files" 888 | if [ "$quality" == "FLAC" ]; then 889 | if find "$DOWNLOADS"/amd/dlclient -iname "*.mp3" | read; then 890 | log "$logheader :: DOWNLOAD :: Unwanted files found!" 891 | log "$logheader :: DOWNLOAD :: Performing cleanup..." 892 | rm "$DOWNLOADS"/amd/dlclient/* 893 | fi 894 | else 895 | if find "$DOWNLOADS"/amd/dlclient -iname "*.flac" | read; then 896 | log "$logheader :: DOWNLOAD :: Unwanted files found!" 897 | log "$logheader :: DOWNLOAD :: Performing cleanup..." 898 | rm "$DOWNLOADS"/amd/dlclient/* 899 | fi 900 | fi 901 | fi 902 | 903 | } 904 | 905 | AddReplaygainTags () { 906 | if [ "$replaygain" == "true" ]; then 907 | log "$logheader :: DOWNLOAD :: Adding Replaygain Tags using r128gain" 908 | r128gain -r -a -c $POSTPROCESSTHREADS "$DOWNLOADS/amd/dlclient" 909 | fi 910 | } 911 | 912 | LidarrList () { 913 | if [ -f "temp-lidarr-missing.json" ]; then 914 | rm "/scripts/temp-lidarr-missing.json" 915 | fi 916 | 917 | if [ -f "/scripts/temp-lidarr-cutoff.json" ]; then 918 | rm "/scripts/temp-lidarr-cutoff.json" 919 | fi 920 | 921 | if [ -f "/scripts/lidarr-monitored-list.json" ]; then 922 | rm "/scripts/lidarr-monitored-list.json" 923 | fi 924 | 925 | if [[ "$LIST" == "missing" || "$LIST" == "both" ]]; then 926 | log "Downloading missing list..." 927 | wget "$LidarrUrl/api/v1/wanted/missing?page=1&pagesize=${amount}&includeArtist=true&sortDir=desc&sortKey=releaseDate&apikey=${LidarrAPIkey}" -O "/scripts/temp-lidarr-missing.json" 928 | missingtotal=$(cat "/scripts/temp-lidarr-missing.json" | jq -r '.records | .[] | .id' | wc -l) 929 | log "FINDING MISSING ALBUMS: ${missingtotal} Found" 930 | fi 931 | if [[ "$LIST" == "cutoff" || "$LIST" == "both" ]]; then 932 | log "Downloading cutoff list..." 933 | wget "$LidarrUrl/api/v1/wanted/cutoff?page=1&pagesize=${amount}&includeArtist=true&sortDir=desc&sortKey=releaseDate&apikey=${LidarrAPIkey}" -O "/scripts/temp-lidarr-cutoff.json" 934 | cuttofftotal=$(cat "/scripts/temp-lidarr-cutoff.json" | jq -r '.records | .[] | .id' | wc -l) 935 | log "FINDING CUTOFF ALBUMS: ${cuttofftotal} Found" 936 | fi 937 | jq -s '.[]' /scripts/temp-lidarr-*.json > "/scripts/lidarr-monitored-list.json" 938 | missinglistalbumids=($(cat "/scripts/lidarr-monitored-list.json" | jq -r '.records | .[] | .id')) 939 | missinglisttotal=$(cat "/scripts/lidarr-monitored-list.json" | jq -r '.records | .[] | .id' | wc -l) 940 | if [ -f "/scripts/temp-lidarr-missing.json" ]; then 941 | rm "/scripts/temp-lidarr-missing.json" 942 | fi 943 | 944 | if [ -f "/scripts/temp-lidarr-cutoff.json" ]; then 945 | rm "/scripts/temp-lidarr-cutoff.json" 946 | fi 947 | 948 | if [ -f "/scripts/lidarr-monitored-list.json" ]; then 949 | rm "/scripts/lidarr-monitored-list.json" 950 | fi 951 | } 952 | 953 | ArtistAlbumList () { 954 | touch -d "168 hours ago" /config/cache/cache-info-check 955 | if [ -f /config/cache/artists/$artistid/checked ]; then 956 | if find /config/cache/artists/$artistid -type f -iname "checked" -not -newer "/config/cache/cache-info-check" | read; then 957 | rm /config/cache/artists/$artistid/checked 958 | if [ -f /config/cache/artists/$artistid/albumlist.json ]; then 959 | rm /config/cache/artists/$artistid/albumlist.json 960 | fi 961 | if [ -f /config/cache/artists/$artistid/albumlistlower.json ]; then 962 | rm /config/cache/artists/$artistid/albumlistlower.json 963 | fi 964 | else 965 | log "$logheader :: Cached info good" 966 | fi 967 | fi 968 | rm /config/cache/cache-info-check 969 | 970 | if [ ! -f /config/cache/artists/$artistid/checked ]; then 971 | albumcount="$(python3 /scripts/artist_discograpy.py "$artistid" | sort -u | wc -l)" 972 | if [ -d /config/cache/artists/$artistid/albums ]; then 973 | cachecount=$(ls /config/cache/artists/$artistid/albums/* | wc -l) 974 | else 975 | cachecount=0 976 | fi 977 | 978 | if [ $albumcount != $cachecount ]; then 979 | log "$logheader :: Searching for All Albums...." 980 | log "$logheader :: $albumcount Albums found!" 981 | albumids=($(python3 /scripts/artist_discograpy.py "$artistid" | sort -u)) 982 | if [ ! -d "/config/temp" ]; then 983 | mkdir "/config/temp" 984 | fi 985 | for id in ${!albumids[@]}; do 986 | currentprocess=$(( $id + 1 )) 987 | albumid="${albumids[$id]}" 988 | if [ ! -d /config/cache/artists/$artistid/albums ]; then 989 | mkdir -p /config/cache/artists/$artistid/albums 990 | chmod $FolderPermissions /config/cache/artists/$artistid 991 | chmod $FolderPermissions /config/cache/artists/$artistid/albums 992 | chown -R abc:abc /config/cache/artists/$artistid 993 | fi 994 | if [ -f /config/cache/artists/$artistid/albums/${albumid}-reg.json ]; then 995 | rm /config/cache/artists/$artistid/albums/${albumid}.json 996 | fi 997 | if [ ! -f /config/cache/artists/$artistid/albums/${albumid}-reg.json ]; then 998 | if wget "https://api.deezer.com/album/${albumid}" -O "/config/temp/${albumid}.json" -q; then 999 | log "$logheader :: $currentprocess of $albumcount :: Downloading Album info..." 1000 | mv /config/temp/${albumid}.json /config/cache/artists/$artistid/albums/${albumid}-reg.json 1001 | chmod $FilePermissions /config/cache/artists/$artistid/albums/${albumid}-reg.json 1002 | albumdata=$(cat /config/cache/artists/$artistid/albums/${albumid}-reg.json) 1003 | converttofilelower=${albumdata,,} 1004 | echo "$converttofilelower" > /config/cache/artists/$artistid/albums/${albumid}-lower.json 1005 | chmod $FilePermissions /config/cache/artists/$artistid/albums/${albumid}-lower.json 1006 | else 1007 | log "$logheader :: $currentprocess of $albumcount :: Error getting album information" 1008 | fi 1009 | else 1010 | log "$logheader :: $currentprocess of $albumcount :: Album info already downloaded" 1011 | fi 1012 | done 1013 | touch /config/cache/artists/$artistid/checked 1014 | chmod $FilePermissions /config/cache/artists/$artistid/checked 1015 | chown -R abc:abc /config/cache/artists/$artistid 1016 | if [ -d "/config/temp" ]; then 1017 | rm -rf "/config/temp" 1018 | fi 1019 | else 1020 | touch /config/cache/artists/$artistid/checked 1021 | chmod $FilePermissions /config/cache/artists/$artistid/checked 1022 | chown -R abc:abc /config/cache/artists/$artistid 1023 | fi 1024 | fi 1025 | } 1026 | 1027 | ArtistMode () { 1028 | log "####### DOWNLOAD AUDIO (ARTIST MODE)" 1029 | wantit=$(curl -s --header "X-Api-Key:"${LidarrAPIkey} --request GET "$LidarrUrl/api/v1/Artist/") 1030 | wantedtotal=$(echo "${wantit}"|jq -r '.[].sortName' | wc -l) 1031 | MBArtistID=($(echo "${wantit}" | jq -r ".[].foreignArtistId")) 1032 | variousartistname="$(echo "${wantit}" | jq -r '.[] | select(.foreignArtistId=="89ad4ac3-39f7-470e-963a-56509c546377") | .artistName')" 1033 | variousartistpath="$(echo "${wantit}" | jq -r '.[] | select(.foreignArtistId=="89ad4ac3-39f7-470e-963a-56509c546377") | .path')" 1034 | for id in ${!MBArtistID[@]}; do 1035 | artistnumber=$(( $id + 1 )) 1036 | mbid="${MBArtistID[$id]}" 1037 | albumartistmbzid="$mbid" 1038 | albummbid="" 1039 | albumreleasegroupmbzid="" 1040 | LidArtistPath="$(echo "${wantit}" | jq -r ".[] | select(.foreignArtistId==\"${mbid}\") | .path")" 1041 | pathbasename="$(dirname "$LidArtistPath")" 1042 | LidArtistNameCap="$(echo "${wantit}" | jq -r ".[] | select(.foreignArtistId==\"${mbid}\") | .artistName")" 1043 | albumartistname="$LidArtistNameCap" 1044 | LidArtistNameCapClean="$(echo "${LidArtistNameCap}" | sed -e "s%[^[:alpha:][:digit:]._()' -]% %g" -e "s/ */ /g")" 1045 | deezerartisturl="" 1046 | deezerartisturl=($(echo "${wantit}" | jq -r ".[] | select(.foreignArtistId==\"${mbid}\") | .links | .[] | select(.name==\"deezer\") | .url")) 1047 | deezerartisturlcount=$(echo "${wantit}" | jq -r ".[] | select(.foreignArtistId==\"${mbid}\") | .links | .[] | select(.name==\"deezer\") | .url" | wc -l) 1048 | originalpath="$LidArtistPath" 1049 | originalLidArtistNameCap="$LidArtistNameCap" 1050 | originalLidArtistNameCapClean="$LidArtistNameCapClean" 1051 | originalalbumartistname="$albumartistname" 1052 | originalalalbumartistmbzid="$albumartistmbzid" 1053 | logheader="" 1054 | logheader="$artistnumber of $wantedtotal :: $LidArtistNameCap" 1055 | logheaderartiststart="$logheader" 1056 | log "$logheader" 1057 | 1058 | if [ -z "$deezerartisturl" ]; then 1059 | log "$logheader :: ERROR :: Deezer Artist ID not found..." 1060 | continue 1061 | fi 1062 | 1063 | for url in ${!deezerartisturl[@]}; do 1064 | if [ ! -d "$pathbasename" ]; then 1065 | echo "ERROR: Path not found, add missing volume that matches Lidarr" 1066 | #continue 1067 | fi 1068 | urlnumber=$(( $url + 1 )) 1069 | deezerid="${deezerartisturl[$url]}" 1070 | DeezerArtistID=$(echo "${deezerid}" | grep -o '[[:digit:]]*') 1071 | artistid="$DeezerArtistID" 1072 | ArtistAlbumList 1073 | 1074 | if [ ! -f /config/cache/artists/$artistid/albumlistlower.json ]; then 1075 | log "$logheader :: Building Album List..." 1076 | albumslistdata=$(jq -s '.' /config/cache/artists/$artistid/albums/*.json) 1077 | echo "$albumslistdata" > /config/cache/artists/$artistid/albumlist.json 1078 | albumsdata=$(cat /config/cache/artists/$artistid/albumlist.json) 1079 | log "$logheader :: Done" 1080 | else 1081 | albumsdata=$(cat /config/cache/artists/$artistid/albumlist.json) 1082 | fi 1083 | log "$logheader :: Building Album List..." 1084 | albumlistdata=$(jq -s '.' /config/cache/artists/$artistid/albums/*-reg.json) 1085 | log "$logheader :: Done" 1086 | deezeralbumlistcount="$(echo "$albumlistdata" | jq -r "sort_by(.nb_tracks) | sort_by(.explicit_lyrics and .nb_tracks) | reverse | .[].id" | wc -l)" 1087 | deezeralbumlistids=($(echo "$albumlistdata" | jq -r "sort_by(.nb_tracks) | sort_by(.explicit_lyrics and .nb_tracks) | reverse | .[].id")) 1088 | logheader="$logheader :: $urlnumber of $deezerartisturlcount" 1089 | logheaderstart="$logheader" 1090 | log "$logheader" 1091 | 1092 | for id in ${!deezeralbumlistids[@]}; do 1093 | deezeralbumprocess=$(( $id + 1 )) 1094 | deezeralbumid="${deezeralbumlistids[$id]}" 1095 | albumreleasegroupmbzid="" 1096 | albummbid="" 1097 | deezeralbumdata="$(cat "/config/cache/artists/$artistid/albums/$deezeralbumid-reg.json")" 1098 | deezeralbumurl="https://deezer.com/album/$deezeralbumid" 1099 | deezeralbumtitle="$(echo "$deezeralbumdata" | jq -r ".title")" 1100 | deezeralbumtitleclean="$(echo "$deezeralbumtitle" | sed -e "s%[^[:alpha:][:digit:]._()' -]% %g" -e "s/ */ /g")" 1101 | deezeralbumartistid="$(echo "$deezeralbumdata" | jq -r ".artist.id" | head -n 1)" 1102 | deezeralbumdate="$(echo "$deezeralbumdata" | jq -r ".release_date")" 1103 | deezeralbumimage="$(echo "$deezeralbumdata" | jq -r ".cover_xl")" 1104 | deezeralbumtype="$(echo "$deezeralbumdata" | jq -r ".record_type")" 1105 | deezeralbumexplicit="$(echo "$deezeralbumdata" | jq -r ".explicit_lyrics")" 1106 | deezeralbumtrackcount="$(echo "$deezeralbumdata" | jq -r ".nb_tracks")" 1107 | if [ "$deezeralbumexplicit" == "true" ]; then 1108 | lyrictype="EXPLICIT" 1109 | else 1110 | lyrictype="CLEAN" 1111 | fi 1112 | deezeralbumyear="${deezeralbumdate:0:4}" 1113 | logheader="$logheader :: $deezeralbumprocess of $deezeralbumlistcount :: PROCESSING :: ${deezeralbumtype^^} :: $deezeralbumyear :: $lyrictype :: $deezeralbumtitle :: $deezeralbumtrackcount Tracks" 1114 | log "$logheader" 1115 | 1116 | LidArtistPath="$originalpath" 1117 | LidArtistNameCap="$originalLidArtistNameCap" 1118 | LidArtistNameCapClean="$originalLidArtistNameCapClean" 1119 | albumartistname="$originalalbumartistname" 1120 | albumartistmbzid="$originalalalbumartistmbzid" 1121 | 1122 | if [ -f /config/logs/downloads/$deezeralbumid ]; then 1123 | log "$logheader :: Album ($deezeralbumid) Already Downloaded..." 1124 | logheader="$logheaderstart" 1125 | continue 1126 | fi 1127 | 1128 | if [ "$deezeralbumartistid" != "$artistid" ]; then 1129 | if [ "$deezeralbumartistid" == "5080" ] && [ ! -z "$variousartistpath" ]; then 1130 | LidArtistPath="$variousartistpath" 1131 | LidArtistNameCap="$variousartistname" 1132 | LidArtistNameCapClean="$variousartistname" 1133 | albumartistname="$variousartistname" 1134 | albumartistmbzid="89ad4ac3-39f7-470e-963a-56509c546377" 1135 | else 1136 | log "$logheader :: Artist ID does not match, skipping..." 1137 | logheader="$logheaderstart" 1138 | continue 1139 | fi 1140 | fi 1141 | 1142 | albumfolder="$LidArtistNameCapClean - ${deezeralbumtype^^} - $deezeralbumyear - $deezeralbumtitleclean ($lyrictype) ($deezeralbumid)" 1143 | 1144 | if [ -f "$LidArtistPath/$albumfolder/errors.txt" ]; then 1145 | log "$logheader :: Existing Download found with errors, retrying..." 1146 | rm -rf "$LidArtistPath/$albumfolder" 1147 | fi 1148 | 1149 | if [ $ALBUM_FILTER == true ]; then 1150 | AlbumFilter 1151 | 1152 | if [ $filtermatch == true ]; then 1153 | log "$logheader :: Album Type matched unwanted filter "$filtertype", skipping..." 1154 | if [ ! -d /config/logs/filtered ]; then 1155 | mkdir -p /config/logs/filtered 1156 | fi 1157 | if [ ! -f /config/logs/filtered/$deezeralbumid ]; then 1158 | touch /config/logs/filtered/$deezeralbumid 1159 | fi 1160 | logheader="$logheaderstart" 1161 | continue 1162 | fi 1163 | fi 1164 | 1165 | if [ -d "$LidArtistPath" ]; then 1166 | if [ "${deezeralbumtype^^}" != "SINGLE" ]; then 1167 | if [ "$deezeralbumexplicit" == "false" ]; then 1168 | if find "$LidArtistPath" -iname "$LidArtistNameCapClean - ${deezeralbumtype^^} - * - $deezeralbumtitleclean (EXPLICIT) *" | read; then 1169 | log "$logheader :: Duplicate EXPLICIT ${deezeralbumtype^^} found, skipping..." 1170 | logheader="$logheaderstart" 1171 | continue 1172 | fi 1173 | if find "$LidArtistPath" -iname "$LidArtistNameCapClean - ${deezeralbumtype^^} - * - $deezeralbumtitleclean (Deluxe*(EXPLICIT) *" | read; then 1174 | log "$logheader :: Duplicate EXPLICIT ${deezeralbumtype^^} Deluxe found, skipping..." 1175 | logheader="$logheaderstart" 1176 | continue 1177 | fi 1178 | if find "$LidArtistPath" -iname "$LidArtistNameCapClean - ${deezeralbumtype^^} - * - $deezeralbumtitleclean (CLEAN) *" | read; then 1179 | log "$logheader :: Duplicate CLEAN ${deezeralbumtype^^} found, skipping..." 1180 | logheader="$logheaderstart" 1181 | continue 1182 | fi 1183 | if find "$LidArtistPath" -iname "$LidArtistNameCapClean - ${deezeralbumtype^^} - * - $deezeralbumtitleclean (Deluxe*(CLEAN) *" | read; then 1184 | log "$logheader :: Duplicate CLEAN ${deezeralbumtype^^} Deluxe found, skipping..." 1185 | logheader="$logheaderstart" 1186 | continue 1187 | fi 1188 | fi 1189 | if [ "$deezeralbumexplicit" == "true" ]; then 1190 | if find "$LidArtistPath" -iname "$LidArtistNameCapClean - ${deezeralbumtype^^} - $deezeralbumyear - $deezeralbumtitleclean (EXPLICIT) *" | read; then 1191 | log "$logheader :: Duplicate EXPLICIT ${deezeralbumtype^^} $deezeralbumyear found, skipping..." 1192 | logheader="$logheaderstart" 1193 | continue 1194 | fi 1195 | if find "$LidArtistPath" -iname "$LidArtistNameCapClean - ${deezeralbumtype^^} - * - $deezeralbumtitleclean (Deluxe*(EXPLICIT) *" | read; then 1196 | log "$logheader :: Duplicate EXPLICIT ${deezeralbumtype^^} Deluxe found, skipping..." 1197 | logheader="$logheaderstart" 1198 | continue 1199 | fi 1200 | if find "$LidArtistPath" -iname "$LidArtistNameCapClean - ${deezeralbumtype^^} - * - $deezeralbumtitleclean (CLEAN) *" | read; then 1201 | log "$logheader :: Duplicate CLEAN ${deezeralbumtype^^} found, skipping..." 1202 | find "$LidArtistPath" -iname "$LidArtistNameCapClean - ${deezeralbumtype^^} - * - $deezeralbumtitleclean (CLEAN) *" -exec rm -rf "{}" \; &> /dev/null 1203 | PlexNotification "$LidArtistPath" 1204 | fi 1205 | fi 1206 | fi 1207 | if [ "${deezeralbumtype^^}" == "SINGLE" ]; then 1208 | if [ "$deezeralbumexplicit" == "false" ]; then 1209 | if find "$LidArtistPath" -iname "$LidArtistNameCapClean - ${deezeralbumtype^^} - * - $deezeralbumtitleclean (EXPLICIT) *" | read; then 1210 | log "$logheader :: Duplicate EXPLICIT SINGLE already downloaded, skipping..." 1211 | logheader="$logheaderstart" 1212 | continue 1213 | fi 1214 | fi 1215 | if [ "$deezeralbumexplicit" == "true" ]; then 1216 | if find "$LidArtistPath" -iname "$LidArtistNameCapClean - ${deezeralbumtype^^} - $deezeralbumyear - $deezeralbumtitleclean (EXPLICIT) *" | read; then 1217 | log "$logheader :: Duplicate EXPLICIT SINGLE already downloaded, skipping..." 1218 | logheader="$logheaderstart" 1219 | continue 1220 | fi 1221 | fi 1222 | fi 1223 | 1224 | if find "$LidArtistPath" -iname "* ($deezeralbumid)" | read; then 1225 | log "$logheader :: Already Downloaded..." 1226 | logheader="$logheaderstart" 1227 | continue 1228 | fi 1229 | fi 1230 | logheader="$logheader :: DOWNLOAD :: $deezeralbumtrackcount Tracks" 1231 | log "$logheader :: Sending \"$deezeralbumurl\" to download client..." 1232 | python3 /scripts/dlclient.py -b $quality "$deezeralbumurl" 1233 | rm -rf /tmp/deemix-imgs/* 1234 | 1235 | if [ -f "$DOWNLOADS/amd/dlclient/errors.txt" ]; then 1236 | log "$logheader :: DOWNLOAD :: ERROR :: Error log found, skipping..." 1237 | rm "$DOWNLOADS"/amd/dlclient/* 1238 | logheader="$logheaderstart" 1239 | continue 1240 | fi 1241 | 1242 | if find "$DOWNLOADS"/amd/dlclient -regex ".*/.*\.\(flac\|mp3\)" | read; then 1243 | DownloadQualityCheck 1244 | fi 1245 | 1246 | downloadcount=$(find "$DOWNLOADS"/amd/dlclient -regex ".*/.*\.\(flac\|mp3\)" | wc -l) 1247 | 1248 | if [ "$deezeralbumtrackcount" != "$downloadcount" ]; then 1249 | log "$logheader :: DOWNLOAD :: ERROR :: Downloaded track count ($downloadcount) does not match requested track count, skipping..." 1250 | rm "$DOWNLOADS"/amd/dlclient/* 1251 | logheader="$logheaderstart" 1252 | continue 1253 | else 1254 | log "$logheader :: DOWNLOAD :: $downloadcount Tracks found!" 1255 | fi 1256 | 1257 | 1258 | if find "$DOWNLOADS"/amd/dlclient -regex ".*/.*\.\(flac\|mp3\)" | read; then 1259 | find "$DOWNLOADS"/amd/dlclient -type d -exec chmod $FolderPermissions {} \; 1260 | find "$DOWNLOADS"/amd/dlclient -type f -exec chmod $FilePermissions {} \; 1261 | chown -R abc:abc "$DOWNLOADS"/amd/dlclient 1262 | else 1263 | log "$logheader :: DOWNLOAD :: ERROR :: No files found" 1264 | logheader="$logheaderstart" 1265 | continue 1266 | fi 1267 | 1268 | file=$(find "$DOWNLOADS"/amd/dlclient -regex ".*/.*\.\(flac\|mp3\)" | head -n 1) 1269 | if [ ! -z "$file" ]; then 1270 | artwork="$(dirname "$file")/folder.jpg" 1271 | if ffmpeg -y -i "$file" -c:v copy "$artwork" 2>/dev/null; then 1272 | log "$logheader :: Artwork Extracted" 1273 | else 1274 | log "$logheader :: ERROR :: No artwork found" 1275 | fi 1276 | fi 1277 | 1278 | if [ $ENABLEPOSTPROCESSING == true ]; then 1279 | TagFix 1280 | Conversion 1281 | AddReplaygainTags 1282 | fi 1283 | 1284 | 1285 | if [ ! -d "$LidArtistPath/$albumfolder" ]; then 1286 | mkdir -p "$LidArtistPath/$albumfolder" 1287 | chmod $FolderPermissions "$LidArtistPath/$albumfolder" 1288 | fi 1289 | mv "$DOWNLOADS"/amd/dlclient/* "$LidArtistPath/$albumfolder"/ 1290 | chmod $FilePermissions "$LidArtistPath/$albumfolder"/* 1291 | chown -R abc:abc "$LidArtistPath/$albumfolder" 1292 | PlexNotification 1293 | logheader="$logheaderstart" 1294 | 1295 | if [ ! -d /config/logs/downloads ]; then 1296 | mkdir -p /config/logs/downloads 1297 | fi 1298 | 1299 | if [ ! -f /config/logs/downloads/$deezeralbumid ]; then 1300 | touch /config/logs/downloads/$deezeralbumid 1301 | fi 1302 | done 1303 | logheader="$logheaderartiststart" 1304 | done 1305 | touch "/config/cache/$LidArtistNameCapClean-$mbid-artist-complete" 1306 | done 1307 | } 1308 | 1309 | WantedMode () { 1310 | echo "####### DOWNLOAD AUDIO (WANTED MODE)" 1311 | LidarrList 1312 | 1313 | for id in ${!missinglistalbumids[@]}; do 1314 | currentprocess=$(( $id + 1 )) 1315 | lidarralbumid="${missinglistalbumids[$id]}" 1316 | albumdeezerurl="" 1317 | error=0 1318 | lidarralbumdata=$(curl -s --header "X-Api-Key:"${LidarrAPIkey} --request GET "$LidarrUrl/api/v1/album?albumIds=${lidarralbumid}") 1319 | OLDIFS="$IFS" 1320 | IFS=$'\n' 1321 | lidarralbumdrecordids=($(echo "${lidarralbumdata}" | jq -r '.[] | .releases | sort_by(.trackCount) | reverse | .[].foreignReleaseId')) 1322 | IFS="$OLDIFS" 1323 | albumreleasegroupmbzid=$(echo "${lidarralbumdata}"| jq -r '.[] | .foreignAlbumId') 1324 | releases=$(curl -s -A "$agent" "${MBRAINZMIRROR}/ws/2/release?release-group=$albumreleasegroupmbzid&inc=url-rels&fmt=json") 1325 | albumreleaseid=($(echo "${releases}"| jq -r '.releases[] | select(.relations[].url.resource | contains("deezer")) | .id')) 1326 | sleep $MBRATELIMIT 1327 | lidarralbumtype="$(echo "${lidarralbumdata}"| jq -r '.[] | .albumType')" 1328 | lidarralbumtypelower="$(echo ${lidarralbumtype,,})" 1329 | albumtitle="$(echo "${lidarralbumdata}"| jq -r '.[] | .title')" 1330 | albumreleasedate="$(echo "${lidarralbumdata}"| jq -r '.[] | .releaseDate')" 1331 | albumreleaseyear="${albumreleasedate:0:4}" 1332 | albumclean="$(echo "$albumtitle" | sed -e "s%[^[:alpha:][:digit:]._()' -]% %g" -e "s/ */ /g")" 1333 | albumartistmbzid=$(echo "${lidarralbumdata}"| jq -r '.[].artist.foreignArtistId') 1334 | albumartistname=$(echo "${lidarralbumdata}"| jq -r '.[].artist.artistName') 1335 | logheader="$currentprocess of $missinglisttotal :: $albumartistname :: $albumreleaseyear :: $lidarralbumtype :: $albumtitle" 1336 | filelogheader="$albumartistname :: $albumreleaseyear :: $lidarralbumtype :: $albumtitle" 1337 | 1338 | if [ -f "/config/logs/searched/$albumreleasegroupmbzid" ]; then 1339 | log "$logheader :: PREVIOUSLY SEARCHED, SKIPPING..." 1340 | continue 1341 | fi 1342 | 1343 | if [ ! -z "$albumreleaseid" ]; then 1344 | for id in ${!albumreleaseid[@]}; do 1345 | currentalbumprocess=$(( $id + 1 )) 1346 | albummbid="${albumreleaseid[$id]}" 1347 | releasedata=$(echo "$releases" | jq -r ".releases[] | select(.id==\"$albummbid\")") 1348 | albumdeezerurl=$(echo "$releasedata" | jq -r '.relations[].url | select(.resource | contains("deezer")) | .resource') 1349 | DeezerAlbumID="$(echo "$albumdeezerurl" | grep -o '[[:digit:]]*')" 1350 | albumdeezerurl="https://api.deezer.com/album/$DeezerAlbumID" 1351 | deezeralbumsearchdata=$(curl -s "${albumdeezerurl}") 1352 | errocheck="$(echo "$deezeralbumsearchdata" | jq -r ".error.code")" 1353 | if [ "$errocheck" != "null" ]; then 1354 | log "$logheader :: ERROR :: Provided URL is broken, fallback to artist search..." 1355 | albumdeezerurl="" 1356 | error=1 1357 | continue 1358 | else 1359 | error=0 1360 | albumdeezerurl="https://deezer.com/album/$DeezerAlbumID" 1361 | deezeralbumtitle="$(echo "$deezeralbumsearchdata" | jq -r ".title")" 1362 | deezeralbumtype="$(echo "$deezeralbumsearchdata" | jq -r ".record_type")" 1363 | deezeralbumdate="$(echo "$deezeralbumsearchdata" | jq -r ".release_date")" 1364 | deezeralbumyear="${deezeralbumdate:0:4}" 1365 | explicit="$(echo "$deezeralbumsearchdata" | jq -r ".explicit_lyrics")" 1366 | if [ "$explicit" == "true" ]; then 1367 | break 1368 | else 1369 | albumdeezerurl="" 1370 | continue 1371 | fi 1372 | fi 1373 | done 1374 | if [ -z "$albumdeezerurl" ]; then 1375 | for id in ${!albumreleaseid[@]}; do 1376 | currentalbumprocess=$(( $id + 1 )) 1377 | albummbid="${albumreleaseid[$id]}" 1378 | releasedata=$(echo "$releases" | jq -r ".releases[] | select(.id==\"$albummbid\")") 1379 | albumdeezerurl=$(echo "$releasedata" | jq -r '.relations[].url | select(.resource | contains("deezer")) | .resource') 1380 | DeezerAlbumID="$(echo "$albumdeezerurl" | grep -o '[[:digit:]]*')" 1381 | albumdeezerurl="https://api.deezer.com/album/$DeezerAlbumID" 1382 | deezeralbumsearchdata=$(curl -s "${albumdeezerurl}") 1383 | errocheck="$(echo "$deezeralbumsearchdata" | jq -r ".error.code")" 1384 | if [ "$errocheck" != "null" ]; then 1385 | log "$logheader :: ERROR :: Provided URL is broken, fallback to artist search..." 1386 | albumdeezerurl="" 1387 | albummbid="" 1388 | error=1 1389 | continue 1390 | else 1391 | error=0 1392 | albumdeezerurl="https://deezer.com/album/$DeezerAlbumID" 1393 | deezeralbumtitle="$(echo "$deezeralbumsearchdata" | jq -r ".title")" 1394 | deezeralbumtype="$(echo "$deezeralbumsearchdata" | jq -r ".record_type")" 1395 | deezeralbumdate="$(echo "$deezeralbumsearchdata" | jq -r ".release_date")" 1396 | deezeralbumyear="${deezeralbumdate:0:4}" 1397 | explicit="$(echo "$deezeralbumsearchdata" | jq -r ".explicit_lyrics")" 1398 | break 1399 | fi 1400 | done 1401 | fi 1402 | else 1403 | albummbid="" 1404 | error=1 1405 | fi 1406 | 1407 | if [ -f "/config/logs/searched/$albumreleasegroupmbzid" ]; then 1408 | log "$logheader :: PREVIOUSLY SEARCHED, SKIPPING..." 1409 | continue 1410 | fi 1411 | 1412 | if [[ -f "/config/logs/notfound.log" && $error == 1 ]]; then 1413 | if cat "/config/logs/notfound.log" | grep -i ":: $albumreleasegroupmbzid ::" | read; then 1414 | log "$logheader :: PREVIOUSLY NOT FOUND SKIPPING..." 1415 | if [ ! -d "/config/logs/searched" ]; then 1416 | mkdir -p "/config/logs/searched" 1417 | fi 1418 | if [ -d "/config/logs/searched" ]; then 1419 | touch /config/logs/searched/$albumreleasegroupmbzid 1420 | fi 1421 | continue 1422 | elif [ -f "/config/logs/searched/$albumreleasegroupmbzid" ]; then 1423 | log "$logheader :: PREVIOUSLY SEARCHED, SKIPPING..." 1424 | continue 1425 | else 1426 | log "$logheader :: SEARCHING..." 1427 | error=0 1428 | fi 1429 | else 1430 | log "$logheader :: SEARCHING..." 1431 | error=0 1432 | fi 1433 | 1434 | sanatizedartistname="$(echo "${albumartistname}" | sed -e "s%[^[:alpha:][:digit:]._()' -]% %g" -e "s/ */ /g")" 1435 | albumartistlistlinkid=($(echo "${lidarralbumdata}"| jq -r '.[].artist | .links | .[] | select(.name=="deezer") | .url' | sort -u | grep -o '[[:digit:]]*')) 1436 | if [ "$albumartistname" == "Korn" ]; then # Fix for online source naming convention... 1437 | originalartistname="$albumartistname" 1438 | albumartistname="KoЯn" 1439 | else 1440 | originalartistname="" 1441 | fi 1442 | artistclean="$(echo "$albumartistname" | sed -e "s%[^[:alpha:][:digit:]._()' -]% %g" -e "s/ */ /g")" 1443 | artistcleans="$(echo "$albumartistname" | sed -e "s%[^[:alpha:][:digit:]._()' -]% %g" -e "s/ */ /g")" 1444 | albumartistnamesearch="$(jq -R -r @uri <<<"${artistcleans}")" 1445 | if [ ! -z "$originalartistname" ]; then # Fix for online source naming convention... 1446 | albumartistname="$originalartistname" 1447 | fi 1448 | albumartistpath=$(echo "${lidarralbumdata}"| jq -r '.[].artist.path') 1449 | albumbimportfolder="$DOWNLOADS/amd/import/$artistclean - $albumclean ($albumreleaseyear)-WEB-$lidarralbumtype-deemix" 1450 | albumbimportfoldername="$(basename "$albumbimportfolder")" 1451 | 1452 | if [ -d "$albumbimportfolder" ]; then 1453 | log "$logheader :: Already Downloaded, skipping..." 1454 | if [ "$remotepath" == "true" ]; then 1455 | albumbimportfolder="$LIDARRREMOTEPATH/amd/import/$artistclean - $albumclean ($albumreleaseyear)-WEB-$lidarralbumtype-deemix" 1456 | albumbimportfoldername="$(basename "$albumbimportfolder")" 1457 | fi 1458 | LidarrProcessIt=$(curl -s "$LidarrUrl/api/v1/command" --header "X-Api-Key:"${LidarrAPIkey} -H "Content-Type: application/json" --data "{\"name\":\"DownloadedAlbumsScan\", \"path\":\"${albumbimportfolder}\"}") 1459 | log "$logheader :: LIDARR IMPORT NOTIFICATION SENT! :: $albumbimportfoldername" 1460 | continue 1461 | fi 1462 | 1463 | if [ -f "/config/logs/download.log" ]; then 1464 | if cat "/config/logs/download.log" | grep -i "$albumreleasegroupmbzid :: $albumtitle :: $albumbimportfolder" | read; then 1465 | log "$logheader :: Already Downloaded" 1466 | continue 1467 | fi 1468 | fi 1469 | 1470 | if [ -z "$albumdeezerurl" ]; then 1471 | 1472 | if [[ "$albumartistname" != "Various Artists" && "$SearchType" != "fuzzy" ]]; then 1473 | if [ -z "${albumartistlistlinkid}" ]; then 1474 | mbjson=$(curl -s -A "$agent" "${MBRAINZMIRROR}/ws/2/artist/${albumartistmbzid}?inc=url-rels&fmt=json") 1475 | albumartistlistlinkid=($(echo "$mbjson" | jq -r '.relations | .[] | .url | select(.resource | contains("deezer")) | .resource' | sort -u | grep -o '[[:digit:]]*')) 1476 | sleep $MBRATELIMIT 1477 | fi 1478 | if [ ! -z "${albumartistlistlinkid}" ]; then 1479 | for id in ${!albumartistlistlinkid[@]}; do 1480 | currentprocess=$(( $id + 1 )) 1481 | deezerartistid="${albumartistlistlinkid[$id]}" 1482 | artistid="$deezerartistid" 1483 | ArtistAlbumList 1484 | 1485 | if [ ! -f /config/cache/artists/$artistid/albumlistlower.json ]; then 1486 | log "$logheader :: Building Album List..." 1487 | albumslistdata=$(jq -s '.' /config/cache/artists/$artistid/albums/*-reg.json) 1488 | echo "$albumslistdata" > /config/cache/artists/$artistid/albumlist.json 1489 | albumsdata=$(cat /config/cache/artists/$artistid/albumlist.json) 1490 | log "$logheader :: Done" 1491 | else 1492 | albumsdata=$(cat /config/cache/artists/$artistid/albumlist.json) 1493 | fi 1494 | 1495 | if [ ! -f /config/cache/artists/$artistid/albumlistlower.json ]; then 1496 | log "$logheader :: Building Lowercase Album List..." 1497 | albumsdatalower=$(jq -s '.' /config/cache/artists/$artistid/albums/*-lower.json) 1498 | echo "$albumsdatalower" > /config/cache/artists/$artistid/albumlistlower.json 1499 | albumsdatalower=$(cat /config/cache/artists/$artistid/albumlistlower.json) 1500 | log "$logheader :: Done" 1501 | else 1502 | albumsdatalower=$(cat /config/cache/artists/$artistid/albumlistlower.json) 1503 | fi 1504 | 1505 | for id in "${!lidarralbumdrecordids[@]}"; do 1506 | ablumrecordreleaseid=${lidarralbumdrecordids[$id]} 1507 | albummbid="" 1508 | ablumrecordreleasedata=$(echo "${lidarralbumdata}" | jq -r ".[] | .releases | .[] | select(.foreignReleaseId==\"$ablumrecordreleaseid\")") 1509 | albumtitle="$(echo "$ablumrecordreleasedata" | jq -r '.title')" 1510 | albumtrackcount=$(echo "$ablumrecordreleasedata" | jq -r '.trackCount') 1511 | first=${albumtitle%% *} 1512 | firstlower=${first,,} 1513 | log "$logheader :: Filtering out Titles not containing \"$first\" and Track Count: $albumtrackcount" 1514 | DeezerArtistAlbumListSortTotal=$(echo "$albumsdatalower" | jq -r "sort_by(.nb_tracks) | sort_by(.explicit_lyrics and .nb_tracks) | reverse | .[] | select(.title | contains(\"$firstlower\")) | select(.nb_tracks==$albumtrackcount) | .id" | wc -l) 1515 | 1516 | if [ "$DeezerArtistAlbumListSortTotal" == "0" ]; then 1517 | log "$logheader :: ERROR :: No albums found..." 1518 | albumdeezerurl="" 1519 | continue 1520 | fi 1521 | DeezerArtistAlbumListAlbumID=($(echo "$albumsdatalower" | jq -r "sort_by(.nb_tracks) | sort_by(.explicit_lyrics and .nb_tracks) | reverse | .[] | select(.title | contains(\"$firstlower\")) | select(.nb_tracks==$albumtrackcount) | .id")) 1522 | 1523 | log "$logheader :: Checking $DeezerArtistAlbumListSortTotal Albums for match ($albumtitle) with Max Distance Score of 2 or less" 1524 | for id in ${!DeezerArtistAlbumListAlbumID[@]}; do 1525 | currentprocess=$(( $id + 1 )) 1526 | deezeralbumid="${DeezerArtistAlbumListAlbumID[$id]}" 1527 | deezeralbumdata="$(echo "$albumsdata" | jq ".[] | select(.id==$deezeralbumid)")" 1528 | deezeralbumtitle="$(echo "$deezeralbumdata" | jq -r ".title")" 1529 | deezeralbumtype="$(echo "$deezeralbumdata" | jq -r ".record_type")" 1530 | deezeralbumdate="$(echo "$deezeralbumdata" | jq -r ".release_date")" 1531 | deezeralbumyear="${deezeralbumdate:0:4}" 1532 | explicit="$(echo "$deezeralbumdata" | jq -r ".explicit_lyrics")" 1533 | diff=$(levenshtein "${albumtitle,,}" "${deezeralbumtitle,,}") 1534 | if [ "$diff" -le "2" ]; then 1535 | log "$logheader :: ${albumtitle,,} vs ${deezeralbumtitle,,} :: Distance = $diff :: $deezeralbumid :: MATCH" 1536 | deezersearchalbumid="$deezeralbumid" 1537 | break 1538 | else 1539 | deezersearchalbumid="" 1540 | continue 1541 | fi 1542 | done 1543 | if [ -z "$deezersearchalbumid" ]; then 1544 | log "$logheader :: $albumtitle :: ERROR :: NO MATCH FOUND" 1545 | albumdeezerurl="" 1546 | continue 1547 | else 1548 | albumdeezerurl="https://deezer.com/album/$deezersearchalbumid" 1549 | break 1550 | fi 1551 | done 1552 | 1553 | if [ -z "$albumdeezerurl" ]; then 1554 | for id in "${!lidarralbumdrecordids[@]}"; do 1555 | ablumrecordreleaseid=${lidarralbumdrecordids[$id]} 1556 | albummbid="" 1557 | ablumrecordreleasedata=$(echo "${lidarralbumdata}" | jq -r ".[] | .releases | .[] | select(.foreignReleaseId==\"$ablumrecordreleaseid\")") 1558 | albumtitle="$(echo "$ablumrecordreleasedata" | jq -r '.title')" 1559 | albumtrackcount=$(echo "$ablumrecordreleasedata" | jq -r '.trackCount') 1560 | first=${albumtitle%% *} 1561 | firstlower=${first,,} 1562 | log "$logheader :: Filtering out Titles not containing \"$first\" and Track Count: $albumtrackcount" 1563 | DeezerArtistAlbumListSortTotal=$(echo "$albumsdatalower" | jq -r "sort_by(.nb_tracks) | sort_by(.explicit_lyrics and .nb_tracks) | reverse | .[] | select(.title | contains(\"$firstlower\")) | select(.nb_tracks==$albumtrackcount) | .id" | wc -l) 1564 | 1565 | if [ "$DeezerArtistAlbumListSortTotal" == "0" ]; then 1566 | log "$logheader :: ERROR :: No albums found..." 1567 | albumdeezerurl="" 1568 | continue 1569 | fi 1570 | DeezerArtistAlbumListAlbumID=($(echo "$albumsdatalower" | jq -r "sort_by(.nb_tracks) | sort_by(.explicit_lyrics and .nb_tracks) | reverse | .[] | select(.title | contains(\"$firstlower\")) | select(.nb_tracks==$albumtrackcount) | .id")) 1571 | log "$logheader :: Checking $DeezerArtistAlbumListSortTotal Albums for match ($albumtitle) with Max Distance Score of $MatchDistance or less" 1572 | for id in ${!DeezerArtistAlbumListAlbumID[@]}; do 1573 | currentprocess=$(( $id + 1 )) 1574 | deezeralbumid="${DeezerArtistAlbumListAlbumID[$id]}" 1575 | deezeralbumdata="$(echo "$albumsdata" | jq ".[] | select(.id==$deezeralbumid)")" 1576 | deezeralbumtitle="$(echo "$deezeralbumdata" | jq -r ".title")" 1577 | deezeralbumtype="$(echo "$deezeralbumdata" | jq -r ".record_type")" 1578 | deezeralbumdate="$(echo "$deezeralbumdata" | jq -r ".release_date")" 1579 | deezeralbumyear="${deezeralbumdate:0:4}" 1580 | explicit="$(echo "$deezeralbumdata" | jq -r ".explicit_lyrics")" 1581 | diff=$(levenshtein "${albumtitle,,}" "${deezeralbumtitle,,}") 1582 | if [ "$diff" -le "$MatchDistance" ]; then 1583 | log "$logheader :: ${albumtitle,,} vs ${deezeralbumtitle,,} :: Distance = $diff :: $deezeralbumid :: MATCH" 1584 | deezersearchalbumid="$deezeralbumid" 1585 | break 1586 | else 1587 | deezersearchalbumid="" 1588 | continue 1589 | fi 1590 | done 1591 | if [ -z "$deezersearchalbumid" ]; then 1592 | log "$logheader :: $albumtitle :: ERROR :: NO MATCH FOUND" 1593 | albumdeezerurl="" 1594 | else 1595 | albumdeezerurl="https://deezer.com/album/$deezersearchalbumid" 1596 | break 1597 | fi 1598 | done 1599 | fi 1600 | done 1601 | 1602 | if [ ! -z "$albumdeezerurl" ]; then 1603 | albumreleaseyear="$deezeralbumyear" 1604 | lidarralbumtype="$deezeralbumtype" 1605 | albumclean="$(echo "$deezeralbumtitle" | sed -e "s%[^[:alpha:][:digit:]._()' -]% %g" -e "s/ */ /g")" 1606 | albumdeezerurl="https://deezer.com/album/$deezersearchalbumid" 1607 | fi 1608 | else 1609 | if ! [ -f "/config/logs/musicbrainzerror.log" ]; then 1610 | touch "/config/logs/musicbrainzerror.log" 1611 | fi 1612 | if [ -f "/config/logs/musicbrainzerror.log" ]; then 1613 | log "$logheader :: ERROR: musicbrainz id: $albumartistmbzid is missing deezer link, see: \"/config/logs/musicbrainzerror.log\" for more detail..." 1614 | if cat "/config/logs/musicbrainzerror.log" | grep "$albumartistmbzid" | read; then 1615 | sleep 0 1616 | else 1617 | echo "Update Musicbrainz Relationship Page: https://musicbrainz.org/artist/$albumartistmbzid/relationships for \"${albumartistname}\" with Deezer Artist Link" >> "/config/logs/musicbrainzerror.log" 1618 | fi 1619 | fi 1620 | fi 1621 | fi 1622 | 1623 | if [[ "$SearchType" == "artist" && "$albumartistname" != "Various Artists" ]]; then 1624 | if [ -z "$albumdeezerurl" ]; then 1625 | log "$logheader :: Skipping fuzzy search..." 1626 | error=1 1627 | fi 1628 | elif [[ -z "$albumdeezerurl" && -z "$albumtidalurl" ]]; then 1629 | log "$logheader :: ERROR :: Fallback to fuzzy search..." 1630 | log "$logheader :: FUZZY SEARCHING..." 1631 | for id in "${!lidarralbumdrecordids[@]}"; do 1632 | ablumrecordreleaseid=${lidarralbumdrecordids[$id]} 1633 | ablumrecordreleasedata=$(echo "${lidarralbumdata}" | jq -r ".[] | .releases | .[] | select(.foreignReleaseId==\"$ablumrecordreleaseid\")") 1634 | albumtitle="$(echo "$ablumrecordreleasedata" | jq -r '.title')" 1635 | albumtrackcount=$(echo "$ablumrecordreleasedata" | jq -r '.trackCount') 1636 | albummbid="" 1637 | albumtitlecleans="$(echo "$albumtitle" | sed -e "s%[^[:alpha:][:digit:]._()' -]% %g" -e "s/ */ /g")" 1638 | albumclean="$(echo "$albumtitle" | sed -e "s%[^[:alpha:][:digit:]._()' -]% %g" -e "s/ */ /g")" 1639 | albumtitlesearch="$(jq -R -r @uri <<<"${albumtitlecleans}")" 1640 | deezersearchalbumid="" 1641 | deezeralbumtitle="" 1642 | explicit="false" 1643 | first=${albumtitle%% *} 1644 | firstlower=${first,,} 1645 | if [ "$albumartistname" != "Various Artists" ]; then 1646 | log "$logheader :: Searching using $albumartistname + $albumtitle" 1647 | deezersearchurl="https://api.deezer.com/search?q=artist:%22${albumartistnamesearch}%22%20album:%22${albumtitlesearch}%22&limit=1000" 1648 | deezeralbumsearchdata=$(curl -s "${deezersearchurl}") 1649 | 1650 | else 1651 | log "$logheader :: Searching using $albumtitle" 1652 | deezersearchurl="https://api.deezer.com/search?q=album:%22${albumtitlesearch}%22&limit=1000" 1653 | deezeralbumsearchdata=$(curl -s "${deezersearchurl}") 1654 | fi 1655 | 1656 | deezersearchcount="$(echo "$deezeralbumsearchdata" | jq -r ".total")" 1657 | if [ "$deezersearchcount" == "0" ]; then 1658 | if [ "$albumartistname" != "Various Artists" ]; then 1659 | log "$logheader :: No results found, fallback search..." 1660 | log "$logheader :: Searching using $albumtitle" 1661 | deezersearchurl="https://api.deezer.com/search?q=album:%22${albumtitlesearch}%22&limit=1000" 1662 | deezeralbumsearchdata=$(curl -s "${deezersearchurl}") 1663 | deezersearchcount="$(echo "$deezeralbumsearchdata" | jq -r ".total")" 1664 | deezersearchdata="$(echo "$deezeralbumsearchdata" | jq -r ".data | .[]")" 1665 | deezersearchdatalower=${deezersearchdata,,} 1666 | searchdata=$(echo "$deezersearchdatalower" | jq -r "select(.album.title| contains (\"$firstlower\"))") 1667 | else 1668 | error=1 1669 | continue 1670 | fi 1671 | else 1672 | deezersearchdata="$(echo "$deezeralbumsearchdata" | jq -r ".data | .[]")" 1673 | deezersearchdatalower=${deezersearchdata,,} 1674 | searchdata=$(echo "$deezersearchdatalower" | jq -r "select(.album.title| contains (\"$firstlower\"))") 1675 | fi 1676 | log "$logheader :: Filtering out Titles not containing \"$first\"" 1677 | deezersearchcount="$(echo "$searchdata" | jq -r ".album.id" | sort -u | wc -l)" 1678 | log "$logheader :: $deezersearchcount Albums Found" 1679 | if [ "$deezersearchcount" == "0" ]; then 1680 | log "$logheader :: ERROR :: No albums found..." 1681 | log "$logheader :: Searching without filter..." 1682 | searchdata=$(echo "$deezersearchdatalower") 1683 | deezersearchcount="$(echo "$searchdata" | jq -r ".album.id" | sort -u | wc -l)" 1684 | fi 1685 | log "$logheader :: $deezersearchcount Albums Found" 1686 | if [ -z "$deezersearchalbumid" ]; then 1687 | if [ ! -d "/scripts/temp" ]; then 1688 | mkdir -p /scripts/temp 1689 | else 1690 | find /scripts/temp -type f -delete 1691 | fi 1692 | 1693 | albumidlist=($(echo "$searchdata" | jq -r "select(.explicit_lyrics==true) |.album.id" | sort -u)) 1694 | albumidlistcount="$(echo "$searchdata" | jq -r "select(.explicit_lyrics==true) |.album.id" | sort -u | wc -l)" 1695 | if [ ! -z "$albumidlist" ]; then 1696 | log "$logheader :: $albumidlistcount Explicit Albums Found" 1697 | for id in ${!albumidlist[@]}; do 1698 | albumid="${albumidlist[$id]}" 1699 | 1700 | if ! find /scripts/temp -type f -iname "*-$albumid" | read; then 1701 | touch "/scripts/temp/explicit-$albumid" 1702 | fi 1703 | 1704 | done 1705 | fi 1706 | 1707 | albumidlist=($(echo "$searchdata" | jq -r "select(.explicit_lyrics==false) |.album.id" | sort -u)) 1708 | albumidlistcount="$(echo "$searchdata" | jq -r "select(.explicit_lyrics==false) |.album.id" | sort -u | wc -l)" 1709 | if [ ! -z "$albumidlist" ]; then 1710 | log "$logheader :: $albumidlistcount Clean Albums Found" 1711 | for id in ${!albumidlist[@]}; do 1712 | albumid="${albumidlist[$id]}" 1713 | if ! find /scripts/temp -type f -iname "*-$albumid" | read; then 1714 | touch "/scripts/temp/clean-$albumid" 1715 | fi 1716 | done 1717 | fi 1718 | 1719 | albumlistalbumid=($(ls /scripts/temp | sort -r | grep -o '[[:digit:]]*')) 1720 | albumlistalbumidcount="$(ls /scripts/temp | sort -r | grep -o '[[:digit:]]*' | wc -l)" 1721 | if [ -d "/scripts/temp" ]; then 1722 | rm -rf /scripts/temp 1723 | fi 1724 | 1725 | if [ -z "$deezersearchalbumid" ]; then 1726 | log "$logheader :: Searching $albumlistalbumidcount Albums for Matches with Max Distance Score of 1 or less" 1727 | for id in "${!albumlistalbumid[@]}"; do 1728 | deezerid=${albumlistalbumid[$id]} 1729 | deezeralbumtitle="$(echo "$searchdata" | jq -r "select(.album.id==$deezerid) | .album.title" | head -n 1)" 1730 | diff=$(levenshtein "${albumtitle,,}" "${deezeralbumtitle,,}") 1731 | if [ "$diff" -le "1" ]; then 1732 | log "$logheader :: ${albumtitle,,} vs ${deezeralbumtitle,,} :: Distance = $diff :: $deezerid :: MATCH" 1733 | deezersearchalbumid="$deezerid" 1734 | else 1735 | log "$logheader :: ${albumtitle,,} vs ${deezeralbumtitle,,} :: Distance = $diff :: $deezerid :: ERROR :: NO MATCH FOUND" 1736 | deezersearchalbumid="" 1737 | continue 1738 | fi 1739 | 1740 | deezeralbumdata=$(curl -s "https://api.deezer.com/album/$deezerid") 1741 | deezeralbumtitle="$(echo "$deezeralbumdata" | jq -r ".title")" 1742 | deezeralbumartist="$(echo "$deezeralbumdata" | jq -r ".artist.name")" 1743 | deezeralbumtype="$(echo "$deezeralbumdata" | jq -r ".record_type")" 1744 | deezeralbumdate="$(echo "$deezeralbumdata" | jq -r ".release_date")" 1745 | deezeralbumyear="${deezeralbumdate:0:4}" 1746 | explicit="$(echo "$deezeralbumdata" | jq -r ".explicit_lyrics")" 1747 | if [[ "$deezeralbumtype" == "single" && "$lidarralbumtypelower" != "single" ]]; then 1748 | log "$logheader :: ERROR :: Album Type Did not Match" 1749 | deezersearchalbumid="" 1750 | continue 1751 | elif [[ "$deezeralbumtype" != "single" && "$lidarralbumtypelower" == "single" ]]; then 1752 | log "$logheader :: ERROR :: Album Type Did not Match" 1753 | deezersearchalbumid="" 1754 | continue 1755 | fi 1756 | 1757 | diff=$(levenshtein "${albumartistname,,}" "${deezeralbumartist,,}") 1758 | if [ "$diff" -le "2" ]; then 1759 | log "$logheader :: ${albumartistname,,} vs ${deezeralbumartist,,} :: Distance = $diff :: Artist Name Match" 1760 | deezersearchalbumid="$deezerid" 1761 | break 1762 | else 1763 | log "$logheader :: ${albumartistname,,} vs ${deezeralbumartist,,} :: Distance = $diff :: ERROR :: Artist Name did not match" 1764 | deezersearchalbumid="" 1765 | continue 1766 | fi 1767 | done 1768 | fi 1769 | fi 1770 | 1771 | if [ -z "$deezersearchalbumid" ]; then 1772 | log "$logheader :: Searching $albumlistalbumidcount Albums for Matches with Max Distance Score of $MatchDistance or less" 1773 | for id in "${!albumlistalbumid[@]}"; do 1774 | deezerid=${albumlistalbumid[$id]} 1775 | deezeralbumtitle="$(echo "$searchdata" | jq -r "select(.album.id==$deezerid) | .album.title" | head -n 1)" 1776 | diff=$(levenshtein "${albumtitle,,}" "${deezeralbumtitle,,}") 1777 | if [ "$diff" -le "$MatchDistance" ]; then 1778 | log "$logheader :: ${albumtitle,,} vs ${deezeralbumtitle,,} :: Distance = $diff :: $deezerid :: MATCH" 1779 | deezersearchalbumid="$deezerid" 1780 | else 1781 | log "$logheader :: ${albumtitle,,} vs ${deezeralbumtitle,,} :: Distance = $diff :: $deezerid :: ERROR :: NO MATCH FOUND" 1782 | deezersearchalbumid="" 1783 | continue 1784 | fi 1785 | 1786 | deezeralbumdata=$(curl -s "https://api.deezer.com/album/$deezerid") 1787 | deezeralbumtitle="$(echo "$deezeralbumdata" | jq -r ".title")" 1788 | deezeralbumartist="$(echo "$deezeralbumdata" | jq -r ".artist.name")" 1789 | deezeralbumtype="$(echo "$deezeralbumdata" | jq -r ".record_type")" 1790 | deezeralbumdate="$(echo "$deezeralbumdata" | jq -r ".release_date")" 1791 | deezeralbumyear="${deezeralbumdate:0:4}" 1792 | explicit="$(echo "$deezeralbumdata" | jq -r ".explicit_lyrics")" 1793 | if [[ "$deezeralbumtype" == "single" && "$lidarralbumtypelower" != "single" ]]; then 1794 | log "$logheader :: ERROR :: Album Type Did not Match" 1795 | deezersearchalbumid="" 1796 | continue 1797 | elif [[ "$deezeralbumtype" != "single" && "$lidarralbumtypelower" == "single" ]]; then 1798 | log "$logheader :: ERROR :: Album Type Did not Match" 1799 | deezersearchalbumid="" 1800 | continue 1801 | fi 1802 | 1803 | diff=$(levenshtein "${albumartistname,,}" "${deezeralbumartist,,}") 1804 | if [ "$diff" -le "2" ]; then 1805 | log "$logheader :: ${albumartistname,,} vs ${deezeralbumartist,,} :: Distance = $diff :: Artist Name Match" 1806 | deezersearchalbumid="$deezerid" 1807 | break 1808 | else 1809 | log "$logheader :: ${albumartistname,,} vs ${deezeralbumartist,,} :: Distance = $diff :: ERROR :: Artist Name did not match" 1810 | deezersearchalbumid="" 1811 | continue 1812 | fi 1813 | done 1814 | fi 1815 | 1816 | if [ ! -z "$deezersearchalbumid" ]; then 1817 | albumdeezerurl="https://deezer.com/album/$deezersearchalbumid" 1818 | albumreleaseyear="$deezeralbumyear" 1819 | lidarralbumtype="$deezeralbumtype" 1820 | albumclean="$(echo "$deezeralbumtitle" | sed -e "s%[^[:alpha:][:digit:]._()' -]% %g" -e "s/ */ /g")" 1821 | error=0 1822 | break 1823 | else 1824 | error=1 1825 | fi 1826 | done 1827 | else 1828 | error=0 1829 | fi 1830 | 1831 | fi 1832 | 1833 | if [ $error == 1 ]; then 1834 | log "$logheader :: ERROR :: No deezer album url found" 1835 | echo "$albumartistname :: $albumreleasegroupmbzid :: $albumtitle" >> "/config/logs/notfound.log" 1836 | if [ ! -d "/config/logs/searched" ]; then 1837 | mkdir -p "/config/logs/searched" 1838 | fi 1839 | if [ -d "/config/logs/searched" ]; then 1840 | touch /config/logs/searched/$albumreleasegroupmbzid 1841 | fi 1842 | continue 1843 | fi 1844 | 1845 | if [ "$explicit" == "true" ]; then 1846 | log "$logheader :: Explicit Release Found" 1847 | fi 1848 | 1849 | albumbimportfolder="$DOWNLOADS/amd/import/$artistclean - $albumclean ($albumreleaseyear)-WEB-$lidarralbumtype-deemix" 1850 | albumbimportfoldername="$(basename "$albumbimportfolder")" 1851 | 1852 | if [ -f "/config/logs/download.log" ]; then 1853 | if cat "/config/logs/download.log" | grep -i "$albumreleasegroupmbzid :: $albumtitle :: $albumbimportfolder" | read; then 1854 | log "$logheader :: Already Downloaded" 1855 | if [ ! -d "/config/logs/searched" ]; then 1856 | mkdir -p "/config/logs/searched" 1857 | fi 1858 | if [ -d "/config/logs/searched" ]; then 1859 | touch /config/logs/searched/$albumreleasegroupmbzid 1860 | fi 1861 | continue 1862 | fi 1863 | fi 1864 | 1865 | if [ -z "$deezeralbumtitle" ]; then 1866 | deezeralbumtitle="$albumtitle" 1867 | fi 1868 | 1869 | if [ ! -d "$albumbimportfolder" ]; then 1870 | log "$logheader :: DOWNLOADING :: $deezeralbumtitle :: $albumdeezerurl..." 1871 | albumdeezerid=$(echo "$albumdeezerurl" | grep -o '[[:digit:]]*') 1872 | python3 /scripts/dlclient.py -b $quality "$albumdeezerurl" 1873 | rm -rf /tmp/deemix-imgs/* 1874 | if find "$DOWNLOADS"/amd/dlclient -regex ".*/.*\.\(flac\|mp3\)" | read; then 1875 | DownloadQualityCheck 1876 | fi 1877 | 1878 | downloadcount=$(find "$DOWNLOADS"/amd/dlclient -regex ".*/.*\.\(flac\|mp3\)" | wc -l) 1879 | 1880 | if [ "$albumtrackcount" != "$downloadcount" ]; then 1881 | log "$logheader :: DOWNLOAD :: ERROR :: Downloaded track count ($downloadcount) does not match requested track count ($albumtrackcount), skipping..." 1882 | rm "$DOWNLOADS"/amd/dlclient/* 1883 | continue 1884 | else 1885 | log "$logheader :: DOWNLOAD :: $downloadcount Tracks found!" 1886 | fi 1887 | 1888 | if find "$DOWNLOADS"/amd/dlclient -regex ".*/.*\.\(flac\|mp3\)" | read; then 1889 | chmod $FilePermissions "$DOWNLOADS"/amd/dlclient/* 1890 | chown -R abc:abc "$DOWNLOADS"/amd/dlclient 1891 | log "$logheader :: DOWNLOAD :: success" 1892 | echo "$filelogheader :: $albumdeezerurl :: $albumreleasegroupmbzid :: $albumtitle :: $albumbimportfolder" >> "/config/logs/download.log" 1893 | if [ ! -d "/config/logs/downloads" ]; then 1894 | mkdir -p "/config/logs/downloads" 1895 | fi 1896 | if [ -d "/config/logs/downloads" ]; then 1897 | touch /config/logs/downloads/$albumdeezerid 1898 | fi 1899 | else 1900 | log "$logheader :: DOWNLOAD :: ERROR :: No files found" 1901 | echo "$albumartistname :: $albumreleasegroupmbzid :: $albumtitle" >> "/config/logs/notfound.log" 1902 | echo "$filelogheader :: $albumdeezerurl :: $albumreleasegroupmbzid :: $albumtitle :: $albumbimportfolder" >> "/config/logs/error.log" 1903 | continue 1904 | fi 1905 | 1906 | file=$(find "$DOWNLOADS"/amd/dlclient -regex ".*/.*\.\(flac\|mp3\)" | head -n 1) 1907 | if [ ! -z "$file" ]; then 1908 | artwork="$(dirname "$file")/folder.jpg" 1909 | if ffmpeg -y -i "$file" -c:v copy "$artwork" 2>/dev/null; then 1910 | log "$logheader :: Artwork Extracted" 1911 | else 1912 | log "$logheader :: ERROR :: No artwork found" 1913 | fi 1914 | fi 1915 | else 1916 | echo "$filelogheader :: $albumdeezerurl :: $albumreleasegroupmbzid :: $albumtitle :: $albumbimportfolder" >> "/config/logs/download.log" 1917 | if [ ! -d "/config/logs/downloads" ]; then 1918 | mkdir -p "/config/logs/downloads" 1919 | fi 1920 | if [ -d "/config/logs/downloads" ]; then 1921 | touch /config/logs/downloads/$albumdeezerid 1922 | fi 1923 | fi 1924 | 1925 | if [ $ENABLEPOSTPROCESSING == true ]; then 1926 | TagFix 1927 | Conversion 1928 | AddReplaygainTags 1929 | fi 1930 | 1931 | if [ ! -d "$DOWNLOADS/amd/import" ]; then 1932 | mkdir -p "$DOWNLOADS/amd/import" 1933 | chmod $FolderPermissions "$DOWNLOADS/amd/import" 1934 | chown -R abc:abc "$DOWNLOADS/amd/import" 1935 | fi 1936 | 1937 | if [ ! -d "$albumbimportfolder" ]; then 1938 | mkdir -p "$albumbimportfolder" 1939 | mv "$DOWNLOADS"/amd/dlclient/* "$albumbimportfolder"/ 1940 | chmod $FolderPermissions "$albumbimportfolder" 1941 | chmod $FilePermissions "$albumbimportfolder"/* 1942 | chown -R abc:abc "$albumbimportfolder" 1943 | fi 1944 | if [ "$remotepath" == "true" ]; then 1945 | albumbimportfolder="$LIDARRREMOTEPATH/amd/import/$artistclean - $albumclean ($albumreleaseyear)-WEB-$lidarralbumtype-deemix" 1946 | albumbimportfoldername="$(basename "$albumbimportfolder")" 1947 | fi 1948 | LidarrProcessIt=$(curl -s "$LidarrUrl/api/v1/command" --header "X-Api-Key:"${LidarrAPIkey} -H "Content-Type: application/json" --data "{\"name\":\"DownloadedAlbumsScan\", \"path\":\"${albumbimportfolder}\"}") 1949 | log "$logheader :: LIDARR IMPORT NOTIFICATION SENT! :: $albumbimportfoldername" 1950 | done 1951 | echo "####### DOWNLOAD AUDIO COMPLETE" 1952 | } 1953 | 1954 | CleanupFailedImports () { 1955 | if [ -d "$DOWNLOADS/amd/import" ]; then 1956 | if find "$DOWNLOADS"/amd/import -mindepth 1 -type d -mmin +480 | read; then 1957 | find "$DOWNLOADS"/amd/import -mindepth 1 -type d -mmin +480 -exec rm -rf "{}" \; &> /dev/null 1958 | fi 1959 | fi 1960 | } 1961 | 1962 | CreateDownloadFolders () { 1963 | if [ ! -d "$DOWNLOADS/amd/import" ]; then 1964 | mkdir -p "$DOWNLOADS/amd/import" 1965 | fi 1966 | 1967 | if [ ! -d "$DOWNLOADS/amd/dlclient" ]; then 1968 | mkdir -p "$DOWNLOADS/amd/dlclient" 1969 | else 1970 | rm "$DOWNLOADS"/amd/dlclient/* &> /dev/null 1971 | fi 1972 | } 1973 | 1974 | SetFolderPermissions () { 1975 | if [ -d "$DOWNLOADS/amd/import" ]; then 1976 | chmod $FolderPermissions "$DOWNLOADS/amd/import" 1977 | chown -R abc:abc "$DOWNLOADS/amd/import" 1978 | fi 1979 | 1980 | if [ -d "$DOWNLOADS/amd/dlclient" ]; then 1981 | chmod $FolderPermissions "$DOWNLOADS/amd/dlclient" 1982 | chown -R abc:abc "$DOWNLOADS/amd/dlclient" 1983 | fi 1984 | 1985 | if [ -d "$DOWNLOADS/amd" ]; then 1986 | chmod $FolderPermissions "$DOWNLOADS/amd" 1987 | chown -R abc:abc "$DOWNLOADS/amd" 1988 | fi 1989 | } 1990 | 1991 | TagFix () { 1992 | if find "$DOWNLOADS/amd/dlclient" -iname "*.flac" | read; then 1993 | if ! [ -x "$(command -v metaflac)" ]; then 1994 | echo "ERROR: FLAC verification utility not installed (ubuntu: apt-get install -y flac)" 1995 | else 1996 | for fname in "$DOWNLOADS"/amd/dlclient/*.flac; do 1997 | filename="$(basename "$fname")" 1998 | metaflac "$fname" --remove-tag=ALBUMARTIST 1999 | metaflac "$fname" --set-tag=ALBUMARTIST="$albumartistname" 2000 | metaflac "$fname" --set-tag=MUSICBRAINZ_ALBUMARTISTID="$albumartistmbzid" 2001 | if [ ! -z "$albumreleasegroupmbzid" ]; then 2002 | metaflac "$fname" --set-tag=MUSICBRAINZ_RELEASEGROUPID="$albumreleasegroupmbzid" 2003 | fi 2004 | if [ ! -z "$albummbid" ]; then 2005 | metaflac "$fname" --set-tag=MUSICBRAINZ_ALBUMID="$albummbid" 2006 | fi 2007 | log "$logheader :: FIXING TAGS :: $filename fixed..." 2008 | done 2009 | fi 2010 | fi 2011 | if find "$DOWNLOADS/amd/dlclient" -iname "*.mp3" | read; then 2012 | if ! [ -x "$(command -v eyeD3)" ]; then 2013 | echo "eyed3 verification utility not installed (ubuntu: apt-get install -y eyed3)" 2014 | else 2015 | for fname in "$DOWNLOADS"/amd/dlclient/*.mp3; do 2016 | filename="$(basename "$fname")" 2017 | eyeD3 "$fname" -b "$albumartistname" &> /dev/null 2018 | eyeD3 "$fname" --user-text-frame="MUSICBRAINZ_ALBUMARTISTID:$albumartistmbzid" &> /dev/null 2019 | if [ ! -z "$albumreleasegroupmbzid" ]; then 2020 | eyeD3 "$fname" --user-text-frame="MUSICBRAINZ_RELEASEGROUPID:$albumreleasegroupmbzid" &> /dev/null 2021 | fi 2022 | if [ ! -z "$albummbid" ]; then 2023 | eyeD3 "$fname" --user-text-frame="MUSICBRAINZ_ALBUMID:$albummbid" &> /dev/null 2024 | fi 2025 | log "$logheader :: FIXING TAGS :: $filename fixed..." 2026 | done 2027 | fi 2028 | fi 2029 | } 2030 | 2031 | function levenshtein { 2032 | if (( $# != 2 )); then 2033 | echo "Usage: $0 word1 word2" >&2 2034 | elif (( ${#1} < ${#2} )); then 2035 | levenshtein "$2" "$1" 2036 | else 2037 | local str1len=${#1} 2038 | local str2len=${#2} 2039 | local d 2040 | 2041 | for (( i = 0; i <= (str1len+1)*(str2len+1); i++ )); do 2042 | d[i]=0 2043 | done 2044 | 2045 | for (( i = 0; i <= str1len; i++ )); do 2046 | d[i+0*str1len]=$i 2047 | done 2048 | 2049 | for (( j = 0; j <= str2len; j++ )); do 2050 | d[0+j*(str1len+1)]=$j 2051 | done 2052 | 2053 | for (( j = 1; j <= str2len; j++ )); do 2054 | for (( i = 1; i <= str1len; i++ )); do 2055 | [ "${1:i-1:1}" = "${2:j-1:1}" ] && local cost=0 || local cost=1 2056 | del=$(( d[(i-1)+str1len*j]+1 )) 2057 | ins=$(( d[i+str1len*(j-1)]+1 )) 2058 | alt=$(( d[(i-1)+str1len*(j-1)]+cost )) 2059 | d[i+str1len*j]=$( echo -e "$del\n$ins\n$alt" | sort -n | head -1 ) 2060 | done 2061 | done 2062 | echo ${d[str1len+str1len*(str2len)]} 2063 | fi 2064 | } 2065 | 2066 | PlexNotification () { 2067 | if [ "$NOTIFYPLEX" == "true" ]; then 2068 | plexlibrarykey="$(echo "$plexlibraries" | jq -r ".MediaContainer.Directory[] | select(.Location.\"@path\"==\"$pathbasename\") | .\"@key\"" | head -n 1)" 2069 | plexfolder="$LidArtistPath/$albumfolder" 2070 | plexfolderencoded="$(jq -R -r @uri <<<"${plexfolder}")" 2071 | curl -s "$PLEXURL/library/sections/$plexlibrarykey/refresh?path=$plexfolderencoded&X-Plex-Token=$PLEXTOKEN" 2072 | log "$logheader :: Plex Scan notification sent! ($albumfolder)" 2073 | fi 2074 | } 2075 | 2076 | log () { 2077 | m_time=`date "+%F %T"` 2078 | echo $m_time" ":: $1 2079 | } 2080 | 2081 | error=1 2082 | until [ $error -eq 0 ] 2083 | do 2084 | Configuration 2085 | done 2086 | CreateDownloadFolders 2087 | SetFolderPermissions 2088 | CleanupFailedImports 2089 | if [ "$DOWNLOADMODE" == "artist" ]; then 2090 | ArtistMode 2091 | fi 2092 | if [ "$DOWNLOADMODE" == "wanted" ]; then 2093 | WantedMode 2094 | fi 2095 | log "####### SCRIPT COMPLETE" 2096 | if [ "$AUTOSTART" == "true" ]; then 2097 | log "####### SCRIPT SLEEPING FOR $SCRIPTINTERVAL" 2098 | fi 2099 | 2100 | exit 0 2101 | -------------------------------------------------------------------------------- /root/scripts/start.bash: -------------------------------------------------------------------------------- 1 | #! /usr/bin/with-contenv bash 2 | 3 | START_PID="$(pgrep -f 'bash /scripts/start.bash')" 4 | 5 | echo "Starting AMD...." 6 | echo "To kill the autorun script, use the following command:" 7 | echo "kill -9 ${START_PID}" 8 | 9 | while true; do 10 | let i++ 11 | bash /scripts/download.bash 2>&1 | tee "/config/logs/script_run_${i}_$(date +"%Y_%m_%d_%I_%M_%p").log" > /proc/1/fd/1 2>/proc/1/fd/2 12 | find "/config/logs" -type f -iname "*.log" -not -newermt "8 hours ago" -delete 13 | sleep "${SCRIPTINTERVAL:-15m}" 14 | done 15 | -------------------------------------------------------------------------------- /root/scripts/tag.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import sys 5 | import enum 6 | import argparse 7 | from mutagen.mp4 import MP4, MP4Cover 8 | 9 | parser = argparse.ArgumentParser(description='Optional app description') 10 | # Argument 11 | parser.add_argument('--file', help='A required integer positional argument') 12 | parser.add_argument('--songtitle', help='A required integer positional argument') 13 | parser.add_argument('--songalbum', help='A required integer positional argument') 14 | parser.add_argument('--songartist', help='A required integer positional argument') 15 | parser.add_argument('--songartistalbum', help='A required integer positional argument') 16 | parser.add_argument('--songbpm', help='A required integer positional argument') 17 | parser.add_argument('--songcopyright', help='A required integer positional argument') 18 | parser.add_argument('--songtracknumber', help='A required integer positional argument') 19 | parser.add_argument('--songtracktotal', help='A required integer positional argument') 20 | parser.add_argument('--songdiscnumber', help='A required integer positional argument') 21 | parser.add_argument('--songdisctotal', help='A required integer positional argument') 22 | parser.add_argument('--songcompilation', help='A required integer positional argument') 23 | parser.add_argument('--songlyricrating', help='A required integer positional argument') 24 | parser.add_argument('--songdate', help='A required integer positional argument') 25 | parser.add_argument('--songyear', help='A required integer positional argument') 26 | parser.add_argument('--songgenre', help='A required integer positional argument') 27 | parser.add_argument('--songcomposer', help='A required integer positional argument') 28 | parser.add_argument('--songisrc', type=str, help='A required integer positional argument') 29 | parser.add_argument('--songartwork', help='A required integer positional argument') 30 | parser.add_argument('--songauthor', type=str, help='A required integer positional argument') 31 | parser.add_argument('--songartists', type=str, help='A required integer positional argument') 32 | parser.add_argument('--songengineer', type=str, help='A required integer positional argument') 33 | parser.add_argument('--songproducer', type=str, help='A required integer positional argument') 34 | parser.add_argument('--songmixer', type=str, help='A required integer positional argument') 35 | parser.add_argument('--songpublisher', type=str, help='A required integer positional argument') 36 | parser.add_argument('--songcomment', type=str, help='A required integer positional argument') 37 | parser.add_argument('--songbarcode', type=str, help='A required integer positional argument') 38 | parser.add_argument('--mbrainzalbumartistid', type=str, help='A required integer positional argument') 39 | parser.add_argument('--mbrainzreleasegroupid', type=str, help='A required integer positional argument') 40 | parser.add_argument('--mbrainzalbumid', type=str, help='A required integer positional argument') 41 | args = parser.parse_args() 42 | 43 | filename = args.file 44 | bpm = int(args.songbpm) 45 | rtng = int(args.songlyricrating) 46 | trackn = int(args.songtracknumber) 47 | trackt = int(args.songtracktotal) 48 | discn = int(args.songdiscnumber) 49 | disct = int(args.songdisctotal) 50 | compilation = int(args.songcompilation) 51 | copyrightext = args.songcopyright 52 | title = args.songtitle 53 | album = args.songalbum 54 | artist = args.songartist 55 | artistalbum = args.songartistalbum 56 | date = args.songdate 57 | year = args.songyear 58 | genre = args.songgenre 59 | composer = args.songcomposer 60 | isrc = args.songisrc 61 | picture = args.songartwork 62 | lyricist = args.songauthor 63 | artists = args.songartists 64 | tracknumber = (trackn, trackt) 65 | discnumber = (discn, disct) 66 | engineer = args.songengineer 67 | producer = args.songproducer 68 | mixer = args.songmixer 69 | label = args.songpublisher 70 | barcode = args.songbarcode 71 | comment = args.songcomment 72 | albumartistid = args.mbrainzalbumartistid 73 | releasegroupid = args.mbrainzreleasegroupid 74 | albumid = args.mbrainzalbumid 75 | 76 | audio = MP4(filename) 77 | audio["\xa9nam"] = [title] 78 | audio["\xa9alb"] = [album] 79 | audio["\xa9ART"] = [artist] 80 | audio["aART"] = [artistalbum] 81 | audio["\xa9day"] = [date] 82 | audio["\xa9gen"] = [genre] 83 | audio["\xa9wrt"] = [composer] 84 | audio["rtng"] = [rtng] 85 | if bpm: 86 | audio["tmpo"] = [bpm] 87 | audio["trkn"] = [tracknumber] 88 | audio["disk"] = [discnumber] 89 | audio["cprt"] = [copyrightext] 90 | if lyricist: 91 | audio["----:com.apple.iTunes:LYRICIST"] = lyricist.encode() 92 | if artists: 93 | audio["----:com.apple.iTunes:ARTISTS"] = artists.encode() 94 | if engineer: 95 | audio["----:com.apple.iTunes:ENGINEER"] = engineer.encode() 96 | if producer: 97 | audio["----:com.apple.iTunes:PRODUCER"] = producer.encode() 98 | if mixer: 99 | audio["----:com.apple.iTunes:MIXER"] = mixer.encode() 100 | if label: 101 | audio["----:com.apple.iTunes:LABEL"] = label.encode() 102 | if barcode: 103 | audio["----:com.apple.iTunes:BARCODE"] = barcode.encode() 104 | if isrc: 105 | audio["----:com.apple.iTunes:ISRC"] = isrc.encode() 106 | if albumartistid: 107 | audio["----:com.apple.iTunes:MusicBrainz Album Artist Id"] = albumartistid.encode() 108 | if releasegroupid: 109 | audio["----:com.apple.iTunes:MusicBrainz Release Group Id"] = releasegroupid.encode() 110 | if albumid: 111 | audio["----:com.apple.iTunes:MusicBrainz Album Id"] = albumid.encode() 112 | 113 | 114 | if ( compilation == 1 ): 115 | audio["cpil"] = [compilation] 116 | audio["stik"] = [1] 117 | audio["\xa9cmt"] = [comment] 118 | with open(picture, "rb") as f: 119 | audio["covr"] = [ 120 | MP4Cover(f.read(), MP4Cover.FORMAT_JPEG) 121 | ] 122 | #audio["\xa9lyr"] = [syncedlyrics] 123 | audio.pprint() 124 | audio.save() 125 | --------------------------------------------------------------------------------