FixAudioData.cs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. using System;
  2. using System.Globalization;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Threading;
  6. using Jellyfin.Data.Enums;
  7. using MediaBrowser.Controller;
  8. using MediaBrowser.Controller.Entities;
  9. using MediaBrowser.Controller.Entities.Audio;
  10. using MediaBrowser.Controller.Persistence;
  11. using MediaBrowser.Model.Entities;
  12. using Microsoft.Extensions.Logging;
  13. namespace Jellyfin.Server.Migrations.Routines
  14. {
  15. /// <summary>
  16. /// Fixes the data column of audio types to be deserializable.
  17. /// </summary>
  18. [JellyfinMigration("2025-04-20T18:00:00", nameof(FixAudioData), "CF6FABC2-9FBE-4933-84A5-FFE52EF22A58")]
  19. #pragma warning disable CS0618 // Type or member is obsolete
  20. internal class FixAudioData : IMigrationRoutine
  21. #pragma warning restore CS0618 // Type or member is obsolete
  22. {
  23. private const string DbFilename = "library.db";
  24. private readonly ILogger<FixAudioData> _logger;
  25. private readonly IServerApplicationPaths _applicationPaths;
  26. private readonly IItemRepository _itemRepository;
  27. public FixAudioData(
  28. IServerApplicationPaths applicationPaths,
  29. ILoggerFactory loggerFactory,
  30. IItemRepository itemRepository)
  31. {
  32. _applicationPaths = applicationPaths;
  33. _itemRepository = itemRepository;
  34. _logger = loggerFactory.CreateLogger<FixAudioData>();
  35. }
  36. /// <inheritdoc/>
  37. public void Perform()
  38. {
  39. var dbPath = Path.Combine(_applicationPaths.DataPath, DbFilename);
  40. // Back up the database before modifying any entries
  41. for (int i = 1; ; i++)
  42. {
  43. var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
  44. if (!File.Exists(bakPath))
  45. {
  46. try
  47. {
  48. _logger.LogInformation("Backing up {Library} to {BackupPath}", DbFilename, bakPath);
  49. File.Copy(dbPath, bakPath);
  50. _logger.LogInformation("{Library} backed up to {BackupPath}", DbFilename, bakPath);
  51. break;
  52. }
  53. catch (Exception ex)
  54. {
  55. _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
  56. throw;
  57. }
  58. }
  59. }
  60. _logger.LogInformation("Backfilling audio lyrics data to database.");
  61. var startIndex = 0;
  62. var records = _itemRepository.GetCount(new InternalItemsQuery
  63. {
  64. IncludeItemTypes = [BaseItemKind.Audio],
  65. });
  66. while (startIndex < records)
  67. {
  68. var results = _itemRepository.GetItemList(new InternalItemsQuery
  69. {
  70. IncludeItemTypes = [BaseItemKind.Audio],
  71. StartIndex = startIndex,
  72. Limit = 5000,
  73. SkipDeserialization = true
  74. })
  75. .Cast<Audio>()
  76. .ToList();
  77. foreach (var audio in results)
  78. {
  79. var lyricMediaStreams = audio.GetMediaStreams().Where(s => s.Type == MediaStreamType.Lyric).Select(s => s.Path).ToList();
  80. if (lyricMediaStreams.Count > 0)
  81. {
  82. audio.HasLyrics = true;
  83. audio.LyricFiles = lyricMediaStreams;
  84. }
  85. }
  86. _itemRepository.SaveItems(results, CancellationToken.None);
  87. startIndex += results.Count;
  88. _logger.LogInformation("Backfilled data for {UpdatedRecords} of {TotalRecords} audio records", startIndex, records);
  89. }
  90. }
  91. }
  92. }