@ -43,7 +43,8 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
@@ -43,7 +43,8 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
string movieTemplateFileName = GetMovieTemplateFileName ( ) ;
string episodeTemplateFileName = GetEpisodeTemplateFileName ( ) ;
if ( movieTemplateFileName is null | | episodeTemplateFileName is null )
string musicVideoTemplateFileName = GetMusicVideoTemplateFileName ( ) ;
if ( movieTemplateFileName is null | | episodeTemplateFileName is null | | musicVideoTemplateFileName is null )
{
return ;
}
@ -64,6 +65,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
@@ -64,6 +65,9 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
string episodeText = await File . ReadAllTextAsync ( episodeTemplateFileName , cancellationToken ) ;
var episodeTemplate = Template . Parse ( episodeText , episodeTemplateFileName ) ;
string musicVideoText = await File . ReadAllTextAsync ( musicVideoTemplateFileName , cancellationToken ) ;
var musicVideoTemplate = Template . Parse ( musicVideoText , musicVideoTemplateFileName ) ;
await using TvContext dbContext = await _d bContextFactory . CreateDbContextAsync ( cancellationToken ) ;
List < Playout > playouts = await dbContext . Playouts
@ -113,6 +117,14 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
@@ -113,6 +117,14 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
. ThenInclude ( mvm = > mvm . Genres )
. Include ( p = > p . Items )
. ThenInclude ( i = > i . MediaItem )
. ThenInclude ( i = > ( i as MusicVideo ) . MusicVideoMetadata )
. ThenInclude ( mvm = > mvm . Studios )
. Include ( p = > p . Items )
. ThenInclude ( i = > i . MediaItem )
. ThenInclude ( i = > ( i as MusicVideo ) . MusicVideoMetadata )
. ThenInclude ( mvm = > mvm . Directors )
. Include ( p = > p . Items )
. ThenInclude ( i = > i . MediaItem )
. ThenInclude ( i = > ( i as MusicVideo ) . Artist )
. ThenInclude ( a = > a . ArtistMetadata )
. ThenInclude ( am = > am . Genres )
@ -305,6 +317,62 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
@@ -305,6 +317,62 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
continue ;
}
if ( displayItem . MediaItem is MusicVideo templateMusicVideo )
{
foreach ( MusicVideoMetadata metadata in templateMusicVideo . MusicVideoMetadata . HeadOrNone ( ) )
{
metadata . Genres ? ? = [ ] ;
metadata . Artists ? ? = [ ] ;
metadata . Studios ? ? = [ ] ;
metadata . Directors ? ? = [ ] ;
string artworkPath = GetPrioritizedArtworkPath ( metadata ) ;
Option < ArtistMetadata > maybeMetadata =
Optional ( templateMusicVideo . Artist ? . ArtistMetadata . HeadOrNone ( ) ) . Flatten ( ) ;
var data = new
{
ProgrammeStart = start ,
ProgrammeStop = stop ,
ChannelNumber = request . ChannelNumber ,
HasCustomTitle = hasCustomTitle ,
CustomTitle = displayItem . CustomTitle ,
ArtistTitle = title ,
MusicVideoTitle = subtitle ,
MusicVideoHasPlot = ! string . IsNullOrWhiteSpace ( metadata . Plot ) ,
MusicVideoPlot = metadata . Plot ,
MusicVideoHasYear = metadata . Year . HasValue ,
MusicVideoYear = metadata . Year ,
MusicVideoGenres = metadata . Genres . Map ( g = > g . Name ) . OrderBy ( n = > n ) ,
ArtistGenres = maybeMetadata . SelectMany ( m = > m . Genres . Map ( g = > g . Name ) ) . OrderBy ( n = > n ) ,
MusicVideoHasArtwork = ! string . IsNullOrWhiteSpace ( artworkPath ) ,
MusicVideoArtworkUrl = artworkPath ,
MusicVideoHasTrack = metadata . Track . HasValue ,
MusicVideoTrack = metadata . Track ,
MusicVideoHasAlbum = ! string . IsNullOrWhiteSpace ( metadata . Album ) ,
MusicVideoAlbum = metadata . Album ,
MusicVideoHasReleaseDate = metadata . ReleaseDate . HasValue ,
MusicVideoReleaseDate = metadata . ReleaseDate ,
MusicVideoAllArtists = metadata . Artists . Map ( a = > a . Name ) ,
MusicVideoStudios = metadata . Studios . Map ( s = > s . Name ) ,
MusicVideoDirectors = metadata . Directors . Map ( d = > d . Name )
} ;
var scriptObject = new ScriptObject ( ) ;
scriptObject . Import ( data ) ;
templateContext . PushGlobal ( scriptObject ) ;
string result = await musicVideoTemplate . RenderAsync ( templateContext ) ;
MarkupMinificationResult minified = minifier . Minify ( result ) ;
await xml . WriteRawAsync ( minified . MinifiedContent ) ;
}
i + + ;
continue ;
}
await xml . WriteStartElementAsync ( null , "programme" , null ) ;
await xml . WriteAttributeStringAsync ( null , "start" , null , start ) ;
await xml . WriteAttributeStringAsync ( null , "stop" , null , stop ) ;
@ -334,93 +402,6 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
@@ -334,93 +402,6 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
}
}
if ( ! hasCustomTitle & & displayItem . MediaItem is Movie movie )
{
foreach ( MovieMetadata metadata in movie . MovieMetadata . HeadOrNone ( ) )
{
if ( metadata . Year . HasValue )
{
await xml . WriteStartElementAsync ( null , "date" , null ) ;
await xml . WriteStringAsync ( metadata . Year . Value . ToString ( CultureInfo . InvariantCulture ) ) ;
await xml . WriteEndElementAsync ( ) ; // date
}
await xml . WriteStartElementAsync ( null , "category" , null ) ;
await xml . WriteAttributeStringAsync ( null , "lang" , null , "en" ) ;
await xml . WriteStringAsync ( "Movie" ) ;
await xml . WriteEndElementAsync ( ) ; // category
foreach ( Genre genre in Optional ( metadata . Genres ) . Flatten ( ) . OrderBy ( g = > g . Name ) )
{
await xml . WriteStartElementAsync ( null , "category" , null ) ;
await xml . WriteAttributeStringAsync ( null , "lang" , null , "en" ) ;
await xml . WriteStringAsync ( genre . Name ) ;
await xml . WriteEndElementAsync ( ) ; // category
}
string poster = Optional ( metadata . Artwork ) . Flatten ( )
. Filter ( a = > a . ArtworkKind = = ArtworkKind . Poster )
. HeadOrNone ( )
. Match ( a = > GetArtworkUrl ( a , ArtworkKind . Poster ) , ( ) = > string . Empty ) ;
if ( ! string . IsNullOrWhiteSpace ( poster ) )
{
await xml . WriteStartElementAsync ( null , "icon" , null ) ;
await xml . WriteAttributeStringAsync ( null , "src" , null , poster ) ;
await xml . WriteEndElementAsync ( ) ; // icon
}
}
}
if ( ! hasCustomTitle & & displayItem . MediaItem is MusicVideo musicVideo )
{
foreach ( MusicVideoMetadata metadata in musicVideo . MusicVideoMetadata . HeadOrNone ( ) )
{
if ( metadata . Year . HasValue )
{
await xml . WriteStartElementAsync ( null , "date" , null ) ;
await xml . WriteStringAsync ( metadata . Year . Value . ToString ( CultureInfo . InvariantCulture ) ) ;
await xml . WriteEndElementAsync ( ) ; // date
}
await xml . WriteStartElementAsync ( null , "category" , null ) ;
await xml . WriteAttributeStringAsync ( null , "lang" , null , "en" ) ;
await xml . WriteStringAsync ( "Music" ) ;
await xml . WriteEndElementAsync ( ) ; // category
// music video genres
foreach ( Genre genre in Optional ( metadata . Genres ) . Flatten ( ) . OrderBy ( g = > g . Name ) )
{
await xml . WriteStartElementAsync ( null , "category" , null ) ;
await xml . WriteAttributeStringAsync ( null , "lang" , null , "en" ) ;
await xml . WriteStringAsync ( genre . Name ) ;
await xml . WriteEndElementAsync ( ) ; // category
}
// artist genres
Option < ArtistMetadata > maybeMetadata =
Optional ( musicVideo . Artist ? . ArtistMetadata . HeadOrNone ( ) ) . Flatten ( ) ;
foreach ( ArtistMetadata artistMetadata in maybeMetadata )
{
foreach ( Genre genre in Optional ( artistMetadata . Genres ) . Flatten ( ) . OrderBy ( g = > g . Name ) )
{
await xml . WriteStartElementAsync ( null , "category" , null ) ;
await xml . WriteAttributeStringAsync ( null , "lang" , null , "en" ) ;
await xml . WriteStringAsync ( genre . Name ) ;
await xml . WriteEndElementAsync ( ) ; // category
}
}
string artworkPath = GetPrioritizedArtworkPath ( metadata ) ;
if ( ! string . IsNullOrWhiteSpace ( artworkPath ) )
{
await xml . WriteStartElementAsync ( null , "icon" , null ) ;
await xml . WriteAttributeStringAsync ( null , "src" , null , artworkPath ) ;
await xml . WriteEndElementAsync ( ) ; // icon
}
}
}
if ( ! hasCustomTitle & & displayItem . MediaItem is Song song )
{
await xml . WriteStartElementAsync ( null , "category" , null ) ;
@ -440,54 +421,6 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
@@ -440,54 +421,6 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
}
}
if ( displayItem . MediaItem is Episode episode & & ( ! hasCustomTitle | | isSameCustomShow ) )
{
Option < ShowMetadata > maybeMetadata =
Optional ( episode . Season ? . Show ? . ShowMetadata . HeadOrNone ( ) ) . Flatten ( ) ;
foreach ( ShowMetadata metadata in maybeMetadata )
{
await xml . WriteStartElementAsync ( null , "category" , null ) ;
await xml . WriteAttributeStringAsync ( null , "lang" , null , "en" ) ;
await xml . WriteStringAsync ( "Series" ) ;
await xml . WriteEndElementAsync ( ) ; // category
foreach ( Genre genre in Optional ( metadata . Genres ) . Flatten ( ) . OrderBy ( g = > g . Name ) )
{
await xml . WriteStartElementAsync ( null , "category" , null ) ;
await xml . WriteAttributeStringAsync ( null , "lang" , null , "en" ) ;
await xml . WriteStringAsync ( genre . Name ) ;
await xml . WriteEndElementAsync ( ) ; // category
}
string artworkPath = GetPrioritizedArtworkPath ( metadata ) ;
if ( ! string . IsNullOrWhiteSpace ( artworkPath ) )
{
await xml . WriteStartElementAsync ( null , "icon" , null ) ;
await xml . WriteAttributeStringAsync ( null , "src" , null , artworkPath ) ;
await xml . WriteEndElementAsync ( ) ; // icon
}
}
if ( ! isSameCustomShow )
{
int s = await Optional ( episode . Season ? . SeasonNumber ) . IfNoneAsync ( - 1 ) ;
// TODO: multi-episode?
int e = episode . EpisodeMetadata . HeadOrNone ( ) . Match ( em = > em . EpisodeNumber , - 1 ) ;
if ( s > = 0 & & e > 0 )
{
await xml . WriteStartElementAsync ( null , "episode-num" , null ) ;
await xml . WriteAttributeStringAsync ( null , "system" , null , "onscreen" ) ;
await xml . WriteStringAsync ( $"S{s:00}E{e:00}" ) ;
await xml . WriteEndElementAsync ( ) ; // episode-num
await xml . WriteStartElementAsync ( null , "episode-num" , null ) ;
await xml . WriteAttributeStringAsync ( null , "system" , null , "xmltv_ns" ) ;
await xml . WriteStringAsync ( $"{s - 1}.{e - 1}.0/1" ) ;
await xml . WriteEndElementAsync ( ) ; // episode-num
}
}
}
await xml . WriteStartElementAsync ( null , "previously-shown" , null ) ;
await xml . WriteEndElementAsync ( ) ; // previously-shown
@ -565,6 +498,29 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
@@ -565,6 +498,29 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
return templateFileName ;
}
private string GetMusicVideoTemplateFileName ( )
{
string templateFileName = Path . Combine ( FileSystemLayout . ChannelGuideTemplatesFolder , "musicVideo.sbntxt" ) ;
// fall back to default template
if ( ! _l ocalFileSystem . FileExists ( templateFileName ) )
{
templateFileName = Path . Combine ( FileSystemLayout . ChannelGuideTemplatesFolder , "_musicVideo.sbntxt" ) ;
}
// fail if file doesn't exist
if ( ! _l ocalFileSystem . FileExists ( templateFileName ) )
{
_l ogger . LogError (
"Unable to generate music video XMLTV fragment without template file {File}; please restart ErsatzTV" ,
templateFileName ) ;
return null ;
}
return templateFileName ;
}
private static string GetArtworkUrl ( Artwork artwork , ArtworkKind artworkKind )
{
string artworkPath = artwork . Path ;