support track selection before playback

pull/1154/head
Luke Pulverenti 7 years ago
parent b9c1f61681
commit 5cb7469028

@ -554,7 +554,6 @@ namespace Emby.Dlna
var list = new List<DeviceProfile> var list = new List<DeviceProfile>
{ {
new SamsungSmartTvProfile(), new SamsungSmartTvProfile(),
new Xbox360Profile(),
new XboxOneProfile(), new XboxOneProfile(),
new SonyPs3Profile(), new SonyPs3Profile(),
new SonyPs4Profile(), new SonyPs4Profile(),

@ -105,7 +105,6 @@
<Compile Include="Profiles\SonyPs3Profile.cs" /> <Compile Include="Profiles\SonyPs3Profile.cs" />
<Compile Include="Profiles\SonyPs4Profile.cs" /> <Compile Include="Profiles\SonyPs4Profile.cs" />
<Compile Include="Profiles\WdtvLiveProfile.cs" /> <Compile Include="Profiles\WdtvLiveProfile.cs" />
<Compile Include="Profiles\Xbox360Profile.cs" />
<Compile Include="Profiles\XboxOneProfile.cs" /> <Compile Include="Profiles\XboxOneProfile.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Server\DescriptionXmlBuilder.cs" /> <Compile Include="Server\DescriptionXmlBuilder.cs" />
@ -175,7 +174,6 @@
<EmbeddedResource Include="Profiles\Xml\Sony PlayStation 3.xml" /> <EmbeddedResource Include="Profiles\Xml\Sony PlayStation 3.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony PlayStation 4.xml" /> <EmbeddedResource Include="Profiles\Xml\Sony PlayStation 4.xml" />
<EmbeddedResource Include="Profiles\Xml\WDTV Live.xml" /> <EmbeddedResource Include="Profiles\Xml\WDTV Live.xml" />
<EmbeddedResource Include="Profiles\Xml\Xbox 360.xml" />
<EmbeddedResource Include="Profiles\Xml\Xbox One.xml" /> <EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -65,13 +65,15 @@ namespace Emby.Dlna.Profiles
{ {
new DirectPlayProfile new DirectPlayProfile
{ {
Container = "m4v,mpegts,ts,3gp,mov,xvid,vob,mkv,wmv,asf,ogm,ogv,m2v,avi,mpg,mpeg,mp4,webm,wtv,m2ts,dvr-ms", // play all
Container = "",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },
new DirectPlayProfile new DirectPlayProfile
{ {
Container = "aac,mp3,mpa,wav,wma,mp2,ogg,oga,webma,ape,opus,flac,m4a", // play all
Container = "",
Type = DlnaProfileType.Audio Type = DlnaProfileType.Audio
} }
}; };

@ -1,326 +0,0 @@
using MediaBrowser.Model.Dlna;
using System.Xml.Serialization;
namespace Emby.Dlna.Profiles
{
/// <summary>
/// Good info on xbox 360 requirements: https://code.google.com/p/jems/wiki/XBox360Notes
/// </summary>
[XmlRoot("Profile")]
public class Xbox360Profile : DefaultProfile
{
public Xbox360Profile()
{
Name = "Xbox 360";
// Required according to above
ModelName = "Windows Media Player Sharing";
ModelNumber = "12.0";
FriendlyName = "${HostName}: 1";
ModelUrl = "http://go.microsoft.com/fwlink/?LinkId=105926";
Manufacturer = "Microsoft Corporation";
ManufacturerUrl = "http://www.microsoft.com";
XDlnaDoc = "DMS-1.50";
ModelDescription = "Emby : UPnP Media Server";
TimelineOffsetSeconds = 40;
RequiresPlainFolders = true;
RequiresPlainVideoItems = true;
EnableMSMediaReceiverRegistrar = true;
Identification = new DeviceIdentification
{
ModelName = "Xbox 360",
Headers = new[]
{
new HttpHeaderInfo {Name = "User-Agent", Value = "Xbox", Match = HeaderMatchType.Substring},
new HttpHeaderInfo {Name = "User-Agent", Value = "Xenon", Match = HeaderMatchType.Substring}
}
};
TranscodingProfiles = new[]
{
new TranscodingProfile
{
Container = "mp3",
AudioCodec = "mp3",
Type = DlnaProfileType.Audio
},
new TranscodingProfile
{
Container = "asf",
VideoCodec = "wmv2",
AudioCodec = "wmav2",
Type = DlnaProfileType.Video,
TranscodeSeekInfo = TranscodeSeekInfo.Bytes,
EstimateContentLength = true
},
new TranscodingProfile
{
Container = "jpeg",
Type = DlnaProfileType.Photo
}
};
DirectPlayProfiles = new[]
{
new DirectPlayProfile
{
Container = "avi",
VideoCodec = "mpeg4",
AudioCodec = "ac3,mp3",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
{
Container = "avi",
VideoCodec = "h264",
AudioCodec = "aac",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
{
Container = "mp4,mov",
VideoCodec = "h264,mpeg4",
AudioCodec = "aac,ac3",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
{
Container = "asf",
VideoCodec = "wmv2,wmv3,vc1",
AudioCodec = "wmav2,wmapro",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
{
Container = "asf",
AudioCodec = "wmav2,wmapro,wmavoice",
Type = DlnaProfileType.Audio
},
new DirectPlayProfile
{
Container = "mp3",
AudioCodec = "mp3",
Type = DlnaProfileType.Audio
},
new DirectPlayProfile
{
Container = "jpeg",
Type = DlnaProfileType.Photo
}
};
ResponseProfiles = new[]
{
new ResponseProfile
{
Container = "avi",
MimeType = "video/avi",
Type = DlnaProfileType.Video
}
};
ContainerProfiles = new[]
{
new ContainerProfile
{
Type = DlnaProfileType.Video,
Container = "mp4,mov",
Conditions = new []
{
new ProfileCondition
{
Condition = ProfileConditionType.Equals,
Property = ProfileConditionValue.Has64BitOffsets,
Value = "false",
IsRequired = false
}
}
},
new ContainerProfile
{
Type = DlnaProfileType.Photo,
Conditions = new []
{
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.Width,
Value = "1920"
},
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.Height,
Value = "1080"
}
}
}
};
CodecProfiles = new[]
{
new CodecProfile
{
Type = CodecType.Video,
Codec = "mpeg4",
Conditions = new []
{
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.Width,
Value = "1280"
},
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.Height,
Value = "720"
},
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.VideoFramerate,
Value = "30",
IsRequired = false
},
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.VideoBitrate,
Value = "5120000",
IsRequired = false
}
}
},
new CodecProfile
{
Type = CodecType.Video,
Codec = "h264",
Conditions = new []
{
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.Width,
Value = "1920"
},
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.Height,
Value = "1080"
},
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.VideoLevel,
Value = "41",
IsRequired = false
},
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.VideoBitrate,
Value = "10240000",
IsRequired = false
}
}
},
new CodecProfile
{
Type = CodecType.Video,
Codec = "wmv2,wmv3,vc1",
Conditions = new []
{
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.Width,
Value = "1920"
},
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.Height,
Value = "1080"
},
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.VideoFramerate,
Value = "30",
IsRequired = false
},
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.VideoBitrate,
Value = "15360000",
IsRequired = false
}
}
},
new CodecProfile
{
Type = CodecType.VideoAudio,
Codec = "ac3,wmav2,wmapro",
Conditions = new []
{
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.AudioChannels,
Value = "6",
IsRequired = false
}
}
},
new CodecProfile
{
Type = CodecType.VideoAudio,
Codec = "aac",
Conditions = new []
{
new ProfileCondition
{
Condition = ProfileConditionType.LessThanEqual,
Property = ProfileConditionValue.AudioChannels,
Value = "2",
IsRequired = false
},
new ProfileCondition
{
Condition = ProfileConditionType.Equals,
Property = ProfileConditionValue.AudioProfile,
Value = "lc",
IsRequired = false
}
}
}
};
SubtitleProfiles = new[]
{
new SubtitleProfile
{
Format = "srt",
Method = SubtitleDeliveryMethod.Embed
}
};
}
}
}

