Support subtitle offset

pull/702/head
Luke Pulverenti 10 years ago
parent f86e8a415a
commit dd7825f6c8

@ -34,6 +34,9 @@ namespace MediaBrowser.Api.Library
[ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Format { get; set; }
[ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public long StartPositionTicks { get; set; }
}
[Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")]
@ -127,10 +130,12 @@ namespace MediaBrowser.Api.Library
private async Task<Stream> GetSubtitles(GetSubtitle request)
{
var stream = await _subtitleEncoder.GetSubtitles(request.Id, request.MediaSourceId, request.Index, request.Format,
CancellationToken.None);
return stream;
return await _subtitleEncoder.GetSubtitles(request.Id,
request.MediaSourceId,
request.Index,
request.Format,
request.StartPositionTicks,
CancellationToken.None).ConfigureAwait(false);
}
public void Delete(DeleteSubtitle request)

@ -65,30 +65,24 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <value>The media encoder.</value>
protected IMediaEncoder MediaEncoder { get; private set; }
protected IEncodingManager EncodingManager { get; private set; }
protected IDtoService DtoService { get; private set; }
protected IFileSystem FileSystem { get; private set; }
protected IItemRepository ItemRepository { get; private set; }
protected ILiveTvManager LiveTvManager { get; private set; }
protected IDlnaManager DlnaManager { get; private set; }
protected IChannelManager ChannelManager { get; private set; }
protected IHttpClient HttpClient { get; private set; }
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient)
protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder)
{
HttpClient = httpClient;
SubtitleEncoder = subtitleEncoder;
ChannelManager = channelManager;
DlnaManager = dlnaManager;
EncodingManager = encodingManager;
LiveTvManager = liveTvManager;
ItemRepository = itemRepository;
FileSystem = fileSystem;
DtoService = dtoService;
ServerConfigurationManager = serverConfig;
UserManager = userManager;
LibraryManager = libraryManager;
@ -587,7 +581,7 @@ namespace MediaBrowser.Api.Playback
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
{
var charenc = MediaEncoder.GetSubtitleLanguageEncodingParam(subtitlePath, state.SubtitleStream.Language);
var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language);
if (!string.IsNullOrEmpty(charenc))
{
@ -1374,6 +1368,12 @@ namespace MediaBrowser.Api.Playback
var path = recording.RecordingInfo.Path;
var mediaUrl = recording.RecordingInfo.Url;
var source = string.IsNullOrEmpty(request.MediaSourceId)
? recording.GetMediaSources(false).First()
: recording.GetMediaSources(false).First(i => string.Equals(i.Id, request.MediaSourceId));
mediaStreams = source.MediaStreams;
if (string.IsNullOrWhiteSpace(path) && string.IsNullOrWhiteSpace(mediaUrl))
{
var streamInfo = await LiveTvManager.GetRecordingStream(request.Id, cancellationToken).ConfigureAwait(false);
@ -1453,51 +1453,45 @@ namespace MediaBrowser.Api.Playback
}
else
{
var mediaSource = string.IsNullOrWhiteSpace(request.MediaSourceId)
? item
: LibraryManager.GetItemById(request.MediaSourceId);
var hasMediaSources = (IHasMediaSources)item;
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
? hasMediaSources.GetMediaSources(false).First()
: hasMediaSources.GetMediaSources(false).First(i => string.Equals(i.Id, request.MediaSourceId));
mediaStreams = mediaSource.MediaStreams;
state.MediaPath = mediaSource.Path;
state.IsRemote = mediaSource.LocationType == LocationType.Remote;
state.InputContainer = mediaSource.Container;
var video = mediaSource as Video;
if (video != null)
if (item is Video)
{
state.IsInputVideo = true;
state.VideoType = video.VideoType;
state.IsoType = video.IsoType;
state.PlayableStreamFileNames = video.PlayableStreamFileNames == null
? new List<string>()
: video.PlayableStreamFileNames.ToList();
state.DeInterlace = string.Equals(video.Container, "wtv", StringComparison.OrdinalIgnoreCase);
if (video.Timestamp.HasValue)
if (mediaSource.VideoType.HasValue)
{
state.InputTimestamp = video.Timestamp.Value;
state.VideoType = mediaSource.VideoType.Value;
}
state.InputContainer = video.Container;
}
state.IsoType = mediaSource.IsoType;
var audio = mediaSource as Audio;
if (audio != null)
{
state.InputContainer = audio.Container;
//state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
if (mediaSource.Timestamp.HasValue)
{
state.InputTimestamp = mediaSource.Timestamp.Value;
}
}
state.RunTimeTicks = mediaSource.RunTimeTicks;
}
var videoRequest = request as VideoStreamRequest;
mediaStreams = mediaStreams ?? ItemRepository.GetMediaStreams(new MediaStreamQuery
if (string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase))
{
ItemId = new Guid(string.IsNullOrWhiteSpace(request.MediaSourceId) ? request.Id : request.MediaSourceId)
state.DeInterlace = true;
}
}).ToList();
var videoRequest = request as VideoStreamRequest;
AttachMediaStreamInfo(state, mediaStreams, videoRequest, url);

