Repository: xbmc/xbmc
Pull request #26736: [Bluray] SonarQube and optimisation.
Author: login: 78andyp
Labels: name: Type: Cleanup
description: non-breaking change which removes non-working or unmaintained functionality
name: Component: Video
name: v22 Piers
Created: 2025-05-03T10:55:59Z | Merged: 2025-05-06T01:30:16Z

## Description
<!--- Provide a general summary of your change in the Pull Request title above -->
<!--- Describe your change in detail here. -->

Implementing, where I can, the suggestions from Sonarqube in the three main bluray related classes - CBlurayDirectory, CDiscDirectoryHelper, and CBlurayDiscCache.

No functional changes.

Some further refactoring into functions to reduce complexity as suggested by SonarQube.

## Motivation and context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How has this been tested?
<!--- Please describe in detail how you tested your change. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

Locally

## What is the effect on users?
<!--- Summarize the effect of this change on Kodi end-users. -->
<!--- If the PR does not have a noticeable impact (e.g., if it only changes documentation), -->
<!--- just leave it empty. Put in more detail the bigger the impact is. -->
<!--- This section may be used for automatic creation of release notes. -->

None

## Screenshots (if appropriate):

## Types of change
<!--- What type of change does your code introduce? Put an `x` with no space in all the boxes that apply like this: [X] -->
- [ ] **Bug fix** (non-breaking change which fixes an issue)
- [ ] **Clean up** (non-breaking change which removes non-working, unmaintained functionality)
- [ ] **Improvement** (non-breaking change which improves existing functionality)
- [ ] **New feature** (non-breaking change which adds functionality)
- [ ] **Breaking change** (fix or feature that will cause existing functionality to change)
- [ ] **Cosmetic change** (non-breaking change that doesn't touch code)
- [ ] **Student submission** (PR was done for educational purposes and will be treated as such)
- [X] **None of the above** (please explain below)

As above

## Checklist:
<!--- Go over all the following points, and put an `X` with no space in all the boxes that apply like this: [X] -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [X] My code follows the **[Code Guidelines](https://github.com/xbmc/xbmc/blob/master/docs/CODE_GUIDELINES.md)** of this project 
- [ ] My change requires a change to the documentation, either Doxygen or wiki
- [ ] I have updated the documentation accordingly
- [X] I have read the **[Contributing](https://github.com/xbmc/xbmc/blob/master/docs/CONTRIBUTING.md)** document
- [ ] I have added tests to cover my change
- [X] All new and existing tests passed



Diff:
diff --git a/xbmc/filesystem/BlurayDirectory.cpp b/xbmc/filesystem/BlurayDirectory.cpp
index f163319560025..7f17e6e6a050e 100644
--- a/xbmc/filesystem/BlurayDirectory.cpp
+++ b/xbmc/filesystem/BlurayDirectory.cpp
@@ -30,12 +30,16 @@
 #include <algorithm>
 #include <cassert>
 #include <chrono>
+#include <cstddef>
 #include <memory>
+#include <ranges>
 #include <regex>
 #include <set>
 #include <string>
+#include <vector>
 
 #include <fmt/chrono.h>
+#include <fmt/ranges.h>
 #include <libbluray/bluray-version.h>
 #include <libbluray/bluray.h>
 #include <libbluray/log_control.h>
@@ -57,24 +61,25 @@ constexpr unsigned int GetDWord(std::span<std::byte> bytes, unsigned int offset)
 {
   if (bytes.size() < offset + 4)
     throw std::out_of_range("Not enough bytes to extract a DWORD");
-  return static_cast<unsigned int>(
-      static_cast<uint8_t>(bytes[offset + 3]) | static_cast<uint8_t>(bytes[offset + 2]) << 8 |
-      static_cast<uint8_t>(bytes[offset + 1]) << 16 | static_cast<uint8_t>(bytes[offset]) << 24);
+  return std::to_integer<unsigned>(bytes[offset + 3]) |
+         std::to_integer<unsigned>(bytes[offset + 2]) << 8 |
+         std::to_integer<unsigned>(bytes[offset + 1]) << 16 |
+         std::to_integer<unsigned>(bytes[offset]) << 24;
 }
 
 constexpr unsigned int GetWord(std::span<std::byte> bytes, unsigned int offset)
 {
   if (bytes.size() < offset + 2)
     throw std::out_of_range("Not enough bytes to extract a WORD");
-  return static_cast<unsigned int>(static_cast<uint8_t>(bytes[offset + 1]) |
-                                   static_cast<uint8_t>(bytes[offset]) << 8);
+  return std::to_integer<unsigned>(bytes[offset + 1]) | std::to_integer<unsigned>(bytes[offset])
+                                                            << 8;
 }
 
 constexpr uint8_t GetByte(std::span<std::byte> bytes, unsigned int offset)
 {
   if (bytes.size() < offset + 1)
     throw std::out_of_range("Not enough bytes to extract a BYTE");
-  return static_cast<uint8_t>(bytes[offset]);
+  return std::to_integer<uint8_t>(bytes[offset]);
 }
 
 std::string GetString(std::span<std::byte> bytes, unsigned int offset, unsigned int length)
@@ -96,8 +101,6 @@ void AddOptionsAndSort(const CURL& url, CFileItemList& items, bool blurayMenuSup
                       LABEL_MASKS("%L", "%I", "%L", "%I")); // FileName, Size | Foldername, Size
 }
 
-std::map<unsigned int, ClipInformation> lcl_clips;
-
 constexpr unsigned int OFFSET_STREAM_TYPE = 1;
 constexpr unsigned int OFFSET_PLAYITEM_PACKET_IDENTIFIER = 2;
 constexpr unsigned int OFFSET_SUBPATH_SUBPATH_ID = 2;
@@ -127,14 +130,14 @@ StreamInformation ParseStream(std::vector<std::byte>& buffer,
   streamInformation.type = type;
   switch (type)
   {
-    case BLURAY_STREAM_TYPE::PLAYITEM: // a stream of the clip used by the PlayItem
+    using enum BLURAY_STREAM_TYPE;
+    case PLAYITEM: // a stream of the clip used by the PlayItem
     {
       streamInformation.packetIdentifier =
           GetWord(buffer, offset + OFFSET_PLAYITEM_PACKET_IDENTIFIER);
       break;
     }
-    case BLURAY_STREAM_TYPE::
-        SUBPATH: // a stream of the clip used by a SubPath (types 2, 3, 4, 5, 6, 8 or 9)
+    case SUBPATH: // a stream of the clip used by a SubPath (types 2, 3, 4, 5, 6, 8 or 9)
     {
       streamInformation.subpathId = GetWord(buffer, offset + OFFSET_SUBPATH_SUBPATH_ID);
       streamInformation.subclipId = GetWord(buffer, offset + OFFSET_SUBPATH_SUBCLIP_ID);
@@ -142,10 +145,8 @@ StreamInformation ParseStream(std::vector<std::byte>& buffer,
           GetWord(buffer, offset + OFFSET_SUBPATH_PACKET_IDENTIFIER);
       break;
     }
-    case BLURAY_STREAM_TYPE::
-        SUBPATH_INMUX_SYNCHRONOUS_PIP: // a stream of the clip used by a SubPath (type 7)
-    case BLURAY_STREAM_TYPE::
-        SUBPATH_DOLBY_VISION_LAYER: // a stream of the clip used by a SubPath (type 10)
+    case SUBPATH_INMUX_SYNCHRONOUS_PIP: // a stream of the clip used by a SubPath (type 7)
+    case SUBPATH_DOLBY_VISION_LAYER: // a stream of the clip used by a SubPath (type 10)
     {
       streamInformation.subpathId = GetWord(buffer, offset + OFFSET_DV_PIP_SUBPATH_ID);
       streamInformation.packetIdentifier =
@@ -160,16 +161,17 @@ StreamInformation ParseStream(std::vector<std::byte>& buffer,
   streamInformation.coding = coding;
   switch (coding)
   {
-    case ENCODING_TYPE::VIDEO_MPEG1:
-    case ENCODING_TYPE::VIDEO_MPEG2:
-    case ENCODING_TYPE::VIDEO_H264:
-    case ENCODING_TYPE::VIDEO_HEVC:
-    case ENCODING_TYPE::VIDEO_VC1:
+    using enum ENCODING_TYPE;
+    case VIDEO_MPEG1:
+    case VIDEO_MPEG2:
+    case VIDEO_H264:
+    case VIDEO_HEVC:
+    case VIDEO_VC1:
     {
       unsigned int flag{GetByte(buffer, offset + OFFSET_STREAM_VIDEO_FLAG_1)};
       streamInformation.format = GetBits(flag, 8, 4);
       streamInformation.rate = GetBits(flag, 4, 4);
-      if (coding == ENCODING_TYPE::VIDEO_HEVC)
+      if (coding == VIDEO_HEVC)
       {
         flag = GetByte(buffer, offset + OFFSET_STREAM_VIDEO_FLAG_2);
         streamInformation.dynamicRangeType = GetBits(flag, 8, 4);
@@ -180,17 +182,17 @@ StreamInformation ParseStream(std::vector<std::byte>& buffer,
       }
       break;
     }
-    case ENCODING_TYPE::AUDIO_MPEG1:
-    case ENCODING_TYPE::AUDIO_MPEG2:
-    case ENCODING_TYPE::AUDIO_LPCM:
-    case ENCODING_TYPE::AUDIO_AC3:
-    case ENCODING_TYPE::AUDIO_DTS:
-    case ENCODING_TYPE::AUDIO_TRUHD:
-    case ENCODING_TYPE::AUDIO_AC3PLUS:
-    case ENCODING_TYPE::AUDIO_DTSHD:
-    case ENCODING_TYPE::AUDIO_DTSHD_MASTER:
-    case ENCODING_TYPE::AUDIO_AC3PLUS_SECONDARY:
-    case ENCODING_TYPE::AUDIO_DTSHD_SECONDARY:
+    case AUDIO_MPEG1:
+    case AUDIO_MPEG2:
+    case AUDIO_LPCM:
+    case AUDIO_AC3:
+    case AUDIO_DTS:
+    case AUDIO_TRUHD:
+    case AUDIO_AC3PLUS:
+    case AUDIO_DTSHD:
+    case AUDIO_DTSHD_MASTER:
+    case AUDIO_AC3PLUS_SECONDARY:
+    case AUDIO_DTSHD_SECONDARY:
     {
       const unsigned int flag{GetByte(buffer, offset + OFFSET_STREAM_AUDIO_FLAG_1)};
       streamInformation.format = GetBits(flag, 8, 4);
@@ -198,13 +200,13 @@ StreamInformation ParseStream(std::vector<std::byte>& buffer,
       streamInformation.language = GetString(buffer, offset + OFFSET_STREAM_AUDIO_LANGUAGE, 3);
       break;
     }
-    case ENCODING_TYPE::SUB_PG:
-    case ENCODING_TYPE::SUB_IG:
+    case SUB_PG:
+    case SUB_IG:
     {
       streamInformation.language = GetString(buffer, offset + OFFSET_STREAM_SUB_LANGUAGE, 3);
       break;
     }
-    case ENCODING_TYPE::SUB_TEXT:
+    case SUB_TEXT:
     {
       streamInformation.characterEncoding = GetByte(buffer, offset + OFFSET_STREAM_TEXT_ENCODING);
       streamInformation.language = GetString(buffer, offset + OFFSET_STREAM_TEXT_LANGUAGE, 3);
@@ -217,7 +219,8 @@ StreamInformation ParseStream(std::vector<std::byte>& buffer,
 
   switch (streamType)
   {
-    case STREAM_TYPE::SECONDARY_AUDIO_STREAM:
+    using enum STREAM_TYPE;
+    case SECONDARY_AUDIO_STREAM:
     {
       const unsigned int numAudioReferences{GetByte(buffer, offset)};
       streamInformation.secondaryAudio_audioReferences.reserve(numAudioReferences);
@@ -230,7 +233,7 @@ StreamInformation ParseStream(std::vector<std::byte>& buffer,
                 (numAudioReferences % 2); // next word boundary
       break;
     }
-    case STREAM_TYPE::SECONDARY_VIDEO_STREAM:
+    case SECONDARY_VIDEO_STREAM:
     {
       const unsigned int numAudioReferences{GetByte(buffer, offset)};
       streamInformation.secondaryVideo_audioReferences.reserve(numAudioReferences);
@@ -252,12 +255,12 @@ StreamInformation ParseStream(std::vector<std::byte>& buffer,
                 (numPGReferences % 2); // next word boundary
       break;
     }
-    case STREAM_TYPE::VIDEO_STREAM:
-    case STREAM_TYPE::AUDIO_STREAM:
-    case STREAM_TYPE::PRESENTATION_GRAPHIC_STREAM:
-    case STREAM_TYPE::INTERACTIVE_GRAPHIC_STREAM:
-    case STREAM_TYPE::PICTURE_IN_PICTURE_SUBTITLE_STREAM:
-    case STREAM_TYPE::DOLBY_VISION_STREAM:
+    case VIDEO_STREAM:
+    case AUDIO_STREAM:
+    case PRESENTATION_GRAPHIC_STREAM:
+    case INTERACTIVE_GRAPHIC_STREAM:
+    case PICTURE_IN_PICTURE_SUBTITLE_STREAM:
+    case DOLBY_VISION_STREAM:
       break;
   }
 
@@ -271,7 +274,11 @@ StreamInformation ParseStream(std::vector<std::byte>& buffer,
 constexpr unsigned int CLPI_HEADER_SIZE = 28;
 constexpr unsigned int OFFSET_CLPI_HEADER = 0;
 constexpr unsigned int OFFSET_CLPI_VERSION = 4;
+// OFFSET_SEQUENCE_INFORMATION = 8;
 constexpr unsigned int OFFSET_CLPI_PROGRAM_INFORMATION = 12;
+// OFFSET_CPI = 16;
+// OFFSET_CLIP_MARK = 20;
+// OFFSET_EXTENSION_DATA = 24;
 constexpr unsigned int OFFSET_CLPI_PROGRAM_INFORMATION_NUM_PROGRAMS = 5;
 constexpr unsigned int OFFSET_CLPI_PROGRAM_INFORMATION_SEQUENCE_START = 0;
 constexpr unsigned int OFFSET_CLPI_PROGRAM_INFORMATION_PROGRAM_ID = 4;
@@ -307,16 +314,10 @@ bool ParseCLPI(std::vector<std::byte>& buffer, ClipInformation& clipInformation,
   clipInformation.version = version;
   clipInformation.clip = clip;
 
-  //const unsigned int sequenceInformationStartAddress{GetDWord(buffer, 8)};
   const unsigned int programInformationStartAddress{
       GetDWord(buffer, OFFSET_CLPI_PROGRAM_INFORMATION)};
-  //const unsigned int CPIStartAddress{GetDWord(buffer, 16)};
-  //const unsigned int clipMarkStartAddress{GetDWord(buffer, 20)};
-  //const unsigned int extensionDataStartAddress{GetDWord(buffer, 24)};
-
   unsigned int offset{programInformationStartAddress};
-  const unsigned int length{GetDWord(buffer, offset)};
-  if (buffer.size() < length + offset)
+  if (const unsigned int length{GetDWord(buffer, offset)}; buffer.size() < length + offset)
   {
     CLog::Log(LOGDEBUG, "Invalid CLPI - too small for Program Information");
     return false;
@@ -353,18 +354,19 @@ bool ParseCLPI(std::vector<std::byte>& buffer, ClipInformation& clipInformation,
       streamInformation.coding = coding;
       switch (coding)
       {
-        case ENCODING_TYPE::VIDEO_MPEG1:
-        case ENCODING_TYPE::VIDEO_MPEG2:
-        case ENCODING_TYPE::VIDEO_H264:
-        case ENCODING_TYPE::VIDEO_HEVC:
-        case ENCODING_TYPE::VIDEO_VC1:
+        using enum ENCODING_TYPE;
+        case VIDEO_MPEG1:
+        case VIDEO_MPEG2:
+        case VIDEO_H264:
+        case VIDEO_HEVC:
+        case VIDEO_VC1:
         {
           const unsigned int flag{GetDWord(buffer, offset + OFFSET_CLPI_STREAM_VIDEO_FLAGS)};
           streamInformation.format = GetBits(flag, 32, 4);
           streamInformation.rate = GetBits(flag, 28, 4);
           streamInformation.aspect = static_cast<ASPECT_RATIO>(GetBits(flag, 24, 4));
           streamInformation.outOfMux = GetBits(flag, 18, 1);
-          if (coding == ENCODING_TYPE::VIDEO_HEVC)
+          if (coding == VIDEO_HEVC)
           {
             streamInformation.copyRestricted = GetBits(flag, 17, 1);
             streamInformation.dynamicRangeType = GetBits(flag, 16, 4);
@@ -373,17 +375,17 @@ bool ParseCLPI(std::vector<std::byte>& buffer, ClipInformation& clipInformation,
           }
           break;
         }
-        case ENCODING_TYPE::AUDIO_MPEG1:
-        case ENCODING_TYPE::AUDIO_MPEG2:
-        case ENCODING_TYPE::AUDIO_LPCM:
-        case ENCODING_TYPE::AUDIO_AC3:
-        case ENCODING_TYPE::AUDIO_DTS:
-        case ENCODING_TYPE::AUDIO_TRUHD:
-        case ENCODING_TYPE::AUDIO_AC3PLUS:
-        case ENCODING_TYPE::AUDIO_DTSHD:
-        case ENCODING_TYPE::AUDIO_DTSHD_MASTER:
-        case ENCODING_TYPE::AUDIO_AC3PLUS_SECONDARY:
-        case ENCODING_TYPE::AUDIO_DTSHD_SECONDARY:
+        case AUDIO_MPEG1:
+        case AUDIO_MPEG2:
+        case AUDIO_LPCM:
+        case AUDIO_AC3:
+        case AUDIO_DTS:
+        case AUDIO_TRUHD:
+        case AUDIO_AC3PLUS:
+        case AUDIO_DTSHD:
+        case AUDIO_DTSHD_MASTER:
+        case AUDIO_AC3PLUS_SECONDARY:
+        case AUDIO_DTSHD_SECONDARY:
         {
           const unsigned int flag{GetByte(buffer, offset + OFFSET_CLPI_STREAM_AUDIO_FLAGS)};
           streamInformation.format = GetBits(flag, 8, 4);
@@ -392,14 +394,14 @@ bool ParseCLPI(std::vector<std::byte>& buffer, ClipInformation& clipInformation,
               GetString(buffer, offset + OFFSET_CLPI_STREAM_AUDIO_LANGUAGE, 3);
           break;
         }
-        case ENCODING_TYPE::SUB_PG:
-        case ENCODING_TYPE::SUB_IG:
+        case SUB_PG:
+        case SUB_IG:
         {
           streamInformation.language =
               GetString(buffer, offset + OFFSET_CLPI_STREAM_SUB_LANGUAGE, 3);
           break;
         }
-        case ENCODING_TYPE::SUB_TEXT:
+        case SUB_TEXT:
         {
           streamInformation.characterEncoding =
               GetByte(buffer, offset + OFFSET_CLPI_STREAM_TEXT_ENCODING);
@@ -562,8 +564,7 @@ bool ParsePlayItem(std::vector<std::byte>& buffer,
   }
 
   // Parse stream number table
-  const unsigned int stnLength{GetWord(buffer, offset)};
-  if (buffer.size() < stnLength + offset)
+  if (const unsigned int stnLength{GetWord(buffer, offset)}; buffer.size() < stnLength + offset)
   {
     CLog::Log(LOGDEBUG, "Invalid MPLS - too small for Stream Number Table");
     return false;
@@ -768,6 +769,7 @@ constexpr unsigned int OFFSET_MPLS_HEADER = 0;
 constexpr unsigned int OFFSET_MPLS_VERSION = 4;
 constexpr unsigned int OFFSET_MPLS_PLAYLIST_POSITION = 8;
 constexpr unsigned int OFFSET_MPLS_PLAYLIST_MARK_POSITION = 12;
+// OFFSET_EXTENSION_POSITION = 16;
 constexpr unsigned int OFFSET_MPLS_APP_INFO_PLAYLIST = 40;
 constexpr unsigned int OFFSET_MPLS_APP_INFO_PLAYBACK_TYPE = 5;
 constexpr unsigned int OFFSET_MPLS_NUM_PLAYITEMS = 6;
@@ -782,10 +784,72 @@ constexpr unsigned int OFFSET_MPLS_PLAYLISTMARK_TIME = 4;
 constexpr unsigned int OFFSET_MPLS_PLAYLISTMARK_PACKET_IDENTIFIER = 8;
 constexpr unsigned int OFFSET_MPLS_PLAYLISTMARK_DURATION = 10;
 
+void GetChaptersAndTimings(PlaylistInformation& playlistInformation)
+{
+  // Update clip timings from corresponding playItems
+  std::chrono::milliseconds startTime{0ms};
+  for (unsigned int i = 0; i < playlistInformation.playItems.size(); ++i)
+  {
+    const auto& playItem = playlistInformation.playItems[i];
+    auto& clip = playlistInformation.clips[i];
+    clip.duration = playItem.outTime - playItem.inTime;
+    clip.time = startTime;
+    startTime += clip.duration;
+  }
+
+  // Update playMark timings
+  int prevChapter{-1};
+  for (int i = 0; i < static_cast<int>(playlistInformation.playlistMarks.size()); ++i)
+  {
+    auto& playlistMark{playlistInformation.playlistMarks[i]};
+
+    // Get referenced playItem and clip to calculate playMark time
+    const auto& clip = playlistInformation.clips[playlistMark.playItemReference];
+    const auto& playItem = playlistInformation.playItems[playlistMark.playItemReference];
+    playlistMark.time = clip.time + playlistMark.time - playItem.inTime;
+
+    if (playlistMark.markType != BLURAY_MARK_TYPE::ENTRY) // chapter
+      continue;
+
+    if (prevChapter >= 0)
+    {
+      auto& prevMark{playlistInformation.playlistMarks[prevChapter]};
+      if (prevMark.duration == 0ms)
+        prevMark.duration = playlistMark.time - prevMark.time;
+    }
+    prevChapter = i;
+  }
+  if (prevChapter >= 0 && playlistInformation.playlistMarks[prevChapter].duration == 0ms)
+  {
+    auto& prevMark{playlistInformation.playlistMarks[prevChapter]};
+    prevMark.duration = playlistInformation.duration - prevMark.time;
+  }
+
+  // Derive chapters from playMarks
+  unsigned int chapter{1};
+  std::chrono::milliseconds start{0ms};
+  for (const auto& playlistMark : playlistInformation.playlistMarks)
+  {
+    if (playlistMark.markType == BLURAY_MARK_TYPE::ENTRY)
+    {
+      ChapterInformation chapterInformation{
+          .chapter = chapter, .start = start, .duration = playlistMark.duration};
+      playlistInformation.chapters.emplace_back(chapterInformation);
+      CLog::Log(LOGDEBUG, "Chapter {} start {} duration {} end {}", chapter,
+                fmt::format("{:%H:%M:%S}", start),
+                fmt::format("{:%H:%M:%S}", chapterInformation.duration),
+                fmt::format("{:%H:%M:%S}", start + chapterInformation.duration));
+      start += chapterInformation.duration;
+      ++chapter;
+    }
+  }
+}
+
 bool ParseMPLS(const CURL& url,
                std::vector<std::byte>& buffer,
                PlaylistInformation& playlistInformation,
-               unsigned int playlist)
+               unsigned int playlist,
+               std::map<unsigned int, ClipInformation>& clipCache)
 {
   // Check size
   if (buffer.size() < MPLS_HEADER_SIZE)
@@ -809,12 +873,11 @@ bool ParseMPLS(const CURL& url,
 
   const unsigned int playlistPosition{GetDWord(buffer, OFFSET_MPLS_PLAYLIST_POSITION)};
   const unsigned int playlistMarkPosition{GetDWord(buffer, OFFSET_MPLS_PLAYLIST_MARK_POSITION)};
-  //const unsigned int extensionPosition{GetDWord(buffer, 16)};
 
   // AppInfoPlaylist
   unsigned int offset{OFFSET_MPLS_APP_INFO_PLAYLIST};
-  const unsigned int appInfoSize{GetDWord(buffer, offset)};
-  if (buffer.size() < appInfoSize + offset)
+  if (const unsigned int appInfoSize{GetDWord(buffer, offset)};
+      buffer.size() < appInfoSize + offset)
   {
     CLog::Log(LOGDEBUG, "Invalid MPLS - too small for AppInfoPlaylist");
     return false;
@@ -829,8 +892,8 @@ bool ParseMPLS(const CURL& url,
 
   // Playlist
   offset = playlistPosition;
-  const unsigned int playlistSize{GetDWord(buffer, offset)};
-  if (buffer.size() < playlistSize + offset)
+  if (const unsigned int playlistSize{GetDWord(buffer, offset)};
+      buffer.size() < playlistSize + offset)
   {
     CLog::Log(LOGDEBUG, "Invalid MPLS - too small for Playlist");
     return false;
@@ -860,20 +923,24 @@ bool ParseMPLS(const CURL& url,
   {
     for (const auto& clip : playItem.angleClips)
     {
-      const auto& it = lcl_clips.find(clip.clip);
-      if (it == lcl_clips.end()) // not in local cache
+      const auto& it = clipCache.find(clip.clip);
+
+      if (it != clipCache.end())
       {
-        ClipInformation clipInformation;
-        if (!ReadCLPI(url, clip.clip, clipInformation))
-        {
-          CLog::Log(LOGDEBUG, "Cannot read clip {} information", clip.clip);
-          return false;
-        }
-        playlistInformation.clips.emplace_back(clipInformation);
-        lcl_clips[clip.clip] = clipInformation;
-      }
-      else // is in local cache
+        // In local cache
         playlistInformation.clips.emplace_back(it->second);
+        continue;
+      }
+
+      // Not in local cache
+      ClipInformation clipInformation;
+      if (!ReadCLPI(url, clip.clip, clipInformation))
+      {
+        CLog::Log(LOGDEBUG, "Cannot read clip {} information", clip.clip);
+        return false;
+      }
+      playlistInformation.clips.emplace_back(clipInformation);
+      clipCache[clip.clip] = clipInformation;
     }
   }
 
@@ -891,24 +958,25 @@ bool ParseMPLS(const CURL& url,
       const unsigned int numSubPlayItems{
           GetByte(buffer, offset + OFFSET_MPLS_SUBPATH_NUM_SUBPLAYITEMS)};
       offset += 10;
-      if (numSubPlayItems > 0)
+
+      if (numSubPlayItems == 0)
+        continue;
+
+      playlistInformation.subPlayItems.reserve(numSubPlayItems);
+      for (unsigned int j = 0; j < numSubPlayItems; ++j)
       {
-        playlistInformation.subPlayItems.reserve(numSubPlayItems);
-        for (unsigned int j = 0; j < numSubPlayItems; ++j)
-        {
-          SubPlayItemInformation subPlayItem;
-          if (!ParseSubPlayItem(buffer, offset, subPlayItem))
-            return false;
-          playlistInformation.subPlayItems.emplace_back(subPlayItem);
-        }
+        SubPlayItemInformation subPlayItem;
+        if (!ParseSubPlayItem(buffer, offset, subPlayItem))
+          return false;
+        playlistInformation.subPlayItems.emplace_back(subPlayItem);
       }
     }
   }
 
   // Parse PlayListMark
   offset = playlistMarkPosition;
-  const unsigned int playlistMarkSize{GetDWord(buffer, offset)};
-  if (buffer.size() < playlistMarkSize + offset)
+  if (const unsigned int playlistMarkSize{GetDWord(buffer, offset)};
+      buffer.size() < playlistMarkSize + offset)
   {
     CLog::Log(LOGDEBUG, "Invalid MPLS - too small for PlayListMark");
     return false;
@@ -925,7 +993,6 @@ bool ParseMPLS(const CURL& url,
   playlistInformation.playlistMarks.reserve(numPlaylistMarks);
   for (unsigned int i = 0; i < numPlaylistMarks; ++i)
   {
-
     PlaylistMarkInformation playlistMark{
         .markType =
             static_cast<BLURAY_MARK_TYPE>(GetByte(buffer, offset + OFFSET_MPLS_PLAYLISTMARK_TYPE)),
@@ -940,63 +1007,7 @@ bool ParseMPLS(const CURL& url,
     offset += MPLS_PLAYLISTMARK_SIZE;
   }
 
-  // Update clip timings from corresponding playItems
-  std::chrono::milliseconds startTime{0ms};
-  for (unsigned int i = 0; i < playlistInformation.playItems.size(); ++i)
-  {
-    const auto& playItem = playlistInformation.playItems[i];
-    auto& clip = playlistInformation.clips[i];
-    clip.duration = playItem.outTime - playItem.inTime;
-    clip.time = startTime;
-    startTime += clip.duration;
-  }
-
-  // Update playMark timings
-  int prevChapter{-1};
-  for (int i = 0; i < static_cast<int>(playlistInformation.playlistMarks.size()); ++i)
-  {
-    auto& playlistMark{playlistInformation.playlistMarks[i]};
-
-    // Get referenced playItem and clip to calculate playMark time
-    const auto& clip = playlistInformation.clips[playlistMark.playItemReference];
-    const auto& playItem = playlistInformation.playItems[playlistMark.playItemReference];
-    playlistMark.time = clip.time + playlistMark.time - playItem.inTime;
-
-    if (playlistMark.markType == BLURAY_MARK_TYPE::ENTRY) // chapter
-    {
-      if (prevChapter >= 0)
-      {
-        auto& prevMark{playlistInformation.playlistMarks[prevChapter]};
-        if (prevMark.duration == 0ms)
-          prevMark.duration = playlistMark.time - prevMark.time;
-      }
-      prevChapter = i;
-    }
-  }
-  if (prevChapter >= 0 && playlistInformation.playlistMarks[prevChapter].duration == 0ms)
-  {
-    auto& prevMark{playlistInformation.playlistMarks[prevChapter]};
-    prevMark.duration = playlistInformation.duration - prevMark.time;
-  }
-
-  // Derive chapters from playMarks
-  unsigned int chapter{1};
-  std::chrono::milliseconds start{0ms};
-  for (const auto& playlistMark : playlistInformation.playlistMarks)
-  {
-    if (playlistMark.markType == BLURAY_MARK_TYPE::ENTRY)
-    {
-      ChapterInformation chapterInformation{
-          .chapter = chapter, .start = start, .duration = playlistMark.duration};
-      playlistInformation.chapters.emplace_back(chapterInformation);
-      CLog::Log(LOGDEBUG, "Chapter {} start {} duration {} end {}", chapter,
-                fmt::format("{:%H:%M:%S}", start),
-                fmt::format("{:%H:%M:%S}", chapterInformation.duration),
-                fmt::format("{:%H:%M:%S}", start + chapterInformation.duration));
-      start += chapterInformation.duration;
-      ++chapter;
-    }
-  }
+  GetChaptersAndTimings(playlistInformation);
 
   return true;
 }
@@ -1061,19 +1072,20 @@ std::shared_ptr<CFileItem> GetFileItem(const CURL& url,
     }
     switch (video.coding)
     {
-      case ENCODING_TYPE::VIDEO_MPEG1:
+      using enum ENCODING_TYPE;
+      case VIDEO_MPEG1:
         videoInfo.codecName = "mpeg1";
         break;
-      case ENCODING_TYPE::VIDEO_MPEG2:
+      case VIDEO_MPEG2:
         videoInfo.codecName = "mpeg2";
         break;
-      case ENCODING_TYPE::VIDEO_VC1:
+      case VIDEO_VC1:
         videoInfo.codecName = "vc1";
         break;
-      case ENCODING_TYPE::VIDEO_H264:
+      case VIDEO_H264:
         videoInfo.codecName = "h264";
         break;
-      case ENCODING_TYPE::VIDEO_HEVC:
+      case VIDEO_HEVC:
         videoInfo.codecName = "hevc";
         break;
       default:
@@ -1082,10 +1094,11 @@ std::shared_ptr<CFileItem> GetFileItem(const CURL& url,
     }
     switch (video.aspect)
     {
-      case ASPECT_RATIO::RATIO_4_3:
+      using enum ASPECT_RATIO;
+      case RATIO_4_3:
         videoInfo.videoAspectRatio = 4.0f / 3.0f;
         break;
-      case ASPECT_RATIO::RATIO_16_9:
+      case RATIO_16_9:
         videoInfo.videoAspectRatio = 16.0f / 9.0f;
         break;
       default:
@@ -1099,7 +1112,7 @@ std::shared_ptr<CFileItem> GetFileItem(const CURL& url,
     videoInfo.fpsScale = 0; // Not in streamdetails
 
     CVideoInfoTag* info = item->GetVideoInfoTag();
-    info->m_streamDetails.SetStreams(videoInfo, static_cast<int>(title.duration.count() / 90000),
+    info->m_streamDetails.SetStreams(videoInfo, static_cast<int>(title.duration.count() / 1000),
                                      AudioStreamInfo{}, SubtitleStreamInfo{});
 
     for (const auto& audio : title.audioStreams)
@@ -1111,27 +1124,28 @@ std::shared_ptr<CFileItem> GetFileItem(const CURL& url,
 
       switch (audio.coding)
       {
-        case ENCODING_TYPE::AUDIO_AC3:
+        using enum ENCODING_TYPE;
+        case AUDIO_AC3:
           audioInfo.codecName = "ac3";
           break;
-        case ENCODING_TYPE::AUDIO_AC3PLUS:
-        case ENCODING_TYPE::AUDIO_AC3PLUS_SECONDARY:
+        case AUDIO_AC3PLUS:
+        case AUDIO_AC3PLUS_SECONDARY:
           audioInfo.codecName = "eac3";
           break;
-        case ENCODING_TYPE::AUDIO_LPCM:
+        case AUDIO_LPCM:
           audioInfo.codecName = "pcm";
           break;
-        case ENCODING_TYPE::AUDIO_DTS:
+        case AUDIO_DTS:
           audioInfo.codecName = "dts";
           break;
-        case ENCODING_TYPE::AUDIO_DTSHD:
-        case ENCODING_TYPE::AUDIO_DTSHD_SECONDARY:
+        case AUDIO_DTSHD:
+        case AUDIO_DTSHD_SECONDARY:
           audioInfo.codecName = "dtshd";
           break;
-        case ENCODING_TYPE::AUDIO_DTSHD_MASTER:
+        case AUDIO_DTSHD_MASTER:
           audioInfo.codecName = "dtshd_ma";
           break;
-        case ENCODING_TYPE::AUDIO_TRUHD:
+        case AUDIO_TRUHD:
           audioInfo.codecName = "truehd";
           break;
         default:
@@ -1200,7 +1214,10 @@ std::string GetCachePath(const CURL& url, const std::string& realPath)
   return path;
 }
 
-bool ReadMPLS(const CURL& url, unsigned int playlist, PlaylistInformation& playlistInformation)
+bool ReadMPLS(const CURL& url,
+              unsigned int playlist,
+              PlaylistInformation& playlistInformation,
+              std::map<unsigned int, ClipInformation>& clipCache)
 {
   const std::string path{url.GetHostName()};
   const std::string playlistFile{URIUtils::AddFileToFolder(
@@ -1216,14 +1233,15 @@ bool ReadMPLS(const CURL& url, unsigned int playlist, PlaylistInformation& playl
   file.Close();
 
   if (read == size)
-    return ParseMPLS(url, buffer, playlistInformation, playlist);
+    return ParseMPLS(url, buffer, playlistInformation, playlist, clipCache);
   return false;
 }
 
 bool GetPlaylistInfoFromDisc(const CURL& url,
                              const std::string& realPath,
                              unsigned int playlist,
-                             PlaylistInfo& playlistInfo)
+                             PlaylistInfo& playlistInfo,
+                             std::map<unsigned int, ClipInformation>& clipCache)
 {
   const std::string path{GetCachePath(url, realPath)};
 
@@ -1232,7 +1250,7 @@ bool GetPlaylistInfoFromDisc(const CURL& url,
   if (!CServiceBroker::GetBlurayDiscCache()->GetPlaylistInfo(path, playlist, p))
   {
     // Retrieve from disc
-    if (!ReadMPLS(url, playlist, p))
+    if (!ReadMPLS(url, playlist, p, clipCache))
       return false;
 
     // Cache and return
@@ -1257,43 +1275,44 @@ bool GetPlaylistInfoFromDisc(const CURL& url,
     {
       switch (stream.coding)
       {
-        case ENCODING_TYPE::VIDEO_MPEG1:
-        case ENCODING_TYPE::VIDEO_MPEG2:
-        case ENCODING_TYPE::VIDEO_VC1:
-        case ENCODING_TYPE::VIDEO_H264:
-        case ENCODING_TYPE::VIDEO_HEVC:
+        using enum ENCODING_TYPE;
+        case VIDEO_MPEG1:
+        case VIDEO_MPEG2:
+        case VIDEO_VC1:
+        case VIDEO_H264:
+        case VIDEO_HEVC:
           playlistInfo.videoStreams.emplace_back(DiscStreamInfo{.coding = stream.coding,
                                                                 .format = stream.format,
                                                                 .rate = stream.rate,
                                                                 .aspect = stream.aspect,
                                                                 .lang = ""});
           break;
-        case ENCODING_TYPE::AUDIO_MPEG1:
-        case ENCODING_TYPE::AUDIO_MPEG2:
-        case ENCODING_TYPE::AUDIO_LPCM:
-        case ENCODING_TYPE::AUDIO_AC3:
-        case ENCODING_TYPE::AUDIO_DTS:
-        case ENCODING_TYPE::AUDIO_TRUHD:
-        case ENCODING_TYPE::AUDIO_AC3PLUS:
-        case ENCODING_TYPE::AUDIO_DTSHD:
-        case ENCODING_TYPE::AUDIO_DTSHD_MASTER:
+        case AUDIO_MPEG1:
+        case AUDIO_MPEG2:
+        case AUDIO_LPCM:
+        case AUDIO_AC3:
+        case AUDIO_DTS:
+        case AUDIO_TRUHD:
+        case AUDIO_AC3PLUS:
+        case AUDIO_DTSHD:
+        case AUDIO_DTSHD_MASTER:
           playlistInfo.audioStreams.emplace_back(DiscStreamInfo{.coding = stream.coding,
                                                                 .format = stream.format,
                                                                 .rate = stream.rate,
                                                                 .aspect = stream.aspect,
                                                                 .lang = stream.language});
           break;
-        case ENCODING_TYPE::SUB_PG:
-        case ENCODING_TYPE::SUB_TEXT:
+        case SUB_PG:
+        case SUB_TEXT:
           playlistInfo.pgStreams.emplace_back(DiscStreamInfo{.coding = stream.coding,
                                                              .format = stream.format,
                                                              .rate = stream.rate,
                                                              .aspect = stream.aspect,
                                                              .lang = stream.language});
           break;
-        case ENCODING_TYPE::SUB_IG:
-        case ENCODING_TYPE::AUDIO_AC3PLUS_SECONDARY:
-        case ENCODING_TYPE::AUDIO_DTSHD_SECONDARY:
+        case SUB_IG:
+        case AUDIO_AC3PLUS_SECONDARY:
+        case AUDIO_DTSHD_SECONDARY:
         default:
           break;
       }
@@ -1302,12 +1321,60 @@ bool GetPlaylistInfoFromDisc(const CURL& url,
   return true;
 }
 
+bool GetPlaylistsFromDisc(const CURL& url,
+                          const std::string& realPath,
+                          int flags,
+                          std::vector<PlaylistInfo>& playlists,
+                          std::map<unsigned int, ClipInformation>& clipCache)
+{
+  const CURL url2{URIUtils::AddFileToFolder(url.GetHostName(), "BDMV", "PLAYLIST", "")};
+  CDirectory::CHints hints;
+  hints.flags = flags;
+  CFileItemList allTitles;
+  if (!CDirectory::GetDirectory(url2, allTitles, hints))
+    return false;
+
+  // Get information on all playlists
+  const std::regex playlistPath("(\\d{5}.mpls)");
+  std::smatch playlistMatch;
+  for (const auto& title : allTitles)
+  {
+    const CURL url3{title->GetPath()};
+    const std::string filename{URIUtils::GetFileName(url3.GetFileName())};
+    if (std::regex_search(filename, playlistMatch, playlistPath))
+    {
+      const unsigned int playlist{static_cast<unsigned int>(std::stoi(playlistMatch[0]))};
+
+      PlaylistInfo t{};
+      if (!GetPlaylistInfoFromDisc(url, realPath, playlist, t, clipCache))
+        CLog::LogF(LOGDEBUG, "Unable to get playlist {}", playlist);
+      else
+        playlists.emplace_back(t);
+    }
+  }
+  return true;
+}
+
+bool IncludePlaylist(GetTitles job,
+                     const PlaylistInfo& title,
+                     std::chrono::milliseconds minDuration,
+                     int mainPlaylist,
+                     unsigned int maxPlaylist)
+{
+  return job == GetTitles::GET_TITLES_ALL || job == GetTitles::GET_TITLES_EPISODES ||
+         (job == GetTitles::GET_TITLES_MAIN && title.duration >= minDuration) ||
+         (job == GetTitles::GET_TITLES_ONE &&
+          (title.playlist == static_cast<unsigned int>(mainPlaylist) ||
+           (mainPlaylist == -1 && title.playlist == maxPlaylist)));
+}
+
 bool GetPlaylists(const CURL& url,
                   const std::string& realPath,
                   int flags,
                   GetTitles job,
                   CFileItemList& items,
-                  SortTitles sort)
+                  SortTitles sort,
+                  std::map<unsigned int, ClipInformation>& clipCache)
 {
   std::vector<PlaylistInfo> playlists;
   int mainPlaylist{-1};
@@ -1320,41 +1387,15 @@ bool GetPlaylists(const CURL& url,
     {
       // Only main playlist is needed
       PlaylistInfo t{};
-      if (!GetPlaylistInfoFromDisc(url, realPath, mainPlaylist, t))
+      if (!GetPlaylistInfoFromDisc(url, realPath, mainPlaylist, t, clipCache))
         CLog::LogF(LOGDEBUG, "Unable to get playlist {}", mainPlaylist);
       else
         playlists.emplace_back(t);
     }
   }
 
-  if (playlists.empty())
-  {
-    const CURL url2{URIUtils::AddFileToFolder(url.GetHostName(), "BDMV", "PLAYLIST", "")};
-    CDirectory::CHints hints;
-    hints.flags = flags;
-    CFileItemList allTitles;
-    if (!CDirectory::GetDirectory(url2, allTitles, hints))
-      return false;
-
-    // Get information on all playlists
-    const std::regex playlistPath("(\\d{5}.mpls)");
-    std::smatch playlistMatch;
-    for (const auto& title : allTitles)
-    {
-      const CURL url3{title->GetPath()};
-      const std::string filename{URIUtils::GetFileName(url3.GetFileName())};
-      if (std::regex_search(filename, playlistMatch, playlistPath))
-      {
-        const unsigned int playlist{static_cast<unsigned int>(std::stoi(playlistMatch[0]))};
-
-        PlaylistInfo t{};
-        if (!GetPlaylistInfoFromDisc(url, realPath, playlist, t))
-          CLog::LogF(LOGDEBUG, "Unable to get playlist {}", playlist);
-        else
-          playlists.emplace_back(t);
-      }
-    }
-  }
+  if (playlists.empty() && !GetPlaylistsFromDisc(url, realPath, flags, playlists, clipCache))
+    return false;
 
   // Remove playlists with no clips
   std::erase_if(playlists, [](const PlaylistInfo& playlist) { return playlist.clips.empty(); });
@@ -1414,60 +1455,61 @@ bool GetPlaylists(const CURL& url,
     }
   }
 
-  if (!playlists.empty())
-  {
-    // Now we have curated playlists, find longest (for main title derivation)
-    const auto& it{std::ranges::max_element(playlists, {}, &PlaylistInfo::duration)};
+  // No playlists found
+  if (playlists.empty())
+    return false;
 
-    const std::chrono::milliseconds maxDuration{it->duration};
-    const unsigned int maxPlaylist{it->playlist};
+  // Now we have curated playlists, find longest (for main title derivation)
+  const auto& it{std::ranges::max_element(playlists, {}, &PlaylistInfo::duration)};
 
-    // Sort
-    // Movies - placing main title - if present - first, then by duration
-    // Episodes - by playlist number
-    if (sort != SortTitles::SORT_TITLES_NONE)
-    {
-      std::ranges::sort(playlists,
-                        [&sort](const PlaylistInfo& i, const PlaylistInfo& j)
+  const std::chrono::milliseconds maxDuration{it->duration};
+  const unsigned int maxPlaylist{it->playlist};
+
+  // Sort
+  // Movies - placing main title - if present - first, then by duration
+  // Episodes - by playlist number
+  if (sort != SortTitles::SORT_TITLES_NONE)
+  {
+    std::ranges::sort(playlists,
+                      [&sort](const PlaylistInfo& i, const PlaylistInfo& j)
+                      {
+                        if (sort == SortTitles::SORT_TITLES_MOVIE)
                         {
-                          if (sort == SortTitles::SORT_TITLES_MOVIE)
-                          {
-                            if (i.duration == j.duration)
-                              return i.playlist < j.playlist;
-                            return i.duration > j.duration;
-                          }
-                          return i.playlist < j.playlist;
-                        });
-
-      const auto& pivot{std::ranges::find_if(
-          playlists, [&mainPlaylist](const PlaylistInfo& title)
-          { return title.playlist == static_cast<unsigned int>(mainPlaylist); })};
-      if (pivot != playlists.end())
-        std::rotate(playlists.begin(), pivot, pivot + 1);
-    }
+                          if (i.duration == j.duration)
+                            return i.playlist < j.playlist;
+                          return i.duration > j.duration;
+                        }
+                        return i.playlist < j.playlist;
+                      });
+
+    const auto& pivot{std::ranges::find_if(
+        playlists, [&mainPlaylist](const PlaylistInfo& title)
+        { return title.playlist == static_cast<unsigned int>(mainPlaylist); })};
+    if (pivot != playlists.end())
+      std::rotate(playlists.begin(), pivot, pivot + 1);
+  }
 
-    const std::chrono::milliseconds minDuration{maxDuration * MAIN_TITLE_LENGTH_PERCENT / 100};
-    for (const auto& title : playlists)
+  const std::chrono::milliseconds minDuration{maxDuration * MAIN_TITLE_LENGTH_PERCENT / 100};
+  for (const auto& title : playlists)
+  {
+    if (IncludePlaylist(job, title, minDuration, mainPlaylist, maxPlaylist))
     {
-      if (job == GetTitles::GET_TITLES_ALL || job == GetTitles::GET_TITLES_EPISODES ||
-          (job == GetTitles::GET_TITLES_MAIN && title.duration >= minDuration) ||
-          (job == GetTitles::GET_TITLES_ONE &&
-           (title.playlist == static_cast<unsigned int>(mainPlaylist) ||
-            (mainPlaylist == -1 && title.playlist == maxPlaylist))))
-      {
-        items.Add(GetFileItem(url, title,
-                              title.playlist == static_cast<unsigned int>(mainPlaylist)
-                                  ? g_localizeStrings.Get(25004) /* Main Title */
-                                  : g_localizeStrings.Get(25005) /* Title */));
-      }
+      items.Add(GetFileItem(url, title,
+                            title.playlist == static_cast<unsigned int>(mainPlaylist)
+                                ? g_localizeStrings.Get(25004) /* Main Title */
+                                : g_localizeStrings.Get(25005) /* Title */));
     }
   }
 
   return !items.IsEmpty();
 }
 
-void GetPlaylistsInformation(
-    const CURL& url, const std::string& realPath, int flags, ClipMap& clips, PlaylistMap& playlists)
+void GetPlaylistsInformation(const CURL& url,
+                             const std::string& realPath,
+                             int flags,
+                             ClipMap& clips,
+                             PlaylistMap& playlists,
+                             std::map<unsigned int, ClipInformation>& clipCache)
 {
   // Check cache
   const std::string path{url.GetHostName()};
@@ -1481,7 +1523,7 @@ void GetPlaylistsInformation(
   // Sort by playlist for grouping later
   CFileItemList allTitles;
   GetPlaylists(url, realPath, flags, GetTitles::GET_TITLES_EPISODES, allTitles,
-               SortTitles::SORT_TITLES_EPISODE);
+               SortTitles::SORT_TITLES_EPISODE, clipCache);
 
   // Get information on all playlists
   // Including relationship between clips and playlists
@@ -1492,7 +1534,7 @@ void GetPlaylistsInformation(
   {
     const int playlist{title->GetProperty("bluray_playlist").asInteger32(0)};
     PlaylistInfo titleInfo;
-    if (!GetPlaylistInfoFromDisc(url, realPath, playlist, titleInfo))
+    if (!GetPlaylistInfoFromDisc(url, realPath, playlist, titleInfo, clipCache))
     {
       CLog::LogF(LOGDEBUG, "Unable to get playlist {}", playlist);
     }
@@ -1507,7 +1549,6 @@ void GetPlaylistsInformation(
       info.chapters = titleInfo.chapters;
 
       // Get clips
-      std::string clipsStr;
       for (const auto& clip : titleInfo.clips)
       {
         // Add clip to playlist
@@ -1528,25 +1569,19 @@ void GetPlaylistsInformation(
           // Additional reference to clip, add this playlist
           it->second.playlists.emplace_back(playlist);
         }
-
-        clipsStr += std::to_string(clip) + ",";
       }
-      if (!clipsStr.empty())
-        clipsStr.pop_back(); // Remove last ','
 
       playlists[playlist] = info;
 
       // Get languages
-      std::string langs;
-      for (const auto& audio : titleInfo.audioStreams)
-        langs += audio.lang + ",";
-      if (!langs.empty())
-        langs.pop_back(); // Remove last ','
+      const std::string langs{fmt::format(
+          "{}",
+          fmt::join(titleInfo.audioStreams | std::views::transform(&DiscStreamInfo::lang), ","))};
 
       playlists[playlist].languages = langs;
 
       CLog::LogF(LOGDEBUG, "Playlist {}, Duration {}, Langs {}, Clips {} ", playlist,
-                 title->GetVideoInfoTag()->GetDuration(), langs, clipsStr);
+                 title->GetVideoInfoTag()->GetDuration(), langs, fmt::join(titleInfo.clips, ","));
     }
   }
 
@@ -1554,12 +1589,8 @@ void GetPlaylistsInformation(
   for (const auto& c : clips)
   {
     const auto& [clip, clipInformation] = c;
-    std::string ps{StringUtils::Format("Clip {0:d} duration {1:d} - playlists ", clip,
-                                       clipInformation.duration.count() / 1000)};
-    for (const auto& playlist : clipInformation.playlists)
-      ps += std::to_string(playlist) + ",";
-    ps.pop_back(); // Remove last ','
-    CLog::LogF(LOGDEBUG, "{}", ps);
+    CLog::LogF(LOGDEBUG, "Clip {:d} duration {:d} - playlists {}", clip,
+               clipInformation.duration.count() / 1000, fmt::join(clipInformation.playlists, ","));
   }
 
   CLog::LogF(LOGDEBUG, "*** Playlist information End ***");
@@ -1570,6 +1601,11 @@ void GetPlaylistsInformation(
 }
 } // namespace
 
+CBlurayDirectory::CBlurayDirectory()
+{
+  m_clipCache.clear();
+}
+
 CBlurayDirectory::~CBlurayDirectory()
 {
   Dispose();
@@ -1620,12 +1656,12 @@ std::string CBlurayDirectory::GetBasePath(const CURL& url)
   return url2.Get(); // BDMV
 }
 
-std::string CBlurayDirectory::GetBlurayTitle()
+std::string CBlurayDirectory::GetBlurayTitle() const
 {
   return GetDiscInfoString(DiscInfo::TITLE);
 }
 
-std::string CBlurayDirectory::GetBlurayID()
+std::string CBlurayDirectory::GetBlurayID() const
 {
   return GetDiscInfoString(DiscInfo::ID);
 }
@@ -1688,14 +1724,14 @@ bool CBlurayDirectory::GetDirectory(const CURL& url, CFileItemList& items)
   if (file == "root")
   {
     GetPlaylists(url, m_realPath, m_flags, GetTitles::GET_TITLES_MAIN, items,
-                 SortTitles::SORT_TITLES_MOVIE);
+                 SortTitles::SORT_TITLES_MOVIE, m_clipCache);
     AddOptionsAndSort(m_url, items, m_blurayMenuSupport);
     return (items.Size() > 2);
   }
 
   if (file == "root/titles")
     return GetPlaylists(url, m_realPath, m_flags, GetTitles::GET_TITLES_ALL, items,
-                        SortTitles::SORT_TITLES_MOVIE);
+                        SortTitles::SORT_TITLES_MOVIE, m_clipCache);
 
   if (StringUtils::StartsWith(file, "root/episode"))
   {
@@ -1726,7 +1762,7 @@ bool CBlurayDirectory::GetDirectory(const CURL& url, CFileItemList& items)
     // Get playlist, clip and language information
     ClipMap clips;
     PlaylistMap playlists;
-    GetPlaylistsInformation(url, m_realPath, m_flags, clips, playlists);
+    GetPlaylistsInformation(url, m_realPath, m_flags, clips, playlists, m_clipCache);
 
     // Get episode playlists
     CDiscDirectoryHelper helper;
@@ -1735,7 +1771,7 @@ bool CBlurayDirectory::GetDirectory(const CURL& url, CFileItemList& items)
     // Heuristics failed so return all playlists
     if (items.IsEmpty())
       GetPlaylists(url, m_realPath, m_flags, GetTitles::GET_TITLES_EPISODES, items,
-                   SortTitles::SORT_TITLES_EPISODE);
+                   SortTitles::SORT_TITLES_EPISODE, m_clipCache);
 
     // Add all titles and menu options
     AddOptionsAndSort(m_url, items, m_blurayMenuSupport);
@@ -1787,8 +1823,7 @@ bool CBlurayDirectory::InitializeBluray(const std::string& root)
 
   m_realPath = root;
 
-  const auto fileHandler{CDirectoryFactory::Create(CURL{root})};
-  if (fileHandler)
+  if (const auto fileHandler{CDirectoryFactory::Create(CURL{root})}; fileHandler)
     m_realPath = fileHandler->ResolveMountPoint(root);
 
   if (!bd_open_files(m_bd, &m_realPath, CBlurayCallback::dir_open, CBlurayCallback::file_open))
@@ -1801,9 +1836,6 @@ bool CBlurayDirectory::InitializeBluray(const std::string& root)
   const BLURAY_DISC_INFO* discInfo{GetDiscInfo()};
   m_blurayMenuSupport = discInfo && !discInfo->no_menu_support;
 
-  // Clear local clip cache
-  lcl_clips.clear();
-
   return true;
 }
 
diff --git a/xbmc/filesystem/BlurayDirectory.h b/xbmc/filesystem/BlurayDirectory.h
index ccd9bf18d4eaf..0db38a6d3cfc5 100644
--- a/xbmc/filesystem/BlurayDirectory.h
+++ b/xbmc/filesystem/BlurayDirectory.h
@@ -187,15 +187,18 @@ struct PlaylistInformation
 class CBlurayDirectory : public IDirectory
 {
 public:
-  CBlurayDirectory() = default;
+  CBlurayDirectory();
   ~CBlurayDirectory() override;
+  CBlurayDirectory(const CBlurayDirectory&) = delete;
+  CBlurayDirectory& operator=(const CBlurayDirectory&) = delete;
+
   bool GetDirectory(const CURL& url, CFileItemList& items) override;
   bool Resolve(CFileItem& item) const override;
 
   bool InitializeBluray(const std::string &root);
   static std::string GetBasePath(const CURL& url);
-  std::string GetBlurayTitle();
-  std::string GetBlurayID();
+  std::string GetBlurayTitle() const;
+  std::string GetBlurayID() const;
 
 private:
   enum class DiscInfo : uint8_t
@@ -213,5 +216,7 @@ class CBlurayDirectory : public IDirectory
   BLURAY* m_bd{nullptr};
   bool m_blurayInitialized{false};
   bool m_blurayMenuSupport{false};
+
+  std::map<unsigned int, ClipInformation> m_clipCache;
 };
 } // namespace XFILE
diff --git a/xbmc/filesystem/BlurayDiscCache.cpp b/xbmc/filesystem/BlurayDiscCache.cpp
index 042ff3905bece..513d504fae7c0 100644
--- a/xbmc/filesystem/BlurayDiscCache.cpp
+++ b/xbmc/filesystem/BlurayDiscCache.cpp
@@ -16,22 +16,22 @@ using namespace XFILE;
 
 CacheMap::iterator CBlurayDiscCache::SetDisc(const std::string& path)
 {
-  std::unique_lock<CCriticalSection> lock(m_cs);
+  std::unique_lock lock(m_cs);
 
   // Get rid of any URL options, else the compare may be wrong
   std::string storedPath{CURL(path).GetWithoutOptions()};
   URIUtils::RemoveSlashAtEnd(storedPath);
 
   Disc disc;
-  const auto it{m_cache.emplace(storedPath, disc)};
-  return it.first;
+  const auto [it, _]{m_cache.try_emplace(storedPath, disc)};
+  return it;
 }
 
 void CBlurayDiscCache::SetPlaylistInfo(const std::string& path,
                                        unsigned int playlist,
                                        const PlaylistInformation& playlistInfo)
 {
-  std::unique_lock<CCriticalSection> lock(m_cs);
+  std::unique_lock lock(m_cs);
 
   // Get rid of any URL options, else the compare may be wrong
   std::string storedPath{CURL(path).GetWithoutOptions()};
@@ -48,7 +48,7 @@ void CBlurayDiscCache::SetMaps(const std::string& path,
                                const PlaylistMap& playlistmap,
                                const ClipMap& clipmap)
 {
-  std::unique_lock<CCriticalSection> lock(m_cs);
+  std::unique_lock lock(m_cs);
 
   // Get rid of any URL options, else the compare may be wrong
   std::string storedPath{CURL(path).GetWithoutOptions()};
@@ -67,14 +67,13 @@ bool CBlurayDiscCache::GetPlaylistInfo(const std::string& path,
                                        unsigned int playlist,
                                        PlaylistInformation& playlistInfo) const
 {
-  std::unique_lock<CCriticalSection> lock(m_cs);
+  std::unique_lock lock(m_cs);
 
   // Get rid of any URL options, else the compare may be wrong
   std::string storedPath{CURL(path).GetWithoutOptions()};
   URIUtils::RemoveSlashAtEnd(storedPath);
 
-  const auto& i{m_cache.find(storedPath)};
-  if (i != m_cache.end())
+  if (const auto& i{m_cache.find(storedPath)}; i != m_cache.end())
   {
     const auto& [_, disc] = *i;
     const auto& j{disc.m_playlists.find(playlist)};
@@ -92,14 +91,13 @@ bool CBlurayDiscCache::GetMaps(const std::string& path,
                                PlaylistMap& playlistmap,
                                ClipMap& clipmap) const
 {
-  std::unique_lock<CCriticalSection> lock(m_cs);
+  std::unique_lock lock(m_cs);
 
   // Get rid of any URL options, else the compare may be wrong
   std::string storedPath{CURL(path).GetWithoutOptions()};
   URIUtils::RemoveSlashAtEnd(storedPath);
 
-  const auto& i{m_cache.find(storedPath)};
-  if (i != m_cache.end())
+  if (const auto& i{m_cache.find(storedPath)}; i != m_cache.end())
   {
     const auto& [_, disc] = *i;
     if (disc.m_mapsSet)
@@ -114,7 +112,7 @@ bool CBlurayDiscCache::GetMaps(const std::string& path,
 
 void CBlurayDiscCache::ClearDisc(const std::string& path)
 {
-  std::unique_lock<CCriticalSection> lock(m_cs);
+  std::unique_lock lock(m_cs);
 
   // Get rid of any URL options, else the compare may be wrong
   std::string storedPath{CURL(path).GetWithoutOptions()};
@@ -125,6 +123,6 @@ void CBlurayDiscCache::ClearDisc(const std::string& path)
 
 void CBlurayDiscCache::Clear()
 {
-  std::unique_lock<CCriticalSection> lock(m_cs);
+  std::unique_lock lock(m_cs);
   m_cache.clear();
 }
diff --git a/xbmc/filesystem/BlurayDiscCache.h b/xbmc/filesystem/BlurayDiscCache.h
index 64e432af35718..a5d40a3854307 100644
--- a/xbmc/filesystem/BlurayDiscCache.h
+++ b/xbmc/filesystem/BlurayDiscCache.h
@@ -25,7 +25,7 @@ struct Disc
 };
 
 using CacheMapEntry = std::pair<std::string, Disc>;
-using CacheMap = std::map<std::string, Disc>;
+using CacheMap = std::map<std::string, Disc, std::less<>>;
 
 namespace XFILE
 {
@@ -34,6 +34,8 @@ class CBlurayDiscCache
 public:
   CBlurayDiscCache() = default;
   ~CBlurayDiscCache() = default;
+  CBlurayDiscCache(const CBlurayDiscCache&) = delete;
+  CBlurayDiscCache& operator=(const CBlurayDiscCache&) = delete;
   void Clear();
 
   CacheMap::iterator SetDisc(const std::string& path);
@@ -49,7 +51,7 @@ class CBlurayDiscCache
 
   void ClearDisc(const std::string& path);
 
-protected:
+private:
   CacheMap m_cache;
 
   mutable CCriticalSection m_cs;
diff --git a/xbmc/filesystem/DiscDirectoryHelper.cpp b/xbmc/filesystem/DiscDirectoryHelper.cpp
index 33d461e0e2ab2..06d3ac13be845 100644
--- a/xbmc/filesystem/DiscDirectoryHelper.cpp
+++ b/xbmc/filesystem/DiscDirectoryHelper.cpp
@@ -33,11 +33,6 @@ using PlaylistVectorEntry = std::pair<unsigned int, PlaylistInfo>;
 
 CDiscDirectoryHelper::CDiscDirectoryHelper()
 {
-  m_allEpisodes = AllEpisodes::SINGLE;
-  m_isSpecial = IsSpecial::EPISODE;
-  m_numEpisodes = 0;
-  m_numSpecials = 0;
-
   m_playAllPlaylists.clear();
   m_playAllPlaylistsMap.clear();
   m_groups.clear();
@@ -93,108 +88,110 @@ void CDiscDirectoryHelper::FindPlayAllPlaylists(const ClipMap& clips, const Play
   //      will have at most one other clip before/after
 
   // Only look for play all playlists if enough playlists and more than one episode on disc
-  if (m_numEpisodes > 1 && playlists.size() >= m_numEpisodes)
+  if (m_numEpisodes < 2 || playlists.size() < m_numEpisodes)
+    return;
+
+  for (const auto& [playlistNumber, playlistInformation] : playlists)
   {
-    for (const auto& playlist : playlists)
-    {
-      const auto& [playlistNumber, playlistInformation] = playlist;
+    // Find playlists that have a clip count = number of episodes on disc (+1/2) (1)
+    if (playlistInformation.clips.size() < m_numEpisodes ||
+        playlistInformation.clips.size() > m_numEpisodes + 2)
+      continue;
 
-      // Find playlists that have a clip count = number of episodes on disc (+1/2) (1)
-      if (playlistInformation.clips.size() >= m_numEpisodes &&
-          playlistInformation.clips.size() <= m_numEpisodes + 2)
-      {
-        bool allClipsQualify{true};
-        bool allowBeginningOrEnd{playlistInformation.clips.size() == m_numEpisodes + 1};
-        const bool allowBeginningAndEnd{playlistInformation.clips.size() == m_numEpisodes + 2};
+    bool allClipsQualify{true};
+    bool allowBeginningOrEnd{playlistInformation.clips.size() == m_numEpisodes + 1};
+    const bool allowBeginningAndEnd{playlistInformation.clips.size() == m_numEpisodes + 2};
 
-        // Map of clips in the play all playlist and the potential single episode playlists containing them
-        std::map<unsigned int, std::vector<unsigned int>> playAllPlaylistClipMap;
+    // Map of clips in the play all playlist and the potential single episode playlists containing them
+    std::map<unsigned int, std::vector<unsigned int>> playAllPlaylistClipMap;
 
-        // For each clip in the playlist
-        for (unsigned int clip : playlistInformation.clips)
+    // For each clip in the playlist
+    for (unsigned int clip : playlistInformation.clips)
+    {
+      if (!clips.contains(clip))
+      {
+        CLog::LogF(LOGERROR, "Clip {} missing in clip map", clip);
+        return;
+      }
+      // Map of potential single episode playlists containing the clip
+      std::vector<unsigned int> playAllPlaylistMap;
+
+      // If clip doesn't appear in another playlist or clip is too short this is not a Play All playlist (2)(3)
+      // BUT we allow first and/or last clip to be shorter (ie start intro/end credits) IF there are enough clips
+      const ClipInfo& clipInformation{clips.find(clip)->second};
+      bool skipClip{false};
+      if (clipInformation.playlists.size() < 2 || clipInformation.duration < MIN_EPISODE_DURATION)
+      {
+        if ((allowBeginningOrEnd || allowBeginningAndEnd) &&
+            clip == playlistInformation.clips.front())
+        {
+          // First clip
+          allowBeginningOrEnd = false; // Cannot have a short clip at end as well
+          skipClip = true;
+        }
+        else if ((allowBeginningOrEnd || allowBeginningAndEnd) &&
+                 clip == playlistInformation.clips.back())
         {
-          if (!clips.contains(clip))
+          // Last clip
+          skipClip = true;
+        }
+        else
+        {
+          allClipsQualify = false;
+          break;
+        }
+      }
+
+      // Now check the other playlists to ensure they contain the clip and have at most only one other clip before or after (4)
+      // (eg. starting recap/ending credits etc..)
+      if (!skipClip)
+      {
+        for (const auto& singleEpisodePlaylist : clipInformation.playlists)
+        {
+          // Exclude potential play all playlist we are currently examining
+          if (singleEpisodePlaylist == playlistNumber)
+            continue;
+
+          if (!playlists.contains(singleEpisodePlaylist))
           {
-            CLog::LogF(LOGERROR, "Clip {} missing in clip map", clip);
+            CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
+                       singleEpisodePlaylist);
             return;
           }
-          // Map of potential single episode playlists containing the clip
-          std::vector<unsigned int> playAllPlaylistMap;
-
-          // If clip doesn't appear in another playlist or clip is too short this is not a Play All playlist (2)(3)
-          // BUT we allow first and/or last clip to be shorter (ie start intro/end credits) IF there are enough clips
-          const ClipInfo& clipInformation{clips.find(clip)->second};
-          bool skipClip{false};
-          if (clipInformation.playlists.size() < 2 ||
-              clipInformation.duration < MIN_EPISODE_DURATION)
-          {
-            if ((allowBeginningOrEnd || allowBeginningAndEnd) &&
-                clip == playlistInformation.clips.front())
-            {
-              // First clip
-              allowBeginningOrEnd = false; // Cannot have a short clip at end as well
-              skipClip = true;
-            }
-            else if ((allowBeginningOrEnd || allowBeginningAndEnd) &&
-                     clip == playlistInformation.clips.back())
-            {
-              // Last clip
-              skipClip = true;
-            }
-            else
-            {
-              allClipsQualify = false;
-              break;
-            }
-          }
+          // Find the potential single episode playlist information
+          const auto& singleEpisodePlaylistInformation{
+              playlists.find(singleEpisodePlaylist)->second};
 
-          // Now check the other playlists to ensure they contain the clip and have at most only one other clip before or after (4)
-          // (eg. starting recap/ending credits etc..)
-          if (!skipClip)
+          // See if potential single episode playlist contains too many clips
+          // If there are 3 clips then expect the middle clip to be the main episode clip
+          if (singleEpisodePlaylistInformation.clips.size() > 3 ||
+              (singleEpisodePlaylistInformation.clips.size() == 3 &&
+               singleEpisodePlaylistInformation.clips[1] != clip))
           {
-            for (const auto& singleEpisodePlaylist : clipInformation.playlists)
-            {
-              if (singleEpisodePlaylist !=
-                  playlistNumber) // Exclude potential play all playlist we are currently examining
-              {
-                if (!playlists.contains(singleEpisodePlaylist))
-                {
-                  CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
-                             singleEpisodePlaylist);
-                  return;
-                }
-                // Find the potential single episode playlist information
-                const auto& singleEpisodePlaylistInformation{
-                    playlists.find(singleEpisodePlaylist)->second};
-
-                // See if potential single episode playlist contains too many clips
-                // If there are 3 clips then expect the middle clip to be the main episode clip
-                if (singleEpisodePlaylistInformation.clips.size() > 3 ||
-                    (singleEpisodePlaylistInformation.clips.size() == 3 &&
-                     singleEpisodePlaylistInformation.clips[1] != clip))
-                {
-                  allClipsQualify = false;
-                  break;
-                }
-                playAllPlaylistMap.emplace_back(singleEpisodePlaylist);
-              }
-            }
+            allClipsQualify = false;
+            break;
           }
-          playAllPlaylistClipMap[clip] = playAllPlaylistMap;
-        }
-
-        // Found potential play all playlist
-        if (allClipsQualify)
-        {
-          CLog::LogF(LOGDEBUG, "Potential play all playlist {}", playlistNumber);
-          m_playAllPlaylists.emplace(playlistNumber);
-          m_playAllPlaylistsMap[playlistNumber] = playAllPlaylistClipMap;
+          playAllPlaylistMap.emplace_back(singleEpisodePlaylist);
         }
       }
+      playAllPlaylistClipMap[clip] = playAllPlaylistMap;
+    }
+
+    // Found potential play all playlist
+    if (allClipsQualify)
+    {
+      CLog::LogF(LOGDEBUG, "Potential play all playlist {}", playlistNumber);
+      m_playAllPlaylists.insert(
+          CandidatePlaylistInformation{.playlist = playlistNumber,
+                                       .duration = playlistInformation.duration,
+                                       .clips = playlistInformation.clips,
+                                       .languages = playlistInformation.languages});
+      m_playAllPlaylistsMap[playlistNumber] = playAllPlaylistClipMap;
     }
-    if (m_playAllPlaylists.empty())
-      CLog::LogF(LOGDEBUG, "No play all playlists found");
   }
+
+  if (m_playAllPlaylists.empty())
+    CLog::LogF(LOGDEBUG, "No play all playlists found");
 }
 
 void CDiscDirectoryHelper::FindGroups(const PlaylistMap& playlists)
@@ -210,37 +207,48 @@ void CDiscDirectoryHelper::FindGroups(const PlaylistMap& playlists)
                        [&](const PlaylistMapEntry& p)
                        {
                          const auto& [playlist, playlistInformation] = p;
+
+                         const auto playAllPlaylistNumbers{
+                             m_playAllPlaylists |
+                             std::views::transform(&CandidatePlaylistInformation::playlist)};
+
                          return playlistInformation.duration >= MIN_EPISODE_DURATION &&
-                                !m_playAllPlaylists.contains(playlist);
+                                std::ranges::find(playAllPlaylistNumbers, playlist) ==
+                                    playAllPlaylistNumbers.end();
                        });
 
   // Find groups
   if (m_numEpisodes > 1)
   {
-    m_groups.emplace_back(1, longPlaylists.begin()->first);
-    std::for_each(++longPlaylists.begin(), longPlaylists.end(),
-                  [&](const PlaylistMapEntry& p)
-                  {
-                    const unsigned int playlist = p.first;
-                    if (m_groups.back().back() == playlist - 1)
-                      m_groups.back().emplace_back(playlist);
-                    else
-                      m_groups.emplace_back(1, playlist); // New group
-                  });
-
-    // See if any groups have at least numEpisode playlists
-    if (std::erase_if(m_groups, [&](const std::vector<unsigned int>& group)
-                      { return group.size() < m_numEpisodes; }) == 0)
+    for (const auto& [playlist, playlistInformation] : longPlaylists)
     {
-      // See if there are exactly numEpisode playlists remaining and no specials, in which case make a group
-      // Assumption has to be playlists match episodes in ascending order
-      if (m_numSpecials == 0 && longPlaylists.size() == m_numEpisodes)
+      CandidatePlaylistInformation groupPlaylist{.playlist = playlist,
+                                                 .duration = playlistInformation.duration,
+                                                 .clips = {},
+                                                 .languages = {}};
+      if (!m_groups.empty() && m_groups.back().back() == playlist - 1)
       {
-        m_groups.clear();
-        m_groups.emplace_back(1, longPlaylists.begin()->first);
-        std::for_each(std::next(longPlaylists.begin()), longPlaylists.end(),
-                      [&](const PlaylistMapEntry& p) { m_groups.back().emplace_back(p.first); });
+        m_groups.back().emplace_back(playlist);
+        m_allGroups.back().emplace_back(groupPlaylist);
       }
+      else
+      {
+        // New group
+        m_groups.emplace_back(std::vector{playlist});
+        m_allGroups.emplace_back(std::vector{groupPlaylist});
+      }
+    }
+
+    // See if any groups have at least numEpisode playlists and there are exactly
+    // numEpisode playlists remaining and no specials, in which case make a group.
+    // Assumption has to be playlists match episodes in ascending order
+    if (std::erase_if(m_groups, [this](const std::vector<unsigned int>& group)
+                      { return group.size() < m_numEpisodes; }) == 0 &&
+        m_numSpecials == 0 && longPlaylists.size() == m_numEpisodes)
+    {
+      m_groups.clear();
+      const auto keys{std::views::keys(longPlaylists)};
+      m_groups.emplace_back(keys.begin(), keys.end());
     }
 
     if (m_groups.empty())
@@ -255,267 +263,266 @@ void CDiscDirectoryHelper::FindGroups(const PlaylistMap& playlists)
   }
 }
 
-void CDiscDirectoryHelper::FindCandidatePlaylists(const std::vector<CVideoInfoTag>& episodesOnDisc,
-                                                  unsigned int episodeIndex,
-                                                  const PlaylistMap& playlists)
+void CDiscDirectoryHelper::UsePlayAllPlaylistMethod(unsigned int episodeIndex,
+                                                    const PlaylistMap& playlists)
 {
-  // At this stage we have a number of ways of trying to determine the correct playlist for an episode
-  //
-  // 1) Using a 'Play All' playlist, if present.
-  //
-  // 2a) Using the longest playlists that are consecutive, using the playlist in the nth position.
-  //
-  // 2b) For single episode discs, look for the longest playlist.
-  //    There are some discs where there are extras that are longer than the episode itself, in n this case
-  //    we look for the playlist with a common starting number (eg. 1, 800 etc..)
+  CLog::LogF(LOGDEBUG, "Using Play All playlist method");
 
-  bool foundEpisode{false};
-  bool findIdenticalEpisodes{false};
+  // Get the playlist
+  const auto& playlistInformation{*m_playAllPlaylists.begin()};
+  const unsigned int playAllPlaylist{playlistInformation.playlist};
+  CLog::LogF(LOGDEBUG, "Using candidate play all playlist {} duration {}", playAllPlaylist,
+             static_cast<int>(playlistInformation.duration.count() / 1000));
 
-  if (m_allEpisodes == AllEpisodes::ALL)
-    CLog::LogF(LOGDEBUG, "Looking for all episodes on disc");
-
-  // Method 1 - Play all playlist
-  if (m_playAllPlaylists.size() == 1)
+  // Find the clip for the episode(s)
+  unsigned int i{0};
+  for (const auto& clip : playlistInformation.clips)
   {
-    CLog::LogF(LOGDEBUG, "Using Play All playlist method");
-
-    // Get the playlist
-    const unsigned int playAllPlaylist{*m_playAllPlaylists.begin()};
-
-    if (!playlists.contains(playAllPlaylist))
+    if (m_allEpisodes == AllEpisodes::ALL ||
+        i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
     {
-      CLog::LogF(LOGERROR, "Play all playlist {} missing in playlist map", playAllPlaylist);
-      return;
-    }
+      if (!m_playAllPlaylistsMap[playAllPlaylist].contains(clip))
+      {
+        CLog::LogF(LOGERROR, "Clip {} missing in play all playlist map", clip);
+        return;
+      }
 
-    // Get the potential single episodes from the map
-    const auto& [playlist, playlistInformation] = *playlists.find(playAllPlaylist);
-    CLog::LogF(LOGDEBUG, "Using candidate play all playlist {} duration {}", playlist,
-               static_cast<int>(playlistInformation.duration.count() / 1000));
+      CLog::LogF(LOGDEBUG, "Clip is {}", clip);
 
-    // Find the clip for the episode(s)
-    unsigned int i{0};
-    for (const auto& clip : playlistInformation.clips)
-    {
-      if (m_allEpisodes == AllEpisodes::ALL ||
-          i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
+      // Find playlist(s) with that clip from map populated earlier
+      const auto& singleEpisodePlaylists{m_playAllPlaylistsMap[playAllPlaylist].find(clip)->second};
+
+      for (const auto& singleEpisodePlaylist : singleEpisodePlaylists)
       {
-        if (!m_playAllPlaylistsMap[playAllPlaylist].contains(clip))
+        if (!playlists.contains(singleEpisodePlaylist))
         {
-          CLog::LogF(LOGERROR, "Clip {} missing in play all playlist map", clip);
+          CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
+                     singleEpisodePlaylist);
           return;
         }
+        // Get playlist information
+        const PlaylistInfo& singleEpisodePlaylistInformation{
+            playlists.find(singleEpisodePlaylist)->second};
+
+        CLog::LogF(LOGDEBUG, "Candidate playlist {} duration {}", singleEpisodePlaylist,
+                   static_cast<int>(singleEpisodePlaylistInformation.duration.count() / 1000));
+
+        m_candidatePlaylists.emplace(
+            singleEpisodePlaylist,
+            CandidatePlaylistInformation{
+                .playlist = singleEpisodePlaylist,
+                .index = i + m_numSpecials,
+                .duration = singleEpisodePlaylistInformation.duration,
+                .clips = singleEpisodePlaylistInformation.clips,
+                .languages = singleEpisodePlaylistInformation
+                                 .languages}); // Also save episodeIndex for all episodes
+      }
+    }
+    ++i;
+  }
+}
 
-        CLog::LogF(LOGDEBUG, "Clip is {}", clip);
+void CDiscDirectoryHelper::UseLongOrCommonMethodForSingleEpisode(unsigned int episodeIndex,
+                                                                 const PlaylistMap& playlists)
+{
+  // Method 2b - Need to think about the special case of only one episode on disc
 
-        // Find playlist(s) with that clip from map populated earlier
-        const auto& singleEpisodePlaylists{
-            m_playAllPlaylistsMap[playAllPlaylist].find(clip)->second};
+  // Sort playlists by length
+  PlaylistVector playlists_length;
+  playlists_length.reserve(playlists.size());
+  playlists_length.assign(playlists.begin(), playlists.end());
+  std::ranges::sort(playlists_length,
+                    [](const PlaylistVectorEntry& i, const PlaylistVectorEntry& j)
+                    {
+                      const auto& [i_playlist, i_playlistInformation] = i;
+                      const auto& [j_playlist, j_playlistInformation] = j;
+                      if (i_playlistInformation.duration == j_playlistInformation.duration)
+                        return i_playlist < j_playlist;
+                      return i_playlistInformation.duration > j_playlistInformation.duration;
+                    });
 
-        for (const auto& singleEpisodePlaylist : singleEpisodePlaylists)
-        {
-          if (!playlists.contains(singleEpisodePlaylist))
-          {
-            CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
-                       singleEpisodePlaylist);
-            return;
-          }
-          // Get playlist information
-          const PlaylistInfo& singleEpisodePlaylistInformation{
-              playlists.find(singleEpisodePlaylist)->second};
+  // Remove duplicate lengths (each episode may have more than one playlist with different languages)
+  const auto& [first, last] = std::ranges::unique(
+      playlists_length, {}, [](const PlaylistVectorEntry& i) { return i.second.duration; });
+  playlists_length.erase(first, last);
 
-          CLog::LogF(LOGDEBUG, "Candidate playlist {} duration {}", singleEpisodePlaylist,
-                     static_cast<int>(singleEpisodePlaylistInformation.duration.count() / 1000));
+  // See how many unique (different length) episode length(>= MIN_EPISODE_LENGTH) playlists there are
+  auto episodeLengthPlaylists{
+      playlists_length | std::views::filter([](const PlaylistVectorEntry& p)
+                                            { return p.second.duration >= MIN_EPISODE_DURATION; })};
 
-          m_candidatePlaylists.insert(
-              {singleEpisodePlaylist,
-               i + m_numSpecials}); // Also save episodeIndex for all episodes
-        }
-      }
-      ++i;
-    }
-    foundEpisode = true;
-    findIdenticalEpisodes = true;
+  // Look for common starting playlists
+  constexpr std::array<unsigned int, 5> commonStartingPlaylists = {801, 800, 1, 811, 0};
+  const auto& playlistsView{playlists | std::views::keys};
+  const auto commonPlaylist{std::ranges::find_first_of(playlistsView, commonStartingPlaylists)};
+
+  if (std::ranges::distance(episodeLengthPlaylists) == 1)
+  {
+    // If only one long playlist, then assume it's that
+    const auto& [playlist, playlistInformation] = *episodeLengthPlaylists.begin();
+    CLog::LogF(LOGDEBUG, "Single Episode - found using single long playlist method");
+    CLog::LogF(LOGDEBUG, "Candidate playlist {}", playlist);
+    m_candidatePlaylists.emplace(
+        playlist, CandidatePlaylistInformation{.playlist = playlist,
+                                               .index = episodeIndex,
+                                               .duration = playlistInformation.duration,
+                                               .clips = playlistInformation.clips,
+                                               .languages = playlistInformation.languages});
   }
+  else if (commonPlaylist != playlistsView.end())
+  {
+    // Found a common playlist, so assume it's that
+    const auto& playlistInformation{playlists.at(*commonPlaylist)};
+    CLog::LogF(LOGDEBUG, "Single Episode - found using common playlist method");
+    CLog::LogF(LOGDEBUG, "Candidate playlist {}", *commonPlaylist);
+    m_candidatePlaylists.emplace(
+        *commonPlaylist, CandidatePlaylistInformation{.playlist = *commonPlaylist,
+                                                      .index = episodeIndex,
+                                                      .duration = playlistInformation.duration,
+                                                      .clips = playlistInformation.clips,
+                                                      .languages = playlistInformation.languages});
+  }
+}
 
-  // Method 2 - Look for the longest playlists in groups, taking the nth playlist
-  if (!foundEpisode)
+void CDiscDirectoryHelper::UseGroupMethod(unsigned int episodeIndex, const PlaylistMap& playlists)
+{
+  // Method 2a - More than one episode on disc
+
+  // Use groups and find nth playlist (or all for all episodes) in group
+  // Groups are already contain at least numEpisodes playlists of minimum duration
+  for (const auto& group : m_groups)
   {
-    CLog::LogF(LOGDEBUG, "Using longest playlists in groups method");
+    if (group.size() != m_numEpisodes)
+      continue;
 
-    if (m_numEpisodes == 1)
+    for (unsigned int i = 0; i < m_numEpisodes; ++i)
     {
-      // Method 2b - Need to think about the special case of only one episode on disc
-
-      // Sort playlists by length
-      PlaylistVector playlists_length;
-      playlists_length.reserve(playlists.size());
-      playlists_length.assign(playlists.begin(), playlists.end());
-      std::ranges::sort(playlists_length,
-                        [](const PlaylistVectorEntry& i, const PlaylistVectorEntry& j)
-                        {
-                          const auto& [i_playlist, i_playlistInformation] = i;
-                          const auto& [j_playlist, j_playlistInformation] = j;
-                          if (i_playlistInformation.duration == j_playlistInformation.duration)
-                            return i_playlist < j_playlist;
-                          return i_playlistInformation.duration > j_playlistInformation.duration;
-                        });
-
-      // Remove duplicate lengths (each episode may have more than one playlist with different languages)
-      const auto& [first, last] = std::ranges::unique(
-          playlists_length, {}, [](const PlaylistVectorEntry& i) { return i.second.duration; });
-      playlists_length.erase(first, last);
-
-      // See how many unique (different length) episode length(>= MIN_EPISODE_LENGTH) playlists there are
-      auto episodeLengthPlaylists{
-          playlists_length |
-          std::views::filter([](const PlaylistVectorEntry& p)
-                             { return p.second.duration >= MIN_EPISODE_DURATION; })};
-
-      // Look for common starting playlists
-      constexpr std::array<unsigned int, 5> commonStartingPlaylists = {801, 800, 1, 811, 0};
-      const auto& playlistsView{playlists | std::views::keys};
-      const auto commonPlaylist{std::ranges::find_first_of(playlistsView, commonStartingPlaylists)};
-
-      if (std::ranges::distance(episodeLengthPlaylists) == 1)
-      {
-        // If only one long playlist, then assume it's that
-        const unsigned int playlist{episodeLengthPlaylists.begin()->first};
-        CLog::LogF(LOGDEBUG, "Single Episode - found using single long playlist method");
-        CLog::LogF(LOGDEBUG, "Candidate playlist {}", playlist);
-        m_candidatePlaylists.insert({playlist, episodeIndex});
-      }
-      else if (commonPlaylist != playlistsView.end())
-      {
-        // Found a common playlist, so assume it's that
-        CLog::LogF(LOGDEBUG, "Single Episode - found using common playlist method");
-        CLog::LogF(LOGDEBUG, "Candidate playlist {}", *commonPlaylist);
-        m_candidatePlaylists.insert({*commonPlaylist, episodeIndex});
-      }
-    }
-    else
-    {
-      // Method 2a - More than one episode on disc
-
-      // Use groups and find nth playlist (or all for all episodes) in group
-      // Groups are already contain at least numEpisodes playlists of minimum duration
-      if (!m_groups.empty())
-      {
-        for (const auto& group : m_groups)
-        {
-          if (group.size() == m_numEpisodes)
-          {
-            for (unsigned int i = 0; i < m_numEpisodes; ++i)
-            {
-              if (m_allEpisodes == AllEpisodes::ALL ||
-                  i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
-              {
-                m_candidatePlaylists.insert(
-                    {group[i], i + m_numSpecials}); // Also save episodeIndex for all episodes
-                CLog::LogF(LOGDEBUG, "Candidate playlist {}", group[i]);
-              }
-            }
-          }
-        }
-        findIdenticalEpisodes = true;
-      }
+      if (m_allEpisodes != AllEpisodes::ALL &&
+          i != episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
+        continue;
+
+      const auto& playlistInformation{playlists.at(group[i])};
+      m_candidatePlaylists.emplace(
+          group[i],
+          CandidatePlaylistInformation{
+              .playlist = group[i],
+              .index = i + m_numSpecials,
+              .duration = playlistInformation.duration,
+              .clips = playlistInformation.clips,
+              .languages =
+                  playlistInformation.languages}); // Also save episodeIndex for all episodes
+      CLog::LogF(LOGDEBUG, "Candidate playlist {}", group[i]);
     }
   }
+}
 
-  // Now deal with the possibility there may be more than one playlist (per episode)
-  // For this, see which is closest in duration to the desired episode duration (from the scraper)
-  // If we are looking for a special then leave all potential episode playlists in array
-  if (m_candidatePlaylists.size() > 1 && m_isSpecial == IsSpecial::EPISODE)
+void CDiscDirectoryHelper::ChooseSingleBestPlaylist(
+    const std::vector<CVideoInfoTag>& episodesOnDisc, const PlaylistMap& playlists)
+{
+  // Rebuild candidatePlaylists
+  auto oldCandidatePlaylists{std::move(m_candidatePlaylists)};
+  m_candidatePlaylists.clear();
+
+  // Loop through each episode (in case of all episodes)
+  // Generate set of indexes of episode entry in episodesOnDisc
+  const auto indexView{oldCandidatePlaylists | std::views::values |
+                       std::views::transform(&CandidatePlaylistInformation::index)};
+  std::set<unsigned int> indexes(indexView.begin(), indexView.end());
+
+  // Loop through each index (episode) and find the playlist with the closest duration
+  for (unsigned int currentEpisodeIndex : indexes)
   {
-    // Rebuild candidatePlaylists
-    const auto oldCandidatePlaylists{m_candidatePlaylists};
-    m_candidatePlaylists.clear();
+    std::chrono::milliseconds duration{episodesOnDisc[currentEpisodeIndex].GetDuration() * 1000};
 
-    // Loop through each episode (in case of all episodes)
-    // Generate set of indexes of episode entry in episodesOnDisc
-    std::set<unsigned int> indexes;
-    for (unsigned int index : oldCandidatePlaylists | std::views::values)
-      indexes.insert(index);
+    // If episode length not known, ensure the longest playlist selected
+    if (duration == 0ms)
+      duration = std::chrono::milliseconds::max();
 
-    for (unsigned int currentEpisodeIndex : indexes)
-    {
-      std::chrono::milliseconds duration{episodesOnDisc[currentEpisodeIndex].GetDuration() * 1000};
-      if (duration == 0ms)
-        duration = std::chrono::milliseconds::
-            max(); // If episode length not known, ensure the longest playlist selected
-
-      // Create vector of candidate playlists, duration difference and chapters
-      std::vector<CandidatePlaylistsDurationInformation> candidatePlaylistsDuration;
-      candidatePlaylistsDuration.reserve(oldCandidatePlaylists.size());
-      for (const auto& [playlist, index] : oldCandidatePlaylists)
-      {
-        if (index == currentEpisodeIndex)
-        {
-          if (!playlists.contains(playlist))
-          {
-            CLog::LogF(LOGERROR, "Playlist {} missing in playlist map", playlist);
-            return;
-          }
-          const auto& playlistInformation{playlists.find(playlist)->second};
-          candidatePlaylistsDuration.emplace_back(CandidatePlaylistsDurationInformation{
-              .playlist = playlist,
-              .durationDelta = abs(playlistInformation.duration - duration),
-              .chapters = static_cast<unsigned int>(playlistInformation.chapters.size())});
-        }
-      }
+    auto filter{oldCandidatePlaylists |
+                std::views::filter([currentEpisodeIndex](const auto& p)
+                                   { return p.second.index == currentEpisodeIndex; }) |
+                std::views::values};
+    auto filteredCandidatePlaylists{std::vector(filter.begin(), filter.end())};
 
-      // Sort descending based on number of chapters and ascending by relative difference in duration
-      std::ranges::sort(candidatePlaylistsDuration,
-                        [](const auto& p, const auto& q)
-                        {
-                          if (p.chapters == q.chapters)
-                            return p.durationDelta < q.durationDelta;
-                          return p.chapters > q.chapters;
-                        });
-
-      // Keep the playlist with most chapters and closest duration
-      const unsigned int playlist{candidatePlaylistsDuration[0].playlist};
-      m_candidatePlaylists.insert({playlist, currentEpisodeIndex});
-
-      CLog::LogF(LOGDEBUG,
-                 "Remaining candidate playlist (closest in duration) is {} for episode index {}",
-                 playlist, currentEpisodeIndex);
-    }
+    for (auto& playlistInformation : filteredCandidatePlaylists)
+      playlistInformation.durationDelta = abs(playlistInformation.duration - duration);
+
+    // Sort descending based on number of chapters and ascending by relative difference in duration
+    std::ranges::sort(
+        filteredCandidatePlaylists,
+        [](const CandidatePlaylistInformation& p, const CandidatePlaylistInformation& q)
+        {
+          if (p.chapters == q.chapters)
+            return p.durationDelta < q.durationDelta;
+          return p.chapters > q.chapters;
+        });
+
+    // Keep the playlist with most chapters and closest duration
+    const unsigned int playlist{filteredCandidatePlaylists[0].playlist};
+    m_candidatePlaylists.emplace(playlist, filteredCandidatePlaylists[0]);
+
+    CLog::LogF(LOGDEBUG,
+               "Remaining candidate playlist (closest in duration) is {} for episode index {}",
+               playlist, currentEpisodeIndex);
   }
+}
 
-  // candidatePlaylists should now (ideally) contain one candidate title for the episode or all episodes
-  // Now look at durations of found playlist and add identical (in case language options)
-  if (findIdenticalEpisodes && !m_candidatePlaylists.empty())
+void CDiscDirectoryHelper::AddIdenticalPlaylists(const PlaylistMap& playlists)
+{
+  for (const auto& [candidatePlaylist, candidatePlaylistInformation] : m_candidatePlaylists)
   {
-    for (const auto& [candidatePlaylist, candidatePlaylistEpisodeIndex] : m_candidatePlaylists)
+    // Find all other playlists of same duration with same clips
+    for (const auto& [playlist, playlistInformation] : playlists)
     {
-      if (!playlists.contains(candidatePlaylist))
-      {
-        CLog::LogF(LOGERROR, "Candidate playlist {} missing in playlist map", candidatePlaylist);
-        return;
-      }
-      // Get candidatePlaylist clips and duration
-      const auto& [candidatePlaylistNumber, candidatePlaylistInformation] =
-          *playlists.find(candidatePlaylist);
-
-      // Find all other playlists of same duration with same clips
-      for (const auto& [playlist, playlistInformation] : playlists)
+      if (playlist != candidatePlaylist &&
+          candidatePlaylistInformation.duration == playlistInformation.duration &&
+          candidatePlaylistInformation.languages != playlistInformation.languages &&
+          candidatePlaylistInformation.clips == playlistInformation.clips)
       {
-        if (playlist != candidatePlaylist &&
-            candidatePlaylistInformation.duration == playlistInformation.duration &&
-            candidatePlaylistInformation.languages != playlistInformation.languages &&
-            candidatePlaylistInformation.clips == playlistInformation.clips)
-        {
-          CLog::LogF(LOGDEBUG, "Adding playlist {} as same duration and clips as playlist {}",
-                     playlist, candidatePlaylist);
-          m_candidatePlaylists.insert({playlist, candidatePlaylistEpisodeIndex});
-        }
+        CLog::LogF(LOGDEBUG, "Adding playlist {} as same duration and clips as playlist {}",
+                   playlist, candidatePlaylist);
+        m_candidatePlaylists.emplace(playlist, candidatePlaylistInformation);
       }
     }
   }
 }
 
+void CDiscDirectoryHelper::FindCandidatePlaylists(const std::vector<CVideoInfoTag>& episodesOnDisc,
+                                                  unsigned int episodeIndex,
+                                                  const PlaylistMap& playlists)
+{
+  // At this stage we have a number of ways of trying to determine the correct playlist for an episode
+  //
+  // 1) Using a 'Play All' playlist, if present.
+  //
+  // 2a) Using the longest playlists that are consecutive, using the playlist in the nth position.
+  //
+  // 2b) For single episode discs, look for the longest playlist.
+  //    There are some discs where there are extras that are longer than the episode itself, in n this case
+  //    we look for the playlist with a common starting number (eg. 1, 800 etc..)
+
+  if (m_allEpisodes == AllEpisodes::ALL)
+    CLog::LogF(LOGDEBUG, "Looking for all episodes on disc");
+
+  if (m_playAllPlaylists.size() == 1)
+    UsePlayAllPlaylistMethod(episodeIndex, playlists);
+  else if (m_numEpisodes == 1)
+    UseLongOrCommonMethodForSingleEpisode(episodeIndex, playlists);
+  else if (!m_groups.empty())
+    UseGroupMethod(episodeIndex, playlists);
+
+  // Now deal with the possibility there may be more than one playlist (per episode)
+  // For this, see which is closest in duration to the desired episode duration (from the scraper)
+  // If we are looking for a special then leave all potential episode playlists in array
+  if (m_candidatePlaylists.size() > 1 && m_isSpecial == IsSpecial::EPISODE)
+    ChooseSingleBestPlaylist(episodesOnDisc, playlists);
+
+  // candidatePlaylists should now (ideally) contain one candidate title for the episode or all episodes
+  // Now look at durations of found playlist and add identical (in case language options)
+  if (!m_candidatePlaylists.empty())
+    AddIdenticalPlaylists(playlists);
+}
+
 void CDiscDirectoryHelper::FindSpecials(const PlaylistMap& playlists)
 {
   if (m_numSpecials == 0)
@@ -532,17 +539,25 @@ void CDiscDirectoryHelper::FindSpecials(const PlaylistMap& playlists)
   playlistsLength.assign(playlists.begin(), playlists.end());
   if (m_numEpisodes > 0)
   {
-    std::erase_if(playlistsLength,
-                  [&](const PlaylistVectorEntry& playlist)
-                  {
-                    const auto& [playlistNumber, playlistInformation] = playlist;
+    std::erase_if(
+        playlistsLength,
+        [this](const PlaylistVectorEntry& playlist)
+        {
+          const auto& [playlistNumber, playlistInformation] = playlist;
+
+          const bool isShort{playlistInformation.duration < MIN_SPECIAL_DURATION};
 
-                    const auto isShort{playlistInformation.duration < MIN_SPECIAL_DURATION};
-                    const auto isEpisode{m_candidatePlaylists.contains(playlistNumber)};
-                    const auto isPlayAll{m_playAllPlaylists.contains(playlistNumber)};
+          const auto candidatePlaylistNumbers{m_candidatePlaylists | std::views::keys};
+          const bool isEpisode{std::ranges::find(candidatePlaylistNumbers, playlistNumber) !=
+                               candidatePlaylistNumbers.end()};
 
-                    return isShort || isEpisode || isPlayAll;
-                  });
+          const auto playAllPlaylistNumbers{
+              m_playAllPlaylists | std::views::transform(&CandidatePlaylistInformation::playlist)};
+          const bool isPlayAll{std::ranges::find(playAllPlaylistNumbers, playlistNumber) !=
+                               playAllPlaylistNumbers.end()};
+
+          return isShort || isEpisode || isPlayAll;
+        });
   }
 
   // Sort playlists by length
@@ -619,7 +634,7 @@ void CDiscDirectoryHelper::GenerateItem(const CURL& url,
   item->SetArt("icon", "DefaultVideo.png");
 }
 
-void CDiscDirectoryHelper::EndPlaylistSearch()
+void CDiscDirectoryHelper::EndPlaylistSearch() const
 {
   CLog::LogF(LOGDEBUG, "*** Episode Search End ***");
 }
@@ -628,7 +643,7 @@ void CDiscDirectoryHelper::PopulateFileItems(const CURL& url,
                                              CFileItemList& items,
                                              int episodeIndex,
                                              const std::vector<CVideoInfoTag>& episodesOnDisc,
-                                             const PlaylistMap& playlists)
+                                             const PlaylistMap& playlists) const
 {
   // Now populate CFileItemList to return
   //
@@ -643,36 +658,27 @@ void CDiscDirectoryHelper::PopulateFileItems(const CURL& url,
   if (m_isSpecial == IsSpecial::EPISODE)
   {
     // Sort by index, number of languages and playlist
-    std::vector<SortedPlaylistsInformation> sortedPlaylists;
-    sortedPlaylists.reserve(m_candidatePlaylists.size());
-    for (const auto& [playlist, index] : m_candidatePlaylists)
-    {
-      if (!playlists.contains(playlist))
-      {
-        CLog::LogF(LOGERROR, "Playlist {} missing in playlist map", playlist);
-        return;
-      }
-      sortedPlaylists.emplace_back(
-          SortedPlaylistsInformation{.playlist = playlist,
-                                     .index = index,
-                                     .languages = playlists.find(playlist)->second.languages});
-    }
-    std::ranges::sort(sortedPlaylists,
-                      [](const auto& i, const auto& j)
-                      {
-                        if (i.index == j.index)
-                        {
-                          if (i.languages.size() == j.languages.size())
-                            return i.playlist < j.playlist;
-                          return i.languages.size() > j.languages.size();
-                        }
-                        return i.index < j.index;
-                      });
-
-    for (const auto& [playlist, index, languages] : sortedPlaylists)
+    auto filter = m_candidatePlaylists | std::views::values;
+    auto sortedPlaylists = std::vector(filter.begin(), filter.end());
+
+    std::ranges::sort(
+        sortedPlaylists,
+        [](const CandidatePlaylistInformation& i, const CandidatePlaylistInformation& j)
+        {
+          if (i.index == j.index)
+          {
+            if (i.languages.size() == j.languages.size())
+              return i.playlist < j.playlist;
+            return i.languages.size() > j.languages.size();
+          }
+          return i.index < j.index;
+        });
+
+    for (const auto& playlist : sortedPlaylists)
     {
       const auto newItem{std::make_shared<CFileItem>("", false)};
-      GenerateItem(url, newItem, playlist, playlists, episodesOnDisc[index], IsSpecial::EPISODE);
+      GenerateItem(url, newItem, playlist.playlist, playlists, episodesOnDisc[playlist.index],
+                   IsSpecial::EPISODE);
       items.Add(newItem);
     }
   }
diff --git a/xbmc/filesystem/DiscDirectoryHelper.h b/xbmc/filesystem/DiscDirectoryHelper.h
index 4ab708d5ea97c..e7e8e38e5a043 100644
--- a/xbmc/filesystem/DiscDirectoryHelper.h
+++ b/xbmc/filesystem/DiscDirectoryHelper.h
@@ -128,22 +128,27 @@ class CDiscDirectoryHelper
     ALL
   };
 
-  struct CandidatePlaylistsDurationInformation
+  struct CandidatePlaylistInformation
   {
     unsigned int playlist{0};
+    unsigned int index{0};
+    std::chrono::milliseconds duration{0ms};
     std::chrono::milliseconds durationDelta{0ms};
+    int multiple{0};
     unsigned int chapters{0};
-  };
-
-  struct SortedPlaylistsInformation
-  {
-    unsigned int playlist{0};
-    unsigned int index{0};
+    std::vector<unsigned int> clips;
     std::string languages;
+
+    bool operator<(const CandidatePlaylistInformation& rhs) const noexcept
+    {
+      return playlist < rhs.playlist;
+    }
   };
 
 public:
   CDiscDirectoryHelper();
+  CDiscDirectoryHelper(const CDiscDirectoryHelper&) = delete;
+  CDiscDirectoryHelper& operator=(const CDiscDirectoryHelper&) = delete;
 
   /*!
    * \brief Populates a CFileItemList with the playlist(s) corresponding to the given episode.
@@ -181,6 +186,13 @@ class CDiscDirectoryHelper
   void InitialisePlaylistSearch(int episodeIndex, const std::vector<CVideoInfoTag>& episodesOnDisc);
   void FindPlayAllPlaylists(const ClipMap& clips, const PlaylistMap& playlists);
   void FindGroups(const PlaylistMap& playlists);
+  void UsePlayAllPlaylistMethod(unsigned int episodeIndex, const PlaylistMap& playlists);
+  void UseLongOrCommonMethodForSingleEpisode(unsigned int episodeIndex,
+                                             const PlaylistMap& playlists);
+  void UseGroupMethod(unsigned int episodeIndex, const PlaylistMap& playlists);
+  void ChooseSingleBestPlaylist(const std::vector<CVideoInfoTag>& episodesOnDisc,
+                                const PlaylistMap& playlists);
+  void AddIdenticalPlaylists(const PlaylistMap& playlists);
   void FindCandidatePlaylists(const std::vector<CVideoInfoTag>& episodesOnDisc,
                               unsigned int episodeIndex,
                               const PlaylistMap& playlists);
@@ -191,22 +203,23 @@ class CDiscDirectoryHelper
                            const PlaylistMap& playlists,
                            const CVideoInfoTag& tag,
                            IsSpecial isSpecial);
-  void EndPlaylistSearch();
+  void EndPlaylistSearch() const;
   void PopulateFileItems(const CURL& url,
                          CFileItemList& items,
                          int episodeIndex,
                          const std::vector<CVideoInfoTag>& episodesOnDisc,
-                         const PlaylistMap& playlists);
+                         const PlaylistMap& playlists) const;
 
-  AllEpisodes m_allEpisodes;
-  IsSpecial m_isSpecial;
-  unsigned int m_numEpisodes;
-  unsigned int m_numSpecials;
+  AllEpisodes m_allEpisodes{AllEpisodes::SINGLE};
+  IsSpecial m_isSpecial{IsSpecial::EPISODE};
+  unsigned int m_numEpisodes{0};
+  unsigned int m_numSpecials{0};
 
-  std::set<unsigned int> m_playAllPlaylists;
+  std::set<CandidatePlaylistInformation> m_playAllPlaylists;
   std::map<unsigned int, std::map<unsigned int, std::vector<unsigned int>>> m_playAllPlaylistsMap;
   std::vector<std::vector<unsigned int>> m_groups;
-  std::map<unsigned int, unsigned int> m_candidatePlaylists;
+  std::vector<std::vector<CandidatePlaylistInformation>> m_allGroups;
+  std::map<unsigned int, CandidatePlaylistInformation> m_candidatePlaylists;
   std::set<unsigned int> m_candidateSpecials;
 };
 } // namespace XFILE


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR implements SonarQube suggestions for the Bluray-related classes by refactoring functions in CDiscDirectoryHelper, CBlurayDiscCache, and CBlurayDirectory to reduce complexity and improve code clarity without altering functionality. Key changes include deleting copy constructors/assignment operators, marking certain functions as const, and refactoring candidate playlist selection logic.

### Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File                                        | Description                                                                          |
| ------------------------------------------- | ------------------------------------------------------------------------------------ |
| xbmc/filesystem/DiscDirectoryHelper.h       | Disables copy operations and adds new helper methods with refactored member initialization. |
| xbmc/filesystem/DiscDirectoryHelper.cpp       | Reorders logic and restructures functions for candidate playlist selection and group handling. |
| xbmc/filesystem/BlurayDiscCache.h            | Deletes copy operations and updates type usage for CacheMap and member visibility.    |
| xbmc/filesystem/BlurayDiscCache.cpp            | Updates lock syntax and uses structured bindings and try_emplace for clarity and efficiency. |
| xbmc/filesystem/BlurayDirectory.h            | Adds a non-default constructor and marks getter functions as const.                 |
</details>



<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**xbmc/filesystem/DiscDirectoryHelper.cpp:375**
* [nitpick] Consider clarifying this compound condition—perhaps by extracting parts into well-named variables or adding inline comments—to improve readability of how special episodes are handled.
```
if (!(m_allEpisodes == AllEpisodes::ALL || i == episodeIndex - m_numSpecials)) // Specials before episodes in episodesOnDisc
```
**xbmc/filesystem/DiscDirectoryHelper.cpp:476**
* [nitpick] The overloaded FindCandidatePlaylists function might benefit from a more descriptive name to clearly differentiate its role from other candidate selection methods.
```
void CDiscDirectoryHelper::FindCandidatePlaylists(const std::vector<CVideoInfoTag>& episodesOnDisc, unsigned int episodeIndex, const PlaylistMap& playlists)
```
</details>


state: COMMENTED
author_association: NONE
submitted_at: 2025-05-03T13:47:20Z
user: login: 78andyp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-03T14:36:33Z
user: login: garbear
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T17:26:08Z
user: login: 78andyp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-03T20:50:10Z
user: login: neo1973
body: Note: I didn't try to understand what the code actually does, I only looked at style and syntax.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T16:58:22Z
user: login: 78andyp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T17:26:41Z
user: login: 78andyp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T17:27:54Z
user: login: 78andyp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T17:29:21Z
user: login: 78andyp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T17:42:10Z
user: login: 78andyp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T17:42:15Z
user: login: neo1973
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-05T23:11:11Z

Review comments:
diff_hunk: @@ -93,108 +88,108 @@ void CDiscDirectoryHelper::FindPlayAllPlaylists(const ClipMap& clips, const Play
   //      will have at most one other clip before/after
 
   // Only look for play all playlists if enough playlists and more than one episode on disc
-  if (m_numEpisodes > 1 && playlists.size() >= m_numEpisodes)
+  if (m_numEpisodes < 2 || playlists.size() < m_numEpisodes)
path: xbmc/filesystem/DiscDirectoryHelper.cpp
user: login: Copilot
body: [nitpick] The inverted condition here may be slightly less intuitive than a positive check for sufficient episodes and playlists. Consider adding a clarifying comment or revising the condition for readability.
```suggestion
  if (m_numEpisodes >= 2 && playlists.size() >= m_numEpisodes)
  {
    // Proceed with finding play all playlists
  }
  else
```
created_at: 2025-05-03T13:47:20Z
author_association: NONE
diff_hunk: @@ -93,108 +88,108 @@ void CDiscDirectoryHelper::FindPlayAllPlaylists(const ClipMap& clips, const Play
   //      will have at most one other clip before/after
 
   // Only look for play all playlists if enough playlists and more than one episode on disc
-  if (m_numEpisodes > 1 && playlists.size() >= m_numEpisodes)
+  if (m_numEpisodes < 2 || playlists.size() < m_numEpisodes)
path: xbmc/filesystem/DiscDirectoryHelper.cpp
user: login: 78andyp
body: This was done to reduce the nesting as per SonarQube.
created_at: 2025-05-03T14:36:33Z
author_association: MEMBER
diff_hunk: @@ -93,108 +88,108 @@ void CDiscDirectoryHelper::FindPlayAllPlaylists(const ClipMap& clips, const Play
   //      will have at most one other clip before/after
 
   // Only look for play all playlists if enough playlists and more than one episode on disc
-  if (m_numEpisodes > 1 && playlists.size() >= m_numEpisodes)
+  if (m_numEpisodes < 2 || playlists.size() < m_numEpisodes)
path: xbmc/filesystem/DiscDirectoryHelper.cpp
user: login: garbear
body: Hit the thumbs down and tell copilot that. The more data, the better it gets.
created_at: 2025-05-03T17:26:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -93,108 +88,108 @@ void CDiscDirectoryHelper::FindPlayAllPlaylists(const ClipMap& clips, const Play
   //      will have at most one other clip before/after
 
   // Only look for play all playlists if enough playlists and more than one episode on disc
-  if (m_numEpisodes > 1 && playlists.size() >= m_numEpisodes)
+  if (m_numEpisodes < 2 || playlists.size() < m_numEpisodes)
path: xbmc/filesystem/DiscDirectoryHelper.cpp
user: login: 78andyp
body: > Hit the thumbs down and tell copilot that. The more data, the better it gets.

Didn't know you could do that - done, thanks.
created_at: 2025-05-03T20:50:10Z
author_association: MEMBER
diff_hunk: @@ -228,19 +223,17 @@ void CDiscDirectoryHelper::FindGroups(const PlaylistMap& playlists)
                       m_groups.emplace_back(1, playlist); // New group
                   });
 
-    // See if any groups have at least numEpisode playlists
-    if (std::erase_if(m_groups, [&](const std::vector<unsigned int>& group)
-                      { return group.size() < m_numEpisodes; }) == 0)
+    // See if any groups have at least numEpisode playlists and there are exactly
+    // numEpisode playlists remaining and no specials, in which case make a group.
+    // Assumption has to be playlists match episodes in ascending order
+    if (std::erase_if(m_groups, [this](const std::vector<unsigned int>& group)
+                      { return group.size() < m_numEpisodes; }) == 0 &&
+        m_numSpecials == 0 && longPlaylists.size() == m_numEpisodes)
     {
-      // See if there are exactly numEpisode playlists remaining and no specials, in which case make a group
-      // Assumption has to be playlists match episodes in ascending order
-      if (m_numSpecials == 0 && longPlaylists.size() == m_numEpisodes)
-      {
-        m_groups.clear();
-        m_groups.emplace_back(1, longPlaylists.begin()->first);
-        std::for_each(std::next(longPlaylists.begin()), longPlaylists.end(),
-                      [&](const PlaylistMapEntry& p) { m_groups.back().emplace_back(p.first); });
-      }
+      m_groups.clear();
+      m_groups.emplace_back(1, longPlaylists.begin()->first);
+      std::for_each(std::next(longPlaylists.begin()), longPlaylists.end(),
+                    [this](const PlaylistMapEntry& p) { m_groups.back().emplace_back(p.first); });
path: xbmc/filesystem/DiscDirectoryHelper.cpp
user: login: neo1973
body: A lot easier to understand:
```suggestion
      m_groups.clear();
      const auto keys = std::views::keys(longPlaylists);
      m_groups.emplace_back(keys.begin(), keys.end());
```
created_at: 2025-05-04T14:47:27Z
author_association: MEMBER
diff_hunk: @@ -255,267 +248,267 @@ void CDiscDirectoryHelper::FindGroups(const PlaylistMap& playlists)
   }
 }
 
-void CDiscDirectoryHelper::FindCandidatePlaylists(const std::vector<CVideoInfoTag>& episodesOnDisc,
-                                                  unsigned int episodeIndex,
-                                                  const PlaylistMap& playlists)
+void CDiscDirectoryHelper::UsePlayAllPlaylistMethod(unsigned int episodeIndex,
+                                                    const PlaylistMap& playlists)
 {
-  // At this stage we have a number of ways of trying to determine the correct playlist for an episode
-  //
-  // 1) Using a 'Play All' playlist, if present.
-  //
-  // 2a) Using the longest playlists that are consecutive, using the playlist in the nth position.
-  //
-  // 2b) For single episode discs, look for the longest playlist.
-  //    There are some discs where there are extras that are longer than the episode itself, in n this case
-  //    we look for the playlist with a common starting number (eg. 1, 800 etc..)
+  CLog::LogF(LOGDEBUG, "Using Play All playlist method");
 
-  bool foundEpisode{false};
-  bool findIdenticalEpisodes{false};
+  // Get the playlist
+  const unsigned int playAllPlaylist{*m_playAllPlaylists.begin()};
 
-  if (m_allEpisodes == AllEpisodes::ALL)
-    CLog::LogF(LOGDEBUG, "Looking for all episodes on disc");
-
-  // Method 1 - Play all playlist
-  if (m_playAllPlaylists.size() == 1)
+  if (!playlists.contains(playAllPlaylist))
   {
-    CLog::LogF(LOGDEBUG, "Using Play All playlist method");
+    CLog::LogF(LOGERROR, "Play all playlist {} missing in playlist map", playAllPlaylist);
+    return;
+  }
 
-    // Get the playlist
-    const unsigned int playAllPlaylist{*m_playAllPlaylists.begin()};
+  // Get the potential single episodes from the map
+  const auto& [playlist, playlistInformation] = *playlists.find(playAllPlaylist);
+  CLog::LogF(LOGDEBUG, "Using candidate play all playlist {} duration {}", playlist,
+             static_cast<int>(playlistInformation.duration.count() / 1000));
 
-    if (!playlists.contains(playAllPlaylist))
+  // Find the clip for the episode(s)
+  unsigned int i{0};
+  for (const auto& clip : playlistInformation.clips)
+  {
+    if (m_allEpisodes == AllEpisodes::ALL ||
+        i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
     {
-      CLog::LogF(LOGERROR, "Play all playlist {} missing in playlist map", playAllPlaylist);
-      return;
-    }
+      if (!m_playAllPlaylistsMap[playAllPlaylist].contains(clip))
+      {
+        CLog::LogF(LOGERROR, "Clip {} missing in play all playlist map", clip);
+        return;
+      }
 
-    // Get the potential single episodes from the map
-    const auto& [playlist, playlistInformation] = *playlists.find(playAllPlaylist);
-    CLog::LogF(LOGDEBUG, "Using candidate play all playlist {} duration {}", playlist,
-               static_cast<int>(playlistInformation.duration.count() / 1000));
+      CLog::LogF(LOGDEBUG, "Clip is {}", clip);
 
-    // Find the clip for the episode(s)
-    unsigned int i{0};
-    for (const auto& clip : playlistInformation.clips)
-    {
-      if (m_allEpisodes == AllEpisodes::ALL ||
-          i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
+      // Find playlist(s) with that clip from map populated earlier
+      const auto& singleEpisodePlaylists{m_playAllPlaylistsMap[playAllPlaylist].find(clip)->second};
+
+      for (const auto& singleEpisodePlaylist : singleEpisodePlaylists)
       {
-        if (!m_playAllPlaylistsMap[playAllPlaylist].contains(clip))
+        if (!playlists.contains(singleEpisodePlaylist))
         {
-          CLog::LogF(LOGERROR, "Clip {} missing in play all playlist map", clip);
+          CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
+                     singleEpisodePlaylist);
           return;
         }
+        // Get playlist information
+        const PlaylistInfo& singleEpisodePlaylistInformation{
+            playlists.find(singleEpisodePlaylist)->second};
+
+        CLog::LogF(LOGDEBUG, "Candidate playlist {} duration {}", singleEpisodePlaylist,
+                   static_cast<int>(singleEpisodePlaylistInformation.duration.count() / 1000));
 
-        CLog::LogF(LOGDEBUG, "Clip is {}", clip);
+        m_candidatePlaylists.insert(
+            {singleEpisodePlaylist, i + m_numSpecials}); // Also save episodeIndex for all episodes
+      }
+    }
+    ++i;
+  }
+}
 
-        // Find playlist(s) with that clip from map populated earlier
-        const auto& singleEpisodePlaylists{
-            m_playAllPlaylistsMap[playAllPlaylist].find(clip)->second};
+void CDiscDirectoryHelper::UseLongOrCommonMethodForSingleEpisode(unsigned int episodeIndex,
+                                                                 const PlaylistMap& playlists)
+{
+  // Method 2b - Need to think about the special case of only one episode on disc
 
-        for (const auto& singleEpisodePlaylist : singleEpisodePlaylists)
-        {
-          if (!playlists.contains(singleEpisodePlaylist))
-          {
-            CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
-                       singleEpisodePlaylist);
-            return;
-          }
-          // Get playlist information
-          const PlaylistInfo& singleEpisodePlaylistInformation{
-              playlists.find(singleEpisodePlaylist)->second};
+  // Sort playlists by length
+  PlaylistVector playlists_length;
+  playlists_length.reserve(playlists.size());
+  playlists_length.assign(playlists.begin(), playlists.end());
+  std::ranges::sort(playlists_length,
+                    [](const PlaylistVectorEntry& i, const PlaylistVectorEntry& j)
+                    {
+                      const auto& [i_playlist, i_playlistInformation] = i;
+                      const auto& [j_playlist, j_playlistInformation] = j;
+                      if (i_playlistInformation.duration == j_playlistInformation.duration)
+                        return i_playlist < j_playlist;
+                      return i_playlistInformation.duration > j_playlistInformation.duration;
+                    });
 
-          CLog::LogF(LOGDEBUG, "Candidate playlist {} duration {}", singleEpisodePlaylist,
-                     static_cast<int>(singleEpisodePlaylistInformation.duration.count() / 1000));
+  // Remove duplicate lengths (each episode may have more than one playlist with different languages)
+  const auto& [first, last] = std::ranges::unique(
+      playlists_length, {}, [](const PlaylistVectorEntry& i) { return i.second.duration; });
+  playlists_length.erase(first, last);
 
-          m_candidatePlaylists.insert(
-              {singleEpisodePlaylist,
-               i + m_numSpecials}); // Also save episodeIndex for all episodes
-        }
-      }
-      ++i;
-    }
-    foundEpisode = true;
-    findIdenticalEpisodes = true;
+  // See how many unique (different length) episode length(>= MIN_EPISODE_LENGTH) playlists there are
+  auto episodeLengthPlaylists{
+      playlists_length | std::views::filter([](const PlaylistVectorEntry& p)
+                                            { return p.second.duration >= MIN_EPISODE_DURATION; })};
+
+  // Look for common starting playlists
+  constexpr std::array<unsigned int, 5> commonStartingPlaylists = {801, 800, 1, 811, 0};
+  const auto& playlistsView{playlists | std::views::keys};
+  const auto commonPlaylist{std::ranges::find_first_of(playlistsView, commonStartingPlaylists)};
+
+  if (std::ranges::distance(episodeLengthPlaylists) == 1)
+  {
+    // If only one long playlist, then assume it's that
+    const unsigned int playlist{episodeLengthPlaylists.begin()->first};
+    CLog::LogF(LOGDEBUG, "Single Episode - found using single long playlist method");
+    CLog::LogF(LOGDEBUG, "Candidate playlist {}", playlist);
+    m_candidatePlaylists.insert({playlist, episodeIndex});
   }
+  else if (commonPlaylist != playlistsView.end())
+  {
+    // Found a common playlist, so assume it's that
+    CLog::LogF(LOGDEBUG, "Single Episode - found using common playlist method");
+    CLog::LogF(LOGDEBUG, "Candidate playlist {}", *commonPlaylist);
+    m_candidatePlaylists.insert({*commonPlaylist, episodeIndex});
+  }
+}
 
-  // Method 2 - Look for the longest playlists in groups, taking the nth playlist
-  if (!foundEpisode)
+void CDiscDirectoryHelper::UseGroupMethod(unsigned int episodeIndex)
+{
+  // Method 2a - More than one episode on disc
+
+  // Use groups and find nth playlist (or all for all episodes) in group
+  // Groups are already contain at least numEpisodes playlists of minimum duration
+  for (const auto& group : m_groups)
   {
-    CLog::LogF(LOGDEBUG, "Using longest playlists in groups method");
+    if (group.size() != m_numEpisodes)
+      continue;
 
-    if (m_numEpisodes == 1)
-    {
-      // Method 2b - Need to think about the special case of only one episode on disc
-
-      // Sort playlists by length
-      PlaylistVector playlists_length;
-      playlists_length.reserve(playlists.size());
-      playlists_length.assign(playlists.begin(), playlists.end());
-      std::ranges::sort(playlists_length,
-                        [](const PlaylistVectorEntry& i, const PlaylistVectorEntry& j)
-                        {
-                          const auto& [i_playlist, i_playlistInformation] = i;
-                          const auto& [j_playlist, j_playlistInformation] = j;
-                          if (i_playlistInformation.duration == j_playlistInformation.duration)
-                            return i_playlist < j_playlist;
-                          return i_playlistInformation.duration > j_playlistInformation.duration;
-                        });
-
-      // Remove duplicate lengths (each episode may have more than one playlist with different languages)
-      const auto& [first, last] = std::ranges::unique(
-          playlists_length, {}, [](const PlaylistVectorEntry& i) { return i.second.duration; });
-      playlists_length.erase(first, last);
-
-      // See how many unique (different length) episode length(>= MIN_EPISODE_LENGTH) playlists there are
-      auto episodeLengthPlaylists{
-          playlists_length |
-          std::views::filter([](const PlaylistVectorEntry& p)
-                             { return p.second.duration >= MIN_EPISODE_DURATION; })};
-
-      // Look for common starting playlists
-      constexpr std::array<unsigned int, 5> commonStartingPlaylists = {801, 800, 1, 811, 0};
-      const auto& playlistsView{playlists | std::views::keys};
-      const auto commonPlaylist{std::ranges::find_first_of(playlistsView, commonStartingPlaylists)};
-
-      if (std::ranges::distance(episodeLengthPlaylists) == 1)
-      {
-        // If only one long playlist, then assume it's that
-        const unsigned int playlist{episodeLengthPlaylists.begin()->first};
-        CLog::LogF(LOGDEBUG, "Single Episode - found using single long playlist method");
-        CLog::LogF(LOGDEBUG, "Candidate playlist {}", playlist);
-        m_candidatePlaylists.insert({playlist, episodeIndex});
-      }
-      else if (commonPlaylist != playlistsView.end())
-      {
-        // Found a common playlist, so assume it's that
-        CLog::LogF(LOGDEBUG, "Single Episode - found using common playlist method");
-        CLog::LogF(LOGDEBUG, "Candidate playlist {}", *commonPlaylist);
-        m_candidatePlaylists.insert({*commonPlaylist, episodeIndex});
-      }
-    }
-    else
+    for (unsigned int i = 0; i < m_numEpisodes; ++i)
     {
-      // Method 2a - More than one episode on disc
+      if (m_allEpisodes != AllEpisodes::ALL &&
+          i != episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
+        continue;
 
-      // Use groups and find nth playlist (or all for all episodes) in group
-      // Groups are already contain at least numEpisodes playlists of minimum duration
-      if (!m_groups.empty())
-      {
-        for (const auto& group : m_groups)
-        {
-          if (group.size() == m_numEpisodes)
-          {
-            for (unsigned int i = 0; i < m_numEpisodes; ++i)
-            {
-              if (m_allEpisodes == AllEpisodes::ALL ||
-                  i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
-              {
-                m_candidatePlaylists.insert(
-                    {group[i], i + m_numSpecials}); // Also save episodeIndex for all episodes
-                CLog::LogF(LOGDEBUG, "Candidate playlist {}", group[i]);
-              }
-            }
-          }
-        }
-        findIdenticalEpisodes = true;
-      }
+      m_candidatePlaylists.insert(
+          {group[i], i + m_numSpecials}); // Also save episodeIndex for all episodes
+      CLog::LogF(LOGDEBUG, "Candidate playlist {}", group[i]);
     }
   }
+}
 
-  // Now deal with the possibility there may be more than one playlist (per episode)
-  // For this, see which is closest in duration to the desired episode duration (from the scraper)
-  // If we are looking for a special then leave all potential episode playlists in array
-  if (m_candidatePlaylists.size() > 1 && m_isSpecial == IsSpecial::EPISODE)
+void CDiscDirectoryHelper::ChooseSingleBestPlaylist(
+    const std::vector<CVideoInfoTag>& episodesOnDisc, const PlaylistMap& playlists)
+{
+  // Rebuild candidatePlaylists
+  const auto oldCandidatePlaylists{m_candidatePlaylists};
path: xbmc/filesystem/DiscDirectoryHelper.cpp
user: login: neo1973
body: ```suggestion
  const auto oldCandidatePlaylists{std::move(m_candidatePlaylists)};
```
created_at: 2025-05-04T15:43:00Z
author_association: MEMBER
diff_hunk: @@ -255,267 +248,267 @@ void CDiscDirectoryHelper::FindGroups(const PlaylistMap& playlists)
   }
 }
 
-void CDiscDirectoryHelper::FindCandidatePlaylists(const std::vector<CVideoInfoTag>& episodesOnDisc,
-                                                  unsigned int episodeIndex,
-                                                  const PlaylistMap& playlists)
+void CDiscDirectoryHelper::UsePlayAllPlaylistMethod(unsigned int episodeIndex,
+                                                    const PlaylistMap& playlists)
 {
-  // At this stage we have a number of ways of trying to determine the correct playlist for an episode
-  //
-  // 1) Using a 'Play All' playlist, if present.
-  //
-  // 2a) Using the longest playlists that are consecutive, using the playlist in the nth position.
-  //
-  // 2b) For single episode discs, look for the longest playlist.
-  //    There are some discs where there are extras that are longer than the episode itself, in n this case
-  //    we look for the playlist with a common starting number (eg. 1, 800 etc..)
+  CLog::LogF(LOGDEBUG, "Using Play All playlist method");
 
-  bool foundEpisode{false};
-  bool findIdenticalEpisodes{false};
+  // Get the playlist
+  const unsigned int playAllPlaylist{*m_playAllPlaylists.begin()};
 
-  if (m_allEpisodes == AllEpisodes::ALL)
-    CLog::LogF(LOGDEBUG, "Looking for all episodes on disc");
-
-  // Method 1 - Play all playlist
-  if (m_playAllPlaylists.size() == 1)
+  if (!playlists.contains(playAllPlaylist))
   {
-    CLog::LogF(LOGDEBUG, "Using Play All playlist method");
+    CLog::LogF(LOGERROR, "Play all playlist {} missing in playlist map", playAllPlaylist);
+    return;
+  }
 
-    // Get the playlist
-    const unsigned int playAllPlaylist{*m_playAllPlaylists.begin()};
+  // Get the potential single episodes from the map
+  const auto& [playlist, playlistInformation] = *playlists.find(playAllPlaylist);
+  CLog::LogF(LOGDEBUG, "Using candidate play all playlist {} duration {}", playlist,
+             static_cast<int>(playlistInformation.duration.count() / 1000));
 
-    if (!playlists.contains(playAllPlaylist))
+  // Find the clip for the episode(s)
+  unsigned int i{0};
+  for (const auto& clip : playlistInformation.clips)
+  {
+    if (m_allEpisodes == AllEpisodes::ALL ||
+        i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
     {
-      CLog::LogF(LOGERROR, "Play all playlist {} missing in playlist map", playAllPlaylist);
-      return;
-    }
+      if (!m_playAllPlaylistsMap[playAllPlaylist].contains(clip))
+      {
+        CLog::LogF(LOGERROR, "Clip {} missing in play all playlist map", clip);
+        return;
+      }
 
-    // Get the potential single episodes from the map
-    const auto& [playlist, playlistInformation] = *playlists.find(playAllPlaylist);
-    CLog::LogF(LOGDEBUG, "Using candidate play all playlist {} duration {}", playlist,
-               static_cast<int>(playlistInformation.duration.count() / 1000));
+      CLog::LogF(LOGDEBUG, "Clip is {}", clip);
 
-    // Find the clip for the episode(s)
-    unsigned int i{0};
-    for (const auto& clip : playlistInformation.clips)
-    {
-      if (m_allEpisodes == AllEpisodes::ALL ||
-          i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
+      // Find playlist(s) with that clip from map populated earlier
+      const auto& singleEpisodePlaylists{m_playAllPlaylistsMap[playAllPlaylist].find(clip)->second};
+
+      for (const auto& singleEpisodePlaylist : singleEpisodePlaylists)
       {
-        if (!m_playAllPlaylistsMap[playAllPlaylist].contains(clip))
+        if (!playlists.contains(singleEpisodePlaylist))
         {
-          CLog::LogF(LOGERROR, "Clip {} missing in play all playlist map", clip);
+          CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
+                     singleEpisodePlaylist);
           return;
         }
+        // Get playlist information
+        const PlaylistInfo& singleEpisodePlaylistInformation{
+            playlists.find(singleEpisodePlaylist)->second};
+
+        CLog::LogF(LOGDEBUG, "Candidate playlist {} duration {}", singleEpisodePlaylist,
+                   static_cast<int>(singleEpisodePlaylistInformation.duration.count() / 1000));
 
-        CLog::LogF(LOGDEBUG, "Clip is {}", clip);
+        m_candidatePlaylists.insert(
+            {singleEpisodePlaylist, i + m_numSpecials}); // Also save episodeIndex for all episodes
+      }
+    }
+    ++i;
+  }
+}
 
-        // Find playlist(s) with that clip from map populated earlier
-        const auto& singleEpisodePlaylists{
-            m_playAllPlaylistsMap[playAllPlaylist].find(clip)->second};
+void CDiscDirectoryHelper::UseLongOrCommonMethodForSingleEpisode(unsigned int episodeIndex,
+                                                                 const PlaylistMap& playlists)
+{
+  // Method 2b - Need to think about the special case of only one episode on disc
 
-        for (const auto& singleEpisodePlaylist : singleEpisodePlaylists)
-        {
-          if (!playlists.contains(singleEpisodePlaylist))
-          {
-            CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
-                       singleEpisodePlaylist);
-            return;
-          }
-          // Get playlist information
-          const PlaylistInfo& singleEpisodePlaylistInformation{
-              playlists.find(singleEpisodePlaylist)->second};
+  // Sort playlists by length
+  PlaylistVector playlists_length;
+  playlists_length.reserve(playlists.size());
+  playlists_length.assign(playlists.begin(), playlists.end());
+  std::ranges::sort(playlists_length,
+                    [](const PlaylistVectorEntry& i, const PlaylistVectorEntry& j)
+                    {
+                      const auto& [i_playlist, i_playlistInformation] = i;
+                      const auto& [j_playlist, j_playlistInformation] = j;
+                      if (i_playlistInformation.duration == j_playlistInformation.duration)
+                        return i_playlist < j_playlist;
+                      return i_playlistInformation.duration > j_playlistInformation.duration;
+                    });
 
-          CLog::LogF(LOGDEBUG, "Candidate playlist {} duration {}", singleEpisodePlaylist,
-                     static_cast<int>(singleEpisodePlaylistInformation.duration.count() / 1000));
+  // Remove duplicate lengths (each episode may have more than one playlist with different languages)
+  const auto& [first, last] = std::ranges::unique(
+      playlists_length, {}, [](const PlaylistVectorEntry& i) { return i.second.duration; });
+  playlists_length.erase(first, last);
 
-          m_candidatePlaylists.insert(
-              {singleEpisodePlaylist,
-               i + m_numSpecials}); // Also save episodeIndex for all episodes
-        }
-      }
-      ++i;
-    }
-    foundEpisode = true;
-    findIdenticalEpisodes = true;
+  // See how many unique (different length) episode length(>= MIN_EPISODE_LENGTH) playlists there are
+  auto episodeLengthPlaylists{
+      playlists_length | std::views::filter([](const PlaylistVectorEntry& p)
+                                            { return p.second.duration >= MIN_EPISODE_DURATION; })};
+
+  // Look for common starting playlists
+  constexpr std::array<unsigned int, 5> commonStartingPlaylists = {801, 800, 1, 811, 0};
+  const auto& playlistsView{playlists | std::views::keys};
+  const auto commonPlaylist{std::ranges::find_first_of(playlistsView, commonStartingPlaylists)};
+
+  if (std::ranges::distance(episodeLengthPlaylists) == 1)
+  {
+    // If only one long playlist, then assume it's that
+    const unsigned int playlist{episodeLengthPlaylists.begin()->first};
+    CLog::LogF(LOGDEBUG, "Single Episode - found using single long playlist method");
+    CLog::LogF(LOGDEBUG, "Candidate playlist {}", playlist);
+    m_candidatePlaylists.insert({playlist, episodeIndex});
   }
+  else if (commonPlaylist != playlistsView.end())
+  {
+    // Found a common playlist, so assume it's that
+    CLog::LogF(LOGDEBUG, "Single Episode - found using common playlist method");
+    CLog::LogF(LOGDEBUG, "Candidate playlist {}", *commonPlaylist);
+    m_candidatePlaylists.insert({*commonPlaylist, episodeIndex});
+  }
+}
 
-  // Method 2 - Look for the longest playlists in groups, taking the nth playlist
-  if (!foundEpisode)
+void CDiscDirectoryHelper::UseGroupMethod(unsigned int episodeIndex)
+{
+  // Method 2a - More than one episode on disc
+
+  // Use groups and find nth playlist (or all for all episodes) in group
+  // Groups are already contain at least numEpisodes playlists of minimum duration
+  for (const auto& group : m_groups)
   {
-    CLog::LogF(LOGDEBUG, "Using longest playlists in groups method");
+    if (group.size() != m_numEpisodes)
+      continue;
 
-    if (m_numEpisodes == 1)
-    {
-      // Method 2b - Need to think about the special case of only one episode on disc
-
-      // Sort playlists by length
-      PlaylistVector playlists_length;
-      playlists_length.reserve(playlists.size());
-      playlists_length.assign(playlists.begin(), playlists.end());
-      std::ranges::sort(playlists_length,
-                        [](const PlaylistVectorEntry& i, const PlaylistVectorEntry& j)
-                        {
-                          const auto& [i_playlist, i_playlistInformation] = i;
-                          const auto& [j_playlist, j_playlistInformation] = j;
-                          if (i_playlistInformation.duration == j_playlistInformation.duration)
-                            return i_playlist < j_playlist;
-                          return i_playlistInformation.duration > j_playlistInformation.duration;
-                        });
-
-      // Remove duplicate lengths (each episode may have more than one playlist with different languages)
-      const auto& [first, last] = std::ranges::unique(
-          playlists_length, {}, [](const PlaylistVectorEntry& i) { return i.second.duration; });
-      playlists_length.erase(first, last);
-
-      // See how many unique (different length) episode length(>= MIN_EPISODE_LENGTH) playlists there are
-      auto episodeLengthPlaylists{
-          playlists_length |
-          std::views::filter([](const PlaylistVectorEntry& p)
-                             { return p.second.duration >= MIN_EPISODE_DURATION; })};
-
-      // Look for common starting playlists
-      constexpr std::array<unsigned int, 5> commonStartingPlaylists = {801, 800, 1, 811, 0};
-      const auto& playlistsView{playlists | std::views::keys};
-      const auto commonPlaylist{std::ranges::find_first_of(playlistsView, commonStartingPlaylists)};
-
-      if (std::ranges::distance(episodeLengthPlaylists) == 1)
-      {
-        // If only one long playlist, then assume it's that
-        const unsigned int playlist{episodeLengthPlaylists.begin()->first};
-        CLog::LogF(LOGDEBUG, "Single Episode - found using single long playlist method");
-        CLog::LogF(LOGDEBUG, "Candidate playlist {}", playlist);
-        m_candidatePlaylists.insert({playlist, episodeIndex});
-      }
-      else if (commonPlaylist != playlistsView.end())
-      {
-        // Found a common playlist, so assume it's that
-        CLog::LogF(LOGDEBUG, "Single Episode - found using common playlist method");
-        CLog::LogF(LOGDEBUG, "Candidate playlist {}", *commonPlaylist);
-        m_candidatePlaylists.insert({*commonPlaylist, episodeIndex});
-      }
-    }
-    else
+    for (unsigned int i = 0; i < m_numEpisodes; ++i)
     {
-      // Method 2a - More than one episode on disc
+      if (m_allEpisodes != AllEpisodes::ALL &&
+          i != episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
+        continue;
 
-      // Use groups and find nth playlist (or all for all episodes) in group
-      // Groups are already contain at least numEpisodes playlists of minimum duration
-      if (!m_groups.empty())
-      {
-        for (const auto& group : m_groups)
-        {
-          if (group.size() == m_numEpisodes)
-          {
-            for (unsigned int i = 0; i < m_numEpisodes; ++i)
-            {
-              if (m_allEpisodes == AllEpisodes::ALL ||
-                  i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
-              {
-                m_candidatePlaylists.insert(
-                    {group[i], i + m_numSpecials}); // Also save episodeIndex for all episodes
-                CLog::LogF(LOGDEBUG, "Candidate playlist {}", group[i]);
-              }
-            }
-          }
-        }
-        findIdenticalEpisodes = true;
-      }
+      m_candidatePlaylists.insert(
+          {group[i], i + m_numSpecials}); // Also save episodeIndex for all episodes
+      CLog::LogF(LOGDEBUG, "Candidate playlist {}", group[i]);
     }
   }
+}
 
-  // Now deal with the possibility there may be more than one playlist (per episode)
-  // For this, see which is closest in duration to the desired episode duration (from the scraper)
-  // If we are looking for a special then leave all potential episode playlists in array
-  if (m_candidatePlaylists.size() > 1 && m_isSpecial == IsSpecial::EPISODE)
+void CDiscDirectoryHelper::ChooseSingleBestPlaylist(
+    const std::vector<CVideoInfoTag>& episodesOnDisc, const PlaylistMap& playlists)
+{
+  // Rebuild candidatePlaylists
+  const auto oldCandidatePlaylists{m_candidatePlaylists};
+  m_candidatePlaylists.clear();
+
+  // Loop through each episode (in case of all episodes)
+  // Generate set of indexes of episode entry in episodesOnDisc
+  std::set<unsigned int> indexes;
+  for (unsigned int index : oldCandidatePlaylists | std::views::values)
+    indexes.insert(index);
path: xbmc/filesystem/DiscDirectoryHelper.cpp
user: login: neo1973
body: You could do without a loop:
```suggestion
  const auto indexView = std::views::values(oldCandidatePlaylists);
  std::set<unsigned int> indexes(indexView.begin(), indexView.end());
```
created_at: 2025-05-04T15:55:43Z
author_association: MEMBER
diff_hunk: @@ -1529,7 +1568,7 @@ void GetPlaylistsInformation(
           it->second.playlists.emplace_back(playlist);
         }
 
-        clipsStr += std::to_string(clip) + ",";
+        clipsStr += fmt::format("{},", clip);
       }
       if (!clipsStr.empty())
         clipsStr.pop_back(); // Remove last ','
path: xbmc/filesystem/BlurayDirectory.cpp
user: login: neo1973
body: You don't have to build the string here, you can let fmt do that when logging below:
```cpp
      CLog::LogF(LOGDEBUG, "Playlist {}, Duration {}, Langs {}, Clips {} ", playlist,
                 title->GetVideoInfoTag()->GetDuration(), langs, fmt::join(clips, ","));
```
created_at: 2025-05-04T16:24:41Z
author_association: MEMBER
diff_hunk: @@ -1557,7 +1596,7 @@ void GetPlaylistsInformation(
     std::string ps{StringUtils::Format("Clip {0:d} duration {1:d} - playlists ", clip,
                                        clipInformation.duration.count() / 1000)};
     for (const auto& playlist : clipInformation.playlists)
-      ps += std::to_string(playlist) + ",";
+      ps += fmt::format("{},", playlist);
     ps.pop_back(); // Remove last ','
     CLog::LogF(LOGDEBUG, "{}", ps);
path: xbmc/filesystem/BlurayDirectory.cpp
user: login: neo1973
body: You can replace that whole block with this:
```cpp
    CLog::LogF(LOGDEBUG, "Clip {:d} duration {:d} - playlists {}", clip,
               clipInformation.duration.count() / 1000, fmt::join(clipInformation.playlists, ","));
```
created_at: 2025-05-04T16:45:59Z
author_association: MEMBER
diff_hunk: @@ -228,19 +223,17 @@ void CDiscDirectoryHelper::FindGroups(const PlaylistMap& playlists)
                       m_groups.emplace_back(1, playlist); // New group
                   });
 
-    // See if any groups have at least numEpisode playlists
-    if (std::erase_if(m_groups, [&](const std::vector<unsigned int>& group)
-                      { return group.size() < m_numEpisodes; }) == 0)
+    // See if any groups have at least numEpisode playlists and there are exactly
+    // numEpisode playlists remaining and no specials, in which case make a group.
+    // Assumption has to be playlists match episodes in ascending order
+    if (std::erase_if(m_groups, [this](const std::vector<unsigned int>& group)
+                      { return group.size() < m_numEpisodes; }) == 0 &&
+        m_numSpecials == 0 && longPlaylists.size() == m_numEpisodes)
     {
-      // See if there are exactly numEpisode playlists remaining and no specials, in which case make a group
-      // Assumption has to be playlists match episodes in ascending order
-      if (m_numSpecials == 0 && longPlaylists.size() == m_numEpisodes)
-      {
-        m_groups.clear();
-        m_groups.emplace_back(1, longPlaylists.begin()->first);
-        std::for_each(std::next(longPlaylists.begin()), longPlaylists.end(),
-                      [&](const PlaylistMapEntry& p) { m_groups.back().emplace_back(p.first); });
-      }
+      m_groups.clear();
+      m_groups.emplace_back(1, longPlaylists.begin()->first);
+      std::for_each(std::next(longPlaylists.begin()), longPlaylists.end(),
+                    [this](const PlaylistMapEntry& p) { m_groups.back().emplace_back(p.first); });
path: xbmc/filesystem/DiscDirectoryHelper.cpp
user: login: 78andyp
body: Thanks. Didn't know you could do that.
created_at: 2025-05-04T17:26:40Z
author_association: MEMBER
diff_hunk: @@ -255,267 +248,267 @@ void CDiscDirectoryHelper::FindGroups(const PlaylistMap& playlists)
   }
 }
 
-void CDiscDirectoryHelper::FindCandidatePlaylists(const std::vector<CVideoInfoTag>& episodesOnDisc,
-                                                  unsigned int episodeIndex,
-                                                  const PlaylistMap& playlists)
+void CDiscDirectoryHelper::UsePlayAllPlaylistMethod(unsigned int episodeIndex,
+                                                    const PlaylistMap& playlists)
 {
-  // At this stage we have a number of ways of trying to determine the correct playlist for an episode
-  //
-  // 1) Using a 'Play All' playlist, if present.
-  //
-  // 2a) Using the longest playlists that are consecutive, using the playlist in the nth position.
-  //
-  // 2b) For single episode discs, look for the longest playlist.
-  //    There are some discs where there are extras that are longer than the episode itself, in n this case
-  //    we look for the playlist with a common starting number (eg. 1, 800 etc..)
+  CLog::LogF(LOGDEBUG, "Using Play All playlist method");
 
-  bool foundEpisode{false};
-  bool findIdenticalEpisodes{false};
+  // Get the playlist
+  const unsigned int playAllPlaylist{*m_playAllPlaylists.begin()};
 
-  if (m_allEpisodes == AllEpisodes::ALL)
-    CLog::LogF(LOGDEBUG, "Looking for all episodes on disc");
-
-  // Method 1 - Play all playlist
-  if (m_playAllPlaylists.size() == 1)
+  if (!playlists.contains(playAllPlaylist))
   {
-    CLog::LogF(LOGDEBUG, "Using Play All playlist method");
+    CLog::LogF(LOGERROR, "Play all playlist {} missing in playlist map", playAllPlaylist);
+    return;
+  }
 
-    // Get the playlist
-    const unsigned int playAllPlaylist{*m_playAllPlaylists.begin()};
+  // Get the potential single episodes from the map
+  const auto& [playlist, playlistInformation] = *playlists.find(playAllPlaylist);
+  CLog::LogF(LOGDEBUG, "Using candidate play all playlist {} duration {}", playlist,
+             static_cast<int>(playlistInformation.duration.count() / 1000));
 
-    if (!playlists.contains(playAllPlaylist))
+  // Find the clip for the episode(s)
+  unsigned int i{0};
+  for (const auto& clip : playlistInformation.clips)
+  {
+    if (m_allEpisodes == AllEpisodes::ALL ||
+        i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
     {
-      CLog::LogF(LOGERROR, "Play all playlist {} missing in playlist map", playAllPlaylist);
-      return;
-    }
+      if (!m_playAllPlaylistsMap[playAllPlaylist].contains(clip))
+      {
+        CLog::LogF(LOGERROR, "Clip {} missing in play all playlist map", clip);
+        return;
+      }
 
-    // Get the potential single episodes from the map
-    const auto& [playlist, playlistInformation] = *playlists.find(playAllPlaylist);
-    CLog::LogF(LOGDEBUG, "Using candidate play all playlist {} duration {}", playlist,
-               static_cast<int>(playlistInformation.duration.count() / 1000));
+      CLog::LogF(LOGDEBUG, "Clip is {}", clip);
 
-    // Find the clip for the episode(s)
-    unsigned int i{0};
-    for (const auto& clip : playlistInformation.clips)
-    {
-      if (m_allEpisodes == AllEpisodes::ALL ||
-          i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
+      // Find playlist(s) with that clip from map populated earlier
+      const auto& singleEpisodePlaylists{m_playAllPlaylistsMap[playAllPlaylist].find(clip)->second};
+
+      for (const auto& singleEpisodePlaylist : singleEpisodePlaylists)
       {
-        if (!m_playAllPlaylistsMap[playAllPlaylist].contains(clip))
+        if (!playlists.contains(singleEpisodePlaylist))
         {
-          CLog::LogF(LOGERROR, "Clip {} missing in play all playlist map", clip);
+          CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
+                     singleEpisodePlaylist);
           return;
         }
+        // Get playlist information
+        const PlaylistInfo& singleEpisodePlaylistInformation{
+            playlists.find(singleEpisodePlaylist)->second};
+
+        CLog::LogF(LOGDEBUG, "Candidate playlist {} duration {}", singleEpisodePlaylist,
+                   static_cast<int>(singleEpisodePlaylistInformation.duration.count() / 1000));
 
-        CLog::LogF(LOGDEBUG, "Clip is {}", clip);
+        m_candidatePlaylists.insert(
+            {singleEpisodePlaylist, i + m_numSpecials}); // Also save episodeIndex for all episodes
+      }
+    }
+    ++i;
+  }
+}
 
-        // Find playlist(s) with that clip from map populated earlier
-        const auto& singleEpisodePlaylists{
-            m_playAllPlaylistsMap[playAllPlaylist].find(clip)->second};
+void CDiscDirectoryHelper::UseLongOrCommonMethodForSingleEpisode(unsigned int episodeIndex,
+                                                                 const PlaylistMap& playlists)
+{
+  // Method 2b - Need to think about the special case of only one episode on disc
 
-        for (const auto& singleEpisodePlaylist : singleEpisodePlaylists)
-        {
-          if (!playlists.contains(singleEpisodePlaylist))
-          {
-            CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
-                       singleEpisodePlaylist);
-            return;
-          }
-          // Get playlist information
-          const PlaylistInfo& singleEpisodePlaylistInformation{
-              playlists.find(singleEpisodePlaylist)->second};
+  // Sort playlists by length
+  PlaylistVector playlists_length;
+  playlists_length.reserve(playlists.size());
+  playlists_length.assign(playlists.begin(), playlists.end());
+  std::ranges::sort(playlists_length,
+                    [](const PlaylistVectorEntry& i, const PlaylistVectorEntry& j)
+                    {
+                      const auto& [i_playlist, i_playlistInformation] = i;
+                      const auto& [j_playlist, j_playlistInformation] = j;
+                      if (i_playlistInformation.duration == j_playlistInformation.duration)
+                        return i_playlist < j_playlist;
+                      return i_playlistInformation.duration > j_playlistInformation.duration;
+                    });
 
-          CLog::LogF(LOGDEBUG, "Candidate playlist {} duration {}", singleEpisodePlaylist,
-                     static_cast<int>(singleEpisodePlaylistInformation.duration.count() / 1000));
+  // Remove duplicate lengths (each episode may have more than one playlist with different languages)
+  const auto& [first, last] = std::ranges::unique(
+      playlists_length, {}, [](const PlaylistVectorEntry& i) { return i.second.duration; });
+  playlists_length.erase(first, last);
 
-          m_candidatePlaylists.insert(
-              {singleEpisodePlaylist,
-               i + m_numSpecials}); // Also save episodeIndex for all episodes
-        }
-      }
-      ++i;
-    }
-    foundEpisode = true;
-    findIdenticalEpisodes = true;
+  // See how many unique (different length) episode length(>= MIN_EPISODE_LENGTH) playlists there are
+  auto episodeLengthPlaylists{
+      playlists_length | std::views::filter([](const PlaylistVectorEntry& p)
+                                            { return p.second.duration >= MIN_EPISODE_DURATION; })};
+
+  // Look for common starting playlists
+  constexpr std::array<unsigned int, 5> commonStartingPlaylists = {801, 800, 1, 811, 0};
+  const auto& playlistsView{playlists | std::views::keys};
+  const auto commonPlaylist{std::ranges::find_first_of(playlistsView, commonStartingPlaylists)};
+
+  if (std::ranges::distance(episodeLengthPlaylists) == 1)
+  {
+    // If only one long playlist, then assume it's that
+    const unsigned int playlist{episodeLengthPlaylists.begin()->first};
+    CLog::LogF(LOGDEBUG, "Single Episode - found using single long playlist method");
+    CLog::LogF(LOGDEBUG, "Candidate playlist {}", playlist);
+    m_candidatePlaylists.insert({playlist, episodeIndex});
   }
+  else if (commonPlaylist != playlistsView.end())
+  {
+    // Found a common playlist, so assume it's that
+    CLog::LogF(LOGDEBUG, "Single Episode - found using common playlist method");
+    CLog::LogF(LOGDEBUG, "Candidate playlist {}", *commonPlaylist);
+    m_candidatePlaylists.insert({*commonPlaylist, episodeIndex});
+  }
+}
 
-  // Method 2 - Look for the longest playlists in groups, taking the nth playlist
-  if (!foundEpisode)
+void CDiscDirectoryHelper::UseGroupMethod(unsigned int episodeIndex)
+{
+  // Method 2a - More than one episode on disc
+
+  // Use groups and find nth playlist (or all for all episodes) in group
+  // Groups are already contain at least numEpisodes playlists of minimum duration
+  for (const auto& group : m_groups)
   {
-    CLog::LogF(LOGDEBUG, "Using longest playlists in groups method");
+    if (group.size() != m_numEpisodes)
+      continue;
 
-    if (m_numEpisodes == 1)
-    {
-      // Method 2b - Need to think about the special case of only one episode on disc
-
-      // Sort playlists by length
-      PlaylistVector playlists_length;
-      playlists_length.reserve(playlists.size());
-      playlists_length.assign(playlists.begin(), playlists.end());
-      std::ranges::sort(playlists_length,
-                        [](const PlaylistVectorEntry& i, const PlaylistVectorEntry& j)
-                        {
-                          const auto& [i_playlist, i_playlistInformation] = i;
-                          const auto& [j_playlist, j_playlistInformation] = j;
-                          if (i_playlistInformation.duration == j_playlistInformation.duration)
-                            return i_playlist < j_playlist;
-                          return i_playlistInformation.duration > j_playlistInformation.duration;
-                        });
-
-      // Remove duplicate lengths (each episode may have more than one playlist with different languages)
-      const auto& [first, last] = std::ranges::unique(
-          playlists_length, {}, [](const PlaylistVectorEntry& i) { return i.second.duration; });
-      playlists_length.erase(first, last);
-
-      // See how many unique (different length) episode length(>= MIN_EPISODE_LENGTH) playlists there are
-      auto episodeLengthPlaylists{
-          playlists_length |
-          std::views::filter([](const PlaylistVectorEntry& p)
-                             { return p.second.duration >= MIN_EPISODE_DURATION; })};
-
-      // Look for common starting playlists
-      constexpr std::array<unsigned int, 5> commonStartingPlaylists = {801, 800, 1, 811, 0};
-      const auto& playlistsView{playlists | std::views::keys};
-      const auto commonPlaylist{std::ranges::find_first_of(playlistsView, commonStartingPlaylists)};
-
-      if (std::ranges::distance(episodeLengthPlaylists) == 1)
-      {
-        // If only one long playlist, then assume it's that
-        const unsigned int playlist{episodeLengthPlaylists.begin()->first};
-        CLog::LogF(LOGDEBUG, "Single Episode - found using single long playlist method");
-        CLog::LogF(LOGDEBUG, "Candidate playlist {}", playlist);
-        m_candidatePlaylists.insert({playlist, episodeIndex});
-      }
-      else if (commonPlaylist != playlistsView.end())
-      {
-        // Found a common playlist, so assume it's that
-        CLog::LogF(LOGDEBUG, "Single Episode - found using common playlist method");
-        CLog::LogF(LOGDEBUG, "Candidate playlist {}", *commonPlaylist);
-        m_candidatePlaylists.insert({*commonPlaylist, episodeIndex});
-      }
-    }
-    else
+    for (unsigned int i = 0; i < m_numEpisodes; ++i)
     {
-      // Method 2a - More than one episode on disc
+      if (m_allEpisodes != AllEpisodes::ALL &&
+          i != episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
+        continue;
 
-      // Use groups and find nth playlist (or all for all episodes) in group
-      // Groups are already contain at least numEpisodes playlists of minimum duration
-      if (!m_groups.empty())
-      {
-        for (const auto& group : m_groups)
-        {
-          if (group.size() == m_numEpisodes)
-          {
-            for (unsigned int i = 0; i < m_numEpisodes; ++i)
-            {
-              if (m_allEpisodes == AllEpisodes::ALL ||
-                  i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
-              {
-                m_candidatePlaylists.insert(
-                    {group[i], i + m_numSpecials}); // Also save episodeIndex for all episodes
-                CLog::LogF(LOGDEBUG, "Candidate playlist {}", group[i]);
-              }
-            }
-          }
-        }
-        findIdenticalEpisodes = true;
-      }
+      m_candidatePlaylists.insert(
+          {group[i], i + m_numSpecials}); // Also save episodeIndex for all episodes
+      CLog::LogF(LOGDEBUG, "Candidate playlist {}", group[i]);
     }
   }
+}
 
-  // Now deal with the possibility there may be more than one playlist (per episode)
-  // For this, see which is closest in duration to the desired episode duration (from the scraper)
-  // If we are looking for a special then leave all potential episode playlists in array
-  if (m_candidatePlaylists.size() > 1 && m_isSpecial == IsSpecial::EPISODE)
+void CDiscDirectoryHelper::ChooseSingleBestPlaylist(
+    const std::vector<CVideoInfoTag>& episodesOnDisc, const PlaylistMap& playlists)
+{
+  // Rebuild candidatePlaylists
+  const auto oldCandidatePlaylists{m_candidatePlaylists};
path: xbmc/filesystem/DiscDirectoryHelper.cpp
user: login: 78andyp
body: Done
created_at: 2025-05-04T17:27:54Z
author_association: MEMBER
diff_hunk: @@ -255,267 +248,267 @@ void CDiscDirectoryHelper::FindGroups(const PlaylistMap& playlists)
   }
 }
 
-void CDiscDirectoryHelper::FindCandidatePlaylists(const std::vector<CVideoInfoTag>& episodesOnDisc,
-                                                  unsigned int episodeIndex,
-                                                  const PlaylistMap& playlists)
+void CDiscDirectoryHelper::UsePlayAllPlaylistMethod(unsigned int episodeIndex,
+                                                    const PlaylistMap& playlists)
 {
-  // At this stage we have a number of ways of trying to determine the correct playlist for an episode
-  //
-  // 1) Using a 'Play All' playlist, if present.
-  //
-  // 2a) Using the longest playlists that are consecutive, using the playlist in the nth position.
-  //
-  // 2b) For single episode discs, look for the longest playlist.
-  //    There are some discs where there are extras that are longer than the episode itself, in n this case
-  //    we look for the playlist with a common starting number (eg. 1, 800 etc..)
+  CLog::LogF(LOGDEBUG, "Using Play All playlist method");
 
-  bool foundEpisode{false};
-  bool findIdenticalEpisodes{false};
+  // Get the playlist
+  const unsigned int playAllPlaylist{*m_playAllPlaylists.begin()};
 
-  if (m_allEpisodes == AllEpisodes::ALL)
-    CLog::LogF(LOGDEBUG, "Looking for all episodes on disc");
-
-  // Method 1 - Play all playlist
-  if (m_playAllPlaylists.size() == 1)
+  if (!playlists.contains(playAllPlaylist))
   {
-    CLog::LogF(LOGDEBUG, "Using Play All playlist method");
+    CLog::LogF(LOGERROR, "Play all playlist {} missing in playlist map", playAllPlaylist);
+    return;
+  }
 
-    // Get the playlist
-    const unsigned int playAllPlaylist{*m_playAllPlaylists.begin()};
+  // Get the potential single episodes from the map
+  const auto& [playlist, playlistInformation] = *playlists.find(playAllPlaylist);
+  CLog::LogF(LOGDEBUG, "Using candidate play all playlist {} duration {}", playlist,
+             static_cast<int>(playlistInformation.duration.count() / 1000));
 
-    if (!playlists.contains(playAllPlaylist))
+  // Find the clip for the episode(s)
+  unsigned int i{0};
+  for (const auto& clip : playlistInformation.clips)
+  {
+    if (m_allEpisodes == AllEpisodes::ALL ||
+        i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
     {
-      CLog::LogF(LOGERROR, "Play all playlist {} missing in playlist map", playAllPlaylist);
-      return;
-    }
+      if (!m_playAllPlaylistsMap[playAllPlaylist].contains(clip))
+      {
+        CLog::LogF(LOGERROR, "Clip {} missing in play all playlist map", clip);
+        return;
+      }
 
-    // Get the potential single episodes from the map
-    const auto& [playlist, playlistInformation] = *playlists.find(playAllPlaylist);
-    CLog::LogF(LOGDEBUG, "Using candidate play all playlist {} duration {}", playlist,
-               static_cast<int>(playlistInformation.duration.count() / 1000));
+      CLog::LogF(LOGDEBUG, "Clip is {}", clip);
 
-    // Find the clip for the episode(s)
-    unsigned int i{0};
-    for (const auto& clip : playlistInformation.clips)
-    {
-      if (m_allEpisodes == AllEpisodes::ALL ||
-          i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
+      // Find playlist(s) with that clip from map populated earlier
+      const auto& singleEpisodePlaylists{m_playAllPlaylistsMap[playAllPlaylist].find(clip)->second};
+
+      for (const auto& singleEpisodePlaylist : singleEpisodePlaylists)
       {
-        if (!m_playAllPlaylistsMap[playAllPlaylist].contains(clip))
+        if (!playlists.contains(singleEpisodePlaylist))
         {
-          CLog::LogF(LOGERROR, "Clip {} missing in play all playlist map", clip);
+          CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
+                     singleEpisodePlaylist);
           return;
         }
+        // Get playlist information
+        const PlaylistInfo& singleEpisodePlaylistInformation{
+            playlists.find(singleEpisodePlaylist)->second};
+
+        CLog::LogF(LOGDEBUG, "Candidate playlist {} duration {}", singleEpisodePlaylist,
+                   static_cast<int>(singleEpisodePlaylistInformation.duration.count() / 1000));
 
-        CLog::LogF(LOGDEBUG, "Clip is {}", clip);
+        m_candidatePlaylists.insert(
+            {singleEpisodePlaylist, i + m_numSpecials}); // Also save episodeIndex for all episodes
+      }
+    }
+    ++i;
+  }
+}
 
-        // Find playlist(s) with that clip from map populated earlier
-        const auto& singleEpisodePlaylists{
-            m_playAllPlaylistsMap[playAllPlaylist].find(clip)->second};
+void CDiscDirectoryHelper::UseLongOrCommonMethodForSingleEpisode(unsigned int episodeIndex,
+                                                                 const PlaylistMap& playlists)
+{
+  // Method 2b - Need to think about the special case of only one episode on disc
 
-        for (const auto& singleEpisodePlaylist : singleEpisodePlaylists)
-        {
-          if (!playlists.contains(singleEpisodePlaylist))
-          {
-            CLog::LogF(LOGERROR, "Single episode playlist {} missing in playlist map",
-                       singleEpisodePlaylist);
-            return;
-          }
-          // Get playlist information
-          const PlaylistInfo& singleEpisodePlaylistInformation{
-              playlists.find(singleEpisodePlaylist)->second};
+  // Sort playlists by length
+  PlaylistVector playlists_length;
+  playlists_length.reserve(playlists.size());
+  playlists_length.assign(playlists.begin(), playlists.end());
+  std::ranges::sort(playlists_length,
+                    [](const PlaylistVectorEntry& i, const PlaylistVectorEntry& j)
+                    {
+                      const auto& [i_playlist, i_playlistInformation] = i;
+                      const auto& [j_playlist, j_playlistInformation] = j;
+                      if (i_playlistInformation.duration == j_playlistInformation.duration)
+                        return i_playlist < j_playlist;
+                      return i_playlistInformation.duration > j_playlistInformation.duration;
+                    });
 
-          CLog::LogF(LOGDEBUG, "Candidate playlist {} duration {}", singleEpisodePlaylist,
-                     static_cast<int>(singleEpisodePlaylistInformation.duration.count() / 1000));
+  // Remove duplicate lengths (each episode may have more than one playlist with different languages)
+  const auto& [first, last] = std::ranges::unique(
+      playlists_length, {}, [](const PlaylistVectorEntry& i) { return i.second.duration; });
+  playlists_length.erase(first, last);
 
-          m_candidatePlaylists.insert(
-              {singleEpisodePlaylist,
-               i + m_numSpecials}); // Also save episodeIndex for all episodes
-        }
-      }
-      ++i;
-    }
-    foundEpisode = true;
-    findIdenticalEpisodes = true;
+  // See how many unique (different length) episode length(>= MIN_EPISODE_LENGTH) playlists there are
+  auto episodeLengthPlaylists{
+      playlists_length | std::views::filter([](const PlaylistVectorEntry& p)
+                                            { return p.second.duration >= MIN_EPISODE_DURATION; })};
+
+  // Look for common starting playlists
+  constexpr std::array<unsigned int, 5> commonStartingPlaylists = {801, 800, 1, 811, 0};
+  const auto& playlistsView{playlists | std::views::keys};
+  const auto commonPlaylist{std::ranges::find_first_of(playlistsView, commonStartingPlaylists)};
+
+  if (std::ranges::distance(episodeLengthPlaylists) == 1)
+  {
+    // If only one long playlist, then assume it's that
+    const unsigned int playlist{episodeLengthPlaylists.begin()->first};
+    CLog::LogF(LOGDEBUG, "Single Episode - found using single long playlist method");
+    CLog::LogF(LOGDEBUG, "Candidate playlist {}", playlist);
+    m_candidatePlaylists.insert({playlist, episodeIndex});
   }
+  else if (commonPlaylist != playlistsView.end())
+  {
+    // Found a common playlist, so assume it's that
+    CLog::LogF(LOGDEBUG, "Single Episode - found using common playlist method");
+    CLog::LogF(LOGDEBUG, "Candidate playlist {}", *commonPlaylist);
+    m_candidatePlaylists.insert({*commonPlaylist, episodeIndex});
+  }
+}
 
-  // Method 2 - Look for the longest playlists in groups, taking the nth playlist
-  if (!foundEpisode)
+void CDiscDirectoryHelper::UseGroupMethod(unsigned int episodeIndex)
+{
+  // Method 2a - More than one episode on disc
+
+  // Use groups and find nth playlist (or all for all episodes) in group
+  // Groups are already contain at least numEpisodes playlists of minimum duration
+  for (const auto& group : m_groups)
   {
-    CLog::LogF(LOGDEBUG, "Using longest playlists in groups method");
+    if (group.size() != m_numEpisodes)
+      continue;
 
-    if (m_numEpisodes == 1)
-    {
-      // Method 2b - Need to think about the special case of only one episode on disc
-
-      // Sort playlists by length
-      PlaylistVector playlists_length;
-      playlists_length.reserve(playlists.size());
-      playlists_length.assign(playlists.begin(), playlists.end());
-      std::ranges::sort(playlists_length,
-                        [](const PlaylistVectorEntry& i, const PlaylistVectorEntry& j)
-                        {
-                          const auto& [i_playlist, i_playlistInformation] = i;
-                          const auto& [j_playlist, j_playlistInformation] = j;
-                          if (i_playlistInformation.duration == j_playlistInformation.duration)
-                            return i_playlist < j_playlist;
-                          return i_playlistInformation.duration > j_playlistInformation.duration;
-                        });
-
-      // Remove duplicate lengths (each episode may have more than one playlist with different languages)
-      const auto& [first, last] = std::ranges::unique(
-          playlists_length, {}, [](const PlaylistVectorEntry& i) { return i.second.duration; });
-      playlists_length.erase(first, last);
-
-      // See how many unique (different length) episode length(>= MIN_EPISODE_LENGTH) playlists there are
-      auto episodeLengthPlaylists{
-          playlists_length |
-          std::views::filter([](const PlaylistVectorEntry& p)
-                             { return p.second.duration >= MIN_EPISODE_DURATION; })};
-
-      // Look for common starting playlists
-      constexpr std::array<unsigned int, 5> commonStartingPlaylists = {801, 800, 1, 811, 0};
-      const auto& playlistsView{playlists | std::views::keys};
-      const auto commonPlaylist{std::ranges::find_first_of(playlistsView, commonStartingPlaylists)};
-
-      if (std::ranges::distance(episodeLengthPlaylists) == 1)
-      {
-        // If only one long playlist, then assume it's that
-        const unsigned int playlist{episodeLengthPlaylists.begin()->first};
-        CLog::LogF(LOGDEBUG, "Single Episode - found using single long playlist method");
-        CLog::LogF(LOGDEBUG, "Candidate playlist {}", playlist);
-        m_candidatePlaylists.insert({playlist, episodeIndex});
-      }
-      else if (commonPlaylist != playlistsView.end())
-      {
-        // Found a common playlist, so assume it's that
-        CLog::LogF(LOGDEBUG, "Single Episode - found using common playlist method");
-        CLog::LogF(LOGDEBUG, "Candidate playlist {}", *commonPlaylist);
-        m_candidatePlaylists.insert({*commonPlaylist, episodeIndex});
-      }
-    }
-    else
+    for (unsigned int i = 0; i < m_numEpisodes; ++i)
     {
-      // Method 2a - More than one episode on disc
+      if (m_allEpisodes != AllEpisodes::ALL &&
+          i != episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
+        continue;
 
-      // Use groups and find nth playlist (or all for all episodes) in group
-      // Groups are already contain at least numEpisodes playlists of minimum duration
-      if (!m_groups.empty())
-      {
-        for (const auto& group : m_groups)
-        {
-          if (group.size() == m_numEpisodes)
-          {
-            for (unsigned int i = 0; i < m_numEpisodes; ++i)
-            {
-              if (m_allEpisodes == AllEpisodes::ALL ||
-                  i == episodeIndex - m_numSpecials) // Specials before episodes in episodesOnDisc
-              {
-                m_candidatePlaylists.insert(
-                    {group[i], i + m_numSpecials}); // Also save episodeIndex for all episodes
-                CLog::LogF(LOGDEBUG, "Candidate playlist {}", group[i]);
-              }
-            }
-          }
-        }
-        findIdenticalEpisodes = true;
-      }
+      m_candidatePlaylists.insert(
+          {group[i], i + m_numSpecials}); // Also save episodeIndex for all episodes
+      CLog::LogF(LOGDEBUG, "Candidate playlist {}", group[i]);
     }
   }
+}
 
-  // Now deal with the possibility there may be more than one playlist (per episode)
-  // For this, see which is closest in duration to the desired episode duration (from the scraper)
-  // If we are looking for a special then leave all potential episode playlists in array
-  if (m_candidatePlaylists.size() > 1 && m_isSpecial == IsSpecial::EPISODE)
+void CDiscDirectoryHelper::ChooseSingleBestPlaylist(
+    const std::vector<CVideoInfoTag>& episodesOnDisc, const PlaylistMap& playlists)
+{
+  // Rebuild candidatePlaylists
+  const auto oldCandidatePlaylists{m_candidatePlaylists};
+  m_candidatePlaylists.clear();
+
+  // Loop through each episode (in case of all episodes)
+  // Generate set of indexes of episode entry in episodesOnDisc
+  std::set<unsigned int> indexes;
+  for (unsigned int index : oldCandidatePlaylists | std::views::values)
+    indexes.insert(index);
path: xbmc/filesystem/DiscDirectoryHelper.cpp
user: login: 78andyp
body: Done
created_at: 2025-05-04T17:29:21Z
author_association: MEMBER
diff_hunk: @@ -1529,7 +1568,7 @@ void GetPlaylistsInformation(
           it->second.playlists.emplace_back(playlist);
         }
 
-        clipsStr += std::to_string(clip) + ",";
+        clipsStr += fmt::format("{},", clip);
       }
       if (!clipsStr.empty())
         clipsStr.pop_back(); // Remove last ','
path: xbmc/filesystem/BlurayDirectory.cpp
user: login: 78andyp
body: Done
created_at: 2025-05-04T17:42:10Z
author_association: MEMBER
diff_hunk: @@ -1557,7 +1596,7 @@ void GetPlaylistsInformation(
     std::string ps{StringUtils::Format("Clip {0:d} duration {1:d} - playlists ", clip,
                                        clipInformation.duration.count() / 1000)};
     for (const auto& playlist : clipInformation.playlists)
-      ps += std::to_string(playlist) + ",";
+      ps += fmt::format("{},", playlist);
     ps.pop_back(); // Remove last ','
     CLog::LogF(LOGDEBUG, "{}", ps);
path: xbmc/filesystem/BlurayDirectory.cpp
user: login: 78andyp
body: Done
created_at: 2025-05-04T17:42:15Z
author_association: MEMBER

PR comments:
user: login: 78andyp
created_at: 2025-05-04T17:18:53Z
body: > Note: I didn't try to understand what the code actually does, I only looked at style and syntax.

No worries, that's my job!!
author_association: MEMBER
user: login: 78andyp
created_at: 2025-05-05T00:37:36Z
body: @neo1973 thanks for the review. As I was going through it again I've made a few more optimisations to remove some .find usage in CDiscDirectoryHelper. I've made this a separate commit so it's clear what's been looked at before.
author_association: MEMBER
user: login: ksooo
created_at: 2025-05-06T05:41:54Z
body: > As I was going through it again I've made a few more optimisations to remove some .find usage in CDiscDirectoryHelper. I've made this a separate commit so it's clear what's been looked at before.

It would imo have been better to open a separate PR (or even two) for these new commits as to my understanding these commits do not fix any SonarQube findings, so strictly speaking you did scope creeping here. One of the commits even is bug fix, not even an optimization. 

author_association: MEMBER