Stream custom live channels using your own media
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

140 lines
6.1 KiB

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Scheduling;
using LanguageExt;
using Microsoft.Extensions.Logging;
using static LanguageExt.Prelude;
namespace ErsatzTV.Core.Metadata
{
public class LocalMediaScanner : ILocalMediaScanner
{
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILocalStatisticsProvider _localStatisticsProvider;
private readonly ILogger<LocalMediaScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IPlayoutBuilder _playoutBuilder;
private readonly IPlayoutRepository _playoutRepository;
private readonly ISmartCollectionBuilder _smartCollectionBuilder;
public LocalMediaScanner(
IMediaItemRepository mediaItemRepository,
IPlayoutRepository playoutRepository,
ILocalStatisticsProvider localStatisticsProvider,
ILocalMetadataProvider localMetadataProvider,
ISmartCollectionBuilder smartCollectionBuilder,
IPlayoutBuilder playoutBuilder,
ILogger<LocalMediaScanner> logger)
{
_mediaItemRepository = mediaItemRepository;
_playoutRepository = playoutRepository;
_localStatisticsProvider = localStatisticsProvider;
_localMetadataProvider = localMetadataProvider;
_smartCollectionBuilder = smartCollectionBuilder;
_playoutBuilder = playoutBuilder;
_logger = logger;
}
public async Task<Unit> ScanLocalMediaSource(LocalMediaSource localMediaSource, string ffprobePath)
{
if (!Directory.Exists(localMediaSource.Folder))
{
_logger.LogWarning(
"Media source folder {Folder} does not exist; skipping scan",
localMediaSource.Folder);
return Unit.Default;
}
List<MediaItem> knownMediaItems = await _mediaItemRepository.GetAllByMediaSourceId(localMediaSource.Id);
var modifiedPlayoutIds = new List<int>();
// remove files that no longer exist
// add new files
// refresh metadata for any files where it is missing
var knownExtensions = new List<string>
{
".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".ogg", ".mp4", ".m4p", ".m4v",
".avi", ".wmv", ".mov", ".mkv"
};
var allFiles = Directory.GetFiles(localMediaSource.Folder, "*", SearchOption.AllDirectories)
.Filter(file => knownExtensions.Contains(Path.GetExtension(file)))
.ToSeq();
// check if the media item exists
(Seq<string> newFiles, Seq<MediaItem> existingMediaItems) = allFiles.Map(
s => Optional(knownMediaItems.Find(i => i.Path == s)).ToEither(s))
.Partition();
// TODO: flag as missing? delete after some period of time?
var removedMediaItems = knownMediaItems.Filter(i => !allFiles.Contains(i.Path)).ToSeq();
modifiedPlayoutIds.AddRange(await _playoutRepository.GetPlayoutIdsForMediaItems(removedMediaItems));
foreach (MediaItem mediaItem in removedMediaItems)
{
_logger.LogDebug("Removing missing local media item {MediaItem}", mediaItem.Path);
await _mediaItemRepository.Delete(mediaItem.Id);
}
// if exists, check if the file was modified
Seq<MediaItem> modifiedMediaItems = existingMediaItems.Filter(
mediaItem =>
{
DateTime lastWrite = File.GetLastWriteTimeUtc(mediaItem.Path);
bool modified = lastWrite > mediaItem.LastWriteTime.IfNone(DateTime.MinValue);
return modified || mediaItem.Metadata == null;
});
modifiedPlayoutIds.AddRange(await _playoutRepository.GetPlayoutIdsForMediaItems(modifiedMediaItems));
foreach (MediaItem mediaItem in modifiedMediaItems)
{
_logger.LogDebug("Refreshing metadata for media item {MediaItem}", mediaItem.Path);
await RefreshMetadata(mediaItem, ffprobePath);
}
// if new, add and store mtime, refresh metadata
var addedMediaItems = new Seq<MediaItem>();
foreach (string path in newFiles)
{
_logger.LogDebug("Adding new media item {MediaItem}", path);
var mediaItem = new MediaItem
{
MediaSourceId = localMediaSource.Id,
Path = path,
LastWriteTime = File.GetLastWriteTimeUtc(path)
};
await _mediaItemRepository.Add(mediaItem);
await RefreshMetadata(mediaItem, ffprobePath);
addedMediaItems.Add(mediaItem);
}
modifiedPlayoutIds.AddRange(await _playoutRepository.GetPlayoutIdsForMediaItems(addedMediaItems));
foreach (int playoutId in modifiedPlayoutIds.Distinct())
{
Option<Playout> maybePlayout = await _playoutRepository.GetFull(playoutId);
await maybePlayout.Match(
async playout =>
{
Playout result = await _playoutBuilder.BuildPlayoutItems(playout, true);
await _playoutRepository.Update(result);
},
Task.CompletedTask);
}
return Unit.Default;
}
private async Task RefreshMetadata(MediaItem mediaItem, string ffprobePath)
{
await _localStatisticsProvider.RefreshStatistics(ffprobePath, mediaItem);
await _localMetadataProvider.RefreshMetadata(mediaItem);
await _smartCollectionBuilder.RefreshSmartCollections(mediaItem);
}
}
}