@ -60,7 +60,7 @@ namespace Emby.Dlna.Profiles
new DirectPlayProfile new DirectPlayProfile
{ {
Container = "ts", Container = "ts",
VideoCodec = "h264,mpeg2video", VideoCodec = "h264,mpeg2video,hevc",
AudioCodec = "ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },
@ -81,7 +81,7 @@ namespace Emby.Dlna.Profiles
new DirectPlayProfile new DirectPlayProfile
{ {
Container = "mp4,mov,mkv,m4v", Container = "mp4,mov,mkv,m4v",
VideoCodec = "h264,mpeg4,mpeg2video", VideoCodec = "h264,mpeg4,mpeg2video,hevc",
AudioCodec = "aac,ac3", AudioCodec = "aac,ac3",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },

@ -29,8 +29,8 @@
<IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests> <IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
<XmlRootAttributes /> <XmlRootAttributes />
<DirectPlayProfiles> <DirectPlayProfiles>
<DirectPlayProfile container="m4v,mpegts,ts,3gp,mov,xvid,vob,mkv,wmv,asf,ogm,ogv,m2v,avi,mpg,mpeg,mp4,webm,wtv,m2ts,dvr-ms" type="Video" /> <DirectPlayProfile container="" type="Video" />
<DirectPlayProfile container="aac,mp3,mpa,wav,wma,mp2,ogg,oga,webma,ape,opus,flac,m4a" type="Audio" /> <DirectPlayProfile container="" type="Audio" />
</DirectPlayProfiles> </DirectPlayProfiles>
<TranscodingProfiles> <TranscodingProfiles>
<TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" /> <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />

File diff suppressed because one or more lines are too long

@ -36,10 +36,10 @@
<IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests> <IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
<XmlRootAttributes /> <XmlRootAttributes />
<DirectPlayProfiles> <DirectPlayProfiles>
<DirectPlayProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264,mpeg2video" type="Video" /> <DirectPlayProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264,mpeg2video,hevc" type="Video" />
<DirectPlayProfile container="avi" audioCodec="ac3,mp3" videoCodec="mpeg4" type="Video" /> <DirectPlayProfile container="avi" audioCodec="ac3,mp3" videoCodec="mpeg4" type="Video" />
<DirectPlayProfile container="avi" audioCodec="aac" videoCodec="h264" type="Video" /> <DirectPlayProfile container="avi" audioCodec="aac" videoCodec="h264" type="Video" />
<DirectPlayProfile container="mp4,mov,mkv,m4v" audioCodec="aac,ac3" videoCodec="h264,mpeg4,mpeg2video" type="Video" /> <DirectPlayProfile container="mp4,mov,mkv,m4v" audioCodec="aac,ac3" videoCodec="h264,mpeg4,mpeg2video,hevc" type="Video" />
<DirectPlayProfile container="asf" audioCodec="wmav2,wmapro" videoCodec="wmv2,wmv3,vc1" type="Video" /> <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro" videoCodec="wmv2,wmv3,vc1" type="Video" />
<DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" type="Audio" /> <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" type="Audio" />
<DirectPlayProfile container="mp3" audioCodec="mp3" type="Audio" /> <DirectPlayProfile container="mp3" audioCodec="mp3" type="Audio" />

@ -996,7 +996,7 @@ namespace Emby.Server.Implementations
NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager); NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
RegisterSingleInstance(NotificationManager); RegisterSingleInstance(NotificationManager);
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager); SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager, ServerConfigurationManager);
RegisterSingleInstance(SubtitleManager); RegisterSingleInstance(SubtitleManager);
RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, SocketFactory, TimerFactory)); RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, SocketFactory, TimerFactory));

@ -296,7 +296,7 @@ namespace Emby.Server.Implementations.Channels
if (requiresCallback != null) if (requiresCallback != null)
{ {
results = await GetChannelItemMediaSourcesInternal(requiresCallback, GetItemExternalId(item), cancellationToken) results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
else else
@ -990,18 +990,6 @@ namespace Emby.Server.Implementations.Channels
return result; return result;
} }
private string GetItemExternalId(BaseItem item)
{
var externalId = item.ExternalId;
if (string.IsNullOrWhiteSpace(externalId))
{
externalId = item.GetProviderId("ProviderExternalId");
}
return externalId;
}
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
private async Task<ChannelItemResult> GetChannelItems(IChannel channel, private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
User user, User user,
@ -1080,7 +1068,7 @@ namespace Emby.Server.Implementations.Channels
{ {
var categoryItem = _libraryManager.GetItemById(new Guid(folderId)); var categoryItem = _libraryManager.GetItemById(new Guid(folderId));
query.FolderId = GetItemExternalId(categoryItem); query.FolderId = categoryItem.ExternalId;
} }
var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false); var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false);

@ -373,6 +373,8 @@ namespace Emby.Server.Implementations.Dto
} }
NormalizeMediaSourceContainers(dto); NormalizeMediaSourceContainers(dto);
dto.SupportsMediaSourceSelection = hasMediaSources.SupportsMediaSourceSelection();
} }
} }

@ -487,14 +487,17 @@ namespace Emby.Server.Implementations.IO
AssertDirectoryExists(dir, path); AssertDirectoryExists(dir, path);
var list = ListFiles(dir, recursive); var list = ListFiles(dir, recursive);
var result = new List<FileSystemMetadata>();
foreach (var file in list) foreach (var file in list)
{ {
if (file.IsDirectory()) if (file.IsDirectory())
{ {
yield return ToMetadata(file); result.Add(ToMetadata(file));
} }
} }
return result;
} }
public IEnumerable<FileSystemMetadata> GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) public IEnumerable<FileSystemMetadata> GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
@ -503,6 +506,7 @@ namespace Emby.Server.Implementations.IO
AssertDirectoryExists(dir, path); AssertDirectoryExists(dir, path);
var list = ListFiles(dir, recursive); var list = ListFiles(dir, recursive);
var result = new List<FileSystemMetadata>();
foreach (var file in list) foreach (var file in list)
{ {
@ -513,10 +517,12 @@ namespace Emby.Server.Implementations.IO
if (extensions == null || extensions.Length == 0 || extensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase)) if (extensions == null || extensions.Length == 0 || extensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{ {
yield return ToMetadata(file); result.Add(ToMetadata(file));
} }
} }
} }
return result;
} }
public IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false) public IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
@ -525,15 +531,19 @@ namespace Emby.Server.Implementations.IO
AssertDirectoryExists(dir, path); AssertDirectoryExists(dir, path);
var list = ListFiles(dir, recursive); var list = ListFiles(dir, recursive);
var result = new List<FileSystemMetadata>();
foreach (var file in list) foreach (var file in list)
{ {
yield return ToMetadata(file); result.Add(ToMetadata(file));
} }
return result;
} }
public IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false) public List<string> GetFileSystemEntryPaths(string path, bool recursive = false)
{ {
var result = new List<string>();
var dir = CreateSmbDirectoryForListFiles(path); var dir = CreateSmbDirectoryForListFiles(path);
AssertDirectoryExists(dir, path); AssertDirectoryExists(dir, path);
@ -541,16 +551,18 @@ namespace Emby.Server.Implementations.IO
foreach (var file in list) foreach (var file in list)
{ {
yield return GetReturnPath(file); result.Add(GetReturnPath(file));
} }
return result;
} }
public IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) public List<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{ {
var dir = CreateSmbDirectoryForListFiles(path); var dir = CreateSmbDirectoryForListFiles(path);
AssertDirectoryExists(dir, path); AssertDirectoryExists(dir, path);
var list = ListFiles(dir, recursive); var list = ListFiles(dir, recursive);
var result = new List<string>();
foreach (var file in list) foreach (var file in list)
{ {
@ -561,44 +573,52 @@ namespace Emby.Server.Implementations.IO
if (extensions == null || extensions.Length == 0 || extensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase)) if (extensions == null || extensions.Length == 0 || extensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{ {
yield return filePath; result.Add(filePath);
} }
} }
} }
return result;
} }
public IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false) public List<string> GetDirectoryPaths(string path, bool recursive = false)
{ {
var dir = CreateSmbDirectoryForListFiles(path); var dir = CreateSmbDirectoryForListFiles(path);
AssertDirectoryExists(dir, path); AssertDirectoryExists(dir, path);
var list = ListFiles(dir, recursive); var list = ListFiles(dir, recursive);
var result = new List<string>();
foreach (var file in list) foreach (var file in list)
{ {
if (file.IsDirectory()) if (file.IsDirectory())
{ {
yield return GetReturnPath(file); result.Add(GetReturnPath(file));
} }
} }
return result;
} }
private IEnumerable<SmbFile> ListFiles(SmbFile dir, bool recursive) private IEnumerable<SmbFile> ListFiles(SmbFile dir, bool recursive)
{ {
var list = dir.ListFiles(); var list = dir.ListFiles();
var result = new List<SmbFile>();
foreach (var file in list) foreach (var file in list)
{ {
yield return file; result.Add(file);
if (recursive && file.IsDirectory()) if (recursive && file.IsDirectory())
{ {
foreach (var subFile in ListFiles(file, recursive)) foreach (var subFile in ListFiles(file, recursive))
{ {
yield return subFile; result.Add(subFile);
} }
} }
} }
return result;
} }
} }
} }

