Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
This commit is contained in:
commit
b957e7c7b9
|
@ -1605,6 +1605,8 @@ namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.AllMediaStreams = mediaStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
|
private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
|
||||||
|
@ -1640,7 +1642,10 @@ namespace MediaBrowser.Api.Playback
|
||||||
// Can't stream copy if we're burning in subtitles
|
// Can't stream copy if we're burning in subtitles
|
||||||
if (request.SubtitleStreamIndex.HasValue)
|
if (request.SubtitleStreamIndex.HasValue)
|
||||||
{
|
{
|
||||||
return false;
|
if (request.SubtitleMethod == SubtitleDeliveryMethod.Encode)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source and target codecs must match
|
// Source and target codecs must match
|
||||||
|
|
|
@ -5,6 +5,8 @@ using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
|
@ -18,8 +20,7 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback.Hls
|
namespace MediaBrowser.Api.Playback.Hls
|
||||||
{
|
{
|
||||||
[Route("/Videos/{Id}/master.m3u8", "GET")]
|
[Route("/Videos/{Id}/master.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
|
||||||
[Api(Description = "Gets a video stream using HTTP live streaming.")]
|
|
||||||
public class GetMasterHlsVideoStream : VideoStreamRequest
|
public class GetMasterHlsVideoStream : VideoStreamRequest
|
||||||
{
|
{
|
||||||
public bool EnableAdaptiveBitrateStreaming { get; set; }
|
public bool EnableAdaptiveBitrateStreaming { get; set; }
|
||||||
|
@ -30,8 +31,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Videos/{Id}/main.m3u8", "GET")]
|
[Route("/Videos/{Id}/main.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
|
||||||
[Api(Description = "Gets a video stream using HTTP live streaming.")]
|
|
||||||
public class GetMainHlsVideoStream : VideoStreamRequest
|
public class GetMainHlsVideoStream : VideoStreamRequest
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -359,7 +359,17 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
var playlistUrl = (state.RunTimeTicks ?? 0) > 0 ? "main.m3u8" : "live.m3u8";
|
var playlistUrl = (state.RunTimeTicks ?? 0) > 0 ? "main.m3u8" : "live.m3u8";
|
||||||
playlistUrl += queryString;
|
playlistUrl += queryString;
|
||||||
|
|
||||||
AppendPlaylist(builder, playlistUrl, totalBitrate);
|
var request = (GetMasterHlsVideoStream) state.Request;
|
||||||
|
|
||||||
|
var subtitleStreams = state.AllMediaStreams
|
||||||
|
.Where(i => i.IsTextSubtitleStream)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var subtitleGroup = subtitleStreams.Count > 0 && request.SubtitleMethod == SubtitleDeliveryMethod.Hls ?
|
||||||
|
"subs" :
|
||||||
|
null;
|
||||||
|
|
||||||
|
AppendPlaylist(builder, playlistUrl, totalBitrate, subtitleGroup);
|
||||||
|
|
||||||
if (EnableAdaptiveBitrateStreaming(state))
|
if (EnableAdaptiveBitrateStreaming(state))
|
||||||
{
|
{
|
||||||
|
@ -369,16 +379,52 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
var variation = GetBitrateVariation(totalBitrate);
|
var variation = GetBitrateVariation(totalBitrate);
|
||||||
|
|
||||||
var newBitrate = totalBitrate - variation;
|
var newBitrate = totalBitrate - variation;
|
||||||
AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate);
|
AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate, subtitleGroup);
|
||||||
|
|
||||||
variation *= 2;
|
variation *= 2;
|
||||||
newBitrate = totalBitrate - variation;
|
newBitrate = totalBitrate - variation;
|
||||||
AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate);
|
AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate, subtitleGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(subtitleGroup))
|
||||||
|
{
|
||||||
|
AddSubtitles(state, subtitleStreams, builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddSubtitles(StreamState state, IEnumerable<MediaStream> subtitles, StringBuilder builder)
|
||||||
|
{
|
||||||
|
var selectedIndex = state.SubtitleStream == null ? (int?)null : state.SubtitleStream.Index;
|
||||||
|
|
||||||
|
foreach (var stream in subtitles)
|
||||||
|
{
|
||||||
|
const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},URI=\"{3}\",LANGUAGE=\"{4}\"";
|
||||||
|
|
||||||
|
var name = stream.Language;
|
||||||
|
|
||||||
|
var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index;
|
||||||
|
var isForced = stream.IsForced;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(name)) name = stream.Codec ?? "Unknown";
|
||||||
|
|
||||||
|
var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}",
|
||||||
|
state.Request.MediaSourceId,
|
||||||
|
stream.Index.ToString(UsCulture),
|
||||||
|
30.ToString(UsCulture));
|
||||||
|
|
||||||
|
var line = string.Format(format,
|
||||||
|
name,
|
||||||
|
isDefault ? "YES" : "NO",
|
||||||
|
isForced ? "YES" : "NO",
|
||||||
|
url,
|
||||||
|
stream.Language ?? "Unknown");
|
||||||
|
|
||||||
|
builder.AppendLine(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool EnableAdaptiveBitrateStreaming(StreamState state)
|
private bool EnableAdaptiveBitrateStreaming(StreamState state)
|
||||||
{
|
{
|
||||||
var request = state.Request as GetMasterHlsVideoStream;
|
var request = state.Request as GetMasterHlsVideoStream;
|
||||||
|
@ -397,9 +443,16 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
return state.VideoRequest.VideoBitRate.HasValue;
|
return state.VideoRequest.VideoBitRate.HasValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AppendPlaylist(StringBuilder builder, string url, int bitrate)
|
private void AppendPlaylist(StringBuilder builder, string url, int bitrate, string subtitleGroup)
|
||||||
{
|
{
|
||||||
builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + bitrate.ToString(UsCulture));
|
var header = "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + bitrate.ToString(UsCulture);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(subtitleGroup))
|
||||||
|
{
|
||||||
|
header += string.Format(",SUBTITLES=\"{0}\"", subtitleGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine(header);
|
||||||
builder.AppendLine(url);
|
builder.AppendLine(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using ServiceStack;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using ServiceStack;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback
|
namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
|
@ -160,6 +161,9 @@ namespace MediaBrowser.Api.Playback
|
||||||
[ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
public string Level { get; set; }
|
public string Level { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "SubtitleDeliveryMethod", Description = "Optional. Specify the subtitle delivery method.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public SubtitleDeliveryMethod SubtitleMethod { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this instance has fixed resolution.
|
/// Gets a value indicating whether this instance has fixed resolution.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -38,6 +38,8 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
public string InputContainer { get; set; }
|
public string InputContainer { get; set; }
|
||||||
|
|
||||||
|
public List<MediaStream> AllMediaStreams { get; set; }
|
||||||
|
|
||||||
public MediaStream AudioStream { get; set; }
|
public MediaStream AudioStream { get; set; }
|
||||||
public MediaStream VideoStream { get; set; }
|
public MediaStream VideoStream { get; set; }
|
||||||
public MediaStream SubtitleStream { get; set; }
|
public MediaStream SubtitleStream { get; set; }
|
||||||
|
@ -78,6 +80,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
SupportedAudioCodecs = new List<string>();
|
SupportedAudioCodecs = new List<string>();
|
||||||
PlayableStreamFileNames = new List<string>();
|
PlayableStreamFileNames = new List<string>();
|
||||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
AllMediaStreams = new List<MediaStream>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string InputAudioSync { get; set; }
|
public string InputAudioSync { get; set; }
|
||||||
|
|
|
@ -41,6 +41,9 @@ namespace MediaBrowser.Api
|
||||||
{
|
{
|
||||||
[ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
|
[ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "EntryIds", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
|
||||||
|
public string EntryIds { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Playlists/{Id}/Items", "GET", Summary = "Gets the original items of a playlist")]
|
[Route("/Playlists/{Id}/Items", "GET", Summary = "Gets the original items of a playlist")]
|
||||||
|
@ -122,9 +125,9 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
public void Delete(RemoveFromPlaylist request)
|
public void Delete(RemoveFromPlaylist request)
|
||||||
{
|
{
|
||||||
//var task = _playlistManager.RemoveFromPlaylist(request.Id, request.Ids.Split(',').Select(i => new Guid(i)));
|
var task = _playlistManager.RemoveFromPlaylist(request.Id, request.EntryIds.Split(','));
|
||||||
|
|
||||||
//Task.WaitAll(task);
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetPlaylistItems request)
|
public object Get(GetPlaylistItems request)
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
using System.IO;
|
using MediaBrowser.Controller.Entities;
|
||||||
using System.Linq;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
|
@ -11,6 +9,10 @@ using MediaBrowser.Model.Providers;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -69,7 +71,8 @@ namespace MediaBrowser.Api.Subtitles
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format (vtt).")]
|
[Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format.")]
|
||||||
|
[Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/{StartPositionTicks}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format.")]
|
||||||
public class GetSubtitle
|
public class GetSubtitle
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -90,6 +93,29 @@ namespace MediaBrowser.Api.Subtitles
|
||||||
|
|
||||||
[ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
public long StartPositionTicks { get; set; }
|
public long StartPositionTicks { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public long? EndPositionTicks { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")]
|
||||||
|
public class GetSubtitlePlaylist
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the id.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The id.</value>
|
||||||
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string MediaSourceId { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
|
||||||
|
public int Index { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "SegmentLength", Description = "The subtitle srgment length", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
|
||||||
|
public int SegmentLength { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SubtitleService : BaseApiService
|
public class SubtitleService : BaseApiService
|
||||||
|
@ -105,6 +131,53 @@ namespace MediaBrowser.Api.Subtitles
|
||||||
_subtitleEncoder = subtitleEncoder;
|
_subtitleEncoder = subtitleEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public object Get(GetSubtitlePlaylist request)
|
||||||
|
{
|
||||||
|
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
|
||||||
|
|
||||||
|
var mediaSource = item.GetMediaSources(false)
|
||||||
|
.First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
|
||||||
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
var runtime = mediaSource.RunTimeTicks ?? -1;
|
||||||
|
|
||||||
|
if (runtime <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("HLS Subtitles are not supported for this media.");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine("#EXTM3U");
|
||||||
|
builder.AppendLine("#EXT-X-TARGETDURATION:" + request.SegmentLength.ToString(CultureInfo.InvariantCulture));
|
||||||
|
builder.AppendLine("#EXT-X-VERSION:3");
|
||||||
|
builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
|
||||||
|
|
||||||
|
long positionTicks = 0;
|
||||||
|
var segmentLengthTicks = TimeSpan.FromSeconds(request.SegmentLength).Ticks;
|
||||||
|
|
||||||
|
while (positionTicks < runtime)
|
||||||
|
{
|
||||||
|
var remaining = runtime - positionTicks;
|
||||||
|
var lengthTicks = Math.Min(remaining, segmentLengthTicks);
|
||||||
|
|
||||||
|
builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);
|
||||||
|
|
||||||
|
var url = string.Format("stream.srt?StartPositionTicks={0}&EndPositionTicks={1}",
|
||||||
|
positionTicks.ToString(CultureInfo.InvariantCulture),
|
||||||
|
endPositionTicks.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
builder.AppendLine(url);
|
||||||
|
|
||||||
|
positionTicks += segmentLengthTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine("#EXT-X-ENDLIST");
|
||||||
|
|
||||||
|
return ResultFactory.GetResult(builder.ToString(), Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
|
||||||
|
}
|
||||||
|
|
||||||
public object Get(GetSubtitle request)
|
public object Get(GetSubtitle request)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(request.Format))
|
if (string.IsNullOrEmpty(request.Format))
|
||||||
|
@ -132,6 +205,7 @@ namespace MediaBrowser.Api.Subtitles
|
||||||
request.Index,
|
request.Index,
|
||||||
request.Format,
|
request.Format,
|
||||||
request.StartPositionTicks,
|
request.StartPositionTicks,
|
||||||
|
request.EndPositionTicks,
|
||||||
CancellationToken.None).ConfigureAwait(false);
|
CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,8 @@ namespace MediaBrowser.Api
|
||||||
/// Initializes a new instance of the <see cref="SystemService" /> class.
|
/// Initializes a new instance of the <see cref="SystemService" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appHost">The app host.</param>
|
/// <param name="appHost">The app host.</param>
|
||||||
|
/// <param name="appPaths">The application paths.</param>
|
||||||
|
/// <param name="fileSystem">The file system.</param>
|
||||||
/// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
|
/// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
|
||||||
public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem)
|
public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
|
|
|
@ -236,6 +236,11 @@ namespace MediaBrowser.Common.Net
|
||||||
return "text/vtt";
|
return "text/vtt";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ext.Equals(".ttml", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "application/ttml+xml";
|
||||||
|
}
|
||||||
|
|
||||||
if (ext.Equals(".bif", StringComparison.OrdinalIgnoreCase))
|
if (ext.Equals(".bif", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return "application/octet-stream";
|
return "application/octet-stream";
|
||||||
|
|
|
@ -34,6 +34,11 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||||
Tags = new List<string>();
|
Tags = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsAddingToPlaylist
|
||||||
|
{
|
||||||
|
get { return LocationType == LocationType.FileSystem && RunTimeTicks.HasValue; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance has embedded image.
|
/// Gets or sets a value indicating whether this instance has embedded image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -21,6 +21,11 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||||
SoundtrackIds = new List<Guid>();
|
SoundtrackIds = new List<Guid>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsAddingToPlaylist
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public MusicArtist MusicArtist
|
public MusicArtist MusicArtist
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,6 +26,11 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsAddingToPlaylist
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<BaseItem> ActualChildren
|
protected override IEnumerable<BaseItem> ActualChildren
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
@ -18,6 +18,11 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||||
return "MusicGenre-" + Name;
|
return "MusicGenre-" + Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsAddingToPlaylist
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the folder containing the item.
|
/// Returns the folder containing the item.
|
||||||
/// If the item is a folder, it returns the folder itself
|
/// If the item is a folder, it returns the folder itself
|
||||||
|
|
|
@ -52,6 +52,14 @@ namespace MediaBrowser.Controller.Entities
|
||||||
|
|
||||||
public List<ItemImageInfo> ImageInfos { get; set; }
|
public List<ItemImageInfo> ImageInfos { get; set; }
|
||||||
|
|
||||||
|
public virtual bool SupportsAddingToPlaylist
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this instance is in mixed folder.
|
/// Gets a value indicating whether this instance is in mixed folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -13,6 +13,9 @@ namespace MediaBrowser.Controller.Entities
|
||||||
public string ItemType { get; set; }
|
public string ItemType { get; set; }
|
||||||
public int? ItemYear { get; set; }
|
public int? ItemYear { get; set; }
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serves as a cache
|
/// Serves as a cache
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -27,6 +30,11 @@ namespace MediaBrowser.Controller.Entities
|
||||||
Type = LinkedChildType.Manual
|
Type = LinkedChildType.Manual
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LinkedChild()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid().ToString("N");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum LinkedChildType
|
public enum LinkedChildType
|
||||||
|
|
|
@ -29,6 +29,11 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsAddingToPlaylist
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public override bool IsPreSorted
|
public override bool IsPreSorted
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,6 +39,11 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
DisplaySpecialsWithSeasons = true;
|
DisplaySpecialsWithSeasons = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsAddingToPlaylist
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public override bool IsPreSorted
|
public override bool IsPreSorted
|
||||||
{
|
{
|
||||||
|
|
|
@ -55,6 +55,11 @@ namespace MediaBrowser.Controller.Entities
|
||||||
LinkedAlternateVersions = new List<LinkedChild>();
|
LinkedAlternateVersions = new List<LinkedChild>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsAddingToPlaylist
|
||||||
|
{
|
||||||
|
get { return LocationType == LocationType.FileSystem && RunTimeTicks.HasValue; }
|
||||||
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public int MediaSourceCount
|
public int MediaSourceCount
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,5 +46,11 @@ namespace MediaBrowser.Controller
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The server identifier.</value>
|
/// <value>The server identifier.</value>
|
||||||
string ServerId { get; }
|
string ServerId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the friendly.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name of the friendly.</value>
|
||||||
|
string FriendlyName { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ namespace MediaBrowser.Controller.Library
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileName(path);
|
var filename = Path.GetFileName(path);
|
||||||
|
|
||||||
if (string.Equals(path, "specials", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
/// <param name="inputFormat">The input format.</param>
|
/// <param name="inputFormat">The input format.</param>
|
||||||
/// <param name="outputFormat">The output format.</param>
|
/// <param name="outputFormat">The output format.</param>
|
||||||
/// <param name="startTimeTicks">The start time ticks.</param>
|
/// <param name="startTimeTicks">The start time ticks.</param>
|
||||||
|
/// <param name="endTimeTicks">The end time ticks.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task{Stream}.</returns>
|
/// <returns>Task{Stream}.</returns>
|
||||||
Task<Stream> ConvertSubtitles(
|
Task<Stream> ConvertSubtitles(
|
||||||
|
@ -20,6 +21,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
string inputFormat,
|
string inputFormat,
|
||||||
string outputFormat,
|
string outputFormat,
|
||||||
long startTimeTicks,
|
long startTimeTicks,
|
||||||
|
long? endTimeTicks,
|
||||||
CancellationToken cancellationToken);
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -30,6 +32,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
|
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
|
||||||
/// <param name="outputFormat">The output format.</param>
|
/// <param name="outputFormat">The output format.</param>
|
||||||
/// <param name="startTimeTicks">The start time ticks.</param>
|
/// <param name="startTimeTicks">The start time ticks.</param>
|
||||||
|
/// <param name="endTimeTicks">The end time ticks.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task{Stream}.</returns>
|
/// <returns>Task{Stream}.</returns>
|
||||||
Task<Stream> GetSubtitles(string itemId,
|
Task<Stream> GetSubtitles(string itemId,
|
||||||
|
@ -37,6 +40,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
int subtitleStreamIndex,
|
int subtitleStreamIndex,
|
||||||
string outputFormat,
|
string outputFormat,
|
||||||
long startTimeTicks,
|
long startTimeTicks,
|
||||||
|
long? endTimeTicks,
|
||||||
CancellationToken cancellationToken);
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -32,9 +32,9 @@ namespace MediaBrowser.Controller.Playlists
|
||||||
/// Removes from playlist.
|
/// Removes from playlist.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="playlistId">The playlist identifier.</param>
|
/// <param name="playlistId">The playlist identifier.</param>
|
||||||
/// <param name="indeces">The indeces.</param>
|
/// <param name="entryIds">The entry ids.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task RemoveFromPlaylist(string playlistId, IEnumerable<int> indeces);
|
Task RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the playlists folder.
|
/// Gets the playlists folder.
|
||||||
|
|
|
@ -183,19 +183,34 @@ namespace MediaBrowser.Dlna.ContentDirectory
|
||||||
//didl.SetAttribute("xmlns:sec", NS_SEC);
|
//didl.SetAttribute("xmlns:sec", NS_SEC);
|
||||||
result.AppendChild(didl);
|
result.AppendChild(didl);
|
||||||
|
|
||||||
var folder = (Folder)GetItemFromObjectId(id, user);
|
var item = GetItemFromObjectId(id, user);
|
||||||
|
|
||||||
var childrenResult = (await GetChildrenSorted(folder, user, sortCriteria, start, requested).ConfigureAwait(false));
|
var totalCount = 0;
|
||||||
|
|
||||||
var totalCount = childrenResult.TotalRecordCount;
|
|
||||||
|
|
||||||
if (string.Equals(flag, "BrowseMetadata"))
|
if (string.Equals(flag, "BrowseMetadata"))
|
||||||
{
|
{
|
||||||
result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, folder, totalCount, filter));
|
var folder = item as Folder;
|
||||||
|
|
||||||
|
if (folder == null)
|
||||||
|
{
|
||||||
|
result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(result, item, deviceId, filter));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var childrenResult = (await GetChildrenSorted(folder, user, sortCriteria, start, requested).ConfigureAwait(false));
|
||||||
|
totalCount = childrenResult.TotalRecordCount;
|
||||||
|
|
||||||
|
result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, folder, totalCount, filter));
|
||||||
|
}
|
||||||
provided++;
|
provided++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var folder = (Folder)item;
|
||||||
|
|
||||||
|
var childrenResult = (await GetChildrenSorted(folder, user, sortCriteria, start, requested).ConfigureAwait(false));
|
||||||
|
totalCount = childrenResult.TotalRecordCount;
|
||||||
|
|
||||||
provided = childrenResult.Items.Length;
|
provided = childrenResult.Items.Length;
|
||||||
|
|
||||||
foreach (var i in childrenResult.Items)
|
foreach (var i in childrenResult.Items)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using MediaBrowser.Common.Net;
|
using System.IO;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
@ -13,6 +14,7 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.Didl
|
namespace MediaBrowser.Dlna.Didl
|
||||||
{
|
{
|
||||||
|
@ -101,7 +103,7 @@ namespace MediaBrowser.Dlna.Didl
|
||||||
{
|
{
|
||||||
var sources = _user == null ? video.GetMediaSources(true).ToList() : video.GetMediaSources(true, _user).ToList();
|
var sources = _user == null ? video.GetMediaSources(true).ToList() : video.GetMediaSources(true, _user).ToList();
|
||||||
|
|
||||||
streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
|
streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
|
||||||
{
|
{
|
||||||
ItemId = video.Id.ToString("N"),
|
ItemId = video.Id.ToString("N"),
|
||||||
MediaSources = sources,
|
MediaSources = sources,
|
||||||
|
@ -137,6 +139,23 @@ namespace MediaBrowser.Dlna.Didl
|
||||||
{
|
{
|
||||||
AddVideoResource(container, video, deviceId, filter, contentFeature, streamInfo);
|
AddVideoResource(container, video, deviceId, filter, contentFeature, streamInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var subtitle in streamInfo.GetExternalSubtitles(_serverAddress))
|
||||||
|
{
|
||||||
|
AddSubtitleElement(container, subtitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddSubtitleElement(XmlElement container, SubtitleStreamInfo info)
|
||||||
|
{
|
||||||
|
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
|
||||||
|
|
||||||
|
res.InnerText = info.Url;
|
||||||
|
|
||||||
|
// TODO: Remove this hard-coding
|
||||||
|
res.SetAttribute("protocolInfo", "http-get:*:text/srt:*");
|
||||||
|
|
||||||
|
container.AppendChild(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddVideoResource(XmlElement container, Video video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo)
|
private void AddVideoResource(XmlElement container, Video video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo)
|
||||||
|
@ -598,9 +617,11 @@ namespace MediaBrowser.Dlna.Didl
|
||||||
}
|
}
|
||||||
|
|
||||||
AddImageResElement(item, element, 4096, 4096, "jpg");
|
AddImageResElement(item, element, 4096, 4096, "jpg");
|
||||||
|
AddImageResElement(item, element, 4096, 4096, "png");
|
||||||
AddImageResElement(item, element, 1024, 768, "jpg");
|
AddImageResElement(item, element, 1024, 768, "jpg");
|
||||||
AddImageResElement(item, element, 640, 480, "jpg");
|
AddImageResElement(item, element, 640, 480, "jpg");
|
||||||
AddImageResElement(item, element, 160, 160, "jpg");
|
AddImageResElement(item, element, 160, 160, "jpg");
|
||||||
|
AddImageResElement(item, element, 160, 160, "png");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddImageResElement(BaseItem item, XmlElement element, int maxWidth, int maxHeight, string format)
|
private void AddImageResElement(BaseItem item, XmlElement element, int maxWidth, int maxHeight, string format)
|
||||||
|
@ -623,7 +644,7 @@ namespace MediaBrowser.Dlna.Didl
|
||||||
var width = albumartUrlInfo.Width;
|
var width = albumartUrlInfo.Width;
|
||||||
var height = albumartUrlInfo.Height;
|
var height = albumartUrlInfo.Height;
|
||||||
|
|
||||||
var contentFeatures = new ContentFeatureBuilder(_profile).BuildImageHeader(format, width, height);
|
var contentFeatures = new ContentFeatureBuilder(_profile).BuildImageHeader(format, width, height, imageInfo.IsDirectStream);
|
||||||
|
|
||||||
res.SetAttribute("protocolInfo", String.Format(
|
res.SetAttribute("protocolInfo", String.Format(
|
||||||
"http-get:*:{0}:{1}",
|
"http-get:*:{0}:{1}",
|
||||||
|
@ -631,6 +652,14 @@ namespace MediaBrowser.Dlna.Didl
|
||||||
contentFeatures
|
contentFeatures
|
||||||
));
|
));
|
||||||
|
|
||||||
|
res.SetAttribute("colorDepth", "24");
|
||||||
|
|
||||||
|
if (imageInfo.IsDirectStream)
|
||||||
|
{
|
||||||
|
// TODO: Add file size
|
||||||
|
//res.SetAttribute("size", imageInfo.Size.Value.ToString(_usCulture));
|
||||||
|
}
|
||||||
|
|
||||||
if (width.HasValue && height.HasValue)
|
if (width.HasValue && height.HasValue)
|
||||||
{
|
{
|
||||||
res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value));
|
res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value));
|
||||||
|
@ -705,7 +734,8 @@ namespace MediaBrowser.Dlna.Didl
|
||||||
Type = type,
|
Type = type,
|
||||||
ImageTag = tag,
|
ImageTag = tag,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height
|
Height = height,
|
||||||
|
File = imageInfo.Path
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -717,6 +747,10 @@ namespace MediaBrowser.Dlna.Didl
|
||||||
|
|
||||||
internal int? Width;
|
internal int? Width;
|
||||||
internal int? Height;
|
internal int? Height;
|
||||||
|
|
||||||
|
internal bool IsDirectStream;
|
||||||
|
|
||||||
|
internal string File;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImageUrlInfo
|
class ImageUrlInfo
|
||||||
|
@ -741,6 +775,8 @@ namespace MediaBrowser.Dlna.Didl
|
||||||
var width = info.Width;
|
var width = info.Width;
|
||||||
var height = info.Height;
|
var height = info.Height;
|
||||||
|
|
||||||
|
info.IsDirectStream = false;
|
||||||
|
|
||||||
if (width.HasValue && height.HasValue)
|
if (width.HasValue && height.HasValue)
|
||||||
{
|
{
|
||||||
var newSize = DrawingUtils.Resize(new ImageSize
|
var newSize = DrawingUtils.Resize(new ImageSize
|
||||||
|
@ -752,6 +788,18 @@ namespace MediaBrowser.Dlna.Didl
|
||||||
|
|
||||||
width = Convert.ToInt32(newSize.Width);
|
width = Convert.ToInt32(newSize.Width);
|
||||||
height = Convert.ToInt32(newSize.Height);
|
height = Convert.ToInt32(newSize.Height);
|
||||||
|
|
||||||
|
var inputFormat = (Path.GetExtension(info.File) ?? string.Empty)
|
||||||
|
.TrimStart('.')
|
||||||
|
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var normalizedFormat = format
|
||||||
|
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (string.Equals(inputFormat, normalizedFormat, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
info.IsDirectStream = maxWidth >= width.Value && maxHeight >= height.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ImageUrlInfo
|
return new ImageUrlInfo
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -29,7 +30,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
|
|
||||||
if (directPlay != null)
|
if (directPlay != null)
|
||||||
{
|
{
|
||||||
playlistItem.StreamInfo.IsDirectStream = true;
|
playlistItem.StreamInfo.PlayMethod = PlayMethod.DirectStream;
|
||||||
playlistItem.StreamInfo.Container = Path.GetExtension(item.Path);
|
playlistItem.StreamInfo.Container = Path.GetExtension(item.Path);
|
||||||
|
|
||||||
return playlistItem;
|
return playlistItem;
|
||||||
|
@ -40,7 +41,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
|
|
||||||
if (transcodingProfile != null)
|
if (transcodingProfile != null)
|
||||||
{
|
{
|
||||||
playlistItem.StreamInfo.IsDirectStream = true;
|
playlistItem.StreamInfo.PlayMethod = PlayMethod.Transcode;
|
||||||
playlistItem.StreamInfo.Container = "." + transcodingProfile.Container.TrimStart('.');
|
playlistItem.StreamInfo.Container = "." + transcodingProfile.Container.TrimStart('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -193,11 +193,12 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SoftSubtitleProfiles = new[]
|
SubtitleProfiles = new[]
|
||||||
{
|
{
|
||||||
new SubtitleProfile
|
new SubtitleProfile
|
||||||
{
|
{
|
||||||
Format = "srt"
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.External
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -339,11 +339,12 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SoftSubtitleProfiles = new[]
|
SubtitleProfiles = new[]
|
||||||
{
|
{
|
||||||
new SubtitleProfile
|
new SubtitleProfile
|
||||||
{
|
{
|
||||||
Format = "smi"
|
Format = "smi",
|
||||||
|
Method = SubtitleDeliveryMethod.External
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,14 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "ttml",
|
||||||
|
Method = SubtitleDeliveryMethod.External
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,4 @@
|
||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -35,6 +35,4 @@
|
||||||
<ContainerProfiles />
|
<ContainerProfiles />
|
||||||
<CodecProfiles />
|
<CodecProfiles />
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -39,6 +39,4 @@
|
||||||
<ContainerProfiles />
|
<ContainerProfiles />
|
||||||
<CodecProfiles />
|
<CodecProfiles />
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -73,6 +73,4 @@
|
||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -39,6 +39,4 @@
|
||||||
<ContainerProfiles />
|
<ContainerProfiles />
|
||||||
<CodecProfiles />
|
<CodecProfiles />
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -45,6 +45,4 @@
|
||||||
<ContainerProfiles />
|
<ContainerProfiles />
|
||||||
<CodecProfiles />
|
<CodecProfiles />
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -68,10 +68,7 @@
|
||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles>
|
<SubtitleProfiles>
|
||||||
<SubtitleProfile>
|
<SubtitleProfile format="srt" method="External" />
|
||||||
<Format>srt</Format>
|
</SubtitleProfiles>
|
||||||
</SubtitleProfile>
|
|
||||||
</SoftSubtitleProfiles>
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -106,10 +106,7 @@
|
||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles>
|
<SubtitleProfiles>
|
||||||
<SubtitleProfile>
|
<SubtitleProfile format="smi" method="External" />
|
||||||
<Format>smi</Format>
|
</SubtitleProfiles>
|
||||||
</SubtitleProfile>
|
|
||||||
</SoftSubtitleProfiles>
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -71,6 +71,4 @@
|
||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -99,6 +99,4 @@
|
||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -107,6 +107,4 @@
|
||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -110,6 +110,4 @@
|
||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -93,6 +93,4 @@
|
||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -93,6 +93,4 @@
|
||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -93,6 +93,4 @@
|
||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -78,6 +78,4 @@
|
||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -62,6 +62,7 @@
|
||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
<SubtitleProfiles>
|
||||||
<ExternalSubtitleProfiles />
|
<SubtitleProfile format="ttml" method="External" />
|
||||||
|
</SubtitleProfiles>
|
||||||
</Profile>
|
</Profile>
|
|
@ -76,6 +76,4 @@
|
||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -100,6 +100,4 @@
|
||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -90,6 +90,4 @@
|
||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -45,6 +45,4 @@
|
||||||
<ContainerProfiles />
|
<ContainerProfiles />
|
||||||
<CodecProfiles />
|
<CodecProfiles />
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
|
@ -18,6 +18,20 @@ namespace MediaBrowser.LocalMetadata.Parsers
|
||||||
{
|
{
|
||||||
switch (reader.Name)
|
switch (reader.Name)
|
||||||
{
|
{
|
||||||
|
case "OwnerUserId":
|
||||||
|
{
|
||||||
|
item.OwnerUserId = reader.ReadElementContentAsString();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "PlaylistMediaType":
|
||||||
|
{
|
||||||
|
item.PlaylistMediaType = reader.ReadElementContentAsString();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case "PlaylistItems":
|
case "PlaylistItems":
|
||||||
|
|
||||||
using (var subReader = reader.ReadSubtree())
|
using (var subReader = reader.ReadSubtree())
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using MediaBrowser.Controller.Entities;
|
using System.Security;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Playlists;
|
using MediaBrowser.Controller.Playlists;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -42,17 +43,34 @@ namespace MediaBrowser.LocalMetadata.Savers
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
public void Save(IHasMetadata item, CancellationToken cancellationToken)
|
public void Save(IHasMetadata item, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var playlist = (Playlist)item;
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
builder.Append("<Item>");
|
builder.Append("<Item>");
|
||||||
|
|
||||||
XmlSaverHelpers.AddCommonNodes((Playlist)item, builder);
|
if (!string.IsNullOrEmpty(playlist.OwnerUserId))
|
||||||
|
{
|
||||||
|
builder.Append("<OwnerUserId>" + SecurityElement.Escape(playlist.OwnerUserId) + "</OwnerUserId>");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(playlist.PlaylistMediaType))
|
||||||
|
{
|
||||||
|
builder.Append("<PlaylistMediaType>" + SecurityElement.Escape(playlist.PlaylistMediaType) + "</PlaylistMediaType>");
|
||||||
|
}
|
||||||
|
|
||||||
|
XmlSaverHelpers.AddCommonNodes(playlist, builder);
|
||||||
|
|
||||||
builder.Append("</Item>");
|
builder.Append("</Item>");
|
||||||
|
|
||||||
var xmlFilePath = GetSavePath(item);
|
var xmlFilePath = GetSavePath(item);
|
||||||
|
|
||||||
XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { });
|
XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
|
||||||
|
{
|
||||||
|
"OwnerUserId",
|
||||||
|
"PlaylistMediaType"
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -704,7 +704,7 @@ namespace MediaBrowser.LocalMetadata.Savers
|
||||||
public static void AddLinkedChildren(Folder item, StringBuilder builder, string pluralNodeName, string singularNodeName)
|
public static void AddLinkedChildren(Folder item, StringBuilder builder, string pluralNodeName, string singularNodeName)
|
||||||
{
|
{
|
||||||
var items = item.LinkedChildren
|
var items = item.LinkedChildren
|
||||||
.Where(i => i.Type == LinkedChildType.Manual && !string.IsNullOrWhiteSpace(i.ItemName))
|
.Where(i => i.Type == LinkedChildType.Manual)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (items.Count == 0)
|
if (items.Count == 0)
|
||||||
|
@ -717,14 +717,20 @@ namespace MediaBrowser.LocalMetadata.Savers
|
||||||
{
|
{
|
||||||
builder.Append("<" + singularNodeName + ">");
|
builder.Append("<" + singularNodeName + ">");
|
||||||
|
|
||||||
builder.Append("<Type>" + SecurityElement.Escape(link.ItemType) + "</Type>");
|
if (!string.IsNullOrWhiteSpace(link.ItemType))
|
||||||
|
{
|
||||||
|
builder.Append("<Type>" + SecurityElement.Escape(link.ItemType) + "</Type>");
|
||||||
|
}
|
||||||
|
|
||||||
if (link.ItemYear.HasValue)
|
if (link.ItemYear.HasValue)
|
||||||
{
|
{
|
||||||
builder.Append("<Year>" + SecurityElement.Escape(link.ItemYear.Value.ToString(UsCulture)) + "</Year>");
|
builder.Append("<Year>" + SecurityElement.Escape(link.ItemYear.Value.ToString(UsCulture)) + "</Year>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append("<Path>" + SecurityElement.Escape((link.Path ?? string.Empty)) + "</Path>");
|
if (!string.IsNullOrWhiteSpace(link.Path))
|
||||||
|
{
|
||||||
|
builder.Append("<Path>" + SecurityElement.Escape((link.Path)) + "</Path>");
|
||||||
|
}
|
||||||
|
|
||||||
builder.Append("</" + singularNodeName + ">");
|
builder.Append("</" + singularNodeName + ">");
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@
|
||||||
<Compile Include="Subtitles\SsaParser.cs" />
|
<Compile Include="Subtitles\SsaParser.cs" />
|
||||||
<Compile Include="Subtitles\SubtitleEncoder.cs" />
|
<Compile Include="Subtitles\SubtitleEncoder.cs" />
|
||||||
<Compile Include="Subtitles\SubtitleTrackInfo.cs" />
|
<Compile Include="Subtitles\SubtitleTrackInfo.cs" />
|
||||||
|
<Compile Include="Subtitles\TtmlWriter.cs" />
|
||||||
<Compile Include="Subtitles\VttWriter.cs" />
|
<Compile Include="Subtitles\VttWriter.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -48,6 +48,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
string inputFormat,
|
string inputFormat,
|
||||||
string outputFormat,
|
string outputFormat,
|
||||||
long startTimeTicks,
|
long startTimeTicks,
|
||||||
|
long? endTimeTicks,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var ms = new MemoryStream();
|
var ms = new MemoryStream();
|
||||||
|
@ -56,6 +57,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
// Return the original without any conversions, if possible
|
// Return the original without any conversions, if possible
|
||||||
if (startTimeTicks == 0 &&
|
if (startTimeTicks == 0 &&
|
||||||
|
!endTimeTicks.HasValue &&
|
||||||
string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase))
|
string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
await stream.CopyToAsync(ms, 81920, cancellationToken).ConfigureAwait(false);
|
await stream.CopyToAsync(ms, 81920, cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -64,7 +66,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
var trackInfo = await GetTrackInfo(stream, inputFormat, cancellationToken).ConfigureAwait(false);
|
var trackInfo = await GetTrackInfo(stream, inputFormat, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
UpdateStartingPosition(trackInfo, startTimeTicks);
|
FilterEvents(trackInfo, startTimeTicks, endTimeTicks, false);
|
||||||
|
|
||||||
var writer = GetWriter(outputFormat);
|
var writer = GetWriter(outputFormat);
|
||||||
|
|
||||||
|
@ -81,19 +83,30 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
return ms;
|
return ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateStartingPosition(SubtitleTrackInfo track, long startPositionTicks)
|
private void FilterEvents(SubtitleTrackInfo track, long startPositionTicks, long? endTimeTicks, bool preserveTimestamps)
|
||||||
{
|
{
|
||||||
if (startPositionTicks == 0) return;
|
// Drop subs that are earlier than what we're looking for
|
||||||
|
track.TrackEvents = track.TrackEvents
|
||||||
|
.SkipWhile(i => (i.StartPositionTicks - startPositionTicks) < 0 || (i.EndPositionTicks - startPositionTicks) < 0)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
foreach (var trackEvent in track.TrackEvents)
|
if (endTimeTicks.HasValue)
|
||||||
{
|
{
|
||||||
trackEvent.EndPositionTicks -= startPositionTicks;
|
var endTime = endTimeTicks.Value;
|
||||||
trackEvent.StartPositionTicks -= startPositionTicks;
|
|
||||||
|
track.TrackEvents = track.TrackEvents
|
||||||
|
.TakeWhile(i => i.StartPositionTicks <= endTime)
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
track.TrackEvents = track.TrackEvents
|
if (!preserveTimestamps)
|
||||||
.SkipWhile(i => i.StartPositionTicks < 0 || i.EndPositionTicks < 0)
|
{
|
||||||
.ToList();
|
foreach (var trackEvent in track.TrackEvents)
|
||||||
|
{
|
||||||
|
trackEvent.EndPositionTicks -= startPositionTicks;
|
||||||
|
trackEvent.StartPositionTicks -= startPositionTicks;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Stream> GetSubtitles(string itemId,
|
public async Task<Stream> GetSubtitles(string itemId,
|
||||||
|
@ -101,6 +114,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
int subtitleStreamIndex,
|
int subtitleStreamIndex,
|
||||||
string outputFormat,
|
string outputFormat,
|
||||||
long startTimeTicks,
|
long startTimeTicks,
|
||||||
|
long? endTimeTicks,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
|
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
|
||||||
|
@ -110,7 +124,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
var inputFormat = subtitle.Item2;
|
var inputFormat = subtitle.Item2;
|
||||||
|
|
||||||
return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, cancellationToken).ConfigureAwait(false);
|
return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,6 +268,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
return new VttWriter();
|
return new VttWriter();
|
||||||
}
|
}
|
||||||
|
if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return new TtmlWriter();
|
||||||
|
}
|
||||||
|
|
||||||
throw new ArgumentException("Unsupported format: " + format);
|
throw new ArgumentException("Unsupported format: " + format);
|
||||||
}
|
}
|
||||||
|
|
59
MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
Normal file
59
MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
|
{
|
||||||
|
public class TtmlWriter : ISubtitleWriter
|
||||||
|
{
|
||||||
|
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Example: https://github.com/zmalltalker/ttml2vtt/blob/master/data/sample.xml
|
||||||
|
// Parser example: https://github.com/mozilla/popcorn-js/blob/master/parsers/parserTTML/popcorn.parserTTML.js
|
||||||
|
|
||||||
|
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
|
{
|
||||||
|
writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
||||||
|
writer.WriteLine("<tt xmlns=\"http://www.w3.org/ns/ttml\" xmlns:tts=\"http://www.w3.org/2006/04/ttaf1#styling\" lang=\"no\">");
|
||||||
|
|
||||||
|
writer.WriteLine("<head>");
|
||||||
|
writer.WriteLine("<styling>");
|
||||||
|
writer.WriteLine("<style id=\"italic\" tts:fontStyle=\"italic\" />");
|
||||||
|
writer.WriteLine("<style id=\"left\" tts:textAlign=\"left\" />");
|
||||||
|
writer.WriteLine("<style id=\"center\" tts:textAlign=\"center\" />");
|
||||||
|
writer.WriteLine("<style id=\"right\" tts:textAlign=\"right\" />");
|
||||||
|
writer.WriteLine("</styling>");
|
||||||
|
writer.WriteLine("</head>");
|
||||||
|
|
||||||
|
writer.WriteLine("<body>");
|
||||||
|
writer.WriteLine("<div>");
|
||||||
|
|
||||||
|
foreach (var trackEvent in info.TrackEvents)
|
||||||
|
{
|
||||||
|
var text = trackEvent.Text;
|
||||||
|
|
||||||
|
text = Regex.Replace(text, @"\\N", "<br/>", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
writer.WriteLine("<p begin=\"{0}\" dur=\"{1}\">{2}</p>",
|
||||||
|
trackEvent.StartPositionTicks,
|
||||||
|
(trackEvent.EndPositionTicks - trackEvent.StartPositionTicks),
|
||||||
|
text);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteLine("</div>");
|
||||||
|
writer.WriteLine("</body>");
|
||||||
|
|
||||||
|
writer.WriteLine("</tt>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatTime(long ticks)
|
||||||
|
{
|
||||||
|
var time = TimeSpan.FromTicks(ticks);
|
||||||
|
|
||||||
|
return string.Format(@"{0:hh\:mm\:ss\,fff}", time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,12 +14,13 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
public string BuildImageHeader(string container,
|
public string BuildImageHeader(string container,
|
||||||
int? width,
|
int? width,
|
||||||
int? height)
|
int? height,
|
||||||
|
bool isDirectStream)
|
||||||
{
|
{
|
||||||
string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetImageOrgOpValue();
|
string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetImageOrgOpValue();
|
||||||
|
|
||||||
// 0 = native, 1 = transcoded
|
// 0 = native, 1 = transcoded
|
||||||
const string orgCi = ";DLNA.ORG_CI=0";
|
var orgCi = isDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
|
||||||
|
|
||||||
DlnaFlags flagValue = DlnaFlags.StreamingTransferMode |
|
DlnaFlags flagValue = DlnaFlags.StreamingTransferMode |
|
||||||
DlnaFlags.BackgroundTransferMode |
|
DlnaFlags.BackgroundTransferMode |
|
||||||
|
|
|
@ -90,8 +90,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
public CodecProfile[] CodecProfiles { get; set; }
|
public CodecProfile[] CodecProfiles { get; set; }
|
||||||
public ResponseProfile[] ResponseProfiles { get; set; }
|
public ResponseProfile[] ResponseProfiles { get; set; }
|
||||||
|
|
||||||
public SubtitleProfile[] SoftSubtitleProfiles { get; set; }
|
public SubtitleProfile[] SubtitleProfiles { get; set; }
|
||||||
public SubtitleProfile[] ExternalSubtitleProfiles { get; set; }
|
|
||||||
|
|
||||||
public DeviceProfile()
|
public DeviceProfile()
|
||||||
{
|
{
|
||||||
|
@ -100,9 +99,6 @@ namespace MediaBrowser.Model.Dlna
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = new ResponseProfile[] { };
|
||||||
CodecProfiles = new CodecProfile[] { };
|
CodecProfiles = new CodecProfile[] { };
|
||||||
ContainerProfiles = new ContainerProfile[] { };
|
ContainerProfiles = new ContainerProfile[] { };
|
||||||
|
|
||||||
SoftSubtitleProfiles = new SubtitleProfile[] { };
|
|
||||||
ExternalSubtitleProfiles = new SubtitleProfile[] { };
|
|
||||||
|
|
||||||
XmlRootAttributes = new XmlAttribute[] { };
|
XmlRootAttributes = new XmlAttribute[] { };
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
orgOp += "0";
|
orgOp += "0";
|
||||||
|
|
||||||
// Byte-based seeking only possible when not transcoding
|
// Byte-based seeking only possible when not transcoding
|
||||||
orgOp += "1";
|
orgOp += "0";
|
||||||
|
|
||||||
return orgOp;
|
return orgOp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -385,7 +385,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
return ResolveImageJPGFormat(width, height);
|
return ResolveImageJPGFormat(width, height);
|
||||||
|
|
||||||
if (StringHelper.EqualsIgnoreCase(container, "png"))
|
if (StringHelper.EqualsIgnoreCase(container, "png"))
|
||||||
return MediaFormatProfile.PNG_LRG;
|
return ResolveImagePNGFormat(width, height);
|
||||||
|
|
||||||
if (StringHelper.EqualsIgnoreCase(container, "gif"))
|
if (StringHelper.EqualsIgnoreCase(container, "gif"))
|
||||||
return MediaFormatProfile.GIF_LRG;
|
return MediaFormatProfile.GIF_LRG;
|
||||||
|
@ -401,7 +401,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
if (width.HasValue && height.HasValue)
|
if (width.HasValue && height.HasValue)
|
||||||
{
|
{
|
||||||
if ((width.Value <= 160) && (height.Value <= 160))
|
if ((width.Value <= 160) && (height.Value <= 160))
|
||||||
return MediaFormatProfile.JPEG_SM;
|
return MediaFormatProfile.JPEG_TN;
|
||||||
|
|
||||||
if ((width.Value <= 640) && (height.Value <= 480))
|
if ((width.Value <= 640) && (height.Value <= 480))
|
||||||
return MediaFormatProfile.JPEG_SM;
|
return MediaFormatProfile.JPEG_SM;
|
||||||
|
@ -416,5 +416,16 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
return MediaFormatProfile.JPEG_SM;
|
return MediaFormatProfile.JPEG_SM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MediaFormatProfile ResolveImagePNGFormat(int? width, int? height)
|
||||||
|
{
|
||||||
|
if (width.HasValue && height.HasValue)
|
||||||
|
{
|
||||||
|
if ((width.Value <= 160) && (height.Value <= 160))
|
||||||
|
return MediaFormatProfile.PNG_TN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MediaFormatProfile.PNG_LRG;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Extensions;
|
using MediaBrowser.Model.Extensions;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
@ -9,7 +10,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
public class StreamBuilder
|
public class StreamBuilder
|
||||||
{
|
{
|
||||||
private string[] _serverTextSubtitleOutputs = new string[] { "srt", "vtt" };
|
private readonly string[] _serverTextSubtitleOutputs = { "srt", "vtt", "ttml" };
|
||||||
|
|
||||||
public StreamInfo BuildAudioItem(AudioOptions options)
|
public StreamInfo BuildAudioItem(AudioOptions options)
|
||||||
{
|
{
|
||||||
|
@ -158,7 +159,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
if (all)
|
if (all)
|
||||||
{
|
{
|
||||||
playlistItem.IsDirectStream = true;
|
playlistItem.PlayMethod = PlayMethod.DirectStream;
|
||||||
playlistItem.Container = item.Container;
|
playlistItem.Container = item.Container;
|
||||||
|
|
||||||
return playlistItem;
|
return playlistItem;
|
||||||
|
@ -179,7 +180,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
if (transcodingProfile != null)
|
if (transcodingProfile != null)
|
||||||
{
|
{
|
||||||
playlistItem.IsDirectStream = false;
|
playlistItem.PlayMethod = PlayMethod.Transcode;
|
||||||
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
||||||
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
|
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
|
||||||
playlistItem.Container = transcodingProfile.Container;
|
playlistItem.Container = transcodingProfile.Container;
|
||||||
|
@ -252,12 +253,12 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
if (directPlay != null)
|
if (directPlay != null)
|
||||||
{
|
{
|
||||||
playlistItem.IsDirectStream = true;
|
playlistItem.PlayMethod = PlayMethod.DirectStream;
|
||||||
playlistItem.Container = item.Container;
|
playlistItem.Container = item.Container;
|
||||||
|
|
||||||
if (subtitleStream != null)
|
if (subtitleStream != null)
|
||||||
{
|
{
|
||||||
playlistItem.SubtitleDeliveryMethod = GetDirectStreamSubtitleDeliveryMethod(subtitleStream, options);
|
playlistItem.SubtitleDeliveryMethod = GetSubtitleDeliveryMethod(subtitleStream, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return playlistItem;
|
return playlistItem;
|
||||||
|
@ -279,10 +280,10 @@ namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
if (subtitleStream != null)
|
if (subtitleStream != null)
|
||||||
{
|
{
|
||||||
playlistItem.SubtitleDeliveryMethod = GetTranscodedSubtitleDeliveryMethod(subtitleStream, options);
|
playlistItem.SubtitleDeliveryMethod = GetSubtitleDeliveryMethod(subtitleStream, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
playlistItem.IsDirectStream = false;
|
playlistItem.PlayMethod = PlayMethod.Transcode;
|
||||||
playlistItem.Container = transcodingProfile.Container;
|
playlistItem.Container = transcodingProfile.Container;
|
||||||
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
|
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
|
||||||
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
||||||
|
@ -499,9 +500,9 @@ namespace MediaBrowser.Model.Dlna
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SubtitleDeliveryMethod subtitleMethod = GetDirectStreamSubtitleDeliveryMethod(subtitleStream, options);
|
SubtitleDeliveryMethod subtitleMethod = GetSubtitleDeliveryMethod(subtitleStream, options);
|
||||||
|
|
||||||
if (subtitleMethod != SubtitleDeliveryMethod.External && subtitleMethod != SubtitleDeliveryMethod.Direct)
|
if (subtitleMethod != SubtitleDeliveryMethod.External && subtitleMethod != SubtitleDeliveryMethod.Embed)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -510,41 +511,14 @@ namespace MediaBrowser.Model.Dlna
|
||||||
return IsAudioEligibleForDirectPlay(item, maxBitrate);
|
return IsAudioEligibleForDirectPlay(item, maxBitrate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SubtitleDeliveryMethod GetDirectStreamSubtitleDeliveryMethod(MediaStream subtitleStream,
|
private SubtitleDeliveryMethod GetSubtitleDeliveryMethod(MediaStream subtitleStream,
|
||||||
VideoOptions options)
|
|
||||||
{
|
|
||||||
if (subtitleStream.IsTextSubtitleStream)
|
|
||||||
{
|
|
||||||
string subtitleFormat = NormalizeSubtitleFormat(subtitleStream.Codec);
|
|
||||||
|
|
||||||
bool supportsDirect = ContainsSubtitleFormat(options.Profile.SoftSubtitleProfiles, new[] { subtitleFormat });
|
|
||||||
|
|
||||||
if (supportsDirect)
|
|
||||||
{
|
|
||||||
return SubtitleDeliveryMethod.Direct;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if the device can retrieve the subtitles externally
|
|
||||||
bool supportsSubsExternally = options.Context == EncodingContext.Streaming &&
|
|
||||||
ContainsSubtitleFormat(options.Profile.ExternalSubtitleProfiles, _serverTextSubtitleOutputs);
|
|
||||||
|
|
||||||
if (supportsSubsExternally)
|
|
||||||
{
|
|
||||||
return SubtitleDeliveryMethod.External;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SubtitleDeliveryMethod.Encode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SubtitleDeliveryMethod GetTranscodedSubtitleDeliveryMethod(MediaStream subtitleStream,
|
|
||||||
VideoOptions options)
|
VideoOptions options)
|
||||||
{
|
{
|
||||||
if (subtitleStream.IsTextSubtitleStream)
|
if (subtitleStream.IsTextSubtitleStream)
|
||||||
{
|
{
|
||||||
// See if the device can retrieve the subtitles externally
|
// See if the device can retrieve the subtitles externally
|
||||||
bool supportsSubsExternally = options.Context == EncodingContext.Streaming &&
|
bool supportsSubsExternally = options.Context == EncodingContext.Streaming &&
|
||||||
ContainsSubtitleFormat(options.Profile.ExternalSubtitleProfiles, _serverTextSubtitleOutputs);
|
ContainsSubtitleFormat(options.Profile.SubtitleProfiles, SubtitleDeliveryMethod.External, _serverTextSubtitleOutputs);
|
||||||
|
|
||||||
if (supportsSubsExternally)
|
if (supportsSubsExternally)
|
||||||
{
|
{
|
||||||
|
@ -552,7 +526,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if the device can retrieve the subtitles externally
|
// See if the device can retrieve the subtitles externally
|
||||||
bool supportsEmbedded = ContainsSubtitleFormat(options.Profile.SoftSubtitleProfiles, _serverTextSubtitleOutputs);
|
bool supportsEmbedded = ContainsSubtitleFormat(options.Profile.SubtitleProfiles, SubtitleDeliveryMethod.Embed, _serverTextSubtitleOutputs);
|
||||||
|
|
||||||
if (supportsEmbedded)
|
if (supportsEmbedded)
|
||||||
{
|
{
|
||||||
|
@ -573,11 +547,11 @@ namespace MediaBrowser.Model.Dlna
|
||||||
return codec;
|
return codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ContainsSubtitleFormat(SubtitleProfile[] profiles, string[] formats)
|
private bool ContainsSubtitleFormat(SubtitleProfile[] profiles, SubtitleDeliveryMethod method, string[] formats)
|
||||||
{
|
{
|
||||||
foreach (SubtitleProfile profile in profiles)
|
foreach (SubtitleProfile profile in profiles)
|
||||||
{
|
{
|
||||||
if (ListHelper.ContainsIgnoreCase(formats, profile.Format))
|
if (method == profile.Method && ListHelper.ContainsIgnoreCase(formats, profile.Format))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Extensions;
|
using MediaBrowser.Model.Extensions;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
public string ItemId { get; set; }
|
public string ItemId { get; set; }
|
||||||
|
|
||||||
public bool IsDirectStream { get; set; }
|
public PlayMethod PlayMethod { get; set; }
|
||||||
|
|
||||||
public DlnaProfileType MediaType { get; set; }
|
public DlnaProfileType MediaType { get; set; }
|
||||||
|
|
||||||
|
@ -59,6 +60,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
public MediaSourceInfo MediaSource { get; set; }
|
public MediaSourceInfo MediaSource { get; set; }
|
||||||
|
|
||||||
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
|
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
|
||||||
|
public string SubtitleFormat { get; set; }
|
||||||
|
|
||||||
public string MediaSourceId
|
public string MediaSourceId
|
||||||
{
|
{
|
||||||
|
@ -68,6 +70,11 @@ namespace MediaBrowser.Model.Dlna
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsDirectStream
|
||||||
|
{
|
||||||
|
get { return PlayMethod == PlayMethod.DirectStream; }
|
||||||
|
}
|
||||||
|
|
||||||
public string ToUrl(string baseUrl)
|
public string ToUrl(string baseUrl)
|
||||||
{
|
{
|
||||||
return ToDlnaUrl(baseUrl);
|
return ToDlnaUrl(baseUrl);
|
||||||
|
@ -124,6 +131,55 @@ namespace MediaBrowser.Model.Dlna
|
||||||
return string.Format("Params={0}", string.Join(";", list.ToArray()));
|
return string.Format("Params={0}", string.Join(";", list.ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<SubtitleStreamInfo> GetExternalSubtitles(string baseUrl)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(baseUrl))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SubtitleStreamInfo> list = new List<SubtitleStreamInfo>();
|
||||||
|
|
||||||
|
if (SubtitleDeliveryMethod != SubtitleDeliveryMethod.External)
|
||||||
|
{
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SubtitleStreamIndex.HasValue)
|
||||||
|
{
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HLS will preserve timestamps so we can just grab the full subtitle stream
|
||||||
|
long startPositionTicks = StringHelper.EqualsIgnoreCase(Protocol, "hls")
|
||||||
|
? 0
|
||||||
|
: StartPositionTicks;
|
||||||
|
|
||||||
|
string url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
|
||||||
|
baseUrl,
|
||||||
|
ItemId,
|
||||||
|
MediaSourceId,
|
||||||
|
StringHelper.ToStringCultureInvariant(SubtitleStreamIndex.Value),
|
||||||
|
StringHelper.ToStringCultureInvariant(startPositionTicks),
|
||||||
|
SubtitleFormat);
|
||||||
|
|
||||||
|
foreach (MediaStream stream in MediaSource.MediaStreams)
|
||||||
|
{
|
||||||
|
if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
|
||||||
|
{
|
||||||
|
list.Add(new SubtitleStreamInfo
|
||||||
|
{
|
||||||
|
Url = url,
|
||||||
|
IsForced = stream.IsForced,
|
||||||
|
Language = stream.Language,
|
||||||
|
Name = stream.Language ?? "Unknown"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the audio stream that will be used
|
/// Returns the audio stream that will be used
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -137,7 +193,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
foreach (MediaStream i in MediaSource.MediaStreams)
|
foreach (MediaStream i in MediaSource.MediaStreams)
|
||||||
{
|
{
|
||||||
if (i.Index == AudioStreamIndex.Value && i.Type == MediaStreamType.Audio)
|
if (i.Index == AudioStreamIndex.Value && i.Type == MediaStreamType.Audio)
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -437,16 +493,24 @@ namespace MediaBrowser.Model.Dlna
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Encode = 0,
|
Encode = 0,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal format is supported natively
|
|
||||||
/// </summary>
|
|
||||||
Direct = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// The embed
|
/// The embed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Embed = 2,
|
Embed = 1,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The external
|
/// The external
|
||||||
/// </summary>
|
/// </summary>
|
||||||
External = 3
|
External = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// The HLS
|
||||||
|
/// </summary>
|
||||||
|
Hls = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubtitleStreamInfo
|
||||||
|
{
|
||||||
|
public string Url { get; set; }
|
||||||
|
public string Language { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public bool IsForced { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
public class SubtitleProfile
|
public class SubtitleProfile
|
||||||
{
|
{
|
||||||
|
[XmlAttribute("format")]
|
||||||
public string Format { get; set; }
|
public string Format { get; set; }
|
||||||
|
|
||||||
|
[XmlAttribute("protocol")]
|
||||||
|
public string Protocol { get; set; }
|
||||||
|
|
||||||
|
[XmlAttribute("method")]
|
||||||
|
public SubtitleDeliveryMethod Method { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -525,6 +525,12 @@ namespace MediaBrowser.Model.Dto
|
||||||
return IsType(type.Name);
|
return IsType(type.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether [supports playlists].
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if [supports playlists]; otherwise, <c>false</c>.</value>
|
||||||
|
public bool SupportsPlaylists { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the specified type is type.
|
/// Determines whether the specified type is type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -631,12 +637,6 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// <value>The type of the media.</value>
|
/// <value>The type of the media.</value>
|
||||||
public string MediaType { get; set; }
|
public string MediaType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the overview HTML.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The overview HTML.</value>
|
|
||||||
public string OverviewHtml { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the end date.
|
/// Gets or sets the end date.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -8,5 +8,6 @@
|
||||||
public const string VTT = "vtt";
|
public const string VTT = "vtt";
|
||||||
public const string SUB = "sub";
|
public const string SUB = "sub";
|
||||||
public const string SMI = "smi";
|
public const string SMI = "smi";
|
||||||
|
public const string TTML = "ttml";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -147,6 +147,7 @@
|
||||||
<Compile Include="Photos\PhotoHelper.cs" />
|
<Compile Include="Photos\PhotoHelper.cs" />
|
||||||
<Compile Include="Photos\PhotoMetadataService.cs" />
|
<Compile Include="Photos\PhotoMetadataService.cs" />
|
||||||
<Compile Include="Photos\PhotoProvider.cs" />
|
<Compile Include="Photos\PhotoProvider.cs" />
|
||||||
|
<Compile Include="Playlists\PlaylistMetadataService.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Manager\ProviderUtils.cs" />
|
<Compile Include="Manager\ProviderUtils.cs" />
|
||||||
<Compile Include="Studios\StudiosImageProvider.cs" />
|
<Compile Include="Studios\StudiosImageProvider.cs" />
|
||||||
|
|
47
MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
Normal file
47
MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Controller.Playlists;
|
||||||
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Providers.Manager;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Providers.Playlists
|
||||||
|
{
|
||||||
|
class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo>
|
||||||
|
{
|
||||||
|
public PlaylistMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem)
|
||||||
|
: base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Merges the specified source.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The source.</param>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <param name="lockedFields">The locked fields.</param>
|
||||||
|
/// <param name="replaceData">if set to <c>true</c> [replace data].</param>
|
||||||
|
/// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
|
||||||
|
protected override void MergeData(Playlist source, Playlist target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
|
||||||
|
{
|
||||||
|
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
|
||||||
|
|
||||||
|
if (replaceData || string.IsNullOrEmpty(target.PlaylistMediaType))
|
||||||
|
{
|
||||||
|
target.PlaylistMediaType = source.PlaylistMediaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replaceData || string.IsNullOrEmpty(target.OwnerUserId))
|
||||||
|
{
|
||||||
|
target.OwnerUserId = source.OwnerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergeMetadataSettings)
|
||||||
|
{
|
||||||
|
target.LinkedChildren = source.LinkedChildren;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -112,6 +112,8 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
|
|
||||||
var dto = new BaseItemDto();
|
var dto = new BaseItemDto();
|
||||||
|
|
||||||
|
dto.SupportsPlaylists = item.SupportsAddingToPlaylist;
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.People))
|
if (fields.Contains(ItemFields.People))
|
||||||
{
|
{
|
||||||
AttachPeople(dto, item);
|
AttachPeople(dto, item);
|
||||||
|
@ -849,17 +851,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.Overview))
|
if (fields.Contains(ItemFields.Overview))
|
||||||
{
|
{
|
||||||
// TODO: Remove this after a while, since it's been moved to the providers
|
dto.Overview = item.Overview;
|
||||||
if (item is MusicArtist)
|
|
||||||
{
|
|
||||||
var strippedOverview = string.IsNullOrEmpty(item.Overview) ? item.Overview : item.Overview.StripHtml();
|
|
||||||
|
|
||||||
dto.Overview = strippedOverview;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dto.Overview = item.Overview;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.ShortOverview))
|
if (fields.Contains(ItemFields.ShortOverview))
|
||||||
|
|
|
@ -12,7 +12,6 @@ using MediaBrowser.Controller.Localization;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Controller.Sorting;
|
using MediaBrowser.Controller.Sorting;
|
||||||
using MediaBrowser.Model.Configuration;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
|
@ -551,24 +550,28 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(info.Path))
|
|
||||||
{
|
|
||||||
item.Path = info.Path;
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(info.Url))
|
|
||||||
{
|
|
||||||
item.Path = info.Url;
|
|
||||||
}
|
|
||||||
|
|
||||||
isNew = true;
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.RecordingInfo = info;
|
item.RecordingInfo = info;
|
||||||
item.ServiceName = serviceName;
|
item.ServiceName = serviceName;
|
||||||
|
|
||||||
|
var originalPath = item.Path;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(info.Path))
|
||||||
|
{
|
||||||
|
item.Path = info.Path;
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(info.Url))
|
||||||
|
{
|
||||||
|
item.Path = info.Url;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathChanged = !string.Equals(originalPath, item.Path);
|
||||||
|
|
||||||
await item.RefreshMetadata(new MetadataRefreshOptions
|
await item.RefreshMetadata(new MetadataRefreshOptions
|
||||||
{
|
{
|
||||||
ForceSave = isNew
|
ForceSave = isNew || pathChanged
|
||||||
|
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
|
|
|
@ -334,5 +334,6 @@
|
||||||
"OptionNewPlaylist": "New playlist...",
|
"OptionNewPlaylist": "New playlist...",
|
||||||
"MessageAddedToPlaylistSuccess": "Ok",
|
"MessageAddedToPlaylistSuccess": "Ok",
|
||||||
"ButtonViewSeriesRecording": "View series recording",
|
"ButtonViewSeriesRecording": "View series recording",
|
||||||
"ValueOriginalAirDate": "Original air date: {0}"
|
"ValueOriginalAirDate": "Original air date: {0}",
|
||||||
|
"ButtonRemoveFromPlaylist": "Remove from playlist"
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,9 +190,29 @@ namespace MediaBrowser.Server.Implementations.Playlists
|
||||||
}, CancellationToken.None).ConfigureAwait(false);
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task RemoveFromPlaylist(string playlistId, IEnumerable<int> indeces)
|
public async Task RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
|
||||||
|
|
||||||
|
if (playlist == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("No Playlist exists with the supplied Id");
|
||||||
|
}
|
||||||
|
|
||||||
|
var children = playlist.LinkedChildren.ToList();
|
||||||
|
|
||||||
|
var idList = entryIds.ToList();
|
||||||
|
|
||||||
|
var removals = children.Where(i => idList.Contains(i.Id));
|
||||||
|
|
||||||
|
playlist.LinkedChildren = children.Except(removals)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
await playlist.RefreshMetadata(new MetadataRefreshOptions
|
||||||
|
{
|
||||||
|
ForceSave = true
|
||||||
|
}, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Folder GetPlaylistsFolder(string userId)
|
public Folder GetPlaylistsFolder(string userId)
|
||||||
|
|
|
@ -124,7 +124,7 @@ namespace MediaBrowser.Server.Implementations.Udp
|
||||||
{
|
{
|
||||||
Address = serverAddress,
|
Address = serverAddress,
|
||||||
Id = _appHost.ServerId,
|
Id = _appHost.ServerId,
|
||||||
Name = _appHost.Name
|
Name = _appHost.FriendlyName
|
||||||
};
|
};
|
||||||
|
|
||||||
await SendAsync(Encoding.UTF8.GetBytes(_json.SerializeToString(response)), endpoint);
|
await SendAsync(Encoding.UTF8.GetBytes(_json.SerializeToString(response)), endpoint);
|
||||||
|
|
|
@ -1058,10 +1058,20 @@ namespace MediaBrowser.ServerApplication
|
||||||
SupportsAutoRunAtStartup = SupportsAutoRunAtStartup,
|
SupportsAutoRunAtStartup = SupportsAutoRunAtStartup,
|
||||||
TranscodingTempPath = ApplicationPaths.TranscodingTempPath,
|
TranscodingTempPath = ApplicationPaths.TranscodingTempPath,
|
||||||
IsRunningAsService = IsRunningAsService,
|
IsRunningAsService = IsRunningAsService,
|
||||||
ServerName = string.IsNullOrWhiteSpace(ServerConfigurationManager.Configuration.ServerName) ? Environment.MachineName : ServerConfigurationManager.Configuration.ServerName
|
ServerName = FriendlyName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string FriendlyName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(ServerConfigurationManager.Configuration.ServerName)
|
||||||
|
? Environment.MachineName
|
||||||
|
: ServerConfigurationManager.Configuration.ServerName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int HttpServerPort
|
public int HttpServerPort
|
||||||
{
|
{
|
||||||
get { return ServerConfigurationManager.Configuration.HttpServerPortNumber; }
|
get { return ServerConfigurationManager.Configuration.HttpServerPortNumber; }
|
||||||
|
|
|
@ -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.422</version>
|
<version>3.0.423</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 Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
|
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
|
||||||
<copyright>Copyright © Media Browser 2013</copyright>
|
<copyright>Copyright © Media Browser 2013</copyright>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency id="MediaBrowser.Common" version="3.0.422" />
|
<dependency id="MediaBrowser.Common" version="3.0.423" />
|
||||||
<dependency id="NLog" version="3.1.0.0" />
|
<dependency id="NLog" version="3.1.0.0" />
|
||||||
<dependency id="SimpleInjector" version="2.5.2" />
|
<dependency id="SimpleInjector" version="2.5.2" />
|
||||||
<dependency id="sharpcompress" version="0.10.2" />
|
<dependency id="sharpcompress" version="0.10.2" />
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MediaBrowser.Common</id>
|
<id>MediaBrowser.Common</id>
|
||||||
<version>3.0.422</version>
|
<version>3.0.423</version>
|
||||||
<title>MediaBrowser.Common</title>
|
<title>MediaBrowser.Common</title>
|
||||||
<authors>Media Browser Team</authors>
|
<authors>Media Browser Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
|
|
|
@ -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.422</version>
|
<version>3.0.423</version>
|
||||||
<title>MediaBrowser.Model - Signed Edition</title>
|
<title>MediaBrowser.Model - Signed Edition</title>
|
||||||
<authors>Media Browser Team</authors>
|
<authors>Media Browser Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
|
|
|
@ -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.422</version>
|
<version>3.0.423</version>
|
||||||
<title>Media Browser.Server.Core</title>
|
<title>Media Browser.Server.Core</title>
|
||||||
<authors>Media Browser Team</authors>
|
<authors>Media Browser 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 Media Browser Server.</description>
|
<description>Contains core components required to build plugins for Media Browser Server.</description>
|
||||||
<copyright>Copyright © Media Browser 2013</copyright>
|
<copyright>Copyright © Media Browser 2013</copyright>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency id="MediaBrowser.Common" version="3.0.422" />
|
<dependency id="MediaBrowser.Common" version="3.0.423" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
<files>
|
<files>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user