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);
}

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)
}).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

@ -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))
{
@ -428,8 +425,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
}
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
@ -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))
{
@ -662,9 +655,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
}
var slowSeekParam = GetSlowSeekCommandLineParameter(offset);
var fastSeekParam = GetFastSeekCommandLineParameter(offset);
var process = new Process
{
StartInfo = new ProcessStartInfo
@ -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
}

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: "POST",
url: self.getUrl("LiveTv/Programs"),
data: JSON.stringify(options),
contentType: "application/json",
dataType: "json"
});
} else {
return self.ajax({
type: "GET",
url: url,
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>