125 lines
3.7 KiB
Dart
125 lines
3.7 KiB
Dart
import 'codec_utils.dart';
|
|
|
|
/// Builds a track label from parts with the standard `' · '` joiner pattern.
|
|
///
|
|
/// Shared by both Plex track models and MPV track label utilities.
|
|
/// If [title] is non-empty it is added first, then [language], then [extraParts].
|
|
/// Falls back to `'$fallbackPrefix ${index + 1}'` when no parts are available.
|
|
String buildTrackLabel({
|
|
String? title,
|
|
String? language,
|
|
List<String> extraParts = const [],
|
|
required int index,
|
|
String fallbackPrefix = 'Track',
|
|
}) {
|
|
final parts = <String>[];
|
|
if (title != null && title.isNotEmpty) parts.add(title);
|
|
if (language != null && language.isNotEmpty) parts.add(language);
|
|
parts.addAll(extraParts);
|
|
return parts.isEmpty ? '$fallbackPrefix ${index + 1}' : parts.join(' · ');
|
|
}
|
|
|
|
String? cleanTrackMetadataValue(String? value) {
|
|
if (value == null) return null;
|
|
var cleaned = value.trim();
|
|
if (cleaned.isEmpty) return null;
|
|
|
|
final prefixed = RegExp(r'^(?:title|lang|language)\s*=\s*(.*)$', caseSensitive: false).firstMatch(cleaned);
|
|
if (prefixed != null) {
|
|
cleaned = prefixed.group(1)?.trim() ?? '';
|
|
}
|
|
|
|
if ((cleaned.startsWith('"') && cleaned.endsWith('"')) || (cleaned.startsWith("'") && cleaned.endsWith("'"))) {
|
|
cleaned = cleaned.substring(1, cleaned.length - 1).trim();
|
|
}
|
|
|
|
return cleaned.isEmpty ? null : cleaned;
|
|
}
|
|
|
|
String? cleanSubtitleTitle(String? title, {String? codec}) {
|
|
var cleaned = cleanTrackMetadataValue(title);
|
|
if (cleaned == null) return null;
|
|
|
|
final codecAliases = _subtitleCodecAliases(codec);
|
|
if (codecAliases.isEmpty) return cleaned;
|
|
|
|
final parts = cleaned.split(RegExp(r'\s+-\s+'));
|
|
while (parts.isNotEmpty && codecAliases.contains(_metadataToken(parts.last))) {
|
|
parts.removeLast();
|
|
}
|
|
cleaned = parts.join(' - ').trim();
|
|
|
|
return cleaned.isEmpty ? null : cleaned;
|
|
}
|
|
|
|
Set<String> _subtitleCodecAliases(String? codec) {
|
|
final aliases = <String>{
|
|
'SUBRIP',
|
|
'SRT',
|
|
'WEBVTT',
|
|
'VTT',
|
|
'ASS',
|
|
'SSA',
|
|
'PGS',
|
|
'PGSSUB',
|
|
'HDMV_PGS_SUBTITLE',
|
|
'DVD',
|
|
'DVDSUB',
|
|
'DVD_SUBTITLE',
|
|
'DVB_SUB',
|
|
'DVB_SUBTITLE',
|
|
};
|
|
if (codec != null && codec.isNotEmpty) {
|
|
aliases.add(_metadataToken(codec));
|
|
aliases.add(_metadataToken(CodecUtils.formatSubtitleCodec(codec)));
|
|
aliases.add(_metadataToken(CodecUtils.getSubtitleExtension(codec)));
|
|
}
|
|
return aliases;
|
|
}
|
|
|
|
String _metadataToken(String value) => value.trim().toUpperCase().replaceAll(RegExp(r'[^A-Z0-9]+'), '_');
|
|
|
|
class TrackLabelBuilder {
|
|
TrackLabelBuilder._();
|
|
|
|
static String buildAudioLabel({
|
|
String? title,
|
|
String? language,
|
|
String? codec,
|
|
int? channelsCount,
|
|
required int index,
|
|
}) {
|
|
final extraParts = <String>[];
|
|
if (codec != null && codec.isNotEmpty) {
|
|
extraParts.add(CodecUtils.formatAudioCodec(codec));
|
|
}
|
|
if (channelsCount != null) {
|
|
extraParts.add('${channelsCount}ch');
|
|
}
|
|
return buildTrackLabel(
|
|
title: title,
|
|
language: language?.toUpperCase(),
|
|
extraParts: extraParts,
|
|
index: index,
|
|
fallbackPrefix: 'Audio Track',
|
|
);
|
|
}
|
|
|
|
static String buildSubtitleLabel({
|
|
String? title,
|
|
String? language,
|
|
String? codec,
|
|
bool forced = false,
|
|
required int index,
|
|
}) {
|
|
final cleanedTitle = cleanSubtitleTitle(title, codec: codec);
|
|
final cleanedLanguage = cleanTrackMetadataValue(language)?.toUpperCase();
|
|
final extraParts = <String>[];
|
|
if (forced && !_metadataToken(cleanedTitle ?? '').split('_').contains('FORCED')) extraParts.add('Forced');
|
|
if (codec != null && codec.isNotEmpty) {
|
|
extraParts.add(CodecUtils.formatSubtitleCodec(codec));
|
|
}
|
|
return buildTrackLabel(title: cleanedTitle, language: cleanedLanguage, extraParts: extraParts, index: index);
|
|
}
|
|
}
|