Repository: xbmc/xbmc
File: xbmc/filesystem/AudioBookFileDirectory.cpp (C++)

Code before:
  {
    av_free(m_ioctx->buffer);
    av_free(m_ioctx);
  }
}

bool CAudioBookFileDirectory::GetDirectory(const CURL& url,
                                           CFileItemList &items)
{
  if (!m_fctx && !ContainsFiles(url))
    return true;

  std::string title;
  std::string author;
  std::string album;

  const int end_time_m4b_file =
      m_fctx->streams[0]->duration * av_q2d(m_fctx->streams[0]->time_base);
  const int end_time_mka_file = m_fctx->duration * av_q2d(av_get_time_base_q());

  AVDictionaryEntry* tag=nullptr;
  while ((tag = av_dict_get(m_fctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
  {
    if (StringUtils::CompareNoCase(tag->key, "title") == 0)
      title = tag->value;
    else if (StringUtils::CompareNoCase(tag->key, "album") == 0)
      album = tag->value;
    else if (StringUtils::CompareNoCase(tag->key, "artist") == 0)
      author = tag->value;
  }

  std::string thumb;
  if (m_fctx->nb_chapters > 1)
    thumb = IMAGE_FILES::URLFromFile(url.Get(), "music");

  for (size_t i=0;i<m_fctx->nb_chapters;++i)
  {
    tag=nullptr;
    std::string chaptitle = StringUtils::Format(g_localizeStrings.Get(25010), i + 1);
    std::string chapauthor;
    std::string chapalbum;
    while ((tag=av_dict_get(m_fctx->chapters[i]->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
    {
      if (StringUtils::CompareNoCase(tag->key, "title") == 0)
        chaptitle = tag->value;
      else if (StringUtils::CompareNoCase(tag->key, "artist") == 0)
        chapauthor = tag->value;
      else if (StringUtils::CompareNoCase(tag->key, "album") == 0)
        chapalbum = tag->value;
    }
    CFileItemPtr item(new CFileItem(url.Get(),false));

Review comment (suggestion):
I don't think the default item separator of " / " (and similar patterns with whitespace in the item separator) works correctly and round-trips strings.
ex multiple artists "a / b", split as "a " " b", because the "/" separator is used, then joined back together with " / " as "a  /  b" (extra spaces not easy to see with the proportional font here). Split/Join don't trim.
Adding m_musicItemSeparator to the front of separators should always work (or test for an exact match in separators instead of the current test for partial string)

Besides that, I don't understand the fine distinction between musicitemseparator and artistseparators and leave it to you, but the m_musicArtistSeparators advancedsettings seems to be ignored? (and the wiki page doesn't list the same values as the defaults in code)

Maybe a trim should be added in the function as a form of data cleanup.

Ideally the list of separators would be static so it's not constructed every time.

Code after:
    avformat_close_input(&m_fctx);
  if (m_ioctx)
  {
    av_free(m_ioctx->buffer);
    av_free(m_ioctx);
  }
}

bool CAudioBookFileDirectory::GetDirectory(const CURL& url,
                                           CFileItemList &items)
{
  if (!m_fctx && !ContainsFiles(url))
    return true;

  std::string title;
  std::string author;
  std::string album;
  std::string desc;
  std::vector<std::string> artistsort;
  std::vector<std::string> tagdata;
  std::vector<std::string> separators{" feat. ", " ft. ", " Feat. ", " Ft. ",  ";", ":",
                                      "|",       "#",     "/",       " with ", "&"};
  const std::string musicsep =
      CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator;
  if (musicsep.find_first_of(";/,&|#") == std::string::npos)
    separators.push_back(musicsep); // add custom music separator from as.xml

  const int end_time_m4b_file =
      m_fctx->streams[0]->duration * av_q2d(m_fctx->streams[0]->time_base);
  const int end_time_mka_file = m_fctx->duration * av_q2d(av_get_time_base_q());
  const bool isAudioBook = url.IsFileType("m4b");
  // Some tags are relevant to the whole album - these are read first
  CMusicInfoTag albumtag;

  AVDictionaryEntry* tag=nullptr;
  while ((tag = av_dict_get(m_fctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
  {
    if (isAudioBook)
    {
      if (StringUtils::CompareNoCase(tag->key, "title") == 0)
        title = tag->value;
      else if (StringUtils::CompareNoCase(tag->key, "album") == 0)
        album = tag->value;
      else if (StringUtils::CompareNoCase(tag->key, "artist") == 0)
        author = tag->value;
      else if (StringUtils::CompareNoCase(tag->key, "description") == 0)
        desc = tag->value;
    }
    else
    {
      std::string key = StringUtils::ToUpper(tag->key);