diff options
Diffstat (limited to 'MediaBrowser.LocalMetadata')
45 files changed, 4105 insertions, 0 deletions
diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs new file mode 100644 index 000000000..cc9bc7bed --- /dev/null +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata +{ + public abstract class BaseXmlProvider<T> : ILocalMetadataProvider<T>, IHasChangeMonitor + where T : IHasMetadata, new() + { + protected IFileSystem FileSystem; + + public async Task<LocalMetadataResult<T>> GetMetadata(ItemInfo info, CancellationToken cancellationToken) + { + var result = new LocalMetadataResult<T>(); + + var file = GetXmlFile(info, new DirectoryService(new NullLogger())); + + if (file == null) + { + return result; + } + + var path = file.FullName; + + await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + result.Item = new T(); + + Fetch(result, path, cancellationToken); + result.HasMetadata = true; + } + catch (FileNotFoundException) + { + result.HasMetadata = false; + } + catch (DirectoryNotFoundException) + { + result.HasMetadata = false; + } + finally + { + XmlProviderUtils.XmlParsingResourcePool.Release(); + } + + return result; + } + + protected abstract void Fetch(LocalMetadataResult<T> result, string path, CancellationToken cancellationToken); + + protected BaseXmlProvider(IFileSystem fileSystem) + { + FileSystem = fileSystem; + } + + protected abstract FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService); + + public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) + { + var file = GetXmlFile(new ItemInfo { IsInMixedFolder = item.IsInMixedFolder, Path = item.Path }, directoryService); + + if (file == null) + { + return false; + } + + return file.Exists && FileSystem.GetLastWriteTimeUtc(file) > date; + } + + public string Name + { + get + { + return "Media Browser Xml"; + } + } + } + + static class XmlProviderUtils + { + internal static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(4, 4); + } +} diff --git a/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs new file mode 100644 index 000000000..29fd76aa5 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; + +namespace MediaBrowser.LocalMetadata.Images +{ + public class CollectionFolderLocalImageProvider : ILocalImageFileProvider, IHasOrder + { + public string Name + { + get { return "Collection Folder Images"; } + } + + public bool Supports(IHasImages item) + { + return item is CollectionFolder && item.SupportsLocalMetadata; + } + + public int Order + { + get + { + // Run after LocalImageProvider + return 1; + } + } + + public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService) + { + var collectionFolder = (CollectionFolder)item; + + return new LocalImageProvider().GetImages(item, collectionFolder.PhysicalLocations, directoryService); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs new file mode 100644 index 000000000..f1e7426aa --- /dev/null +++ b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.LocalMetadata.Images +{ + public class EpisodeLocalLocalImageProvider : ILocalImageFileProvider + { + public string Name + { + get { return "Local Images"; } + } + + public bool Supports(IHasImages item) + { + return item is Episode && item.SupportsLocalMetadata; + } + + public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService) + { + var parentPath = Path.GetDirectoryName(item.Path); + + var parentPathFiles = directoryService.GetFileSystemEntries(parentPath); + + var nameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path); + + var files = GetFilesFromParentFolder(nameWithoutExtension, parentPathFiles); + + if (files.Count > 0) + { + return files; + } + + var metadataPath = Path.Combine(parentPath, "metadata"); + + if (parentPathFiles.Any(i => string.Equals(i.FullName, metadataPath, StringComparison.OrdinalIgnoreCase))) + { + return GetFilesFromParentFolder(nameWithoutExtension, directoryService.GetFiles(metadataPath)); + } + + return new List<LocalImageInfo>(); + } + + private List<LocalImageInfo> GetFilesFromParentFolder(string filenameWithoutExtension, IEnumerable<FileSystemInfo> parentPathFiles) + { + var thumbName = filenameWithoutExtension + "-thumb"; + + return parentPathFiles + .Where(i => + { + if (BaseItem.SupportedImageExtensions.Contains(i.Extension)) + { + var currentNameWithoutExtension = Path.GetFileNameWithoutExtension(i.Name); + + if (string.Equals(filenameWithoutExtension, currentNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (string.Equals(thumbName, currentNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + }) + .Select(i => new LocalImageInfo + { + FileInfo = (FileInfo)i, + Type = ImageType.Primary + }) + .ToList(); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Images/ImagesByNameImageProvider.cs b/MediaBrowser.LocalMetadata/Images/ImagesByNameImageProvider.cs new file mode 100644 index 000000000..3f84df462 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Images/ImagesByNameImageProvider.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.IO; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; + +namespace MediaBrowser.LocalMetadata.Images +{ + public class ImagesByNameImageProvider : ILocalImageFileProvider, IHasOrder + { + private readonly IFileSystem _fileSystem; + private readonly IServerConfigurationManager _config; + + public ImagesByNameImageProvider(IFileSystem fileSystem, IServerConfigurationManager config) + { + _fileSystem = fileSystem; + _config = config; + } + + public string Name + { + get { return "Images By Name"; } + } + + public bool Supports(IHasImages item) + { + return item is CollectionFolder; + } + + public int Order + { + get + { + // Run after LocalImageProvider, and after CollectionFolderImageProvider + return 2; + } + } + + public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService) + { + var name = _fileSystem.GetValidFilename(item.Name); + + var path = Path.Combine(_config.ApplicationPaths.GeneralPath, name); + + try + { + return new LocalImageProvider().GetImages(item, path, directoryService); + } + catch (DirectoryNotFoundException) + { + return new List<LocalImageInfo>(); + } + } + } +} diff --git a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs new file mode 100644 index 000000000..8c4f6247c --- /dev/null +++ b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; + +namespace MediaBrowser.LocalMetadata.Images +{ + public class InternalMetadataFolderImageProvider : ILocalImageFileProvider, IHasOrder + { + private readonly IServerConfigurationManager _config; + + public InternalMetadataFolderImageProvider(IServerConfigurationManager config) + { + _config = config; + } + + public string Name + { + get { return "Internal Images"; } + } + + public bool Supports(IHasImages item) + { + if (!item.IsSaveLocalMetadataEnabled()) + { + return true; + } + + // Extracted images will be saved in here + if (item is Audio) + { + return true; + } + + if (item.SupportsLocalMetadata) + { + return false; + } + + return true; + } + + public int Order + { + get + { + // Make sure this is last so that all other locations are scanned first + return 1000; + } + } + + public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService) + { + var path = _config.ApplicationPaths.GetInternalMetadataPath(item.Id); + + try + { + return new LocalImageProvider().GetImages(item, path, directoryService); + } + catch (DirectoryNotFoundException) + { + return new List<LocalImageInfo>(); + } + } + } +} diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs new file mode 100644 index 000000000..a5ef7977b --- /dev/null +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.LocalMetadata.Images +{ + public class LocalImageProvider : ILocalImageFileProvider + { + public string Name + { + get { return "Local Images"; } + } + + public int Order + { + get { return 0; } + } + + public bool Supports(IHasImages item) + { + if (item.SupportsLocalMetadata) + { + // Episode has it's own provider + if (item.IsOwnedItem || item is Episode || item is Audio) + { + return false; + } + + return true; + } + + if (item.LocationType == LocationType.Virtual) + { + var season = item as Season; + + if (season != null) + { + var series = season.Series; + + if (series != null && series.LocationType == LocationType.FileSystem) + { + return true; + } + } + } + + return false; + } + + private IEnumerable<FileSystemInfo> GetFiles(IHasImages item, bool includeDirectories, IDirectoryService directoryService) + { + if (item.LocationType != LocationType.FileSystem) + { + return new List<FileSystemInfo>(); + } + + var path = item.ContainingFolderPath; + + if (includeDirectories) + { + return directoryService.GetFileSystemEntries(path) + .Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase) || + (i.Attributes & FileAttributes.Directory) == FileAttributes.Directory); + } + + return directoryService.GetFiles(path) + .Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase)); + } + + public List<LocalImageInfo> GetImages(IHasImages item, IDirectoryService directoryService) + { + var files = GetFiles(item, true, directoryService).ToList(); + + var list = new List<LocalImageInfo>(); + + PopulateImages(item, list, files, true, directoryService); + + return list; + } + + public List<LocalImageInfo> GetImages(IHasImages item, string path, IDirectoryService directoryService) + { + return GetImages(item, new[] { path }, directoryService); + } + + public List<LocalImageInfo> GetImages(IHasImages item, IEnumerable<string> paths, IDirectoryService directoryService) + { + var files = paths.SelectMany(directoryService.GetFiles) + .Where(i => + { + var ext = i.Extension; + + return !string.IsNullOrEmpty(ext) && + BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); + }) + .ToList(); + + var list = new List<LocalImageInfo>(); + + PopulateImages(item, list, files, false, directoryService); + + return list; + } + + private void PopulateImages(IHasImages item, List<LocalImageInfo> images, List<FileSystemInfo> files, bool supportParentSeriesFiles, IDirectoryService directoryService) + { + var imagePrefix = string.Empty; + + var baseItem = item as BaseItem; + if (baseItem != null && baseItem.IsInMixedFolder) + { + imagePrefix = Path.GetFileNameWithoutExtension(item.Path) + "-"; + } + + PopulatePrimaryImages(item, images, files, imagePrefix); + PopulateBackdrops(item, images, files, imagePrefix, directoryService); + PopulateScreenshots(images, files, imagePrefix); + + AddImage(files, images, imagePrefix + "logo", ImageType.Logo); + AddImage(files, images, imagePrefix + "clearart", ImageType.Art); + AddImage(files, images, imagePrefix + "disc", ImageType.Disc); + AddImage(files, images, imagePrefix + "cdart", ImageType.Disc); + AddImage(files, images, imagePrefix + "box", ImageType.Box); + AddImage(files, images, imagePrefix + "back", ImageType.BoxRear); + AddImage(files, images, imagePrefix + "boxrear", ImageType.BoxRear); + AddImage(files, images, imagePrefix + "menu", ImageType.Menu); + + // Banner + AddImage(files, images, imagePrefix + "banner", ImageType.Banner); + + // Thumb + AddImage(files, images, imagePrefix + "thumb", ImageType.Thumb); + AddImage(files, images, imagePrefix + "landscape", ImageType.Thumb); + + if (supportParentSeriesFiles) + { + var season = item as Season; + + if (season != null) + { + PopulateSeasonImagesFromSeriesFolder(season, images, directoryService); + } + } + } + + private void PopulatePrimaryImages(IHasImages item, List<LocalImageInfo> images, List<FileSystemInfo> files, string imagePrefix) + { + AddImage(files, images, imagePrefix + "folder", ImageType.Primary); + AddImage(files, images, imagePrefix + "cover", ImageType.Primary); + AddImage(files, images, imagePrefix + "poster", ImageType.Primary); + AddImage(files, images, imagePrefix + "default", ImageType.Primary); + + // Support plex/xbmc convention + if (item is Series) + { + AddImage(files, images, imagePrefix + "show", ImageType.Primary); + } + + // Support plex/xbmc convention + if (item is Movie || item is MusicVideo || item is AdultVideo) + { + AddImage(files, images, imagePrefix + "movie", ImageType.Primary); + } + + if (!string.IsNullOrEmpty(item.Path)) + { + var name = Path.GetFileNameWithoutExtension(item.Path); + + if (!string.IsNullOrEmpty(name)) + { + AddImage(files, images, name, ImageType.Primary); + AddImage(files, images, name + "-poster", ImageType.Primary); + } + } + } + + private void PopulateBackdrops(IHasImages item, List<LocalImageInfo> images, List<FileSystemInfo> files, string imagePrefix, IDirectoryService directoryService) + { + PopulateBackdrops(images, files, imagePrefix, "backdrop", "backdrop", ImageType.Backdrop); + + if (!string.IsNullOrEmpty(item.Path)) + { + var name = Path.GetFileNameWithoutExtension(item.Path); + + if (!string.IsNullOrEmpty(name)) + { + AddImage(files, images, imagePrefix + name + "-fanart", ImageType.Backdrop); + } + } + + PopulateBackdrops(images, files, imagePrefix, "fanart", "fanart-", ImageType.Backdrop); + PopulateBackdrops(images, files, imagePrefix, "background", "background-", ImageType.Backdrop); + PopulateBackdrops(images, files, imagePrefix, "art", "art-", ImageType.Backdrop); + + var extraFanartFolder = files + .FirstOrDefault(i => string.Equals(i.Name, "extrafanart", StringComparison.OrdinalIgnoreCase)); + + if (extraFanartFolder != null) + { + PopulateBackdropsFromExtraFanart(extraFanartFolder.FullName, images, directoryService); + } + } + + private void PopulateBackdropsFromExtraFanart(string path, List<LocalImageInfo> images, IDirectoryService directoryService) + { + var imageFiles = directoryService.GetFiles(path) + .Where(i => + { + var extension = i.Extension; + + if (string.IsNullOrEmpty(extension)) + { + return false; + } + + return BaseItem.SupportedImageExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + }); + + images.AddRange(imageFiles.Select(i => new LocalImageInfo + { + FileInfo = i, + Type = ImageType.Backdrop + })); + } + + private void PopulateScreenshots(List<LocalImageInfo> images, List<FileSystemInfo> files, string imagePrefix) + { + PopulateBackdrops(images, files, imagePrefix, "screenshot", "screenshot", ImageType.Screenshot); + } + + private void PopulateBackdrops(List<LocalImageInfo> images, List<FileSystemInfo> files, string imagePrefix, string firstFileName, string subsequentFileNamePrefix, ImageType type) + { + AddImage(files, images, imagePrefix + firstFileName, type); + + var unfound = 0; + for (var i = 1; i <= 20; i++) + { + // Screenshot Image + var found = AddImage(files, images, imagePrefix + subsequentFileNamePrefix + i, type); + + if (!found) + { + unfound++; + + if (unfound >= 3) + { + break; + } + } + } + } + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private void PopulateSeasonImagesFromSeriesFolder(Season season, List<LocalImageInfo> images, IDirectoryService directoryService) + { + var seasonNumber = season.IndexNumber; + + var series = season.Series; + if (!seasonNumber.HasValue || series.LocationType != LocationType.FileSystem) + { + return; + } + + var seriesFiles = GetFiles(series, false, directoryService).ToList(); + + // Try using the season name + var prefix = season.Name.ToLower().Replace(" ", string.Empty); + + var filenamePrefixes = new List<string> { prefix }; + + var seasonMarker = seasonNumber.Value == 0 + ? "-specials" + : seasonNumber.Value.ToString("00", _usCulture); + + // Get this one directly from the file system since we have to go up a level + if (!string.Equals(prefix, seasonMarker, StringComparison.OrdinalIgnoreCase)) + { + filenamePrefixes.Add("season" + seasonMarker); + } + + foreach (var filename in filenamePrefixes) + { + AddImage(seriesFiles, images, filename + "-poster", ImageType.Primary); + AddImage(seriesFiles, images, filename + "-fanart", ImageType.Backdrop); + AddImage(seriesFiles, images, filename + "-banner", ImageType.Banner); + AddImage(seriesFiles, images, filename + "-landscape", ImageType.Thumb); + } + } + + private bool AddImage(IEnumerable<FileSystemInfo> files, List<LocalImageInfo> images, string name, ImageType type) + { + var image = GetImage(files, name) as FileInfo; + + if (image != null) + { + images.Add(new LocalImageInfo + { + FileInfo = image, + Type = type + }); + + return true; + } + + return false; + } + + private FileSystemInfo GetImage(IEnumerable<FileSystemInfo> files, string name) + { + var candidates = files + .Where(i => string.Equals(name, Path.GetFileNameWithoutExtension(i.Name), StringComparison.OrdinalIgnoreCase)) + .ToList(); + + return BaseItem.SupportedImageExtensions + .Select(i => candidates.FirstOrDefault(c => string.Equals(c.Extension, i, StringComparison.OrdinalIgnoreCase))) + .FirstOrDefault(i => i != null); + } + } +} diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj new file mode 100644 index 000000000..0d2c0b97f --- /dev/null +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>MediaBrowser.LocalMetadata</RootNamespace> + <AssemblyName>MediaBrowser.LocalMetadata</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="BaseXmlProvider.cs" /> + <Compile Include="Images\CollectionFolderImageProvider.cs" /> + <Compile Include="Images\EpisodeLocalImageProvider.cs" /> + <Compile Include="Images\ImagesByNameImageProvider.cs" /> + <Compile Include="Images\InternalMetadataFolderImageProvider.cs" /> + <Compile Include="Images\LocalImageProvider.cs" /> + <Compile Include="Parsers\BoxSetXmlParser.cs" /> + <Compile Include="Parsers\EpisodeXmlParser.cs" /> + <Compile Include="Parsers\GameSystemXmlParser.cs" /> + <Compile Include="Parsers\GameXmlParser.cs" /> + <Compile Include="Parsers\MovieXmlParser.cs" /> + <Compile Include="Parsers\MusicVideoXmlParser.cs" /> + <Compile Include="Parsers\SeasonXmlParser.cs" /> + <Compile Include="Parsers\SeriesXmlParser.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Providers\AdultVideoXmlProvider.cs" /> + <Compile Include="Providers\AlbumXmlProvider.cs" /> + <Compile Include="Providers\ArtistXmlProvider.cs" /> + <Compile Include="Providers\BoxSetXmlProvider.cs" /> + <Compile Include="Providers\ChannelXmlProvider.cs" /> + <Compile Include="Providers\EpisodeXmlProvider.cs" /> + <Compile Include="Providers\FolderXmlProvider.cs" /> + <Compile Include="Providers\GameSystemXmlProvider.cs" /> + <Compile Include="Providers\GameXmlProvider.cs" /> + <Compile Include="Providers\MovieXmlProvider.cs" /> + <Compile Include="Providers\MusicVideoXmlProvider.cs" /> + <Compile Include="Providers\PersonXmlProvider.cs" /> + <Compile Include="Providers\SeasonXmlProvider.cs" /> + <Compile Include="Providers\SeriesXmlProvider.cs" /> + <Compile Include="Providers\TrailerXmlProvider.cs" /> + <Compile Include="Providers\VideoXmlProvider.cs" /> + <Compile Include="Savers\AlbumXmlSaver.cs" /> + <Compile Include="Savers\ArtistXmlSaver.cs" /> + <Compile Include="Savers\BoxSetXmlSaver.cs" /> + <Compile Include="Savers\ChannelXmlSaver.cs" /> + <Compile Include="Savers\EpisodeXmlSaver.cs" /> + <Compile Include="Savers\FolderXmlSaver.cs" /> + <Compile Include="Savers\GameSystemXmlSaver.cs" /> + <Compile Include="Savers\GameXmlSaver.cs" /> + <Compile Include="Savers\MovieXmlSaver.cs" /> + <Compile Include="Savers\PersonXmlSaver.cs" /> + <Compile Include="Savers\SeasonXmlSaver.cs" /> + <Compile Include="Savers\SeriesXmlSaver.cs" /> + <Compile Include="Savers\XmlSaverHelpers.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> + <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project> + <Name>MediaBrowser.Common</Name> + </ProjectReference> + <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj"> + <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project> + <Name>MediaBrowser.Controller</Name> + </ProjectReference> + <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> + <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> + <Name>MediaBrowser.Model</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs new file mode 100644 index 000000000..51a4684d7 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Xml; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Parsers +{ + public class BoxSetXmlParser : BaseItemXmlParser<BoxSet> + { + private readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + public BoxSetXmlParser(ILogger logger) + : base(logger) + { + } + + protected override void FetchDataFromXmlNode(XmlReader reader, BoxSet item) + { + switch (reader.Name) + { + case "CollectionItems": + + using (var subReader = reader.ReadSubtree()) + { + FetchFromCollectionItemsNode(subReader, item); + } + break; + + default: + base.FetchDataFromXmlNode(reader, item); + break; + } + } + + private void FetchFromCollectionItemsNode(XmlReader reader, BoxSet item) + { + reader.MoveToContent(); + + var list = new List<LinkedChild>(); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "CollectionItem": + { + using (var subReader = reader.ReadSubtree()) + { + var child = GetLinkedChild(subReader); + + if (child != null) + { + list.Add(child); + } + } + + break; + } + + default: + reader.Skip(); + break; + } + } + } + + item.LinkedChildren = list; + } + + private LinkedChild GetLinkedChild(XmlReader reader) + { + reader.MoveToContent(); + + var linkedItem = new LinkedChild + { + Type = LinkedChildType.Manual + }; + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Name": + { + linkedItem.ItemName = reader.ReadElementContentAsString(); + break; + } + + case "Type": + { + linkedItem.ItemType = reader.ReadElementContentAsString(); + break; + } + + case "Year": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval)) + { + linkedItem.ItemYear = rval; + } + } + + break; + } + + default: + reader.Skip(); + break; + } + } + } + + return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem; + } + } +} diff --git a/MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs new file mode 100644 index 000000000..8430f3b3c --- /dev/null +++ b/MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Xml; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Parsers +{ + /// <summary> + /// Class EpisodeXmlParser + /// </summary> + public class EpisodeXmlParser : BaseItemXmlParser<Episode> + { + private List<LocalImageInfo> _imagesFound; + private List<ChapterInfo> _chaptersFound; + + public EpisodeXmlParser(ILogger logger) + : base(logger) + { + } + + private string _xmlPath; + + public void Fetch(Episode item, + List<LocalImageInfo> images, + List<ChapterInfo> chapters, + string metadataFile, + CancellationToken cancellationToken) + { + _imagesFound = images; + _chaptersFound = chapters; + _xmlPath = metadataFile; + + Fetch(item, metadataFile, cancellationToken); + } + + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// <summary> + /// Fetches the data from XML node. + /// </summary> + /// <param name="reader">The reader.</param> + /// <param name="item">The item.</param> + protected override void FetchDataFromXmlNode(XmlReader reader, Episode item) + { + switch (reader.Name) + { + case "Chapters": + + _chaptersFound.AddRange(FetchChaptersFromXmlNode(item, reader.ReadSubtree())); + break; + + case "Episode": + + //MB generated metadata is within an "Episode" node + using (var subTree = reader.ReadSubtree()) + { + subTree.MoveToContent(); + + // Loop through each element + while (subTree.Read()) + { + if (subTree.NodeType == XmlNodeType.Element) + { + FetchDataFromXmlNode(subTree, item); + } + } + + } + break; + + case "filename": + { + var filename = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(filename)) + { + // Strip off everything but the filename. Some metadata tools like MetaBrowser v1.0 will have an 'episodes' prefix + // even though it's actually using the metadata folder. + filename = Path.GetFileName(filename); + + var parentFolder = Path.GetDirectoryName(_xmlPath); + filename = Path.Combine(parentFolder, filename); + var file = new FileInfo(filename); + + if (file.Exists) + { + _imagesFound.Add(new LocalImageInfo + { + Type = ImageType.Primary, + FileInfo = file + }); + } + } + break; + } + case "SeasonNumber": + { + var number = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(number)) + { + int num; + + if (int.TryParse(number, out num)) + { + item.ParentIndexNumber = num; + } + } + break; + } + + case "EpisodeNumber": + { + var number = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(number)) + { + int num; + + if (int.TryParse(number, out num)) + { + item.IndexNumber = num; + } + } + break; + } + + case "EpisodeNumberEnd": + { + var number = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(number)) + { + int num; + + if (int.TryParse(number, out num)) + { + item.IndexNumberEnd = num; + } + } + break; + } + + case "absolute_number": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + // int.TryParse is local aware, so it can be probamatic, force us culture + if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval)) + { + item.AbsoluteEpisodeNumber = rval; + } + } + + break; + } + case "DVD_episodenumber": + { + var number = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(number)) + { + float num; + + if (float.TryParse(number, NumberStyles.Any, UsCulture, out num)) + { + item.DvdEpisodeNumber = num; + } + } + break; + } + + case "DVD_season": + { + var number = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(number)) + { + float num; + + if (float.TryParse(number, NumberStyles.Any, UsCulture, out num)) + { + item.DvdSeasonNumber = Convert.ToInt32(num); + } + } + break; + } + + case "airsbefore_episode": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + // int.TryParse is local aware, so it can be probamatic, force us culture + if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval)) + { + item.AirsBeforeEpisodeNumber = rval; + } + } + + break; + } + + case "airsafter_season": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + // int.TryParse is local aware, so it can be probamatic, force us culture + if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval)) + { + item.AirsAfterSeasonNumber = rval; + } + } + + break; + } + + case "airsbefore_season": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + // int.TryParse is local aware, so it can be probamatic, force us culture + if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval)) + { + item.AirsBeforeSeasonNumber = rval; + } + } + + break; + } + + case "EpisodeName": + { + var name = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(name)) + { + item.Name = name; + } + break; + } + + + default: + base.FetchDataFromXmlNode(reader, item); + break; + } + } + } +} diff --git a/MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs new file mode 100644 index 000000000..d449108c4 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs @@ -0,0 +1,64 @@ +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Parsers +{ + public class GameSystemXmlParser : BaseItemXmlParser<GameSystem> + { + public GameSystemXmlParser(ILogger logger) + : base(logger) + { + } + + private readonly Task _cachedTask = Task.FromResult(true); + public Task FetchAsync(GameSystem item, string metadataFile, CancellationToken cancellationToken) + { + Fetch(item, metadataFile, cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); + + return _cachedTask; + } + + /// <summary> + /// Fetches the data from XML node. + /// </summary> + /// <param name="reader">The reader.</param> + /// <param name="item">The item.</param> + protected override void FetchDataFromXmlNode(XmlReader reader, GameSystem item) + { + switch (reader.Name) + { + case "GameSystem": + { + var val = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(val)) + { + item.GameSystemName = val; + } + break; + } + + case "GamesDbId": + { + var val = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(val)) + { + item.SetProviderId(MetadataProviders.Gamesdb, val); + } + break; + } + + + default: + base.FetchDataFromXmlNode(reader, item); + break; + } + } + } +} diff --git a/MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs new file mode 100644 index 000000000..2caced8a9 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs @@ -0,0 +1,105 @@ +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Parsers +{ + /// <summary> + /// Class EpisodeXmlParser + /// </summary> + public class GameXmlParser : BaseItemXmlParser<Game> + { + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public GameXmlParser(ILogger logger) + : base(logger) + { + } + + private readonly Task _cachedTask = Task.FromResult(true); + public Task FetchAsync(Game item, string metadataFile, CancellationToken cancellationToken) + { + Fetch(item, metadataFile, cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); + + return _cachedTask; + } + + /// <summary> + /// Fetches the data from XML node. + /// </summary> + /// <param name="reader">The reader.</param> + /// <param name="item">The item.</param> + protected override void FetchDataFromXmlNode(XmlReader reader, Game item) + { + switch (reader.Name) + { + case "GameSystem": + { + var val = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(val)) + { + item.GameSystem = val; + } + break; + } + + case "GamesDbId": + { + var val = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(val)) + { + item.SetProviderId(MetadataProviders.Gamesdb, val); + } + break; + } + + case "NesBox": + { + var val = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(val)) + { + item.SetProviderId(MetadataProviders.NesBox, val); + } + break; + } + + case "NesBoxRom": + { + var val = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(val)) + { + item.SetProviderId(MetadataProviders.NesBoxRom, val); + } + break; + } + + case "Players": + { + var val = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(val)) + { + int num; + + if (int.TryParse(val, NumberStyles.Integer, _usCulture, out num)) + { + item.PlayersSupported = num; + } + } + break; + } + + + default: + base.FetchDataFromXmlNode(reader, item); + break; + } + } + } +} diff --git a/MediaBrowser.LocalMetadata/Parsers/MovieXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/MovieXmlParser.cs new file mode 100644 index 000000000..388a0d20d --- /dev/null +++ b/MediaBrowser.LocalMetadata/Parsers/MovieXmlParser.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Threading; +using System.Xml; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Parsers +{ + /// <summary> + /// Class EpisodeXmlParser + /// </summary> + public class MovieXmlParser : BaseItemXmlParser<Video> + { + private List<ChapterInfo> _chaptersFound; + + public MovieXmlParser(ILogger logger) + : base(logger) + { + } + + public void Fetch(Video item, + List<ChapterInfo> chapters, + string metadataFile, + CancellationToken cancellationToken) + { + _chaptersFound = chapters; + + Fetch(item, metadataFile, cancellationToken); + } + + /// <summary> + /// Fetches the data from XML node. + /// </summary> + /// <param name="reader">The reader.</param> + /// <param name="item">The item.</param> + protected override void FetchDataFromXmlNode(XmlReader reader, Video item) + { + switch (reader.Name) + { + case "TmdbCollectionName": + { + var val = reader.ReadElementContentAsString(); + var movie = item as Movie; + + if (!string.IsNullOrWhiteSpace(val) && movie != null) + { + movie.TmdbCollectionName = val; + } + + break; + } + + case "Chapters": + + _chaptersFound.AddRange(FetchChaptersFromXmlNode(item, reader.ReadSubtree())); + break; + + default: + base.FetchDataFromXmlNode(reader, item); + break; + } + } + } +} diff --git a/MediaBrowser.LocalMetadata/Parsers/MusicVideoXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/MusicVideoXmlParser.cs new file mode 100644 index 000000000..b88ff6c3a --- /dev/null +++ b/MediaBrowser.LocalMetadata/Parsers/MusicVideoXmlParser.cs @@ -0,0 +1,42 @@ +using System.Xml; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Parsers +{ + public class MusicVideoXmlParser : BaseItemXmlParser<MusicVideo> + { + /// <summary> + /// Initializes a new instance of the <see cref="BaseItemXmlParser{T}" /> class. + /// </summary> + /// <param name="logger">The logger.</param> + public MusicVideoXmlParser(ILogger logger) + : base(logger) + { + } + + /// <summary> + /// Fetches the data from XML node. + /// </summary> + /// <param name="reader">The reader.</param> + /// <param name="item">The item.</param> + protected override void FetchDataFromXmlNode(XmlReader reader, MusicVideo item) + { + switch (reader.Name) + { + case "Artist": + item.Artist = reader.ReadElementContentAsString(); + break; + + case "Album": + item.Album = reader.ReadElementContentAsString(); + break; + + default: + base.FetchDataFromXmlNode(reader, item); + break; + } + } + } +} diff --git a/MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs new file mode 100644 index 000000000..62a7d37cf --- /dev/null +++ b/MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs @@ -0,0 +1,46 @@ +using System.Xml; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Parsers +{ + public class SeasonXmlParser : BaseItemXmlParser<Season> + { + public SeasonXmlParser(ILogger logger) + : base(logger) + { + } + + /// <summary> + /// Fetches the data from XML node. + /// </summary> + /// <param name="reader">The reader.</param> + /// <param name="item">The item.</param> + protected override void FetchDataFromXmlNode(XmlReader reader, Season item) + { + switch (reader.Name) + { + case "SeasonNumber": + { + var number = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(number)) + { + int num; + + if (int.TryParse(number, out num)) + { + item.IndexNumber = num; + } + } + break; + } + + default: + base.FetchDataFromXmlNode(reader, item); + break; + } + } + } +} diff --git a/MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs new file mode 100644 index 000000000..a3d45034e --- /dev/null +++ b/MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs @@ -0,0 +1,118 @@ +using System; +using System.Xml; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Parsers +{ + /// <summary> + /// Class SeriesXmlParser + /// </summary> + public class SeriesXmlParser : BaseItemXmlParser<Series> + { + /// <summary> + /// Initializes a new instance of the <see cref="BaseItemXmlParser{T}" /> class. + /// </summary> + /// <param name="logger">The logger.</param> + public SeriesXmlParser(ILogger logger) + : base(logger) + { + } + + /// <summary> + /// Fetches the data from XML node. + /// </summary> + /// <param name="reader">The reader.</param> + /// <param name="item">The item.</param> + protected override void FetchDataFromXmlNode(XmlReader reader, Series item) + { + switch (reader.Name) + { + case "Series": + //MB generated metadata is within a "Series" node + using (var subTree = reader.ReadSubtree()) + { + subTree.MoveToContent(); + + // Loop through each element + while (subTree.Read()) + { + if (subTree.NodeType == XmlNodeType.Element) + { + FetchDataFromXmlNode(subTree, item); + } + } + + } + break; + + case "id": + string id = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(id)) + { + item.SetProviderId(MetadataProviders.Tvdb, id); + } + break; + + case "Airs_DayOfWeek": + { + item.AirDays = TVUtils.GetAirDays(reader.ReadElementContentAsString()); + break; + } + + case "Airs_Time": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + item.AirTime = val; + } + break; + } + + case "AnimeSeriesIndex": + { + var number = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(number)) + { + int num; + + if (int.TryParse(number, out num)) + { + item.AnimeSeriesIndex = num; + } + } + break; + } + case "Status": + { + var status = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(status)) + { + SeriesStatus seriesStatus; + if (Enum.TryParse(status, true, out seriesStatus)) + { + item.Status = seriesStatus; + } + else + { + Logger.Info("Unrecognized series status: " + status); + } + } + + break; + } + + default: + base.FetchDataFromXmlNode(reader, item); + break; + } + } + } +} diff --git a/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs b/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..dd1c89579 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MediaBrowser.LocalMetadata")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MediaBrowser.LocalMetadata")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1c669501-2113-493a-b0ed-f8fd26311941")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MediaBrowser.LocalMetadata/Providers/AdultVideoXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/AdultVideoXmlProvider.cs new file mode 100644 index 000000000..fa17d597d --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/AdultVideoXmlProvider.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.LocalMetadata.Parsers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + class AdultVideoXmlProvider : BaseXmlProvider<AdultVideo> + { + private readonly ILogger _logger; + + public AdultVideoXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<AdultVideo> result, string path, CancellationToken cancellationToken) + { + var chapters = new List<ChapterInfo>(); + + new MovieXmlParser(_logger).Fetch(result.Item, chapters, path, cancellationToken); + + result.Chapters = chapters; + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/AlbumXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/AlbumXmlProvider.cs new file mode 100644 index 000000000..646922769 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/AlbumXmlProvider.cs @@ -0,0 +1,30 @@ +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + public class AlbumXmlProvider : BaseXmlProvider<MusicAlbum> + { + private readonly ILogger _logger; + + public AlbumXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<MusicAlbum> result, string path, CancellationToken cancellationToken) + { + new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(result.Item, path, cancellationToken); + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return directoryService.GetFile(Path.Combine(info.Path, "album.xml")); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/ArtistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/ArtistXmlProvider.cs new file mode 100644 index 000000000..0b5ebfb11 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/ArtistXmlProvider.cs @@ -0,0 +1,30 @@ +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + class ArtistXmlProvider : BaseXmlProvider<MusicArtist> + { + private readonly ILogger _logger; + + public ArtistXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<MusicArtist> result, string path, CancellationToken cancellationToken) + { + new BaseItemXmlParser<MusicArtist>(_logger).Fetch(result.Item, path, cancellationToken); + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return directoryService.GetFile(Path.Combine(info.Path, "artist.xml")); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs new file mode 100644 index 000000000..871c2bd92 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs @@ -0,0 +1,34 @@ +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.LocalMetadata.Parsers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + /// <summary> + /// Class BoxSetXmlProvider. + /// </summary> + public class BoxSetXmlProvider : BaseXmlProvider<BoxSet> + { + private readonly ILogger _logger; + + public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<BoxSet> result, string path, CancellationToken cancellationToken) + { + new BoxSetXmlParser(_logger).Fetch(result.Item, path, cancellationToken); + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return directoryService.GetFile(Path.Combine(info.Path, "collection.xml")); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/ChannelXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/ChannelXmlProvider.cs new file mode 100644 index 000000000..78845487a --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/ChannelXmlProvider.cs @@ -0,0 +1,30 @@ +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + public class ChannelXmlProvider : BaseXmlProvider<LiveTvChannel> + { + private readonly ILogger _logger; + + public ChannelXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<LiveTvChannel> result, string path, CancellationToken cancellationToken) + { + new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(result.Item, path, cancellationToken); + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return directoryService.GetFile(Path.Combine(info.Path, "channel.xml")); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs new file mode 100644 index 000000000..dff3c1c07 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.LocalMetadata.Parsers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + public class EpisodeXmlProvider : BaseXmlProvider<Episode>, IHasOrder + { + private readonly ILogger _logger; + + public EpisodeXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<Episode> result, string path, CancellationToken cancellationToken) + { + var images = new List<LocalImageInfo>(); + var chapters = new List<ChapterInfo>(); + + new EpisodeXmlParser(_logger).Fetch(result.Item, images, chapters, path, cancellationToken); + + result.Images = images; + result.Chapters = chapters; + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + var metadataPath = Path.GetDirectoryName(info.Path); + metadataPath = Path.Combine(metadataPath, "metadata"); + + var metadataFile = Path.Combine(metadataPath, Path.ChangeExtension(Path.GetFileName(info.Path), ".xml")); + + return directoryService.GetFile(metadataFile); + } + + public int Order + { + get + { + // After Xbmc + return 1; + } + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/FolderXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/FolderXmlProvider.cs new file mode 100644 index 000000000..0a2b33744 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/FolderXmlProvider.cs @@ -0,0 +1,33 @@ +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + /// <summary> + /// Provides metadata for Folders and all subclasses by parsing folder.xml + /// </summary> + public class FolderXmlProvider : BaseXmlProvider<Folder> + { + private readonly ILogger _logger; + + public FolderXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<Folder> result, string path, CancellationToken cancellationToken) + { + new BaseItemXmlParser<Folder>(_logger).Fetch(result.Item, path, cancellationToken); + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return new FileInfo(Path.Combine(info.Path, "folder.xml")); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs new file mode 100644 index 000000000..dd486da1d --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs @@ -0,0 +1,31 @@ +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.LocalMetadata.Parsers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + public class GameSystemXmlProvider : BaseXmlProvider<GameSystem> + { + private readonly ILogger _logger; + + public GameSystemXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<GameSystem> result, string path, CancellationToken cancellationToken) + { + new GameSystemXmlParser(_logger).Fetch(result.Item, path, cancellationToken); + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return directoryService.GetFile(Path.Combine(info.Path, "gamesystem.xml")); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs new file mode 100644 index 000000000..681706321 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs @@ -0,0 +1,46 @@ +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.LocalMetadata.Parsers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + public class GameXmlProvider : BaseXmlProvider<Game> + { + private readonly ILogger _logger; + + public GameXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<Game> result, string path, CancellationToken cancellationToken) + { + new GameXmlParser(_logger).Fetch(result.Item, path, cancellationToken); + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + var fileInfo = FileSystem.GetFileSystemInfo(info.Path); + + var directoryInfo = fileInfo as DirectoryInfo; + + if (directoryInfo == null) + { + directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path)); + } + + var directoryPath = directoryInfo.FullName; + + var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml"); + + var file = new FileInfo(specificFile); + + return info.IsInMixedFolder || file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "game.xml")); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs new file mode 100644 index 000000000..6ba1912a5 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.LocalMetadata.Parsers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + public class MovieXmlProvider : BaseXmlProvider<Movie> + { + private readonly ILogger _logger; + + public MovieXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<Movie> result, string path, CancellationToken cancellationToken) + { + var chapters = new List<ChapterInfo>(); + + new MovieXmlParser(_logger).Fetch(result.Item, chapters, path, cancellationToken); + + result.Chapters = chapters; + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return GetXmlFileInfo(info, FileSystem); + } + + public static FileInfo GetXmlFileInfo(ItemInfo info, IFileSystem fileSystem) + { + var fileInfo = fileSystem.GetFileSystemInfo(info.Path); + + var directoryInfo = fileInfo as DirectoryInfo; + + if (directoryInfo == null) + { + directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path)); + } + + var directoryPath = directoryInfo.FullName; + + var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml"); + + var file = new FileInfo(specificFile); + + // In a mixed folder, only {moviename}.xml is supported + if (info.IsInMixedFolder) + { + return file; + } + + // If in it's own folder, prefer movie.xml, but allow the specific file as well + var movieFile = new FileInfo(Path.Combine(directoryPath, "movie.xml")); + + return movieFile.Exists ? movieFile : file; + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/MusicVideoXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/MusicVideoXmlProvider.cs new file mode 100644 index 000000000..6289dcb56 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/MusicVideoXmlProvider.cs @@ -0,0 +1,31 @@ +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.LocalMetadata.Parsers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + class MusicVideoXmlProvider : BaseXmlProvider<MusicVideo> + { + private readonly ILogger _logger; + + public MusicVideoXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<MusicVideo> result, string path, CancellationToken cancellationToken) + { + new MusicVideoXmlParser(_logger).Fetch(result.Item, path, cancellationToken); + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/PersonXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PersonXmlProvider.cs new file mode 100644 index 000000000..9f27d6c7d --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/PersonXmlProvider.cs @@ -0,0 +1,30 @@ +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + public class PersonXmlProvider : BaseXmlProvider<Person> + { + private readonly ILogger _logger; + + public PersonXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<Person> result, string path, CancellationToken cancellationToken) + { + new BaseItemXmlParser<Person>(_logger).Fetch(result.Item, path, cancellationToken); + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return directoryService.GetFile(Path.Combine(info.Path, "person.xml")); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs new file mode 100644 index 000000000..2320982c3 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs @@ -0,0 +1,44 @@ +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.LocalMetadata.Parsers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + /// <summary> + /// Class SeriesProviderFromXml + /// </summary> + public class SeasonXmlProvider : BaseXmlProvider<Season>, IHasOrder + { + private readonly ILogger _logger; + + public SeasonXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<Season> result, string path, CancellationToken cancellationToken) + { + new SeasonXmlParser(_logger).Fetch(result.Item, path, cancellationToken); + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return directoryService.GetFile(Path.Combine(info.Path, "season.xml")); + } + + public int Order + { + get + { + // After Xbmc + return 1; + } + } + } +} + diff --git a/MediaBrowser.LocalMetadata/Providers/SeriesXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/SeriesXmlProvider.cs new file mode 100644 index 000000000..311c10287 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/SeriesXmlProvider.cs @@ -0,0 +1,43 @@ +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Providers; +using MediaBrowser.LocalMetadata.Parsers; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + /// <summary> + /// Class SeriesProviderFromXml + /// </summary> + public class SeriesXmlProvider : BaseXmlProvider<Series>, IHasOrder + { + private readonly ILogger _logger; + + public SeriesXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<Series> result, string path, CancellationToken cancellationToken) + { + new SeriesXmlParser(_logger).Fetch(result.Item, path, cancellationToken); + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return directoryService.GetFile(Path.Combine(info.Path, "series.xml")); + } + + public int Order + { + get + { + // After Xbmc + return 1; + } + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/TrailerXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/TrailerXmlProvider.cs new file mode 100644 index 000000000..db3b2fcf0 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/TrailerXmlProvider.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.LocalMetadata.Parsers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + public class TrailerXmlProvider : BaseXmlProvider<Trailer> + { + private readonly ILogger _logger; + + public TrailerXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<Trailer> result, string path, CancellationToken cancellationToken) + { + var chapters = new List<ChapterInfo>(); + + new MovieXmlParser(_logger).Fetch(result.Item, chapters, path, cancellationToken); + + result.Chapters = chapters; + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Providers/VideoXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/VideoXmlProvider.cs new file mode 100644 index 000000000..25aa61baf --- /dev/null +++ b/MediaBrowser.LocalMetadata/Providers/VideoXmlProvider.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.LocalMetadata.Parsers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.LocalMetadata.Providers +{ + class VideoXmlProvider : BaseXmlProvider<Video> + { + private readonly ILogger _logger; + + public VideoXmlProvider(IFileSystem fileSystem, ILogger logger) + : base(fileSystem) + { + _logger = logger; + } + + protected override void Fetch(LocalMetadataResult<Video> result, string path, CancellationToken cancellationToken) + { + var chapters = new List<ChapterInfo>(); + + new MovieXmlParser(_logger).Fetch(result.Item, chapters, path, cancellationToken); + + result.Chapters = chapters; + } + + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + { + return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/AlbumXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/AlbumXmlSaver.cs new file mode 100644 index 000000000..05022464d --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/AlbumXmlSaver.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; + +namespace MediaBrowser.LocalMetadata.Savers +{ + class AlbumXmlSaver : IMetadataFileSaver + { + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is MusicAlbum && updateType >= ItemUpdateType.MetadataDownload; + } + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append("<Item>"); + + XmlSaverHelpers.AddCommonNodes((MusicAlbum)item, builder); + + builder.Append("</Item>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { }); + } + + /// <summary> + /// Gets the save path. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "album.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/ArtistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/ArtistXmlSaver.cs new file mode 100644 index 000000000..b932c5c7c --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/ArtistXmlSaver.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; + +namespace MediaBrowser.LocalMetadata.Savers +{ + class ArtistXmlSaver : IMetadataFileSaver + { + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is MusicArtist && updateType >= ItemUpdateType.MetadataDownload; + } + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append("<Item>"); + + XmlSaverHelpers.AddCommonNodes((MusicArtist)item, builder); + + builder.Append("</Item>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { }); + } + + /// <summary> + /// Gets the save path. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "artist.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs new file mode 100644 index 000000000..db7b40c7d --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; + +namespace MediaBrowser.LocalMetadata.Savers +{ + public class BoxSetXmlSaver : IMetadataFileSaver + { + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is BoxSet && updateType >= ItemUpdateType.MetadataDownload; + } + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append("<Item>"); + + XmlSaverHelpers.AddCommonNodes((BoxSet)item, builder); + + builder.Append("</Item>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { }); + } + + /// <summary> + /// Gets the save path. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "collection.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/ChannelXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/ChannelXmlSaver.cs new file mode 100644 index 000000000..3b7783012 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/ChannelXmlSaver.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; + +namespace MediaBrowser.LocalMetadata.Savers +{ + /// <summary> + /// Class PersonXmlSaver + /// </summary> + public class ChannelXmlSaver : IMetadataFileSaver + { + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is LiveTvChannel && updateType >= ItemUpdateType.MetadataDownload; + } + + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append("<Item>"); + + XmlSaverHelpers.AddCommonNodes((LiveTvChannel)item, builder); + + builder.Append("</Item>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> + { + }); + } + + /// <summary> + /// Gets the save path. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "channel.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/EpisodeXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/EpisodeXmlSaver.cs new file mode 100644 index 000000000..275ec2fe8 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/EpisodeXmlSaver.cs @@ -0,0 +1,151 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Security; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; + +namespace MediaBrowser.LocalMetadata.Savers +{ + public class EpisodeXmlSaver : IMetadataFileSaver + { + private readonly IItemRepository _itemRepository; + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public EpisodeXmlSaver(IItemRepository itemRepository) + { + _itemRepository = itemRepository; + } + + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is Episode && updateType >= ItemUpdateType.MetadataDownload; + } + + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var episode = (Episode)item; + + var builder = new StringBuilder(); + + builder.Append("<Item>"); + + if (!string.IsNullOrEmpty(item.Name)) + { + builder.Append("<EpisodeName>" + SecurityElement.Escape(episode.Name) + "</EpisodeName>"); + } + + if (episode.IndexNumber.HasValue) + { + builder.Append("<EpisodeNumber>" + SecurityElement.Escape(episode.IndexNumber.Value.ToString(_usCulture)) + "</EpisodeNumber>"); + } + + if (episode.IndexNumberEnd.HasValue) + { + builder.Append("<EpisodeNumberEnd>" + SecurityElement.Escape(episode.IndexNumberEnd.Value.ToString(_usCulture)) + "</EpisodeNumberEnd>"); + } + + if (episode.AirsAfterSeasonNumber.HasValue) + { + builder.Append("<airsafter_season>" + SecurityElement.Escape(episode.AirsAfterSeasonNumber.Value.ToString(_usCulture)) + "</airsafter_season>"); + } + if (episode.AirsBeforeEpisodeNumber.HasValue) + { + builder.Append("<airsbefore_episode>" + SecurityElement.Escape(episode.AirsBeforeEpisodeNumber.Value.ToString(_usCulture)) + "</airsbefore_episode>"); + } + if (episode.AirsBeforeSeasonNumber.HasValue) + { + builder.Append("<airsbefore_season>" + SecurityElement.Escape(episode.AirsBeforeSeasonNumber.Value.ToString(_usCulture)) + "</airsbefore_season>"); + } + + if (episode.ParentIndexNumber.HasValue) + { + builder.Append("<SeasonNumber>" + SecurityElement.Escape(episode.ParentIndexNumber.Value.ToString(_usCulture)) + "</SeasonNumber>"); + } + + if (episode.AbsoluteEpisodeNumber.HasValue) + { + builder.Append("<absolute_number>" + SecurityElement.Escape(episode.AbsoluteEpisodeNumber.Value.ToString(_usCulture)) + "</absolute_number>"); + } + + if (episode.DvdEpisodeNumber.HasValue) + { + builder.Append("<DVD_episodenumber>" + SecurityElement.Escape(episode.DvdEpisodeNumber.Value.ToString(_usCulture)) + "</DVD_episodenumber>"); + } + + if (episode.DvdSeasonNumber.HasValue) + { + builder.Append("<DVD_season>" + SecurityElement.Escape(episode.DvdSeasonNumber.Value.ToString(_usCulture)) + "</DVD_season>"); + } + + if (episode.PremiereDate.HasValue) + { + builder.Append("<FirstAired>" + SecurityElement.Escape(episode.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd")) + "</FirstAired>"); + } + + XmlSaverHelpers.AddCommonNodes(episode, builder); + XmlSaverHelpers.AddMediaInfo(episode, builder, _itemRepository); + + builder.Append("</Item>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> + { + "FirstAired", + "SeasonNumber", + "EpisodeNumber", + "EpisodeName", + "EpisodeNumberEnd", + "airsafter_season", + "airsbefore_episode", + "airsbefore_season", + "DVD_episodenumber", + "DVD_season", + "absolute_number" + }); + } + + /// <summary> + /// Gets the save path. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public string GetSavePath(IHasMetadata item) + { + var filename = Path.ChangeExtension(Path.GetFileName(item.Path), ".xml"); + + return Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs new file mode 100644 index 000000000..6dd65b69c --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; + +namespace MediaBrowser.LocalMetadata.Savers +{ + public class FolderXmlSaver : IMetadataFileSaver + { + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + if (item is Folder) + { + if (!(item is Series) && !(item is BoxSet) && !(item is MusicArtist) && !(item is MusicAlbum) && + !(item is Season) && + !(item is GameSystem)) + { + return updateType >= ItemUpdateType.MetadataDownload; + } + } + + return false; + } + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append("<Item>"); + + XmlSaverHelpers.AddCommonNodes((Folder)item, builder); + + builder.Append("</Item>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { }); + } + + /// <summary> + /// Gets the save path. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "folder.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/GameSystemXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/GameSystemXmlSaver.cs new file mode 100644 index 000000000..163c79ce2 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/GameSystemXmlSaver.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.IO; +using System.Security; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; + +namespace MediaBrowser.LocalMetadata.Savers +{ + public class GameSystemXmlSaver : IMetadataFileSaver + { + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is GameSystem && updateType >= ItemUpdateType.MetadataDownload; + } + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var gameSystem = (GameSystem)item; + + var builder = new StringBuilder(); + + builder.Append("<Item>"); + + if (!string.IsNullOrEmpty(gameSystem.GameSystemName)) + { + builder.Append("<GameSystem>" + SecurityElement.Escape(gameSystem.GameSystemName) + "</GameSystem>"); + } + + XmlSaverHelpers.AddCommonNodes(gameSystem, builder); + + builder.Append("</Item>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { }); + } + + /// <summary> + /// Gets the save path. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "gamesystem.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs new file mode 100644 index 000000000..7eeaa211f --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Security; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.LocalMetadata.Savers +{ + /// <summary> + /// Saves game.xml for games + /// </summary> + public class GameXmlSaver : IMetadataFileSaver + { + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is Game && updateType >= ItemUpdateType.MetadataDownload; + } + + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append("<Item>"); + + var game = (Game)item; + + if (game.PlayersSupported.HasValue) + { + builder.Append("<Players>" + SecurityElement.Escape(game.PlayersSupported.Value.ToString(UsCulture)) + "</Players>"); + } + + if (!string.IsNullOrEmpty(game.GameSystem)) + { + builder.Append("<GameSystem>" + SecurityElement.Escape(game.GameSystem) + "</GameSystem>"); + } + + var val = game.GetProviderId(MetadataProviders.NesBox); + + if (!string.IsNullOrEmpty(val)) + { + builder.Append("<NesBox>" + SecurityElement.Escape(val) + "</NesBox>"); + } + + val = game.GetProviderId(MetadataProviders.NesBoxRom); + + if (!string.IsNullOrEmpty(val)) + { + builder.Append("<NesBoxRom>" + SecurityElement.Escape(val) + "</NesBoxRom>"); + } + + XmlSaverHelpers.AddCommonNodes(game, builder); + + builder.Append("</Item>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> + { + "Players", + "GameSystem", + "NesBox", + "NesBoxRom" + }); + } + + public string GetSavePath(IHasMetadata item) + { + return GetGameSavePath((Game)item); + } + + public static string GetGameSavePath(Game item) + { + if (item.IsInMixedFolder) + { + return Path.ChangeExtension(item.Path, ".xml"); + } + + return Path.Combine(item.ContainingFolderPath, "game.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs new file mode 100644 index 000000000..ef81790a6 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs @@ -0,0 +1,133 @@ +using System.Collections.Generic; +using System.IO; +using System.Security; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; + +namespace MediaBrowser.LocalMetadata.Savers +{ + /// <summary> + /// Saves movie.xml for movies, trailers and music videos + /// </summary> + public class MovieXmlSaver : IMetadataFileSaver + { + private readonly IItemRepository _itemRepository; + + public MovieXmlSaver(IItemRepository itemRepository) + { + _itemRepository = itemRepository; + } + + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + var video = item as Video; + + // Check parent for null to avoid running this against things like video backdrops + if (video != null && !(item is Episode) && !video.IsOwnedItem) + { + return updateType >= ItemUpdateType.MetadataDownload; + } + + return false; + } + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var video = (Video)item; + + var builder = new StringBuilder(); + + builder.Append("<Title>"); + + XmlSaverHelpers.AddCommonNodes(video, builder); + + var musicVideo = item as MusicVideo; + + if (musicVideo != null) + { + if (!string.IsNullOrEmpty(musicVideo.Artist)) + { + builder.Append("<Artist>" + SecurityElement.Escape(musicVideo.Artist) + "</Artist>"); + } + if (!string.IsNullOrEmpty(musicVideo.Album)) + { + builder.Append("<Album>" + SecurityElement.Escape(musicVideo.Album) + "</Album>"); + } + } + + var movie = item as Movie; + + if (movie != null) + { + if (!string.IsNullOrEmpty(movie.TmdbCollectionName)) + { + builder.Append("<TmdbCollectionName>" + SecurityElement.Escape(movie.TmdbCollectionName) + "</TmdbCollectionName>"); + } + } + + XmlSaverHelpers.AddMediaInfo(video, builder, _itemRepository); + + builder.Append("</Title>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> + { + // Deprecated. No longer saving in this field. + "IMDBrating", + + // Deprecated. No longer saving in this field. + "Description", + + "Artist", + "Album", + "TmdbCollectionName" + }); + } + + public string GetSavePath(IHasMetadata item) + { + return GetMovieSavePath((Video)item); + } + + public static string GetMovieSavePath(Video item) + { + if (item.IsInMixedFolder) + { + return Path.ChangeExtension(item.Path, ".xml"); + } + + return Path.Combine(item.ContainingFolderPath, "movie.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs new file mode 100644 index 000000000..2ea60f47c --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.IO; +using System.Security; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; + +namespace MediaBrowser.LocalMetadata.Savers +{ + /// <summary> + /// Class PersonXmlSaver + /// </summary> + public class PersonXmlSaver : IMetadataFileSaver + { + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is Person && updateType >= ItemUpdateType.MetadataDownload; + } + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var person = (Person)item; + + var builder = new StringBuilder(); + + builder.Append("<Item>"); + + XmlSaverHelpers.AddCommonNodes(person, builder); + + if (!string.IsNullOrEmpty(person.PlaceOfBirth)) + { + builder.Append("<PlaceOfBirth>" + SecurityElement.Escape(person.PlaceOfBirth) + "</PlaceOfBirth>"); + } + + builder.Append("</Item>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> + { + "PlaceOfBirth" + }); + } + + /// <summary> + /// Gets the save path. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "person.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/SeasonXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/SeasonXmlSaver.cs new file mode 100644 index 000000000..b9908875d --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/SeasonXmlSaver.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Security; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; + +namespace MediaBrowser.LocalMetadata.Savers +{ + public class SeasonXmlSaver : IMetadataFileSaver + { + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + if (!(item is Season)) + { + return false; + } + + return updateType >= ItemUpdateType.MetadataDownload || (updateType >= ItemUpdateType.MetadataImport && File.Exists(GetSavePath(item))); + } + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append("<Item>"); + + var season = (Season)item; + + if (season.IndexNumber.HasValue) + { + builder.Append("<SeasonNumber>" + SecurityElement.Escape(season.IndexNumber.Value.ToString(_usCulture)) + "</SeasonNumber>"); + } + + XmlSaverHelpers.AddCommonNodes((Season)item, builder); + + builder.Append("</Item>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> + { + "SeasonNumber" + }); + } + + /// <summary> + /// Gets the save path. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "season.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/SeriesXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/SeriesXmlSaver.cs new file mode 100644 index 000000000..23ea52820 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/SeriesXmlSaver.cs @@ -0,0 +1,135 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Security; +using System.Text; +using System.Threading; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.LocalMetadata.Savers +{ + public class SeriesXmlSaver : IMetadataFileSaver + { + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is Series && updateType >= ItemUpdateType.MetadataDownload; + } + + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var series = (Series)item; + + var builder = new StringBuilder(); + + builder.Append("<Series>"); + + var tvdb = item.GetProviderId(MetadataProviders.Tvdb); + + if (!string.IsNullOrEmpty(tvdb)) + { + builder.Append("<id>" + SecurityElement.Escape(tvdb) + "</id>"); + } + + if (series.Status.HasValue) + { + builder.Append("<Status>" + SecurityElement.Escape(series.Status.Value.ToString()) + "</Status>"); + } + + if (series.Studios.Count > 0) + { + builder.Append("<Network>" + SecurityElement.Escape(series.Studios[0]) + "</Network>"); + } + + if (!string.IsNullOrEmpty(series.AirTime)) + { + builder.Append("<Airs_Time>" + SecurityElement.Escape(series.AirTime) + "</Airs_Time>"); + } + + if (series.AirDays != null) + { + if (series.AirDays.Count == 7) + { + builder.Append("<Airs_DayOfWeek>" + SecurityElement.Escape("Daily") + "</Airs_DayOfWeek>"); + } + else if (series.AirDays.Count > 0) + { + builder.Append("<Airs_DayOfWeek>" + SecurityElement.Escape(series.AirDays[0].ToString()) + "</Airs_DayOfWeek>"); + } + } + + if (series.PremiereDate.HasValue) + { + builder.Append("<FirstAired>" + SecurityElement.Escape(series.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd")) + "</FirstAired>"); + } + + if (series.AnimeSeriesIndex.HasValue) + { + builder.Append("<AnimeSeriesIndex>" + SecurityElement.Escape(series.AnimeSeriesIndex.Value.ToString(UsCulture)) + "</AnimeSeriesIndex>"); + } + + XmlSaverHelpers.AddCommonNodes(series, builder); + + builder.Append("</Series>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> + { + "id", + "Status", + "Network", + "Airs_Time", + "Airs_DayOfWeek", + "FirstAired", + + // Don't preserve old series node + "Series", + + "SeriesName", + + // Deprecated. No longer saving in this field. + "AnimeSeriesIndex" + }); + } + + /// <summary> + /// Gets the save path. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "series.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs new file mode 100644 index 000000000..c43875b04 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs @@ -0,0 +1,718 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security; +using System.Text; +using System.Xml; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.LocalMetadata.Savers +{ + /// <summary> + /// Class XmlHelpers + /// </summary> + public static class XmlSaverHelpers + { + private static readonly Dictionary<string, string> CommonTags = new[] { + + "Added", + "AspectRatio", + "AudioDbAlbumId", + "AudioDbArtistId", + "AwardSummary", + "BirthDate", + "Budget", + + // Deprecated. No longer saving in this field. + "certification", + + "Chapters", + "ContentRating", + "Countries", + "CustomRating", + "CriticRating", + "CriticRatingSummary", + "DeathDate", + "DisplayOrder", + "EndDate", + "Genres", + "Genre", + "GamesDbId", + + // Deprecated. No longer saving in this field. + "IMDB_ID", + + "IMDB", + + // Deprecated. No longer saving in this field. + "IMDbId", + + "Language", + "LocalTitle", + "LockData", + "LockedFields", + "Format3D", + "Metascore", + + // Deprecated. No longer saving in this field. + "MPAARating", + + "MusicBrainzArtistId", + "MusicBrainzAlbumArtistId", + "MusicBrainzAlbumId", + "MusicBrainzReleaseGroupId", + + // Deprecated. No longer saving in this field. + "MusicbrainzId", + + "Overview", + "ShortOverview", + "Persons", + "PlotKeywords", + "PremiereDate", + "ProductionYear", + "Rating", + "Revenue", + "RottenTomatoesId", + "RunningTime", + + // Deprecated. No longer saving in this field. + "Runtime", + + "SortTitle", + "Studios", + "Tags", + + // Deprecated. No longer saving in this field. + "TagLine", + + "Taglines", + "TMDbCollectionId", + "TMDbId", + + // Deprecated. No longer saving in this field. + "Trailer", + + "Trailers", + "TVcomId", + "TvDbId", + "Type", + "TVRageId", + "VoteCount", + "Website", + "Zap2ItId", + "CollectionItems" + + }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); + + /// <summary> + /// The us culture + /// </summary> + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// <summary> + /// Saves the specified XML. + /// </summary> + /// <param name="xml">The XML.</param> + /// <param name="path">The path.</param> + /// <param name="xmlTagsUsed">The XML tags used.</param> + public static void Save(StringBuilder xml, string path, List<string> xmlTagsUsed) + { + if (File.Exists(path)) + { + var position = xml.ToString().LastIndexOf("</", StringComparison.OrdinalIgnoreCase); + xml.Insert(position, GetCustomTags(path, xmlTagsUsed)); + } + + var xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(xml.ToString()); + + //Add the new node to the document. + xmlDocument.InsertBefore(xmlDocument.CreateXmlDeclaration("1.0", "UTF-8", "yes"), xmlDocument.DocumentElement); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + var wasHidden = false; + + var file = new FileInfo(path); + + // This will fail if the file is hidden + if (file.Exists) + { + if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + { + file.Attributes &= ~FileAttributes.Hidden; + + wasHidden = true; + } + } + + using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + using (var streamWriter = new StreamWriter(filestream, Encoding.UTF8)) + { + xmlDocument.Save(streamWriter); + } + } + + if (wasHidden) + { + file.Refresh(); + + // Add back the attribute + file.Attributes |= FileAttributes.Hidden; + } + } + + /// <summary> + /// Gets the custom tags. + /// </summary> + /// <param name="path">The path.</param> + /// <param name="xmlTagsUsed">The XML tags used.</param> + /// <returns>System.String.</returns> + private static string GetCustomTags(string path, List<string> xmlTagsUsed) + { + var settings = new XmlReaderSettings + { + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true, + ValidationType = ValidationType.None + }; + + var builder = new StringBuilder(); + + using (var streamReader = new StreamReader(path, Encoding.UTF8)) + { + // Use XmlReader for best performance + using (var reader = XmlReader.Create(streamReader, settings)) + { + reader.MoveToContent(); + + // Loop through each element + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + var name = reader.Name; + + if (!CommonTags.ContainsKey(name) && !xmlTagsUsed.Contains(name, StringComparer.OrdinalIgnoreCase)) + { + builder.AppendLine(reader.ReadOuterXml()); + } + else + { + reader.Skip(); + } + } + } + } + } + + return builder.ToString(); + } + + /// <summary> + /// Adds the common nodes. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="builder">The builder.</param> + public static void AddCommonNodes(BaseItem item, StringBuilder builder) + { + if (!string.IsNullOrEmpty(item.OfficialRating)) + { + builder.Append("<ContentRating>" + SecurityElement.Escape(item.OfficialRating) + "</ContentRating>"); + } + + builder.Append("<Added>" + SecurityElement.Escape(item.DateCreated.ToLocalTime().ToString("G")) + "</Added>"); + + builder.Append("<LockData>" + item.IsLocked.ToString().ToLower() + "</LockData>"); + + if (item.LockedFields.Count > 0) + { + builder.Append("<LockedFields>" + string.Join("|", item.LockedFields.Select(i => i.ToString()).ToArray()) + "</LockedFields>"); + } + + if (!string.IsNullOrEmpty(item.DisplayMediaType)) + { + builder.Append("<Type>" + SecurityElement.Escape(item.DisplayMediaType) + "</Type>"); + } + + var hasCriticRating = item as IHasCriticRating; + if (hasCriticRating != null) + { + if (hasCriticRating.CriticRating.HasValue) + { + builder.Append("<CriticRating>" + SecurityElement.Escape(hasCriticRating.CriticRating.Value.ToString(UsCulture)) + "</CriticRating>"); + } + + if (!string.IsNullOrEmpty(hasCriticRating.CriticRatingSummary)) + { + builder.Append("<CriticRatingSummary><![CDATA[" + hasCriticRating.CriticRatingSummary + "]]></CriticRatingSummary>"); + } + } + + if (!string.IsNullOrEmpty(item.Overview)) + { + builder.Append("<Overview><![CDATA[" + item.Overview + "]]></Overview>"); + } + + var hasShortOverview = item as IHasShortOverview; + if (hasShortOverview != null) + { + if (!string.IsNullOrEmpty(hasShortOverview.ShortOverview)) + { + builder.Append("<ShortOverview><![CDATA[" + hasShortOverview.ShortOverview + "]]></ShortOverview>"); + } + } + + if (!string.IsNullOrEmpty(item.CustomRating)) + { + builder.Append("<CustomRating>" + SecurityElement.Escape(item.CustomRating) + "</CustomRating>"); + } + + if (!string.IsNullOrEmpty(item.Name) && !(item is Episode)) + { + builder.Append("<LocalTitle>" + SecurityElement.Escape(item.Name) + "</LocalTitle>"); + } + + if (!string.IsNullOrEmpty(item.ForcedSortName)) + { + builder.Append("<SortTitle>" + SecurityElement.Escape(item.ForcedSortName) + "</SortTitle>"); + } + + if (item.PremiereDate.HasValue) + { + if (item is Person) + { + builder.Append("<BirthDate>" + SecurityElement.Escape(item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd")) + "</BirthDate>"); + } + else if (!(item is Episode)) + { + builder.Append("<PremiereDate>" + SecurityElement.Escape(item.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd")) + "</PremiereDate>"); + } + } + + if (item.EndDate.HasValue) + { + if (item is Person) + { + builder.Append("<DeathDate>" + SecurityElement.Escape(item.EndDate.Value.ToString("yyyy-MM-dd")) + "</DeathDate>"); + } + else if (!(item is Episode)) + { + builder.Append("<EndDate>" + SecurityElement.Escape(item.EndDate.Value.ToString("yyyy-MM-dd")) + "</EndDate>"); + } + } + + var hasTrailers = item as IHasTrailers; + if (hasTrailers != null) + { + if (hasTrailers.RemoteTrailers.Count > 0) + { + builder.Append("<Trailers>"); + + foreach (var trailer in hasTrailers.RemoteTrailers) + { + builder.Append("<Trailer>" + SecurityElement.Escape(trailer.Url) + "</Trailer>"); + } + + builder.Append("</Trailers>"); + } + } + + var hasProductionLocations = item as IHasProductionLocations; + if (hasProductionLocations != null) + { + if (hasProductionLocations.ProductionLocations.Count > 0) + { + builder.Append("<Countries>"); + + foreach (var name in hasProductionLocations.ProductionLocations) + { + builder.Append("<Country>" + SecurityElement.Escape(name) + "</Country>"); + } + + builder.Append("</Countries>"); + } + } + + var hasDisplayOrder = item as IHasDisplayOrder; + if (hasDisplayOrder != null && !string.IsNullOrEmpty(hasDisplayOrder.DisplayOrder)) + { + builder.Append("<DisplayOrder>" + SecurityElement.Escape(hasDisplayOrder.DisplayOrder) + "</DisplayOrder>"); + } + + var hasMetascore = item as IHasMetascore; + if (hasMetascore != null && hasMetascore.Metascore.HasValue) + { + builder.Append("<Metascore>" + SecurityElement.Escape(hasMetascore.Metascore.Value.ToString(UsCulture)) + "</Metascore>"); + } + + var hasAwards = item as IHasAwards; + if (hasAwards != null && !string.IsNullOrEmpty(hasAwards.AwardSummary)) + { + builder.Append("<AwardSummary>" + SecurityElement.Escape(hasAwards.AwardSummary) + "</AwardSummary>"); + } + + var hasBudget = item as IHasBudget; + if (hasBudget != null) + { + if (hasBudget.Budget.HasValue) + { + builder.Append("<Budget>" + SecurityElement.Escape(hasBudget.Budget.Value.ToString(UsCulture)) + "</Budget>"); + } + + if (hasBudget.Revenue.HasValue) + { + builder.Append("<Revenue>" + SecurityElement.Escape(hasBudget.Revenue.Value.ToString(UsCulture)) + "</Revenue>"); + } + } + + if (item.CommunityRating.HasValue) + { + builder.Append("<Rating>" + SecurityElement.Escape(item.CommunityRating.Value.ToString(UsCulture)) + "</Rating>"); + } + if (item.VoteCount.HasValue) + { + builder.Append("<VoteCount>" + SecurityElement.Escape(item.VoteCount.Value.ToString(UsCulture)) + "</VoteCount>"); + } + + if (item.ProductionYear.HasValue && !(item is Person)) + { + builder.Append("<ProductionYear>" + SecurityElement.Escape(item.ProductionYear.Value.ToString(UsCulture)) + "</ProductionYear>"); + } + + if (!string.IsNullOrEmpty(item.HomePageUrl)) + { + builder.Append("<Website>" + SecurityElement.Escape(item.HomePageUrl) + "</Website>"); + } + + var hasAspectRatio = item as IHasAspectRatio; + if (hasAspectRatio != null) + { + if (!string.IsNullOrEmpty(hasAspectRatio.AspectRatio)) + { + builder.Append("<AspectRatio>" + SecurityElement.Escape(hasAspectRatio.AspectRatio) + "</AspectRatio>"); + } + } + + var hasLanguage = item as IHasPreferredMetadataLanguage; + if (hasLanguage != null) + { + if (!string.IsNullOrEmpty(hasLanguage.PreferredMetadataLanguage)) + { + builder.Append("<Language>" + SecurityElement.Escape(hasLanguage.PreferredMetadataLanguage) + "</Language>"); + } + } + + // Use original runtime here, actual file runtime later in MediaInfo + var runTimeTicks = item.RunTimeTicks; + + if (runTimeTicks.HasValue) + { + var timespan = TimeSpan.FromTicks(runTimeTicks.Value); + + builder.Append("<RunningTime>" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + "</RunningTime>"); + } + + var imdb = item.GetProviderId(MetadataProviders.Imdb); + + if (!string.IsNullOrEmpty(imdb)) + { + builder.Append("<IMDB>" + SecurityElement.Escape(imdb) + "</IMDB>"); + } + + var tmdb = item.GetProviderId(MetadataProviders.Tmdb); + + if (!string.IsNullOrEmpty(tmdb)) + { + builder.Append("<TMDbId>" + SecurityElement.Escape(tmdb) + "</TMDbId>"); + } + + if (!(item is Series)) + { + var tvdb = item.GetProviderId(MetadataProviders.Tvdb); + + if (!string.IsNullOrEmpty(tvdb)) + { + builder.Append("<TvDbId>" + SecurityElement.Escape(tvdb) + "</TvDbId>"); + } + } + + var externalId = item.GetProviderId(MetadataProviders.Tvcom); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<TVcomId>" + SecurityElement.Escape(externalId) + "</TVcomId>"); + } + + externalId = item.GetProviderId(MetadataProviders.RottenTomatoes); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<RottenTomatoesId>" + SecurityElement.Escape(externalId) + "</RottenTomatoesId>"); + } + + externalId = item.GetProviderId(MetadataProviders.Zap2It); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<Zap2ItId>" + SecurityElement.Escape(externalId) + "</Zap2ItId>"); + } + + externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbum); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<MusicBrainzAlbumId>" + SecurityElement.Escape(externalId) + "</MusicBrainzAlbumId>"); + } + + externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<MusicBrainzAlbumArtistId>" + SecurityElement.Escape(externalId) + "</MusicBrainzAlbumArtistId>"); + } + + externalId = item.GetProviderId(MetadataProviders.MusicBrainzArtist); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<MusicBrainzArtistId>" + SecurityElement.Escape(externalId) + "</MusicBrainzArtistId>"); + } + + externalId = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<MusicBrainzReleaseGroupId>" + SecurityElement.Escape(externalId) + "</MusicBrainzReleaseGroupId>"); + } + + externalId = item.GetProviderId(MetadataProviders.Gamesdb); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<GamesDbId>" + SecurityElement.Escape(externalId) + "</GamesDbId>"); + } + + externalId = item.GetProviderId(MetadataProviders.TmdbCollection); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<TMDbCollectionId>" + SecurityElement.Escape(externalId) + "</TMDbCollectionId>"); + } + + externalId = item.GetProviderId(MetadataProviders.AudioDbArtist); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<AudioDbArtistId>" + SecurityElement.Escape(externalId) + "</AudioDbArtistId>"); + } + + externalId = item.GetProviderId(MetadataProviders.AudioDbAlbum); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<AudioDbAlbumId>" + SecurityElement.Escape(externalId) + "</AudioDbAlbumId>"); + } + + externalId = item.GetProviderId(MetadataProviders.TvRage); + + if (!string.IsNullOrEmpty(externalId)) + { + builder.Append("<TVRageId>" + SecurityElement.Escape(externalId) + "</TVRageId>"); + } + + var hasTagline = item as IHasTaglines; + if (hasTagline != null) + { + if (hasTagline.Taglines.Count > 0) + { + builder.Append("<Taglines>"); + + foreach (var tagline in hasTagline.Taglines) + { + builder.Append("<Tagline>" + SecurityElement.Escape(tagline) + "</Tagline>"); + } + + builder.Append("</Taglines>"); + } + } + + if (item.Genres.Count > 0) + { + builder.Append("<Genres>"); + + foreach (var genre in item.Genres) + { + builder.Append("<Genre>" + SecurityElement.Escape(genre) + "</Genre>"); + } + + builder.Append("</Genres>"); + } + + if (item.Studios.Count > 0) + { + builder.Append("<Studios>"); + + foreach (var studio in item.Studios) + { + builder.Append("<Studio>" + SecurityElement.Escape(studio) + "</Studio>"); + } + + builder.Append("</Studios>"); + } + + var hasTags = item as IHasTags; + if (hasTags != null) + { + if (hasTags.Tags.Count > 0) + { + builder.Append("<Tags>"); + + foreach (var tag in hasTags.Tags) + { + builder.Append("<Tag>" + SecurityElement.Escape(tag) + "</Tag>"); + } + + builder.Append("</Tags>"); + } + } + + var hasKeywords = item as IHasKeywords; + if (hasKeywords != null) + { + if (hasKeywords.Keywords.Count > 0) + { + builder.Append("<PlotKeywords>"); + + foreach (var tag in hasKeywords.Keywords) + { + builder.Append("<PlotKeyword>" + SecurityElement.Escape(tag) + "</PlotKeyword>"); + } + + builder.Append("</PlotKeywords>"); + } + } + + if (item.People.Count > 0) + { + builder.Append("<Persons>"); + + foreach (var person in item.People) + { + builder.Append("<Person>"); + builder.Append("<Name>" + SecurityElement.Escape(person.Name) + "</Name>"); + builder.Append("<Type>" + SecurityElement.Escape(person.Type) + "</Type>"); + builder.Append("<Role>" + SecurityElement.Escape(person.Role) + "</Role>"); + + if (person.SortOrder.HasValue) + { + builder.Append("<SortOrder>" + SecurityElement.Escape(person.SortOrder.Value.ToString(UsCulture)) + "</SortOrder>"); + } + + builder.Append("</Person>"); + } + + builder.Append("</Persons>"); + } + + var folder = item as BoxSet; + if (folder != null) + { + AddCollectionItems(folder, builder); + } + } + + public static void AddChapters(Video item, StringBuilder builder, IItemRepository repository) + { + var chapters = repository.GetChapters(item.Id); + + builder.Append("<Chapters>"); + + foreach (var chapter in chapters) + { + builder.Append("<Chapter>"); + builder.Append("<Name>" + SecurityElement.Escape(chapter.Name) + "</Name>"); + + var time = TimeSpan.FromTicks(chapter.StartPositionTicks); + var ms = Convert.ToInt64(time.TotalMilliseconds); + + builder.Append("<StartPositionMs>" + SecurityElement.Escape(ms.ToString(UsCulture)) + "</StartPositionMs>"); + builder.Append("</Chapter>"); + } + + builder.Append("</Chapters>"); + } + + /// <summary> + /// Appends the media info. + /// </summary> + /// <typeparam name="T"></typeparam> + public static void AddMediaInfo<T>(T item, StringBuilder builder, IItemRepository itemRepository) + where T : BaseItem + { + var video = item as Video; + + if (video != null) + { + //AddChapters(video, builder, itemRepository); + + if (video.Video3DFormat.HasValue) + { + switch (video.Video3DFormat.Value) + { + case Video3DFormat.FullSideBySide: + builder.Append("<Format3D>FSBS</Format3D>"); + break; + case Video3DFormat.FullTopAndBottom: + builder.Append("<Format3D>FTAB</Format3D>"); + break; + case Video3DFormat.HalfSideBySide: + builder.Append("<Format3D>HSBS</Format3D>"); + break; + case Video3DFormat.HalfTopAndBottom: + builder.Append("<Format3D>HTAB</Format3D>"); + break; + } + } + } + } + + public static void AddCollectionItems(Folder item, StringBuilder builder) + { + var items = item.LinkedChildren + .Where(i => i.Type == LinkedChildType.Manual && !string.IsNullOrWhiteSpace(i.ItemName)) + .ToList(); + + if (items.Count == 0) + { + return; + } + + builder.Append("<CollectionItems>"); + foreach (var link in items) + { + builder.Append("<CollectionItem>"); + + builder.Append("<Name>" + SecurityElement.Escape(link.ItemName) + "</Name>"); + builder.Append("<Type>" + SecurityElement.Escape(link.ItemType) + "</Type>"); + + if (link.ItemYear.HasValue) + { + builder.Append("<Year>" + SecurityElement.Escape(link.ItemYear.Value.ToString(UsCulture)) + "</Year>"); + } + + builder.Append("</CollectionItem>"); + } + builder.Append("</CollectionItems>"); + } + } +} |
