improve direct play of live streams

This commit is contained in:
Luke Pulverenti 2015-04-04 15:35:29 -04:00
parent 8c61abf6d2
commit 2b7a80cfb5
25 changed files with 1220 additions and 898 deletions

View File

@ -223,7 +223,7 @@ namespace MediaBrowser.Api.Playback
int? subtitleStreamIndex, int? subtitleStreamIndex,
string playSessionId) string playSessionId)
{ {
var streamBuilder = new StreamBuilder(); var streamBuilder = new StreamBuilder(Logger);
var options = new VideoOptions var options = new VideoOptions
{ {

View File

@ -211,7 +211,6 @@
<Compile Include="MediaEncoding\IEncodingManager.cs" /> <Compile Include="MediaEncoding\IEncodingManager.cs" />
<Compile Include="MediaEncoding\ImageEncodingOptions.cs" /> <Compile Include="MediaEncoding\ImageEncodingOptions.cs" />
<Compile Include="MediaEncoding\IMediaEncoder.cs" /> <Compile Include="MediaEncoding\IMediaEncoder.cs" />
<Compile Include="MediaEncoding\InternalMediaInfoResult.cs" />
<Compile Include="MediaEncoding\ISubtitleEncoder.cs" /> <Compile Include="MediaEncoding\ISubtitleEncoder.cs" />
<Compile Include="MediaEncoding\MediaStreamSelector.cs" /> <Compile Include="MediaEncoding\MediaStreamSelector.cs" />
<Compile Include="Net\AuthenticatedAttribute.cs" /> <Compile Include="Net\AuthenticatedAttribute.cs" />

View File

@ -68,11 +68,13 @@ namespace MediaBrowser.Controller.MediaEncoding
/// Gets the media info. /// Gets the media info.
/// </summary> /// </summary>
/// <param name="inputFiles">The input files.</param> /// <param name="inputFiles">The input files.</param>
/// <param name="primaryPath">The primary path.</param>
/// <param name="protocol">The protocol.</param> /// <param name="protocol">The protocol.</param>
/// <param name="isAudio">if set to <c>true</c> [is audio].</param> /// <param name="isAudio">if set to <c>true</c> [is audio].</param>
/// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task<InternalMediaInfoResult> GetMediaInfo(string[] inputFiles, MediaProtocol protocol, bool isAudio, CancellationToken cancellationToken); Task<MediaInfo> GetMediaInfo(string[] inputFiles, string primaryPath, MediaProtocol protocol, bool isAudio, bool extractChapters, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Gets the probe size argument. /// Gets the probe size argument.

View File

@ -1,9 +1,7 @@
using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -46,291 +44,5 @@ namespace MediaBrowser.Controller.MediaEncoding
.Where(f => !string.IsNullOrEmpty(f)) .Where(f => !string.IsNullOrEmpty(f))
.ToList(); .ToList();
} }
public static MediaInfo GetMediaInfo(InternalMediaInfoResult data)
{
var internalStreams = data.streams ?? new MediaStreamInfo[] { };
var info = new MediaInfo
{
MediaStreams = internalStreams.Select(s => GetMediaStream(s, data.format))
.Where(i => i != null)
.ToList()
};
if (data.format != null)
{
info.Format = data.format.format_name;
if (!string.IsNullOrEmpty(data.format.bit_rate))
{
info.TotalBitrate = int.Parse(data.format.bit_rate, UsCulture);
}
}
return info;
}
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary>
/// Converts ffprobe stream info to our MediaStream class
/// </summary>
/// <param name="streamInfo">The stream info.</param>
/// <param name="formatInfo">The format info.</param>
/// <returns>MediaStream.</returns>
private static MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo)
{
var stream = new MediaStream
{
Codec = streamInfo.codec_name,
Profile = streamInfo.profile,
Level = streamInfo.level,
Index = streamInfo.index,
PixelFormat = streamInfo.pix_fmt
};
if (streamInfo.tags != null)
{
stream.Language = GetDictionaryValue(streamInfo.tags, "language");
}
if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase))
{
stream.Type = MediaStreamType.Audio;
stream.Channels = streamInfo.channels;
if (!string.IsNullOrEmpty(streamInfo.sample_rate))
{
stream.SampleRate = int.Parse(streamInfo.sample_rate, UsCulture);
}
stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout);
}
else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase))
{
stream.Type = MediaStreamType.Subtitle;
}
else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase))
{
stream.Type = (streamInfo.codec_name ?? string.Empty).IndexOf("mjpeg", StringComparison.OrdinalIgnoreCase) != -1
? MediaStreamType.EmbeddedImage
: MediaStreamType.Video;
stream.Width = streamInfo.width;
stream.Height = streamInfo.height;
stream.AspectRatio = GetAspectRatio(streamInfo);
stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate);
stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate);
stream.BitDepth = GetBitDepth(stream.PixelFormat);
//stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
// string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) ||
// string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);
stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase);
}
else
{
return null;
}
// Get stream bitrate
var bitrate = 0;
if (!string.IsNullOrEmpty(streamInfo.bit_rate))
{
bitrate = int.Parse(streamInfo.bit_rate, UsCulture);
}
else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video)
{
// If the stream info doesn't have a bitrate get the value from the media format info
bitrate = int.Parse(formatInfo.bit_rate, UsCulture);
}
if (bitrate > 0)
{
stream.BitRate = bitrate;
}
if (streamInfo.disposition != null)
{
var isDefault = GetDictionaryValue(streamInfo.disposition, "default");
var isForced = GetDictionaryValue(streamInfo.disposition, "forced");
stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase);
stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase);
}
return stream;
}
private static int? GetBitDepth(string pixelFormat)
{
var eightBit = new List<string>
{
"yuv420p",
"yuv411p",
"yuvj420p",
"uyyvyy411",
"nv12",
"nv21",
"rgb444le",
"rgb444be",
"bgr444le",
"bgr444be",
"yuvj411p"
};
if (!string.IsNullOrEmpty(pixelFormat))
{
if (eightBit.Contains(pixelFormat, StringComparer.OrdinalIgnoreCase))
{
return 8;
}
}
return null;
}
/// <summary>
/// Gets a string from an FFProbeResult tags dictionary
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
/// <returns>System.String.</returns>
private static string GetDictionaryValue(Dictionary<string, string> tags, string key)
{
if (tags == null)
{
return null;
}
string val;
tags.TryGetValue(key, out val);
return val;
}
private static string ParseChannelLayout(string input)
{
if (string.IsNullOrEmpty(input))
{
return input;
}
return input.Split('(').FirstOrDefault();
}
private static string GetAspectRatio(MediaStreamInfo info)
{
var original = info.display_aspect_ratio;
int height;
int width;
var parts = (original ?? string.Empty).Split(':');
if (!(parts.Length == 2 &&
int.TryParse(parts[0], NumberStyles.Any, UsCulture, out width) &&
int.TryParse(parts[1], NumberStyles.Any, UsCulture, out height) &&
width > 0 &&
height > 0))
{
width = info.width;
height = info.height;
}
if (width > 0 && height > 0)
{
double ratio = width;
ratio /= height;
if (IsClose(ratio, 1.777777778, .03))
{
return "16:9";
}
if (IsClose(ratio, 1.3333333333, .05))
{
return "4:3";
}
if (IsClose(ratio, 1.41))
{
return "1.41:1";
}
if (IsClose(ratio, 1.5))
{
return "1.5:1";
}
if (IsClose(ratio, 1.6))
{
return "1.6:1";
}
if (IsClose(ratio, 1.66666666667))
{
return "5:3";
}
if (IsClose(ratio, 1.85, .02))
{
return "1.85:1";
}
if (IsClose(ratio, 2.35, .025))
{
return "2.35:1";
}
if (IsClose(ratio, 2.4, .025))
{
return "2.40:1";
}
}
return original;
}
private static bool IsClose(double d1, double d2, double variance = .005)
{
return Math.Abs(d1 - d2) <= variance;
}
/// <summary>
/// Gets a frame rate from a string value in ffprobe output
/// This could be a number or in the format of 2997/125.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>System.Nullable{System.Single}.</returns>
private static float? GetFrameRate(string value)
{
if (!string.IsNullOrEmpty(value))
{
var parts = value.Split('/');
float result;
if (parts.Length == 2)
{
result = float.Parse(parts[0], UsCulture) / float.Parse(parts[1], UsCulture);
}
else
{
result = float.Parse(parts[0], UsCulture);
}
return float.IsNaN(result) ? (float?)null : result;
}
return null;
}
} }
} }

View File

