diff --git a/go_backend/exports.go b/go_backend/exports.go index 4537e49e..1c353cc8 100644 --- a/go_backend/exports.go +++ b/go_backend/exports.go @@ -313,6 +313,7 @@ type DownloadResponse struct { AlreadyExists bool `json:"already_exists,omitempty"` ActualBitDepth int `json:"actual_bit_depth,omitempty"` ActualSampleRate int `json:"actual_sample_rate,omitempty"` + AudioCodec string `json:"audio_codec,omitempty"` ActualExtension string `json:"actual_extension,omitempty"` ActualContainer string `json:"actual_container,omitempty"` RequiresContainerConversion bool `json:"requires_container_conversion,omitempty"` @@ -342,6 +343,7 @@ type DownloadResult struct { FilePath string BitDepth int SampleRate int + AudioCodec string Title string Artist string Album string @@ -863,6 +865,7 @@ func buildDownloadSuccessResponse( AlreadyExists: alreadyExists, ActualBitDepth: result.BitDepth, ActualSampleRate: result.SampleRate, + AudioCodec: result.AudioCodec, ActualExtension: result.ActualExtension, ActualContainer: result.ActualContainer, RequiresContainerConversion: result.RequiresContainerConversion, @@ -920,7 +923,12 @@ func enrichResultQualityFromFile(result *DownloadResult) { if qErr == nil { result.BitDepth = quality.BitDepth result.SampleRate = quality.SampleRate - GoLog("[Download] Actual quality from file: %d-bit/%dHz\n", quality.BitDepth, quality.SampleRate) + result.AudioCodec = quality.Codec + if quality.Codec != "" { + GoLog("[Download] Actual quality from file: %s %d-bit/%dHz\n", quality.Codec, quality.BitDepth, quality.SampleRate) + } else { + GoLog("[Download] Actual quality from file: %d-bit/%dHz\n", quality.BitDepth, quality.SampleRate) + } return } diff --git a/go_backend/extension_providers.go b/go_backend/extension_providers.go index 33f5f539..60531362 100644 --- a/go_backend/extension_providers.go +++ b/go_backend/extension_providers.go @@ -236,6 +236,7 @@ func normalizeExtensionDownloadResult(result *ExtDownloadResult) (DownloadResult FilePath: strings.TrimSpace(result.FilePath), BitDepth: result.BitDepth, SampleRate: result.SampleRate, + AudioCodec: strings.TrimSpace(result.AudioCodec), Title: result.Title, Artist: result.Artist, Album: result.Album, @@ -420,6 +421,7 @@ type ExtDownloadResult struct { AlreadyExists bool `json:"already_exists,omitempty"` BitDepth int `json:"bit_depth,omitempty"` SampleRate int `json:"sample_rate,omitempty"` + AudioCodec string `json:"audio_codec,omitempty"` ErrorMessage string `json:"error_message,omitempty"` ErrorType string `json:"error_type,omitempty"` @@ -873,6 +875,7 @@ func parseExtensionDownloadResultValue(vm *goja.Runtime, value goja.Value) ExtDo AlreadyExists: gojaObjectBool(obj, "already_exists", "alreadyExists"), BitDepth: gojaObjectInt(obj, "bit_depth", "bitDepth"), SampleRate: gojaObjectInt(obj, "sample_rate", "sampleRate"), + AudioCodec: gojaObjectString(obj, "audio_codec", "audioCodec", "codec"), ErrorMessage: gojaObjectString(obj, "error_message", "errorMessage", "error"), ErrorType: gojaObjectString(obj, "error_type", "errorType"), Title: gojaObjectString(obj, "title"), diff --git a/go_backend/extension_runtime_ffmpeg.go b/go_backend/extension_runtime_ffmpeg.go index 8269e919..d9a9e2a5 100644 --- a/go_backend/extension_runtime_ffmpeg.go +++ b/go_backend/extension_runtime_ffmpeg.go @@ -131,6 +131,7 @@ func (r *extensionRuntime) ffmpegGetInfo(call goja.FunctionCall) goja.Value { "sample_rate": quality.SampleRate, "total_samples": quality.TotalSamples, "duration": float64(quality.TotalSamples) / float64(quality.SampleRate), + "codec": quality.Codec, }) } diff --git a/go_backend/extension_runtime_file.go b/go_backend/extension_runtime_file.go index 8ef83fdc..1634022b 100644 --- a/go_backend/extension_runtime_file.go +++ b/go_backend/extension_runtime_file.go @@ -136,6 +136,7 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { var onProgress goja.Callable var headers map[string]string var chunkedDownload bool + trackItemBytes := true var chunkSize int64 if len(call.Arguments) > 2 && !goja.IsUndefined(call.Arguments[2]) && !goja.IsNull(call.Arguments[2]) { optionsObj := call.Arguments[2].Export() @@ -151,6 +152,15 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { onProgress = callable } } + if trackBytes, ok := opts["trackItemBytes"]; ok { + if v, ok := trackBytes.(bool); ok { + trackItemBytes = v + } + } else if trackBytes, ok := opts["track_item_bytes"]; ok { + if v, ok := trackBytes.(bool); ok { + trackItemBytes = v + } + } if chunked, ok := opts["chunked"]; ok { switch v := chunked.(type) { case bool: @@ -194,7 +204,7 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { } if chunkedDownload { - return r.fileDownloadChunked(client, urlStr, fullPath, headers, ua, chunkSize, onProgress) + return r.fileDownloadChunked(client, urlStr, fullPath, headers, ua, chunkSize, onProgress, trackItemBytes) } req, err := http.NewRequest("GET", urlStr, nil) @@ -244,7 +254,7 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { } contentLength := resp.ContentLength - shouldTrackItemBytes := activeItemID != "" + shouldTrackItemBytes := activeItemID != "" && trackItemBytes if shouldTrackItemBytes && contentLength > 0 { SetItemBytesTotal(activeItemID, contentLength) } @@ -321,7 +331,7 @@ func (r *extensionRuntime) fileDownload(call goja.FunctionCall) goja.Value { // fileDownloadChunked downloads a URL using sequential Range requests. // This is needed for servers (like YouTube's googlevideo CDN) that reject // non-ranged or large-range requests with 403 and require small chunk downloads. -func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, fullPath string, headers map[string]string, ua string, chunkSize int64, onProgress goja.Callable) goja.Value { +func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, fullPath string, headers map[string]string, ua string, chunkSize int64, onProgress goja.Callable, trackItemBytes bool) goja.Value { // First, get the total content length with a small probe request probeReq, err := http.NewRequest("GET", urlStr, nil) if err != nil { @@ -391,7 +401,7 @@ func (r *extensionRuntime) fileDownloadChunked(client *http.Client, urlStr, full SetItemDownloading(activeItemID) } - shouldTrackItemBytes := activeItemID != "" + shouldTrackItemBytes := activeItemID != "" && trackItemBytes if shouldTrackItemBytes && totalSize > 0 { SetItemBytesTotal(activeItemID, totalSize) } diff --git a/go_backend/extension_runtime_utils.go b/go_backend/extension_runtime_utils.go index e06541e6..8b568c9b 100644 --- a/go_backend/extension_runtime_utils.go +++ b/go_backend/extension_runtime_utils.go @@ -415,6 +415,7 @@ func (r *extensionRuntime) RegisterGoBackendAPIs(vm *goja.Runtime) { "sampleRate": quality.SampleRate, "totalSamples": quality.TotalSamples, "duration": quality.Duration, + "codec": quality.Codec, }) }) diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index 8f066b9d..13c7637d 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -6246,6 +6246,16 @@ class DownloadQueueNotifier extends Notifier { if (context.quality == 'HIGH' || context.outputExt != '.flac') { return filePath; } + final resultAudioFormat = _normalizeAudioFormatValue( + result['audio_codec']?.toString() ?? + result['actual_audio_codec']?.toString(), + ); + if (_isLossyAudioFormat(resultAudioFormat)) { + _log.d( + 'Native-worker output is $resultAudioFormat; preserving native container.', + ); + return filePath; + } final requiresContainerConversion = result['requires_container_conversion'] == true || result['requiresContainerConversion'] == true; @@ -7411,10 +7421,16 @@ class DownloadQueueNotifier extends Notifier { result, filePath: filePath, ); + final resultAudioFormat = _normalizeAudioFormatValue( + result['audio_codec']?.toString() ?? + result['actual_audio_codec']?.toString(), + ); + final resultIsLossyAudio = _isLossyAudioFormat(resultAudioFormat); final requiresContainerConversion = result['requires_container_conversion'] == true || result['requiresContainerConversion'] == true || - _shouldRequestContainerConversion(actualService, safOutputExt); + (!resultIsLossyAudio && + _shouldRequestContainerConversion(actualService, safOutputExt)); final preferredOutputExt = _extensionPreferredOutputExt(actualService); final shouldPreserveNativeM4a = !requiresContainerConversion &&