M3UParser.cpp 3.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. /*
  2. * Copyright (c) 2021, Cesar Torres <shortanemoia@protonmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "M3UParser.h"
  7. #include <AK/OwnPtr.h>
  8. #include <AK/RefPtr.h>
  9. #include <AK/ScopeGuard.h>
  10. #include <AK/Utf8View.h>
  11. #include <LibCore/File.h>
  12. M3UParser::M3UParser()
  13. {
  14. }
  15. NonnullOwnPtr<M3UParser> M3UParser::from_file(StringView path)
  16. {
  17. auto file_result = Core::File::open(path, Core::File::OpenMode::Read).release_value_but_fixme_should_propagate_errors();
  18. auto contents = file_result->read_until_eof().release_value_but_fixme_should_propagate_errors();
  19. auto use_utf8 = path.ends_with(".m3u8"sv, CaseSensitivity::CaseInsensitive);
  20. return from_memory(DeprecatedString { contents, NoChomp }, use_utf8);
  21. }
  22. NonnullOwnPtr<M3UParser> M3UParser::from_memory(DeprecatedString const& m3u_contents, bool utf8)
  23. {
  24. auto parser = make<M3UParser>();
  25. VERIFY(!m3u_contents.is_empty() && !m3u_contents.is_whitespace());
  26. parser->m_m3u_raw_data = m3u_contents;
  27. parser->m_use_utf8 = utf8;
  28. return parser;
  29. }
  30. NonnullOwnPtr<Vector<M3UEntry>> M3UParser::parse(bool include_extended_info)
  31. {
  32. auto vec = make<Vector<M3UEntry>>();
  33. if (m_use_utf8) {
  34. // TODO: Implement M3U8 parsing
  35. TODO();
  36. return vec;
  37. }
  38. auto lines = m_m3u_raw_data.split_view('\n');
  39. bool has_extended_info_tag = include_extended_info && (lines[0] == "#EXTM3U");
  40. M3UExtendedInfo metadata_for_next_file {};
  41. for (auto& line : lines) {
  42. line = line.trim_whitespace();
  43. if (!has_extended_info_tag || !line.starts_with('#')) {
  44. vec->append({ line, metadata_for_next_file });
  45. metadata_for_next_file = {};
  46. continue;
  47. }
  48. auto tag = [&line](StringView tag_name) -> Optional<StringView> {
  49. if (line.starts_with(tag_name)) {
  50. auto value = line.substring_view(tag_name.length());
  51. VERIFY(!value.is_empty());
  52. return value;
  53. }
  54. return {};
  55. };
  56. if (auto ext_inf = tag("#EXTINF:"sv); ext_inf.has_value()) {
  57. auto separator = ext_inf.value().find(',');
  58. VERIFY(separator.has_value());
  59. auto seconds = ext_inf.value().substring_view(0, separator.value());
  60. VERIFY(!seconds.is_whitespace() && !seconds.is_null() && !seconds.is_empty());
  61. metadata_for_next_file.track_length_in_seconds = seconds.to_uint();
  62. auto display_name = ext_inf.value().substring_view(seconds.length() + 1);
  63. VERIFY(!display_name.is_empty() && !display_name.is_null() && !display_name.is_empty());
  64. metadata_for_next_file.track_display_title = display_name;
  65. // TODO: support the alternative, non-standard #EXTINF value of a key=value dictionary
  66. continue;
  67. }
  68. if (auto playlist = tag("#PLAYLIST:"sv); playlist.has_value())
  69. m_parsed_playlist_title = move(playlist.value());
  70. else if (auto ext_grp = tag("#EXTGRP:"sv); ext_grp.has_value())
  71. metadata_for_next_file.group_name = move(ext_grp.value());
  72. else if (auto ext_alb = tag("#EXTALB:"sv); ext_alb.has_value())
  73. metadata_for_next_file.album_title = move(ext_alb.value());
  74. else if (auto ext_art = tag("#EXTART:"sv); ext_art.has_value())
  75. metadata_for_next_file.album_artist = move(ext_art.value());
  76. else if (auto ext_genre = tag("#EXTGENRE:"sv); ext_genre.has_value())
  77. metadata_for_next_file.album_genre = move(ext_genre.value());
  78. // TODO: Support M3A files (M3U files with embedded mp3 files)
  79. }
  80. return vec;
  81. }