@ -25,7 +25,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public abstract class BaseHlsService : BaseStreamingService
{
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
{
}

@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Hls
public class DynamicHlsService : BaseHlsService
{
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
{
}

@ -56,7 +56,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public class VideoHlsService : BaseHlsService
{
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
{
}

@ -44,7 +44,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class AudioService : BaseProgressiveStreamingService
{
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient, imageProcessor)
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, imageProcessor, httpClient)
{
}

@ -4,11 +4,9 @@ using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using ServiceStack.Web;
using System;
@ -25,10 +23,12 @@ namespace MediaBrowser.Api.Playback.Progressive
public abstract class BaseProgressiveStreamingService : BaseStreamingService
{
protected readonly IImageProcessor ImageProcessor;
protected readonly IHttpClient HttpClient;
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
{
ImageProcessor = imageProcessor;
HttpClient = httpClient;
}
/// <summary>

@ -61,7 +61,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class VideoService : BaseProgressiveStreamingService
{
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient, imageProcessor)
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, imageProcessor, httpClient)
{
}

@ -7,7 +7,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
namespace MediaBrowser.Controller.Entities.Audio
{
@ -15,7 +14,6 @@ namespace MediaBrowser.Controller.Entities.Audio
/// Class Audio
/// </summary>
public class Audio : BaseItem,
IHasMediaStreams,
IHasAlbumArtist,
IHasArtist,
IHasMusicGenres,

@ -1,10 +0,0 @@

namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// This is essentially a marker interface
/// </summary>
public interface IHasMediaStreams
{
}
}

@ -20,7 +20,6 @@ namespace MediaBrowser.Controller.Entities
/// Class Video
/// </summary>
public class Video : BaseItem,
IHasMediaStreams,
IHasAspectRatio,
IHasTags,
ISupportsPlaceHolders,

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Controller.LiveTv
{
public interface ILiveTvRecording : IHasImages, IHasMediaStreams
public interface ILiveTvRecording : IHasImages, IHasMediaSources
{
string ServiceName { get; set; }

@ -128,7 +128,6 @@
<Compile Include="Entities\IHasImages.cs" />
<Compile Include="Entities\IHasKeywords.cs" />
<Compile Include="Entities\IHasMediaSources.cs" />
<Compile Include="Entities\IHasMediaStreams.cs" />
<Compile Include="Entities\IHasMetascore.cs" />
<Compile Include="Entities\IHasPreferredMetadataLanguage.cs" />
<Compile Include="Entities\IHasProductionLocations.cs" />

@ -41,26 +41,6 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
Task<Stream> ExtractVideoImage(string[] inputFiles, InputType type, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
/// <summary>
/// Extracts the text subtitle.
/// </summary>
/// <param name="inputFiles">The input files.</param>
/// <param name="type">The type.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="copySubtitleStream">if set to true, copy stream instead of converting.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, bool copySubtitleStream, string outputPath, CancellationToken cancellationToken);
/// <summary>
/// Gets the subtitle language encoding parameter.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="language">The language.</param>
/// <returns>System.String.</returns>
string GetSubtitleLanguageEncodingParam(string path, string language);
/// <summary>
/// Gets the media info.

@ -6,16 +6,46 @@ namespace MediaBrowser.Controller.MediaEncoding
{
public interface ISubtitleEncoder
{
/// <summary>
/// Converts the subtitles.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="inputFormat">The input format.</param>
/// <param name="outputFormat">The output format.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
Task<Stream> ConvertSubtitles(
Stream stream,
string inputFormat,
Stream stream,
string inputFormat,
string outputFormat,
long startTimeTicks,
CancellationToken cancellationToken);
Task<Stream> GetSubtitles(string itemId,
/// <summary>
/// Gets the subtitles.
/// </summary>
/// <param name="itemId">The item identifier.</param>
/// <param name="mediaSourceId">The media source identifier.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="outputFormat">The output format.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
Task<Stream> GetSubtitles(string itemId,
string mediaSourceId,
int subtitleStreamIndex,
string outputFormat,
long startTimeTicks,
CancellationToken cancellationToken);
/// <summary>
/// Gets the subtitle language encoding parameter.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="language">The language.</param>
/// <returns>System.String.</returns>
string GetSubtitleFileCharacterSet(string path, string language);
}
}

@ -285,289 +285,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
return string.Empty;
}
/// <summary>
/// Gets the subtitle language encoding param.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="language">The language.</param>
/// <returns>System.String.</returns>
public string GetSubtitleLanguageEncodingParam(string path, string language)
{
if (GetFileEncoding(path).Equals(Encoding.UTF8))
{
return string.Empty;
}
switch (language.ToLower())
{
case "pol":
case "cze":
case "ces":
case "slo":
case "slk":
case "hun":
case "slv":
case "srp":
case "hrv":
case "rum":
case "ron":
case "rup":
case "alb":
case "sqi":
return "windows-1250";
case "ara":
return "windows-1256";
case "heb":
return "windows-1255";
case "grc":
case "gre":
return "windows-1253";
case "crh":
case "ota":
case "tur":
return "windows-1254";
case "rus":
return "windows-1251";
case "vie":
return "windows-1258";
case "kor":
return "cp949";
default:
return "windows-1252";
}
}
private static Encoding GetFileEncoding(string srcFile)
{
// *** Detect byte order mark if any - otherwise assume default
var buffer = new byte[5];
using (var file = new FileStream(srcFile, FileMode.Open))
{
file.Read(buffer, 0, 5);
}
if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)
return Encoding.UTF8;
if (buffer[0] == 0xfe && buffer[1] == 0xff)
return Encoding.Unicode;
if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)
return Encoding.UTF32;
if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76)
return Encoding.UTF7;
// It's ok - anything aside from utf is ok since that's what we're looking for
return Encoding.Default;
}
/// <summary>
/// Extracts the text subtitle.
/// </summary>
/// <param name="inputFiles">The input files.</param>
/// <param name="type">The type.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="copySubtitleStream">if set to true, copy stream instead of converting.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentException">Must use inputPath list overload</exception>
public async Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex,
bool copySubtitleStream, string outputPath, CancellationToken cancellationToken)
{
var semaphore = GetLock(outputPath);
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (!File.Exists(outputPath))
{
await
ExtractTextSubtitleInternal(GetInputArgument(inputFiles, type), subtitleStreamIndex,
copySubtitleStream, outputPath, cancellationToken).ConfigureAwait(false);
}
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// Extracts the text subtitle.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="copySubtitleStream">if set to true, copy stream instead of converting.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">inputPath
/// or
/// outputPath
/// or
/// cancellationToken</exception>
/// <exception cref="System.ApplicationException"></exception>
private async Task ExtractTextSubtitleInternal(string inputPath, int subtitleStreamIndex,
bool copySubtitleStream, string outputPath, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
{
throw new ArgumentNullException("inputPath");
}
if (string.IsNullOrEmpty(outputPath))
{
throw new ArgumentNullException("outputPath");
}
string processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s ass \"{2}\"", inputPath,
subtitleStreamIndex, outputPath);
if (copySubtitleStream)
{
processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s copy \"{2}\"", inputPath,
subtitleStreamIndex, outputPath);
}
var process = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = true,
FileName = FFMpegPath,
Arguments = processArgs,
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
}
};
_logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-extract-" + Guid.NewGuid() + ".txt");
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read,
true);
try
{
process.Start();
}
catch (Exception ex)
{
logFileStream.Dispose();
_logger.ErrorException("Error starting ffmpeg", ex);
throw;
}
process.StandardError.BaseStream.CopyToAsync(logFileStream);
var ranToCompletion = process.WaitForExit(60000);
if (!ranToCompletion)
{
try
{
_logger.Info("Killing ffmpeg subtitle extraction process");
process.Kill();
process.WaitForExit(1000);
}
catch (Exception ex)
{
_logger.ErrorException("Error killing subtitle extraction process", ex);
}
finally
{
logFileStream.Dispose();
}
}
var exitCode = ranToCompletion ? process.ExitCode : -1;
process.Dispose();
var failed = false;
if (exitCode == -1)
{
failed = true;
if (File.Exists(outputPath))
{
try
{
_logger.Info("Deleting extracted subtitle due to failure: ", outputPath);
File.Delete(outputPath);
}
catch (IOException ex)
{
_logger.ErrorException("Error deleting extracted subtitle {0}", ex, outputPath);
}
}
}
else if (!File.Exists(outputPath))
{
failed = true;
}
if (failed)
{
var msg = string.Format("ffmpeg subtitle extraction failed for {0} to {1}", inputPath, outputPath);
_logger.Error(msg);
throw new ApplicationException(msg);
}
else
{
var msg = string.Format("ffmpeg subtitle extraction completed for {0} to {1}", inputPath, outputPath);
_logger.Info(msg);
}
await SetAssFont(outputPath).ConfigureAwait(false);
}
/// <summary>
/// Sets the ass font.
/// </summary>
/// <param name="file">The file.</param>
/// <returns>Task.</returns>
private async Task SetAssFont(string file)
{
_logger.Info("Setting ass font within {0}", file);
string text;
Encoding encoding;
using (var reader = new StreamReader(file, detectEncodingFromByteOrderMarks: true))
{
encoding = reader.CurrentEncoding;
text = await reader.ReadToEndAsync().ConfigureAwait(false);
}
var newText = text.Replace(",Arial,", ",Arial Unicode MS,");
if (!string.Equals(text, newText))
{
using (var writer = new StreamWriter(file, false, encoding))
{
writer.Write(newText);
}
}
}
public Task<Stream> ExtractAudioImage(string path, CancellationToken cancellationToken)
{
return ExtractImage(new[] { path }, InputType.File, true, null, null, cancellationToken);

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;

@ -47,13 +47,16 @@ namespace MediaBrowser.MediaEncoding.Subtitles
public async Task<Stream> ConvertSubtitles(Stream stream,
string inputFormat,
string outputFormat,
long startTimeTicks,
CancellationToken cancellationToken)
{
var ms = new MemoryStream();
try
{
if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase))
// Return the original without any conversions, if possible
if (startTimeTicks == 0 &&
string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase))
{
await stream.CopyToAsync(ms, 81920, cancellationToken).ConfigureAwait(false);
}
@ -61,6 +64,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
var trackInfo = await GetTrackInfo(stream, inputFormat, cancellationToken).ConfigureAwait(false);
UpdateStartingPosition(trackInfo, startTimeTicks);
var writer = GetWriter(outputFormat);
writer.Write(trackInfo, ms, cancellationToken);
@ -76,10 +81,26 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return ms;
}
private void UpdateStartingPosition(SubtitleTrackInfo track, long startPositionTicks)
{
if (startPositionTicks == 0) return;
foreach (var trackEvent in track.TrackEvents)
{
trackEvent.EndPositionTicks -= startPositionTicks;
trackEvent.StartPositionTicks -= startPositionTicks;
}
track.TrackEvents = track.TrackEvents
.SkipWhile(i => i.StartPositionTicks < 0 || i.EndPositionTicks < 0)
.ToList();
}
public async Task<Stream> GetSubtitles(string itemId,
string mediaSourceId,
int subtitleStreamIndex,
string outputFormat,
long startTimeTicks,
CancellationToken cancellationToken)
{
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
@ -89,7 +110,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
var inputFormat = subtitle.Item2;
return await ConvertSubtitles(stream, inputFormat, outputFormat, cancellationToken).ConfigureAwait(false);
return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, cancellationToken).ConfigureAwait(false);
}
}
@ -127,11 +148,36 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, inputType, subtitleStream, cancellationToken).ConfigureAwait(false);
var stream = File.OpenRead(fileInfo.Item1);
var stream = await GetSubtitleStream(fileInfo.Item1, subtitleStream.Language).ConfigureAwait(false);
return new Tuple<Stream, string>(stream, fileInfo.Item2);
}
private async Task<Stream> GetSubtitleStream(string path, string language)
{
if (!string.IsNullOrEmpty(language))
{
var charset = GetSubtitleFileCharacterSet(path, language);
if (!string.IsNullOrEmpty(charset))
{
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
{
using (var reader = new StreamReader(fs, Encoding.GetEncoding(charset)))
{
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
var bytes = Encoding.UTF8.GetBytes(text);
return new MemoryStream(bytes);
}
}
}
}
return File.OpenRead(path);
}
private async Task<Tuple<string, string>> GetReadableFile(string mediaPath,
string[] inputFiles,
InputType type,
@ -282,10 +328,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new ArgumentNullException("outputPath");
}
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
var encodingParam = string.IsNullOrEmpty(language)
? string.Empty
: _mediaEncoder.GetSubtitleLanguageEncodingParam(inputPath, language);
: GetSubtitleFileCharacterSet(inputPath, language);
if (!string.IsNullOrEmpty(encodingParam))
{
@ -456,7 +503,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new ArgumentNullException("outputPath");
}
string processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s ass \"{2}\"", inputPath,
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s ass \"{2}\"", inputPath,
subtitleStreamIndex, outputPath);
if (copySubtitleStream)
@ -615,5 +664,80 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return Path.Combine(SubtitleCachePath, prefix, filename);
}
/// <summary>
/// Gets the subtitle language encoding param.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="language">The language.</param>
/// <returns>System.String.</returns>
public string GetSubtitleFileCharacterSet(string path, string language)
{
if (GetFileEncoding(path).Equals(Encoding.UTF8))
{
return string.Empty;
}
switch (language.ToLower())
{
case "pol":
case "cze":
case "ces":
case "slo":
case "slk":
case "hun":
case "slv":
case "srp":
case "hrv":
case "rum":
case "ron":
case "rup":
case "alb":
case "sqi":
return "windows-1250";
case "ara":
return "windows-1256";
case "heb":
return "windows-1255";
case "grc":
case "gre":
return "windows-1253";
case "crh":
case "ota":
case "tur":
return "windows-1254";
case "rus":
return "windows-1251";
case "vie":
return "windows-1258";
case "kor":
return "cp949";
default:
return "windows-1252";
}
}
private static Encoding GetFileEncoding(string srcFile)
{
// *** Detect byte order mark if any - otherwise assume default
var buffer = new byte[5];
using (var file = new FileStream(srcFile, FileMode.Open))
{
file.Read(buffer, 0, 5);
}
if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)
return Encoding.UTF8;
if (buffer[0] == 0xfe && buffer[1] == 0xff)
return Encoding.Unicode;
if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)
return Encoding.UTF32;
if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76)
return Encoding.UTF7;
// It's ok - anything aside from utf is ok since that's what we're looking for
return Encoding.Default;
}
}
}

