├── .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 | [](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 | 
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 |
--------------------------------------------------------------------------------