mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-22 01:43:29 +01:00
Ability to cache subsonic artist images locally on disk (#61)
This commit is contained in:
35
src/app.ts
35
src/app.ts
@@ -2,7 +2,13 @@ import path from "path";
|
||||
import fs from "fs";
|
||||
import server from "./server";
|
||||
import logger from "./logger";
|
||||
import { appendMimeTypeToClientFor, DEFAULT, Subsonic } from "./subsonic";
|
||||
import {
|
||||
appendMimeTypeToClientFor,
|
||||
axiosImageFetcher,
|
||||
cachingImageFetcher,
|
||||
DEFAULT,
|
||||
Subsonic,
|
||||
} from "./subsonic";
|
||||
import encryption from "./encryption";
|
||||
import { InMemoryAccessTokens, sha256 } from "./access_tokens";
|
||||
import { InMemoryLinkCodes } from "./link_codes";
|
||||
@@ -28,10 +34,15 @@ const streamUserAgent = config.subsonic.customClientsFor
|
||||
? appendMimeTypeToClientFor(config.subsonic.customClientsFor.split(","))
|
||||
: DEFAULT;
|
||||
|
||||
const artistImageFetcher = config.subsonic.artistImageCache
|
||||
? cachingImageFetcher(config.subsonic.artistImageCache, axiosImageFetcher)
|
||||
: axiosImageFetcher;
|
||||
|
||||
const subsonic = new Subsonic(
|
||||
config.subsonic.url,
|
||||
encryption(config.secret),
|
||||
streamUserAgent
|
||||
streamUserAgent,
|
||||
artistImageFetcher
|
||||
);
|
||||
|
||||
const featureFlagAwareMusicService: MusicService = {
|
||||
@@ -60,7 +71,9 @@ const featureFlagAwareMusicService: MusicService = {
|
||||
|
||||
export const GIT_INFO = path.join(__dirname, "..", ".gitinfo");
|
||||
|
||||
const version = fs.existsSync(GIT_INFO) ? fs.readFileSync(GIT_INFO).toString().trim() : "v??"
|
||||
const version = fs.existsSync(GIT_INFO)
|
||||
? fs.readFileSync(GIT_INFO).toString().trim()
|
||||
: "v??";
|
||||
|
||||
const app = server(
|
||||
sonosSystem,
|
||||
@@ -69,12 +82,12 @@ const app = server(
|
||||
featureFlagAwareMusicService,
|
||||
{
|
||||
linkCodes: () => new InMemoryLinkCodes(),
|
||||
accessTokens: () => new InMemoryAccessTokens(sha256(config.secret)),
|
||||
accessTokens: () => new InMemoryAccessTokens(sha256(config.secret)),
|
||||
clock: SystemClock,
|
||||
iconColors: config.icons,
|
||||
applyContextPath: true,
|
||||
logRequests: true,
|
||||
version
|
||||
version,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -90,12 +103,12 @@ if (config.sonos.autoRegister) {
|
||||
);
|
||||
}
|
||||
});
|
||||
} else if(config.sonos.discovery.enabled) {
|
||||
sonosSystem.devices().then(devices => {
|
||||
devices.forEach(d => {
|
||||
logger.info(`Found device ${d.name}(${d.group}) @ ${d.ip}:${d.port}`)
|
||||
})
|
||||
})
|
||||
} else if (config.sonos.discovery.enabled) {
|
||||
sonosSystem.devices().then((devices) => {
|
||||
devices.forEach((d) => {
|
||||
logger.info(`Found device ${d.name}(${d.group}) @ ${d.ip}:${d.port}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -84,6 +84,7 @@ export default function () {
|
||||
subsonic: {
|
||||
url: bnbEnvVar("SUBSONIC_URL", { legacy: ["BONOB_NAVIDROME_URL"], default: `http://${hostname()}:4533` })!,
|
||||
customClientsFor: bnbEnvVar("SUBSONIC_CUSTOM_CLIENTS", { legacy: ["BONOB_NAVIDROME_CUSTOM_CLIENTS"] }),
|
||||
artistImageCache: bnbEnvVar("SUBSONIC_ARTIST_IMAGE_CACHE"),
|
||||
},
|
||||
scrobbleTracks: bnbEnvVar("SCROBBLE_TRACKS", { default: "true" }) == "true",
|
||||
reportNowPlaying:
|
||||
|
||||
@@ -18,10 +18,13 @@ import {
|
||||
AlbumSummary,
|
||||
Genre,
|
||||
Track,
|
||||
CoverArt,
|
||||
} from "./music_service";
|
||||
import X2JS from "x2js";
|
||||
import sharp from "sharp";
|
||||
import _ from "underscore";
|
||||
import fse from "fs-extra";
|
||||
import path from "path";
|
||||
|
||||
import axios, { AxiosRequestConfig } from "axios";
|
||||
import { Encryption } from "./encryption";
|
||||
@@ -311,19 +314,61 @@ export const asURLSearchParams = (q: any) => {
|
||||
return urlSearchParams;
|
||||
};
|
||||
|
||||
export type ImageFetcher = (url: string) => Promise<CoverArt | undefined>;
|
||||
|
||||
export const cachingImageFetcher =
|
||||
(cacheDir: string, delegate: ImageFetcher) =>
|
||||
(url: string): Promise<CoverArt | undefined> => {
|
||||
const filename = path.join(cacheDir, `${Md5.hashStr(url)}.png`);
|
||||
return fse
|
||||
.readFile(filename)
|
||||
.then((data) => ({ contentType: "image/png", data }))
|
||||
.catch(() =>
|
||||
delegate(url).then((image) => {
|
||||
if (image) {
|
||||
return sharp(image.data)
|
||||
.png()
|
||||
.toBuffer()
|
||||
.then((png) => {
|
||||
return fse
|
||||
.writeFile(filename, png)
|
||||
.then(() => ({ contentType: "image/png", data: png }));
|
||||
});
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const axiosImageFetcher = (url: string): Promise<CoverArt | undefined> =>
|
||||
axios
|
||||
.get(url, {
|
||||
headers: BROWSER_HEADERS,
|
||||
responseType: "arraybuffer",
|
||||
})
|
||||
.then((res) => ({
|
||||
contentType: res.headers["content-type"],
|
||||
data: Buffer.from(res.data, "binary"),
|
||||
}))
|
||||
.catch(() => undefined);
|
||||
|
||||
export class Subsonic implements MusicService {
|
||||
url: string;
|
||||
encryption: Encryption;
|
||||
streamClientApplication: StreamClientApplication;
|
||||
externalImageFetcher: ImageFetcher;
|
||||
|
||||
constructor(
|
||||
url: string,
|
||||
encryption: Encryption,
|
||||
streamClientApplication: StreamClientApplication = DEFAULT
|
||||
streamClientApplication: StreamClientApplication = DEFAULT,
|
||||
externalImageFetcher: ImageFetcher = axiosImageFetcher
|
||||
) {
|
||||
this.url = url;
|
||||
this.encryption = encryption;
|
||||
this.streamClientApplication = streamClientApplication;
|
||||
this.externalImageFetcher = externalImageFetcher;
|
||||
}
|
||||
|
||||
get = async (
|
||||
@@ -630,28 +675,21 @@ export class Subsonic implements MusicService {
|
||||
(it) => it.coverArt
|
||||
);
|
||||
if (artist.image.large) {
|
||||
return axios
|
||||
.get(artist.image.large!, {
|
||||
headers: BROWSER_HEADERS,
|
||||
responseType: "arraybuffer",
|
||||
})
|
||||
.then((res) => {
|
||||
const image = Buffer.from(res.data, "binary");
|
||||
if (size) {
|
||||
return sharp(image)
|
||||
return this.externalImageFetcher(artist.image.large!).then(
|
||||
(image) => {
|
||||
if (image && size) {
|
||||
return sharp(image.data)
|
||||
.resize(size)
|
||||
.toBuffer()
|
||||
.then((resized) => ({
|
||||
contentType: res.headers["content-type"],
|
||||
contentType: image.contentType,
|
||||
data: resized,
|
||||
}));
|
||||
} else {
|
||||
return {
|
||||
contentType: res.headers["content-type"],
|
||||
data: image,
|
||||
};
|
||||
return image;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
} else if (albumsWithCoverArt.length > 0) {
|
||||
return subsonic
|
||||
.getCoverArt(
|
||||
|
||||
Reference in New Issue
Block a user