mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-25 09:14:17 +02:00
fix: resolve missing track/disc numbers from search downloads and suppress FFmpeg log noise
- Tidal: use actual API track_number/disc_number when request values are 0 (fixes search/popular downloads having no track position in metadata) - Extension enrichment: copy TrackNumber/DiscNumber back from enriched results - Extension fallback download: add request metadata fallback for non-source extensions (Album, AlbumArtist, ReleaseDate, ISRC, TrackNumber, DiscNumber) - FFmpeg: add -v error -hide_banner to all commands (embed, convert, CUE split) to eliminate banner, build config, and full metadata/lyrics dump in logcat - ebur128: add framelog=quiet to suppress per-frame loudness measurements while keeping the summary needed for ReplayGain parsing - Track metadata screen: separate embedded lyrics check from online fetch, show file-only state with manual online fetch button
This commit is contained in:
@@ -1037,6 +1037,14 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
GoLog("[DownloadWithExtensionFallback] ReleaseDate from enrichment: %s\n", enrichedTrack.ReleaseDate)
|
||||
req.ReleaseDate = enrichedTrack.ReleaseDate
|
||||
}
|
||||
if enrichedTrack.TrackNumber > 0 && req.TrackNumber == 0 {
|
||||
GoLog("[DownloadWithExtensionFallback] TrackNumber from enrichment: %d\n", enrichedTrack.TrackNumber)
|
||||
req.TrackNumber = enrichedTrack.TrackNumber
|
||||
}
|
||||
if enrichedTrack.DiscNumber > 0 && req.DiscNumber == 0 {
|
||||
GoLog("[DownloadWithExtensionFallback] DiscNumber from enrichment: %d\n", enrichedTrack.DiscNumber)
|
||||
req.DiscNumber = enrichedTrack.DiscNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1427,6 +1435,28 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
}
|
||||
}
|
||||
|
||||
if req.AlbumName != "" && resp.Album == "" {
|
||||
resp.Album = req.AlbumName
|
||||
}
|
||||
if req.AlbumArtist != "" && resp.AlbumArtist == "" {
|
||||
resp.AlbumArtist = req.AlbumArtist
|
||||
}
|
||||
if req.ReleaseDate != "" && resp.ReleaseDate == "" {
|
||||
resp.ReleaseDate = req.ReleaseDate
|
||||
}
|
||||
if req.ISRC != "" && resp.ISRC == "" {
|
||||
resp.ISRC = req.ISRC
|
||||
}
|
||||
if req.TrackNumber > 0 && resp.TrackNumber == 0 {
|
||||
resp.TrackNumber = req.TrackNumber
|
||||
}
|
||||
if req.DiscNumber > 0 && resp.DiscNumber == 0 {
|
||||
resp.DiscNumber = req.DiscNumber
|
||||
}
|
||||
if req.CoverURL != "" && resp.CoverURL == "" {
|
||||
resp.CoverURL = req.CoverURL
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
||||
+15
-2
@@ -2162,13 +2162,26 @@ func resolveTidalTrackForRequest(req DownloadRequest, downloader *TidalDownloade
|
||||
GoLog("[%s] Track %d verified: '%s - %s' ✓\n", logPrefix, trackID, resolved.ArtistName, resolved.Title)
|
||||
}
|
||||
|
||||
// Use track_number / disc_number from the actual Tidal API data when the
|
||||
// request doesn't carry them (e.g. downloads from search results / popular).
|
||||
resolvedTrackNumber := req.TrackNumber
|
||||
resolvedDiscNumber := req.DiscNumber
|
||||
if actualTrack != nil {
|
||||
if resolvedTrackNumber == 0 && actualTrack.TrackNumber > 0 {
|
||||
resolvedTrackNumber = actualTrack.TrackNumber
|
||||
}
|
||||
if resolvedDiscNumber == 0 && actualTrack.VolumeNumber > 0 {
|
||||
resolvedDiscNumber = actualTrack.VolumeNumber
|
||||
}
|
||||
}
|
||||
|
||||
track := &TidalTrack{
|
||||
ID: trackID,
|
||||
Title: strings.TrimSpace(req.TrackName),
|
||||
ISRC: strings.TrimSpace(req.ISRC),
|
||||
Duration: expectedDurationSec,
|
||||
TrackNumber: req.TrackNumber,
|
||||
VolumeNumber: req.DiscNumber,
|
||||
TrackNumber: resolvedTrackNumber,
|
||||
VolumeNumber: resolvedDiscNumber,
|
||||
}
|
||||
track.Artist.Name = strings.TrimSpace(req.ArtistName)
|
||||
track.Album.Title = strings.TrimSpace(req.AlbumName)
|
||||
|
||||
@@ -2248,6 +2248,18 @@ abstract class AppLocalizations {
|
||||
/// **'Lyrics not available for this track'**
|
||||
String get trackLyricsNotAvailable;
|
||||
|
||||
/// Message when no embedded lyrics in audio file
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No lyrics found in this file'**
|
||||
String get trackLyricsNotInFile;
|
||||
|
||||
/// Action - fetch lyrics from online providers
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Fetch from Online'**
|
||||
String get trackFetchOnlineLyrics;
|
||||
|
||||
/// Message when lyrics request times out
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -1219,6 +1219,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get trackLyricsNotAvailable =>
|
||||
'Lyrics sind für diesen Titel nicht verfügbar';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout =>
|
||||
'Anfrage Timeout. Versuche es später erneut.';
|
||||
|
||||
@@ -1200,6 +1200,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get trackLyricsNotAvailable => 'Lyrics not available for this track';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout => 'Request timed out. Try again later.';
|
||||
|
||||
|
||||
@@ -1200,6 +1200,12 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get trackLyricsNotAvailable => 'Lyrics not available for this track';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout => 'Request timed out. Try again later.';
|
||||
|
||||
|
||||
@@ -1202,6 +1202,12 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get trackLyricsNotAvailable => 'Lyrics not available for this track';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout => 'Request timed out. Try again later.';
|
||||
|
||||
|
||||
@@ -1200,6 +1200,12 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
@override
|
||||
String get trackLyricsNotAvailable => 'Lyrics not available for this track';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout => 'Request timed out. Try again later.';
|
||||
|
||||
|
||||
@@ -1207,6 +1207,12 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
String get trackLyricsNotAvailable => 'Lirik tidak tersedia untuk lagu ini';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout => 'Permintaan timeout. Coba lagi nanti.';
|
||||
|
||||
|
||||
@@ -1194,6 +1194,12 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get trackLyricsNotAvailable => 'このトラックの歌詞は利用できません';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout => 'リクエストがタイムアウトしました。後ほどお試しください。';
|
||||
|
||||
|
||||
@@ -1180,6 +1180,12 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get trackLyricsNotAvailable => 'Lyrics not available for this track';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout => 'Request timed out. Try again later.';
|
||||
|
||||
|
||||
@@ -1200,6 +1200,12 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get trackLyricsNotAvailable => 'Lyrics not available for this track';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout => 'Request timed out. Try again later.';
|
||||
|
||||
|
||||
@@ -1200,6 +1200,12 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get trackLyricsNotAvailable => 'Lyrics not available for this track';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout => 'Request timed out. Try again later.';
|
||||
|
||||
|
||||
@@ -1220,6 +1220,12 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get trackLyricsNotAvailable =>
|
||||
'Текст песни недоступен для этого трека';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout =>
|
||||
'Время ожидания запроса истекло. Повторите попытку позже.';
|
||||
|
||||
@@ -1206,6 +1206,12 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
@override
|
||||
String get trackLyricsNotAvailable => 'Lyrics not available for this track';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout => 'Request timed out. Try again later.';
|
||||
|
||||
|
||||
@@ -1200,6 +1200,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get trackLyricsNotAvailable => 'Lyrics not available for this track';
|
||||
|
||||
@override
|
||||
String get trackLyricsNotInFile => 'No lyrics found in this file';
|
||||
|
||||
@override
|
||||
String get trackFetchOnlineLyrics => 'Fetch from Online';
|
||||
|
||||
@override
|
||||
String get trackLyricsTimeout => 'Request timed out. Try again later.';
|
||||
|
||||
|
||||
@@ -1563,6 +1563,14 @@
|
||||
"@trackLyricsNotAvailable": {
|
||||
"description": "Message when lyrics not found"
|
||||
},
|
||||
"trackLyricsNotInFile": "No lyrics found in this file",
|
||||
"@trackLyricsNotInFile": {
|
||||
"description": "Message when no embedded lyrics in audio file"
|
||||
},
|
||||
"trackFetchOnlineLyrics": "Fetch from Online",
|
||||
"@trackFetchOnlineLyrics": {
|
||||
"description": "Action - fetch lyrics from online providers"
|
||||
},
|
||||
"trackLyricsTimeout": "Request timed out. Try again later.",
|
||||
"@trackLyricsTimeout": {
|
||||
"description": "Message when lyrics request times out"
|
||||
|
||||
@@ -69,6 +69,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
bool _lyricsEmbedded = false;
|
||||
bool _isEmbedding = false;
|
||||
bool _isInstrumental = false;
|
||||
bool _embeddedLyricsChecked = false;
|
||||
bool _isConverting = false;
|
||||
bool _hasMetadataChanges = false;
|
||||
bool _hasLoadedResolvedAudioMetadata = false;
|
||||
@@ -241,7 +242,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
}
|
||||
|
||||
if (mounted && exists && _lyrics == null && !_lyricsLoading) {
|
||||
_fetchLyrics();
|
||||
_checkEmbeddedLyrics();
|
||||
}
|
||||
if (mounted &&
|
||||
exists &&
|
||||
@@ -1664,7 +1665,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _fetchLyrics,
|
||||
onPressed: _fetchOnlineLyrics,
|
||||
child: Text(context.l10n.dialogRetry),
|
||||
),
|
||||
],
|
||||
@@ -1732,6 +1733,46 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
],
|
||||
],
|
||||
)
|
||||
else if (_embeddedLyricsChecked && _fileExists)
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest.withValues(
|
||||
alpha: 0.5,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.lyrics_outlined,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.l10n.trackLyricsNotInFile,
|
||||
style: TextStyle(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Center(
|
||||
child: FilledButton.tonalIcon(
|
||||
onPressed: _fetchOnlineLyrics,
|
||||
icon: const Icon(Icons.cloud_download_outlined),
|
||||
label: Text(context.l10n.trackFetchOnlineLyrics),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
Center(
|
||||
child: FilledButton.tonalIcon(
|
||||
@@ -1746,6 +1787,134 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Check for lyrics embedded in the audio file only (no network requests).
|
||||
/// Called automatically when the screen opens.
|
||||
Future<void> _checkEmbeddedLyrics() async {
|
||||
if (_lyricsLoading || !_fileExists) return;
|
||||
|
||||
setState(() {
|
||||
_lyricsLoading = true;
|
||||
_lyricsError = null;
|
||||
_isInstrumental = false;
|
||||
_lyricsSource = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final embeddedResult =
|
||||
await PlatformBridge.getLyricsLRCWithSource(
|
||||
'',
|
||||
trackName,
|
||||
artistName,
|
||||
filePath: cleanFilePath,
|
||||
durationMs: 0,
|
||||
).timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => <String, dynamic>{'lyrics': '', 'source': ''},
|
||||
);
|
||||
|
||||
final embeddedLyrics = embeddedResult['lyrics']?.toString() ?? '';
|
||||
final embeddedSource = embeddedResult['source']?.toString() ?? '';
|
||||
|
||||
if (mounted) {
|
||||
if (embeddedLyrics.isNotEmpty) {
|
||||
final cleanLyrics = _cleanLrcForDisplay(embeddedLyrics);
|
||||
setState(() {
|
||||
_lyrics = cleanLyrics;
|
||||
_rawLyrics = embeddedLyrics;
|
||||
_lyricsSource = embeddedSource.isNotEmpty
|
||||
? embeddedSource
|
||||
: 'Embedded';
|
||||
_lyricsEmbedded = true;
|
||||
_lyricsLoading = false;
|
||||
_embeddedLyricsChecked = true;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_lyricsLoading = false;
|
||||
_embeddedLyricsChecked = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_lyricsLoading = false;
|
||||
_embeddedLyricsChecked = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch lyrics from online providers. Only called by user action.
|
||||
Future<void> _fetchOnlineLyrics() async {
|
||||
if (_lyricsLoading) return;
|
||||
|
||||
setState(() {
|
||||
_lyricsLoading = true;
|
||||
_lyricsError = null;
|
||||
_isInstrumental = false;
|
||||
_lyricsSource = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final durationMs = (duration ?? 0) * 1000;
|
||||
|
||||
final result = await PlatformBridge.getLyricsLRCWithSource(
|
||||
_spotifyId ?? '',
|
||||
trackName,
|
||||
artistName,
|
||||
filePath: null,
|
||||
durationMs: durationMs,
|
||||
).timeout(const Duration(seconds: 20));
|
||||
|
||||
final lrcText = result['lyrics']?.toString() ?? '';
|
||||
final source = result['source']?.toString() ?? '';
|
||||
final instrumental =
|
||||
(result['instrumental'] as bool? ?? false) ||
|
||||
lrcText == '[instrumental:true]';
|
||||
|
||||
if (mounted) {
|
||||
if (instrumental) {
|
||||
setState(() {
|
||||
_isInstrumental = true;
|
||||
_lyricsSource = source.isNotEmpty ? source : null;
|
||||
_lyricsLoading = false;
|
||||
});
|
||||
} else if (lrcText.isEmpty) {
|
||||
setState(() {
|
||||
_lyricsError = context.l10n.trackLyricsNotAvailable;
|
||||
_lyricsLoading = false;
|
||||
});
|
||||
} else {
|
||||
final cleanLyrics = _cleanLrcForDisplay(lrcText);
|
||||
setState(() {
|
||||
_lyrics = cleanLyrics;
|
||||
_rawLyrics = lrcText;
|
||||
_lyricsSource = source.isNotEmpty ? source : null;
|
||||
_lyricsEmbedded = false;
|
||||
_lyricsLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
} on TimeoutException {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_lyricsError = context.l10n.trackLyricsTimeout;
|
||||
_lyricsLoading = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_lyricsError = context.l10n.trackLyricsLoadFailed;
|
||||
_lyricsLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Full lyrics fetch: check embedded first, then online.
|
||||
/// Used by the "Load Lyrics" button when file doesn't exist (non-local items).
|
||||
Future<void> _fetchLyrics() async {
|
||||
if (_lyricsLoading) return;
|
||||
|
||||
@@ -1786,6 +1955,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
: 'Embedded';
|
||||
_lyricsEmbedded = true;
|
||||
_lyricsLoading = false;
|
||||
_embeddedLyricsChecked = true;
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -190,10 +190,10 @@ class FFmpegService {
|
||||
String command;
|
||||
if (format == 'opus') {
|
||||
command =
|
||||
'-i "$inputPath" -codec:a libopus -b:a $bitrateValue -vbr on -compression_level 10 -map 0:a "$outputPath" -y';
|
||||
'-v error -hide_banner -i "$inputPath" -codec:a libopus -b:a $bitrateValue -vbr on -compression_level 10 -map 0:a "$outputPath" -y';
|
||||
} else {
|
||||
command =
|
||||
'-i "$inputPath" -codec:a libmp3lame -b:a $bitrateValue -map 0:a -id3v2_version 3 "$outputPath" -y';
|
||||
'-v error -hide_banner -i "$inputPath" -codec:a libmp3lame -b:a $bitrateValue -map 0:a -id3v2_version 3 "$outputPath" -y';
|
||||
}
|
||||
|
||||
final result = await _execute(command);
|
||||
@@ -327,7 +327,7 @@ class FFmpegService {
|
||||
final outputPath = _buildOutputPath(inputPath, '.mp3');
|
||||
|
||||
final command =
|
||||
'-i "$inputPath" -codec:a libmp3lame -b:a $bitrate -map 0:a -map_metadata 0 -id3v2_version 3 "$outputPath" -y';
|
||||
'-v error -hide_banner -i "$inputPath" -codec:a libmp3lame -b:a $bitrate -map 0:a -map_metadata 0 -id3v2_version 3 "$outputPath" -y';
|
||||
|
||||
final result = await _execute(command);
|
||||
|
||||
@@ -779,7 +779,7 @@ class FFmpegService {
|
||||
final outputPath = _buildOutputPath(inputPath, '.opus');
|
||||
|
||||
final command =
|
||||
'-i "$inputPath" -codec:a libopus -b:a $bitrate -vbr on -compression_level 10 -map 0:a -map_metadata 0 "$outputPath" -y';
|
||||
'-v error -hide_banner -i "$inputPath" -codec:a libopus -b:a $bitrate -vbr on -compression_level 10 -map 0:a -map_metadata 0 "$outputPath" -y';
|
||||
|
||||
final result = await _execute(command);
|
||||
|
||||
@@ -852,10 +852,10 @@ class FFmpegService {
|
||||
String command;
|
||||
if (codec == 'alac') {
|
||||
command =
|
||||
'-i "$inputPath" -codec:a alac -map 0:a -map_metadata 0 "$outputPath" -y';
|
||||
'-v error -hide_banner -i "$inputPath" -codec:a alac -map 0:a -map_metadata 0 "$outputPath" -y';
|
||||
} else {
|
||||
command =
|
||||
'-i "$inputPath" -codec:a aac -b:a $bitrate -map 0:a -map_metadata 0 "$outputPath" -y';
|
||||
'-v error -hide_banner -i "$inputPath" -codec:a aac -b:a $bitrate -map 0:a -map_metadata 0 "$outputPath" -y';
|
||||
}
|
||||
|
||||
final result = await _execute(command);
|
||||
@@ -895,8 +895,10 @@ class FFmpegService {
|
||||
// Run FFmpeg with ebur128 filter + astats for true peak.
|
||||
// -nostats suppresses the interactive progress line.
|
||||
// ebur128=peak=true prints integrated loudness + true peak.
|
||||
// framelog=quiet suppresses per-frame measurements (very verbose),
|
||||
// keeping only the final summary which we parse.
|
||||
final command =
|
||||
'-nostats -i "$filePath" -filter_complex ebur128=peak=true -f null -';
|
||||
'-hide_banner -nostats -i "$filePath" -filter_complex ebur128=peak=true:framelog=quiet -f null -';
|
||||
|
||||
_log.d(
|
||||
'Scanning ReplayGain for: ${filePath.split(Platform.pathSeparator).last}',
|
||||
@@ -998,7 +1000,7 @@ class FFmpegService {
|
||||
// -map_metadata 0 preserves all existing metadata from the input.
|
||||
// -metadata flags add/overwrite only the specified keys.
|
||||
final command =
|
||||
'-i "$filePath" -map 0 -c copy -map_metadata 0 '
|
||||
'-v error -hide_banner -i "$filePath" -map 0 -c copy -map_metadata 0 '
|
||||
'-metadata REPLAYGAIN_ALBUM_GAIN="$sanitizedGain" '
|
||||
'-metadata REPLAYGAIN_ALBUM_PEAK="$sanitizedPeak" '
|
||||
'"$tempOutput" -y';
|
||||
@@ -1048,6 +1050,7 @@ class FFmpegService {
|
||||
final tempOutput = _nextTempEmbedPath(tempDir.path, '.flac');
|
||||
|
||||
final StringBuffer cmdBuffer = StringBuffer();
|
||||
cmdBuffer.write('-v error -hide_banner ');
|
||||
cmdBuffer.write('-i "$flacPath" ');
|
||||
|
||||
if (coverPath != null) {
|
||||
@@ -1127,6 +1130,7 @@ class FFmpegService {
|
||||
final tempOutput = _nextTempEmbedPath(tempDir.path, '.mp3');
|
||||
|
||||
final StringBuffer cmdBuffer = StringBuffer();
|
||||
cmdBuffer.write('-v error -hide_banner ');
|
||||
cmdBuffer.write('-i "$mp3Path" ');
|
||||
|
||||
if (coverPath != null) {
|
||||
@@ -1213,6 +1217,9 @@ class FFmpegService {
|
||||
final tempOutput = _nextTempEmbedPath(tempDir.path, '.opus');
|
||||
final mapMetaValue = preserveMetadata ? '0' : '-1';
|
||||
final arguments = <String>[
|
||||
'-v',
|
||||
'error',
|
||||
'-hide_banner',
|
||||
'-i',
|
||||
opusPath,
|
||||
'-map',
|
||||
@@ -1305,6 +1312,7 @@ class FFmpegService {
|
||||
final tempOutput = _nextTempEmbedPath(tempDir.path, '.m4a');
|
||||
|
||||
final cmdBuffer = StringBuffer();
|
||||
cmdBuffer.write('-v error -hide_banner ');
|
||||
cmdBuffer.write('-i "$m4aPath" ');
|
||||
|
||||
final hasCover = coverPath != null && await File(coverPath).exists();
|
||||
@@ -1528,10 +1536,10 @@ class FFmpegService {
|
||||
String command;
|
||||
if (format == 'opus') {
|
||||
command =
|
||||
'-i "$inputPath" -codec:a libopus -b:a $bitrate -vbr on -compression_level 10 -map 0:a "$outputPath" -y';
|
||||
'-v error -hide_banner -i "$inputPath" -codec:a libopus -b:a $bitrate -vbr on -compression_level 10 -map 0:a "$outputPath" -y';
|
||||
} else {
|
||||
command =
|
||||
'-i "$inputPath" -codec:a libmp3lame -b:a $bitrate -map 0:a -id3v2_version 3 "$outputPath" -y';
|
||||
'-v error -hide_banner -i "$inputPath" -codec:a libmp3lame -b:a $bitrate -map 0:a -id3v2_version 3 "$outputPath" -y';
|
||||
}
|
||||
|
||||
_log.i(
|
||||
@@ -1605,6 +1613,7 @@ class FFmpegService {
|
||||
final outputPath = _buildOutputPath(inputPath, '.m4a');
|
||||
|
||||
final cmdBuffer = StringBuffer();
|
||||
cmdBuffer.write('-v error -hide_banner ');
|
||||
cmdBuffer.write('-i "$inputPath" ');
|
||||
|
||||
final hasCover =
|
||||
@@ -1667,6 +1676,7 @@ class FFmpegService {
|
||||
final outputPath = _buildOutputPath(inputPath, '.flac');
|
||||
|
||||
final cmdBuffer = StringBuffer();
|
||||
cmdBuffer.write('-v error -hide_banner ');
|
||||
cmdBuffer.write('-i "$inputPath" ');
|
||||
|
||||
final hasCover =
|
||||
@@ -2080,6 +2090,7 @@ class FFmpegService {
|
||||
final outputPath = '$outputDir${Platform.pathSeparator}$outputFileName';
|
||||
|
||||
final StringBuffer cmdBuffer = StringBuffer();
|
||||
cmdBuffer.write('-v error -hide_banner ');
|
||||
cmdBuffer.write('-i "$audioPath" ');
|
||||
|
||||
final startTime = _formatSecondsForFFmpeg(track.startSec);
|
||||
|
||||
Reference in New Issue
Block a user