Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: shazam #113

Merged
merged 6 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,11 @@ coverage/
.flaskenv*
!.env.project
!.env.vault
.history
.history

/shazam_api/lib/
/shazam_api/include/
/shazam_api/__pycache__
/shazam_api/pyvenv.cfg
/shazam_api/pyvenv.cfg
.DS_Store
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ ARG dart_entryfile

WORKDIR /app
COPY pubspec.* /app/
COPY shazam_client /app/
RUN dart pub get

COPY . /app
COPY . /app
RUN dart pub get

Expand Down
1 change: 0 additions & 1 deletion bin/radio_horizon_development.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ Future<void> main() async {
// Initialise our services
MusicService.init(client);
await DatabaseService.init(client);
SongRecognitionService.init(client, DatabaseService.instance);

client.onReady.listen((_) async {
BootUpService.init(client, DatabaseService.instance);
Expand Down
1 change: 0 additions & 1 deletion bin/radio_horizon_production.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ Future<void> main() async {
// Initialise our services
MusicService.init(client);
await DatabaseService.init(client);
SongRecognitionService.init(client, DatabaseService.instance);
BootUpService.init(client, DatabaseService.instance);

// Connect
Expand Down
12 changes: 12 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ services:
- .env/.env.production
links:
- lavalink
- shazam_api
depends_on:
- lavalink
- shazam_api

lavalink:
image: ghcr.io/lavalink-devs/lavalink:3
Expand All @@ -20,3 +22,13 @@ services:
- 2333
volumes:
- ./lavalink.yml:/opt/Lavalink/application.yml

shazam_api:
build:
context: ./shazam_api
expose:
- 5000
volumes:
- ./shazam_api:/app
environment:
FLASK_ENV: production
12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ services:
- .env/.env.development
links:
- lavalink
- shazam_api
depends_on:
- lavalink
- shazam_api

lavalink:
image: ghcr.io/lavalink-devs/lavalink:3
Expand All @@ -23,3 +25,13 @@ services:
- 2333
volumes:
- ./lavalink.yml:/opt/Lavalink/application.yml

shazam_api:
build:
context: ./shazam_api
expose:
- 5000
volumes:
- ./shazam_api:/app
environment:
FLASK_ENV: development
2 changes: 1 addition & 1 deletion lib/src/commands/music.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ ChatCommand:music-play: {
);
}

await SongRecognitionService.instance
await DatabaseService.instance
.deleteRadioFromList(context.guild!.id);
}),
localizedDescriptions: localizedValues(
Expand Down
81 changes: 19 additions & 62 deletions lib/src/commands/radio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import 'package:logging/logging.dart';
import 'package:nyxx/nyxx.dart';
import 'package:nyxx_commands/nyxx_commands.dart';
import 'package:nyxx_interactions/nyxx_interactions.dart';
import 'package:nyxx_pagination/nyxx_pagination.dart';
import 'package:radio_browser_api/radio_browser_api.dart';
import 'package:radio_horizon/radio_horizon.dart';
import 'package:radio_horizon/src/checks.dart';
import 'package:radio_horizon/src/models/song_recognition/current_station_info.dart';
import 'package:retry/retry.dart';
import 'package:shazam_client/shazam_client.dart';

final _enRadioCommand = AppLocale.en.translations.commands.radio;
final _enPlayCommand = _enRadioCommand.children.play;
Expand Down Expand Up @@ -124,7 +124,8 @@ ChatCommand:radio-play: {
channelId: context.channel.id,
).startPlaying();

await SongRecognitionService.instance.setCurrentRadio(
final databaseService = DatabaseService.instance;
await databaseService.setCurrentRadio(
context.guild!.id,
context.member!.voiceState!.channel!.id,
context.channel.id,
Expand Down Expand Up @@ -158,39 +159,18 @@ ChatCommand:radio-play: {
final translations = getCommandTranslations(context);
final commandTranslations = translations.radio.children.recognize;
CurrentStationInfo? stationInfo;
MusicLinksResponse? linksResponse;

try {
final recognitionService = SongRecognitionService.instance;
final databaseService = DatabaseService.instance;
final guildId = context.guild!.id;

final stopwatch = Stopwatch()..start();
var recognitionSampleDuration = 10;

final guildRadio = await recognitionService.currentRadio(guildId);
final guildRadio = await databaseService.currentRadio(guildId);

try {
final info = await retry(
() async => recognitionService.getCurrentStationInfo(guildRadio),
);
if (!info.hasTitle) {
throw Exception('No title');
}
final node = MusicService.instance.cluster
.getOrCreatePlayerNode(context.guild!.id);
final tracks = await node.autoSearch(info.title!);
stationInfo = info.copyWith(
image:
'https://img.youtube.com/vi/${tracks.tracks.first.info?.identifier}/hqdefault.jpg',
);
} catch (exception, stacktrace) {
_logger.severe(
'Failed to get current station info',
exception,
stacktrace,
);

ShazamResult? result;
SongModel? result;
await retry(
() async {
result = await recognitionService.identify(
Expand Down Expand Up @@ -219,21 +199,18 @@ ChatCommand:radio-play: {

stationInfo =
CurrentStationInfo.fromShazamResult(result!, guildRadio);
}

try {
linksResponse = await SongRecognitionService.instance
.getMusicLinks(stationInfo.title!);
} catch (exception, stacktrace) {
_logger.severe(
'Failed to get music links for ${stationInfo.title}',
exception,
stacktrace,
} catch (e) {
await context.respond(
MessageBuilder.embed(
EmbedBuilder()
..color = DiscordColor.red
..title = commandTranslations.errors.noResults,
),
);
return null;
}

final color = getRandomColor();
stopwatch.stop();

final embed = EmbedBuilder()
..color = color
Expand All @@ -245,7 +222,8 @@ ChatCommand:radio-play: {
name: commandTranslations.radioStationField,
content: stationInfo.name,
)
..thumbnailUrl = stationInfo.image;
..thumbnailUrl = stationInfo.image
..url = stationInfo.url;

final genre = stationInfo.genre;
if (genre != null) {
Expand All @@ -255,28 +233,7 @@ ChatCommand:radio-play: {
);
}

embed.addField(
name: commandTranslations.computationalTimeField,
content: '${stopwatch.elapsedMilliseconds}ms',
);

final lyricsPages = stationInfo.lyricsPages(color: color);
if (lyricsPages == null || lyricsPages.isEmpty) {
final builder = ComponentMessageBuilder()..embeds = [embed];
linksResponse?.componentRows.forEach(builder.addComponentRow);
return await context.respond(builder);
}

final paginator = EmbedComponentPagination(
context.commands.interactions!,
[embed, ...lyricsPages],
user: context.user,
);

final messageBuilder = paginator.initMessageBuilder();
linksResponse?.componentRows.forEach(messageBuilder.addComponentRow);

await context.respond(messageBuilder);
await context.respond(MessageBuilder.embed(embed));
} catch (e, stacktrace) {
_logger.severe(
'Failed to recognize radio',
Expand Down Expand Up @@ -316,8 +273,8 @@ ChatCommand:radio-play: {

late GuildRadio? guildRadio;
try {
guildRadio = await SongRecognitionService.instance
.currentRadio(context.guild!.id);
guildRadio =
await DatabaseService.instance.currentRadio(context.guild!.id);
} on RadioNotPlayingException {
await context.respond(
MessageBuilder.embed(
Expand Down
78 changes: 15 additions & 63 deletions lib/src/models/song_recognition/current_station_info.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:nyxx/nyxx.dart';
import 'package:radio_horizon/radio_horizon.dart';
import 'package:shazam_client/shazam_client.dart';

part 'current_station_info.g.dart';

Expand All @@ -14,34 +14,26 @@ class CurrentStationInfo {
this.title,
this.image,
this.url,
}) : _lyrics = null;

const CurrentStationInfo._lyrics({
this.description,
this.genre,
this.name,
this.title,
this.image,
this.url,
List<String>? lyrics,
}) : _lyrics = lyrics,
contentType = null;
});

factory CurrentStationInfo.fromJson(Map<String, dynamic> json) =>
_$CurrentStationInfoFromJson(json);

factory CurrentStationInfo.fromShazamResult(
ShazamResult result,
GuildRadio radio,
) =>
CurrentStationInfo._lyrics(
name: result.headline,
title: radio.station.name,
SongModel result, [
GuildRadio? guildRadio,
]) =>
CurrentStationInfo(
title: '${result.title} - ${result.subtitle}',
description: result.subtitle,
url: radio.station.urlResolved ?? radio.station.url,
image: result.share?.image ?? radio.station.favicon,
url: Uri.https(
'youtube.com',
'/results',
{'search_query': result.title},
).toString(),
image: result.images?.coverart,
genre: result.genres?.primary,
lyrics: result.lyrics,
name: guildRadio?.station.name,
);

CurrentStationInfo copyWith({
Expand All @@ -53,8 +45,7 @@ class CurrentStationInfo {
String? url,
List<String>? lyrics,
}) =>
CurrentStationInfo._lyrics(
lyrics: lyrics ?? _lyrics,
CurrentStationInfo(
description: description ?? this.description,
genre: genre ?? this.genre,
name: name ?? this.name,
Expand Down Expand Up @@ -83,46 +74,7 @@ class CurrentStationInfo {
final String? url;

final String? image;
final List<String>? _lyrics;

bool get hasName => name != null && name!.isNotEmpty;
bool get hasTitle => title != null && title!.isNotEmpty;

List<List<String>>? paragraphedLyrics(int paragraphsPerPage) {
if (_lyrics == null || _lyrics!.isEmpty) return null;
final lyrics = _lyrics ?? [];
final paragraphs = lyrics.join('\n').split('\n\n');

final m = (paragraphs.length / paragraphsPerPage).round();
final lists = List.generate(
3,
(i) => paragraphs.sublist(
m * i,
(i + 1) * m <= paragraphs.length ? (i + 1) * m : null,
),
);

return lists;
}

List<EmbedBuilder>? lyricsPages({
required DiscordColor color,
}) {
final lyricsPages = <EmbedBuilder>[];
final llyrics = paragraphedLyrics(3);

if (llyrics == null) return null;

// add 3 paragraphs per page
for (var i = 0; i < llyrics.length; i++) {
final embed = EmbedBuilder()
..color = color
..title = title
..description = llyrics[i].join('\n\n');

lyricsPages.add(embed);
}

return lyricsPages;
}
}
Loading
Loading