@ -274,7 +274,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
return false; return false;
} }
return FileSystem.GetFilePaths(fullPath).Any(i => string.Equals(Path.GetExtension(i), ".vob", StringComparison.OrdinalIgnoreCase)); return directoryService.GetFilePaths(fullPath).Any(i => string.Equals(Path.GetExtension(i), ".vob", StringComparison.OrdinalIgnoreCase));
} }
/// <summary> /// <summary>

@ -2445,6 +2445,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
existingTimer.Status = RecordingStatus.Cancelled; existingTimer.Status = RecordingStatus.Cancelled;
} }
else if (!existingTimer.IsManual)
{
existingTimer.Status = RecordingStatus.New;
}
if (existingTimer.Status != RecordingStatus.Cancelled) if (existingTimer.Status != RecordingStatus.Cancelled)
{ {

@ -1232,6 +1232,8 @@ namespace Emby.Server.Implementations.LiveTv
var newChannelIdList = new List<Guid>(); var newChannelIdList = new List<Guid>();
var newProgramIdList = new List<Guid>(); var newProgramIdList = new List<Guid>();
var cleanDatabase = true;
foreach (var service in _services) foreach (var service in _services)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -1254,6 +1256,7 @@ namespace Emby.Server.Implementations.LiveTv
} }
catch (Exception ex) catch (Exception ex)
{ {
cleanDatabase = false;
_logger.ErrorException("Error refreshing channels for service", ex); _logger.ErrorException("Error refreshing channels for service", ex);
} }
@ -1264,8 +1267,11 @@ namespace Emby.Server.Implementations.LiveTv
progress.Report(100 * percent); progress.Report(100 * percent);
} }
await CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false); if (cleanDatabase)
await CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false); {
await CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false);
await CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false);
}
var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault(); var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
@ -1291,8 +1297,9 @@ namespace Emby.Server.Implementations.LiveTv
{ {
progress.Report(10); progress.Report(10);
var allChannels = await GetChannels(service, cancellationToken).ConfigureAwait(false); var allChannelsList = (await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false))
var allChannelsList = allChannels.ToList(); .Select(i => new Tuple<string, ChannelInfo>(service.Name, i))
.ToList();
var list = new List<LiveTvChannel>(); var list = new List<LiveTvChannel>();
@ -1507,13 +1514,6 @@ namespace Emby.Server.Implementations.LiveTv
return 7; return 7;
} }
private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
{
var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i));
}
private DateTime _lastRecordingRefreshTime; private DateTime _lastRecordingRefreshTime;
private async Task RefreshRecordings(Guid internalLiveTvFolderId, CancellationToken cancellationToken) private async Task RefreshRecordings(Guid internalLiveTvFolderId, CancellationToken cancellationToken)
{ {

@ -73,9 +73,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
//OpenedMediaSource.SupportsTranscoding = true; //OpenedMediaSource.SupportsTranscoding = true;
} }
public override void Close() protected override void CloseInternal()
{ {
Logger.Info("Closing HDHR live stream");
LiveStreamCancellationTokenSource.Cancel(); LiveStreamCancellationTokenSource.Cancel();
} }
@ -106,7 +105,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
Logger.ErrorException("Error copying live stream.", ex); Logger.ErrorException("Error copying live stream.", ex);
} }
EnableStreamSharing = false;
await DeleteTempFile(TempFilePath).ConfigureAwait(false); await DeleteTempFile(TempFilePath).ConfigureAwait(false);
}); });
} }

@ -105,9 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
await taskCompletionSource.Task.ConfigureAwait(false); await taskCompletionSource.Task.ConfigureAwait(false);
} }
public override void Close() protected override void CloseInternal()
{ {
Logger.Info("Closing HDHR UDP live stream");
LiveStreamCancellationTokenSource.Cancel(); LiveStreamCancellationTokenSource.Cancel();
} }
@ -134,6 +133,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
openTaskCompletionSource.TrySetException(ex); openTaskCompletionSource.TrySetException(ex);
} }
EnableStreamSharing = false;
try try
{ {
await hdHomerunManager.StopStreaming().ConfigureAwait(false); await hdHomerunManager.StopStreaming().ConfigureAwait(false);

@ -52,7 +52,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return Task.FromResult(true); return Task.FromResult(true);
} }
public virtual void Close() public void Close()
{
EnableStreamSharing = false;
Logger.Info("Closing " + GetType().Name);
CloseInternal();
}
protected virtual void CloseInternal()
{ {
} }