@ -12,6 +12,7 @@ using MediaBrowser.Dlna.ContentDirectory;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using System; using System;
using System.Globalization; using System.Globalization;
@ -126,7 +127,7 @@ namespace MediaBrowser.Dlna.Didl
{ {
var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList(); var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions streamInfo = new StreamBuilder(new NullLogger()).BuildVideoItem(new VideoOptions
{ {
ItemId = GetClientId(video), ItemId = GetClientId(video),
MediaSources = sources, MediaSources = sources,
@ -353,7 +354,7 @@ namespace MediaBrowser.Dlna.Didl
{ {
var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList(); var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions streamInfo = new StreamBuilder(new NullLogger()).BuildAudioItem(new AudioOptions
{ {
ItemId = GetClientId(audio), ItemId = GetClientId(audio),
MediaSources = sources, MediaSources = sources,

View File

@ -542,7 +542,7 @@ namespace MediaBrowser.Dlna.PlayTo
{ {
return new PlaylistItem return new PlaylistItem
{ {
StreamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions StreamInfo = new StreamBuilder(_logger).BuildVideoItem(new VideoOptions
{ {
ItemId = item.Id.ToString("N"), ItemId = item.Id.ToString("N"),
MediaSources = mediaSources, MediaSources = mediaSources,
@ -562,7 +562,7 @@ namespace MediaBrowser.Dlna.PlayTo
{ {
return new PlaylistItem return new PlaylistItem
{ {
StreamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions StreamInfo = new StreamBuilder(_logger).BuildAudioItem(new AudioOptions
{ {
ItemId = item.Id.ToString("N"), ItemId = item.Id.ToString("N"),
MediaSources = mediaSources, MediaSources = mediaSources,

View File

@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -103,15 +104,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// Gets the media info. /// Gets the media info.
/// </summary> /// </summary>
/// <param name="inputFiles">The input files.</param> /// <param name="inputFiles">The input files.</param>
/// <param name="primaryPath">The primary path.</param>
/// <param name="protocol">The protocol.</param> /// <param name="protocol">The protocol.</param>
/// <param name="isAudio">if set to <c>true</c> [is audio].</param> /// <param name="isAudio">if set to <c>true</c> [is audio].</param>
/// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
public Task<InternalMediaInfoResult> GetMediaInfo(string[] inputFiles, MediaProtocol protocol, bool isAudio, public Task<Model.Entities.MediaInfo> GetMediaInfo(string[] inputFiles, string primaryPath, MediaProtocol protocol, bool isAudio,
CancellationToken cancellationToken) bool extractChapters, CancellationToken cancellationToken)
{ {
return GetMediaInfoInternal(GetInputArgument(inputFiles, protocol), !isAudio, return GetMediaInfoInternal(GetInputArgument(inputFiles, protocol), primaryPath, protocol, !isAudio && extractChapters,
GetProbeSizeArgument(inputFiles, protocol), cancellationToken); GetProbeSizeArgument(inputFiles, protocol), isAudio, cancellationToken);
} }
/// <summary> /// <summary>
@ -141,13 +144,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// Gets the media info internal. /// Gets the media info internal.
/// </summary> /// </summary>
/// <param name="inputPath">The input path.</param> /// <param name="inputPath">The input path.</param>
/// <param name="primaryPath">The primary path.</param>
/// <param name="protocol">The protocol.</param>
/// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param> /// <param name="extractChapters">if set to <c>true</c> [extract chapters].</param>
/// <param name="probeSizeArgument">The probe size argument.</param> /// <param name="probeSizeArgument">The probe size argument.</param>
/// <param name="isAudio">if set to <c>true</c> [is audio].</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MediaInfoResult}.</returns> /// <returns>Task{MediaInfoResult}.</returns>
/// <exception cref="System.ApplicationException"></exception> /// <exception cref="System.ApplicationException"></exception>
private async Task<InternalMediaInfoResult> GetMediaInfoInternal(string inputPath, bool extractChapters, private async Task<Model.Entities.MediaInfo> GetMediaInfoInternal(string inputPath, string primaryPath, MediaProtocol protocol, bool extractChapters,
string probeSizeArgument, string probeSizeArgument,
bool isAudio,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var args = extractChapters var args = extractChapters
@ -244,7 +251,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
} }
return result; return new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, isAudio, primaryPath, protocol);
} }
/// <summary> /// <summary>

View File

@ -68,6 +68,9 @@
<Compile Include="Encoder\JobLogger.cs" /> <Compile Include="Encoder\JobLogger.cs" />
<Compile Include="Encoder\MediaEncoder.cs" /> <Compile Include="Encoder\MediaEncoder.cs" />
<Compile Include="Encoder\VideoEncoder.cs" /> <Compile Include="Encoder\VideoEncoder.cs" />
<Compile Include="Probing\FFProbeHelpers.cs" />
<Compile Include="Probing\InternalMediaInfoResult.cs" />
<Compile Include="Probing\ProbeResultNormalizer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Subtitles\ISubtitleParser.cs" /> <Compile Include="Subtitles\ISubtitleParser.cs" />
<Compile Include="Subtitles\ISubtitleWriter.cs" /> <Compile Include="Subtitles\ISubtitleWriter.cs" />
@ -91,6 +94,10 @@
<Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project> <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
<Name>MediaBrowser.Controller</Name> <Name>MediaBrowser.Controller</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\MediaBrowser.MediaInfo\MediaBrowser.MediaInfo.csproj">
<Project>{6e4145e4-c6d4-4e4d-94f2-87188db6e239}</Project>
<Name>MediaBrowser.MediaInfo</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name> <Name>MediaBrowser.Model</Name>

View File

@ -2,7 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace MediaBrowser.Providers.MediaInfo namespace MediaBrowser.MediaEncoding.Probing
{ {
public static class FFProbeHelpers public static class FFProbeHelpers
{ {

View File

@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace MediaBrowser.Controller.MediaEncoding namespace MediaBrowser.MediaEncoding.Probing
{ {
/// <summary> /// <summary>
/// Class MediaInfoResult /// Class MediaInfoResult

View File

@ -0,0 +1,882 @@
using MediaBrowser.Common.IO;
using MediaBrowser.MediaInfo;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Probing
{
public class ProbeResultNormalizer
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem)
{
_logger = logger;
_fileSystem = fileSystem;
}
public Model.Entities.MediaInfo GetMediaInfo(InternalMediaInfoResult data, bool isAudio, string path, MediaProtocol protocol)
{
var info = new Model.Entities.MediaInfo
{
Path = path,
Protocol = protocol
};
FFProbeHelpers.NormalizeFFProbeResult(data);
SetSize(data, info);
var internalStreams = data.streams ?? new MediaStreamInfo[] { };
info.MediaStreams = internalStreams.Select(s => GetMediaStream(s, data.format))
.Where(i => i != null)
.ToList();
if (data.format != null)
{
info.Container = data.format.format_name;
if (!string.IsNullOrEmpty(data.format.bit_rate))
{
info.Bitrate = int.Parse(data.format.bit_rate, _usCulture);
}
}
if (isAudio)
{
SetAudioRuntimeTicks(data, info);
if (data.format != null && data.format.tags != null)
{
SetAudioInfoFromTags(info, data.format.tags);
}
}
else
{
FetchWtvInfo(info, data);
if (data.Chapters != null)
{
info.Chapters = data.Chapters.Select(GetChapterInfo).ToList();
}
ExtractTimestamp(info);
var videoStream = info.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
if (videoStream != null)
{
UpdateFromMediaInfo(info, videoStream);
}
}
return info;
}
/// <summary>
/// Converts ffprobe stream info to our MediaStream class
/// </summary>
/// <param name="streamInfo">The stream info.</param>
/// <param name="formatInfo">The format info.</param>
/// <returns>MediaStream.</returns>
private MediaStream GetMediaStream(MediaStreamInfo streamInfo, MediaFormatInfo formatInfo)
{
var stream = new MediaStream
{
Codec = streamInfo.codec_name,
Profile = streamInfo.profile,
Level = streamInfo.level,
Index = streamInfo.index,
PixelFormat = streamInfo.pix_fmt
};
if (streamInfo.tags != null)
{
stream.Language = GetDictionaryValue(streamInfo.tags, "language");
}
if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase))
{
stream.Type = MediaStreamType.Audio;
stream.Channels = streamInfo.channels;
if (!string.IsNullOrEmpty(streamInfo.sample_rate))
{
stream.SampleRate = int.Parse(streamInfo.sample_rate, _usCulture);
}
stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout);
}
else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase))
{
stream.Type = MediaStreamType.Subtitle;
}
else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase))
{
stream.Type = (streamInfo.codec_name ?? string.Empty).IndexOf("mjpeg", StringComparison.OrdinalIgnoreCase) != -1
? MediaStreamType.EmbeddedImage
: MediaStreamType.Video;
stream.Width = streamInfo.width;
stream.Height = streamInfo.height;
stream.AspectRatio = GetAspectRatio(streamInfo);
stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate);
stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate);
stream.BitDepth = GetBitDepth(stream.PixelFormat);
//stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
// string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) ||
// string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);
stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase);
}
else
{
return null;
}
// Get stream bitrate
var bitrate = 0;
if (!string.IsNullOrEmpty(streamInfo.bit_rate))
{
bitrate = int.Parse(streamInfo.bit_rate, _usCulture);
}
else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video)
{
// If the stream info doesn't have a bitrate get the value from the media format info
bitrate = int.Parse(formatInfo.bit_rate, _usCulture);
}
if (bitrate > 0)
{
stream.BitRate = bitrate;
}
if (streamInfo.disposition != null)
{
var isDefault = GetDictionaryValue(streamInfo.disposition, "default");
var isForced = GetDictionaryValue(streamInfo.disposition, "forced");
stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase);
stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase);
}
return stream;
}
private int? GetBitDepth(string pixelFormat)
{
var eightBit = new List<string>
{
"yuv420p",
"yuv411p",
"yuvj420p",
"uyyvyy411",
"nv12",
"nv21",
"rgb444le",
"rgb444be",
"bgr444le",
"bgr444be",
"yuvj411p"
};
if (!string.IsNullOrEmpty(pixelFormat))
{
if (eightBit.Contains(pixelFormat, StringComparer.OrdinalIgnoreCase))
{
return 8;
}
}
return null;
}
/// <summary>
/// Gets a string from an FFProbeResult tags dictionary
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="key">The key.</param>
/// <returns>System.String.</returns>
private string GetDictionaryValue(Dictionary<string, string> tags, string key)
{
if (tags == null)
{
return null;
}
string val;
tags.TryGetValue(key, out val);
return val;
}
private string ParseChannelLayout(string input)
{
if (string.IsNullOrEmpty(input))
{
return input;
}
return input.Split('(').FirstOrDefault();
}
private string GetAspectRatio(MediaStreamInfo info)
{
var original = info.display_aspect_ratio;
int height;
int width;
var parts = (original ?? string.Empty).Split(':');
if (!(parts.Length == 2 &&
int.TryParse(parts[0], NumberStyles.Any, _usCulture, out width) &&
int.TryParse(parts[1], NumberStyles.Any, _usCulture, out height) &&
width > 0 &&
height > 0))
{
width = info.width;
height = info.height;
}
if (width > 0 && height > 0)
{
double ratio = width;
ratio /= height;
if (IsClose(ratio, 1.777777778, .03))
{
return "16:9";
}
if (IsClose(ratio, 1.3333333333, .05))
{
return "4:3";
}
if (IsClose(ratio, 1.41))
{
return "1.41:1";
}
if (IsClose(ratio, 1.5))
{
return "1.5:1";
}
if (IsClose(ratio, 1.6))
{
return "1.6:1";
}
if (IsClose(ratio, 1.66666666667))
{
return "5:3";
}
if (IsClose(ratio, 1.85, .02))
{
return "1.85:1";
}
if (IsClose(ratio, 2.35, .025))
{
return "2.35:1";
}
if (IsClose(ratio, 2.4, .025))
{
return "2.40:1";
}
}
return original;
}
private bool IsClose(double d1, double d2, double variance = .005)
{
return Math.Abs(d1 - d2) <= variance;
}
/// <summary>
/// Gets a frame rate from a string value in ffprobe output
/// This could be a number or in the format of 2997/125.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>System.Nullable{System.Single}.</returns>
private float? GetFrameRate(string value)
{
if (!string.IsNullOrEmpty(value))
{
var parts = value.Split('/');
float result;
if (parts.Length == 2)
{
result = float.Parse(parts[0], _usCulture) / float.Parse(parts[1], _usCulture);
}
else
{
result = float.Parse(parts[0], _usCulture);
}
return float.IsNaN(result) ? (float?)null : result;
}
return null;
}
private void SetAudioRuntimeTicks(InternalMediaInfoResult result, Model.Entities.MediaInfo data)
{
if (result.streams != null)
{
// Get the first audio stream
var stream = result.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase));
if (stream != null)
{
// Get duration from stream properties
var duration = stream.duration;
// If it's not there go into format properties
if (string.IsNullOrEmpty(duration))
{
duration = result.format.duration;
}
// If we got something, parse it
if (!string.IsNullOrEmpty(duration))
{
data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks;
}
}
}
}
private void SetSize(InternalMediaInfoResult data, Model.Entities.MediaInfo info)
{
if (data.format != null)
{
if (!string.IsNullOrEmpty(data.format.size))
{
info.Size = long.Parse(data.format.size, _usCulture);
}
else
{
info.Size = null;
}
}
}
private void SetAudioInfoFromTags(Model.Entities.MediaInfo audio, Dictionary<string, string> tags)
{
var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
// Only set Name if title was found in the dictionary
if (!string.IsNullOrEmpty(title))
{
audio.Title = title;
}
var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
if (!string.IsNullOrWhiteSpace(composer))
{
foreach (var person in Split(composer, false))
{
audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer });
}
}
audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
if (!string.IsNullOrWhiteSpace(artists))
{
audio.Artists = artists.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
else
{
var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist");
if (string.IsNullOrWhiteSpace(artist))
{
audio.Artists.Clear();
}
else
{
audio.Artists = SplitArtists(artist)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
}
var albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist");
if (string.IsNullOrWhiteSpace(albumArtist))
{
albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist");
}
if (string.IsNullOrWhiteSpace(albumArtist))
{
albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist");
}
if (string.IsNullOrWhiteSpace(albumArtist))
{
audio.AlbumArtists = new List<string>();
}
else
{
audio.AlbumArtists = SplitArtists(albumArtist)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
// Track number
audio.IndexNumber = GetDictionaryDiscValue(tags, "track");
// Disc number
audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc");
audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
// Several different forms of retaildate
audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "date");
// If we don't have a ProductionYear try and get it from PremiereDate
if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue)
{
audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year;
}
FetchGenres(audio, tags);
// There's several values in tags may or may not be present
FetchStudios(audio, tags, "organization");
FetchStudios(audio, tags, "ensemble");
FetchStudios(audio, tags, "publisher");
// These support mulitple values, but for now we only store the first.
audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")));
audio.SetProviderId(MetadataProviders.MusicBrainzArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")));
audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")));
audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")));
audio.SetProviderId(MetadataProviders.MusicBrainzTrack, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")));
}
private string GetMultipleMusicBrainzId(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
return value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
.Select(i => i.Trim())
.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
}
private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
/// <summary>
/// Splits the specified val.
/// </summary>
/// <param name="val">The val.</param>
/// <param name="allowCommaDelimiter">if set to <c>true</c> [allow comma delimiter].</param>
/// <returns>System.String[][].</returns>
private IEnumerable<string> Split(string val, bool allowCommaDelimiter)
{
// Only use the comma as a delimeter if there are no slashes or pipes.
// We want to be careful not to split names that have commas in them
var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ?
_nameDelimiters :
new[] { ',' };
return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim());
}
private const string ArtistReplaceValue = " | ";
private IEnumerable<string> SplitArtists(string val)
{
val = val.Replace(" featuring ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase)
.Replace(" feat. ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase);
var artistsFound = new List<string>();
foreach (var whitelistArtist in GetSplitWhitelist())
{
var originalVal = val;
val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase);
if (!string.Equals(originalVal, val, StringComparison.OrdinalIgnoreCase))
{
artistsFound.Add(whitelistArtist);
}
}
// Only use the comma as a delimeter if there are no slashes or pipes.
// We want to be careful not to split names that have commas in them
var delimeter = _nameDelimiters;
var artists = val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim());
artistsFound.AddRange(artists);
return artistsFound;
}
private List<string> _splitWhiteList = null;
private IEnumerable<string> GetSplitWhitelist()
{
if (_splitWhiteList == null)
{
var file = GetType().Namespace + ".whitelist.txt";
using (var stream = GetType().Assembly.GetManifestResourceStream(file))
{
using (var reader = new StreamReader(stream))
{
var list = new List<string>();
while (!reader.EndOfStream)
{
var val = reader.ReadLine();
if (!string.IsNullOrWhiteSpace(val))
{
list.Add(val);
}
}
_splitWhiteList = list;
}
}
}
return _splitWhiteList;
}
/// <summary>
/// Gets the studios from the tags collection
/// </summary>
/// <param name="audio">The audio.</param>
/// <param name="tags">The tags.</param>
/// <param name="tagName">Name of the tag.</param>
private void FetchStudios(Model.Entities.MediaInfo audio, Dictionary<string, string> tags, string tagName)
{
var val = FFProbeHelpers.GetDictionaryValue(tags, tagName);
if (!string.IsNullOrEmpty(val))
{
var studios = Split(val, true);
foreach (var studio in studios)
{
// Sometimes the artist name is listed here, account for that
if (audio.Artists.Contains(studio, StringComparer.OrdinalIgnoreCase))
{
continue;
}
if (audio.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase))
{
continue;
}
audio.Studios.Add(studio);
}
audio.Studios = audio.Studios
.Where(i => !string.IsNullOrWhiteSpace(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
}
/// <summary>
/// Gets the genres from the tags collection
/// </summary>
/// <param name="info">The information.</param>
/// <param name="tags">The tags.</param>
private void FetchGenres(Model.Entities.MediaInfo info, Dictionary<string, string> tags)
{
var val = FFProbeHelpers.GetDictionaryValue(tags, "genre");
if (!string.IsNullOrEmpty(val))
{
foreach (var genre in Split(val, true))
{
info.Genres.Add(genre);
}
info.Genres = info.Genres
.Where(i => !string.IsNullOrWhiteSpace(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
}
/// <summary>
/// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="tagName">Name of the tag.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
private int? GetDictionaryDiscValue(Dictionary<string, string> tags, string tagName)
{
var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName);
if (!string.IsNullOrEmpty(disc))
{
disc = disc.Split('/')[0];
int num;
if (int.TryParse(disc, out num))
{
return num;
}
}
return null;
}
private ChapterInfo GetChapterInfo(MediaChapter chapter)
{
var info = new ChapterInfo();
if (chapter.tags != null)
{
string name;
if (chapter.tags.TryGetValue("title", out name))
{
info.Name = name;
}
}
// Limit accuracy to milliseconds to match xml saving
var secondsString = chapter.start_time;
double seconds;
if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out seconds))
{
var ms = Math.Round(TimeSpan.FromSeconds(seconds).TotalMilliseconds);
info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks;
}
return info;
}
private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
private void FetchWtvInfo(Model.Entities.MediaInfo video, InternalMediaInfoResult data)
{
if (data.format == null || data.format.tags == null)
{
return;
}
var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre");
if (!string.IsNullOrWhiteSpace(genres))
{
//genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");
}
if (!string.IsNullOrWhiteSpace(genres))
{
video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim())
.ToList();
}
var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");
if (!string.IsNullOrWhiteSpace(officialRating))
{
video.OfficialRating = officialRating;
}
var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits");
if (!string.IsNullOrEmpty(people))
{
video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => new BaseItemPerson { Name = i.Trim(), Type = PersonType.Actor })
.ToList();
}
var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");
if (!string.IsNullOrWhiteSpace(year))
{
int val;
if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val))
{
video.ProductionYear = val;
}
}
var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime");
if (!string.IsNullOrWhiteSpace(premiereDateString))
{
DateTime val;
// Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
// DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None)
if (DateTime.TryParse(year, null, DateTimeStyles.None, out val))
{
video.PremiereDate = val.ToUniversalTime();
}
}
var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");
var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle");
// For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
// Sometimes for TV Shows the Subtitle field is empty and the subtitle description contains the subtitle, extract if possible. See ticket https://mcebuddy2x.codeplex.com/workitem/1910
// The format is -> EPISODE/TOTAL_EPISODES_IN_SEASON. SUBTITLE: DESCRIPTION
// OR -> COMMENT. SUBTITLE: DESCRIPTION
// e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S]
// e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S]
if (String.IsNullOrWhiteSpace(subTitle) && !String.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename
{
string[] parts = description.Split(':');
if (parts.Length > 0)
{
string subtitle = parts[0];
try
{
if (subtitle.Contains("/")) // It contains a episode number and season number
{
string[] numbers = subtitle.Split(' ');
video.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]);
int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]);
description = String.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
}
else
throw new Exception(); // Switch to default parsing
}
catch // Default parsing
{
if (subtitle.Contains(".")) // skip the comment, keep the subtitle
description = String.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first
else
description = subtitle.Trim(); // Clean up whitespaces and save it
}
}
}
if (!string.IsNullOrWhiteSpace(description))
{
video.Overview = description;
}
}
private void ExtractTimestamp(Model.Entities.MediaInfo video)
{
if (video.VideoType == VideoType.VideoFile)
{
if (string.Equals(video.Container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) ||
string.Equals(video.Container, "m2ts", StringComparison.OrdinalIgnoreCase) ||
string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase))
{
try
{
video.Timestamp = GetMpegTimestamp(video.Path);
_logger.Debug("Video has {0} timestamp", video.Timestamp);
}
catch (Exception ex)
{
_logger.ErrorException("Error extracting timestamp info from {0}", ex, video.Path);
video.Timestamp = null;
}
}
}
}
private TransportStreamTimestamp GetMpegTimestamp(string path)
{
var packetBuffer = new byte['Å'];
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
fs.Read(packetBuffer, 0, packetBuffer.Length);
}
if (packetBuffer[0] == 71)
{
return TransportStreamTimestamp.None;
}
if ((packetBuffer[4] == 71) && (packetBuffer['Ä'] == 71))
{
if ((packetBuffer[0] == 0) && (packetBuffer[1] == 0) && (packetBuffer[2] == 0) && (packetBuffer[3] == 0))
{
return TransportStreamTimestamp.Zero;
}
return TransportStreamTimestamp.Valid;
}
return TransportStreamTimestamp.None;
}
private void UpdateFromMediaInfo(MediaSourceInfo video, MediaStream videoStream)
{
if (video.VideoType == VideoType.VideoFile && video.Protocol == MediaProtocol.File)
{
if (videoStream != null)
{
try
{
var result = new MediaInfoLib().GetVideoInfo(video.Path);
videoStream.IsCabac = result.IsCabac ?? videoStream.IsCabac;
videoStream.IsInterlaced = result.IsInterlaced ?? videoStream.IsInterlaced;
videoStream.BitDepth = result.BitDepth ?? videoStream.BitDepth;
videoStream.RefFrames = result.RefFrames;
}
catch (Exception ex)
{
_logger.ErrorException("Error running MediaInfo on {0}", ex, video.Path);
}
}
}
}
}
}

View File

@ -1,6 +1,7 @@
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using System; using System;
@ -11,13 +12,16 @@ namespace MediaBrowser.Model.Dlna
public class StreamBuilder public class StreamBuilder
{ {
private readonly ILocalPlayer _localPlayer; private readonly ILocalPlayer _localPlayer;
private readonly ILogger _logger;
public StreamBuilder(ILocalPlayer localPlayer) public StreamBuilder(ILocalPlayer localPlayer, ILogger logger)
{ {
_localPlayer = localPlayer; _localPlayer = localPlayer;
_logger = logger;
} }
public StreamBuilder()
: this(new NullLocalPlayer()) public StreamBuilder(ILogger logger)
: this(new NullLocalPlayer(), logger)
{ {
} }
@ -353,6 +357,12 @@ namespace MediaBrowser.Model.Dlna
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options); bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options);
bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options); bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options);
_logger.Debug("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
options.Profile.Name ?? "Unknown Profile",
item.Path ?? "Unknown path",
isEligibleForDirectPlay,
isEligibleForDirectStream);
if (isEligibleForDirectPlay || isEligibleForDirectStream) if (isEligibleForDirectPlay || isEligibleForDirectStream)
{ {
// See if it can be direct played // See if it can be direct played
@ -504,6 +514,10 @@ namespace MediaBrowser.Model.Dlna
if (directPlay == null) if (directPlay == null)
{ {
_logger.Debug("Profile: {0}, No direct play profiles found for Path: {1}",
profile.Name ?? "Unknown Profile",
mediaSource.Path ?? "Unknown path");
return null; return null;
} }
@ -550,6 +564,11 @@ namespace MediaBrowser.Model.Dlna
{ {
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams)) if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
{ {
_logger.Debug("Profile: {0}, DirectPlay=false. Reason=VideoContainerProfile.{1} Path: {2}",
profile.Name ?? "Unknown Profile",
i.Property,
mediaSource.Path ?? "Unknown path");
return null; return null;
} }
} }
@ -558,6 +577,10 @@ namespace MediaBrowser.Model.Dlna
if (string.IsNullOrEmpty(videoCodec)) if (string.IsNullOrEmpty(videoCodec))
{ {
_logger.Debug("Profile: {0}, DirectPlay=false. Reason=Unknown video codec. Path: {1}",
profile.Name ?? "Unknown Profile",
mediaSource.Path ?? "Unknown path");
return null; return null;
} }
@ -577,6 +600,11 @@ namespace MediaBrowser.Model.Dlna
{ {
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams)) if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
{ {
_logger.Debug("Profile: {0}, DirectPlay=false. Reason=VideoCodecProfile.{1} Path: {2}",
profile.Name ?? "Unknown Profile",
i.Property,
mediaSource.Path ?? "Unknown path");
return null; return null;
} }
} }
@ -587,6 +615,10 @@ namespace MediaBrowser.Model.Dlna
if (string.IsNullOrEmpty(audioCodec)) if (string.IsNullOrEmpty(audioCodec))
{ {
_logger.Debug("Profile: {0}, DirectPlay=false. Reason=Unknown audio codec. Path: {1}",
profile.Name ?? "Unknown Profile",
mediaSource.Path ?? "Unknown path");
return null; return null;
} }
@ -607,6 +639,11 @@ namespace MediaBrowser.Model.Dlna
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream); bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio)) if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
{ {
_logger.Debug("Profile: {0}, DirectPlay=false. Reason=VideoAudioCodecProfile.{1} Path: {2}",
profile.Name ?? "Unknown Profile",
i.Property,
mediaSource.Path ?? "Unknown path");
return null; return null;
} }
} }

