Skip to content

feat(yandex_music): improve configuration with My Wave, Liked Tracks, and organized settings#3147

Open
trudenboy wants to merge 51 commits intomusic-assistant:devfrom
trudenboy:refactor/yandex-config-categories
Open

feat(yandex_music): improve configuration with My Wave, Liked Tracks, and organized settings#3147
trudenboy wants to merge 51 commits intomusic-assistant:devfrom
trudenboy:refactor/yandex-config-categories

Conversation

@trudenboy
Copy link
Contributor

@trudenboy trudenboy commented Feb 12, 2026

Summary

Major improvements to Yandex Music provider: FLAC lossless playback, Lyrics support, Picks & Mixes curated collections, extended recommendations, and streamlined configuration.

Scope: 11 files changed, ~3300 lines added, ~400 removed. 94 unit tests.


New Features

FLAC Lossless Playback

Lossless audio streaming with AES-256-CTR decryption for premium subscribers via /get-file-info API with HMAC-SHA256 signed requests. Lossless requests use _call_with_retry for automatic reconnection on transient failures — HMAC signature is recomputed with fresh timestamp on each retry.

Quality tiers:

Tier Codec Bitrate
Efficient AAC ~64 kbps
Balanced (default) AAC ~192 kbps
High MP3 ~320 kbps
Superb FLAC lossless

Streaming modes (for encrypted FLAC):

  • Direct — decrypt on-the-fly, chunk-by-chunk
  • Buffered (default) — async queue decouples download from consumption
  • Preload — full download + decrypt to temp file before playback (enables seek)

Preload mode auto-falls back to Buffered when file exceeds configurable size limit.

Lyrics