@ -1,7 +1,7 @@
{ {
"Latest": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438", "Latest": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438",
"ValueSpecialEpisodeName": "\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u043d\u0438 - {0}", "ValueSpecialEpisodeName": "\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u043d\u0438 - {0}",
"Inherit": "Inherit", "Inherit": "\u041d\u0430\u0441\u043b\u0435\u0434\u044f\u0432\u0430\u043d\u0435",
"Books": "\u041a\u043d\u0438\u0433\u0438", "Books": "\u041a\u043d\u0438\u0433\u0438",
"Music": "\u041c\u0443\u0437\u0438\u043a\u0430", "Music": "\u041c\u0443\u0437\u0438\u043a\u0430",
"Games": "\u0418\u0433\u0440\u0438", "Games": "\u0418\u0433\u0440\u0438",
@ -35,7 +35,7 @@
"UserDownloadingItemWithValues": "{0} is downloading {1}", "UserDownloadingItemWithValues": "{0} is downloading {1}",
"HeaderLiveTV": "\u0422\u0435\u043b\u0435\u0432\u0438\u0437\u0438\u044f \u043d\u0430 \u0436\u0438\u0432\u043e", "HeaderLiveTV": "\u0422\u0435\u043b\u0435\u0432\u0438\u0437\u0438\u044f \u043d\u0430 \u0436\u0438\u0432\u043e",
"ChapterNameValue": "\u0413\u043b\u0430\u0432\u0430 {0}", "ChapterNameValue": "\u0413\u043b\u0430\u0432\u0430 {0}",
"ScheduledTaskFailedWithName": "{0} failed", "ScheduledTaskFailedWithName": "{0} \u0441\u0435 \u043f\u0440\u043e\u0432\u0430\u043b\u0438",
"LabelRunningTimeValue": "Running time: {0}", "LabelRunningTimeValue": "Running time: {0}",
"ScheduledTaskStartedWithName": "{0} \u0437\u0430\u043f\u043e\u0447\u043d\u0430", "ScheduledTaskStartedWithName": "{0} \u0437\u0430\u043f\u043e\u0447\u043d\u0430",
"VersionNumber": "\u0412\u0435\u0440\u0441\u0438\u044f {0}", "VersionNumber": "\u0412\u0435\u0440\u0441\u0438\u044f {0}",
@ -49,40 +49,40 @@
"DeviceOnlineWithName": "{0} \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d", "DeviceOnlineWithName": "{0} \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d",
"UserOnlineFromDevice": "{0} \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f \u043e\u0442 {1}", "UserOnlineFromDevice": "{0} \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f \u043e\u0442 {1}",
"ProviderValue": "\u0414\u043e\u0441\u0442\u0430\u0432\u0447\u0438\u043a: {0}", "ProviderValue": "\u0414\u043e\u0441\u0442\u0430\u0432\u0447\u0438\u043a: {0}",
"SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", "SubtitlesDownloadedForItem": "\u0418\u0437\u0442\u0435\u0433\u043b\u0435\u043d\u0438 \u0441\u0430 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u0438 \u0437\u0430 {0}",
"UserCreatedWithName": "User {0} has been created", "UserCreatedWithName": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f\u0442 {0} \u0435 \u0441\u044a\u0437\u0434\u0430\u0434\u0435\u043d",
"UserPasswordChangedWithName": "Password has been changed for user {0}", "UserPasswordChangedWithName": "\u041f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u043d\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f {0} \u0435 \u043f\u0440\u043e\u043c\u0435\u043d\u0435\u043d\u0430",
"UserDeletedWithName": "User {0} has been deleted", "UserDeletedWithName": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f\u0442 {0} \u0435 \u0438\u0437\u0442\u0440\u0438\u0442",
"UserConfigurationUpdatedWithName": "User configuration has been updated for {0}", "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
"MessageServerConfigurationUpdated": "Server configuration has been updated", "MessageServerConfigurationUpdated": "Server configuration has been updated",
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
"MessageApplicationUpdated": "Emby Server has been updated", "MessageApplicationUpdated": "\u0421\u044a\u0440\u0432\u044a\u0440\u044a\u0442 \u0435 \u043e\u0431\u043d\u043e\u0432\u0435\u043d",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
"AuthenticationSucceededWithUserName": "{0} successfully authenticated", "AuthenticationSucceededWithUserName": "{0} \u0441\u0435 \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e",
"UserOfflineFromDevice": "{0} has disconnected from {1}", "UserOfflineFromDevice": "{0} \u0441\u0435 \u0440\u0430\u0437\u043a\u0430\u0447\u0438 \u043e\u0442 {1}",
"DeviceOfflineWithName": "{0} has disconnected", "DeviceOfflineWithName": "{0} \u0441\u0435 \u0440\u0430\u0437\u043a\u0430\u0447\u0438",
"UserStartedPlayingItemWithValues": "{0} has started playing {1}", "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
"UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}", "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
"NotificationOptionPluginError": "Plugin failure", "NotificationOptionPluginError": "\u0413\u0440\u0435\u0448\u043a\u0430 \u0432 \u043f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430",
"NotificationOptionApplicationUpdateAvailable": "Application update available", "NotificationOptionApplicationUpdateAvailable": "\u041d\u0430\u043b\u0438\u0447\u043d\u043e \u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0430\u0442\u0430",
"NotificationOptionApplicationUpdateInstalled": "Application update installed", "NotificationOptionApplicationUpdateInstalled": "\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u0442\u043e \u043d\u0430 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0430\u0442\u0430 \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u043e",
"NotificationOptionPluginUpdateInstalled": "\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u0442\u043e \u043d\u0430 \u043f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u043e", "NotificationOptionPluginUpdateInstalled": "\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u0442\u043e \u043d\u0430 \u043f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u043e",
"NotificationOptionPluginInstalled": "\u041f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0430", "NotificationOptionPluginInstalled": "\u041f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0430",
"NotificationOptionPluginUninstalled": "\u041f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0434\u0435\u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0430", "NotificationOptionPluginUninstalled": "\u041f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0434\u0435\u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0430",
"NotificationOptionVideoPlayback": "Video playback started", "NotificationOptionVideoPlayback": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0432\u0438\u0434\u0435\u043e \u0437\u0430\u043f\u043e\u0447\u043d\u0430",
"NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlayback": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0437\u0432\u0443\u043a \u0437\u0430\u043f\u043e\u0447\u043d\u0430",
"NotificationOptionGamePlayback": "Game playback started", "NotificationOptionGamePlayback": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0438\u0433\u0440\u0430\u0442\u0430 \u0437\u0430\u043f\u043e\u0447\u043d\u0430",
"NotificationOptionVideoPlaybackStopped": "Video playback stopped", "NotificationOptionVideoPlaybackStopped": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0432\u0438\u0434\u0435\u043e \u0435 \u0441\u043f\u0440\u044f\u043d\u043e",
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionAudioPlaybackStopped": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0437\u0432\u0443\u043a \u0435 \u0441\u043f\u0440\u044f\u043d\u043e",
"NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionGamePlaybackStopped": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0438\u0433\u0440\u0430\u0442\u0430 \u0435 \u0441\u043f\u0440\u044f\u043d\u0430",
"NotificationOptionTaskFailed": "Scheduled task failure", "NotificationOptionTaskFailed": "\u0413\u0440\u0435\u0448\u043a\u0430 \u0432 \u043f\u043b\u0430\u043d\u0438\u0440\u0430\u043d\u0430 \u0437\u0430\u0434\u0430\u0447\u0430",
"NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionInstallationFailed": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0435",
"NotificationOptionNewLibraryContent": "\u0414\u043e\u0431\u0430\u0432\u0435\u043d\u043e \u0435 \u043d\u043e\u0432\u043e \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435", "NotificationOptionNewLibraryContent": "\u0414\u043e\u0431\u0430\u0432\u0435\u043d\u043e \u0435 \u043d\u043e\u0432\u043e \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435",
"NotificationOptionCameraImageUploaded": "Camera image uploaded", "NotificationOptionCameraImageUploaded": "\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0442\u043e \u043e\u0442 \u0444\u043e\u0442\u043e\u0430\u043f\u0430\u0440\u0430\u0442\u0430 \u0435 \u043a\u0430\u0447\u0435\u043d\u043e",
"NotificationOptionUserLockedOut": "User locked out", "NotificationOptionUserLockedOut": "User locked out",
"NotificationOptionServerRestartRequired": "\u041d\u0443\u0436\u043d\u043e \u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u043f\u0443\u0441\u043a\u0430\u043d\u0435 \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430", "NotificationOptionServerRestartRequired": "\u041d\u0443\u0436\u043d\u043e \u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u043f\u0443\u0441\u043a\u0430\u043d\u0435 \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
"UserLockedOutWithName": "User {0} has been locked out", "UserLockedOutWithName": "User {0} has been locked out",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureForItem": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u0437\u0442\u0435\u0433\u043b\u044f\u043d\u0435 \u043d\u0430 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u0438 \u0437\u0430 {0}",
"Sync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0430\u043d\u0435", "Sync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0430\u043d\u0435",
"User": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b", "User": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b",
"System": "\u0421\u0438\u0441\u0442\u0435\u043c\u0430", "System": "\u0421\u0438\u0441\u0442\u0435\u043c\u0430",

@ -2,13 +2,13 @@
"Latest": "Latest", "Latest": "Latest",
"ValueSpecialEpisodeName": "Special - {0}", "ValueSpecialEpisodeName": "Special - {0}",
"Inherit": "Inherit", "Inherit": "Inherit",
"Books": "Books", "Books": "Knihy",
"Music": "Music", "Music": "Hudba",
"Games": "Games", "Games": "Hry",
"Photos": "Photos", "Photos": "Fotky",
"MixedContent": "Mixed content", "MixedContent": "Zmie\u0161an\u00fd obsah",
"MusicVideos": "Music videos", "MusicVideos": "Hudobn\u00e9 vide\u00e1",
"HomeVideos": "Home videos", "HomeVideos": "Dom\u00e1ce vide\u00e1",
"Playlists": "Playlists", "Playlists": "Playlists",
"HeaderRecordingGroups": "Recording Groups", "HeaderRecordingGroups": "Recording Groups",
"HeaderContinueWatching": "Continue Watching", "HeaderContinueWatching": "Continue Watching",
@ -16,20 +16,20 @@
"HeaderFavoriteSongs": "Ob\u013e\u00faben\u00e9 pesni\u010dky", "HeaderFavoriteSongs": "Ob\u013e\u00faben\u00e9 pesni\u010dky",
"HeaderAlbumArtists": "Album Artists", "HeaderAlbumArtists": "Album Artists",
"HeaderFavoriteAlbums": "Favorite Albums", "HeaderFavoriteAlbums": "Favorite Albums",
"HeaderFavoriteEpisodes": "Favorite Episodes", "HeaderFavoriteEpisodes": "Ob\u013e\u00faben\u00e9 epiz\u00f3dy",
"HeaderFavoriteShows": "Ob\u013e\u00faben\u00e9 seri\u00e1ly", "HeaderFavoriteShows": "Ob\u013e\u00faben\u00e9 seri\u00e1ly",
"HeaderNextUp": "Next Up", "HeaderNextUp": "Nasleduje",
"Favorites": "Ob\u013e\u00faben\u00e9", "Favorites": "Ob\u013e\u00faben\u00e9",
"Collections": "Collections", "Collections": "Zbierky",
"Channels": "Channels", "Channels": "Kan\u00e1ly",
"Movies": "Movies", "Movies": "Filmy",
"Albums": "Albums", "Albums": "Albumy",
"Artists": "Artists", "Artists": "Umelci",
"Folders": "Folders", "Folders": "Prie\u010dinky",
"Songs": "Songs", "Songs": "Skladby",
"TvShows": "TV Shows", "TvShows": "TV Shows",
"Shows": "Series", "Shows": "Series",
"Genres": "Genres", "Genres": "\u017d\u00e1nre",
"NameSeasonNumber": "Season {0}", "NameSeasonNumber": "Season {0}",
"AppDeviceValues": "App: {0}, Device: {1}", "AppDeviceValues": "App: {0}, Device: {1}",
"UserDownloadingItemWithValues": "{0} is downloading {1}", "UserDownloadingItemWithValues": "{0} is downloading {1}",
@ -86,6 +86,6 @@
"Sync": "Sync", "Sync": "Sync",
"User": "User", "User": "User",
"System": "System", "System": "System",
"Application": "Application", "Application": "Aplik\u00e1cia",
"Plugin": "Plugin" "Plugin": "Plugin"
} }

