diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index 08c5d56db..abd0278c2 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -223,7 +223,7 @@ namespace MediaBrowser.Api.Playback
int? subtitleStreamIndex,
string playSessionId)
{
- var streamBuilder = new StreamBuilder();
+ var streamBuilder = new StreamBuilder(Logger);
var options = new VideoOptions
{
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 2fa62f79f..9a4a2cb62 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -211,7 +211,6 @@
-
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 47544f972..bb5674864 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -63,16 +63,18 @@ namespace MediaBrowser.Controller.MediaEncoding
string filenamePrefix,
int? maxWidth,
CancellationToken cancellationToken);
-
+
///
/// Gets the media info.
///
/// The input files.
+ /// The primary path.
/// The protocol.
/// if set to true [is audio].
+ /// if set to true [extract chapters].
/// The cancellation token.
/// Task.
- Task GetMediaInfo(string[] inputFiles, MediaProtocol protocol, bool isAudio, CancellationToken cancellationToken);
+ Task GetMediaInfo(string[] inputFiles, string primaryPath, MediaProtocol protocol, bool isAudio, bool extractChapters, CancellationToken cancellationToken);
///
/// Gets the probe size argument.
diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
index 530c127da..da9dd4dfd 100644
--- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
+++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
@@ -1,9 +1,7 @@
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.IO;
using System.Linq;
@@ -46,291 +44,5 @@ namespace MediaBrowser.Controller.MediaEncoding
.Where(f => !string.IsNullOrEmpty(f))
.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");
-
- ///
- /// Converts ffprobe stream info to our MediaStream class
- ///
- /// The stream info.
- /// The format info.
- /// MediaStream.
- 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
- {
- "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;
- }
-
- ///
- /// Gets a string from an FFProbeResult tags dictionary
- ///
- /// The tags.
- /// The key.
- /// System.String.
- private static string GetDictionaryValue(Dictionary 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;
- }
-
- ///
- /// Gets a frame rate from a string value in ffprobe output
- /// This could be a number or in the format of 2997/125.
- ///
- /// The value.
- /// System.Nullable{System.Single}.
- 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;
- }
-
}
}
diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs
index 19dab2246..629b95f67 100644
--- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs
+++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs
@@ -12,6 +12,7 @@ using MediaBrowser.Dlna.ContentDirectory;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using System;
using System.Globalization;
@@ -126,7 +127,7 @@ namespace MediaBrowser.Dlna.Didl
{
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),
MediaSources = sources,
@@ -353,7 +354,7 @@ namespace MediaBrowser.Dlna.Didl
{
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),
MediaSources = sources,
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
index 5b129243c..d88adc8c6 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
@@ -542,7 +542,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
return new PlaylistItem
{
- StreamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
+ StreamInfo = new StreamBuilder(_logger).BuildVideoItem(new VideoOptions
{
ItemId = item.Id.ToString("N"),
MediaSources = mediaSources,
@@ -562,7 +562,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
return new PlaylistItem
{
- StreamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
+ StreamInfo = new StreamBuilder(_logger).BuildAudioItem(new AudioOptions
{
ItemId = item.Id.ToString("N"),
MediaSources = mediaSources,
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 425889807..18d9ccece 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Session;
+using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
@@ -103,15 +104,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// Gets the media info.
///
/// The input files.
+ /// The primary path.
/// The protocol.
/// if set to true [is audio].
+ /// if set to true [extract chapters].
/// The cancellation token.
/// Task.
- public Task GetMediaInfo(string[] inputFiles, MediaProtocol protocol, bool isAudio,
- CancellationToken cancellationToken)
+ public Task GetMediaInfo(string[] inputFiles, string primaryPath, MediaProtocol protocol, bool isAudio,
+ bool extractChapters, CancellationToken cancellationToken)
{
- return GetMediaInfoInternal(GetInputArgument(inputFiles, protocol), !isAudio,
- GetProbeSizeArgument(inputFiles, protocol), cancellationToken);
+ return GetMediaInfoInternal(GetInputArgument(inputFiles, protocol), primaryPath, protocol, !isAudio && extractChapters,
+ GetProbeSizeArgument(inputFiles, protocol), isAudio, cancellationToken);
}
///
@@ -141,13 +144,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// Gets the media info internal.
///
/// The input path.
+ /// The primary path.
+ /// The protocol.
/// if set to true [extract chapters].
/// The probe size argument.
+ /// if set to true [is audio].
/// The cancellation token.
/// Task{MediaInfoResult}.
///
- private async Task GetMediaInfoInternal(string inputPath, bool extractChapters,
+ private async Task GetMediaInfoInternal(string inputPath, string primaryPath, MediaProtocol protocol, bool extractChapters,
string probeSizeArgument,
+ bool isAudio,
CancellationToken cancellationToken)
{
var args = extractChapters
@@ -244,7 +251,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- return result;
+ return new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, isAudio, primaryPath, protocol);
}
///
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 72dc0feac..c3038cadb 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -68,6 +68,9 @@
+
+
+
@@ -91,6 +94,10 @@
{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}
MediaBrowser.Controller
+
+ {6e4145e4-c6d4-4e4d-94f2-87188db6e239}
+ MediaBrowser.MediaInfo
+
{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}
MediaBrowser.Model
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
similarity index 98%
rename from MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs
rename to MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
index 2044979e4..859f38950 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeHelpers.cs
+++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
@@ -2,7 +2,7 @@
using System;
using System.Collections.Generic;
-namespace MediaBrowser.Providers.MediaInfo
+namespace MediaBrowser.MediaEncoding.Probing
{
public static class FFProbeHelpers
{
diff --git a/MediaBrowser.Controller/MediaEncoding/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
similarity index 99%
rename from MediaBrowser.Controller/MediaEncoding/InternalMediaInfoResult.cs
rename to MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
index 796fdb723..9b95e83e7 100644
--- a/MediaBrowser.Controller/MediaEncoding/InternalMediaInfoResult.cs
+++ b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-namespace MediaBrowser.Controller.MediaEncoding
+namespace MediaBrowser.MediaEncoding.Probing
{
///
/// Class MediaInfoResult
@@ -89,7 +89,7 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// The channel_layout.
public string channel_layout { get; set; }
-
+
///
/// Gets or sets the avg_frame_rate.
///
@@ -317,7 +317,7 @@ namespace MediaBrowser.Controller.MediaEncoding
///
/// The probe_score.
public int probe_score { get; set; }
-
+
///
/// Gets or sets the tags.
///
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
new file mode 100644
index 000000000..48e8b6ee2
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -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;
+ }
+
+ ///
+ /// Converts ffprobe stream info to our MediaStream class
+ ///
+ /// The stream info.
+ /// The format info.
+ /// MediaStream.
+ 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
+ {
+ "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;
+ }
+
+ ///
+ /// Gets a string from an FFProbeResult tags dictionary
+ ///
+ /// The tags.
+ /// The key.
+ /// System.String.
+ private string GetDictionaryValue(Dictionary 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;
+ }
+
+ ///
+ /// Gets a frame rate from a string value in ffprobe output
+ /// This could be a number or in the format of 2997/125.
+ ///
+ /// The value.
+ /// System.Nullable{System.Single}.
+ 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 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();
+ }
+ 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 = { '/', '|', ';', '\\' };
+
+ ///
+ /// Splits the specified val.
+ ///
+ /// The val.
+ /// if set to true [allow comma delimiter].
+ /// System.String[][].
+ private IEnumerable 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 SplitArtists(string val)
+ {
+ val = val.Replace(" featuring ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase)
+ .Replace(" feat. ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase);
+
+ var artistsFound = new List();
+
+ 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 _splitWhiteList = null;
+
+ private IEnumerable 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();
+
+ while (!reader.EndOfStream)
+ {
+ var val = reader.ReadLine();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ list.Add(val);
+ }
+ }
+
+ _splitWhiteList = list;
+ }
+ }
+ }
+
+ return _splitWhiteList;
+ }
+
+ ///
+ /// Gets the studios from the tags collection
+ ///
+ /// The audio.
+ /// The tags.
+ /// Name of the tag.
+ private void FetchStudios(Model.Entities.MediaInfo audio, Dictionary 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();
+ }
+ }
+
+ ///
+ /// Gets the genres from the tags collection
+ ///
+ /// The information.
+ /// The tags.
+ private void FetchGenres(Model.Entities.MediaInfo info, Dictionary 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();
+ }
+ }
+
+ ///
+ /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'
+ ///
+ /// The tags.
+ /// Name of the tag.
+ /// System.Nullable{System.Int32}.
+ private int? GetDictionaryDiscValue(Dictionary 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);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index bc9f07d04..daeb50225 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
using System;
@@ -11,13 +12,16 @@ namespace MediaBrowser.Model.Dlna
public class StreamBuilder
{
private readonly ILocalPlayer _localPlayer;
+ private readonly ILogger _logger;
- public StreamBuilder(ILocalPlayer localPlayer)
+ public StreamBuilder(ILocalPlayer localPlayer, ILogger logger)
{
_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 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)
{
// See if it can be direct played
@@ -504,6 +514,10 @@ namespace MediaBrowser.Model.Dlna
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;
}
@@ -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))
{
+ _logger.Debug("Profile: {0}, DirectPlay=false. Reason=VideoContainerProfile.{1} Path: {2}",
+ profile.Name ?? "Unknown Profile",
+ i.Property,
+ mediaSource.Path ?? "Unknown path");
+
return null;
}
}
@@ -558,6 +577,10 @@ namespace MediaBrowser.Model.Dlna
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;
}
@@ -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))
{
+ _logger.Debug("Profile: {0}, DirectPlay=false. Reason=VideoCodecProfile.{1} Path: {2}",
+ profile.Name ?? "Unknown Profile",
+ i.Property,
+ mediaSource.Path ?? "Unknown path");
+
return null;
}
}
@@ -587,6 +615,10 @@ namespace MediaBrowser.Model.Dlna
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;
}
@@ -607,6 +639,11 @@ namespace MediaBrowser.Model.Dlna
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
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;
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index a908c7850..f6ff79b11 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -70,6 +70,7 @@ namespace MediaBrowser.Model.Dlna
public string SubtitleFormat { get; set; }
public string PlaySessionId { get; set; }
+ public List AllMediaSources { get; set; }
public string MediaSourceId
{
diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs
index 46485316e..8e7750562 100644
--- a/MediaBrowser.Model/Dto/BaseItemPerson.cs
+++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs
@@ -1,7 +1,7 @@
-using System.ComponentModel;
+using MediaBrowser.Model.Extensions;
+using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.Serialization;
-using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Dto
{
diff --git a/MediaBrowser.Model/Entities/MediaInfo.cs b/MediaBrowser.Model/Entities/MediaInfo.cs
index ef26cfa14..67efe3108 100644
--- a/MediaBrowser.Model/Entities/MediaInfo.cs
+++ b/MediaBrowser.Model/Entities/MediaInfo.cs
@@ -1,26 +1,65 @@
+using MediaBrowser.Model.Dto;
+using System;
using System.Collections.Generic;
namespace MediaBrowser.Model.Entities
{
- public class MediaInfo
+ public class MediaInfo : MediaSourceInfo, IHasProviderIds
{
- ///
- /// Gets or sets the media streams.
- ///
- /// The media streams.
- public List MediaStreams { get; set; }
+ public List Chapters { get; set; }
///
- /// Gets or sets the format.
+ /// Gets or sets the title.
///
- /// The format.
- public string Format { get; set; }
-
- public int? TotalBitrate { get; set; }
+ /// The title.
+ public string Title { get; set; }
+ ///
+ /// Gets or sets the album.
+ ///
+ /// The album.
+ public string Album { get; set; }
+ ///
+ /// Gets or sets the artists.
+ ///
+ /// The artists.
+ public List Artists { get; set; }
+ ///
+ /// Gets or sets the album artists.
+ ///
+ /// The album artists.
+ public List AlbumArtists { get; set; }
+ ///
+ /// Gets or sets the studios.
+ ///
+ /// The studios.
+ public List Studios { get; set; }
+ public List 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 People { get; set; }
+ public Dictionary ProviderIds { get; set; }
+ ///
+ /// Gets or sets the official rating.
+ ///
+ /// The official rating.
+ public string OfficialRating { get; set; }
+ ///
+ /// Gets or sets the overview.
+ ///
+ /// The overview.
+ public string Overview { get; set; }
public MediaInfo()
{
- MediaStreams = new List();
+ Chapters = new List();
+ Artists = new List();
+ AlbumArtists = new List();
+ Studios = new List();
+ Genres = new List();
+ People = new List();
+ ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase);
}
}
}
\ No newline at end of file
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 3b5103f20..b58c6648a 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -102,7 +102,6 @@
-
@@ -198,10 +197,6 @@
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}
MediaBrowser.Controller
-
- {6e4145e4-c6d4-4e4d-94f2-87188db6e239}
- MediaBrowser.MediaInfo
-
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}
MediaBrowser.Model
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
index ea191dd08..b1bed7310 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -8,8 +7,6 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -49,18 +46,14 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested();
- FFProbeHelpers.NormalizeFFProbeResult(result);
-
- cancellationToken.ThrowIfCancellationRequested();
-
await Fetch(item, cancellationToken, result).ConfigureAwait(false);
return ItemUpdateType.MetadataImport;
}
- private const string SchemaVersion = "1";
+ private const string SchemaVersion = "2";
- private async Task GetMediaInfo(BaseItem item, CancellationToken cancellationToken)
+ private async Task GetMediaInfo(BaseItem item, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -71,7 +64,7 @@ namespace MediaBrowser.Providers.MediaInfo
try
{
- return _json.DeserializeFromFile(cachePath);
+ return _json.DeserializeFromFile(cachePath);
}
catch (FileNotFoundException)
{
@@ -83,7 +76,7 @@ namespace MediaBrowser.Providers.MediaInfo
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));
_json.SerializeToFile(result, cachePath);
@@ -96,61 +89,23 @@ namespace MediaBrowser.Providers.MediaInfo
///
/// The audio.
/// The cancellation token.
- /// The data.
+ /// The media information.
/// Task.
- 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;
- audio.FormatName = mediaInfo.Format;
- audio.TotalBitrate = mediaInfo.TotalBitrate;
+ audio.FormatName = mediaInfo.Container;
+ audio.TotalBitrate = mediaInfo.Bitrate;
audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.EmbeddedImage);
- if (data.streams != null)
- {
- // Get the first audio stream
- var stream = data.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase));
+ audio.RunTimeTicks = mediaInfo.RunTimeTicks;
+ audio.Size = mediaInfo.Size;
- if (stream != null)
- {
- // Get duration from stream properties
- var duration = stream.duration;
+ var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
+ audio.Container = extension;
- // If it's not there go into format properties
- 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);
- }
- }
+ FetchDataFromTags(audio, mediaInfo);
return _itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken);
}
@@ -159,92 +114,36 @@ namespace MediaBrowser.Providers.MediaInfo
/// Fetches data from the tags dictionary
///
/// The audio.
- /// The tags.
- private void FetchDataFromTags(Audio audio, Dictionary tags)
+ /// The data.
+ 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
- if (!string.IsNullOrEmpty(title))
+ if (!string.IsNullOrEmpty(data.Title))
{
- audio.Name = title;
+ audio.Name = data.Title;
}
if (!audio.LockedFields.Contains(MetadataFields.Cast))
{
audio.People.Clear();
- var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
-
- if (!string.IsNullOrWhiteSpace(composer))
+ foreach (var person in data.People)
{
- 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");
-
- 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();
- }
- 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");
+ audio.Album = data.Album;
+ audio.Artists = data.Artists;
+ audio.AlbumArtists = data.AlbumArtists;
+ audio.IndexNumber = data.IndexNumber;
+ audio.ProductionYear = data.ProductionYear;
+ audio.PremiereDate = data.PremiereDate;
// If we don't have a ProductionYear try and get it from PremiereDate
if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue)
@@ -254,192 +153,29 @@ namespace MediaBrowser.Providers.MediaInfo
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))
{
audio.Studios.Clear();
- // 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 = { '/', '|', ';', '\\' };
-
- ///
- /// Splits the specified val.
- ///
- /// The val.
- /// if set to true [allow comma delimiter].
- /// System.String[][].
- private IEnumerable 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 SplitArtists(string val)
- {
- val = val.Replace(" featuring ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase)
- .Replace(" feat. ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase);
-
- var artistsFound = new List();
-
- 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 _splitWhiteList = null;
-
- private IEnumerable 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();
-
- while (!reader.EndOfStream)
- {
- var val = reader.ReadLine();
-
- if (!string.IsNullOrWhiteSpace(val))
- {
- list.Add(val);
- }
- }
-
- _splitWhiteList = list;
- }
- }
- }
-
- return _splitWhiteList;
- }
-
- ///
- /// Gets the studios from the tags collection
- ///
- /// The audio.
- /// The tags.
- /// Name of the tag.
- private void FetchStudios(Audio audio, Dictionary 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)
+ foreach (var studio in data.Studios)
{
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));
}
-
- ///
- /// Gets the genres from the tags collection
- ///
- /// The audio.
- /// The tags.
- private void FetchGenres(Audio audio, Dictionary tags)
- {
- var val = FFProbeHelpers.GetDictionaryValue(tags, "genre");
-
- if (!string.IsNullOrEmpty(val))
- {
- audio.Genres.Clear();
-
- foreach (var genre in Split(val, true))
- {
- audio.AddGenre(genre);
- }
- }
- }
-
- ///
- /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'
- ///
- /// The tags.
- /// Name of the tag.
- /// System.Nullable{System.Int32}.
- private int? GetDictionaryDiscValue(Dictionary 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;
- }
-
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index ca11f858a..cec66f3c1 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -13,7 +13,6 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles;
-using MediaBrowser.MediaInfo;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
@@ -116,10 +115,6 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested();
- FFProbeHelpers.NormalizeFFProbeResult(result);
-
- cancellationToken.ThrowIfCancellationRequested();
-
await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, options).ConfigureAwait(false);
}
@@ -136,7 +131,7 @@ namespace MediaBrowser.Providers.MediaInfo
private const string SchemaVersion = "1";
- private async Task GetMediaInfo(Video item,
+ private async Task GetMediaInfo(Video item,
IIsoMount isoMount,
CancellationToken cancellationToken)
{
@@ -149,7 +144,7 @@ namespace MediaBrowser.Providers.MediaInfo
try
{
- return _json.DeserializeFromFile(cachePath);
+ return _json.DeserializeFromFile(cachePath);
}
catch (FileNotFoundException)
{
@@ -165,7 +160,7 @@ namespace MediaBrowser.Providers.MediaInfo
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));
_json.SerializeToFile(result, cachePath);
@@ -175,52 +170,37 @@ namespace MediaBrowser.Providers.MediaInfo
protected async Task Fetch(Video video,
CancellationToken cancellationToken,
- InternalMediaInfoResult data,
+ Model.Entities.MediaInfo mediaInfo,
IIsoMount isoMount,
BlurayDiscInfo blurayInfo,
MetadataRefreshOptions options)
{
- var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data);
var mediaStreams = mediaInfo.MediaStreams;
- video.TotalBitrate = mediaInfo.TotalBitrate;
- video.FormatName = (mediaInfo.Format ?? string.Empty)
+ video.TotalBitrate = mediaInfo.Bitrate;
+ video.FormatName = (mediaInfo.Container ?? string.Empty)
.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
- 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;
- }
+ video.RunTimeTicks = mediaInfo.RunTimeTicks;
}
- var mediaChapters = (data.Chapters ?? new MediaChapter[] { }).ToList();
- var chapters = mediaChapters.Select(GetChapterInfo).ToList();
+ if (video.VideoType == VideoType.VideoFile)
+ {
+ var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');
+ video.Container = extension;
+ }
+ else
+ {
+ video.Container = null;
+ }
+
+ var chapters = mediaInfo.Chapters ?? new List();
if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
{
FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
@@ -228,7 +208,7 @@ namespace MediaBrowser.Providers.MediaInfo
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);
@@ -238,9 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo
video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
-
- ExtractTimestamp(video);
- UpdateFromMediaInfo(video, videoStream);
+ video.Timestamp = mediaInfo.Timestamp;
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 chapters)
{
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 chapters, List mediaStreams, BlurayDiscInfo blurayInfo)
{
var video = (Video)item;
@@ -419,129 +348,79 @@ namespace MediaBrowser.Providers.MediaInfo
return _blurayExaminer.GetDiscInfo(path);
}
- public const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
-
- private void FetchWtvInfo(Video video, InternalMediaInfoResult data)
+ private void FetchEmbeddedInfo(Video video, Model.Entities.MediaInfo 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))
{
- var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");
-
- if (!string.IsNullOrWhiteSpace(officialRating))
+ if (!string.IsNullOrWhiteSpace(data.OfficialRating))
{
- video.OfficialRating = officialRating;
+ video.OfficialRating = data.OfficialRating;
}
}
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)
- .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)
+ video.AddPerson(new PersonInfo
{
- string subtitle = parts[0];
- try
- {
- 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
- }
- }
+ Name = person.Name,
+ Type = person.Type,
+ Role = person.Role
+ });
}
}
+ 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 (!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)
{
var path = mount == null ? item.Path : mount.MountedPath;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index d549cad46..18cc172cc 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
@@ -18,12 +19,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger _logger;
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;
_jsonSerializer = jsonSerializer;
_mediaSourceManager = mediaSourceManager;
+ _mediaEncoder = mediaEncoder;
_logger = logManager.GetLogger(GetType().Name);
}
@@ -90,14 +93,86 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
+ MediaSourceInfo stream;
+ var isAudio = false;
+
var keys = openToken.Split(new[] { '|' }, 2);
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)
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
index 185c3464e..d8fc425d1 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
@@ -493,7 +493,7 @@ namespace MediaBrowser.Server.Implementations.Sync
conversionOptions.ItemId = item.Id.ToString("N");
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;
// 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.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList();
- var streamInfo = new StreamBuilder().BuildAudioItem(conversionOptions);
+ var streamInfo = new StreamBuilder(_logger).BuildAudioItem(conversionOptions);
var mediaSource = streamInfo.MediaSource;
jobItem.MediaSourceId = streamInfo.MediaSourceId;
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index 9ecfe2b20..bfc95a296 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/Nuget/MediaBrowser.Common.Internal.nuspec
@@ -2,7 +2,7 @@
MediaBrowser.Common.Internal
- 3.0.613
+ 3.0.615
MediaBrowser.Common.Internal
Luke
ebr,Luke,scottisafool
@@ -12,7 +12,7 @@
Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.
Copyright © Emby 2013
-
+
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index 18078aef3..77d5242a9 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
MediaBrowser.Common
- 3.0.613
+ 3.0.615
MediaBrowser.Common
Emby Team
ebr,Luke,scottisafool
diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec
index f84c44729..12b5eb442 100644
--- a/Nuget/MediaBrowser.Model.Signed.nuspec
+++ b/Nuget/MediaBrowser.Model.Signed.nuspec
@@ -2,7 +2,7 @@
MediaBrowser.Model.Signed
- 3.0.613
+ 3.0.615
MediaBrowser.Model - Signed Edition
Emby Team
ebr,Luke,scottisafool
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index 1d28c22a8..cbf4e9579 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
MediaBrowser.Server.Core
- 3.0.613
+ 3.0.615
Media Browser.Server.Core
Emby Team
ebr,Luke,scottisafool
@@ -12,7 +12,7 @@
Contains core components required to build plugins for Emby Server.
Copyright © Emby 2013
-
+
diff --git a/SharedVersion.cs b/SharedVersion.cs
index 8177f8cca..405e1c708 100644
--- a/SharedVersion.cs
+++ b/SharedVersion.cs
@@ -1,4 +1,4 @@
using System.Reflection;
-//[assembly: AssemblyVersion("3.0.*")]
-[assembly: AssemblyVersion("3.0.5572.0")]
+[assembly: AssemblyVersion("3.0.*")]
+//[assembly: AssemblyVersion("3.0.5572.0")]