@ -130,7 +130,6 @@ namespace MediaBrowser.Model.Entities
/// <value><c>true</c> if this instance is external; otherwise, <c>false</c>.</value>
public bool IsExternal { get; set; }
[IgnoreDataMember]
public bool IsGraphicalSubtitleStream
{
get

@ -1,5 +1,4 @@
using System.Threading;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
@ -30,7 +29,6 @@ namespace MediaBrowser.Server.Implementations.Dto
{
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataRepository;
private readonly IItemRepository _itemRepo;
@ -41,11 +39,10 @@ namespace MediaBrowser.Server.Implementations.Dto
private readonly Func<IChannelManager> _channelManagerFactory;
public DtoService(ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager, Func<IChannelManager> channelManagerFactory)
public DtoService(ILogger logger, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager, Func<IChannelManager> channelManagerFactory)
{
_logger = logger;
_libraryManager = libraryManager;
_userManager = userManager;
_userDataRepository = userDataRepository;
_itemRepo = itemRepo;
_imageProcessor = imageProcessor;
@ -993,9 +990,9 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.MediaStreams))
{
// Add VideoInfo
var iHasMediaStreams = item as IHasMediaStreams;
var iHasMediaSources = item as IHasMediaSources;
if (iHasMediaStreams != null)
if (iHasMediaSources != null)
{
List<MediaStream> mediaStreams;
@ -1007,11 +1004,7 @@ namespace MediaBrowser.Server.Implementations.Dto
}
else
{
mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery
{
ItemId = item.Id
}).ToList();
mediaStreams = iHasMediaSources.GetMediaSources(true).First().MediaStreams;
}
dto.MediaStreams = mediaStreams;

@ -498,7 +498,7 @@ namespace MediaBrowser.ServerApplication
ImageProcessor = new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, MediaEncoder);
RegisterSingleInstance(ImageProcessor);
DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager);
DtoService = new DtoService(Logger, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager);
RegisterSingleInstance(DtoService);
SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient);

@ -46,18 +46,12 @@ namespace MediaBrowser.ServerApplication
var json = FormatJson(_jsonSerializer.SerializeToString(item));
if (item is IHasMediaStreams)
var hasMediaSources = item as IHasMediaSources;
if (hasMediaSources != null)
{
var mediaStreams = _itemRepository.GetMediaStreams(new MediaStreamQuery
{
ItemId = item.Id
var sources = hasMediaSources.GetMediaSources(false).ToList();
}).ToList();
if (mediaStreams.Count > 0)
{
json += "\n\nMedia Streams:\n\n" + FormatJson(_jsonSerializer.SerializeToString(mediaStreams));
}
json += "\n\nMedia Sources:\n\n" + FormatJson(_jsonSerializer.SerializeToString(sources));
}
txtJson.Text = json;

Loading…
Cancel
Save