@ -27,7 +27,7 @@
"Artists": "Artister", "Artists": "Artister",
"Folders": "Mappar", "Folders": "Mappar",
"Songs": "L\u00e5tar", "Songs": "L\u00e5tar",
"TvShows": "TV Shows", "TvShows": "TV-serier",
"Shows": "Serier", "Shows": "Serier",
"Genres": "Genrer", "Genres": "Genrer",
"NameSeasonNumber": "S\u00e4song {0}", "NameSeasonNumber": "S\u00e4song {0}",

@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Localization
var localizationPath = LocalizationPath; var localizationPath = LocalizationPath;
_fileSystem.CreateDirectory(localizationPath); _fileSystem.CreateDirectory(localizationPath);
var existingFiles = GetRatingsFiles(localizationPath) var existingFiles = GetRatingsFiles(localizationPath)
.Select(Path.GetFileName) .Select(Path.GetFileName)
@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.Localization
} }
} }
} }
foreach (var file in GetRatingsFiles(localizationPath)) foreach (var file in GetRatingsFiles(localizationPath))
{ {
LoadRatings(file); LoadRatings(file);
@ -128,12 +128,20 @@ namespace Emby.Server.Implementations.Localization
return _textLocalizer.NormalizeFormKD(text); return _textLocalizer.NormalizeFormKD(text);
} }
private CultureDto[] _cultures;
/// <summary> /// <summary>
/// Gets the cultures. /// Gets the cultures.
/// </summary> /// </summary>
/// <returns>IEnumerable{CultureDto}.</returns> /// <returns>IEnumerable{CultureDto}.</returns>
public CultureDto[] GetCultures() public CultureDto[] GetCultures()
{ {
var result = _cultures;
if (result != null)
{
return result;
}
var type = GetType(); var type = GetType();
var path = type.Namespace + ".iso6392.txt"; var path = type.Namespace + ".iso6392.txt";
@ -166,10 +174,14 @@ namespace Emby.Server.Implementations.Localization
} }
} }
return list.Where(i => !string.IsNullOrWhiteSpace(i.Name) && result = list.Where(i => !string.IsNullOrWhiteSpace(i.Name) &&
!string.IsNullOrWhiteSpace(i.DisplayName) && !string.IsNullOrWhiteSpace(i.DisplayName) &&
!string.IsNullOrWhiteSpace(i.ThreeLetterISOLanguageName) && !string.IsNullOrWhiteSpace(i.ThreeLetterISOLanguageName) &&
!string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToArray(); !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToArray();
_cultures = result;
return result;
} }
/// <summary> /// <summary>
@ -239,7 +251,7 @@ namespace Emby.Server.Implementations.Localization
/// <returns>Dictionary{System.StringParentalRating}.</returns> /// <returns>Dictionary{System.StringParentalRating}.</returns>
private void LoadRatings(string file) private void LoadRatings(string file)
{ {
var dict = _fileSystem.ReadAllLines(file).Select(i => var dict = _fileSystem.ReadAllLines(file).Select(i =>
{ {
if (!string.IsNullOrWhiteSpace(i)) if (!string.IsNullOrWhiteSpace(i))
{ {
@ -269,7 +281,7 @@ namespace Emby.Server.Implementations.Localization
_allParentalRatings.TryAdd(countryCode, dict); _allParentalRatings.TryAdd(countryCode, dict);
} }
private readonly string[] _unratedValues = {"n/a", "unrated", "not rated"}; private readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
/// <summary> /// <summary>
/// Gets the rating level. /// Gets the rating level.

@ -17,6 +17,7 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
namespace Emby.Server.Implementations.MediaEncoder namespace Emby.Server.Implementations.MediaEncoder
{ {
@ -89,7 +90,7 @@ namespace Emby.Server.Implementations.MediaEncoder
/// </summary> /// </summary>
private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks; private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
public async Task<bool> RefreshChapterImages(Video video, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken) public async Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
{ {
if (!IsEligibleForChapterImageExtraction(video)) if (!IsEligibleForChapterImageExtraction(video))
{ {
@ -101,7 +102,7 @@ namespace Emby.Server.Implementations.MediaEncoder
var runtimeTicks = video.RunTimeTicks ?? 0; var runtimeTicks = video.RunTimeTicks ?? 0;
var currentImages = GetSavedChapterImages(video); var currentImages = GetSavedChapterImages(video, directoryService);
foreach (var chapter in chapters) foreach (var chapter in chapters)
{ {
@ -194,13 +195,13 @@ namespace Emby.Server.Implementations.MediaEncoder
return Path.Combine(GetChapterImagesPath(video), filename); return Path.Combine(GetChapterImagesPath(video), filename);
} }
private List<string> GetSavedChapterImages(Video video) private List<string> GetSavedChapterImages(Video video, IDirectoryService directoryService)
{ {
var path = GetChapterImagesPath(video); var path = GetChapterImagesPath(video);
try try
{ {
return _fileSystem.GetFilePaths(path) return directoryService.GetFilePaths(path)
.ToList(); .ToList();
} }
catch (IOException) catch (IOException)

@ -16,6 +16,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Controller.Providers;
namespace Emby.Server.Implementations.ScheduledTasks namespace Emby.Server.Implementations.ScheduledTasks
{ {
@ -120,6 +121,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
previouslyFailedImages = new List<string>(); previouslyFailedImages = new List<string>();
} }
var directoryService = new DirectoryService(_fileSystem);
foreach (var video in videos) foreach (var video in videos)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -132,7 +135,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
{ {
var chapters = _itemRepo.GetChapters(video.Id); var chapters = _itemRepo.GetChapters(video.Id);
var success = await _encodingManager.RefreshChapterImages(video, chapters, extract, true, CancellationToken.None); var success = await _encodingManager.RefreshChapterImages(video, directoryService, chapters, extract, true, CancellationToken.None);
if (!success) if (!success)
{ {

@ -51,6 +51,11 @@ namespace MediaBrowser.Controller.Entities.Audio
return 1; return 1;
} }
public bool SupportsMediaSourceSelection()
{
return false;
}
[IgnoreDataMember] [IgnoreDataMember]
public override bool SupportsPlayedStatus public override bool SupportsPlayedStatus
{ {

@ -2040,7 +2040,7 @@ namespace MediaBrowser.Controller.Entities
.Where(i => i.IsLocalFile) .Where(i => i.IsLocalFile)
.Select(i => FileSystem.GetDirectoryName(i.Path)) .Select(i => FileSystem.GetDirectoryName(i.Path))
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.SelectMany(i => FileSystem.GetFilePaths(i)) .SelectMany(i => directoryService.GetFilePaths(i))
.ToList(); .ToList();
var deletedImages = ImageInfos var deletedImages = ImageInfos

@ -13,5 +13,6 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns> /// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns>
List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution); List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution);
List<MediaStream> GetMediaStreams(); List<MediaStream> GetMediaStreams();
bool SupportsMediaSourceSelection();
} }
} }

@ -109,6 +109,11 @@ namespace MediaBrowser.Controller.Entities
get { return true; } get { return true; }
} }
public bool SupportsMediaSourceSelection()
{
return SourceType == SourceType.Library;
}
/// <summary> /// <summary>
/// Gets or sets the timestamp. /// Gets or sets the timestamp.
/// </summary> /// </summary>

@ -24,6 +24,11 @@ namespace MediaBrowser.Controller.LiveTv
return list; return list;
} }
public bool SupportsMediaSourceSelection()
{
return false;
}
public override UnratedItem GetBlockUnratedType() public override UnratedItem GetBlockUnratedType()
{ {
return UnratedItem.LiveTvChannel; return UnratedItem.LiveTvChannel;

@ -1325,6 +1325,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue) if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
{ {
videoSizeParam = string.Format("scale={0}:{1}", state.VideoStream.Width.Value.ToString(_usCulture), state.VideoStream.Height.Value.ToString(_usCulture)); videoSizeParam = string.Format("scale={0}:{1}", state.VideoStream.Width.Value.ToString(_usCulture), state.VideoStream.Height.Value.ToString(_usCulture));
videoSizeParam += ":force_original_aspect_ratio=decrease";
} }
var mapPrefix = state.SubtitleStream.IsExternal ? var mapPrefix = state.SubtitleStream.IsExternal ?
@ -1335,7 +1337,7 @@ namespace MediaBrowser.Controller.MediaEncoding
? 0 ? 0
: state.SubtitleStream.Index; : state.SubtitleStream.Index;
return string.Format(" -filter_complex \"[{0}:{1}]{4}[sub] ; [0:{2}] [sub] overlay{3}\"", return string.Format(" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"",
mapPrefix.ToString(_usCulture), mapPrefix.ToString(_usCulture),
subtitleStreamIndex.ToString(_usCulture), subtitleStreamIndex.ToString(_usCulture),
state.VideoStream.Index.ToString(_usCulture), state.VideoStream.Index.ToString(_usCulture),
@ -2094,6 +2096,12 @@ namespace MediaBrowser.Controller.MediaEncoding
args += " -avoid_negative_ts disabled -start_at_zero"; args += " -avoid_negative_ts disabled -start_at_zero";
} }
// This is for internal graphical subs
if (hasGraphicalSubs)
{
args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
}
var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset); var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset);
if (!string.IsNullOrEmpty(qualityParam)) if (!string.IsNullOrEmpty(qualityParam))
@ -2101,12 +2109,6 @@ namespace MediaBrowser.Controller.MediaEncoding
args += " " + qualityParam.Trim(); args += " " + qualityParam.Trim();
} }
// This is for internal graphical subs
if (hasGraphicalSubs)
{
args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
}
if (!state.RunTimeTicks.HasValue) if (!state.RunTimeTicks.HasValue)
{ {
args += " -flags -global_header"; args += " -flags -global_header";

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Controller.MediaEncoding namespace MediaBrowser.Controller.MediaEncoding
{ {
@ -11,6 +12,6 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Refreshes the chapter images. /// Refreshes the chapter images.
/// </summary> /// </summary>
Task<bool> RefreshChapterImages(Video video, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken); Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
} }
} }

@ -14,11 +14,11 @@ namespace MediaBrowser.Controller.Providers
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = private readonly Dictionary<string, FileSystemMetadata[]> _cache = new Dictionary<string, FileSystemMetadata[]>(StringComparer.OrdinalIgnoreCase);
new ConcurrentDictionary<string, FileSystemMetadata[]>(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = private readonly Dictionary<string, FileSystemMetadata> _fileCache = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
new ConcurrentDictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, List<string>> _filePathCache = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
public DirectoryService(ILogger logger, IFileSystem fileSystem) public DirectoryService(ILogger logger, IFileSystem fileSystem)
{ {
@ -32,11 +32,6 @@ namespace MediaBrowser.Controller.Providers
} }
public FileSystemMetadata[] GetFileSystemEntries(string path) public FileSystemMetadata[] GetFileSystemEntries(string path)
{
return GetFileSystemEntries(path, false);
}
private FileSystemMetadata[] GetFileSystemEntries(string path, bool clearCache)
{ {
if (string.IsNullOrWhiteSpace(path)) if (string.IsNullOrWhiteSpace(path))
{ {
@ -45,13 +40,6 @@ namespace MediaBrowser.Controller.Providers
FileSystemMetadata[] entries; FileSystemMetadata[] entries;
if (clearCache)
{
FileSystemMetadata[] removed;
_cache.TryRemove(path, out removed);
}
if (!_cache.TryGetValue(path, out entries)) if (!_cache.TryGetValue(path, out entries))
{ {
//_logger.Debug("Getting files for " + path); //_logger.Debug("Getting files for " + path);
@ -66,21 +54,17 @@ namespace MediaBrowser.Controller.Providers
entries = new FileSystemMetadata[] { }; entries = new FileSystemMetadata[] { };
} }
_cache.TryAdd(path, entries); //_cache.TryAdd(path, entries);
_cache[path] = entries;
} }
return entries; return entries;
} }
public List<FileSystemMetadata> GetFiles(string path) public List<FileSystemMetadata> GetFiles(string path)
{
return GetFiles(path, false);
}
public List<FileSystemMetadata> GetFiles(string path, bool clearCache)
{ {
var list = new List<FileSystemMetadata>(); var list = new List<FileSystemMetadata>();
var items = GetFileSystemEntries(path, clearCache); var items = GetFileSystemEntries(path);
foreach (var item in items) foreach (var item in items)
{ {
if (!item.IsDirectory) if (!item.IsDirectory)
@ -100,7 +84,8 @@ namespace MediaBrowser.Controller.Providers
if (file != null && file.Exists) if (file != null && file.Exists)
{ {
_fileCache.TryAdd(path, file); //_fileCache.TryAdd(path, file);
_fileCache[path] = file;
} }
else else
{ {
@ -111,5 +96,24 @@ namespace MediaBrowser.Controller.Providers
return file; return file;
//return _fileSystem.GetFileInfo(path); //return _fileSystem.GetFileInfo(path);
} }
public List<string> GetFilePaths(string path)
{
return GetFilePaths(path, false);
}
public List<string> GetFilePaths(string path, bool clearCache)
{
List<string> result;
if (clearCache || !_filePathCache.TryGetValue(path, out result))
{
result = _fileSystem.GetFilePaths(path).ToList();
_filePathCache[path] = result;
}
return result;
}
} }
} }

@ -8,5 +8,8 @@ namespace MediaBrowser.Controller.Providers
FileSystemMetadata[] GetFileSystemEntries(string path); FileSystemMetadata[] GetFileSystemEntries(string path);
List<FileSystemMetadata> GetFiles(string path); List<FileSystemMetadata> GetFiles(string path);
FileSystemMetadata GetFile(string path); FileSystemMetadata GetFile(string path);
List<string> GetFilePaths(string path);
List<string> GetFilePaths(string path, bool clearCache);
} }
} }

@ -75,7 +75,8 @@ namespace MediaBrowser.Model.Dto
public bool? CanDownload { get; set; } public bool? CanDownload { get; set; }
public bool? HasSubtitles { get; set; } public bool? HasSubtitles { get; set; }
public bool? SupportsMediaSourceSelection { get; set; }
public string PreferredMetadataLanguage { get; set; } public string PreferredMetadataLanguage { get; set; }
public string PreferredMetadataCountryCode { get; set; } public string PreferredMetadataCountryCode { get; set; }

@ -13,6 +13,7 @@ namespace MediaBrowser.Model.Providers
public bool IsOpenSubtitleVipAccount { get; set; } public bool IsOpenSubtitleVipAccount { get; set; }
public bool RequirePerfectMatch { get; set; } public bool RequirePerfectMatch { get; set; }
public bool SaveSubtitlesInMediaFolders { get; set; }
public SubtitleOptions() public SubtitleOptions()
{ {
@ -20,6 +21,7 @@ namespace MediaBrowser.Model.Providers
SkipIfAudioTrackMatches = true; SkipIfAudioTrackMatches = true;
RequirePerfectMatch = true; RequirePerfectMatch = true;
SaveSubtitlesInMediaFolders = true;
} }
} }
} }

