rework text subtitles

This commit is contained in:
Luke Pulverenti 2014-01-10 08:52:01 -05:00
parent c23bd68220
commit ec4000404d
21 changed files with 293 additions and 175 deletions

View File

@ -1,11 +1,11 @@
using System.Globalization;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using ServiceStack;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -27,6 +27,20 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string UserId { get; set; }
/// <summary>
/// Skips over a given number of items within the results. Use for paging.
/// </summary>
/// <value>The start index.</value>
[ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? StartIndex { get; set; }
/// <summary>
/// The maximum number of items to return
/// </summary>
/// <value>The limit.</value>
[ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Limit { get; set; }
}
[Route("/LiveTv/Channels/{Id}", "GET")]
@ -116,26 +130,26 @@ namespace MediaBrowser.Api.LiveTv
public string SeriesTimerId { get; set; }
}
[Route("/LiveTv/Programs", "GET")]
[Route("/LiveTv/Programs", "GET,POST")]
[Api(Description = "Gets available live tv epgs..")]
public class GetPrograms : IReturn<QueryResult<ProgramInfoDto>>
{
[ApiMember(Name = "ChannelIds", Description = "The channels to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
[ApiMember(Name = "ChannelIds", Description = "The channels to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string ChannelIds { get; set; }
[ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
[ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string UserId { get; set; }
[ApiMember(Name = "MinStartDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
[ApiMember(Name = "MinStartDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string MinStartDate { get; set; }
[ApiMember(Name = "MaxStartDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
[ApiMember(Name = "MaxStartDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string MaxStartDate { get; set; }
[ApiMember(Name = "MinEndDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
[ApiMember(Name = "MinEndDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string MinEndDate { get; set; }
[ApiMember(Name = "MaxEndDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
[ApiMember(Name = "MaxEndDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string MaxEndDate { get; set; }
}
@ -260,7 +274,9 @@ namespace MediaBrowser.Api.LiveTv
var result = _liveTvManager.GetChannels(new ChannelQuery
{
ChannelType = request.Type,
UserId = request.UserId
UserId = request.UserId,
StartIndex = request.StartIndex,
Limit = request.Limit
}, CancellationToken.None).Result;
@ -309,6 +325,11 @@ namespace MediaBrowser.Api.LiveTv
return ToOptimizedResult(result);
}
public object Post(GetPrograms request)
{
return Get(request);
}
public object Get(GetRecordings request)
{
var result = _liveTvManager.GetRecordings(new RecordingQuery

View File

@ -253,25 +253,44 @@ namespace MediaBrowser.Api.Playback
return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
}
protected EncodingQuality GetQualitySetting()
{
var quality = ServerConfigurationManager.Configuration.MediaEncodingQuality;
if (quality == EncodingQuality.Auto)
{
var cpuCount = Environment.ProcessorCount;
if (cpuCount >= 4)
{
return EncodingQuality.HighQuality;
}
return EncodingQuality.HighSpeed;
}
return quality;
}
/// <summary>
/// Gets the number of threads.
/// </summary>
/// <returns>System.Int32.</returns>
/// <exception cref="System.Exception">Unrecognized MediaEncodingQuality value.</exception>
protected int GetNumberOfThreads()
protected int GetNumberOfThreads(bool isWebm)
{
var quality = ServerConfigurationManager.Configuration.MediaEncodingQuality;
// Webm: http://www.webmproject.org/docs/encoder-parameters/
// The decoder will usually automatically use an appropriate number of threads according to how many cores are available but it can only use multiple threads
// for the coefficient data if the encoder selected --token-parts > 0 at encode time.
switch (quality)
switch (GetQualitySetting())
{
case EncodingQuality.Auto:
return 0;
case EncodingQuality.HighSpeed:
return 2;
case EncodingQuality.HighQuality:
return 2;
return isWebm ? Math.Min(3, Environment.ProcessorCount - 1) : 2;
case EncodingQuality.MaxQuality:
return 0;
return isWebm ? Math.Max(2, Environment.ProcessorCount - 1) : 0;
default:
throw new Exception("Unrecognized MediaEncodingQuality value.");
}
@ -285,30 +304,74 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected string GetVideoQualityParam(StreamState state, string videoCodec)
{
var args = string.Empty;
// webm
if (videoCodec.Equals("libvpx", StringComparison.OrdinalIgnoreCase))
{
args = "-speed 16 -quality good -profile:v 0 -slices 8";
// http://www.webmproject.org/docs/encoder-parameters/
return "-speed 16 -quality good -profile:v 0 -slices 8";
}
// asf/wmv
else if (videoCodec.Equals("wmv2", StringComparison.OrdinalIgnoreCase))
if (videoCodec.Equals("wmv2", StringComparison.OrdinalIgnoreCase))
{
args = "-g 100 -qmax 15";
return "-g 100 -qmax 15";
}
else if (videoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase))
if (videoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase))
{
args = "-preset superfast";
}
else if (videoCodec.Equals("mpeg4", StringComparison.OrdinalIgnoreCase))
{
args = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
return "-preset superfast";
}
return args.Trim();
if (videoCodec.Equals("mpeg4", StringComparison.OrdinalIgnoreCase))
{
return "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
}
return string.Empty;
}
protected string GetAudioFilterParam(StreamState state, bool isHls)
{
var volParam = string.Empty;
var audioSampleRate = string.Empty;
var channels = GetNumAudioChannelsParam(state.Request, state.AudioStream);
// Boost volume to 200% when downsampling from 6ch to 2ch
if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
{
volParam = ",volume=2.000000";
}
if (state.Request.AudioSampleRate.HasValue)
{
audioSampleRate = state.Request.AudioSampleRate.Value + ":";
}
var adelay = isHls ? "adelay=1," : string.Empty;
var pts = string.Empty;
if (state.SubtitleStream != null)
{
if (state.SubtitleStream.Codec.IndexOf("srt", StringComparison.OrdinalIgnoreCase) != -1 ||
state.SubtitleStream.Codec.IndexOf("subrip", StringComparison.OrdinalIgnoreCase) != -1 ||
string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) ||
string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
{
var seconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds;
pts = string.Format(",asetpts=PTS-{0}/TB",
Math.Round(seconds).ToString(UsCulture));
}
}
return string.Format("-af \"{0}aresample={1}async=1{2}{3}\"",
adelay,
audioSampleRate,
volParam,
pts);
}
/// <summary>
@ -323,6 +386,7 @@ namespace MediaBrowser.Api.Playback
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
var assSubtitleParam = string.Empty;
var copyTsParam = string.Empty;
var request = state.VideoRequest;
@ -333,7 +397,8 @@ namespace MediaBrowser.Api.Playback
string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) ||
string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
{
assSubtitleParam = GetTextSubtitleParam(state, request.StartTimeTicks, performTextSubtitleConversion);
assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion);
copyTsParam = " -copyts";
}
}
@ -343,7 +408,7 @@ namespace MediaBrowser.Api.Playback
var widthParam = request.Width.Value.ToString(UsCulture);
var heightParam = request.Height.Value.ToString(UsCulture);
return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam);
return string.Format("{3} -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam, copyTsParam);
}
var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase);
@ -354,8 +419,8 @@ namespace MediaBrowser.Api.Playback
var widthParam = request.Width.Value.ToString(UsCulture);
return isH264Output ?
string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", widthParam, assSubtitleParam) :
string.Format(" -vf \"scale={0}:-1{1}\"", widthParam, assSubtitleParam);
string.Format("{2} -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", widthParam, assSubtitleParam, copyTsParam) :
string.Format("{2} -vf \"scale={0}:-1{1}\"", widthParam, assSubtitleParam, copyTsParam);
}
// If a fixed height was requested
@ -364,8 +429,8 @@ namespace MediaBrowser.Api.Playback
var heightParam = request.Height.Value.ToString(UsCulture);
return isH264Output ?
string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", heightParam, assSubtitleParam) :
string.Format(" -vf \"scale=-1:{0}{1}\"", heightParam, assSubtitleParam);
string.Format("{2} -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", heightParam, assSubtitleParam, copyTsParam) :
string.Format("{2} -vf \"scale=-1:{0}{1}\"", heightParam, assSubtitleParam, copyTsParam);
}
// If a max width was requested
@ -374,8 +439,8 @@ namespace MediaBrowser.Api.Playback
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
return isH264Output ?
string.Format(" -vf \"scale=min(iw\\,{0}):trunc(ow/a/2)*2{1}\"", maxWidthParam, assSubtitleParam) :
string.Format(" -vf \"scale=min(iw\\,{0}):-1{1}\"", maxWidthParam, assSubtitleParam);
string.Format("{2} -vf \"scale=min(iw\\,{0}):trunc(ow/a/2)*2{1}\"", maxWidthParam, assSubtitleParam, copyTsParam) :
string.Format("{2} -vf \"scale=min(iw\\,{0}):-1{1}\"", maxWidthParam, assSubtitleParam, copyTsParam);
}
// If a max height was requested
@ -384,8 +449,8 @@ namespace MediaBrowser.Api.Playback
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
return isH264Output ?
string.Format(" -vf \"scale=trunc(oh*a*2)/2:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam) :
string.Format(" -vf \"scale=-1:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam);
string.Format("{2} -vf \"scale=trunc(oh*a*2)/2:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam, copyTsParam) :
string.Format("{2} -vf \"scale=-1:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam, copyTsParam);
}
if (state.VideoStream == null)
@ -408,45 +473,45 @@ namespace MediaBrowser.Api.Playback
var widthParam = outputSize.Width.ToString(UsCulture);
var heightParam = outputSize.Height.ToString(UsCulture);
return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam);
return string.Format("{3} -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam, copyTsParam);
}
// Otherwise use -vf scale since ffmpeg will ensure internally that the aspect ratio is preserved
return string.Format(" -vf \"scale={0}:-1{1}\"", Convert.ToInt32(outputSize.Width), assSubtitleParam);
return string.Format("{2} -vf \"scale={0}:-1{1}\"", Convert.ToInt32(outputSize.Width), assSubtitleParam, copyTsParam);
}
/// <summary>
/// Gets the text subtitle param.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <returns>System.String.</returns>
protected string GetTextSubtitleParam(StreamState state, long? startTimeTicks, bool performConversion)
protected string GetTextSubtitleParam(StreamState state, bool performConversion)
{
var path = state.SubtitleStream.IsExternal ? GetConvertedAssPath(state.MediaPath, state.SubtitleStream, startTimeTicks, performConversion) :
GetExtractedAssPath(state, startTimeTicks, performConversion);
var path = state.SubtitleStream.IsExternal ? GetConvertedAssPath(state.MediaPath, state.SubtitleStream, performConversion) :
GetExtractedAssPath(state, performConversion);
if (string.IsNullOrEmpty(path))
{
return string.Empty;
}
return string.Format(",ass='{0}'", path.Replace('\\', '/').Replace(":/", "\\:/"));
var seconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds;
return string.Format(",ass='{0}',setpts=PTS -{1}/TB",
path.Replace('\\', '/').Replace(":/", "\\:/"),
Math.Round(seconds).ToString(UsCulture));
}
/// <summary>
/// Gets the extracted ass path.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <returns>System.String.</returns>
private string GetExtractedAssPath(StreamState state, long? startTimeTicks, bool performConversion)
private string GetExtractedAssPath(StreamState state, bool performConversion)
{
var offset = TimeSpan.FromTicks(startTimeTicks ?? 0);
var path = FFMpegManager.Instance.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream, offset, ".ass");
var path = FFMpegManager.Instance.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream, ".ass");
if (performConversion)
{
@ -460,7 +525,7 @@ namespace MediaBrowser.Api.Playback
Directory.CreateDirectory(parentPath);
var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, offset, path, CancellationToken.None);
var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, path, CancellationToken.None);
Task.WaitAll(task);
}
@ -478,14 +543,11 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="mediaPath">The media path.</param>
/// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <returns>System.String.</returns>
private string GetConvertedAssPath(string mediaPath, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
private string GetConvertedAssPath(string mediaPath, MediaStream subtitleStream, bool performConversion)
{
var offset = TimeSpan.FromTicks(startTimeTicks ?? 0);
var path = FFMpegManager.Instance.GetSubtitleCachePath(mediaPath, subtitleStream, offset, ".ass");
var path = FFMpegManager.Instance.GetSubtitleCachePath(mediaPath, subtitleStream, ".ass");
if (performConversion)
{
@ -495,7 +557,7 @@ namespace MediaBrowser.Api.Playback
Directory.CreateDirectory(parentPath);
var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, offset, CancellationToken.None);
var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, CancellationToken.None);
Task.WaitAll(task);
}
@ -534,9 +596,9 @@ namespace MediaBrowser.Api.Playback
videoSizeParam = string.Format(",scale={0}:{1}", state.VideoStream.Width.Value.ToString(UsCulture), state.VideoStream.Height.Value.ToString(UsCulture));
}
return string.Format(" -filter_complex \"[0:{0}]format=yuva444p{3},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{1}] [sub] overlay{2}\"",
state.SubtitleStream.Index,
state.VideoStream.Index,
return string.Format(" -filter_complex \"[0:{0}]format=yuva444p{3},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{1}] [sub] overlay{2}\"",
state.SubtitleStream.Index,
state.VideoStream.Index,
outputSizeParam,
videoSizeParam);
}

View File

@ -113,7 +113,7 @@ namespace MediaBrowser.Api.Playback.Hls
if (isPlaylistNewlyCreated)
{
var minimumSegmentCount = 3;
var quality = ServerConfigurationManager.Configuration.MediaEncodingQuality;
var quality = GetQualitySetting();
if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality)
{
@ -267,9 +267,9 @@ namespace MediaBrowser.Api.Playback.Hls
var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds);
var threads = GetNumberOfThreads();
var threads = GetNumberOfThreads(false);
var args = string.Format("{0}{1} {2} {3} -i {4}{5} -threads {6} {7} {8} -sc_threshold 0 {9} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{10}\"",
var args = string.Format("{0}{1} {2} {3} -i {4}{5} -map_metadata -1 -threads {6} {7} {8} -sc_threshold 0 {9} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{10}\"",
itsOffset,
probeSize,
GetUserAgentParam(state.MediaPath),

View File

@ -80,21 +80,7 @@ namespace MediaBrowser.Api.Playback.Hls
args += " -ab " + bitrate.Value.ToString(UsCulture);
}
var volParam = string.Empty;
var audioSampleRate = string.Empty;
// Boost volume to 200% when downsampling from 6ch to 2ch
if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
{
volParam = ",volume=2.000000";
}
if (state.Request.AudioSampleRate.HasValue)
{
audioSampleRate = state.Request.AudioSampleRate.Value + ":";
}
args += string.Format(" -af \"adelay=1,aresample={0}async=1{1}\"", audioSampleRate, volParam);
args += " " + GetAudioFilterParam(state, true);
return args;
}

View File

@ -102,7 +102,7 @@ namespace MediaBrowser.Api.Playback.Progressive
const string vn = " -vn";
var threads = GetNumberOfThreads();
var threads = GetNumberOfThreads(false);
return string.Format("{0} -i {1}{2} -threads {3}{4} {5} -id3v2_version 3 -write_id3v1 1 \"{6}\"",
GetFastSeekCommandLineParameter(request),

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.IO;
using System;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@ -106,6 +107,7 @@ namespace MediaBrowser.Api.Playback.Progressive
var transferMode = GetHeader("transferMode.dlna.org");
responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
var contentFeatures = string.Empty;
var extension = GetOutputFileExtension(state);
@ -118,22 +120,22 @@ namespace MediaBrowser.Api.Playback.Progressive
const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000";
//if (string.Equals(extension, ".mp3", StringComparison.OrdinalIgnoreCase))
//{
// contentFeatures = "DLNA.ORG_PN=MP3";
//}
//else if (string.Equals(extension, ".aac", StringComparison.OrdinalIgnoreCase))
//{
// contentFeatures = "DLNA.ORG_PN=AAC_ISO";
//}
//else if (string.Equals(extension, ".wma", StringComparison.OrdinalIgnoreCase))
//{
// contentFeatures = "DLNA.ORG_PN=WMABASE";
//}
//else if (string.Equals(extension, ".avi", StringComparison.OrdinalIgnoreCase))
//{
// contentFeatures = "DLNA.ORG_PN=AVI";
//}
if (string.Equals(extension, ".mp3", StringComparison.OrdinalIgnoreCase))
{
contentFeatures = "DLNA.ORG_PN=MP3";
}
else if (string.Equals(extension, ".aac", StringComparison.OrdinalIgnoreCase))
{
contentFeatures = "DLNA.ORG_PN=AAC_ISO";
}
else if (string.Equals(extension, ".wma", StringComparison.OrdinalIgnoreCase))
{
contentFeatures = "DLNA.ORG_PN=WMABASE";
}
else if (string.Equals(extension, ".avi", StringComparison.OrdinalIgnoreCase))
{
contentFeatures = "DLNA.ORG_PN=AVI";
}
//else if (string.Equals(extension, ".mp4", StringComparison.OrdinalIgnoreCase))
//{
// contentFeatures = "DLNA.ORG_PN=MPEG4_P2_SP_AAC";

View File

@ -104,9 +104,9 @@ namespace MediaBrowser.Api.Playback.Progressive
format = " -f mp4 -movflags frag_keyframe+empty_moov";
}
var threads = string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase) ? 2 : GetNumberOfThreads();
var threads = GetNumberOfThreads(string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
return string.Format("{0} {1} {2} -i {3}{4}{5} {6} {7} -threads {8} {9}{10} \"{11}\"",
return string.Format("{0} {1} {2} -i {3}{4}{5} {6} {7} -map_metadata -1 -threads {8} {9}{10} \"{11}\"",
probeSize,
GetUserAgentParam(state.MediaPath),
GetFastSeekCommandLineParameter(state.Request),
@ -170,6 +170,7 @@ namespace MediaBrowser.Api.Playback.Progressive
if (bitrate.HasValue)
{
qualityParam += string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
//qualityParam += string.Format(" -maxrate {0} -bufsize {1}", bitrate.Value.ToString(UsCulture), (bitrate.Value * 2).ToString(UsCulture));
}
if (!string.IsNullOrEmpty(qualityParam))
@ -238,21 +239,7 @@ namespace MediaBrowser.Api.Playback.Progressive
args += " -ab " + bitrate.Value.ToString(UsCulture);
}
var volParam = string.Empty;
var AudioSampleRate = string.Empty;
// Boost volume to 200% when downsampling from 6ch to 2ch
if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
{
volParam = ",volume=2.000000";
}
if (state.Request.AudioSampleRate.HasValue)
{
AudioSampleRate = state.Request.AudioSampleRate.Value + ":";
}
args += string.Format(" -af \"aresample={0}async=1{1}\"", AudioSampleRate, volParam);
args += " " + GetAudioFilterParam(state, true);
return args;
}

View File

@ -40,11 +40,10 @@ namespace MediaBrowser.Common.MediaInfo
/// <param name="inputFiles">The input files.</param>
/// <param name="type">The type.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="offset">The offset.</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, TimeSpan offset, string outputPath, CancellationToken cancellationToken);
Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken);
/// <summary>
/// Converts the text subtitle to ass.
@ -52,10 +51,9 @@ namespace MediaBrowser.Common.MediaInfo
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="language">The language.</param>
/// <param name="offset">The offset.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, TimeSpan offset, CancellationToken cancellationToken);
Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, CancellationToken cancellationToken);
/// <summary>
/// Gets the media info.

View File

@ -67,7 +67,7 @@ namespace MediaBrowser.Common.Net
}
if (ext.Equals(".avi", StringComparison.OrdinalIgnoreCase))
{
return "video/avi";
return "video/x-msvideo";
}
if (ext.Equals(".m4v", StringComparison.OrdinalIgnoreCase))
{

View File

@ -235,12 +235,11 @@ namespace MediaBrowser.Controller.MediaInfo
/// </summary>
/// <param name="mediaPath">The media path.</param>
/// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="offset">The offset.</param>
/// <param name="outputExtension">The output extension.</param>
/// <returns>System.String.</returns>
public string GetSubtitleCachePath(string mediaPath, MediaStream subtitleStream, TimeSpan? offset, string outputExtension)
public string GetSubtitleCachePath(string mediaPath, MediaStream subtitleStream, string outputExtension)
{
var ticksParam = offset.HasValue ? "_" + offset.Value.Ticks : "";
var ticksParam = string.Empty;
if (subtitleStream.IsExternal)
{

View File

@ -17,5 +17,17 @@ namespace MediaBrowser.Model.LiveTv
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
/// <summary>
/// Skips over a given number of items within the results. Use for paging.
/// </summary>
/// <value>The start index.</value>
public int? StartIndex { get; set; }
/// <summary>
/// The maximum number of items to return
/// </summary>
/// <value>The limit.</value>
public int? Limit { get; set; }
}
}

View File

@ -51,7 +51,7 @@ namespace MediaBrowser.Providers.Movies
{
var list = new List<RemoteImageInfo>();
var results = FetchImages(item, _jsonSerializer);
var results = await FetchImages((BaseItem)item, _jsonSerializer, cancellationToken).ConfigureAwait(false);
if (results == null)
{
@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.Movies
var tmdbImageUrl = tmdbSettings.images.base_url + "original";
list.AddRange(GetPosters(results, item).Select(i => new RemoteImageInfo
list.AddRange(GetPosters(results).Select(i => new RemoteImageInfo
{
Url = tmdbImageUrl + i.file_path,
CommunityRating = i.vote_average,
@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.Movies
RatingType = RatingType.Score
}));
list.AddRange(GetBackdrops(results, item).Select(i => new RemoteImageInfo
list.AddRange(GetBackdrops(results).Select(i => new RemoteImageInfo
{
Url = tmdbImageUrl + i.file_path,
CommunityRating = i.vote_average,
@ -119,9 +119,8 @@ namespace MediaBrowser.Providers.Movies
/// Gets the posters.
/// </summary>
/// <param name="images">The images.</param>
/// <param name="item">The item.</param>
/// <returns>IEnumerable{MovieDbProvider.Poster}.</returns>
private IEnumerable<MovieDbProvider.Poster> GetPosters(MovieDbProvider.Images images, IHasImages item)
private IEnumerable<MovieDbProvider.Poster> GetPosters(MovieDbProvider.Images images)
{
return images.posters ?? new List<MovieDbProvider.Poster>();
}
@ -130,9 +129,8 @@ namespace MediaBrowser.Providers.Movies
/// Gets the backdrops.
/// </summary>
/// <param name="images">The images.</param>
/// <param name="item">The item.</param>
/// <returns>IEnumerable{MovieDbProvider.Backdrop}.</returns>
private IEnumerable<MovieDbProvider.Backdrop> GetBackdrops(MovieDbProvider.Images images, IHasImages item)
private IEnumerable<MovieDbProvider.Backdrop> GetBackdrops(MovieDbProvider.Images images)
{
var eligibleBackdrops = images.backdrops == null ? new List<MovieDbProvider.Backdrop>() :
images.backdrops
@ -147,10 +145,14 @@ namespace MediaBrowser.Providers.Movies
/// </summary>
/// <param name="item">The item.</param>
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MovieImages}.</returns>
private MovieDbProvider.Images FetchImages(IHasImages item, IJsonSerializer jsonSerializer)
private async Task<MovieDbProvider.Images> FetchImages(BaseItem item, IJsonSerializer jsonSerializer,
CancellationToken cancellationToken)
{
var path = MovieDbProvider.Current.GetDataFilePath((BaseItem)item);
await MovieDbProvider.Current.EnsureMovieInfo(item, cancellationToken).ConfigureAwait(false);
var path = MovieDbProvider.Current.GetDataFilePath(item);
if (!string.IsNullOrEmpty(path))
{

View File

@ -52,7 +52,7 @@ namespace MediaBrowser.Providers.Movies
if (!string.IsNullOrEmpty(id))
{
await MovieDbPersonProvider.Current.DownloadPersonInfoIfNeeded(id, cancellationToken).ConfigureAwait(false);
await MovieDbPersonProvider.Current.EnsurePersonInfo(id, cancellationToken).ConfigureAwait(false);
var dataFilePath = MovieDbPersonProvider.GetPersonDataFilePath(_config.ApplicationPaths, id);

View File

@ -235,7 +235,7 @@ namespace MediaBrowser.Providers.Movies
/// <returns>Task.</returns>
private async Task FetchInfo(Person person, string id, bool isForcedRefresh, CancellationToken cancellationToken)
{
await DownloadPersonInfoIfNeeded(id, cancellationToken).ConfigureAwait(false);
await EnsurePersonInfo(id, cancellationToken).ConfigureAwait(false);
if (isForcedRefresh || !HasAltMeta(person))
{
@ -249,7 +249,7 @@ namespace MediaBrowser.Providers.Movies
}
}
internal async Task DownloadPersonInfoIfNeeded(string id, CancellationToken cancellationToken)
internal async Task EnsurePersonInfo(string id, CancellationToken cancellationToken)
{
var personDataPath = GetPersonDataPath(ConfigurationManager.ApplicationPaths, id);

View File

@ -558,6 +558,31 @@ namespace MediaBrowser.Providers.Movies
JsonSerializer.SerializeToFile(mainResult, dataFilePath);
}
internal Task EnsureMovieInfo(BaseItem item, CancellationToken cancellationToken)
{
var path = GetDataFilePath(item);
var fileInfo = _fileSystem.GetFileSystemInfo(path);
if (fileInfo.Exists)
{
// If it's recent or automatic updates are enabled, don't re-download
if (ConfigurationManager.Configuration.EnableTmdbUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
{
return Task.FromResult(true);
}
}
var id = item.GetProviderId(MetadataProviders.Tmdb);
if (string.IsNullOrEmpty(id))
{
return Task.FromResult(true);
}
return DownloadMovieInfo(id, item is BoxSet, item.GetPreferredMetadataLanguage(), cancellationToken);
}
/// <summary>
/// Gets the data file path.
/// </summary>
@ -575,7 +600,7 @@ namespace MediaBrowser.Providers.Movies
return GetDataFilePath(item is BoxSet, id, item.GetPreferredMetadataLanguage());
}
internal string GetDataFilePath(bool isBoxset, string tmdbId, string preferredLanguage)
private string GetDataFilePath(bool isBoxset, string tmdbId, string preferredLanguage)
{
var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, isBoxset, tmdbId);

View File

@ -69,7 +69,7 @@ namespace MediaBrowser.Providers.Movies
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
if (!_config.Configuration.EnableInternetProviders)
if (!_config.Configuration.EnableInternetProviders && !_config.Configuration.EnableTmdbUpdates)
{
progress.Report(100);
return;

View File

@ -98,7 +98,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
});
}
var returnChannels = channels.OrderBy(i =>
channels = channels.OrderBy(i =>
{
double number = 0;
@ -109,14 +109,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return number;
}).ThenBy(i => i.Name)
.Select(i => _tvDtoService.GetChannelInfoDto(i, GetCurrentProgram(i.ChannelInfo.Id), user))
.ToArray();
}).ThenBy(i => i.Name);
var allChannels = channels.ToList();
IEnumerable<LiveTvChannel> allEnumerable = allChannels;
if (query.StartIndex.HasValue)
{
allEnumerable = allEnumerable.Skip(query.StartIndex.Value);
}
if (query.Limit.HasValue)
{
allEnumerable = allEnumerable.Take(query.Limit.Value);
}
var returnChannels = allEnumerable
.Select(i => _tvDtoService.GetChannelInfoDto(i, GetCurrentProgram(i.ChannelInfo.Id), user))
.ToArray();
var result = new QueryResult<ChannelInfoDto>
{
Items = returnChannels,
TotalRecordCount = returnChannels.Length
TotalRecordCount = allChannels.Count
};
return Task.FromResult(result);
@ -575,9 +590,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
.Where(i => _tvDtoService.GetInternalSeriesTimerId(currentServiceName, i.SeriesTimerId) == guid);
}
IEnumerable<ILiveTvRecording> entities = await GetEntities(recordings, service.Name, cancellationToken).ConfigureAwait(false);
recordings = recordings.OrderByDescending(i => i.StartDate);
entities = entities.OrderByDescending(i => i.RecordingInfo.StartDate);
IEnumerable<ILiveTvRecording> entities = await GetEntities(recordings, service.Name, cancellationToken).ConfigureAwait(false);
if (user != null)
{

View File

@ -96,7 +96,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
{
return _semaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
}
/// <summary>
/// Gets the media info.
/// </summary>
@ -378,11 +378,9 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="language">The language.</param>
/// <param name="offset">The offset.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public async Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, TimeSpan offset,
CancellationToken cancellationToken)
public async Task ConvertTextSubtitleToAss(string inputPath, string outputPath, string language, CancellationToken cancellationToken)
{
var semaphore = GetLock(outputPath);
@ -392,7 +390,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
{
if (!File.Exists(outputPath))
{
await ConvertTextSubtitleToAssInternal(inputPath, outputPath, language, offset).ConfigureAwait(false);
await ConvertTextSubtitleToAssInternal(inputPath, outputPath, language).ConfigureAwait(false);
}
}
finally
@ -409,13 +407,12 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="language">The language.</param>
/// <param name="offset">The offset.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">inputPath
/// or
/// outputPath</exception>
/// <exception cref="System.ApplicationException"></exception>
private async Task ConvertTextSubtitleToAssInternal(string inputPath, string outputPath, string language, TimeSpan offset)
private async Task ConvertTextSubtitleToAssInternal(string inputPath, string outputPath, string language)
{
if (string.IsNullOrEmpty(inputPath))
{
@ -427,8 +424,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
throw new ArgumentNullException("outputPath");
}
var slowSeekParam = offset.TotalSeconds > 0 ? " -ss " + offset.TotalSeconds.ToString(UsCulture) : string.Empty;
var encodingParam = string.IsNullOrEmpty(language) ? string.Empty :
GetSubtitleLanguageEncodingParam(language) + " ";
@ -444,7 +439,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
UseShellExecute = false,
FileName = FFMpegPath,
Arguments =
string.Format("{0} -i \"{1}\" {2} -c:s ass \"{3}\"", encodingParam, inputPath, slowSeekParam, outputPath),
string.Format("{0} -i \"{1}\" -c:s ass \"{2}\"", encodingParam, inputPath, outputPath),
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
@ -557,7 +552,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
return string.Empty;
}
/// <summary>
/// Gets the subtitle language encoding param.
/// </summary>
@ -598,7 +593,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
case "vie":
return "-sub_charenc windows-1258";
case "kor":
return "-sub_charenc cp949";
return "-sub_charenc cp949";
default:
return "-sub_charenc windows-1252";
}
@ -610,12 +605,11 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// <param name="inputFiles">The input files.</param>
/// <param name="type">The type.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="offset">The offset.</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, TimeSpan offset, string outputPath, CancellationToken cancellationToken)
public async Task ExtractTextSubtitle(string[] inputFiles, InputType type, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken)
{
var semaphore = GetLock(outputPath);
@ -625,7 +619,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
{
if (!File.Exists(outputPath))
{
await ExtractTextSubtitleInternal(GetInputArgument(inputFiles, type), subtitleStreamIndex, offset, outputPath, cancellationToken).ConfigureAwait(false);
await ExtractTextSubtitleInternal(GetInputArgument(inputFiles, type), subtitleStreamIndex, outputPath, cancellationToken).ConfigureAwait(false);
}
}
finally
@ -639,7 +633,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="offset">The offset.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
@ -649,7 +642,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// or
/// cancellationToken</exception>
/// <exception cref="System.ApplicationException"></exception>
private async Task ExtractTextSubtitleInternal(string inputPath, int subtitleStreamIndex, TimeSpan offset, string outputPath, CancellationToken cancellationToken)
private async Task ExtractTextSubtitleInternal(string inputPath, int subtitleStreamIndex, string outputPath, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
{
@ -661,9 +654,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
throw new ArgumentNullException("outputPath");
}
var slowSeekParam = GetSlowSeekCommandLineParameter(offset);
var fastSeekParam = GetFastSeekCommandLineParameter(offset);
var process = new Process
{
@ -676,7 +666,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
RedirectStandardError = true,
FileName = FFMpegPath,
Arguments = string.Format(" {0} -i {1} {2} -map 0:{3} -an -vn -c:s ass \"{4}\"", fastSeekParam, inputPath, slowSeekParam, subtitleStreamIndex, outputPath),
Arguments = string.Format("-i {0} -map 0:{1} -an -vn -c:s ass \"{2}\"", inputPath, subtitleStreamIndex, outputPath),
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
}
@ -872,8 +862,8 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
switch (threedFormat.Value)
{
case Video3DFormat.HalfSideBySide:
vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
// hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
// hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
break;
case Video3DFormat.FullSideBySide:
vf = "crop=iw/2:ih:0:0,setdar=dar=a,,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
@ -882,7 +872,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
case Video3DFormat.HalfTopAndBottom:
vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
//htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600
break;
break;
case Video3DFormat.FullTopAndBottom:
vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
// ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600
@ -892,7 +882,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"thumbnail,{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf) :
string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf);
string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf);
var probeSize = GetProbeSizeArgument(type);

View File

@ -90,6 +90,12 @@ namespace MediaBrowser.Server.Implementations.Session
var vals = message.Data.Split('|');
if (vals.Length < 3)
{
_logger.Error("Client sent invalid identity message.");
return;
}
var client = vals[0];
var deviceId = vals[1];
var version = vals[2];

View File

@ -436,13 +436,26 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
self.getLiveTvPrograms = function (options) {
var url = self.getUrl("LiveTv/Programs", options || {});
options = options || {};
if (options.channelIds) {
return self.ajax({
type: "GET",
url: url,
dataType: "json"
});
return self.ajax({
type: "POST",
url: self.getUrl("LiveTv/Programs"),
data: JSON.stringify(options),
contentType: "application/json",
dataType: "json"
});
} else {
return self.ajax({
type: "GET",
url: self.getUrl("LiveTv/Programs", options),
dataType: "json"
});
}
};
self.getLiveTvRecordings = function (options) {

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MediaBrowser.ApiClient.Javascript" version="3.0.219" targetFramework="net45" />
<package id="MediaBrowser.ApiClient.Javascript" version="3.0.223" targetFramework="net45" />
</packages>