@ -1,4 +1,5 @@
using MediaBrowser.Common.Configuration ;
using System.Collections.Generic ;
using MediaBrowser.Common.Configuration ;
using MediaBrowser.Common.Net ;
using MediaBrowser.Common.Net ;
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Entities ;
using MediaBrowser.Controller.Entities ;
@ -8,7 +9,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.Entities ;
using MediaBrowser.Model.IO ;
using MediaBrowser.Model.IO ;
using MediaBrowser.Model.Logging ;
using MediaBrowser.Model.Logging ;
using MediaBrowser.Providers.Extensions ;
using System ;
using System ;
using System.Globalization ;
using System.Globalization ;
using System.IO ;
using System.IO ;
@ -18,7 +18,6 @@ using System.Text;
using System.Threading ;
using System.Threading ;
using System.Threading.Tasks ;
using System.Threading.Tasks ;
using System.Xml ;
using System.Xml ;
using System.Xml.Linq ;
namespace MediaBrowser.Providers.TV
namespace MediaBrowser.Providers.TV
{
{
@ -240,10 +239,7 @@ namespace MediaBrowser.Providers.TV
var seriesXmlPath = Path . Combine ( seriesDataPath , seriesXmlFilename ) ;
var seriesXmlPath = Path . Combine ( seriesDataPath , seriesXmlFilename ) ;
var actorsXmlPath = Path . Combine ( seriesDataPath , "actors.xml" ) ;
var actorsXmlPath = Path . Combine ( seriesDataPath , "actors.xml" ) ;
var seriesDoc = new XmlDocument ( ) ;
FetchSeriesInfo ( series , seriesXmlPath , cancellationToken ) ;
seriesDoc . Load ( seriesXmlPath ) ;
FetchMainInfo ( series , seriesDoc ) ;
if ( ! series . LockedFields . Contains ( MetadataFields . Cast ) )
if ( ! series . LockedFields . Contains ( MetadataFields . Cast ) )
{
{
@ -317,112 +313,363 @@ namespace MediaBrowser.Providers.TV
return dataPath ;
return dataPath ;
}
}
/// <summary>
private void FetchSeriesInfo ( Series item , string seriesXmlPath , CancellationToken cancellationToken )
/// Fetches the main info.
/// </summary>
/// <param name="series">The series.</param>
/// <param name="doc">The doc.</param>
private void FetchMainInfo ( Series series , XmlDocument doc )
{
{
if ( ! series . LockedFields . Contains ( MetadataFields . Name ) )
var settings = new XmlReaderSettings
{
series . Name = doc . SafeGetString ( "//SeriesName" ) ;
}
if ( ! series . LockedFields . Contains ( MetadataFields . Overview ) )
{
{
series . Overview = doc . SafeGetString ( "//Overview" ) ;
CheckCharacters = false ,
}
IgnoreProcessingInstructions = true ,
IgnoreComments = true ,
ValidationType = ValidationType . None
} ;
var imdbId = doc . SafeGetString ( "//IMDB_ID" ) ;
var episiodeAirDates = new List < DateTime > ( ) ;
if ( ! string . IsNullOrWhiteSpace ( imdbId ) )
using ( var streamReader = new StreamReader ( seriesXmlPath , Encoding . UTF8 ) )
{
{
series . SetProviderId ( MetadataProviders . Imdb , imdbId ) ;
// Use XmlReader for best performance
}
using ( var reader = XmlReader . Create ( streamReader , settings ) )
{
reader . MoveToContent ( ) ;
var zap2ItId = doc . SafeGetString ( "//zap2it_id" ) ;
// Loop through each element
while ( reader . Read ( ) )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( ! string . IsNullOrWhiteSpace ( zap2ItId ) )
if ( reader . NodeType = = XmlNodeType . Element )
{
{
series . SetProviderId ( MetadataProviders . Zap2It , zap2ItId ) ;
switch ( reader . Name )
}
{
case "Series" :
// Only fill this if it doesn't already have a value, since we get it from imdb which has better data
{
if ( ! series . CommunityRating . HasValue | | string . IsNullOrWhiteSpace ( series . GetProviderId ( MetadataProviders . Imdb ) ) )
using ( var subtree = reader . ReadSubtree ( ) )
{
{
series . CommunityRating = doc . SafeGetSingle ( "//Rating" , 0 , 10 ) ;
FetchDataFromSeriesNode ( item , subtree , cancellationToken ) ;
}
}
break ;
}
series . AirDays = TVUtils . GetAirDays ( doc . SafeGetString ( "//Airs_DayOfWeek" ) ) ;
case "Episode" :
series . AirTime = doc . SafeGetString ( "//Airs_Time" ) ;
{
SeriesStatus seriesStatus ;
using ( var subtree = reader . ReadSubtree ( ) )
if ( Enum . TryParse ( doc . SafeGetString ( "//Status" ) , true , out seriesStatus ) )
{
series . Status = seriesStatus ;
var date = GetFirstAiredDateFromEpisodeNode ( subtree , cancellationToken ) ;
series . PremiereDate = doc . SafeGetDateTime ( "//FirstAired" ) ;
if ( series . PremiereDate . HasValue )
if ( date . HasValue )
series . ProductionYear = series . PremiereDate . Value . Year ;
{
episiodeAirDates . Add ( date . Value ) ;
}
}
break ;
}
if ( ! series . LockedFields . Contains ( MetadataFields . Runtime ) )
default :
reader . Skip ( ) ;
break ;
}
}
}
}
}
if ( item . Status . HasValue & & item . Status . Value = = SeriesStatus . Ended & & episiodeAirDates . Count > 0 )
{
{
series . RunTimeTicks = TimeSpan . FromMinutes ( doc . SafeGetInt32 ( "//Runtime" ) ) . Ticks ;
item. EndDate = episiodeAirDates . Max ( ) ;
}
}
}
private void FetchDataFromSeriesNode ( Series item , XmlReader reader , CancellationToken cancellationToken )
{
reader . MoveToContent ( ) ;
if ( ! series . LockedFields . Contains ( MetadataFields . Studios ) )
// Loop through each element
while ( reader . Read ( ) )
{
{
string s = doc . SafeGetString ( "//Network" ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( ! string . IsNullOrWhiteSpace ( s ) )
if ( reader . NodeType = = XmlNodeType . Element )
{
{
series . Studios . Clear ( ) ;
switch ( reader . Name )
foreach ( var studio in s . Trim ( ) . Split ( '|' ) )
{
{
series . AddStudio ( studio ) ;
case "SeriesName" :
{
if ( ! item . LockedFields . Contains ( MetadataFields . Name ) )
{
item . Name = ( reader . ReadElementContentAsString ( ) ? ? string . Empty ) . Trim ( ) ;
}
break ;
}
case "Overview" :
{
if ( ! item . LockedFields . Contains ( MetadataFields . Overview ) )
{
item . Overview = ( reader . ReadElementContentAsString ( ) ? ? string . Empty ) . Trim ( ) ;
}
break ;
}
case "Airs_DayOfWeek" :
{
var val = reader . ReadElementContentAsString ( ) ;
if ( ! string . IsNullOrWhiteSpace ( val ) )
{
item . AirDays = TVUtils . GetAirDays ( val ) ;
}
break ;
}
case "Airs_Time" :
{
var val = reader . ReadElementContentAsString ( ) ;
if ( ! string . IsNullOrWhiteSpace ( val ) )
{
item . AirTime = val ;
}
break ;
}
case "ContentRating" :
{
var val = reader . ReadElementContentAsString ( ) ;
if ( ! string . IsNullOrWhiteSpace ( val ) )
{
item . OfficialRating = val ;
}
break ;
}
case "Rating" :
{
var val = reader . ReadElementContentAsString ( ) ;
if ( ! string . IsNullOrWhiteSpace ( val ) )
{
// Only fill this if it doesn't already have a value, since we get it from imdb which has better data
if ( ! item . CommunityRating . HasValue | | string . IsNullOrWhiteSpace ( item . GetProviderId ( MetadataProviders . Imdb ) ) )
{
float rval ;
// float.TryParse is local aware, so it can be probamatic, force us culture
if ( float . TryParse ( val , NumberStyles . AllowDecimalPoint , UsCulture , out rval ) )
{
item . CommunityRating = rval ;
}
}
}
break ;
}
case "IMDB_ID" :
{
var val = reader . ReadElementContentAsString ( ) ;
if ( ! string . IsNullOrWhiteSpace ( val ) )
{
item . SetProviderId ( MetadataProviders . Imdb , val ) ;
}
break ;
}
case "zap2it_id" :
{
var val = reader . ReadElementContentAsString ( ) ;
if ( ! string . IsNullOrWhiteSpace ( val ) )
{
item . SetProviderId ( MetadataProviders . Zap2It , val ) ;
}
break ;
}
case "Status" :
{
var val = reader . ReadElementContentAsString ( ) ;
if ( ! string . IsNullOrWhiteSpace ( val ) )
{
SeriesStatus seriesStatus ;
if ( Enum . TryParse ( val , true , out seriesStatus ) )
item . Status = seriesStatus ;
}
break ;
}
case "FirstAired" :
{
var val = reader . ReadElementContentAsString ( ) ;
if ( ! string . IsNullOrWhiteSpace ( val ) )
{
DateTime date ;
if ( DateTime . TryParse ( val , out date ) )
{
date = date . ToUniversalTime ( ) ;
item . PremiereDate = date ;
item . ProductionYear = date . Year ;
}
}
break ;
}
case "Runtime" :
{
var val = reader . ReadElementContentAsString ( ) ;
if ( ! string . IsNullOrWhiteSpace ( val ) & & ! item . LockedFields . Contains ( MetadataFields . Runtime ) )
{
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 . RunTimeTicks = TimeSpan . FromMinutes ( rval ) . Ticks ;
}
}
break ;
}
case "Genre" :
{
var val = reader . ReadElementContentAsString ( ) ;
if ( ! string . IsNullOrWhiteSpace ( val ) )
{
// Only fill this in if there's no existing genres, because Imdb data from Omdb is preferred
if ( ! item . LockedFields . Contains ( MetadataFields . Genres ) & & ( item . Genres . Count = = 0 | | ! string . Equals ( ConfigurationManager . Configuration . PreferredMetadataLanguage , "en" , StringComparison . OrdinalIgnoreCase ) ) )
{
var vals = val
. Split ( new [ ] { '|' } , StringSplitOptions . RemoveEmptyEntries )
. Select ( i = > i . Trim ( ) )
. Where ( i = > ! string . IsNullOrWhiteSpace ( i ) )
. ToArray ( ) ;
if ( vals . Length > 0 )
{
item . Genres . Clear ( ) ;
foreach ( var genre in vals )
{
item . AddGenre ( genre ) ;
}
}
}
}
break ;
}
case "Network" :
{
var val = reader . ReadElementContentAsString ( ) ;
if ( ! string . IsNullOrWhiteSpace ( val ) )
{
if ( ! item . LockedFields . Contains ( MetadataFields . Studios ) )
{
var vals = val
. Split ( new [ ] { '|' } , StringSplitOptions . RemoveEmptyEntries )
. Select ( i = > i . Trim ( ) )
. Where ( i = > ! string . IsNullOrWhiteSpace ( i ) )
. ToArray ( ) ;
if ( vals . Length > 0 )
{
item . Studios . Clear ( ) ;
foreach ( var genre in vals )
{
item . AddStudio ( genre ) ;
}
}
}
}
break ;
}
default :
reader . Skip ( ) ;
break ;
}
}
}
}
}
}
}
series . OfficialRating = doc . SafeGetString ( "//ContentRating" ) ;
private DateTime ? GetFirstAiredDateFromEpisodeNode ( XmlReader reader , CancellationToken cancellationToken )
{
DateTime ? airDate = null ;
int? seasonNumber = null ;
reader . MoveToContent ( ) ;
// Only fill this in if there's no existing genres, because Imdb data from Omdb is preferred
// Loop through each element
if ( ! series . LockedFields . Contains ( MetadataFields . Genres ) & & ( series . Genres . Count = = 0 | | ! string . Equals ( ConfigurationManager . Configuration . PreferredMetadataLanguage , "en" , StringComparison . OrdinalIgnoreCase ) ) )
while ( reader . Read ( ) )
{
{
string g = doc . SafeGetString ( "//Genre" ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( g ! = null )
if ( reader. NodeType = = XmlNodeType . Element )
{
{
string [ ] genres = g . Trim ( '|' ) . Split ( '|' ) ;
switch ( reader . Name )
if ( g . Length > 0 )
{
{
series . Genres . Clear ( ) ;
case "FirstAired" :
{
var val = reader . ReadElementContentAsString ( ) ;
foreach ( var genre in genres )
if ( ! string . IsNullOrWhiteSpace ( val ) )
{
{
series . AddGenre ( genre ) ;
DateTime date ;
}
if ( DateTime . TryParse ( val , out date ) )
{
airDate = date . ToUniversalTime ( ) ;
}
}
break ;
}
case "SeasonNumber" :
{
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 ) )
{
seasonNumber = rval ;
}
}
break ;
}
default :
reader . Skip ( ) ;
break ;
}
}
}
}
}
}
if ( series . Status = = SeriesStatus . Ended ) {
if ( seasonNumber . HasValue & & seasonNumber . Value ! = 0 )
var document = XDocument . Load ( new XmlNodeReader ( doc ) ) ;
{
var dates = document . Descendants ( "Episode" ) . Where ( x = > {
return airDate ;
var seasonNumber = x . Element ( "SeasonNumber" ) ;
var firstAired = x . Element ( "FirstAired" ) ;
return firstAired ! = null & & seasonNumber ! = null & & ( ! string . IsNullOrEmpty ( seasonNumber . Value ) & & seasonNumber . Value ! = "0" ) & & ! string . IsNullOrEmpty ( firstAired . Value ) ;
} ) . Select ( x = > {
DateTime ? date = null ;
DateTime tempDate ;
var firstAired = x . Element ( "FirstAired" ) ;
if ( firstAired ! = null & & DateTime . TryParse ( firstAired . Value , out tempDate ) )
{
date = tempDate ;
}
return date ;
} ) . ToList ( ) ;
if ( dates . Any ( x = > x . HasValue ) )
series . EndDate = dates . Where ( x = > x . HasValue ) . Max ( ) ;
}
}
return null ;
}
}
/// <summary>
/// <summary>
@ -503,7 +750,7 @@ namespace MediaBrowser.Providers.TV
personInfo . Role = ( reader . ReadElementContentAsString ( ) ? ? string . Empty ) . Trim ( ) ;
personInfo . Role = ( reader . ReadElementContentAsString ( ) ? ? string . Empty ) . Trim ( ) ;
break ;
break ;
}
}
default :
default :
reader . Skip ( ) ;
reader . Skip ( ) ;
break ;
break ;