@ -39,6 +39,7 @@
ChannelDown = 31, ChannelDown = 31,
SetMaxStreamingBitrate = 31, SetMaxStreamingBitrate = 31,
Guide = 32, Guide = 32,
ToggleStats = 33 ToggleStats = 33,
PlayMediaSource = 34
} }
} }

@ -27,6 +27,7 @@ namespace MediaBrowser.Providers.Manager
protected readonly IFileSystem FileSystem; protected readonly IFileSystem FileSystem;
protected readonly IUserDataManager UserDataManager; protected readonly IUserDataManager UserDataManager;
protected readonly ILibraryManager LibraryManager; protected readonly ILibraryManager LibraryManager;
private readonly SubtitleResolver _subtitleResolver;
protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager)
{ {
@ -36,6 +37,8 @@ namespace MediaBrowser.Providers.Manager
FileSystem = fileSystem; FileSystem = fileSystem;
UserDataManager = userDataManager; UserDataManager = userDataManager;
LibraryManager = libraryManager; LibraryManager = libraryManager;
_subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager, fileSystem);
} }
public async Task<ItemUpdateType> RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) public async Task<ItemUpdateType> RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
@ -76,8 +79,7 @@ namespace MediaBrowser.Providers.Manager
if (video != null && !video.IsPlaceHolder) if (video != null && !video.IsPlaceHolder)
{ {
requiresRefresh = !video.SubtitleFiles requiresRefresh = !video.SubtitleFiles
.SequenceEqual(SubtitleResolver.GetSubtitleFiles(video, refreshOptions.DirectoryService, FileSystem, false) .SequenceEqual(_subtitleResolver.GetExternalSubtitleFiles(video, refreshOptions.DirectoryService, false), StringComparer.Ordinal);
.OrderBy(i => i), StringComparer.OrdinalIgnoreCase);
} }
} }
} }

