|
|
|
@ -109,31 +109,31 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
.Filter(pi => pi.Start <= until) |
|
|
|
.Filter(pi => pi.Start <= until) |
|
|
|
.ToListAsync(cancellationToken); |
|
|
|
.ToListAsync(cancellationToken); |
|
|
|
|
|
|
|
|
|
|
|
// TODO: support other media kinds (movies, other videos, etc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var mediaItemIds = playoutItems.Map(pi => pi.MediaItemId).ToList(); |
|
|
|
var mediaItemIds = playoutItems.Map(pi => pi.MediaItemId).ToList(); |
|
|
|
|
|
|
|
|
|
|
|
// filter for subtitles that need extraction
|
|
|
|
// filter for items with text subtitles or font attachments
|
|
|
|
List<int> unextractedMediaItemIds = |
|
|
|
List<int> mediaItemIdsWithTextSubtitles = |
|
|
|
await GetUnextractedMediaItemIds(dbContext, mediaItemIds, cancellationToken); |
|
|
|
await GetMediaItemIdsWithTextSubtitles(dbContext, mediaItemIds, cancellationToken); |
|
|
|
|
|
|
|
|
|
|
|
if (unextractedMediaItemIds.Any()) |
|
|
|
if (mediaItemIdsWithTextSubtitles.Any()) |
|
|
|
{ |
|
|
|
{ |
|
|
|
_logger.LogDebug( |
|
|
|
_logger.LogDebug( |
|
|
|
"Found media items {MediaItemIds} with text subtitles to extract for playouts {PlayoutIds}", |
|
|
|
"Checking media items {MediaItemIds} for text subtitles or fonts to extract for playouts {PlayoutIds}", |
|
|
|
unextractedMediaItemIds, |
|
|
|
mediaItemIdsWithTextSubtitles, |
|
|
|
playoutIdsToCheck); |
|
|
|
playoutIdsToCheck); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
else |
|
|
|
{ |
|
|
|
{ |
|
|
|
_logger.LogDebug("Found no text subtitles to extract for playouts {PlayoutIds}", playoutIdsToCheck); |
|
|
|
_logger.LogDebug( |
|
|
|
|
|
|
|
"Found no text subtitles or fonts to extract for playouts {PlayoutIds}", |
|
|
|
|
|
|
|
playoutIdsToCheck); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// sort by start time
|
|
|
|
// sort by start time
|
|
|
|
var toUpdate = playoutItems |
|
|
|
var toUpdate = playoutItems |
|
|
|
.Filter(pi => pi.Finish >= DateTime.UtcNow) |
|
|
|
.Filter(pi => pi.Finish >= DateTime.UtcNow) |
|
|
|
.DistinctBy(pi => pi.MediaItemId) |
|
|
|
.DistinctBy(pi => pi.MediaItemId) |
|
|
|
.Filter(pi => unextractedMediaItemIds.Contains(pi.MediaItemId)) |
|
|
|
.Filter(pi => mediaItemIdsWithTextSubtitles.Contains(pi.MediaItemId)) |
|
|
|
.OrderBy(pi => pi.StartOffset) |
|
|
|
.OrderBy(pi => pi.StartOffset) |
|
|
|
.Map(pi => pi.MediaItemId) |
|
|
|
.Map(pi => pi.MediaItemId) |
|
|
|
.ToList(); |
|
|
|
.ToList(); |
|
|
|
@ -145,14 +145,13 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
return Unit.Default; |
|
|
|
return Unit.Default; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
PlayoutItem pi = playoutItems.Find(pi => pi.MediaItemId == mediaItemId); |
|
|
|
|
|
|
|
_logger.LogDebug("Extracting subtitles for item with start time {StartTime}", pi?.StartOffset); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// extract subtitles and fonts for each item and update db
|
|
|
|
// extract subtitles and fonts for each item and update db
|
|
|
|
await ExtractSubtitles(dbContext, mediaItemId, ffmpegPath, cancellationToken); |
|
|
|
await ExtractSubtitles(dbContext, mediaItemId, ffmpegPath, cancellationToken); |
|
|
|
// await ExtractFonts(dbContext, episodeId, ffmpegPath, cancellationToken);
|
|
|
|
await ExtractFonts(dbContext, mediaItemId, ffmpegPath, cancellationToken); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("Done checking playouts {PlayoutIds} for text subtitles to extract", playoutIdsToCheck); |
|
|
|
|
|
|
|
|
|
|
|
return Unit.Default; |
|
|
|
return Unit.Default; |
|
|
|
} |
|
|
|
} |
|
|
|
catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) |
|
|
|
catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) |
|
|
|
@ -161,7 +160,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async Task<List<int>> GetUnextractedMediaItemIds( |
|
|
|
private async Task<List<int>> GetMediaItemIdsWithTextSubtitles( |
|
|
|
TvContext dbContext, |
|
|
|
TvContext dbContext, |
|
|
|
List<int> mediaItemIds, |
|
|
|
List<int> mediaItemIds, |
|
|
|
CancellationToken cancellationToken) |
|
|
|
CancellationToken cancellationToken) |
|
|
|
@ -174,7 +173,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
.Filter(em => mediaItemIds.Contains(em.EpisodeId)) |
|
|
|
.Filter(em => mediaItemIds.Contains(em.EpisodeId)) |
|
|
|
.Filter( |
|
|
|
.Filter( |
|
|
|
em => em.Subtitles.Any( |
|
|
|
em => em.Subtitles.Any( |
|
|
|
s => s.SubtitleKind == SubtitleKind.Embedded && s.IsExtracted == false && |
|
|
|
s => s.SubtitleKind == SubtitleKind.Embedded && |
|
|
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle")) |
|
|
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle")) |
|
|
|
.Map(em => em.EpisodeId) |
|
|
|
.Map(em => em.EpisodeId) |
|
|
|
.ToListAsync(cancellationToken); |
|
|
|
.ToListAsync(cancellationToken); |
|
|
|
@ -184,7 +183,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
.Filter(mm => mediaItemIds.Contains(mm.MovieId)) |
|
|
|
.Filter(mm => mediaItemIds.Contains(mm.MovieId)) |
|
|
|
.Filter( |
|
|
|
.Filter( |
|
|
|
mm => mm.Subtitles.Any( |
|
|
|
mm => mm.Subtitles.Any( |
|
|
|
s => s.SubtitleKind == SubtitleKind.Embedded && s.IsExtracted == false && |
|
|
|
s => s.SubtitleKind == SubtitleKind.Embedded && |
|
|
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle")) |
|
|
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle")) |
|
|
|
.Map(mm => mm.MovieId) |
|
|
|
.Map(mm => mm.MovieId) |
|
|
|
.ToListAsync(cancellationToken); |
|
|
|
.ToListAsync(cancellationToken); |
|
|
|
@ -194,7 +193,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
.Filter(mm => mediaItemIds.Contains(mm.MusicVideoId)) |
|
|
|
.Filter(mm => mediaItemIds.Contains(mm.MusicVideoId)) |
|
|
|
.Filter( |
|
|
|
.Filter( |
|
|
|
mm => mm.Subtitles.Any( |
|
|
|
mm => mm.Subtitles.Any( |
|
|
|
s => s.SubtitleKind == SubtitleKind.Embedded && s.IsExtracted == false && |
|
|
|
s => s.SubtitleKind == SubtitleKind.Embedded && |
|
|
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle")) |
|
|
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle")) |
|
|
|
.Map(mm => mm.MusicVideoId) |
|
|
|
.Map(mm => mm.MusicVideoId) |
|
|
|
.ToListAsync(cancellationToken); |
|
|
|
.ToListAsync(cancellationToken); |
|
|
|
@ -204,7 +203,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
.Filter(ovm => mediaItemIds.Contains(ovm.OtherVideoId)) |
|
|
|
.Filter(ovm => mediaItemIds.Contains(ovm.OtherVideoId)) |
|
|
|
.Filter( |
|
|
|
.Filter( |
|
|
|
ovm => ovm.Subtitles.Any( |
|
|
|
ovm => ovm.Subtitles.Any( |
|
|
|
s => s.SubtitleKind == SubtitleKind.Embedded && s.IsExtracted == false && |
|
|
|
s => s.SubtitleKind == SubtitleKind.Embedded && |
|
|
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle")) |
|
|
|
s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle")) |
|
|
|
.Map(ovm => ovm.OtherVideoId) |
|
|
|
.Map(ovm => ovm.OtherVideoId) |
|
|
|
.ToListAsync(cancellationToken); |
|
|
|
.ToListAsync(cancellationToken); |
|
|
|
@ -218,40 +217,13 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
return result; |
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private async Task<Unit> ExtractSubtitles( |
|
|
|
private async Task ExtractSubtitles( |
|
|
|
TvContext dbContext, |
|
|
|
TvContext dbContext, |
|
|
|
int mediaItemId, |
|
|
|
int mediaItemId, |
|
|
|
string ffmpegPath, |
|
|
|
string ffmpegPath, |
|
|
|
CancellationToken cancellationToken) |
|
|
|
CancellationToken cancellationToken) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Option<MediaItem> maybeMediaItem = await dbContext.MediaItems |
|
|
|
foreach (MediaItem mediaItem in await GetMediaItem(dbContext, mediaItemId)) |
|
|
|
.Include(mi => (mi as Episode).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.MediaFiles) |
|
|
|
|
|
|
|
.Include(mi => (mi as Episode).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.Streams) |
|
|
|
|
|
|
|
.Include(mi => (mi as Episode).EpisodeMetadata) |
|
|
|
|
|
|
|
.ThenInclude(em => em.Subtitles) |
|
|
|
|
|
|
|
.Include(mi => (mi as Movie).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.MediaFiles) |
|
|
|
|
|
|
|
.Include(mi => (mi as Movie).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.Streams) |
|
|
|
|
|
|
|
.Include(mi => (mi as Movie).MovieMetadata) |
|
|
|
|
|
|
|
.ThenInclude(em => em.Subtitles) |
|
|
|
|
|
|
|
.Include(mi => (mi as MusicVideo).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.MediaFiles) |
|
|
|
|
|
|
|
.Include(mi => (mi as MusicVideo).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.Streams) |
|
|
|
|
|
|
|
.Include(mi => (mi as MusicVideo).MusicVideoMetadata) |
|
|
|
|
|
|
|
.ThenInclude(em => em.Subtitles) |
|
|
|
|
|
|
|
.Include(mi => (mi as OtherVideo).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.MediaFiles) |
|
|
|
|
|
|
|
.Include(mi => (mi as OtherVideo).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.Streams) |
|
|
|
|
|
|
|
.Include(mi => (mi as OtherVideo).OtherVideoMetadata) |
|
|
|
|
|
|
|
.ThenInclude(em => em.Subtitles) |
|
|
|
|
|
|
|
.SelectOneAsync(e => e.Id, e => e.Id == mediaItemId); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach (MediaItem mediaItem in maybeMediaItem) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
foreach (List<Subtitle> allSubtitles in GetSubtitles(mediaItem)) |
|
|
|
foreach (List<Subtitle> allSubtitles in GetSubtitles(mediaItem)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
@ -273,6 +245,11 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (subtitlesToExtract.Count == 0) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
string mediaItemPath = await GetMediaItemPath(mediaItem); |
|
|
|
string mediaItemPath = await GetMediaItemPath(mediaItem); |
|
|
|
|
|
|
|
|
|
|
|
ArgumentsBuilder args = new ArgumentsBuilder() |
|
|
|
ArgumentsBuilder args = new ArgumentsBuilder() |
|
|
|
@ -316,10 +293,36 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return Unit.Default; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static async Task<Option<MediaItem>> GetMediaItem(TvContext dbContext, int mediaItemId) => |
|
|
|
|
|
|
|
await dbContext.MediaItems |
|
|
|
|
|
|
|
.Include(mi => (mi as Episode).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.MediaFiles) |
|
|
|
|
|
|
|
.Include(mi => (mi as Episode).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.Streams) |
|
|
|
|
|
|
|
.Include(mi => (mi as Episode).EpisodeMetadata) |
|
|
|
|
|
|
|
.ThenInclude(em => em.Subtitles) |
|
|
|
|
|
|
|
.Include(mi => (mi as Movie).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.MediaFiles) |
|
|
|
|
|
|
|
.Include(mi => (mi as Movie).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.Streams) |
|
|
|
|
|
|
|
.Include(mi => (mi as Movie).MovieMetadata) |
|
|
|
|
|
|
|
.ThenInclude(em => em.Subtitles) |
|
|
|
|
|
|
|
.Include(mi => (mi as MusicVideo).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.MediaFiles) |
|
|
|
|
|
|
|
.Include(mi => (mi as MusicVideo).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.Streams) |
|
|
|
|
|
|
|
.Include(mi => (mi as MusicVideo).MusicVideoMetadata) |
|
|
|
|
|
|
|
.ThenInclude(em => em.Subtitles) |
|
|
|
|
|
|
|
.Include(mi => (mi as OtherVideo).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.MediaFiles) |
|
|
|
|
|
|
|
.Include(mi => (mi as OtherVideo).MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.Streams) |
|
|
|
|
|
|
|
.Include(mi => (mi as OtherVideo).OtherVideoMetadata) |
|
|
|
|
|
|
|
.ThenInclude(em => em.Subtitles) |
|
|
|
|
|
|
|
.SelectOneAsync(e => e.Id, e => e.Id == mediaItemId); |
|
|
|
|
|
|
|
|
|
|
|
private static Option<List<Subtitle>> GetSubtitles(MediaItem mediaItem) => |
|
|
|
private static Option<List<Subtitle>> GetSubtitles(MediaItem mediaItem) => |
|
|
|
mediaItem switch |
|
|
|
mediaItem switch |
|
|
|
{ |
|
|
|
{ |
|
|
|
@ -330,44 +333,64 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
_ => None |
|
|
|
_ => None |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
private async Task<Unit> ExtractFonts( |
|
|
|
private async Task ExtractFonts( |
|
|
|
TvContext dbContext, |
|
|
|
TvContext dbContext, |
|
|
|
int mediaItemId, |
|
|
|
int mediaItemId, |
|
|
|
string ffmpegPath, |
|
|
|
string ffmpegPath, |
|
|
|
CancellationToken cancellationToken) |
|
|
|
CancellationToken cancellationToken) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Option<Episode> maybeEpisode = await dbContext.Episodes |
|
|
|
foreach (MediaItem mediaItem in await GetMediaItem(dbContext, mediaItemId)) |
|
|
|
.Include(e => e.MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.MediaFiles) |
|
|
|
|
|
|
|
.Include(e => e.MediaVersions) |
|
|
|
|
|
|
|
.ThenInclude(mv => mv.Streams) |
|
|
|
|
|
|
|
.Include(e => e.EpisodeMetadata) |
|
|
|
|
|
|
|
.ThenInclude(em => em.Subtitles) |
|
|
|
|
|
|
|
.SelectOneAsync(e => e.Id, e => e.Id == mediaItemId); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach (Episode episode in maybeEpisode) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
string mediaItemPath = episode.GetHeadVersion().MediaFiles.Head().Path; |
|
|
|
MediaVersion headVersion = mediaItem.GetHeadVersion(); |
|
|
|
|
|
|
|
var attachments = headVersion.Streams |
|
|
|
var arguments = $"-nostdin -hide_banner -dump_attachment:t \"\" -i \"{mediaItemPath}\" -y"; |
|
|
|
.Filter(s => s.MediaStreamKind == MediaStreamKind.Attachment) |
|
|
|
|
|
|
|
.OrderBy(s => s.Index) |
|
|
|
BufferedCommandResult result = await Cli.Wrap(ffmpegPath) |
|
|
|
.ToList(); |
|
|
|
.WithWorkingDirectory(FileSystemLayout.FontsCacheFolder) |
|
|
|
|
|
|
|
.WithArguments(arguments) |
|
|
|
|
|
|
|
.WithValidation(CommandResultValidation.None) |
|
|
|
|
|
|
|
.ExecuteBufferedAsync(cancellationToken); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if (result.ExitCode == 0)
|
|
|
|
|
|
|
|
// {
|
|
|
|
|
|
|
|
// _logger.LogDebug("Successfully extracted attached fonts");
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// else
|
|
|
|
|
|
|
|
// {
|
|
|
|
|
|
|
|
// _logger.LogError("Failed to extract attached fonts. {Error}", result.StandardError);
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Unit.Default; |
|
|
|
for (var attachmentIndex = 0; attachmentIndex < attachments.Count; attachmentIndex++) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
MediaStream fontStream = attachments[attachmentIndex]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!(fontStream.MimeType ?? string.Empty).Contains("font") && |
|
|
|
|
|
|
|
!(fontStream.MimeType ?? string.Empty).Contains("opentype")) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// not a font
|
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string fullOutputPath = Path.Combine(FileSystemLayout.FontsCacheFolder, fontStream.FileName); |
|
|
|
|
|
|
|
if (_localFileSystem.FileExists(fullOutputPath)) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// already extracted
|
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
string mediaItemPath = await GetMediaItemPath(mediaItem); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var arguments = |
|
|
|
|
|
|
|
$"-nostdin -hide_banner -dump_attachment:t:{attachmentIndex} \"\" -i \"{mediaItemPath}\" -y"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BufferedCommandResult result = await Cli.Wrap(ffmpegPath) |
|
|
|
|
|
|
|
.WithWorkingDirectory(FileSystemLayout.FontsCacheFolder) |
|
|
|
|
|
|
|
.WithArguments(arguments) |
|
|
|
|
|
|
|
.WithValidation(CommandResultValidation.None) |
|
|
|
|
|
|
|
.ExecuteBufferedAsync(cancellationToken); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ffmpeg seems to return exit code 1 in all cases when dumping an attachment
|
|
|
|
|
|
|
|
// so ignore it and check success a different way
|
|
|
|
|
|
|
|
if (_localFileSystem.FileExists(fullOutputPath)) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
_logger.LogDebug("Successfully extracted font {Font}", fontStream.FileName); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
_logger.LogError( |
|
|
|
|
|
|
|
"Failed to extract attached font {Font}. {Error}", |
|
|
|
|
|
|
|
fontStream.FileName, |
|
|
|
|
|
|
|
result.StandardError); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static Task<Validation<BaseError, string>> FFmpegPathMustExist(TvContext dbContext) => |
|
|
|
private static Task<Validation<BaseError, string>> FFmpegPathMustExist(TvContext dbContext) => |
|
|
|
@ -442,6 +465,4 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private record SubtitleToExtract(Subtitle Subtitle, string OutputPath); |
|
|
|
private record SubtitleToExtract(Subtitle Subtitle, string OutputPath); |
|
|
|
|
|
|
|
|
|
|
|
private record FontToExtract(MediaStream Stream, string OutputPath); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|