View File

@ -70,6 +70,7 @@ namespace MediaBrowser.Model.Dlna
public string SubtitleFormat { get; set; } public string SubtitleFormat { get; set; }
public string PlaySessionId { get; set; } public string PlaySessionId { get; set; }
public List<MediaSourceInfo> AllMediaSources { get; set; }
public string MediaSourceId public string MediaSourceId
{ {

View File

@ -1,7 +1,7 @@
using System.ComponentModel; using MediaBrowser.Model.Extensions;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Dto namespace MediaBrowser.Model.Dto
{ {

View File

@ -1,26 +1,65 @@
using MediaBrowser.Model.Dto;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace MediaBrowser.Model.Entities namespace MediaBrowser.Model.Entities
{ {
public class MediaInfo public class MediaInfo : MediaSourceInfo, IHasProviderIds
{ {
/// <summary> public List<ChapterInfo> Chapters { get; set; }
/// Gets or sets the media streams.
/// </summary>
/// <value>The media streams.</value>
public List<MediaStream> MediaStreams { get; set; }
/// <summary> /// <summary>
/// Gets or sets the format. /// Gets or sets the title.
/// </summary> /// </summary>
/// <value>The format.</value> /// <value>The title.</value>
public string Format { get; set; } public string Title { get; set; }
/// <summary>
public int? TotalBitrate { get; set; } /// Gets or sets the album.
/// </summary>
/// <value>The album.</value>
public string Album { get; set; }
/// <summary>
/// Gets or sets the artists.
/// </summary>
/// <value>The artists.</value>
public List<string> Artists { get; set; }
/// <summary>
/// Gets or sets the album artists.
/// </summary>
/// <value>The album artists.</value>
public List<string> AlbumArtists { get; set; }
/// <summary>
/// Gets or sets the studios.
/// </summary>
/// <value>The studios.</value>
public List<string> Studios { get; set; }
public List<string> Genres { get; set; }
public int? IndexNumber { get; set; }
public int? ParentIndexNumber { get; set; }
public int? ProductionYear { get; set; }
public DateTime? PremiereDate { get; set; }
public List<BaseItemPerson> People { get; set; }
public Dictionary<string, string> ProviderIds { get; set; }
/// <summary>
/// Gets or sets the official rating.
/// </summary>
/// <value>The official rating.</value>
public string OfficialRating { get; set; }
/// <summary>
/// Gets or sets the overview.
/// </summary>
/// <value>The overview.</value>
public string Overview { get; set; }
public MediaInfo() public MediaInfo()
{ {
MediaStreams = new List<MediaStream>(); Chapters = new List<ChapterInfo>();
Artists = new List<string>();
AlbumArtists = new List<string>();
Studios = new List<string>();
Genres = new List<string>();
People = new List<BaseItemPerson>();
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
} }
} }
} }

View File

@ -102,7 +102,6 @@
<Compile Include="Manager\MetadataService.cs" /> <Compile Include="Manager\MetadataService.cs" />
<Compile Include="Manager\SeriesOrderManager.cs" /> <Compile Include="Manager\SeriesOrderManager.cs" />
<Compile Include="MediaInfo\FFProbeAudioInfo.cs" /> <Compile Include="MediaInfo\FFProbeAudioInfo.cs" />
<Compile Include="MediaInfo\FFProbeHelpers.cs" />
<Compile Include="MediaInfo\FFProbeProvider.cs" /> <Compile Include="MediaInfo\FFProbeProvider.cs" />
<Compile Include="MediaInfo\FFProbeVideoInfo.cs" /> <Compile Include="MediaInfo\FFProbeVideoInfo.cs" />
<Compile Include="MediaInfo\SubtitleDownloader.cs" /> <Compile Include="MediaInfo\SubtitleDownloader.cs" />
@ -198,10 +197,6 @@
<Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project> <Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project>
<Name>MediaBrowser.Controller</Name> <Name>MediaBrowser.Controller</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\MediaBrowser.MediaInfo\MediaBrowser.MediaInfo.csproj">
<Project>{6e4145e4-c6d4-4e4d-94f2-87188db6e239}</Project>
<Name>MediaBrowser.MediaInfo</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project> <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
<Name>MediaBrowser.Model</Name> <Name>MediaBrowser.Model</Name>

View File

@ -1,5 +1,4 @@
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -8,8 +7,6 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -49,18 +46,14 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
FFProbeHelpers.NormalizeFFProbeResult(result);
cancellationToken.ThrowIfCancellationRequested();
await Fetch(item, cancellationToken, result).ConfigureAwait(false); await Fetch(item, cancellationToken, result).ConfigureAwait(false);
return ItemUpdateType.MetadataImport; return ItemUpdateType.MetadataImport;
} }
private const string SchemaVersion = "1"; private const string SchemaVersion = "2";
private async Task<InternalMediaInfoResult> GetMediaInfo(BaseItem item, CancellationToken cancellationToken) private async Task<Model.Entities.MediaInfo> GetMediaInfo(BaseItem item, CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -71,7 +64,7 @@ namespace MediaBrowser.Providers.MediaInfo
try try
{ {
return _json.DeserializeFromFile<InternalMediaInfoResult>(cachePath); return _json.DeserializeFromFile<Model.Entities.MediaInfo>(cachePath);
} }
catch (FileNotFoundException) catch (FileNotFoundException)
{ {
@ -83,7 +76,7 @@ namespace MediaBrowser.Providers.MediaInfo
var inputPath = new[] { item.Path }; var inputPath = new[] { item.Path };
var result = await _mediaEncoder.GetMediaInfo(inputPath, MediaProtocol.File, false, cancellationToken).ConfigureAwait(false); var result = await _mediaEncoder.GetMediaInfo(inputPath, item.Path, MediaProtocol.File, true, false, cancellationToken).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); Directory.CreateDirectory(Path.GetDirectoryName(cachePath));
_json.SerializeToFile(result, cachePath); _json.SerializeToFile(result, cachePath);
@ -96,61 +89,23 @@ namespace MediaBrowser.Providers.MediaInfo
/// </summary> /// </summary>
/// <param name="audio">The audio.</param> /// <param name="audio">The audio.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <param name="data">The data.</param> /// <param name="mediaInfo">The media information.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
protected Task Fetch(Audio audio, CancellationToken cancellationToken, InternalMediaInfoResult data) protected Task Fetch(Audio audio, CancellationToken cancellationToken, Model.Entities.MediaInfo mediaInfo)
{ {
var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data);
var mediaStreams = mediaInfo.MediaStreams; var mediaStreams = mediaInfo.MediaStreams;
audio.FormatName = mediaInfo.Format; audio.FormatName = mediaInfo.Container;
audio.TotalBitrate = mediaInfo.TotalBitrate; audio.TotalBitrate = mediaInfo.Bitrate;
audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.EmbeddedImage); audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.EmbeddedImage);
if (data.streams != null) audio.RunTimeTicks = mediaInfo.RunTimeTicks;
{ audio.Size = mediaInfo.Size;
// Get the first audio stream
var stream = data.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase));
if (stream != null) var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
{ audio.Container = extension;
// Get duration from stream properties
var duration = stream.duration;
// If it's not there go into format properties FetchDataFromTags(audio, mediaInfo);
if (string.IsNullOrEmpty(duration))
{
duration = data.format.duration;
}
// If we got something, parse it
if (!string.IsNullOrEmpty(duration))
{
audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks;
}
}
}
if (data.format != null)
{
var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
audio.Container = extension;
if (!string.IsNullOrEmpty(data.format.size))
{
audio.Size = long.Parse(data.format.size, _usCulture);
}
else
{
audio.Size = null;
}
if (data.format.tags != null)
{
FetchDataFromTags(audio, data.format.tags);
}
}
return _itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken); return _itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken);
} }
@ -159,92 +114,36 @@ namespace MediaBrowser.Providers.MediaInfo
/// Fetches data from the tags dictionary /// Fetches data from the tags dictionary
/// </summary> /// </summary>
/// <param name="audio">The audio.</param> /// <param name="audio">The audio.</param>
/// <param name="tags">The tags.</param> /// <param name="data">The data.</param>
private void FetchDataFromTags(Audio audio, Dictionary<string, string> tags) private void FetchDataFromTags(Audio audio, Model.Entities.MediaInfo data)
{ {
var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
// Only set Name if title was found in the dictionary // Only set Name if title was found in the dictionary
if (!string.IsNullOrEmpty(title)) if (!string.IsNullOrEmpty(data.Title))
{ {
audio.Name = title; audio.Name = data.Title;
} }
if (!audio.LockedFields.Contains(MetadataFields.Cast)) if (!audio.LockedFields.Contains(MetadataFields.Cast))
{ {
audio.People.Clear(); audio.People.Clear();
var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer"); foreach (var person in data.People)
if (!string.IsNullOrWhiteSpace(composer))
{ {
foreach (var person in Split(composer, false)) audio.AddPerson(new PersonInfo
{ {
audio.AddPerson(new PersonInfo { Name = person, Type = PersonType.Composer }); Name = person.Name,
} Type = person.Type,
Role = person.Role
});
} }
} }
audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album"); audio.Album = data.Album;
audio.Artists = data.Artists;
var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists"); audio.AlbumArtists = data.AlbumArtists;
audio.IndexNumber = data.IndexNumber;
if (!string.IsNullOrWhiteSpace(artists)) audio.ProductionYear = data.ProductionYear;
{ audio.PremiereDate = data.PremiereDate;
audio.Artists = artists.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
else
{
var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist");
if (string.IsNullOrWhiteSpace(artist))
{
audio.Artists.Clear();
}
else
{
audio.Artists = SplitArtists(artist)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
}
var albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist");
if (string.IsNullOrWhiteSpace(albumArtist))
{
albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist");
}
if (string.IsNullOrWhiteSpace(albumArtist))
{
albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist");
}
if (string.IsNullOrWhiteSpace(albumArtist))
{
audio.AlbumArtists = new List<string>();
}
else
{
audio.AlbumArtists = SplitArtists(albumArtist)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
// Track number
audio.IndexNumber = GetDictionaryDiscValue(tags, "track");
// Disc number
audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc");
audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
// Several different forms of retaildate
audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "date");
// If we don't have a ProductionYear try and get it from PremiereDate // If we don't have a ProductionYear try and get it from PremiereDate
if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue) if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue)
@ -254,192 +153,29 @@ namespace MediaBrowser.Providers.MediaInfo
if (!audio.LockedFields.Contains(MetadataFields.Genres)) if (!audio.LockedFields.Contains(MetadataFields.Genres))
{ {
FetchGenres(audio, tags); audio.Genres.Clear();
foreach (var genre in data.Genres)
{
audio.AddGenre(genre);
}
} }
if (!audio.LockedFields.Contains(MetadataFields.Studios)) if (!audio.LockedFields.Contains(MetadataFields.Studios))
{ {
audio.Studios.Clear(); audio.Studios.Clear();
// There's several values in tags may or may not be present foreach (var studio in data.Studios)
FetchStudios(audio, tags, "organization");
FetchStudios(audio, tags, "ensemble");
FetchStudios(audio, tags, "publisher");
}
// These support mulitple values, but for now we only store the first.
audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")));
audio.SetProviderId(MetadataProviders.MusicBrainzArtist, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")));
audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")));
audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")));
audio.SetProviderId(MetadataProviders.MusicBrainzTrack, GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")));
}
private string GetMultipleMusicBrainzId(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
return value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
.Select(i => i.Trim())
.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
}
private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
/// <summary>
/// Splits the specified val.
/// </summary>
/// <param name="val">The val.</param>
/// <param name="allowCommaDelimiter">if set to <c>true</c> [allow comma delimiter].</param>
/// <returns>System.String[][].</returns>
private IEnumerable<string> Split(string val, bool allowCommaDelimiter)
{
// Only use the comma as a delimeter if there are no slashes or pipes.
// We want to be careful not to split names that have commas in them
var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ?
_nameDelimiters :
new[] { ',' };
return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim());
}
private const string ArtistReplaceValue = " | ";
private IEnumerable<string> SplitArtists(string val)
{
val = val.Replace(" featuring ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase)
.Replace(" feat. ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase);
var artistsFound = new List<string>();
foreach (var whitelistArtist in GetSplitWhitelist())
{
var originalVal = val;
val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase);
if (!string.Equals(originalVal, val, StringComparison.OrdinalIgnoreCase))
{
artistsFound.Add(whitelistArtist);
}
}
// Only use the comma as a delimeter if there are no slashes or pipes.
// We want to be careful not to split names that have commas in them
var delimeter = _nameDelimiters;
var artists = val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim());
artistsFound.AddRange(artists);
return artistsFound;
}
private List<string> _splitWhiteList = null;
private IEnumerable<string> GetSplitWhitelist()
{
if (_splitWhiteList == null)
{
var file = GetType().Namespace + ".whitelist.txt";
using (var stream = GetType().Assembly.GetManifestResourceStream(file))
{
using (var reader = new StreamReader(stream))
{
var list = new List<string>();
while (!reader.EndOfStream)
{
var val = reader.ReadLine();
if (!string.IsNullOrWhiteSpace(val))
{
list.Add(val);
}
}
_splitWhiteList = list;
}
}
}
return _splitWhiteList;
}
/// <summary>
/// Gets the studios from the tags collection
/// </summary>
/// <param name="audio">The audio.</param>
/// <param name="tags">The tags.</param>
/// <param name="tagName">Name of the tag.</param>
private void FetchStudios(Audio audio, Dictionary<string, string> tags, string tagName)
{
var val = FFProbeHelpers.GetDictionaryValue(tags, tagName);
if (!string.IsNullOrEmpty(val))
{
// Sometimes the artist name is listed here, account for that
var studios = Split(val, true).Where(i => !audio.HasAnyArtist(i));
foreach (var studio in studios)
{ {
audio.AddStudio(studio); audio.AddStudio(studio);
} }
} }
audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, data.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist));
audio.SetProviderId(MetadataProviders.MusicBrainzArtist, data.GetProviderId(MetadataProviders.MusicBrainzArtist));
audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, data.GetProviderId(MetadataProviders.MusicBrainzAlbum));
audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, data.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup));
audio.SetProviderId(MetadataProviders.MusicBrainzTrack, data.GetProviderId(MetadataProviders.MusicBrainzTrack));
} }
/// <summary>
/// Gets the genres from the tags collection
/// </summary>
/// <param name="audio">The audio.</param>
/// <param name="tags">The tags.</param>
private void FetchGenres(Audio audio, Dictionary<string, string> tags)
{
var val = FFProbeHelpers.GetDictionaryValue(tags, "genre");
if (!string.IsNullOrEmpty(val))
{
audio.Genres.Clear();
foreach (var genre in Split(val, true))
{
audio.AddGenre(genre);
}
}
}
/// <summary>
/// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'
/// </summary>
/// <param name="tags">The tags.</param>
/// <param name="tagName">Name of the tag.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
private int? GetDictionaryDiscValue(Dictionary<string, string> tags, string tagName)
{
var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName);
if (!string.IsNullOrEmpty(disc))
{
disc = disc.Split('/')[0];
int num;
if (int.TryParse(disc, out num))
{
return num;
}
}
return null;
}
} }
} }