@ -229,7 +229,7 @@ namespace MediaBrowser.Providers.MediaInfo
extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan; extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
} }
await _encodingManager.RefreshChapterImages(video, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false); await _encodingManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false);
_chapterManager.SaveChapters(video.Id.ToString(), chapters); _chapterManager.SaveChapters(video.Id.ToString(), chapters);
} }
@ -472,7 +472,7 @@ namespace MediaBrowser.Providers.MediaInfo
var subtitleResolver = new SubtitleResolver(_localization, _fileSystem); var subtitleResolver = new SubtitleResolver(_localization, _fileSystem);
var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1); var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, false).ToList(); var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, false);
var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default || var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
@ -497,7 +497,7 @@ namespace MediaBrowser.Providers.MediaInfo
// Rescan // Rescan
if (downloadedLanguages.Count > 0) if (downloadedLanguages.Count > 0)
{ {
externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, true).ToList(); externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, true);
} }
} }

@ -16,29 +16,91 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private string[] SubtitleExtensions = new[]
{
".srt",
".ssa",
".ass",
".sub",
".smi",
".sami",
".vtt"
};
public SubtitleResolver(ILocalizationManager localization, IFileSystem fileSystem) public SubtitleResolver(ILocalizationManager localization, IFileSystem fileSystem)
{ {
_localization = localization; _localization = localization;
_fileSystem = fileSystem; _fileSystem = fileSystem;
} }
public IEnumerable<MediaStream> GetExternalSubtitleStreams(Video video, public List<MediaStream> GetExternalSubtitleStreams(Video video,
int startIndex, int startIndex,
IDirectoryService directoryService, IDirectoryService directoryService,
bool clearCache) bool clearCache)
{ {
var files = GetSubtitleFiles(video, directoryService, _fileSystem, clearCache);
var streams = new List<MediaStream>(); var streams = new List<MediaStream>();
var videoFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(video.Path); GetExternalSubtitleStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache);
startIndex += streams.Count;
try
{
GetExternalSubtitleStreams(streams, video.GetInternalMetadataPath(), video.Path, startIndex, directoryService, clearCache);
}
catch (IOException)
{
}
return streams;
}
public List<string> GetExternalSubtitleFiles(Video video,
IDirectoryService directoryService,
bool clearCache)
{
var streams = GetExternalSubtitleStreams(video, 0, directoryService, clearCache);
var list = new List<string>();
foreach (var stream in streams)
{
list.Add(stream.Path);
}
return list;
}
private void GetExternalSubtitleStreams(List<MediaStream> streams, string folder,
string videoPath,
int startIndex,
IDirectoryService directoryService,
bool clearCache)
{
var videoFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(videoPath);
videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoFileNameWithoutExtension); videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoFileNameWithoutExtension);
var files = directoryService.GetFilePaths(folder, clearCache);
foreach (var fullName in files) foreach (var fullName in files)
{ {
var extension = Path.GetExtension(fullName);
if (!SubtitleExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
continue;
}
var fileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(fullName); var fileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(fullName);
fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fileNameWithoutExtension); fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fileNameWithoutExtension);
if (!string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) &&
!fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var codec = Path.GetExtension(fullName).ToLower().TrimStart('.'); var codec = Path.GetExtension(fullName).ToLower().TrimStart('.');
if (string.Equals(codec, "txt", StringComparison.OrdinalIgnoreCase)) if (string.Equals(codec, "txt", StringComparison.OrdinalIgnoreCase))
@ -98,8 +160,6 @@ namespace MediaBrowser.Providers.MediaInfo
}); });
} }
} }
return streams;
} }
private string NormalizeFilenameForSubtitleComparison(string filename) private string NormalizeFilenameForSubtitleComparison(string filename)
@ -115,48 +175,5 @@ namespace MediaBrowser.Providers.MediaInfo
return filename; return filename;
} }
private static IEnumerable<string> SubtitleExtensions
{
get
{
return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami", ".vtt" };
}
}
public static IEnumerable<string> GetSubtitleFiles(Video video, IDirectoryService directoryService, IFileSystem fileSystem, bool clearCache)
{
var containingPath = video.ContainingFolderPath;
if (string.IsNullOrEmpty(containingPath))
{
throw new ArgumentException(string.Format("Cannot search for items that don't have a path: {0} {1}", video.Name, video.Id));
}
var files = fileSystem.GetFilePaths(containingPath, clearCache);
var videoFileNameWithoutExtension = fileSystem.GetFileNameWithoutExtension(video.Path);
return files.Where(i =>
{
var extension = Path.GetExtension(i);
if (SubtitleExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
var fileNameWithoutExtension = fileSystem.GetFileNameWithoutExtension(i);
if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
});
}
} }
} }

@ -16,8 +16,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
namespace MediaBrowser.Providers.Subtitles namespace MediaBrowser.Providers.Subtitles
@ -30,17 +30,19 @@ namespace MediaBrowser.Providers.Subtitles
private readonly ILibraryMonitor _monitor; private readonly ILibraryMonitor _monitor;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _config;
public event EventHandler<SubtitleDownloadEventArgs> SubtitlesDownloaded; public event EventHandler<SubtitleDownloadEventArgs> SubtitlesDownloaded;
public event EventHandler<SubtitleDownloadFailureEventArgs> SubtitleDownloadFailure; public event EventHandler<SubtitleDownloadFailureEventArgs> SubtitleDownloadFailure;
public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager) public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IServerConfigurationManager config)
{ {
_logger = logger; _logger = logger;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_monitor = monitor; _monitor = monitor;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_config = config;
} }
public void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders) public void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders)
@ -102,6 +104,11 @@ namespace MediaBrowser.Providers.Subtitles
return results.SelectMany(i => i).ToArray(); return results.SelectMany(i => i).ToArray();
} }
private SubtitleOptions GetOptions()
{
return _config.GetConfiguration<SubtitleOptions>("subtitles");
}
public async Task DownloadSubtitles(Video video, public async Task DownloadSubtitles(Video video,
string subtitleId, string subtitleId,
CancellationToken cancellationToken) CancellationToken cancellationToken)
@ -109,49 +116,37 @@ namespace MediaBrowser.Providers.Subtitles
var parts = subtitleId.Split(new[] { '_' }, 2); var parts = subtitleId.Split(new[] { '_' }, 2);
var provider = GetProvider(parts.First()); var provider = GetProvider(parts.First());
var saveInMediaFolder = GetOptions().SaveSubtitlesInMediaFolders && video.SupportsLocalMetadata;
try try
{ {
var response = await GetRemoteSubtitles(subtitleId, cancellationToken).ConfigureAwait(false); var response = await GetRemoteSubtitles(subtitleId, cancellationToken).ConfigureAwait(false);
using (var stream = response.Stream) using (var stream = response.Stream)
{ {
var savePath = Path.Combine(_fileSystem.GetDirectoryName(video.Path), using (var memoryStream = new MemoryStream())
_fileSystem.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower());
if (response.IsForced)
{ {
savePath += ".forced"; await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
} memoryStream.Position = 0;
savePath += "." + response.Format.ToLower();
_logger.Info("Saving subtitles to {0}", savePath); var savePaths = new List<string>();
var saveFileName = _fileSystem.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower();
_monitor.ReportFileSystemChangeBeginning(savePath); if (response.IsForced)
{
saveFileName += ".forced";
}
try saveFileName += "." + response.Format.ToLower();
{
//var isText = MediaStream.IsTextFormat(response.Format);
using (var fs = _fileSystem.GetFileStream(savePath, FileOpenMode.Create, FileAccessMode.Write, if (saveInMediaFolder)
FileShareMode.Read, true))
{ {
await stream.CopyToAsync(fs).ConfigureAwait(false); savePaths.Add(Path.Combine(video.ContainingFolderPath, saveFileName));
} }
EventHelper.FireEventIfNotNull(SubtitlesDownloaded, this, new SubtitleDownloadEventArgs savePaths.Add(Path.Combine(video.GetInternalMetadataPath(), saveFileName));
{
Item = video,
Format = response.Format,
Language = response.Language,
IsForced = response.IsForced,
Provider = provider.Name
}, _logger); await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false);
}
finally
{
_monitor.ReportFileSystemChangeComplete(savePath, false);
} }
} }
} }
@ -173,6 +168,46 @@ namespace MediaBrowser.Providers.Subtitles
} }
} }
private async Task TrySaveToFiles(Stream stream, List<string> savePaths)
{
Exception exceptionToThrow = null;
foreach (var savePath in savePaths)
{
_logger.Info("Saving subtitles to {0}", savePath);
_monitor.ReportFileSystemChangeBeginning(savePath);
try
{
using (var fs = _fileSystem.GetFileStream(savePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
{
await stream.CopyToAsync(fs).ConfigureAwait(false);
}
return;
}
catch (Exception ex)
{
if (exceptionToThrow == null)
{
exceptionToThrow = ex;
}
}
finally
{
_monitor.ReportFileSystemChangeComplete(savePath, false);
}
stream.Position = 0;
}
if (exceptionToThrow != null)
{
throw exceptionToThrow;
}
}
public Task<RemoteSubtitleInfo[]> SearchSubtitles(Video video, string language, bool? isPerfectMatch, CancellationToken cancellationToken) public Task<RemoteSubtitleInfo[]> SearchSubtitles(Video video, string language, bool? isPerfectMatch, CancellationToken cancellationToken)
{ {
if (video.LocationType != LocationType.FileSystem || if (video.LocationType != LocationType.FileSystem ||

@ -1,3 +1,3 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("3.2.36.1")] [assembly: AssemblyVersion("3.2.36.2")]

Loading…
Cancel
Save