Native lyrics from Yandex Music API:

  • Synced LRC format (timestamped) when available — detected via \[\d{2}:\d{2} regex
  • Plain text fallback
  • Fetched from already-loaded track object (no extra API call)
  • Cached with track data (30 days)
  • Geo-restrictions handled silently

Picks & Mixes (Curated Collections)

Browse access to Yandex Music curated playlists with runtime tag validation:

Picks/
├── Mood      (sad, romantic, party, relax, in the mood, ...)
├── Activity  (morning, evening, background, ...)
├── Era       (80s, 90s, 2000s, ...)
└── Genres    (rock, jazz, classical, electronic, r&b, top, by genre, ...)

Mixes/
└── Seasonal  (winter, summer, autumn, newyear)

How it works:

  • Tags sourced from hardcoded category lists + Landing API discovery (landing("mixes"))
  • Each tag validated at runtime via client.get_tag_playlists() — only tags with actual playlists are shown
  • Tag validation runs in parallel (asyncio.gather + Semaphore(8)) to minimize latency
  • Editorial /post/ URLs from landing API are filtered out (only /tag/ entries kept)
  • Validated tags are categorized into folders via TAG_SLUG_CATEGORY mapping (unknown tags default to Mood)
  • Tags sorted within categories by TAG_CATEGORY_ORDER for consistent display
  • Empty categories hidden — if no valid tags exist for a category, the folder is not shown
  • Validation results cached for 1 hour to minimize API calls; tag playlists cached for 10 minutes
  • Category and tag folders have is_playable=False to prevent accidentally loading thousands of tracks
  • Dynamic seasonal fallback — if a seasonal tag (e.g., "spring") is not available, falls back dynamically via _validate_tag()

Extended Recommendations (Home Page)

Section Source Cache
My Wave Rotor radio user:onyourwave 10 min
Made for You Feed API (Playlist of the Day, DejaVu, etc.) 30 min
Chart Chart API 1 hour
New Releases New releases API (albums) 1 hour
New Playlists New playlists API 1 hour
Top Picks Tag top 1 hour
Mood Mix Rotating validated mood tag 30 min
Activity Mix Rotating validated activity tag 30 min
Seasonal Mix Month-based tag 6 hours

Mood Mix and Activity Mix use pre-validated tags selected outside the cached method to ensure random.choice actually rotates on each cache miss.

My Wave Improvements

  • Composite track_id@station_id item IDs for rotor feedback
  • Rotor feedback: radioStarted, trackStarted, trackFinished, skip
  • Duplicate track protection across batches (with asyncio.Lock)
  • Configurable max tracks (default: 150)
  • MY_WAVE_BATCH_SIZE controls the number of API batches to fetch (not per-batch size)
  • Virtual playlist with cursor-based pagination
  • "Load more" in browse
  • Infinite loop fix: break when all tracks in a batch are duplicates

Liked Tracks

  • Virtual playlist in library (always visible)
  • Reverse chronological sort (most recently liked first)
  • Configurable max tracks (default: 500)
  • Batch fetching of full track details

Similar Tracks

Uses Yandex Rotor station track:{id} to get similar tracks for MA radio mode.


Configuration

8 settings total (4 shown by default, 4 advanced):

Setting Type Default Advanced
Token secure string no
Reset auth action no
Audio quality select Balanced no
FLAC streaming mode select Buffered yes
Preload max file size (MB) integer 10–500 100 yes (visible when mode=Preload)
My Wave max tracks integer 150 yes
Liked Tracks max tracks integer 500 yes
API Base URL string https://api.music.yandex.net yes

Architecture

New Files

File Purpose
streaming.py YandexMusicStreamingManager — quality selection, AES-256-CTR decryption, streaming modes, shared aiohttp.ClientSession lifecycle, temp file management
constants.py All configuration keys, quality/mode constants, browse names (RU/EN), tag categories, TAG_SLUG_CATEGORY mapping, TAG_CATEGORY_ORDER

Modified Files

File Changes
__init__.py Setup with SUPPORTED_FEATURES including LYRICS, BROWSE, RECOMMENDATIONS, SIMILAR_TRACKS; config entries
manifest.json Added pycryptodome==3.23.0 dependency
api_client.py YandexMusicClient wrapper with retry logic, rotor station API, /get-file-info lossless endpoint (with _call_with_retry), Landing tags API (with /post/ URL filtering), lyrics API (from already-fetched track object), library edit methods
provider.py Browse (My Wave, Picks, Mixes with runtime tag validation), 9 recommendation methods, virtual playlists (My Wave, Liked Tracks), similar tracks via rotor, rotor feedback in on_played/on_streamed, locale-aware names (RU/EN), is_playable=False for navigation folders
parsers.py parse_track accepts optional lyrics/lyrics_synced params

Data Flow

get_stream_details(item_id)
├── Quality=Superb → get_file_info_lossless (FLAC, via _call_with_retry)
│   ├── Encrypted → check streaming_mode
│   │   ├── Preload + fits limit → download+decrypt → LOCAL_FILE
│   │   ├── Preload + too large → CUSTOM + streaming_mode_override=buffered
│   │   └── Direct/Buffered → CUSTOM (decrypt in get_audio_stream)
│   └── Unencrypted → HTTP (direct URL)
└── Other quality → download-info API → select best codec → HTTP

Reliability

  • Race condition fix: asyncio.Lock protects _my_wave_seen_track_ids across concurrent browse/playlist/recommendations calls
  • Cache key normalization: get_track() normalizes composite IDs (track_id@station_idtrack_id) before caching to prevent duplicate cache entries
  • Auto-reconnect: _call_with_retry handles Server disconnected / network errors (includes lossless API)
  • Shared streaming session: Single aiohttp.ClientSession reused across streams (created lazily, closed on unload)
  • Temp file cleanup: On stream completion + periodic stale check (10 min max age) + full cleanup on unload
  • File descriptor safety: temp_fd = -1 sentinel pattern prevents double-close and fd leaks on HTTP failures
  • Cancelled task cleanup: Background download tasks are cancelled + awaited with suppress(CancelledError)
  • Infinite loop fix: My Wave batch fetching breaks when all tracks in a batch are duplicates
  • Tag validation: Runtime validation eliminates empty browse folders
  • Logging: Downgraded noisy per-call INFO/WARNING logs to DEBUG; %s formatting throughout

Tests

94 tests across 4 files:

Test file Tests Coverage
test_integration.py 15 Registration, sync, browse, search, get track/album/artist/playlist, library edit, stream details (high + lossless quality)
test_streaming.py 17 Quality selection across all 4 tiers (Efficient, Balanced, High, Superb), content type mapping, AAC variant coverage, edge cases
test_recommendations.py 35 All 9 recommendation methods with mocked API responses, error handling, edge cases, duplicate filtering, aggregate tests
test_api_client.py 6 Client initialization, connection handling, retry logic
conftest.py (parsers) 21 Snapshot tests for track/album/artist/playlist parsing

All tests pass. Pre-commit (ruff lint + format) passes.


Localization

Browse folder names adapt to MA locale:

  • ru_* → Russian names (Моя волна, Мне нравится, Подборки, etc.)
  • Other → English names (My Wave, My Favorites, Picks, etc.)

🤖 Generated with Claude Code

trudenboy and others added 4 commits February 12, 2026 20:45
Add 6 new configuration options for Yandex Music provider:
- My Wave maximum tracks (default: 150) - Control total number of tracks fetched
- My Wave batch count (default: 3) - Number of API calls for initial load
- Track details batch size (default: 50) - Batch size for track detail requests
- Discovery initial tracks (default: 5) - Initial display limit for Discover
- Browse initial tracks (default: 15) - Initial display limit for Browse
- Enable Discover (default: true) - Toggle recommendations on/off

Implemented duplicate protection for My Wave tracks using set-based tracking.
Recommendations now refresh every 60 seconds instead of 3 hours for fresher discoveries.

All new settings have sensible defaults that maintain current behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Advanced setting for API base URL in Yandex Music provider,
allowing users to change the API endpoint if service updates their URL.

Changes:
- Add CONF_BASE_URL and DEFAULT_BASE_URL constants
- Add Advanced ConfigEntry for base_url in provider settings
- Update YandexMusicClient to accept base_url parameter
- Pass base_url from config to ClientAsync initialization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rting

This commit adds three major improvements to the Liked Tracks feature:

1. Reverse chronological sorting:
   - Liked tracks are now sorted by timestamp (most recent first)
   - Matches mobile app behavior for better UX
   - Applied automatically in get_liked_tracks() method

2. Browse folder visibility toggle:
   - Added CONF_ENABLE_LIKED_TRACKS_BROWSE config option
   - Allows hiding Liked Tracks folder from Browse section
   - Default: True (backward compatible)

3. Virtual playlist for Liked Tracks:
   - Added LIKED_TRACKS_PLAYLIST_ID virtual playlist
   - Appears in library playlists (similar to My Wave)
   - Supports full MA playlist features (radio, favorites, etc.)
   - Configurable via CONF_ENABLE_LIKED_TRACKS_PLAYLIST
   - Respects CONF_LIKED_TRACKS_MAX_TRACKS limit (default: 500)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…es and advanced flags

Restructures the 16 Yandex Music provider configuration entries to improve UX:
- Groups My Wave settings (6 entries) in "my_wave" category
- Groups Liked Tracks settings (3 entries) in "liked_tracks" category
- Marks performance tuning settings as advanced (7 entries total)
- Maintains authentication/quality settings at top level

This reduces visible clutter from 16 to ~8 settings by default, with advanced
options hidden behind a toggle. No breaking changes - all config keys, defaults,
and functionality remain unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

🔒 Dependency Security Report

📦 Modified Dependencies

music_assistant/providers/yandex_music/manifest.json

Added:

Unchanged dependencies

🔍 Vulnerability Scan Results

No known vulnerabilities found
✅ No known vulnerabilities found


Automated Security Checks

  • Vulnerability Scan: Passed - No known vulnerabilities
  • Trusted Sources: All packages have verified source repositories
  • Typosquatting Check: No suspicious package names detected
  • License Compatibility: All licenses are OSI-approved and compatible
  • Supply Chain Risk: Passed - packages appear mature and maintained

Manual Review

Maintainer approval required:

  • I have reviewed the changes above and approve these dependency updates

To approve: Comment /approve-dependencies or manually add the dependencies-reviewed label.

trudenboy and others added 4 commits February 13, 2026 17:05
…ryption

This commit fully implements lossless FLAC playback for Yandex Music, fixing the
issue where only MP3 320kbps was available despite user having a premium subscription.

## Changes

### Quality Levels Restructure
- Replaced HIGH/LOSSLESS quality options with three-tier system matching reference implementation
- EFFICIENT (AAC ~64kbps) - Low quality, efficient bandwidth
- BALANCED (AAC ~192kbps) - Medium quality (new default)
- SUPERB (FLAC Lossless) - Highest quality
- Updated manifest.json and config entries to reflect new quality options

### API Authentication Fix
- Implemented manual HMAC-SHA256 sign calculation matching yandex-music-downloader-realflac
- Critical fix: Remove last character from base64-encoded sign ([:-1])
- Fixed HTTP 403 "not-allowed" errors from /get-file-info endpoint
- Uses DEFAULT_SIGN_KEY from yandex-music library

### FLAC Decryption Implementation
- Added _decrypt_track_url() method using AES-256 CTR mode
- Uses PyCrypto (pycryptodome) which supports 12-byte nonce for CTR mode
- Key is HEX-encoded (bytes.fromhex), not base64 as initially attempted
- Downloads encrypted stream, decrypts on-the-fly, saves to temp file
- Returns StreamDetails with StreamType.LOCAL_FILE pointing to decrypted temp file

### Streaming Logic Updates
- Enhanced get_stream_details() to handle encrypted URLs from encraw transport
- Detects needs_decryption flag in API response
- Falls back gracefully to MP3 if decryption fails
- Supports both encrypted and unencrypted FLAC URLs
- Updated _select_best_quality() to intelligently select based on three quality tiers

### Dependencies
- Added pycryptodome==3.21.0 to support AES CTR mode with 12-byte nonce
- Uses aiohttp for direct HTTP download of encrypted streams

### Testing
- All existing tests pass (7/7 in test_streaming.py)
- Type checking passes (mypy success)
- Code quality checks pass (ruff linter/formatter)

## Technical Details

The Yandex Music API returns encrypted URLs when using transports=encraw. The decryption
process matches the working reference implementation:

1. Calculate HMAC-SHA256 sign with all param values joined
2. Base64 encode and remove last character (critical!)
3. Request /get-file-info with quality=lossless, codecs=flac-mp4,flac,...
4. Download encrypted stream from returned URL
5. Decrypt using AES-256 CTR with 12-byte null nonce and HEX-decoded key
6. Save decrypted FLAC to temporary file
7. Return stream details pointing to temp file

## Tested

- Server starts successfully with Yandex Music provider loaded
- FLAC codec detected from API (codec=flac-mp4)
- Encryption detected and decryption executes (55MB encrypted → decrypted)
- StreamType.LOCAL_FILE allows playback from temp file
- Graceful fallback to MP3 if decryption fails

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace temp file approach with memory-based streaming decryption for better
performance and reduced disk I/O.

## Changes

### StreamType.CUSTOM Implementation
- Use AsyncGenerator[bytes, None] for streaming decryption
- No temporary files on disk - all processing in memory
- Store encrypted URL and key in StreamDetails.data

### Streaming Decryption Pipeline
- Download encrypted stream in 64KB chunks using aiohttp
- Decrypt incrementally with AES-256 CTR mode
- Counter auto-increments for each block (streaming-friendly)
- Yield decrypted chunks directly to audio pipeline

### Performance Improvements
- **First chunk:** 0.39s vs 5+ seconds with temp file approach
- **No disk I/O:** Streaming directly from memory
- **Lower latency:** Start playback while downloading/decrypting
- **Efficient:** 64KB chunks balance memory and throughput

### Implementation Details
- Added get_audio_stream() method to YandexMusicStreamingManager
- Cipher initialization with 12-byte null nonce (PyCrypto)
- ClientTimeout: connect=30s, sock_read=600s for stable streaming
- Proper error handling and logging throughout pipeline

### Technical Notes
AES CTR mode is ideal for streaming because:
- Each block can be encrypted/decrypted independently
- Counter increments automatically - no state management needed
- Supports arbitrary chunk sizes (not just 16-byte blocks)

## Tested
- All 7 unit tests pass
- Type checking passes (mypy)
- Code quality checks pass (ruff)
- Live streaming confirmed: codec=flac, streamtype=custom
- First audio chunk in <0.4s

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pywidevine==1.9.0 requires pycryptodome>=3.23.0
Updated from 3.21.0 to 3.23.0 to satisfy both dependencies
Add new "High" quality level between Balanced and Superb:
- Efficient (AAC ~64kbps) - Low quality
- Balanced (AAC ~192kbps) - Medium quality
- High (MP3 ~320kbps) - High quality lossy (NEW)
- Superb (FLAC Lossless) - Highest quality

Implementation:
- Add QUALITY_HIGH constant in constants.py
- Add "High (MP3 ~320kbps)" option in config UI
- Update _select_best_quality() logic to prefer MP3 >=256kbps
- Fallback chain: high bitrate MP3 → any MP3 → highest non-FLAC

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
trudenboy and others added 10 commits February 13, 2026 21:40
On low-power devices, encrypted FLAC streaming breaks after ~1 minute
because AES decryption can't keep up with download speed, causing the
server to drop the connection (ClientPayloadError).

Add 3 configurable streaming modes to decouple download from decryption:
- Direct: on-the-fly decrypt (original behavior, fast devices)
- Buffered: async queue with backpressure (recommended, default)
- Preload: full download then decrypt via SpooledTemporaryFile (slow devices)

Closes #29

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_ensure_connected() now attempts reconnect when _client is None instead
of raising immediately. Added _call_with_retry() helper that wraps API
calls with one reconnect attempt on connection errors. Refactored all
API methods to use it, eliminating ad-hoc retry loops.

Fixes permanent provider death after temporary network outage where
_reconnect() failure set _client=None with no recovery path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, and playlists

Add 4 new recommendation sections beyond My Wave: Made for You (personalized
feed playlists), Chart (top tracks), New Releases (albums), and New Playlists
(editorial). Each section has its own config toggle under a "Discovery" category
and independent cache TTLs (30min for feed, 1h for chart/releases/playlists).

Closes #34

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Preload mode now downloads and decrypts to temp file before playback
- Returns StreamType.LOCAL_FILE with can_seek=True for proper navigation
- Files exceeding size limit (config) fall back to Buffered mode
- Rename config 'Preload memory limit' to 'Preload max file size' with updated description
- Add temp file cleanup on stream completion and provider unload

Fixes: seek not working when using Superb quality + Preload mode
Fixes: progress bar starting before audio is ready

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Browse:
- picks/ folder with mood, activity, era, genres subfolders
- mixes/ folder with seasonal collections (winter, summer, autumn, newyear)
- Each tag returns curated playlists from Yandex Music tags API

Discovery (home page):
- Top Picks: curated playlists (tag: top)
- Mood Mix: rotating mood playlists (chill, sad, romantic, party, relax)
- Activity Mix: rotating activity playlists (workout, focus, morning, evening, driving)
- Seasonal Mix: playlists based on current season

Configuration (new picks_mixes category):
- Enable Picks in Browse
- Enable Mixes in Browse
- Enable Top Picks on Home
- Enable Mood/Activity/Seasonal Mix on Home

Localization:
- All folder and tag names in Russian and English

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Add ProviderFeature.LYRICS to supported features
- Add get_track_lyrics() method to api_client.py
- Extend parse_track() to accept lyrics and lyrics_synced parameters
- Fetch lyrics in get_track() and attach to track metadata
- Support both synced LRC format and plain text lyrics
- Handle geo-restrictions and unavailable lyrics gracefully

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Split get_playlist() to separate virtual playlists (not cached) from real ones
- Virtual playlists (My Wave, Liked Tracks) now always use current locale
- Real playlists continue to be cached for 30 days
- Add debug logging for locale detection

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Fix base path calculation in _browse_picks() and _browse_mixes()
- Previously: base was incorrectly trimming to parent path
- Now: base correctly appends to current path
- Add debug logging for troubleshooting

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…ests

The lyrics feature added in 4fc11fd introduced a get_track_lyrics call
in get_track, but the test fixtures were not updated with the mock,
causing ValueError on tuple unpacking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 16, 2026 08:47
…le cleanup (#54)

* Initial plan

* Fix LRC regex, HMAC sign construction, and temp file cleanup order

Co-authored-by: trudenboy <139659391+trudenboy@users.noreply.github.com>

* Fix linting issues in tests

Co-authored-by: trudenboy <139659391+trudenboy@users.noreply.github.com>

* Fix spelling in test comments

Co-authored-by: trudenboy <139659391+trudenboy@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: trudenboy <139659391+trudenboy@users.noreply.github.com>
Copy link
Contributor

@MarvinSchenkel MarvinSchenkel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decryption logic needs to be rethought. We can't cache entire tracks. I will have another look at this PR when that has been refactor 👍

@MarvinSchenkel MarvinSchenkel marked this pull request as draft February 17, 2026 10:09
trudenboy and others added 3 commits February 17, 2026 14:02
…ate import

- Use == instead of is for dict comparison (BROWSE_NAMES_RU)
- Remove duplicate AsyncGenerator import from TYPE_CHECKING block

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test_temp_file_replacement_order test was failing because
MinimalProvider lacked client and mass attributes expected by
YandexMusicStreamingManager.__init__.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@trudenboy
Copy link
Contributor Author

trudenboy commented Feb 17, 2026

@MarvinSchenkel Thanks for the review! I've addressed the decryption_key / Apple Music suggestion in the inline reply — in short, Yandex uses raw AES-256-CTR (no MP4/CENC container), so ffmpeg's -decryption_key cannot handle it. PyCryptodome manual decryption is the only viable approach. But I will do some additional research on this topic.

Regarding "caching entire tracks" — this only happens in Preload mode, which is:

  • Not the default — the default streaming mode is Buffered (async queue, streams as it decrypts, no full file caching)
  • An optional advanced setting for users on low-powered devices (Raspberry Pi, cheap NAS) where simultaneous download + decryption + streaming causes audio stuttering
  • Size-gated — configurable max file size (default 100 MB), files exceeding the limit automatically fall back to Buffered mode
  • Tested on low-powered hardware — specifically designed for devices where CPU/IO constraints make real-time decrypt+stream unreliable

The default Buffered mode streams and decrypts on-the-fly via an async queue without caching entire tracks. The Direct mode does the same but without the queue buffer. Preload is simply an additional option for edge cases where real-time processing isn't feasible.

Preloaded temp files are never left behind — there's a 3-level cleanup mechanism:

  1. on_streamed callback — the temp file is deleted immediately after playback finishes (or the track is skipped)
  2. Stale file sweep — on every get_stream_details call, files older than 10 minutes are automatically cleaned up (catches edge cases like interrupted streams)
  3. Provider unload — all remaining temp files are cleaned up when the provider is unloaded/removed

Update: Also fixed container handling for flac-mp4 codec (FLAC audio inside MP4 container) in the latest commits:

  • b6b11a2_get_content_type("flac-mp4") now correctly returns (ContentType.MP4, ContentType.FLAC) instead of (ContentType.FLAC, ContentType.UNKNOWN). This properly separates the container format (MP4) from the audio codec (FLAC), so ffmpeg gets the correct -acodec flac hint while auto-detecting the MP4 container. Preloaded temp files also get the correct .mp4 extension.
  • f59a49f — Follow-up cleanup: log messages now use the actual codec string (flac / flac-mp4) instead of hardcoded "FLAC", TEMP_FILE_PREFIX renamed from "yandex_flac_" to "yandex_audio_" (since temp files can be .mp4), and added a clarifying comment in _select_best_quality about flac-mp4 forward compatibility.

Happy to discuss further or adjust anything! 🙏

@trudenboy trudenboy marked this pull request as ready for review February 17, 2026 11:41
Copilot AI review requested due to automatic review settings February 17, 2026 11:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Address Copilot review comments on PR music-assistant#3147:
- _browse_my_wave: cap fetch loop to BROWSE_INITIAL_TRACKS on initial browse
  instead of post-loop slicing, preventing tracks from being marked as "seen"
  but never shown to the user
- _get_discovered_tags: add locale parameter to cache key so tag titles are
  re-fetched when locale changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
trudenboy added a commit to trudenboy/ma-server that referenced this pull request Feb 17, 2026
Address Copilot review comments on PR music-assistant#3147:
- _browse_my_wave: cap fetch loop to BROWSE_INITIAL_TRACKS on initial browse
  instead of post-loop slicing, preventing tracks from being marked as "seen"
  but never shown to the user
- _get_discovered_tags: add locale parameter to cache key so tag titles are
  re-fetched when locale changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@trudenboy trudenboy marked this pull request as draft February 17, 2026 13:30
trudenboy and others added 4 commits February 17, 2026 16:49
Yandex API returns codec "flac-mp4" meaning FLAC audio inside an MP4
container. Previously _get_content_type mapped this to ContentType.FLAC,
causing ffmpeg to misidentify the container format. Now correctly returns
(ContentType.MP4, ContentType.FLAC) as container/codec pair, matching
how Apple Music handles MP4+AAC. Also fixes temp file extension for
preload mode (.mp4 instead of .flac).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Follow-up to b6b11a2 (flac-mp4 container fix). Replaces hardcoded
"FLAC" in log messages with the actual codec string so logs correctly
reflect flac-mp4 vs flac. Renames TEMP_FILE_PREFIX to "yandex_audio_"
since temp files may now be .mp4. Adds clarifying comment about
flac-mp4 in _select_best_quality.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Yandex API does not return sample rate or bit depth, so AudioFormat
used model defaults (44100/16) even for flac-mp4 streams that are
actually 48kHz/24bit. This caused unnecessary downsampling in
_select_pcm_format. Add codec-based defaults via _get_audio_params and
_build_audio_format helpers to set the correct values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Increase default buffer from 2MB (32 chunks) to 8MB (128 chunks)
to reduce stuttering on slow/unstable connections (~45s of FLAC audio).
Add CONF_STREAM_BUFFER_MB config entry (1-32 MB, default 8) so users
can tune the buffer size for their network conditions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@trudenboy trudenboy marked this pull request as ready for review February 17, 2026 15:32
Copilot AI review requested due to automatic review settings February 17, 2026 15:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants