Author SHA1 Message Date
GitBluub 46d427363f one more radioart for later 2024-01-12 12:34:40 +01:00
GitBluub 0b8b58e915 feat: overlay artist image and background for cover 2023-11-16 04:52:38 +01:00
13 changed files with 842 additions and 10193 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

+806 -10144
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -21,6 +21,7 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@jimp/plugin-circle": "^0.22.10",
"@nestjs-modules/mailer": "^1.9.1",
"@nestjs/common": "^10.1.0",
"@nestjs/config": "^3.0.0",
@@ -36,9 +37,9 @@
"@types/passport": "^1.0.12",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"json-logger-service": "^9.0.1",
"class-validator": "^0.14.0",
"jimp": "^0.22.10",
"json-logger-service": "^9.0.1",
"node-fetch": "^2.6.12",
"nodemailer": "^6.9.5",
"passport-google-oauth20": "^2.0.0",
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+27 -5
View File
@@ -37,6 +37,7 @@ import { Song as _Song } from 'src/_gen/prisma-class/song';
import { SongHistory } from 'src/_gen/prisma-class/song_history';
import { IncludeMap, mapInclude } from 'src/utils/include';
import { Public } from 'src/auth/public';
import Jimp from 'jimp';
class SongHistoryResult {
@ApiProperty()
@@ -45,6 +46,9 @@ class SongHistoryResult {
history: SongHistory[];
}
const BACKGROUND_COVER = 'radioart3.jpeg';
const ICON = 'icon_dark.png';
@Controller('song')
@ApiTags('song')
@UseGuards(JwtAuthGuard)
@@ -85,6 +89,21 @@ export class SongController {
}
}
async gen_illustration(song: Song) {
const img = await Jimp.read(BACKGROUND_COVER);
// @ts-ignore
const artist_img = await Jimp.read(`/assets/artists/${song.artist.name}/illustration.png`);
const logo = await Jimp.read(ICON);
img.cover(600, 600);
artist_img.cover(400, 400);
logo.cover(70, 70);
artist_img.circle();
img.composite(artist_img, 100, 100);
img.composite(logo, 10, 10);
return img;
}
@Get(':id/illustration')
@ApiOperation({
description: 'Streams the illustration of the requested song',
@@ -93,13 +112,16 @@ export class SongController {
@ApiOkResponse({ description: 'Returns the illustration succesfully' })
@Public()
async getIllustration(@Param('id', ParseIntPipe) id: number) {
const song = await this.songService.song({ id });
const song = await this.songService.song({ id }, { artist: true } );
if (!song) throw new NotFoundException('Song not found');
//await this.gen_illustration(song);
if (song.illustrationPath === null) throw new NotFoundException();
if (!existsSync(song.illustrationPath))
throw new NotFoundException('Illustration not found');
if (!existsSync(song.illustrationPath)) {
let img = await this.gen_illustration(song);
img.write(song.illustrationPath);
}
try {
const file = createReadStream(song.illustrationPath);
return new StreamableFile(file);
+6 -36
View File
@@ -1,56 +1,26 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';
import { Slider, Switch, Text, View } from 'native-base';
import { Audio } from 'expo-av';
const MetronomeToggle = (props: { enabled: boolean, onToggle: (stateAfterToggle: boolean) => void }) => {
const [isEnabled, setEnabled] = useState(false);
useEffect(() => {
setEnabled(props.enabled)
}, [props.enabled])
return <Switch value={isEnabled} onToggle={() => {
console.log(isEnabled);
props.onToggle(!isEnabled);
setEnabled(!isEnabled);
}} />
}
export const Metronome = ({ paused = false, bpm }: { paused?: boolean; bpm: number }) => {
const audio = useRef<Audio.Sound | null>(null);
const ref = useRef<HTMLAudioElement | null>(null);
const enabled = useRef<boolean>(false);
const volume = useRef<number>(50);
useEffect(() => {
if (!enabled) {
return;
} else if (!audio.current) {
Audio.Sound.createAsync(require('../assets/metronome.mp3')).then((a) => {
audio.current = a.sound;
});
}
return () => {
audio.current?.unloadAsync();
};
}, [enabled]);
useEffect(() => {
if (paused) return;
const int = setInterval(() => {
if (!enabled.current) return;
if (!audio.current) return;
audio.current?.playAsync();
if (!ref.current) ref.current = new Audio('/assets/metronome.mp3');
ref.current.volume = volume.current / 100;
ref.current.play();
}, 60000 / bpm);
return () => clearInterval(int);
}, [bpm, paused]);
useEffect(() => {
audio.current?.setVolumeAsync(volume.current / 100);
}, [volume.current]);
return (
<View>
<Text>Metronome Settings</Text>
<Text>Enabled:</Text>
<MetronomeToggle enabled={enabled.current} onToggle={(e) => (enabled.current = e)} />
<Switch value={enabled.current} onToggle={() => (enabled.current = !enabled.current)} />
<Text>Volume:</Text>
<Slider
maxWidth={'500px'}
-1
View File
@@ -25,7 +25,6 @@
"@react-navigation/native-stack": "^6.9.14",
"@reduxjs/toolkit": "^1.9.6",
"expo": "~49.0.13",
"expo-av": "~13.4.1",
"expo-image-picker": "~14.3.2",
"expo-linear-gradient": "~12.3.0",
"expo-linking": "~5.0.2",
-5
View File
@@ -6056,11 +6056,6 @@ expo-asset@~8.10.1:
path-browserify "^1.0.0"
url-parse "^1.5.9"
expo-av@~13.4.1:
version "13.4.1"
resolved "https://registry.yarnpkg.com/expo-av/-/expo-av-13.4.1.tgz#83029b3202e64bf060f0a7a24591d93bf7ab728c"
integrity sha512-0K8QEFzZeNGrppzYUs8wOtjOyPPkxzlVYbEHM6WzIQOG1uYYNOJkJB3aQHVFXHmz+AzoHwMrUjcschofwaMNeg==
expo-constants@~14.4.2:
version "14.4.2"
resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-14.4.2.tgz#cac5e8b524069545739b8d8595ce96cc5be6578c"