View File

@ -13,7 +13,6 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.Subtitles;
using MediaBrowser.MediaInfo;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -116,10 +115,6 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
FFProbeHelpers.NormalizeFFProbeResult(result);
cancellationToken.ThrowIfCancellationRequested();
await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, options).ConfigureAwait(false); await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, options).ConfigureAwait(false);
} }
@ -136,7 +131,7 @@ namespace MediaBrowser.Providers.MediaInfo
private const string SchemaVersion = "1"; private const string SchemaVersion = "1";
private async Task<InternalMediaInfoResult> GetMediaInfo(Video item, private async Task<Model.Entities.MediaInfo> GetMediaInfo(Video item,
IIsoMount isoMount, IIsoMount isoMount,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
@ -149,7 +144,7 @@ namespace MediaBrowser.Providers.MediaInfo
try try
{ {
return _json.DeserializeFromFile<InternalMediaInfoResult>(cachePath); return _json.DeserializeFromFile<Model.Entities.MediaInfo>(cachePath);
} }
catch (FileNotFoundException) catch (FileNotFoundException)
{ {
@ -165,7 +160,7 @@ namespace MediaBrowser.Providers.MediaInfo
var inputPath = MediaEncoderHelpers.GetInputArgument(item.Path, protocol, isoMount, item.PlayableStreamFileNames); var inputPath = MediaEncoderHelpers.GetInputArgument(item.Path, protocol, isoMount, item.PlayableStreamFileNames);
var result = await _mediaEncoder.GetMediaInfo(inputPath, protocol, false, cancellationToken).ConfigureAwait(false); var result = await _mediaEncoder.GetMediaInfo(inputPath, item.Path, protocol, false, true, cancellationToken).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); Directory.CreateDirectory(Path.GetDirectoryName(cachePath));
_json.SerializeToFile(result, cachePath); _json.SerializeToFile(result, cachePath);
@ -175,52 +170,37 @@ namespace MediaBrowser.Providers.MediaInfo
protected async Task Fetch(Video video, protected async Task Fetch(Video video,
CancellationToken cancellationToken, CancellationToken cancellationToken,
InternalMediaInfoResult data, Model.Entities.MediaInfo mediaInfo,
IIsoMount isoMount, IIsoMount isoMount,
BlurayDiscInfo blurayInfo, BlurayDiscInfo blurayInfo,
MetadataRefreshOptions options) MetadataRefreshOptions options)
{ {
var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data);
var mediaStreams = mediaInfo.MediaStreams; var mediaStreams = mediaInfo.MediaStreams;
video.TotalBitrate = mediaInfo.TotalBitrate; video.TotalBitrate = mediaInfo.Bitrate;
video.FormatName = (mediaInfo.Format ?? string.Empty) video.FormatName = (mediaInfo.Container ?? string.Empty)
.Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase); .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
if (data.format != null) // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
if (needToSetRuntime)
{ {
// For dvd's this may not always be accurate, so don't set the runtime if the item already has one video.RunTimeTicks = mediaInfo.RunTimeTicks;
var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
{
video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
}
if (video.VideoType == VideoType.VideoFile)
{
var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');
video.Container = extension;
}
else
{
video.Container = null;
}
if (!string.IsNullOrEmpty(data.format.size))
{
video.Size = long.Parse(data.format.size, _usCulture);
}
else
{
video.Size = null;
}
} }
var mediaChapters = (data.Chapters ?? new MediaChapter[] { }).ToList(); if (video.VideoType == VideoType.VideoFile)
var chapters = mediaChapters.Select(GetChapterInfo).ToList(); {
var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');
video.Container = extension;
}
else
{
video.Container = null;
}
var chapters = mediaInfo.Chapters ?? new List<ChapterInfo>();
if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay)) if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
{ {
FetchBdInfo(video, chapters, mediaStreams, blurayInfo); FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
@ -228,7 +208,7 @@ namespace MediaBrowser.Providers.MediaInfo
await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
FetchWtvInfo(video, data); FetchEmbeddedInfo(video, mediaInfo);
video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270); video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);
@ -238,9 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo
video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index; video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle); video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
video.Timestamp = mediaInfo.Timestamp;
ExtractTimestamp(video);
UpdateFromMediaInfo(video, videoStream);
await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false); await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);
@ -283,29 +261,6 @@ namespace MediaBrowser.Providers.MediaInfo
} }
} }
private void UpdateFromMediaInfo(Video video, MediaStream videoStream)
{
if (video.VideoType == VideoType.VideoFile && video.LocationType != LocationType.Remote && video.LocationType != LocationType.Virtual)
{
if (videoStream != null)
{
try
{
var result = new MediaInfoLib().GetVideoInfo(video.Path);
videoStream.IsCabac = result.IsCabac ?? videoStream.IsCabac;
videoStream.IsInterlaced = result.IsInterlaced ?? videoStream.IsInterlaced;
videoStream.BitDepth = result.BitDepth ?? videoStream.BitDepth;
videoStream.RefFrames = result.RefFrames;
}
catch (Exception ex)
{
_logger.ErrorException("Error running MediaInfo on {0}", ex, video.Path);
}
}
}
}
private void NormalizeChapterNames(List<ChapterInfo> chapters) private void NormalizeChapterNames(List<ChapterInfo> chapters)
{ {
var index = 1; var index = 1;
@ -325,32 +280,6 @@ namespace MediaBrowser.Providers.MediaInfo
} }
} }
private ChapterInfo GetChapterInfo(MediaChapter chapter)
{
var info = new ChapterInfo();
if (chapter.tags != null)
{
string name;
if (chapter.tags.TryGetValue("title", out name))
{
info.Name = name;
}
}
// Limit accuracy to milliseconds to match xml saving
var secondsString = chapter.start_time;
double seconds;
if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out seconds))
{
var ms = Math.Round(TimeSpan.FromSeconds(seconds).TotalMilliseconds);
info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks;
}
return info;
}
private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo) private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo)
{ {
var video = (Video)item; var video = (Video)item;
@ -419,129 +348,79 @@ namespace MediaBrowser.Providers.MediaInfo
return _blurayExaminer.GetDiscInfo(path); return _blurayExaminer.GetDiscInfo(path);
} }
public const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames) private void FetchEmbeddedInfo(Video video, Model.Entities.MediaInfo data)
private void FetchWtvInfo(Video video, InternalMediaInfoResult data)
{ {
if (data.format == null || data.format.tags == null)
{
return;
}
if (!video.LockedFields.Contains(MetadataFields.Genres))
{
var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre");
if (!string.IsNullOrWhiteSpace(genres))
{
//genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");
}
if (!string.IsNullOrWhiteSpace(genres))
{
video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim())
.ToList();
}
}
if (!video.LockedFields.Contains(MetadataFields.OfficialRating)) if (!video.LockedFields.Contains(MetadataFields.OfficialRating))
{ {
var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating"); if (!string.IsNullOrWhiteSpace(data.OfficialRating))
if (!string.IsNullOrWhiteSpace(officialRating))
{ {
video.OfficialRating = officialRating; video.OfficialRating = data.OfficialRating;
} }
} }
if (!video.LockedFields.Contains(MetadataFields.Cast)) if (!video.LockedFields.Contains(MetadataFields.Cast))
{ {
var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits"); video.People.Clear();
if (!string.IsNullOrEmpty(people)) foreach (var person in data.People)
{ {
video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries) video.AddPerson(new PersonInfo
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => new PersonInfo { Name = i.Trim(), Type = PersonType.Actor })
.ToList();
}
}
var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");
if (!string.IsNullOrWhiteSpace(year))
{
int val;
if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val))
{
video.ProductionYear = val;
}
}
var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime");
if (!string.IsNullOrWhiteSpace(premiereDateString))
{
DateTime val;
// Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
// DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None)
if (DateTime.TryParse(year, null, DateTimeStyles.None, out val))
{
video.PremiereDate = val.ToUniversalTime();
}
}
var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");
var episode = video as Episode;
if (episode != null)
{
var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle");
// For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
// Sometimes for TV Shows the Subtitle field is empty and the subtitle description contains the subtitle, extract if possible. See ticket https://mcebuddy2x.codeplex.com/workitem/1910
// The format is -> EPISODE/TOTAL_EPISODES_IN_SEASON. SUBTITLE: DESCRIPTION
// OR -> COMMENT. SUBTITLE: DESCRIPTION
// e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S]
// e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S]
if (String.IsNullOrWhiteSpace(subTitle) && !String.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename
{
string[] parts = description.Split(':');
if (parts.Length > 0)
{ {
string subtitle = parts[0]; Name = person.Name,
try Type = person.Type,
{ Role = person.Role
if (subtitle.Contains("/")) // It contains a episode number and season number });
{
string[] numbers = subtitle.Split(' ');
episode.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]);
int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]);
description = String.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
}
else
throw new Exception(); // Switch to default parsing
}
catch // Default parsing
{
if (subtitle.Contains(".")) // skip the comment, keep the subtitle
description = String.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first
else
description = subtitle.Trim(); // Clean up whitespaces and save it
}
}
} }
} }
if (!video.LockedFields.Contains(MetadataFields.Genres))
{
video.Genres.Clear();
foreach (var genre in data.Genres)
{
video.AddGenre(genre);
}
}
if (!video.LockedFields.Contains(MetadataFields.Studios))
{
video.Studios.Clear();
foreach (var studio in data.Studios)
{
video.AddStudio(studio);
}
}
if (data.ProductionYear.HasValue)
{
video.ProductionYear = data.ProductionYear;
}
if (data.PremiereDate.HasValue)
{
video.PremiereDate = data.PremiereDate;
}
if (data.IndexNumber.HasValue)
{
video.IndexNumber = data.IndexNumber;
}
if (data.ParentIndexNumber.HasValue)
{
video.ParentIndexNumber = data.ParentIndexNumber;
}
// If we don't have a ProductionYear try and get it from PremiereDate
if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)
{
video.ProductionYear = video.PremiereDate.Value.ToLocalTime().Year;
}
if (!video.LockedFields.Contains(MetadataFields.Overview)) if (!video.LockedFields.Contains(MetadataFields.Overview))
{ {
if (!string.IsNullOrWhiteSpace(description)) if (!string.IsNullOrWhiteSpace(data.Overview))
{ {
video.Overview = description; video.Overview = data.Overview;
} }
} }
} }
@ -709,56 +588,6 @@ namespace MediaBrowser.Providers.MediaInfo
} }
} }
private void ExtractTimestamp(Video video)
{
if (video.VideoType == VideoType.VideoFile)
{
if (string.Equals(video.Container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) ||
string.Equals(video.Container, "m2ts", StringComparison.OrdinalIgnoreCase) ||
string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase))
{
try
{
video.Timestamp = GetMpegTimestamp(video.Path);
_logger.Debug("Video has {0} timestamp", video.Timestamp);
}
catch (Exception ex)
{
_logger.ErrorException("Error extracting timestamp info from {0}", ex, video.Path);
video.Timestamp = null;
}
}
}
}
private TransportStreamTimestamp GetMpegTimestamp(string path)
{
var packetBuffer = new byte['Å'];
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
fs.Read(packetBuffer, 0, packetBuffer.Length);
}
if (packetBuffer[0] == 71)
{
return TransportStreamTimestamp.None;
}
if ((packetBuffer[4] == 71) && (packetBuffer['Ä'] == 71))
{
if ((packetBuffer[0] == 0) && (packetBuffer[1] == 0) && (packetBuffer[2] == 0) && (packetBuffer[3] == 0))
{
return TransportStreamTimestamp.Zero;
}
return TransportStreamTimestamp.Valid;
}
return TransportStreamTimestamp.None;
}
private void FetchFromDvdLib(Video item, IIsoMount mount) private void FetchFromDvdLib(Video item, IIsoMount mount)
{ {
var path = mount == null ? item.Path : mount.MountedPath; var path = mount == null ? item.Path : mount.MountedPath;

View File

@ -1,6 +1,7 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
@ -18,12 +19,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager, IMediaSourceManager mediaSourceManager) public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
{ {
_liveTvManager = liveTvManager; _liveTvManager = liveTvManager;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_mediaEncoder = mediaEncoder;
_logger = logManager.GetLogger(GetType().Name); _logger = logManager.GetLogger(GetType().Name);
} }
@ -90,14 +93,86 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken) public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{ {
MediaSourceInfo stream;
var isAudio = false;
var keys = openToken.Split(new[] { '|' }, 2); var keys = openToken.Split(new[] { '|' }, 2);
if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase)) if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
{ {
return await _liveTvManager.GetChannelStream(keys[1], cancellationToken).ConfigureAwait(false); stream = await _liveTvManager.GetChannelStream(keys[1], cancellationToken).ConfigureAwait(false);
}
else
{
stream = await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false);
} }
return await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false); try
{
await AddMediaInfo(stream, isAudio, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error probing live tv stream", ex);
}
return stream;
}
private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
{
var inputPaths = new[] { mediaSource.Path };
var info = await _mediaEncoder.GetMediaInfo(inputPaths, mediaSource.Path, mediaSource.Protocol, isAudio, false, cancellationToken)
.ConfigureAwait(false);
mediaSource.Bitrate = info.Bitrate;
mediaSource.Container = info.Container;
mediaSource.Formats = info.Formats;
mediaSource.MediaStreams = info.MediaStreams;
mediaSource.RunTimeTicks = info.RunTimeTicks;
mediaSource.Size = info.Size;
mediaSource.Timestamp = info.Timestamp;
mediaSource.Video3DFormat = info.Video3DFormat;
mediaSource.VideoType = info.VideoType;
mediaSource.DefaultSubtitleStreamIndex = null;
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Audio);
if (audioStream == null || audioStream.Index == -1)
{
mediaSource.DefaultAudioStreamIndex = null;
}
else
{
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
}
// Try to estimate this
if (!mediaSource.Bitrate.HasValue)
{
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == Model.Entities.MediaStreamType.Video);
if (videoStream != null)
{
var width = videoStream.Width ?? 1920;
if (width >= 1900)
{
mediaSource.Bitrate = 10000000;
}
else if (width >= 1260)
{
mediaSource.Bitrate = 6000000;
}
else if (width >= 700)
{
mediaSource.Bitrate = 4000000;
}
}
}
} }
public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)

View File

@ -493,7 +493,7 @@ namespace MediaBrowser.Server.Implementations.Sync
conversionOptions.ItemId = item.Id.ToString("N"); conversionOptions.ItemId = item.Id.ToString("N");
conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList(); conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList();
var streamInfo = new StreamBuilder().BuildVideoItem(conversionOptions); var streamInfo = new StreamBuilder(_logger).BuildVideoItem(conversionOptions);
var mediaSource = streamInfo.MediaSource; var mediaSource = streamInfo.MediaSource;
// No sense creating external subs if we're already burning one into the video // No sense creating external subs if we're already burning one into the video
@ -690,7 +690,7 @@ namespace MediaBrowser.Server.Implementations.Sync
conversionOptions.ItemId = item.Id.ToString("N"); conversionOptions.ItemId = item.Id.ToString("N");
conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList(); conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList();
var streamInfo = new StreamBuilder().BuildAudioItem(conversionOptions); var streamInfo = new StreamBuilder(_logger).BuildAudioItem(conversionOptions);
var mediaSource = streamInfo.MediaSource; var mediaSource = streamInfo.MediaSource;
jobItem.MediaSourceId = streamInfo.MediaSourceId; jobItem.MediaSourceId = streamInfo.MediaSourceId;

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Common.Internal</id> <id>MediaBrowser.Common.Internal</id>
<version>3.0.613</version> <version>3.0.615</version>
<title>MediaBrowser.Common.Internal</title> <title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors> <authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners> <owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description> <description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Emby 2013</copyright> <copyright>Copyright © Emby 2013</copyright>
<dependencies> <dependencies>
<dependency id="MediaBrowser.Common" version="3.0.613" /> <dependency id="MediaBrowser.Common" version="3.0.615" />
<dependency id="NLog" version="3.2.0.0" /> <dependency id="NLog" version="3.2.0.0" />
<dependency id="SimpleInjector" version="2.7.0" /> <dependency id="SimpleInjector" version="2.7.0" />
</dependencies> </dependencies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Common</id> <id>MediaBrowser.Common</id>
<version>3.0.613</version> <version>3.0.615</version>
<title>MediaBrowser.Common</title> <title>MediaBrowser.Common</title>
<authors>Emby Team</authors> <authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners> <owners>ebr,Luke,scottisafool</owners>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Model.Signed</id> <id>MediaBrowser.Model.Signed</id>
<version>3.0.613</version> <version>3.0.615</version>
<title>MediaBrowser.Model - Signed Edition</title> <title>MediaBrowser.Model - Signed Edition</title>
<authors>Emby Team</authors> <authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners> <owners>ebr,Luke,scottisafool</owners>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Server.Core</id> <id>MediaBrowser.Server.Core</id>
<version>3.0.613</version> <version>3.0.615</version>
<title>Media Browser.Server.Core</title> <title>Media Browser.Server.Core</title>
<authors>Emby Team</authors> <authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners> <owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Emby Server.</description> <description>Contains core components required to build plugins for Emby Server.</description>
<copyright>Copyright © Emby 2013</copyright> <copyright>Copyright © Emby 2013</copyright>
<dependencies> <dependencies>
<dependency id="MediaBrowser.Common" version="3.0.613" /> <dependency id="MediaBrowser.Common" version="3.0.615" />
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>

View File

@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
//[assembly: AssemblyVersion("3.0.*")] [assembly: AssemblyVersion("3.0.*")]
[assembly: AssemblyVersion("3.0.5572.0")] //[assembly: AssemblyVersion("3.0.5572.0")]